Clipping / schemas.py
aliSaac510's picture
fix defaults
8c64cc3
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from enum import Enum
from datetime import datetime
# ======== التعدادات الأساسية ========
class LayoutType(str, Enum):
CINEMATIC_BLUR = "cinematic_blur"
SPLIT_SCREEN = "split_screen"
CROP_CENTER = "crop_center"
FIT_CENTER = "fit_center"
class AspectRatio(str, Enum):
RATIO_9_16 = "9:16" # Shorts, TikTok, Reels
RATIO_1_1 = "1:1" # Instagram Square
RATIO_16_9 = "16:9" # Standard YouTube
RATIO_4_5 = "4:5" # Facebook Portrait
ORIGINAL = "original" # Keep source ratio
class ShortsStyle(str, Enum):
ORIGINAL = "original" # Keep original style
CINEMATIC = "cinematic" # Blurred background
CROP_FILL = "crop_fill" # Crop to fill target ratio
SPLIT_SCREEN = "split" # Split screen
FIT_BARS = "fit_bars" # Letterboxing
class ProcessingType(str, Enum):
TRANSCRIPT = "transcript"
CROP = "crop"
EFFECTS = "effects"
AUDIO = "audio"
COMBINED = "combined"
class VersionStatus(str, Enum):
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
# ======== النماذج الأساسية ========
class Timestamp(BaseModel):
start_time: float
end_time: float
class Dimensions(BaseModel):
width: Optional[int] = 0
height: Optional[int] = 0
target_ratio: AspectRatio = AspectRatio.RATIO_9_16
style: ShortsStyle = ShortsStyle.ORIGINAL
video_scale: float = 1.0
vertical_shift: float = 0.0
blur_intensity: int = 20
background_image_url: Optional[str] = None
audio_path: Optional[str] = None
video_volume: float = 1.0
music_volume: float = 0.2
loop_music: bool = True
background_video_url: Optional[str] = None
class WordTiming(BaseModel):
word: str
start: float
end: float
confidence: Optional[float] = 1.0
class SentenceTiming(BaseModel):
text: str
start: float
end: float
words: List[WordTiming]
class SubtitleStyle(str, Enum):
YOUTUBE_CASUAL = "youtube_casual"
GAMING = "gaming"
PROFESSIONAL = "professional"
MUSIC = "music"
EDUCATIONAL = "educational"
VLOG = "vlog"
class SubtitlePreset(BaseModel):
name: str
font_name: str = "Arial"
font_size: int = 48
primary_color: str = "#FFFFFF"
secondary_color: str = "#FFFF00"
outline_color: str = "#000000"
back_color: str = "#000000"
outline_width: float = 2.0
shadow_depth: float = 1.0
alignment: int = 2
margin_v: int = 100
pop_up_scale: float = 1.2
highlight_mode: str = "karaoke"
glow_effect: bool = True
elastic_bounce: bool = True
rotation_enabled: bool = False
back_box_enabled: bool = True
display_mode: str = "word" # "word" or "sentence"
max_words_per_line: int = 3
uppercase: bool = False
letter_spacing: float = 0.0
line_spacing: int = 0
background_opacity: float = 1.0
glow_intensity: int = 0
rotation_angle: float = 0.0
margin_h: int = 20
class ClipRequest(BaseModel):
video_url: Optional[str] = None
aspect_ratio: AspectRatio = AspectRatio.RATIO_9_16
style: ShortsStyle = ShortsStyle.ORIGINAL
video_volume: float = 1.0
music_volume: float = 0.2
loop_music: bool = True
add_subtitles: bool = False
subtitle_style: Optional[SubtitleStyle] = SubtitleStyle.YOUTUBE_CASUAL
subtitle_preset: Optional[SubtitlePreset] = None
transcription: Optional[List[WordTiming]] = None
custom_dimensions: Optional[Dimensions] = None
timestamps: Optional[List[Timestamp]] = None
# ======== نماذج الترانسكريبت ========
class TranscriptSegment(BaseModel):
start: float = Field(..., description="Start time in seconds")
end: float = Field(..., description="End time in seconds")
text: str = Field(..., description="Transcript text")
position: Optional[str] = Field("bottom", description="Text position: top, bottom, center")
font_size: Optional[int] = Field(None, description="Override font size for this segment")
font_color: Optional[str] = Field(None, description="Override font color for this segment")
class TranscriptConfig(BaseModel):
segments: List[TranscriptSegment] = Field(..., description="List of transcript segments")
font_size: int = Field(default=24, ge=12, le=72, description="Default font size")
font_color: str = Field(default="#FFFFFF", pattern=r"^#[0-9A-Fa-f]{6}$", description="Default font color")
font_family: str = Field(default="Arial", description="Font family")
position: str = Field(default="bottom", pattern="^(top|bottom|center)$", description="Default text position")
background_color: Optional[str] = Field(default=None, pattern=r"^#[0-9A-Fa-f]{6}$", description="Text background color")
background_alpha: float = Field(default=0.8, ge=0.0, le=1.0, description="Background transparency")
margin: int = Field(default=20, ge=0, le=100, description="Margin from edges")
opacity: float = Field(default=1.0, ge=0.0, le=1.0, description="Text opacity")
animation: Optional[str] = Field(None, description="Text animation type")
shadow: bool = Field(default=False, description="Add text shadow")
outline: bool = Field(default=False, description="Add text outline")
# ======== نماذج القص ========
class CropConfig(BaseModel):
x1: int = Field(default=0, ge=0, description="Top-left X coordinate")
y1: int = Field(default=0, ge=0, description="Top-left Y coordinate")
x2: Optional[int] = Field(None, ge=0, description="Bottom-right X coordinate")
y2: Optional[int] = Field(None, ge=0, description="Bottom-right Y coordinate")
width: Optional[int] = Field(None, ge=1, description="Crop width")
height: Optional[int] = Field(None, ge=1, description="Crop height")
aspect_ratio: Optional[str] = Field(None, description="Force aspect ratio (e.g., '16:9', '9:16')")
center_crop: bool = Field(default=False, description="Crop from center")
def get_crop_coordinates(self, video_width: int, video_height: int) -> tuple:
"""Calculate crop coordinates based on configuration"""
if self.center_crop and self.width and self.height:
center_x = video_width // 2
center_y = video_height // 2
half_width = self.width // 2
half_height = self.height // 2
x1 = max(0, center_x - half_width)
y1 = max(0, center_y - half_height)
x2 = min(video_width, center_x + half_width)
y2 = min(video_height, center_y + half_height)
return (x1, y1, x2, y2)
# Use provided coordinates or calculate from width/height
x2 = self.x2 or (self.x1 + self.width) if self.width else video_width
y2 = self.y2 or (self.y1 + self.height) if self.height else video_height
return (self.x1, self.y1, x2, y2)
# ======== نماذج التأثيرات ========
class EffectsConfig(BaseModel):
brightness: Optional[float] = Field(None, ge=0.1, le=3.0, description="Brightness multiplier")
contrast: Optional[float] = Field(None, ge=0.1, le=3.0, description="Contrast multiplier")
saturation: Optional[float] = Field(None, ge=0.0, le=3.0, description="Saturation multiplier")
speed: Optional[float] = Field(None, gt=0.1, le=10.0, description="Playback speed multiplier")
fade_in: Optional[float] = Field(None, ge=0.0, description="Fade in duration in seconds")
fade_out: Optional[float] = Field(None, ge=0.0, description="Fade out duration in seconds")
blur: Optional[float] = Field(None, ge=0.0, le=10.0, description="Blur radius")
sharpen: Optional[float] = Field(None, ge=0.0, le=5.0, description="Sharpen intensity")
vignette: Optional[float] = Field(None, ge=0.0, le=1.0, description="Vignette intensity")
noise: Optional[float] = Field(None, ge=0.0, le=1.0, description="Noise amount")
sepia: bool = Field(default=False, description="Apply sepia tone")
black_white: bool = Field(default=False, description="Convert to black and white")
vintage: bool = Field(default=False, description="Apply vintage filter")
# ======== نماذج الصوت ========
class AudioConfig(BaseModel):
volume: Optional[float] = Field(None, ge=0.0, le=2.0, description="Volume multiplier")
normalize: bool = Field(default=False, description="Normalize audio")
remove_noise: bool = Field(default=False, description="Remove background noise")
bass_boost: Optional[float] = Field(None, ge=0.0, le=2.0, description="Bass boost intensity")
treble_boost: Optional[float] = Field(None, ge=0.0, le=2.0, description="Treble boost intensity")
fade_in: Optional[float] = Field(None, ge=0.0, description="Audio fade in duration")
fade_out: Optional[float] = Field(None, ge=0.0, description="Audio fade out duration")
speed: Optional[float] = Field(None, gt=0.1, le=2.0, description="Audio speed multiplier")
pitch_shift: Optional[float] = Field(None, ge=-12, le=12, description="Pitch shift in semitones")
# ======== نماذج إدارة النسخ ========
class VideoVersionResponse(BaseModel):
version_id: str
version_name: str
processing_type: ProcessingType
status: VersionStatus
file_path: str
file_size: int
duration: float
resolution: str
created_at: datetime
parent_version: Optional[str]
processing_config: Dict[str, Any]
metadata: Dict[str, Any]
class OriginalVideoResponse(BaseModel):
original_id: str
file_name: str
file_path: str
file_size: int
upload_date: datetime
duration: float
resolution: str
format: str
metadata: Dict[str, Any]
versions_count: int
versions: List[str]
class ProcessingRequest(BaseModel):
original_id: str
processing_type: ProcessingType
version_name: Optional[str] = None
transcript_config: Optional[TranscriptConfig] = None
crop_config: Optional[CropConfig] = None
effects_config: Optional[EffectsConfig] = None
audio_config: Optional[AudioConfig] = None
priority: str = Field(default="normal", pattern="^(low|normal|high)$")
metadata: Dict[str, Any] = Field(default_factory=dict)
class ProcessingResponse(BaseModel):
version_id: str
original_id: str
processing_type: ProcessingType
status: VersionStatus
message: str
estimated_time: Optional[int] = None
created_at: datetime
# ======== نماذج الإحصائيات ========
class StorageStats(BaseModel):
original_videos: int
total_versions: int
originals_size: int
versions_size: int
total_size: int
class SystemStats(BaseModel):
storage: StorageStats
system_status: str
active_processes: int
queue_size: int
timestamp: datetime
# ======== نماذج التحميل والاستجابة ========
class UploadResponse(BaseModel):
original_id: str
file_name: str
file_size: int
upload_date: datetime
message: str
status: str
class VersionListResponse(BaseModel):
original_id: str
versions: List[VideoVersionResponse]
total_count: int
class VersionTreeResponse(BaseModel):
original_id: str
version_tree: Dict[str, List[str]]
versions_info: Dict[str, VideoVersionResponse]