Development - Musfiq0/enhanced-screens-comparison GitHub Wiki
๐ฉโ๐ป Development
Guide for developers who want to contribute to, extend, or understand the Enhanced Screens Comparison tool codebase.
๐๏ธ Development Setup
Prerequisites
- Python 3.8+ (3.9+ recommended)
- Git for version control
- Code editor (VS Code, PyCharm, etc.)
- Windows 10/11 (primary development platform)
Environment Setup
# Clone the repository
git clone https://github.com/Musfiq0/enhanced-screens-comparison.git
cd enhanced-screens-comparison
# Create virtual environment (recommended)
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # Linux/Mac
# Install development dependencies
pip install -r requirements.txt
# Install optional development tools
pip install black flake8 pytest mypy
Verify Installation
# Test core functionality
python comparev2.py --demo
# Test GUI
python gui_app.py
# Run any available tests
pytest # If tests are implemented
๐ Project Structure
Core Architecture
enhanced-screens-comparison/
โโโ ๐ Core Application
โ โโโ comparev2.py # CLI engine and comparison logic
โ โโโ gui_app.py # GUI application and interface
โ โโโ icon.ico # Application icon and branding
โโโ ๐จ Build System
โ โโโ build_exe.py # Executable builder script
โ โโโ screenshot_comparison.spec # PyInstaller configuration
โ โโโ version_info.txt # Windows executable metadata
โ โโโ build.bat # Build automation script
โโโ ๐ฆ Configuration
โ โโโ requirements.txt # Python dependencies
โ โโโ LICENSE # Project license (MIT)
โ โโโ PROJECT_STRUCTURE.md # Project overview
โโโ ๐ Launchers
โ โโโ START_HERE.bat # Main application launcher
โ โโโ run_gui.bat # Direct GUI launcher
โโโ ๐ Documentation
โ โโโ README.md # User documentation
โ โโโ wiki/ # Detailed wiki pages
โโโ ๐ Runtime
โโโ Screenshots/ # Generated output (runtime)
โโโ __pycache__/ # Python cache (runtime)
โโโ dist/ # Built executable (after build)
Module Breakdown
comparev2.py
- Core Engine
Purpose: Command-line interface and video processing engine
Key Components:
- Video processing backends: VapourSynth, OpenCV, PIL integration
- Screenshot generation: Frame extraction and processing
- Upload integration: slow.pics API communication
- CLI interface: Interactive prompts and command parsing
- Error handling: Comprehensive error management
Main Functions:
def main(): # CLI entry point
def process_videos(): # Core processing logic
def generate_screenshots(): # Screenshot generation
def upload_to_slowpics(): # Upload functionality
def get_video_info(): # Video metadata extraction
gui_app.py
- Graphical Interface
Purpose: Tkinter-based GUI for user-friendly interaction
Key Components:
- Main window: Primary interface and tab management
- Drag & Drop: tkinterdnd2 integration for file handling
- Settings management: Configuration persistence
- Progress tracking: Real-time processing updates
- Upload interface: Results management and sharing
Main Classes:
class VideoComparisonGUI: # Main application window
class VideoConfigDialog: # Video configuration popup
class SettingsTab: # Global settings interface
class UploadTab: # Upload management interface
build_exe.py
- Build System
Purpose: PyInstaller automation for executable creation
Features:
- Automatic dependency detection: Finds all required modules
- Resource bundling: Icons, data files, and assets
- Version management: Windows executable metadata
- Optimization: Size and performance optimization
๐ง Development Guidelines
Code Style
Python Standards
- PEP 8: Follow Python style guidelines
- Type hints: Use type annotations where possible
- Docstrings: Document functions and classes
- Error handling: Comprehensive exception management
Formatting
# Auto-format code
black comparev2.py gui_app.py
# Check style compliance
flake8 comparev2.py gui_app.py
# Type checking
mypy comparev2.py gui_app.py
Example Code Style
def process_video(
video_path: str,
output_dir: str,
frames: List[int],
crop_settings: Optional[Dict[str, int]] = None
) -> bool:
"""
Process a single video file to generate comparison screenshots.
Args:
video_path: Path to the input video file
output_dir: Directory for output screenshots
frames: List of frame numbers to extract
crop_settings: Optional crop configuration dict
Returns:
True if processing successful, False otherwise
Raises:
VideoProcessingError: If video cannot be processed
FileNotFoundError: If video file doesn't exist
"""
try:
# Implementation here
return True
except Exception as e:
logger.error(f"Video processing failed: {e}")
return False
Architecture Patterns
Backend System
The tool uses a flexible backend system for video processing:
# Backend priority order
BACKENDS = [
'vapoursynth', # Highest quality
'opencv', # Reliable default
'pil' # Universal fallback
]
def get_best_backend():
"""Detect and return the best available backend."""
for backend in BACKENDS:
if is_backend_available(backend):
return backend
raise RuntimeError("No video processing backend available")
Error Handling Strategy
class VideoProcessingError(Exception):
"""Custom exception for video processing errors."""
pass
def safe_process_video(video_path: str) -> Optional[VideoInfo]:
"""Safely process video with comprehensive error handling."""
try:
return process_video_internal(video_path)
except FileNotFoundError:
logger.error(f"Video file not found: {video_path}")
return None
except VideoProcessingError as e:
logger.error(f"Processing error: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error: {e}")
return None
Configuration Management
import json
from pathlib import Path
class ConfigManager:
"""Manage application configuration and persistence."""
def __init__(self, config_file: str = "config.json"):
self.config_file = Path(config_file)
self.config = self.load_config()
def load_config(self) -> dict:
"""Load configuration from file."""
if self.config_file.exists():
return json.loads(self.config_file.read_text())
return self.get_default_config()
def save_config(self):
"""Save current configuration to file."""
self.config_file.write_text(json.dumps(self.config, indent=2))
๐งช Testing Strategy
Test Structure
tests/
โโโ test_video_processing.py # Video processing tests
โโโ test_gui_components.py # GUI component tests
โโโ test_upload_functionality.py # Upload feature tests
โโโ test_configuration.py # Configuration tests
โโโ fixtures/ # Test data and fixtures
โโโ sample_videos/ # Small test videos
โโโ expected_outputs/ # Expected results
Unit Tests Example
import unittest
from unittest.mock import patch, MagicMock
from comparev2 import process_video, get_video_info
class TestVideoProcessing(unittest.TestCase):
def setUp(self):
"""Set up test fixtures."""
self.test_video = "fixtures/sample_videos/test.mp4"
self.output_dir = "temp_test_output"
def test_video_info_extraction(self):
"""Test video metadata extraction."""
info = get_video_info(self.test_video)
self.assertIsNotNone(info)
self.assertIn('width', info)
self.assertIn('height', info)
self.assertIn('fps', info)
@patch('comparev2.cv2.VideoCapture')
def test_opencv_backend(self, mock_cv2):
"""Test OpenCV backend functionality."""
mock_cap = MagicMock()
mock_cv2.return_value = mock_cap
mock_cap.isOpened.return_value = True
result = process_video(self.test_video, self.output_dir, [100])
self.assertTrue(result)
def tearDown(self):
"""Clean up test artifacts."""
# Remove temporary files
pass
if __name__ == '__main__':
unittest.main()
Integration Tests
class TestFullWorkflow(unittest.TestCase):
"""Test complete comparison workflow."""
def test_multiple_source_comparison(self):
"""Test complete multiple source comparison."""
videos = [
"fixtures/sample_videos/source1.mp4",
"fixtures/sample_videos/source2.mp4"
]
config = {
'comparison_mode': 'multiple_sources',
'frames': [100, 500, 1000],
'target_resolution': '1080p',
'upload_enabled': False
}
result = run_comparison(videos, config)
self.assertTrue(result['success'])
self.assertEqual(len(result['screenshots']), 6) # 3 frames ร 2 videos
๐ Extension Points
Adding New Backends
To add a new video processing backend:
class NewBackend:
"""New video processing backend."""
@staticmethod
def is_available() -> bool:
"""Check if backend is available."""
try:
import new_video_library
return True
except ImportError:
return False
def __init__(self, video_path: str):
"""Initialize backend with video file."""
self.video_path = video_path
# Backend-specific initialization
def get_frame(self, frame_number: int) -> np.ndarray:
"""Extract specific frame from video."""
# Backend-specific frame extraction
pass
def get_info(self) -> dict:
"""Get video information."""
# Backend-specific metadata extraction
pass
# Register backend
AVAILABLE_BACKENDS['new_backend'] = NewBackend
Adding New Crop Presets
# In crop preset configuration
CROP_PRESETS = {
'new_streaming_service': {
'name': 'New Streaming Service Logo',
'description': 'Remove logo overlay from New Streaming Service',
'crop': {'top': 0, 'bottom': 60, 'left': 0, 'right': 200}
},
'custom_format': {
'name': 'Custom Cinema Format',
'description': 'Custom aspect ratio correction',
'crop': {'top': 100, 'bottom': 100, 'left': 50, 'right': 50}
}
}
Adding New Upload Services
class NewUploadService:
"""Integration with new comparison hosting service."""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.newservice.com"
def upload_comparison(self, screenshots: List[str], metadata: dict) -> str:
"""Upload comparison to service."""
# Service-specific upload logic
pass
def create_collection(self, name: str, description: str) -> str:
"""Create new comparison collection."""
# Service-specific collection creation
pass
# Register service
UPLOAD_SERVICES['new_service'] = NewUploadService
๐ Build and Deployment
Development Build
# Install in development mode
pip install -e .
# Run with development settings
python comparev2.py --debug
# Test GUI with development features
python gui_app.py --dev-mode
Production Build
# Clean previous builds
python build_exe.py --clean
# Build executable
python build_exe.py
# Test executable
dist\screenshot_comparison.exe --demo
Build Configuration
Edit screenshot_comparison.spec
for custom build settings:
# PyInstaller spec file
a = Analysis(['gui_app.py'],
pathex=['.'],
binaries=[],
datas=[('icon.ico', '.'), ('requirements.txt', '.')],
hiddenimports=['tkinterdnd2', 'requests_toolbelt'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False)
๐ Performance Optimization
Profiling
import cProfile
import pstats
def profile_video_processing():
"""Profile video processing performance."""
profiler = cProfile.Profile()
profiler.enable()
# Run video processing
process_video("test_video.mp4", "output", [100, 500, 1000])
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(20) # Top 20 functions
Memory Optimization
import gc
import psutil
def monitor_memory_usage():
"""Monitor memory usage during processing."""
process = psutil.Process()
print(f"Memory before: {process.memory_info().rss / 1024 / 1024:.1f} MB")
# Video processing code here
gc.collect() # Force garbage collection
print(f"Memory after: {process.memory_info().rss / 1024 / 1024:.1f} MB")
๐ Debugging
Debug Configuration
import logging
# Configure debug logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('debug.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
Common Debug Scenarios
# Video processing debug
def debug_video_processing(video_path: str):
"""Debug video processing issues."""
logger.debug(f"Processing video: {video_path}")
# Check file existence
if not os.path.exists(video_path):
logger.error(f"Video file not found: {video_path}")
return
# Check file permissions
if not os.access(video_path, os.R_OK):
logger.error(f"Cannot read video file: {video_path}")
return
# Test backend availability
backend = get_best_backend()
logger.debug(f"Using backend: {backend}")
# Process with detailed logging
try:
result = process_video_with_logging(video_path)
logger.debug(f"Processing result: {result}")
except Exception as e:
logger.exception(f"Processing failed: {e}")
๐ API Reference
Core Functions
def process_videos(videos: List[VideoConfig], settings: ComparisonSettings) -> ProcessingResult:
"""Main video processing function."""
def generate_screenshots(video: VideoConfig, frames: List[int]) -> List[str]:
"""Generate screenshots from video at specified frames."""
def upload_comparison(screenshots: List[str], metadata: UploadMetadata) -> str:
"""Upload comparison to hosting service."""
Configuration Classes
@dataclass
class VideoConfig:
path: str
display_name: str
crop_settings: Optional[CropSettings]
target_resolution: str
trim_start: int = 0
trim_end: int = 0
@dataclass
class ComparisonSettings:
mode: str # 'multiple_sources' or 'source_vs_encode'
frame_selection: str # 'interval' or 'custom'
frames: List[int]
upload_enabled: bool
output_directory: str
Contributing to Enhanced Screens Comparison makes video comparison better for everyone! ๐