Ryan Malloy 5ca1b7a07d Migrate to Procrastinate 3.x with backward compatibility for 2.x
- Add comprehensive compatibility layer supporting both Procrastinate 2.x and 3.x
- Implement version-aware database migration system with pre/post migrations for 3.x
- Create worker option mapping for seamless transition between versions
- Add extensive test coverage for all compatibility features
- Update dependency constraints to support both 2.x and 3.x simultaneously
- Provide Docker containerization with uv caching and multi-service orchestration
- Include demo applications and web interface for testing capabilities
- Bump version to 0.2.0 reflecting new compatibility features

Key Features:
- Automatic version detection and feature flagging
- Unified connector creation across PostgreSQL drivers
- Worker option translation (timeout → fetch_job_polling_interval)
- Database migration utilities with CLI and programmatic interfaces
- Complete Docker Compose setup with PostgreSQL, Redis, workers, and demos

Files Added:
- src/video_processor/tasks/compat.py - Core compatibility layer
- src/video_processor/tasks/migration.py - Migration utilities
- src/video_processor/tasks/worker_compatibility.py - Worker CLI
- tests/test_procrastinate_compat.py - Compatibility tests
- tests/test_procrastinate_migration.py - Migration tests
- Dockerfile - Multi-stage build with uv caching
- docker-compose.yml - Complete development environment
- examples/docker_demo.py - Containerized demo application
- examples/web_demo.py - Flask web interface demo

Migration Support:
- Procrastinate 2.x: Single migration command compatibility
- Procrastinate 3.x: Separate pre/post migration phases
- Database URL validation and connection testing
- Version-specific feature detection and graceful degradation
2025-09-05 10:38:12 -06:00

88 lines
2.9 KiB
Python

"""Configuration management using Pydantic."""
from pathlib import Path
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
# Optional dependency detection for 360° features
try:
from .utils.video_360 import (
HAS_360_SUPPORT,
ProjectionType,
StereoMode,
Video360Utils,
)
except ImportError:
# Fallback types when 360° libraries not available
ProjectionType = str
StereoMode = str
HAS_360_SUPPORT = False
class ProcessorConfig(BaseModel):
"""Configuration for video processor."""
# Storage settings
storage_backend: Literal["local", "s3"] = "local"
base_path: Path = Field(default=Path("/tmp/videos"))
# Encoding settings
output_formats: list[Literal["mp4", "webm", "ogv"]] = Field(default=["mp4"])
quality_preset: Literal["low", "medium", "high", "ultra"] = "medium"
# FFmpeg settings
ffmpeg_path: str = "/usr/bin/ffmpeg"
# Thumbnail settings
thumbnail_timestamps: list[int] = Field(default=[1]) # seconds
thumbnail_width: int = 640
# Sprite settings
generate_sprites: bool = True
sprite_interval: int = 10 # seconds between sprite frames
# Custom FFmpeg options
custom_ffmpeg_options: dict[str, str] = Field(default_factory=dict)
# File permissions
file_permissions: int = 0o644
directory_permissions: int = 0o755
# 360° Video settings (only active if 360° libraries are available)
enable_360_processing: bool = Field(default=HAS_360_SUPPORT)
auto_detect_360: bool = Field(default=True)
force_360_projection: ProjectionType | None = Field(default=None)
video_360_bitrate_multiplier: float = Field(default=2.5, ge=1.0, le=5.0)
generate_360_thumbnails: bool = Field(default=True)
thumbnail_360_projections: list[Literal["front", "back", "up", "down", "left", "right", "stereographic"]] = Field(
default=["front", "stereographic"]
)
@field_validator("base_path")
@classmethod
def validate_base_path(cls, v: Path) -> Path:
"""Ensure base path is absolute."""
return v.resolve()
@field_validator("output_formats")
@classmethod
def validate_output_formats(cls, v: list[str]) -> list[str]:
"""Ensure at least one output format is specified."""
if not v:
raise ValueError("At least one output format must be specified")
return v
@field_validator("enable_360_processing")
@classmethod
def validate_360_processing(cls, v: bool) -> bool:
"""Validate 360° processing can be enabled."""
if v and not HAS_360_SUPPORT:
raise ValueError(
"360° processing requires optional dependencies. "
"Install with: pip install 'video-processor[video-360]' or uv add 'video-processor[video-360]'"
)
return v
model_config = ConfigDict(validate_assignment=True)