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>
131 lines
4.3 KiB
Python
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",
|
|
]
|