refactor: writer.py vira pacote, um módulo por assunto
O writer tinha 4.199 linhas, das quais 3.300 numa única classe com dezoito
assuntos dentro. Achar o trecho de zoom exigia rolar por marcadores,
velocidade e legendas.
Agora é o pacote fcpxml/writer/, com um arquivo por assunto e o
FCPXMLModifier montado por composição de mixins. Mixins, e não objetos
separados, porque todas essas operações mexem no mesmo documento e nos
mesmos índices — separá-las em objetos independentes transformaria toda
chamada interna em travessia de fronteira sem nada em troca. A divisão que
importa aqui é de leitura, não de estado.
Nenhuma mudança de comportamento e nenhuma alteração nos ~50 pontos que
importam do writer: o __init__ re-exporta tudo, inclusive os nomes com
underscore que a suíte já usava.
core 723 carga, índices, navegação na spine, save
titles 600 títulos e legendas dinâmicas
cut 333 dividir, cortar faixas, apagar
speed 297 velocidade e zoom
(+ 20 módulos menores)
Único ajuste de chamada: quatro testes faziam patch em
fcpxml.writer.subprocess, que agora mora em writer.document (ver
Engine/docs/05_EXPERIENCIAS.md #23).
Lint zerado, 1441 testes passando.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1bebee4359
commit
4f5cf94443
@@ -0,0 +1,165 @@
|
||||
"""Marcadores: um, por timecode, e em lote.
|
||||
|
||||
Extraído de writer.py — ver fcpxml/writer/__init__.py para o conjunto.
|
||||
"""
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from ..models import (
|
||||
MarkerColor,
|
||||
MarkerType,
|
||||
TimeValue,
|
||||
)
|
||||
from .helpers import build_marker_element
|
||||
|
||||
|
||||
class MarkersMixin:
|
||||
"""Marcadores: um, por timecode, e em lote."""
|
||||
|
||||
# ========================================================================
|
||||
# MARKER OPERATIONS
|
||||
# ========================================================================
|
||||
|
||||
def add_marker(
|
||||
self,
|
||||
clip_id: 'str | ET.Element',
|
||||
timecode: str,
|
||||
name: str,
|
||||
marker_type: "MarkerType | str" = MarkerType.STANDARD,
|
||||
color: Optional[MarkerColor] = None,
|
||||
note: Optional[str] = None
|
||||
) -> ET.Element:
|
||||
"""
|
||||
Add a marker to a clip.
|
||||
|
||||
Args:
|
||||
clip_id: Target clip identifier (name or ID)
|
||||
timecode: Position within clip (relative to clip start)
|
||||
name: Marker label
|
||||
marker_type: STANDARD, TODO, COMPLETED, or CHAPTER (enum or string)
|
||||
color: Optional marker color
|
||||
note: Optional marker note
|
||||
|
||||
Returns:
|
||||
The created marker element
|
||||
"""
|
||||
clip = self._require_clip(clip_id)
|
||||
|
||||
if isinstance(marker_type, str):
|
||||
marker_type = MarkerType.from_string(marker_type)
|
||||
|
||||
time_value = self._parse_time(timecode)
|
||||
|
||||
return build_marker_element(
|
||||
parent=clip,
|
||||
marker_type=marker_type,
|
||||
start=time_value.to_fcpxml(),
|
||||
duration=f"1/{int(self.fps)}s",
|
||||
name=name,
|
||||
note=note,
|
||||
)
|
||||
|
||||
def add_marker_at_timeline(
|
||||
self,
|
||||
timecode: str,
|
||||
name: str,
|
||||
marker_type: "MarkerType | str" = MarkerType.STANDARD,
|
||||
color: Optional[MarkerColor] = None,
|
||||
note: Optional[str] = None
|
||||
) -> ET.Element:
|
||||
"""Add a marker at a timeline position (finds the containing clip).
|
||||
|
||||
Uses ``_find_spine_clip_at_seconds`` to walk the spine directly,
|
||||
avoiding the name-indexed ``self.clips`` dict which silently drops
|
||||
duplicate-named clips.
|
||||
"""
|
||||
if isinstance(marker_type, str):
|
||||
marker_type = MarkerType.from_string(marker_type)
|
||||
time_value = self._parse_time(timecode)
|
||||
target_seconds = time_value.to_seconds()
|
||||
|
||||
clip, relative_seconds = self._find_spine_clip_at_seconds(target_seconds)
|
||||
relative_tc = TimeValue.from_seconds(relative_seconds, self.fps)
|
||||
|
||||
return build_marker_element(
|
||||
parent=clip,
|
||||
marker_type=marker_type,
|
||||
start=relative_tc.to_fcpxml(),
|
||||
duration=f"1/{int(self.fps)}s",
|
||||
name=name,
|
||||
note=note,
|
||||
)
|
||||
|
||||
def batch_add_markers(
|
||||
self,
|
||||
markers: List[Dict[str, Any]],
|
||||
auto_at_cuts: bool = False,
|
||||
auto_at_intervals: Optional[str] = None
|
||||
) -> List[ET.Element]:
|
||||
"""
|
||||
Add multiple markers at once.
|
||||
|
||||
Args:
|
||||
markers: List of marker specs [{timecode, name, marker_type, color}]
|
||||
auto_at_cuts: Add marker at every cut point
|
||||
auto_at_intervals: Add markers at regular intervals (e.g., "00:00:30:00")
|
||||
|
||||
Returns:
|
||||
List of created marker elements
|
||||
"""
|
||||
created = []
|
||||
|
||||
# Handle explicit markers
|
||||
for m in markers:
|
||||
marker = self.add_marker_at_timeline(
|
||||
timecode=m['timecode'],
|
||||
name=m['name'],
|
||||
marker_type=MarkerType.from_string(m.get('marker_type', 'standard')),
|
||||
color=MarkerColor[m['color'].upper()] if m.get('color') else None,
|
||||
note=m.get('note')
|
||||
)
|
||||
created.append(marker)
|
||||
|
||||
# Auto-detect at cuts — add a marker at the start of every spine clip.
|
||||
if auto_at_cuts:
|
||||
for i, clip in self._iter_spine_clips():
|
||||
clip_start = clip.get('start', '0s')
|
||||
marker = build_marker_element(
|
||||
parent=clip,
|
||||
marker_type=MarkerType.STANDARD,
|
||||
start=clip_start,
|
||||
duration=f"1/{int(self.fps)}s",
|
||||
name=f"Cut {i+1}",
|
||||
)
|
||||
created.append(marker)
|
||||
|
||||
# Auto-detect at intervals — place markers at regular time steps.
|
||||
if auto_at_intervals:
|
||||
interval = self._parse_time(auto_at_intervals).to_seconds()
|
||||
total_duration = self._timeline_duration().to_seconds()
|
||||
if total_duration > 0:
|
||||
|
||||
current = interval
|
||||
count = 1
|
||||
while current < total_duration:
|
||||
try:
|
||||
clip, relative = self._find_spine_clip_at_seconds(current)
|
||||
except ValueError:
|
||||
current += interval
|
||||
count += 1
|
||||
continue
|
||||
rel_tv = TimeValue.from_seconds(relative, self.fps)
|
||||
marker = build_marker_element(
|
||||
parent=clip,
|
||||
marker_type=MarkerType.STANDARD,
|
||||
start=rel_tv.to_fcpxml(),
|
||||
duration=f"1/{int(self.fps)}s",
|
||||
name=f"Marker {count}",
|
||||
)
|
||||
created.append(marker)
|
||||
current += interval
|
||||
count += 1
|
||||
|
||||
return created
|
||||
|
||||
Reference in New Issue
Block a user