""" Pydantic schemas for YouTube analysis endpoints. """ from __future__ import annotations from typing import Dict, List, Literal, Optional from pydantic import BaseModel, Field class YouTubeAnalyzeRequest(BaseModel): url: str = Field(..., description="YouTube video URL") include_raw: bool = Field( default=False, description="Include raw service responses in output", ) class YouTubeSource(BaseModel): url: str normalized_url: str video_id: str start_time_sec: Optional[int] = None title: Optional[str] = None duration_sec: Optional[float] = None audio_format: Optional[str] = None class AnalysisSummary(BaseModel): """Summary of AI detection result.""" is_ai_generated: bool confidence: float = Field(..., ge=0.0, le=1.0) decision_source: str model_version: str indicators: List[str] class ServiceResult(BaseModel): available: bool response: Optional[Dict[str, object]] = None error: Optional[str] = None class YouTubeAnalyzeResponse(BaseModel): request_id: str status: Literal["ok", "partial"] source: YouTubeSource summary: AnalysisSummary music_ai: ServiceResult ses_analizi: ServiceResult warnings: List[str] errors: List[str] timings: Dict[str, float] class AudioAugmentationOptions(BaseModel): model_config = {"populate_by_name": True} pitch_shift: bool = Field(default=False, alias="pitchShift", description="Apply random pitch shifting") speed_change: bool = Field(default=False, alias="speedChange", description="Apply random speed change") bass_boost: bool = Field(default=False, alias="bassBoost", description="Apply bass boost equalization") trim_silence: bool = Field(default=False, alias="trimSilence", description="Trim leading and trailing silence") mix_audio: bool = Field(default=False, alias="mixAudio", description="Mix with a second uploaded audio track") add_noise: bool = Field(default=False, alias="addNoise", description="Add Gaussian noise") AudioTargetFormat = Literal["wav", "mp3", "flac", "ogg"] class AudioConvertOptions(BaseModel): model_config = {"populate_by_name": True} target_format: AudioTargetFormat = Field( default="wav", alias="targetFormat", description="Output container/codec to convert to" ) bitrate_kbps: int = Field( default=192, alias="bitrateKbps", ge=64, le=320, description="Target bitrate for lossy formats (mp3/ogg); ignored for wav/flac" ) class DatasetEntryMetadata(BaseModel): model_config = {"populate_by_name": True} duration_sec: float = Field(alias="durationSec") tempo_bpm: float = Field(alias="tempoBpm") key: str loudness_db: float = Field(alias="loudnessDb") tags: List[str] class DreamAnalysisRequest(BaseModel): dream_text: str = Field(..., alias="dreamText", min_length=1, max_length=4000) language: str = Field(default="English", description="Output language for the interpretation") model_config = {"populate_by_name": True} class DreamAnalysisResponse(BaseModel): model_config = {"populate_by_name": True} emotions: List[str] themes: List[str] symbols: List[str] interpretation: str lucidity_indicator: bool = Field(alias="lucidityIndicator") RemixCrossfadeCurve = Literal["equal_power", "linear"] class RemixOptions(BaseModel): """Options for the Creator Studio AI Remix engine — auto tempo/key matching plus a crossfade blend between two uploaded tracks.""" model_config = {"populate_by_name": True} match_tempo: bool = Field( default=True, alias="matchTempo", description="Time-stretch the second track to the first track's detected BPM" ) match_key: bool = Field( default=False, alias="matchKey", description="Pitch-shift the second track to the first track's detected key" ) crossfade_seconds: float = Field( default=4.0, alias="crossfadeSeconds", ge=0.5, le=30.0, description="Length of the crossfade transition between the two tracks" ) crossfade_curve: RemixCrossfadeCurve = Field( default="equal_power", alias="crossfadeCurve", description="Crossfade power curve — equal_power avoids a volume dip at the midpoint" ) class RemixAnalysis(BaseModel): """Reports what the remix engine actually detected/did to each track, so the UI can show real numbers instead of a black box.""" model_config = {"populate_by_name": True} track_a_bpm: float = Field(alias="trackABpm") track_b_bpm: float = Field(alias="trackBBpm") track_a_key: str = Field(alias="trackAKey") track_b_key: str = Field(alias="trackBKey") applied_stretch_rate: float = Field(alias="appliedStretchRate") applied_pitch_shift_semitones: float = Field(alias="appliedPitchShiftSemitones") output_duration_sec: float = Field(alias="outputDurationSec") class MultitrackChannelSettings(BaseModel): """Per-track mix settings for the Creator Studio Multitrack mixer. Index-matched to the uploaded files (channel 0 -> first file, etc.).""" model_config = {"populate_by_name": True} gain_db: float = Field( default=0.0, alias="gainDb", ge=-60.0, le=12.0, description="Gain applied to this track before mixing, in dB" ) pan: float = Field( default=0.0, ge=-1.0, le=1.0, description="Stereo position: -1.0 full left, 0.0 center, 1.0 full right" ) muted: bool = Field(default=False, description="Exclude this track from the mix entirely") class MultitrackOptions(BaseModel): model_config = {"populate_by_name": True} channels: List[MultitrackChannelSettings] = Field( default_factory=list, description="Per-track settings, index-matched to the uploaded files" ) normalize_output: bool = Field( default=True, alias="normalizeOutput", description="Peak-normalize the final mix to prevent clipping" ) class MultitrackChannelReport(BaseModel): """What actually happened to one channel during mixdown.""" model_config = {"populate_by_name": True} index: int duration_sec: float = Field(alias="durationSec") applied_gain_db: float = Field(alias="appliedGainDb") pan: float muted: bool peak_level: float = Field(alias="peakLevel") class MultitrackAnalysis(BaseModel): model_config = {"populate_by_name": True} channels: List[MultitrackChannelReport] output_duration_sec: float = Field(alias="outputDurationSec") output_peak_level: float = Field(alias="outputPeakLevel") clipping_prevented: bool = Field(alias="clippingPrevented")