Ryan Malloy 840bd34f29 🎬 Video Processor v0.4.0 - Complete Multimedia Processing Platform
Professional video processing pipeline with AI analysis, 360° processing,
and adaptive streaming capabilities.

 Core Features:
• AI-powered content analysis with scene detection and quality assessment
• Next-generation codec support (AV1, HEVC, HDR10)
• Adaptive streaming (HLS/DASH) with smart bitrate ladders
• Complete 360° video processing with multiple projection support
• Spatial audio processing (Ambisonic, binaural, object-based)
• Viewport-adaptive streaming with up to 75% bandwidth savings
• Professional testing framework with video-themed HTML dashboards

🏗️ Architecture:
• Modern Python 3.11+ with full type hints
• Pydantic-based configuration with validation
• Async processing with Procrastinate task queue
• Comprehensive test coverage with 11 detailed examples
• Professional documentation structure

🚀 Production Ready:
• MIT License for open source use
• PyPI-ready package metadata
• Docker support for scalable deployment
• Quality assurance with ruff, mypy, and pytest
• Comprehensive example library

From simple encoding to immersive experiences - complete multimedia
processing platform for modern applications.
2025-09-22 01:18:49 -06:00

356 lines
10 KiB
Python

"""Video processing specific test fixtures and utilities."""
import asyncio
import tempfile
import shutil
from pathlib import Path
from typing import Dict, List, Optional, Generator, Any
from unittest.mock import Mock, AsyncMock
import pytest
from video_processor import ProcessorConfig, VideoProcessor
from .quality import QualityMetricsCalculator
@pytest.fixture
def quality_tracker(request) -> QualityMetricsCalculator:
"""Fixture to track test quality metrics."""
test_name = request.node.name
tracker = QualityMetricsCalculator(test_name)
yield tracker
# Finalize and save metrics
metrics = tracker.finalize()
# In a real implementation, you'd save to database here
# For now, we'll store in test metadata
request.node.quality_metrics = metrics
@pytest.fixture
def enhanced_temp_dir() -> Generator[Path, None, None]:
"""Enhanced temporary directory with proper cleanup and structure."""
temp_path = Path(tempfile.mkdtemp(prefix="video_test_"))
# Create standard directory structure
(temp_path / "input").mkdir()
(temp_path / "output").mkdir()
(temp_path / "thumbnails").mkdir()
(temp_path / "sprites").mkdir()
(temp_path / "logs").mkdir()
yield temp_path
shutil.rmtree(temp_path, ignore_errors=True)
@pytest.fixture
def video_config(enhanced_temp_dir: Path) -> ProcessorConfig:
"""Enhanced video processor configuration for testing."""
return ProcessorConfig(
base_path=enhanced_temp_dir,
output_formats=["mp4", "webm"],
quality_preset="medium",
thumbnail_timestamp=1,
sprite_interval=2.0,
generate_thumbnails=True,
generate_sprites=True,
)
@pytest.fixture
def enhanced_processor(video_config: ProcessorConfig) -> VideoProcessor:
"""Enhanced video processor with test-specific configurations."""
processor = VideoProcessor(video_config)
# Add test-specific hooks or mocks here if needed
return processor
@pytest.fixture
def mock_ffmpeg_environment(monkeypatch):
"""Comprehensive FFmpeg mocking environment."""
def mock_run_success(*args, **kwargs):
return Mock(returncode=0, stdout=b"", stderr=b"frame=100 fps=30")
def mock_run_failure(*args, **kwargs):
return Mock(returncode=1, stdout=b"", stderr=b"Error: Invalid codec")
def mock_probe_success(*args, **kwargs):
return {
'streams': [
{
'codec_name': 'h264',
'width': 1920,
'height': 1080,
'duration': '10.0',
'bit_rate': '5000000'
}
]
}
# Default to success, can be overridden in specific tests
monkeypatch.setattr("subprocess.run", mock_run_success)
monkeypatch.setattr("ffmpeg.probe", mock_probe_success)
return {
"success": mock_run_success,
"failure": mock_run_failure,
"probe": mock_probe_success
}
@pytest.fixture
def test_video_scenarios() -> Dict[str, Dict[str, Any]]:
"""Predefined test video scenarios for comprehensive testing."""
return {
"standard_hd": {
"name": "Standard HD Video",
"resolution": "1920x1080",
"duration": 10.0,
"codec": "h264",
"expected_outputs": ["mp4", "webm"],
"quality_threshold": 8.0
},
"short_clip": {
"name": "Short Video Clip",
"resolution": "1280x720",
"duration": 2.0,
"codec": "h264",
"expected_outputs": ["mp4"],
"quality_threshold": 7.5
},
"high_bitrate": {
"name": "High Bitrate Video",
"resolution": "3840x2160",
"duration": 5.0,
"codec": "h265",
"expected_outputs": ["mp4", "webm"],
"quality_threshold": 9.0
},
"edge_case_dimensions": {
"name": "Odd Dimensions",
"resolution": "1921x1081",
"duration": 3.0,
"codec": "h264",
"expected_outputs": ["mp4"],
"quality_threshold": 6.0
}
}
@pytest.fixture
def performance_benchmarks() -> Dict[str, Dict[str, float]]:
"""Performance benchmarks for different video processing operations."""
return {
"encoding": {
"h264_720p": 15.0, # fps
"h264_1080p": 8.0,
"h265_720p": 6.0,
"h265_1080p": 3.0,
"webm_720p": 12.0,
"webm_1080p": 6.0
},
"thumbnails": {
"generation_time_720p": 0.5, # seconds
"generation_time_1080p": 1.0,
"generation_time_4k": 2.0
},
"sprites": {
"creation_time_per_minute": 2.0, # seconds
"max_sprite_size_mb": 5.0
}
}
@pytest.fixture
def video_360_fixtures() -> Dict[str, Any]:
"""Specialized fixtures for 360° video testing."""
return {
"equirectangular": {
"projection": "equirectangular",
"fov": 360,
"resolution": "4096x2048",
"expected_processing_time": 30.0
},
"cubemap": {
"projection": "cubemap",
"face_size": 1024,
"expected_faces": 6,
"processing_complexity": "high"
},
"stereoscopic": {
"stereo_mode": "top_bottom",
"eye_separation": 65, # mm
"depth_maps": True
}
}
@pytest.fixture
def ai_analysis_fixtures() -> Dict[str, Any]:
"""Fixtures for AI-powered video analysis testing."""
return {
"scene_detection": {
"min_scene_duration": 2.0,
"confidence_threshold": 0.8,
"expected_scenes": [
{"start": 0.0, "end": 5.0, "type": "indoor"},
{"start": 5.0, "end": 10.0, "type": "outdoor"}
]
},
"object_tracking": {
"min_object_size": 50, # pixels
"tracking_confidence": 0.7,
"max_objects_per_frame": 10
},
"quality_assessment": {
"sharpness_threshold": 0.6,
"noise_threshold": 0.3,
"compression_artifacts": 0.2
}
}
@pytest.fixture
def streaming_fixtures() -> Dict[str, Any]:
"""Fixtures for streaming and adaptive bitrate testing."""
return {
"adaptive_streams": {
"resolutions": ["360p", "720p", "1080p"],
"bitrates": [800, 2500, 5000], # kbps
"segment_duration": 4.0, # seconds
"playlist_type": "vod"
},
"live_streaming": {
"latency_target": 3.0, # seconds
"buffer_size": 6.0, # seconds
"keyframe_interval": 2.0
}
}
@pytest.fixture
async def async_test_environment():
"""Async environment setup for testing async video processing."""
# Setup async environment
tasks = []
try:
yield {
"loop": asyncio.get_event_loop(),
"tasks": tasks,
"semaphore": asyncio.Semaphore(4) # Limit concurrent operations
}
finally:
# Cleanup any remaining tasks
for task in tasks:
if not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
@pytest.fixture
def mock_procrastinate_advanced():
"""Advanced Procrastinate mocking with realistic behavior."""
class MockJob:
def __init__(self, job_id: str, status: str = "todo"):
self.id = job_id
self.status = status
self.result = None
self.exception = None
class MockApp:
def __init__(self):
self.jobs = {}
self.task_counter = 0
async def defer_async(self, task_name: str, **kwargs) -> MockJob:
self.task_counter += 1
job_id = f"test-job-{self.task_counter}"
job = MockJob(job_id)
self.jobs[job_id] = job
# Simulate async processing
await asyncio.sleep(0.1)
job.status = "succeeded"
job.result = {"processed": True, "output_path": "/test/output.mp4"}
return job
async def get_job_status(self, job_id: str) -> str:
return self.jobs.get(job_id, MockJob("unknown", "failed")).status
return MockApp()
# For backward compatibility, create a class that holds these fixtures
class VideoTestFixtures:
"""Legacy class for accessing fixtures."""
@staticmethod
def enhanced_temp_dir():
return enhanced_temp_dir()
@staticmethod
def video_config(enhanced_temp_dir):
return video_config(enhanced_temp_dir)
@staticmethod
def enhanced_processor(video_config):
return enhanced_processor(video_config)
@staticmethod
def mock_ffmpeg_environment(monkeypatch):
return mock_ffmpeg_environment(monkeypatch)
@staticmethod
def test_video_scenarios():
return test_video_scenarios()
@staticmethod
def performance_benchmarks():
return performance_benchmarks()
@staticmethod
def video_360_fixtures():
return video_360_fixtures()
@staticmethod
def ai_analysis_fixtures():
return ai_analysis_fixtures()
@staticmethod
def streaming_fixtures():
return streaming_fixtures()
@staticmethod
def async_test_environment():
return async_test_environment()
@staticmethod
def mock_procrastinate_advanced():
return mock_procrastinate_advanced()
@staticmethod
def quality_tracker(request):
return quality_tracker(request)
# Export commonly used fixtures for easy import
__all__ = [
"VideoTestFixtures",
"enhanced_temp_dir",
"video_config",
"enhanced_processor",
"mock_ffmpeg_environment",
"test_video_scenarios",
"performance_benchmarks",
"video_360_fixtures",
"ai_analysis_fixtures",
"streaming_fixtures",
"async_test_environment",
"mock_procrastinate_advanced",
"quality_tracker"
]