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! ๐Ÿš€