"""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 ```` / ```` 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, }