Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reorganize be v1.0 #54

Open
wants to merge 18 commits into
base: 34-improvement-backend-improvements-for-typing-tests-modularity-and-pathlib
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bcf29af
add type hints, replace os with pathlib
prakash-2002-ramanathan Oct 19, 2024
9a5b773
Change directory structure, create backend folder
prakash-2002-ramanathan Oct 19, 2024
3291b21
Ensure consistent JSON communication
prakash-2002-ramanathan Oct 22, 2024
24f1799
Add tests, refactor backend and improve logs
prakash-2002-ramanathan Oct 23, 2024
7b8057b
Merge branch 'main' into reorganize-BE-v1.0
prakash-2002-ramanathan Oct 23, 2024
27053d7
Make the requested changes for PR #34 issue
prakash-2002-ramanathan Oct 31, 2024
692c722
Make final changes to give a proper proper
prakash-2002-ramanathan Oct 31, 2024
8ae4673
Merge branch 'main' into reorganize-BE-v1.0
prakash-2002-ramanathan Oct 31, 2024
af3121e
Move app.py outside of the backedn directory
prakash-2002-ramanathan Nov 6, 2024
49adcd4
remove changs in the core, fix raw print messges
prakash-2002-ramanathan Nov 6, 2024
fe80d9a
Address the todos
prakash-2002-ramanathan Nov 6, 2024
27a0c4f
Add more tests, verify tests are working, improve
prakash-2002-ramanathan Nov 8, 2024
95d411c
Change response_handler and testing
prakash-2002-ramanathan Nov 11, 2024
3e5238d
Fix the tests and the index.html to use pytest
prakash-2002-ramanathan Nov 17, 2024
5401bb0
change port to 8080
prakash-2002-ramanathan Nov 17, 2024
cfc85f3
Merge branch 'main' into reorganize-BE-v1.0
prakash-2002-ramanathan Nov 17, 2024
fd7bfc2
Fix the new frontend with the backend
prakash-2002-ramanathan Nov 17, 2024
a8d9f6e
Change port to 8080
prakash-2002-ramanathan Nov 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions MediaProcessor/src/AudioProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ AudioProcessor::AudioProcessor(const fs::path& inputVideoPath, const fs::path& o
m_processedChunksDir = m_outputDir / "processed_chunks";

m_numChunks = ConfigManager::getInstance().getOptimalThreadCount();
std::cout << "INFO: using " << m_numChunks << " threads." << std::endl;
std::cerr << "INFO: using " << m_numChunks << " threads." << std::endl;
}

bool AudioProcessor::isolateVocals() {
Expand All @@ -41,8 +41,8 @@ bool AudioProcessor::isolateVocals() {
Utils::ensureDirectoryExists(m_outputDir);
Utils::removeFileIfExists(m_outputAudioPath);

std::cout << "Input video path: " << m_inputVideoPath << std::endl;
std::cout << "Output audio path: " << m_outputAudioPath << std::endl;
std::cerr << "Input video path: " << m_inputVideoPath << std::endl;
std::cerr << "Output audio path: " << m_outputAudioPath << std::endl;

if (!extractAudio()) {
return false;
Expand Down Expand Up @@ -92,7 +92,7 @@ bool AudioProcessor::extractAudio() {
return false;
}

std::cout << "Audio extracted successfully to: " << m_outputAudioPath << std::endl;
std::cerr << "Audio extracted successfully to: " << m_outputAudioPath << std::endl;
return true;
}

Expand Down
4 changes: 2 additions & 2 deletions MediaProcessor/src/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ bool ensureDirectoryExists(const std::filesystem::path &path) {
*/

if (!std::filesystem::exists(path)) {
std::cout << "Output directory does not exist, creating it: " << path << std::endl;
std::cerr << "Output directory does not exist, creating it: " << path << std::endl;
std::filesystem::create_directories(path);
return true;
}
Expand All @@ -67,7 +67,7 @@ bool ensureDirectoryExists(const std::filesystem::path &path) {

bool removeFileIfExists(const std::filesystem::path &filePath) {
if (std::filesystem::exists(filePath)) {
std::cout << "File already exists, removing it: " << filePath << std::endl;
std::cerr << "File already exists, removing it: " << filePath << std::endl;
std::filesystem::remove(filePath);
return true;
}
Expand Down
6 changes: 3 additions & 3 deletions MediaProcessor/src/VideoProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ VideoProcessor::VideoProcessor(const fs::path& videoPath, const fs::path& audioP
bool VideoProcessor::mergeMedia() {
Utils::removeFileIfExists(m_outputPath); // to avoid interactive ffmpeg prompt

std::cout << "Merging video and audio..." << std::endl;
std::cerr << "Merging video and audio..." << std::endl;

// Prepare FFmpeg command
CommandBuilder cmd;
Expand All @@ -38,15 +38,15 @@ bool VideoProcessor::mergeMedia() {

std::string ffmpegCommand = cmd.build();

std::cout << "Running FFmpeg command: " << ffmpegCommand << std::endl;
std::cerr << "Running FFmpeg command: " << ffmpegCommand << std::endl;
bool success = Utils::runCommand(ffmpegCommand);

if (!success) {
std::cerr << "Error: Failed to merge audio and video using FFmpeg." << std::endl;
return false;
}

std::cout << "Merging completed successfully." << std::endl;
std::cerr << "Merging completed successfully." << std::endl;

return true;
}
Expand Down
57 changes: 44 additions & 13 deletions MediaProcessor/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <filesystem>
#include <iostream>
#include <nlohmann/json.hpp>
#include <string>

#include "AudioProcessor.h"
Expand All @@ -8,12 +9,13 @@
#include "VideoProcessor.h"

namespace fs = std::filesystem;

using json = nlohmann::json;
using namespace MediaProcessor;

int main(int argc, char* argv[]) {
int main() {
/**
* @brief Processes a video file to isolate vocals and merge them back with the original video.
* @brief Processes a video file to isolate vocals and merge them back with the original video
* using JSON input and output.
*
* This program removes music, sound effects, and noise while retaining clear vocals.
*
Expand All @@ -23,22 +25,47 @@ int main(int argc, char* argv[]) {
* 3. Process the audio in chunks with parallel processing.
* 4. Merge the isolated vocals back with the original video.
*
* @param argc Number of command-line arguments.
* @param argv Array of command-line argument strings.
* @param json_input This input is received from the input cin.
* @return Exit status code (0 for success, non-zero for failure).
*
* Usage: <executable> <video_file_path>
* Usage: JSON input via stdin
*/

if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <video_file_path>" << std::endl;
// Read JSON input from stdin
std::string json_input;
std::getline(std::cin, json_input);

json input;
try {
input = json::parse(json_input);
} catch (const json::parse_error& e) {
std::cerr << "Invalid JSON input." << e.what() << std::endl;
return 1;
}

// Extract video file path and config file path from JSON
std::string video_file_path, config_file_path;
try {
json data = input.at("data");
video_file_path = data.at("video_file_path").get<std::string>();
config_file_path = data.at("config_file_path").get<std::string>();

std::cerr << "\nParameters received by Media Handler\n"
<< "video_file_path: " << video_file_path << "\n"
<< "config_file_path: " << config_file_path << "\n";

} catch (const json::exception& e) {
std::cerr << "Missing or invalid input fields." << e.what() << std::endl;
return 1;
}
fs::path inputMediaPath = fs::absolute(argv[1]);

fs::path inputMediaPath = fs::absolute(video_file_path);
fs::path configFilePath = fs::absolute(config_file_path);

// Load the configuration
ConfigManager& configManager = ConfigManager::getInstance();
if (!configManager.loadConfig("config.json")) {
std::cerr << "Error: Could not load configuration." << std::endl;
if (!configManager.loadConfig(configFilePath)) {
std::cerr << "Error: Could not load configuration from " << configFilePath << std::endl;
return 1;
}

Expand All @@ -51,11 +78,15 @@ int main(int argc, char* argv[]) {
}

VideoProcessor videoProcessor(inputMediaPath, extractedVocalsPath, processedMediaPath);

if (!videoProcessor.mergeMedia()) {
std::cerr << "Failed to merge audio and video." << std::endl;
return 1;
}

std::cout << "Video processed successfully: " << processedMediaPath << std::endl;
// Output JSON success response
json success_response = {{"status", "success"},
{"message", "Video processed successfully."},
{"data", {{"processed_video_path", processedMediaPath.string()}}}};
std::cout << success_response.dump() << std::endl;
return 0;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please run the clang (with the file available on project root, i.e. .clang-format) for MediaProcessor/src or setup the pre-commit hooks which would do this for you

61 changes: 34 additions & 27 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import os
import re
import subprocess
import typing
import flask
from pathlib import Path
from urllib.parse import urlparse


import yt_dlp
from flask import Flask, jsonify, render_template, request, send_from_directory, url_for

Expand All @@ -25,13 +29,15 @@
config = json.load(config_file)

# Define base paths using absolute references
BASE_DIR = os.path.abspath(os.path.dirname(__file__))

DOWNLOADS_PATH = os.path.abspath(config["downloads_path"])
UPLOADS_PATH = os.path.abspath(config.get("uploads_path", os.path.join(BASE_DIR, "uploads"))) # Defaults to uploads/

DEEPFILTERNET_PATH = os.path.abspath(config["deep_filter_path"])
FFMPEG_PATH = os.path.abspath(config["ffmpeg_path"])
BASE_DIR = str(Path(__file__).parent.resolve())

DOWNLOADS_PATH = str(Path(config["downloads_path"]).resolve())
UPLOADS_PATH = str(Path(config.get("uploads_path", Path(BASE_DIR)/"uploads")).resolve()) # Defaults to uploads/

DEEPFILTERNET_PATH = str(Path(config["deep_filter_path"]).resolve())
FFMPEG_PATH = str(Path(config["ffmpeg_path"]).resolve())


os.environ["DEEPFILTERNET_PATH"] = DEEPFILTERNET_PATH
Expand All @@ -42,27 +48,27 @@ class Utils:
"""Utility class for common operations like file cleanup and sanitization."""

@staticmethod
def ensure_dir_exists(directory):
if not os.path.exists(directory):
os.makedirs(directory)
def ensure_dir_exists(directory: str) -> None:
if not Path(directory).exists():
Path(directory).mkdir()

@staticmethod
def remove_files_by_base(base_filename):
def remove_files_by_base(base_filename: str) -> None:
"""Removes any existing files with the same base name."""
base_path = os.path.join(app.config["UPLOAD_FOLDER"], base_filename)
base_path = Path(app.config["UPLOAD_FOLDER"])/ base_filename
file_paths = [base_path + ".webm", base_path + "_isolated_audio.wav", base_path + "_processed_video.mp4"]
for path in file_paths:
if os.path.exists(path):
if Path(path).exists():
logging.info(f"Removing old file: {path}")
os.remove(path)
Path(path).unlink()

@staticmethod
def sanitize_filename(filename):
def sanitize_filename(filename: str) -> str:
"""Replace non-alphanumerics (except periods and underscores) with underscores."""
return re.sub(r"[^a-zA-Z0-9._-]", "_", filename)

@staticmethod
def validate_url(url):
def validate_url(url: str) -> bool:
"""Basic URL validation"""
parsed_url = urlparse(url)
return all([parsed_url.scheme, parsed_url.netloc])
Expand All @@ -75,7 +81,7 @@ class MediaHandler:
"""Class to handle video download and processing logic."""

@staticmethod
def download_media(url):
def download_media(url: str) -> typing.Optional[str]:
try:
# Extract media info first to sanitize title
with yt_dlp.YoutubeDL() as ydl:
Expand All @@ -85,7 +91,7 @@ def download_media(url):

ydl_opts = {
"format": "bestvideo+bestaudio/best",
"outtmpl": os.path.join(app.config["UPLOAD_FOLDER"], sanitized_title + ".%(ext)s"),
"outtmpl": str(Path(app.config["UPLOAD_FOLDER"])/f"{sanitized_title}.%(ext)s"),
"noplaylist": True,
"keepvideo": True,
"n_threads": 6,
Expand All @@ -101,15 +107,15 @@ def download_media(url):
merged_ext = result.get("ext", "mp4")

# Return the sanitized file path for processing
video_file = os.path.join(app.config["UPLOAD_FOLDER"], sanitized_title + "." + merged_ext)
return os.path.abspath(video_file)
video_file = Path(app.config["UPLOAD_FOLDER"])/f"{sanitized_title}.{merged_ext}"
return str(Path(video_file).resolve())

except Exception as e:
logging.error(f"Error downloading video: {e}")
return None

@staticmethod
def process_with_media_processor(video_path):
def process_with_media_processor(video_path: str) -> typing.Optional[str]:
"""Run the C++ MediaProcessor binary with the video path"""

try:
Expand All @@ -131,7 +137,7 @@ def process_with_media_processor(video_path):
# Remove any surrounding quotes (TODO: encapsulate)
if processed_video_path.startswith('"') and processed_video_path.endswith('"'):
processed_video_path = processed_video_path[1:-1]
processed_video_path = os.path.abspath(processed_video_path)
processed_video_path = str(Path(processed_video_path).resolve())
logging.info(f"Processed video path returned: {processed_video_path}")
return processed_video_path

Expand All @@ -142,7 +148,7 @@ def process_with_media_processor(video_path):


@app.route("/", methods=["GET", "POST"])
def index():
def index()-> typing.Union[flask.Response, str]:
if request.method == "POST":
url = request.form["url"]

Expand All @@ -161,24 +167,25 @@ def index():
return jsonify(
{
"status": "completed",
"video_url": url_for("serve_video", filename=os.path.basename(processed_video_path)),
"video_url": url_for("serve_video", filename=Path(processed_video_path).name),
}
)

else:
return jsonify({"status": "error", "message": "Failed to process video."})

return render_template("index.html")


@app.route("/video/<filename>")
def serve_video(filename):
def serve_video(filename: str) -> typing.Union[flask.Response, tuple[flask.Response, int]]:
try:
# Construct the abs path for the file to be served (TODO: encapsulate)
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
abs_file_path = os.path.abspath(file_path)
file_path = Path(app.config["UPLOAD_FOLDER"]) / filename
abs_file_path = str(Path(file_path).resolve())
logging.debug(f"Attempting to serve video from path: {abs_file_path}")

if not os.path.exists(abs_file_path):
if not Path(abs_file_path).exists():
logging.error(f"File does not exist: {abs_file_path}")
return jsonify({"status": "error", "message": "File not found."}), 404

Expand All @@ -190,4 +197,4 @@ def serve_video(filename):


if __name__ == "__main__":
app.run(port=8080, debug=True)
omeryusufyagci marked this conversation as resolved.
Show resolved Hide resolved
app.run(host='0.0.0.0', port=9090, debug=True)
Loading