Skip to content

Commit

Permalink
Add tests, refactor backend and improve logs
Browse files Browse the repository at this point in the history
*Add: Add unit tests for all static functions in utils.py and
media_handler.py

*improve: Improve the logs, remove irrelevant logs

*refactor: remove unused import statements, change structure

<Signed off by Prakash([email protected])>
  • Loading branch information
prakash-2002-ramanathan committed Oct 23, 2024
1 parent 3291b21 commit 24f1799
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 37 deletions.
8 changes: 1 addition & 7 deletions MediaProcessor/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ int main() {
std::string json_input;
std::getline(std::cin, json_input);

std::cerr << "The subprocess has been started";

json input;
try {
input = json::parse(json_input);
Expand All @@ -52,7 +50,7 @@ int main() {
video_file_path = data.at("video_file_path").get<std::string>();
config_file_path = data.at("config_file_path").get<std::string>();

std::cerr << "\nThese is the passed parameter\n\n"
std::cerr << "\nParameters received by Media Handler\n"
<< "video_file_path: " << video_file_path << "\n"
<< "config_file_path: " << config_file_path << "\n";

Expand All @@ -79,17 +77,13 @@ int main() {
return 1;
}

std::cerr << "Before Merging" << std::endl;
VideoProcessor videoProcessor(inputMediaPath, extractedVocalsPath, processedMediaPath);

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

std::cerr << "Before sending the JSON response" << std::endl;

json success_response = {{"status", "success"},
{"message", "Video processed successfully."},
{"data", {{"processed_video_path", processedMediaPath.string()}}}};
Expand Down
22 changes: 5 additions & 17 deletions backend/app.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import json
import logging
import os
import typing
import flask
from pathlib import Path
from urllib.parse import urlparse
from utils import Utils
from media_handler import MediaHandler

from typing import Union
from response_handler import ResponseHandler

from flask import Flask, jsonify, render_template, request, send_from_directory, url_for, Response
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 @@ -26,7 +23,6 @@
app = Flask(__name__, template_folder="../templates")

BASE_DIR = Path(__file__).parent.parent.resolve()

# Construct the path to config.json
config_path = str((BASE_DIR / "config.json").resolve())

Expand All @@ -35,20 +31,17 @@
with open(config_path) as config_file:
config = json.load(config_file)

# Define base paths using absolute references

# Define base paths using absolute references
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())


print(f"Config path: {config_path}\n Downlad path: {DOWNLOADS_PATH} \nUpload path: {UPLOADS_PATH}\n deepfile:{DEEPFILTERNET_PATH}")

os.environ["DEEPFILTERNET_PATH"] = DEEPFILTERNET_PATH
app.config["UPLOAD_FOLDER"] = UPLOADS_PATH


#Log for dev reference
print(f"Config path: {config_path}\nDownlad path: {DOWNLOADS_PATH} \nUpload path: {UPLOADS_PATH}\nDeepfile:{DEEPFILTERNET_PATH}")

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

Expand All @@ -69,17 +62,13 @@ def index()-> Union[Response, str]:
processed_video_path = MediaHandler.process_with_media_processor(video_path,BASE_DIR,config_path)
#Since backend is in a different directory compared to config, am explicity passing the config_path

print(f"The Media handerler has successfully performed the operation"+processed_video_path)


if processed_video_path:
return ResponseHandler.success(
"Video processed successfully.",
{
"video_url": url_for("serve_video", filename=Path(processed_video_path).name)
}
)

else:
return ResponseHandler.error("Failed to process video.", 500)

Expand All @@ -100,10 +89,9 @@ def serve_video(filename: str) -> Union[Response, tuple[Response, int]]:
# 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 ResponseHandler.error("Failed to serve video.", 500)

if __name__ == "__main__":
app.run(host='0.0.0.0', port=9090, debug=True)
app.run(port=9090, debug=True)
17 changes: 7 additions & 10 deletions backend/media_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
import yt_dlp
from utils import Utils
from typing import Optional, Union
from flask import Response
from response_handler import ResponseHandler

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

@staticmethod
def download_media(url: str, base_directory: str) -> Optional[str]:
def download_media(url: str, base_directory: Union[Path, str]) -> Optional[str]:
try:
# Extract media info first to sanitize title
with yt_dlp.YoutubeDL() as ydl:
Expand Down Expand Up @@ -47,19 +46,19 @@ def download_media(url: str, base_directory: str) -> Optional[str]:
return None

@staticmethod
def process_with_media_processor(video_path: str, base_directory:Union[Response, str], config_path: str) -> Optional[str]:
def process_with_media_processor(video_path: str, base_directory:Union[Path, str], config_path: str) -> Optional[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")],
[str(base_directory / "MediaProcessor/build/MediaProcessor")],
input= ResponseHandler.core_data_passer("The input data",input_data),
capture_output=True,
text=True
Expand All @@ -68,14 +67,12 @@ def process_with_media_processor(video_path: str, base_directory:Union[Response,
if result.returncode != 0:
logging.error(f"Error processing video: {result.stderr}")
return None
print(f"Return code: {result.returncode}")
print(f"stdout: {result.stdout}")
print(f"stderr: {result.stderr}")


#To print all the logs which have been moved to stderr
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"]
Expand Down
105 changes: 105 additions & 0 deletions backend/tests/test_media_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import tempfile
import unittest
from unittest.mock import patch, MagicMock
from pathlib import Path
from media_handler import MediaHandler
import json

class TestMediaHandler(unittest.TestCase):

def setUp(self):
"""Set up base directory and mock paths for testing."""
self.base_directory = tempfile.mkdtemp()
self.video_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
self.config_path = "/path/to/config.json"
self.video_path = "/path/to/video.mp4"
self.input_data = {
"video_file_path": self.video_path,
"config_file_path": self.config_path
}

@patch("media_handler.yt_dlp.YoutubeDL")
def test_download_media(self, mock_yt_dlp):
# Mock YoutubeDL instance
mock_ydl_instance = MagicMock()

# Mock extract_info for download=False and download=True
mock_ydl_instance.extract_info.side_effect = [
{"title": "Test Video", "ext": "mp4"}, # For download=False
{"ext": "mp4"} # For download=True
]

# Set the return value when instantiating YoutubeDL
mock_yt_dl_context_manager = MagicMock()
mock_yt_dl_context_manager.__enter__.return_value = mock_ydl_instance
mock_yt_dlp.return_value = mock_yt_dl_context_manager

# Test download_media method
result = MediaHandler.download_media(self.video_url, self.base_directory)
expected_file = Path(self.base_directory) / "Test_Video.mp4"
self.assertEqual(result, str(expected_file.resolve()))

@patch("media_handler.subprocess.run")
@patch("media_handler.ResponseHandler.core_data_passer")
def test_process_with_media_processor_success(self, mock_core_data_passer, mock_subprocess_run):
# Mock response from core_data_passer
mock_core_data_passer.return_value = json.dumps(
{ "status": "success",
"message": "the input data",
"data": self.input_data}
)
# Mock subprocess.run to simulate a successful response
mock_subprocess_run.return_value = MagicMock(
returncode=0,
stdout=json.dumps({
"status": "success",
"data": {"processed_video_path": "/path/to/processed_video.mp4"}
}),
stderr=""
)

# Test process_with_media_processor
result = MediaHandler.process_with_media_processor(
self.video_path, self.base_directory, self.config_path
)

self.assertEqual(result, "/path/to/processed_video.mp4")





@patch("media_handler.subprocess.run")
def test_process_with_media_processor_failure(self, mock_subprocess_run):
# Mock subprocess.run to simulate a failed response
mock_subprocess_run.return_value = MagicMock(
returncode=1,
stdout="",
stderr="Error processing video"
)

# Test process_with_media_processor with failure
result = MediaHandler.process_with_media_processor(
self.video_path, self.base_directory, self.config_path
)

self.assertIsNone(result)

@patch("media_handler.subprocess.run")
def test_process_with_media_processor_invalid_json(self, mock_subprocess_run):
# Mock subprocess.run to simulate invalid JSON output
mock_subprocess_run.return_value = MagicMock(
returncode=0,
stdout="Invalid JSON Output",
stderr=""
)

# Test process_with_media_processor with invalid JSON
result = MediaHandler.process_with_media_processor(
self.video_path, self.base_directory, self.config_path
)

self.assertIsNone(result)

if __name__ == "__main__":
unittest.main()
73 changes: 73 additions & 0 deletions backend/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import unittest
import os
import tempfile
from pathlib import Path
from utils import Utils

class TestUtils(unittest.TestCase):

def setUp(self):
"""Set up temporary directories and files for testing."""
self.test_dir = tempfile.mkdtemp()
self.upload_folder = self.test_dir
self.base_filename = "test_file"

def tearDown(self):
"""Clean up the temporary directory."""
for root, dirs, files in os.walk(self.test_dir, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))

def test_ensure_dir_exists(self):
# Test creating a new directory
new_dir = Path(self.test_dir) / "new_folder"
Utils.ensure_dir_exists(str(new_dir))
self.assertTrue(new_dir.exists())

def test_remove_files_by_base(self):
# Create temporary files for testing
file_paths = [
Path(self.upload_folder) / (f"{self.base_filename}.webm"),
Path(self.upload_folder) / (f"{self.base_filename}_isolated_audio.wav"),
Path(self.upload_folder) / (f"{self.base_filename}_processed_video.mp4")
]
# Create the files
for file_path in file_paths:
file_path.touch()

# Ensure the files were created
for file_path in file_paths:
self.assertTrue(file_path.exists())

# Remove files using the method
Utils.remove_files_by_base(self.base_filename, self.upload_folder)

# Check if files are removed
for file_path in file_paths:
self.assertFalse(file_path.exists())


def test_sanitize_filename(self):
# Test filename sanitization
sanitized_filename = Utils.sanitize_filename("test@file!.mp4")
self.assertEqual(sanitized_filename, "test_file_.mp4")

sanitized_filename = Utils.sanitize_filename("valid_file-name.mp4")
self.assertEqual(sanitized_filename, "valid_file-name.mp4")

def test_validate_url(self):
# Test valid URLs
valid_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
self.assertTrue(Utils.validate_url(valid_url))

# Test invalid URLs
invalid_url = "invalid_url"
self.assertFalse(Utils.validate_url(invalid_url))

no_scheme_url = "www.youtube.com"
self.assertFalse(Utils.validate_url(no_scheme_url))

if __name__ == "__main__":
unittest.main()
4 changes: 1 addition & 3 deletions backend/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import json
import logging
import os
import re
from pathlib import Path
from urllib.parse import urlparse
Expand All @@ -17,7 +15,7 @@ def ensure_dir_exists(directory: str) -> None:
@staticmethod
def remove_files_by_base(base_filename: str, upload_folder:str) -> None:
"""Removes any existing files with the same base name."""
base_path = Path(upload_folder)/ base_filename
base_path = str(Path(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():
Expand Down

0 comments on commit 24f1799

Please sign in to comment.