Fase 0 do roteiro de reestruturação (Engine/docs/10_MAPA_REESTRUTURACAO.md): move code/WHISPERX (2,6 GB de backups órfãos, sem uso ativo, sem .gitmodules) para ~/Archives/G-ART-WHISPERX-backup fora do workspace git; traz admin/ para o gate de lint de run_after_fix.sh; corrige fcpxml/writer/adjustment.py, que gerava um wrapper <adjustment> inexistente no DTD 1.13 (filtros agora vão direto no <clip>, na ordem exigida), com teste de regressão novo. Achado à parte: .gitignore tinha uma regra solta "models/" (pensada só para o cache do Whisper em code/models/) que também escondia do git todo o pacote fcpxml/models/ — nunca commitado, sem proteção nenhuma. Corrigida para /code/models/, ancorada na raiz. Docs atualizados no mesmo commit (02_MODULES, 09_MANUTENCAO, 10_MAPA_REESTRUTURACAO, 05_EXPERIENCIAS #34 e #36), conforme a regra do CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
94 lines
3.2 KiB
Python
94 lines
3.2 KiB
Python
"""Especificações de geração: rough cut, ritmo e montagem.
|
|
|
|
Extraído de models.py — ver fcpxml/models/__init__.py.
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import List, Optional, Tuple
|
|
|
|
from .enums import PacingCurve
|
|
|
|
|
|
@dataclass
|
|
class SegmentSpec:
|
|
"""Specification for a segment in auto rough cut."""
|
|
name: str
|
|
keywords: List[str] = field(default_factory=list)
|
|
duration_seconds: float = 0.0
|
|
priority: str = "best" # favorites, longest, shortest, random, best
|
|
|
|
@dataclass
|
|
class PacingConfig:
|
|
"""Configuration for rough cut pacing."""
|
|
pacing: str = "medium" # slow, medium, fast, dynamic
|
|
min_clip_duration: float = 1.0
|
|
max_clip_duration: float = 8.0
|
|
avg_clip_duration: Optional[float] = None
|
|
vary_pacing: bool = True
|
|
|
|
def get_duration_range(self) -> Tuple[float, float]:
|
|
"""Get min/max based on pacing style."""
|
|
ranges = {
|
|
"slow": (5.0, 10.0),
|
|
"medium": (2.0, 5.0),
|
|
"fast": (0.5, 2.0),
|
|
"dynamic": (1.0, 6.0),
|
|
}
|
|
return ranges.get(self.pacing, (2.0, 5.0))
|
|
|
|
@dataclass
|
|
class RoughCutResult:
|
|
"""Result of auto rough cut generation."""
|
|
output_path: str
|
|
clips_used: int
|
|
clips_available: int
|
|
target_duration: float
|
|
actual_duration: float
|
|
segments: int
|
|
average_clip_duration: float
|
|
|
|
@dataclass
|
|
class MontageConfig:
|
|
"""Configuration for montage generation with pacing curves."""
|
|
target_duration: float # Target duration in seconds
|
|
pacing_curve: 'PacingCurve'
|
|
start_duration: float = 2.0 # Clip duration at start
|
|
end_duration: float = 0.5 # Clip duration at end
|
|
min_duration: float = 0.2 # Minimum allowed clip duration
|
|
max_duration: float = 5.0 # Maximum allowed clip duration
|
|
|
|
def get_duration_at_position(self, position: float) -> float:
|
|
"""
|
|
Calculate clip duration for a given position (0.0 to 1.0).
|
|
|
|
Args:
|
|
position: Position in montage (0.0 = start, 1.0 = end)
|
|
|
|
Returns:
|
|
Target duration in seconds for a clip at this position
|
|
"""
|
|
if self.pacing_curve == PacingCurve.CONSTANT:
|
|
duration = (self.start_duration + self.end_duration) / 2
|
|
|
|
elif self.pacing_curve == PacingCurve.ACCELERATING:
|
|
# Linear interpolation from start to end duration
|
|
duration = self.start_duration + (self.end_duration - self.start_duration) * position
|
|
|
|
elif self.pacing_curve == PacingCurve.DECELERATING:
|
|
# Reverse: start fast, end slow
|
|
duration = self.end_duration + (self.start_duration - self.end_duration) * position
|
|
|
|
elif self.pacing_curve == PacingCurve.PYRAMID:
|
|
# Slow → fast → slow (parabolic curve)
|
|
if position < 0.5:
|
|
# First half: slow to fast
|
|
duration = self.start_duration + (self.end_duration - self.start_duration) * (position * 2)
|
|
else:
|
|
# Second half: fast to slow
|
|
duration = self.end_duration + (self.start_duration - self.end_duration) * ((position - 0.5) * 2)
|
|
else:
|
|
duration = self.start_duration
|
|
|
|
# Clamp to min/max
|
|
return max(self.min_duration, min(self.max_duration, duration))
|