Files
gart/code/fcpxml/writer/relink.py
T
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

95 lines
3.5 KiB
Python

"""Repontar a mídia de um projeto para novos arquivos.
Extraído de writer.py — ver fcpxml/writer/__init__.py para o conjunto.
"""
from pathlib import Path
from typing import Any, Dict
class RelinkMixin:
"""Repontar a mídia de um projeto para novos arquivos."""
# ========================================================================
# MEDIA RELINK
# ========================================================================
def relink_media(
self,
find: str,
replace: str,
dry_run: bool = False,
) -> Dict[str, Any]:
"""Bulk-rewrite media source paths (programmatic relink).
Rewrites the ``src`` of every ``<asset>`` / ``<media-rep>`` whose
path starts with *find*, substituting *replace* — the standard
technique for relinking a moved or renamed media folder without
opening Final Cut Pro. FCP relinks via the ``media-rep`` file URL
on import; the device-specific bookmark blob is left untouched
(FCP regenerates it).
*find* / *replace* accept plain paths (``/Volumes/OldDrive``) or
``file://`` URLs; percent-encoding in existing URLs is handled.
Matching is prefix-based on whole path segments, so ``/Media/A``
matches ``/Media/A/clip.mov`` but not ``/Media/AB/clip.mov``.
Args:
find: Old path prefix to match.
replace: New path prefix to substitute.
dry_run: When True, report what would change without
mutating the tree.
Returns:
Summary dict: ``total_assets``, ``relinked`` (reference
count), ``dry_run``, and ``changes`` — a list of
``{asset, old, new, target_exists}`` entries
(``target_exists`` checks the new path on this machine).
"""
from urllib.parse import quote, unquote, urlparse
def _to_path(value: str) -> str:
if value.startswith('file://'):
return unquote(urlparse(value).path)
return value
find_path = _to_path(find).rstrip('/')
replace_path = _to_path(replace).rstrip('/')
if not find_path:
raise ValueError("relink_media: 'find' must be a non-empty path prefix")
changes = []
for asset_id, info in self.resources.items():
elem = info['element']
targets = [(elem, elem.get('src'))]
media_rep = elem.find('media-rep')
if media_rep is not None:
targets.append((media_rep, media_rep.get('src')))
for node, old_src in targets:
if not old_src:
continue
was_url = old_src.startswith('file://')
old_path = _to_path(old_src)
if old_path != find_path and not old_path.startswith(find_path + '/'):
continue
new_path = replace_path + old_path[len(find_path):]
new_src = 'file://' + quote(new_path) if was_url else new_path
if not dry_run:
node.set('src', new_src)
info['src'] = new_src
changes.append({
'asset': info.get('name') or asset_id,
'old': old_src,
'new': new_src,
'target_exists': Path(new_path).exists(),
})
return {
'total_assets': len(self.resources),
'relinked': len(changes),
'dry_run': dry_run,
'changes': changes,
}