Files
gart/code/fcpxml/writer/__init__.py
João HenriqueandClaude Opus 5 4f5cf94443 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>
2026-08-19 21:38:49 -04:00

131 lines
4.3 KiB
Python

"""
FCPXML Writer — Generate and modify Final Cut Pro XML files.
This package provides two complementary workflows for working with FCPXML:
**Generation** (``FCPXMLWriter``, in :mod:`.generator`):
Build a new FCPXML document from Python dataclass objects (``Project``,
``Timeline``, ``Clip``, ``Marker``). Useful for creating rough cuts,
montage exports, and template-based projects.
**Modification** (``FCPXMLModifier``, in :mod:`.modifier`):
Load an existing FCPXML file, apply surgical edits (markers, trims,
reorders, transitions, speed changes, silence removal, etc.), and save.
This is the primary API used by the MCP server's tool handlers.
Layout
------
This was one 4.200-line module. It is now one module per subject, because the
subjects barely touch each other: whoever is fixing a zoom ramp has no reason
to scroll past subtitle layout to find it.
helpers sanitising, scales, shared element builders
document asset creation, timebases, serialisation (``write_fcpxml``)
validation structural checks (``validate_fcpxml``)
core ``ModifierCore``: load, indices, spine navigation, ``save``
<subject> one mixin per editing subject (markers, trim, speed, …)
modifier ``FCPXMLModifier`` = core + every mixin
generator ``FCPXMLWriter``
api one-line convenience wrappers
Everything the rest of the project imported from the old module is re-exported
here, so ``from fcpxml.writer import FCPXMLModifier`` keeps working unchanged —
including the underscore-prefixed helpers the test suite reaches for.
Architecture notes
------------------
- All time arithmetic uses ``TimeValue`` (rational fractions) — never floats —
to match FCPXML's native ``"600/2400s"`` format and avoid rounding drift.
- The ``FCPXMLModifier`` builds three in-memory indices at init
(``clips``, ``resources``, ``formats``) so lookups are O(1) by ID/name.
- Spine-based editing: clips live inside a ``<spine>`` element (the primary
storyline). Connected clips attach via ``lane`` attributes on spine clips.
Most editing methods find the target clip in the spine, mutate it, then
ripple offsets on subsequent siblings.
- ``write_fcpxml()`` handles DTD-compliant serialisation and optional
timebase enforcement for all output paths.
"""
from ..models import TimeValue
from .api import add_marker_to_file, modify_fcpxml, trim_clip_in_file
from .core import ModifierCore
from .document import (
_STILL_IMAGE_EXTENSIONS,
_enforce_standard_timebases,
_ensure_video_asset,
write_fcpxml,
)
from .generator import FCPXMLWriter
from .helpers import (
_ASSET_CLIP_CHILD_ORDER,
_CHILD_ORDER_INDEX,
_MAX_MARKER_NAME_LENGTH,
_MAX_NOTE_LENGTH,
CLIP_AND_AUDIO_TAGS,
CLIP_TAGS,
FCP_EFFECTS,
HOLD_AT_CUT_THRESHOLD,
SPINE_ELEMENT_TAGS,
START_AT_CUT_THRESHOLD,
_create_asset_element,
_dtd_insert,
_fmt_scale,
_probe_audio_info,
_sanitize_xml_value,
build_marker_element,
list_effects,
)
from .modifier import FCPXMLModifier
from .validation import (
_check_asset_sources,
_check_child_order,
_check_effect_refs,
_check_frame_alignment,
_check_required_attributes,
_check_timebases,
_document_frame_duration,
validate_fcpxml,
)
__all__ = [
"FCPXMLModifier",
"FCPXMLWriter",
"ModifierCore",
"TimeValue",
"FCP_EFFECTS",
"CLIP_TAGS",
"CLIP_AND_AUDIO_TAGS",
"SPINE_ELEMENT_TAGS",
"HOLD_AT_CUT_THRESHOLD",
"START_AT_CUT_THRESHOLD",
"add_marker_to_file",
"build_marker_element",
"list_effects",
"modify_fcpxml",
"trim_clip_in_file",
"validate_fcpxml",
"write_fcpxml",
# Internos que o resto do projeto (e a suíte) já importava deste módulo
# quando ele era um arquivo só. Ficam aqui para a divisão não virar uma
# quebra de API disfarçada de reorganização.
"_ASSET_CLIP_CHILD_ORDER",
"_CHILD_ORDER_INDEX",
"_MAX_MARKER_NAME_LENGTH",
"_MAX_NOTE_LENGTH",
"_STILL_IMAGE_EXTENSIONS",
"_check_asset_sources",
"_check_child_order",
"_check_effect_refs",
"_check_frame_alignment",
"_check_required_attributes",
"_check_timebases",
"_create_asset_element",
"_document_frame_duration",
"_dtd_insert",
"_enforce_standard_timebases",
"_ensure_video_asset",
"_fmt_scale",
"_probe_audio_info",
"_sanitize_xml_value",
]