Files
gart/code/fcpxml/models/planning.py
T
João HenriqueandClaude Sonnet 5 d13f643ebc chore(fase0): higiene do repositório + corrige gitignore que escondia fcpxml/models/
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>
2026-09-23 08:28:44 -04:00

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))