From 27053d7c3030393a03580e99320b6cb9f991c8d6 Mon Sep 17 00:00:00 2001 From: prakash_ramanathan Date: Thu, 31 Oct 2024 23:31:24 +0530 Subject: [PATCH] Make the requested changes for PR #34 issue *(deletion) Original app.py *(Updation) requirements *(modification) make changes to preserve the CLI nature and only make the backend changes --- MediaProcessor/src/main.cpp | 60 +++-------- app.py | 200 ------------------------------------ backend/media_handler.py | 33 ++---- requirements.txt | 4 + 4 files changed, 31 insertions(+), 266 deletions(-) delete mode 100644 app.py diff --git a/MediaProcessor/src/main.cpp b/MediaProcessor/src/main.cpp index 6c207a1..953051f 100644 --- a/MediaProcessor/src/main.cpp +++ b/MediaProcessor/src/main.cpp @@ -4,18 +4,17 @@ #include #include "AudioProcessor.h" +#include "CommunicationHandler.h" #include "ConfigManager.h" #include "Utils.h" #include "VideoProcessor.h" namespace fs = std::filesystem; -using json = nlohmann::json; using namespace MediaProcessor; -int main() { +int main(int argc, char* argv[]) { /** - * @brief Processes a video file to isolate vocals and merge them back with the original video - * using JSON input and output. + * @brief Processes a video file to isolate vocals and merge them back with the original video. * * This program removes music, sound effects, and noise while retaining clear vocals. * @@ -25,47 +24,24 @@ int main() { * 3. Process the audio in chunks with parallel processing. * 4. Merge the isolated vocals back with the original video. * - * @param json_input This input is received from the input cin. + * @param argc Number of command-line arguments. + * @param argv Array of command-line argument strings. * @return Exit status code (0 for success, non-zero for failure). * - * Usage: JSON input via stdin + * Usage: */ - - // 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(); - config_file_path = data.at("config_file_path").get(); - - 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; + if (argc != 3) { + std::string usageMessage = + "Usage: " + std::string(argv[0]) + " "; + CommunicationHandler::handleError(usageMessage); } - fs::path inputMediaPath = fs::absolute(video_file_path); - fs::path configFilePath = fs::absolute(config_file_path); + fs::path inputMediaPath = fs::absolute(argv[1]); + fs::path configFilePath = fs::absolute(argv[2]); - // Load the configuration ConfigManager& configManager = ConfigManager::getInstance(); - if (!configManager.loadConfig(configFilePath)) { - std::cerr << "Error: Could not load configuration from " << configFilePath << std::endl; + if (!configManager.loadConfig("config.json")) { + std::cerr << "Error: Could not load configuration." << std::endl; return 1; } @@ -78,15 +54,11 @@ int main() { } VideoProcessor videoProcessor(inputMediaPath, extractedVocalsPath, processedMediaPath); - if (!videoProcessor.mergeMedia()) { std::cerr << "Failed to merge audio and video." << std::endl; return 1; } - // 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; + + std::cout << "Video processed successfully: " << processedMediaPath << std::endl; return 0; } diff --git a/app.py b/app.py deleted file mode 100644 index 1a9e9f7..0000000 --- a/app.py +++ /dev/null @@ -1,200 +0,0 @@ -import json -import logging -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 - -""" -This is the backend of the Fast Music Remover tool. - -Workflow: -1) Download YouTube video via `yt-dlp` -2) Send a processing request to the `MediaProcessor` C++ binary, which uses DeepFilterNet for fast filtering -3) Serve the processed video on the frontend - -""" - -app = Flask(__name__) - -# Load config and set paths -with open("config.json") as config_file: - config = json.load(config_file) - -# Define base paths using absolute references - - -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 -app.config["UPLOAD_FOLDER"] = UPLOADS_PATH - - -class Utils: - """Utility class for common operations like file cleanup and sanitization.""" - - @staticmethod - def ensure_dir_exists(directory: str) -> None: - if not Path(directory).exists(): - Path(directory).mkdir() - - @staticmethod - def remove_files_by_base(base_filename: str) -> None: - """Removes any existing files with the same base name.""" - 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 Path(path).exists(): - logging.info(f"Removing old file: {path}") - Path(path).unlink() - - @staticmethod - 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: str) -> bool: - """Basic URL validation""" - parsed_url = urlparse(url) - return all([parsed_url.scheme, parsed_url.netloc]) - - -Utils.ensure_dir_exists(app.config["UPLOAD_FOLDER"]) - - -class MediaHandler: - """Class to handle video download and processing logic.""" - - @staticmethod - def download_media(url: str) -> typing.Optional[str]: - try: - # Extract media info first to sanitize title - with yt_dlp.YoutubeDL() as ydl: - info_dict = ydl.extract_info(url, download=False) - base_title = info_dict["title"] - sanitized_title = Utils.sanitize_filename(base_title) - - ydl_opts = { - "format": "bestvideo+bestaudio/best", - "outtmpl": str(Path(app.config["UPLOAD_FOLDER"])/f"{sanitized_title}.%(ext)s"), - "noplaylist": True, - "keepvideo": True, - "n_threads": 6, - } - - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - result = ydl.extract_info(url, download=True) - - # Determine file extension - if "requested_formats" in result: - merged_ext = result["ext"] - else: - merged_ext = result.get("ext", "mp4") - - # Return the sanitized file path for processing - 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: str) -> typing.Optional[str]: - """Run the C++ MediaProcessor binary with the video path""" - - try: - logging.info(f"Processing video at path: {video_path}") - - result = subprocess.run( - ["./MediaProcessor/build/MediaProcessor", str(video_path)], capture_output=True, text=True - ) - - if result.returncode != 0: - logging.error(f"Error processing video: {result.stderr}") - return None - - # Parse the output to get the processed video path (TODO: encapsulate) - for line in result.stdout.splitlines(): - if "Video processed successfully" in line: - processed_video_path = line.split(": ", 1)[1].strip() - - # 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 = str(Path(processed_video_path).resolve()) - logging.info(f"Processed video path returned: {processed_video_path}") - return processed_video_path - - return None - except Exception as e: - logging.error(f"Error running C++ binary: {e}") - return None - - -@app.route("/", methods=["GET", "POST"]) -def index()-> typing.Union[flask.Response, str]: - if request.method == "POST": - url = request.form["url"] - - if not Utils.validate_url(url): - return jsonify({"status": "error", "message": "Invalid URL provided."}) - - if url: - video_path = MediaHandler.download_media(url) - - if not video_path: - return jsonify({"status": "error", "message": "Failed to download video."}) - - processed_video_path = MediaHandler.process_with_media_processor(video_path) - - if processed_video_path: - return jsonify( - { - "status": "completed", - "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/") -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 = 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 Path(abs_file_path).exists(): - logging.error(f"File does not exist: {abs_file_path}") - return jsonify({"status": "error", "message": "File not found."}), 404 - - # Serve the file from the uploads directory - return send_from_directory(directory=app.config["UPLOAD_FOLDER"], path=filename, mimetype="video/mp4") - except Exception as e: - logging.error(f"Error serving video: {e}") - return jsonify({"status": "error", "message": "Failed to serve video."}), 500 - - -if __name__ == "__main__": - app.run(host='0.0.0.0', port=9090, debug=True) diff --git a/backend/media_handler.py b/backend/media_handler.py index 51b7834..dc80bce 100644 --- a/backend/media_handler.py +++ b/backend/media_handler.py @@ -50,18 +50,11 @@ def process_with_media_processor(video_path: str, base_directory:Union[Path, str """Run the C++ MediaProcessor binary with the video path""" try: logging.info(f"Processing video at path: {video_path}") - - input_data = { - "video_file_path": video_path, - "config_file_path": config_path - } base_directory = Path(base_directory) + result = subprocess.run( - [str(base_directory / "MediaProcessor/build/MediaProcessor")], - input= ResponseHandler.core_data_passer("The input data",input_data), - capture_output=True, - text=True + [str(base_directory / "MediaProcessor/build/MediaProcessor"), str(video_path), config_path], capture_output=True, text=True ) if result.returncode != 0: @@ -72,20 +65,16 @@ def process_with_media_processor(video_path: str, base_directory:Union[Path, str print(result.stderr) # Parse the output to get the processed video path (TODO: encapsulate) - try: - response = json.loads(result.stdout) - if response.get("status") == "success": - processed_video_path = response["data"]["processed_video_path"] - # Log the processed video path - logging.info(f"Processed video path returned: {processed_video_path}") - # Return success response - return processed_video_path - - else: - logging.error("Unexpected response from MediaProcessor") + for line in result.stdout.splitlines(): + if "Video processed successfully" in line: + processed_video_path = line.split(": ", 1)[1].strip() - except json.JSONDecodeError: - logging.error("Failed to parse JSON from MediaProcessor output") + # 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 = str(Path(processed_video_path).resolve()) + logging.info(f"Processed video path returned: {processed_video_path}") + return processed_video_path return None except Exception as e: diff --git a/requirements.txt b/requirements.txt index 8651de8..9483d9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ gevent-websocket==0.10.1 greenlet==3.1.0 h11==0.14.0 idna==3.8 +iniconfig==2.0.0 itsdangerous==2.2.0 Jinja2==3.1.4 MarkupSafe==2.1.5 @@ -27,12 +28,15 @@ mypy-extensions==1.0.0 packaging==24.1 pathspec==0.12.1 platformdirs==4.3.6 +pluggy==1.5.0 pycodestyle==2.12.1 pycryptodomex==3.20.0 pyflakes==3.2.0 +pytest==8.3.3 python-engineio==4.9.1 python-socketio==5.11.4 requests==2.32.3 +setuptools==75.2.0 simple-websocket==1.0.0 tomli==2.0.2 typing_extensions==4.12.2