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 all 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_processedChunksPath = m_outputPath / "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_outputPath);
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 @@ -83,7 +83,7 @@ fs::path prepareAudioOutputPath(const fs::path &inputPath) {

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 @@ -92,7 +92,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
2 changes: 1 addition & 1 deletion MediaProcessor/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ int main(int argc, char* argv[]) {
}

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

}
181 changes: 43 additions & 138 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import json
import logging
import os
import re
import subprocess
from urllib.parse import urlparse
from pathlib import Path
from backend.utils import Utils
from backend.media_handler import MediaHandler

import yt_dlp
from flask import Flask, jsonify, render_template, request, send_from_directory, url_for
from typing import Union
from backend.response_handler import ResponseHandler

from flask import Flask, render_template, request, send_from_directory, url_for, Response

"""
This is the backend of the Fast Music Remover tool.
Expand All @@ -23,191 +25,94 @@
- A JSON response with the URL to the processed video is returned to the frontend, allowing the user to view or download the final output.
"""

app = Flask(__name__, template_folder="templates")

app = Flask(__name__)
BASE_DIR = Path(__file__).parent.resolve()
# Construct the path to config.json
config_path = str((BASE_DIR / "config.json").resolve())

# Load config and set paths
with open("config.json") as config_file:
# Load the config file
with open(config_path) as config_file:
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")))

DEEPFILTERNET_PATH = os.path.abspath(config["deep_filter_path"])
FFMPEG_PATH = os.path.abspath(config["ffmpeg_path"])

DOWNLOADS_PATH = str((BASE_DIR/config["downloads_path"]).resolve())
UPLOADS_PATH = str((BASE_DIR / config.get("uploads_path", "uploads")).resolve())
DEEPFILTERNET_PATH = str((BASE_DIR / 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):
if not os.path.exists(directory):
os.makedirs(directory)

@staticmethod
def remove_files_by_base(base_filename):
"""Removes any existing files with the same base name."""
base_path = os.path.join(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):
logging.info(f"Removing old file: {path}")
os.remove(path)

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

@staticmethod
def validate_url(url):
"""Basic URL validation."""
parsed_url = urlparse(url)
return all([parsed_url.scheme, parsed_url.netloc])
#Log for dev reference:
logging.info(f"Config path: {config_path}\nDownlad path: {DOWNLOADS_PATH} \nUpload path: {UPLOADS_PATH}\nDeepfile:{DEEPFILTERNET_PATH}")


Utils.ensure_dir_exists(app.config["UPLOAD_FOLDER"])


class MediaHandler:
"""Class to handle video download and processing logic."""

@staticmethod
def download_media(url):
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": os.path.join(app.config["UPLOAD_FOLDER"], 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 = os.path.join(app.config["UPLOAD_FOLDER"], sanitized_title + "." + merged_ext)
return os.path.abspath(video_file)

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

@staticmethod
def process_with_media_processor(video_path):
"""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
for line in result.stdout.splitlines():
if "Video processed successfully" in line:
processed_video_path = line.split(": ", 1)[1].strip()

# Remove any surrounding quotes
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)
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():
def index() -> Union[dict, str]:
if request.method == "POST":
url = request.form.get("url")
file = request.files.get("file")

# Ensure only one of URL or file is provided
if not url and not file:
return jsonify({"status": "error", "message": "Please provide a URL or upload a file."})
return ResponseHandler.generate_error_response("Please provide a URL or upload a file.", 400)
if url and file:
return jsonify({"status": "error", "message": "Please provide only one input: either a URL or a file."})
return ResponseHandler.generate_error_response("Please provide only one input: either a URL or a file.", 400)

# Handle URL case
if url:
if not Utils.validate_url(url):
return jsonify({"status": "error", "message": "Invalid URL provided."})
return ResponseHandler.generate_error_response("Invalid URL provided.", 400)

video_path = MediaHandler.download_media(url)
video_path = MediaHandler.download_media(url, UPLOADS_PATH)
if not video_path:
return jsonify(
{"status": "error", "message": "Failed to download video: URL may be invalid or restricted."}
)
return ResponseHandler.generate_error_response("Failed to download video: URL may be invalid or restricted.", 500)

# Handle file upload case
elif file:
sanitized_filename = Utils.sanitize_filename(file.filename)
video_path = os.path.join(app.config["UPLOAD_FOLDER"], sanitized_filename)
video_path = str((Path(UPLOADS_PATH) / sanitized_filename).resolve())
try:
file.save(video_path)
except Exception as e:
logging.error(f"Error saving uploaded file: {e}")
return jsonify({"status": "error", "message": "Failed to save uploaded file."})
return ResponseHandler.generate_error_response("Failed to save uploaded file.", 500)

# Process video
processed_video_path = MediaHandler.process_with_media_processor(video_path)
processed_video_path = MediaHandler.process_with_media_processor(video_path, BASE_DIR)
if not processed_video_path:
return jsonify({"status": "error", "message": "Failed to process video."})
return ResponseHandler.generate_error_response("Failed to process video.", 500)

return jsonify(
return ResponseHandler.generate_success_response(
"Video processed successfully.",
{
"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)
}
)

return render_template("index.html")



@app.route("/video/<filename>")
def serve_video(filename):
def serve_video(filename: str) -> Response:
try:
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):
logging.error(f"File does not exist: {abs_file_path}")
return jsonify({"status": "error", "message": "File not found."}), 404
if not Path(abs_file_path).exists():
logging.generate_error_response(f"File does not exist: {abs_file_path}")
return ResponseHandler.generate_error_response("File not found.", 404)


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

return ResponseHandler.generate_error_response("Failed to serve video.", 500)

if __name__ == "__main__":
app.run(port=8080, debug=True)
omeryusufyagci marked this conversation as resolved.
Show resolved Hide resolved
app.run(port=8080, debug=True)
Empty file added backend/__init__.py
Empty file.
Loading