Skip to content

Error Handling Examples

Examples for handling various error scenarios.

Conversion Errors

FFmpeg Failure

from video_converter.core.orchestrator import Orchestrator
from video_converter.core.config import Config
from video_converter.core.types import ConversionRequest

async def safe_convert(input_path: Path, output_path: Path):
    """Convert with proper error handling."""
    config = Config.load()
    orchestrator = Orchestrator(config)

    try:
        result = await orchestrator.convert_single(input_path, output_path)
        if result.success:
            print(f"Success: {result.compression_ratio:.1%} saved")
        else:
            print(f"Failed: {result.error_message}")
    except FFmpegError as e:
        print(f"FFmpeg error: {e}")
        # Try software encoder as fallback
        result = await orchestrator.convert_single(
            input_path, output_path, encoder="software"
        )
    except FileNotFoundError:
        print("Input file not found")
    except Exception as e:
        print(f"Unexpected error: {e}")

Retry with Exponential Backoff

from video_converter.processors.retry_manager import RetryManager, RetryConfig

async def convert_with_retry(input_path: Path, output_path: Path):
    """Convert with automatic retry on failure."""
    retry_config = RetryConfig(
        max_retries=3,
        base_delay=5.0,
        max_delay=60.0,
        exponential_base=2.0
    )

    retry_manager = RetryManager(retry_config)

    async def do_convert():
        result = await converter.convert(ConversionRequest(
            input_path=input_path,
            output_path=output_path
        ))
        if not result.success:
            raise ConversionError(result.error_message)
        return result

    try:
        result = await retry_manager.execute(do_convert)
        print(f"Success after {retry_manager.attempts} attempts")
    except MaxRetriesExceeded:
        print(f"Failed after {retry_config.max_retries} attempts")

Validation Errors

Quality Validation

from video_converter.processors.quality_validator import QualityValidator
from video_converter.processors.vmaf_analyzer import VmafAnalyzer

async def validate_conversion(original: Path, converted: Path):
    """Validate conversion quality."""
    validator = QualityValidator()

    # Basic validation
    result = await validator.validate(original, converted)

    if not result.valid:
        print("Validation failed:")
        for error in result.errors:
            print(f"  - {error}")
        return False

    # VMAF validation (optional)
    vmaf = VmafAnalyzer()
    scores = await vmaf.analyze(original, converted)

    if scores.mean < 85.0:
        print(f"Low VMAF score: {scores.mean:.1f}")
        return False

    print(f"Validation passed (VMAF: {scores.mean:.1f})")
    return True

Metadata Verification

from video_converter.processors.gps import GPSHandler

async def verify_metadata(original: Path, converted: Path):
    """Verify metadata was preserved."""
    gps_handler = GPSHandler()

    # Extract GPS from both files
    original_gps = gps_handler.extract(original)
    converted_gps = gps_handler.extract(converted)

    if original_gps and not converted_gps:
        print("GPS data was lost during conversion!")
        # Attempt to fix
        gps_handler.copy(original, converted)
        return False

    if original_gps and converted_gps:
        if not original_gps.matches(converted_gps):
            print("GPS coordinates don't match!")
            return False

    print("Metadata verification passed")
    return True

File Access Errors

Photos Library Access

from video_converter.extractors.photos_extractor import PhotosExtractor

def check_photos_access():
    """Check Photos library access permission."""
    try:
        extractor = PhotosExtractor()
        videos = extractor.scan_videos(limit=1)
        print("Photos access: OK")
        return True
    except PermissionError:
        print("Photos access denied!")
        print("Grant access in System Preferences > Privacy > Photos")
        return False
    except PhotosLibraryNotFound:
        print("Photos library not found")
        return False

iCloud Download Errors

from video_converter.extractors.icloud_handler import iCloudHandler, CloudStatus

async def handle_icloud_video(video_info):
    """Handle iCloud-only video."""
    icloud = iCloudHandler()

    status = icloud.get_status(video_info.path)

    if status == CloudStatus.LOCAL:
        return video_info.path

    if status == CloudStatus.DOWNLOADING:
        print("Video is already downloading...")
        await icloud.wait_for_download(video_info.path, timeout=600)

    if status == CloudStatus.CLOUD_ONLY:
        print("Requesting download from iCloud...")
        try:
            await icloud.download(video_info.path, timeout=600)
        except iCloudTimeoutError:
            print("iCloud download timed out")
            raise
        except iCloudQuotaExceeded:
            print("iCloud quota exceeded")
            raise

    return video_info.path

Batch Error Handling

Skip and Continue

async def batch_convert_resilient(videos: list[Path]):
    """Convert batch, skipping failures."""
    results = {
        "success": [],
        "failed": [],
        "skipped": []
    }

    for video in videos:
        try:
            result = await convert_single(video)
            if result.success:
                results["success"].append(video)
            else:
                results["failed"].append((video, result.error_message))
        except SkipVideoError as e:
            results["skipped"].append((video, str(e)))
        except Exception as e:
            results["failed"].append((video, str(e)))
            # Log but continue
            logger.error(f"Failed to convert {video}: {e}")

    # Summary
    print(f"Success: {len(results['success'])}")
    print(f"Failed: {len(results['failed'])}")
    print(f"Skipped: {len(results['skipped'])}")

    return results

Error Report Generation

import json
from datetime import datetime

def generate_error_report(results: dict, output_path: Path):
    """Generate detailed error report."""
    report = {
        "timestamp": datetime.now().isoformat(),
        "summary": {
            "total": len(results["success"]) + len(results["failed"]) + len(results["skipped"]),
            "success": len(results["success"]),
            "failed": len(results["failed"]),
            "skipped": len(results["skipped"])
        },
        "failures": [
            {"file": str(path), "error": error}
            for path, error in results["failed"]
        ],
        "skipped": [
            {"file": str(path), "reason": reason}
            for path, reason in results["skipped"]
        ]
    }

    with open(output_path, "w") as f:
        json.dump(report, f, indent=2)

    print(f"Error report saved to {output_path}")

CLI Error Handling

#!/bin/bash
# Robust conversion script with error handling

set -e  # Exit on error

log_file="conversion_$(date +%Y%m%d_%H%M%S).log"

run_conversion() {
    video-converter run --mode photos 2>&1 | tee "$log_file"
    return ${PIPESTATUS[0]}
}

if run_conversion; then
    echo "Conversion completed successfully"
    osascript -e 'display notification "Conversion complete" with title "Video Converter"'
else
    echo "Conversion failed - check $log_file for details"
    osascript -e 'display notification "Conversion failed" with title "Video Converter"'
    exit 1
fi