django and fastapi apps

This commit is contained in:
2026-02-03 12:20:40 -03:00
parent d31a3ed612
commit 67573713bd
54 changed files with 3272 additions and 11 deletions

64
schema/models/__init__.py Normal file
View File

@@ -0,0 +1,64 @@
"""
MPR Schema Models
This module exports all dataclasses, enums, and constants that the generator
should process. Add new models here to have them included in generation.
"""
from .grpc import (
GRPC_SERVICE,
CancelRequest,
CancelResponse,
Empty,
JobRequest,
JobResponse,
ProgressRequest,
ProgressUpdate,
WorkerStatus,
)
from .jobs import JobStatus, TranscodeJob
from .media import AssetStatus, MediaAsset
from .presets import BUILTIN_PRESETS, TranscodePreset
# Core domain models - generates Django, Pydantic, TypeScript
DATACLASSES = [MediaAsset, TranscodePreset, TranscodeJob]
# Status enums - included in generated code
ENUMS = [AssetStatus, JobStatus]
# gRPC messages - generates Proto
GRPC_MESSAGES = [
JobRequest,
JobResponse,
ProgressRequest,
ProgressUpdate,
CancelRequest,
CancelResponse,
WorkerStatus,
Empty,
]
__all__ = [
# Models
"MediaAsset",
"TranscodePreset",
"TranscodeJob",
# Enums
"AssetStatus",
"JobStatus",
# gRPC
"GRPC_SERVICE",
"JobRequest",
"JobResponse",
"ProgressRequest",
"ProgressUpdate",
"CancelRequest",
"CancelResponse",
"WorkerStatus",
"Empty",
# For generator
"DATACLASSES",
"ENUMS",
"GRPC_MESSAGES",
"BUILTIN_PRESETS",
]

130
schema/models/grpc.py Normal file
View File

@@ -0,0 +1,130 @@
"""
gRPC message definitions for MPR worker communication.
This is the source of truth for gRPC messages. The generator creates:
- grpc/protos/worker.proto (protobuf definition)
- grpc/worker_pb2.py (generated Python classes)
- grpc/worker_pb2_grpc.py (generated gRPC stubs)
"""
from dataclasses import dataclass
from typing import Optional
# -----------------------------------------------------------------------------
# Request Messages
# -----------------------------------------------------------------------------
@dataclass
class JobRequest:
"""Request to submit a transcode/trim job."""
job_id: str
source_path: str
output_path: str
preset_json: str # Serialized TranscodePreset
trim_start: Optional[float] = None
trim_end: Optional[float] = None
@dataclass
class ProgressRequest:
"""Request to stream progress updates for a job."""
job_id: str
@dataclass
class CancelRequest:
"""Request to cancel a running job."""
job_id: str
@dataclass
class Empty:
"""Empty message for requests with no parameters."""
pass
# -----------------------------------------------------------------------------
# Response Messages
# -----------------------------------------------------------------------------
@dataclass
class JobResponse:
"""Response after submitting a job."""
job_id: str
accepted: bool
message: str
@dataclass
class ProgressUpdate:
"""Streaming progress update from worker."""
job_id: str
progress: int # 0-100
current_frame: int
current_time: float
speed: float # e.g., 2.5x
status: str # pending, processing, completed, failed, cancelled
error: Optional[str] = None
@dataclass
class CancelResponse:
"""Response after cancel request."""
job_id: str
cancelled: bool
message: str
@dataclass
class WorkerStatus:
"""Worker health and capabilities."""
available: bool
active_jobs: int
supported_codecs: list[str]
gpu_available: bool
# -----------------------------------------------------------------------------
# Service Definition (for documentation, generator uses this)
# -----------------------------------------------------------------------------
GRPC_SERVICE = {
"name": "WorkerService",
"package": "mpr.worker",
"methods": [
{
"name": "SubmitJob",
"request": JobRequest,
"response": JobResponse,
"stream_response": False,
},
{
"name": "StreamProgress",
"request": ProgressRequest,
"response": ProgressUpdate,
"stream_response": True, # Server streaming
},
{
"name": "CancelJob",
"request": CancelRequest,
"response": CancelResponse,
"stream_response": False,
},
{
"name": "GetWorkerStatus",
"request": Empty,
"response": WorkerStatus,
"stream_response": False,
},
],
}

78
schema/models/jobs.py Normal file
View File

@@ -0,0 +1,78 @@
"""
TranscodeJob Schema Definition
Source of truth for job data model.
"""
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Dict, Optional
from uuid import UUID
class JobStatus(str, Enum):
"""Status of a transcode/trim job."""
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
@dataclass
class TranscodeJob:
"""
A transcoding or trimming job in the queue.
Jobs can either:
- Transcode using a preset (full re-encode)
- Trim only (stream copy with -c:v copy -c:a copy)
A trim-only job has no preset and uses stream copy.
"""
id: UUID
# Input
source_asset_id: UUID
# Configuration
preset_id: Optional[UUID] = None
preset_snapshot: Dict[str, Any] = field(
default_factory=dict
) # Copy at creation time
# Trimming (optional)
trim_start: Optional[float] = None # seconds
trim_end: Optional[float] = None # seconds
# Output
output_filename: str = ""
output_path: Optional[str] = None
output_asset_id: Optional[UUID] = None
# Status & Progress
status: JobStatus = JobStatus.PENDING
progress: float = 0.0 # 0.0 to 100.0
current_frame: Optional[int] = None
current_time: Optional[float] = None # seconds processed
speed: Optional[str] = None # "2.5x"
error_message: Optional[str] = None
# Worker tracking
celery_task_id: Optional[str] = None
priority: int = 0 # Lower = higher priority
# Timestamps
created_at: Optional[datetime] = None
started_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
@property
def is_trim_only(self) -> bool:
"""Check if this is a trim-only job (stream copy, no transcode)."""
return self.preset_id is None and (
self.trim_start is not None or self.trim_end is not None
)

59
schema/models/media.py Normal file
View File

@@ -0,0 +1,59 @@
"""
MediaAsset Schema Definition
Source of truth for media asset data model.
"""
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional
from uuid import UUID
class AssetStatus(str, Enum):
"""Status of a media asset after probing."""
PENDING = "pending"
READY = "ready"
ERROR = "error"
@dataclass
class MediaAsset:
"""
A video/audio file registered in the system.
Metadata is populated asynchronously via ffprobe after registration.
"""
id: UUID
filename: str
file_path: str
# Status
status: AssetStatus = AssetStatus.PENDING
error_message: Optional[str] = None
# File info
file_size: Optional[int] = None
# Media metadata (populated by ffprobe)
duration: Optional[float] = None # seconds
video_codec: Optional[str] = None
audio_codec: Optional[str] = None
width: Optional[int] = None
height: Optional[int] = None
framerate: Optional[float] = None
bitrate: Optional[int] = None # bits per second
# Full ffprobe output and custom metadata
properties: Dict[str, Any] = field(default_factory=dict)
# User annotations
comments: str = ""
tags: List[str] = field(default_factory=list)
# Timestamps
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None

128
schema/models/presets.py Normal file
View File

@@ -0,0 +1,128 @@
"""
TranscodePreset Schema Definition
Source of truth for preset data model.
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Optional
from uuid import UUID
@dataclass
class TranscodePreset:
"""
A reusable transcoding configuration (like Handbrake presets).
Presets can be builtin (shipped with the app) or user-created.
"""
id: UUID
name: str
description: str = ""
is_builtin: bool = False
# Output container
container: str = "mp4" # mp4, mkv, webm, mov, avi
# Video settings
video_codec: str = "libx264"
video_bitrate: Optional[str] = None # "2M", "5000k"
video_crf: Optional[int] = None # Quality-based (0-51 for x264)
video_preset: Optional[str] = None # ultrafast...veryslow
resolution: Optional[str] = None # "1920x1080", "1280x720"
framerate: Optional[float] = None
# Audio settings
audio_codec: str = "aac"
audio_bitrate: Optional[str] = None # "128k", "320k"
audio_channels: Optional[int] = None # 2 for stereo
audio_samplerate: Optional[int] = None # 44100, 48000
# Advanced: extra FFmpeg arguments
extra_args: List[str] = field(default_factory=list)
# Timestamps
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
BUILTIN_PRESETS = [
{
"name": "DaVinci Resolve",
"description": "MPEG-4 (xvid) + MP3 - Compatible with DaVinci Resolve Free",
"container": "avi",
"video_codec": "mpeg4",
"video_crf": 5,
"audio_codec": "libmp3lame",
"audio_bitrate": "320k",
"audio_samplerate": 48000,
"extra_args": ["-vtag", "xvid", "-pix_fmt", "yuv420p"],
},
{
"name": "Copy (Trim Only)",
"description": "Stream copy - No transcoding, fast trimming only",
"container": "mp4",
"video_codec": "copy",
"audio_codec": "copy",
},
{
"name": "Web H.264",
"description": "H.264 + AAC - General web playback",
"container": "mp4",
"video_codec": "libx264",
"video_crf": 23,
"video_preset": "medium",
"audio_codec": "aac",
"audio_bitrate": "128k",
},
{
"name": "Web H.265",
"description": "HEVC + AAC - Smaller files, modern browsers",
"container": "mp4",
"video_codec": "libx265",
"video_crf": 28,
"video_preset": "medium",
"audio_codec": "aac",
"audio_bitrate": "128k",
},
{
"name": "DNxHR HQ",
"description": "DNxHR High Quality - Professional editing",
"container": "mov",
"video_codec": "dnxhd",
"audio_codec": "pcm_s16le",
"audio_samplerate": 48000,
"extra_args": ["-profile:v", "dnxhr_hq", "-pix_fmt", "yuv422p"],
},
{
"name": "H.264 NVENC",
"description": "NVIDIA GPU encoding - Fast H.264",
"container": "mp4",
"video_codec": "h264_nvenc",
"video_bitrate": "10M",
"audio_codec": "aac",
"audio_bitrate": "192k",
"extra_args": ["-preset", "p4", "-rc", "vbr", "-cq", "19"],
},
{
"name": "HEVC NVENC",
"description": "NVIDIA GPU encoding - HEVC/H.265",
"container": "mp4",
"video_codec": "hevc_nvenc",
"video_bitrate": "8M",
"audio_codec": "aac",
"audio_bitrate": "192k",
"extra_args": ["-preset", "p4", "-rc", "vbr", "-cq", "23"],
},
{
"name": "Archive ProRes",
"description": "Apple ProRes 422 HQ - Archival quality",
"container": "mov",
"video_codec": "prores_ks",
"audio_codec": "pcm_s16le",
"audio_samplerate": 48000,
"extra_args": ["-profile:v", "3"], # ProRes 422 HQ
},
]