Spaces:
Sleeping
Sleeping
| """ | |
| Tests for video_processor.py | |
| - extract_keyframes: tested with a real temp directory + mocked subprocess | |
| - encode_image_to_base64: tested with a real temp file | |
| - generate_vertical_reel_clip: filter-chain logic tested without running FFmpeg | |
| - analyze_scene_with_gemini / run_openai_fallback: patched at the boundary | |
| """ | |
| import os | |
| import sys | |
| import glob | |
| import base64 | |
| import tempfile | |
| import subprocess | |
| import pytest | |
| from unittest.mock import patch, MagicMock, call | |
| sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../app"))) | |
| import video_processor | |
| from video_processor import ( | |
| encode_image_to_base64, | |
| extract_keyframes, | |
| generate_vertical_reel_clip, | |
| ) | |
| # βββ encode_image_to_base64 βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestEncodeImageToBase64: | |
| def test_encodes_real_file(self, tmp_path): | |
| img = tmp_path / "frame.jpg" | |
| img.write_bytes(b"\xff\xd8\xff") # minimal JPEG header bytes | |
| result = encode_image_to_base64(str(img)) | |
| # Result must be a valid base64 string that decodes back to our bytes | |
| assert base64.b64decode(result) == b"\xff\xd8\xff" | |
| def test_returns_string(self, tmp_path): | |
| img = tmp_path / "frame.jpg" | |
| img.write_bytes(b"abc") | |
| assert isinstance(encode_image_to_base64(str(img)), str) | |
| def test_missing_file_raises(self): | |
| with pytest.raises(FileNotFoundError): | |
| encode_image_to_base64("/nonexistent/path/frame.jpg") | |
| # βββ extract_keyframes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestExtractKeyframes: | |
| def test_creates_output_dir_if_missing(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| output_dir = tmp_path / "frames" | |
| extract_keyframes("fake.mp4", str(output_dir), interval_seconds=5) | |
| assert output_dir.exists() | |
| def test_clears_existing_jpgs_before_extraction(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| output_dir = tmp_path / "frames" | |
| output_dir.mkdir() | |
| # Pre-populate with stale files | |
| (output_dir / "keyframe_0001.jpg").write_bytes(b"old") | |
| (output_dir / "keyframe_0002.jpg").write_bytes(b"old") | |
| extract_keyframes("fake.mp4", str(output_dir), interval_seconds=5) | |
| # After the call (before ffmpeg actually writes) the old files are gone | |
| assert len(list(output_dir.glob("*.jpg"))) == 0 | |
| def test_ffmpeg_command_uses_correct_fps_filter(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| output_dir = tmp_path / "frames" | |
| extract_keyframes("fake.mp4", str(output_dir), interval_seconds=10) | |
| cmd = mock_run.call_args[0][0] | |
| assert "fps=1/10" in cmd | |
| def test_ffmpeg_uses_high_quality_jpeg(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| output_dir = tmp_path / "frames" | |
| extract_keyframes("fake.mp4", str(output_dir), interval_seconds=5) | |
| cmd = mock_run.call_args[0][0] | |
| assert "-q:v" in cmd and "2" in cmd | |
| def test_raises_on_ffmpeg_failure(self, mock_run, tmp_path): | |
| mock_run.side_effect = subprocess.CalledProcessError(1, "ffmpeg") | |
| output_dir = tmp_path / "frames" | |
| with pytest.raises(subprocess.CalledProcessError): | |
| extract_keyframes("bad.mp4", str(output_dir)) | |
| # βββ generate_vertical_reel_clip βββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestGenerateVerticalReelClip: | |
| def test_blurred_mode_uses_boxblur_filter(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") # must exist β validation guard checks os.path.exists | |
| out = str(tmp_path / "reel.mp4") | |
| generate_vertical_reel_clip(str(src), 0.0, 30.0, out, render_mode="blurred") | |
| cmd = mock_run.call_args[0][0] | |
| vf_value = cmd[cmd.index("-vf") + 1] | |
| assert "boxblur" in vf_value | |
| assert "split" in vf_value | |
| def test_center_mode_uses_center_crop(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| out = str(tmp_path / "reel.mp4") | |
| generate_vertical_reel_clip(str(src), 0.0, 30.0, out, render_mode="center") | |
| cmd = mock_run.call_args[0][0] | |
| vf_value = cmd[cmd.index("-vf") + 1] | |
| assert "(iw-ow)/2" in vf_value | |
| def test_left_mode_uses_zero_x_offset(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| out = str(tmp_path / "reel.mp4") | |
| generate_vertical_reel_clip(str(src), 10.0, 40.0, out, render_mode="left") | |
| cmd = mock_run.call_args[0][0] | |
| vf_value = cmd[cmd.index("-vf") + 1] | |
| assert "crop" in vf_value | |
| assert ":0," in vf_value or vf_value.endswith(":0") | |
| def test_right_mode_uses_iw_ow_offset(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| out = str(tmp_path / "reel.mp4") | |
| generate_vertical_reel_clip(str(src), 5.0, 25.0, out, render_mode="right") | |
| cmd = mock_run.call_args[0][0] | |
| vf_value = cmd[cmd.index("-vf") + 1] | |
| assert "iw-ow" in vf_value | |
| def test_output_uses_libx264_and_aac(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| out = str(tmp_path / "reel.mp4") | |
| generate_vertical_reel_clip(str(src), 0.0, 30.0, out) | |
| cmd = mock_run.call_args[0][0] | |
| assert "libx264" in cmd | |
| assert "aac" in cmd | |
| def test_removes_existing_output_before_render(self, mock_run, tmp_path): | |
| mock_run.return_value = MagicMock(returncode=0) | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| out = tmp_path / "reel.mp4" | |
| out.write_bytes(b"old content") | |
| generate_vertical_reel_clip(str(src), 0.0, 10.0, str(out)) | |
| assert not out.exists() | |
| def test_raises_on_ffmpeg_failure(self, mock_run, tmp_path): | |
| mock_run.side_effect = subprocess.CalledProcessError( | |
| 1, "ffmpeg", stderr=b"encoding error" | |
| ) | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| with pytest.raises(subprocess.CalledProcessError): | |
| generate_vertical_reel_clip(str(src), 0.0, 30.0, str(tmp_path / "out.mp4")) | |
| def test_raises_when_end_before_start(self, tmp_path): | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| with pytest.raises(ValueError, match="end_time"): | |
| generate_vertical_reel_clip(str(src), 30.0, 10.0, str(tmp_path / "out.mp4")) | |
| def test_raises_when_negative_start(self, tmp_path): | |
| src = tmp_path / "src.mp4" | |
| src.write_bytes(b"fake") | |
| with pytest.raises(ValueError, match="negative"): | |
| generate_vertical_reel_clip(str(src), -5.0, 10.0, str(tmp_path / "out.mp4")) | |
| def test_raises_when_source_missing(self, tmp_path): | |
| with pytest.raises(FileNotFoundError): | |
| generate_vertical_reel_clip("nonexistent.mp4", 0.0, 10.0, str(tmp_path / "out.mp4")) | |