chore: atualização geral
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
"""Export / relink — tool schemas and handlers.
|
||||
|
||||
Extracted from server.py; see Engine/docs/03_SERVER_TOOLS.md for the tool catalog.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from mcp.types import TextContent, Tool
|
||||
|
||||
from fcpxml.export import DaVinciExporter
|
||||
from fcpxml.writer import FCPXMLModifier
|
||||
from server_tools._shared import (
|
||||
_require_timeline,
|
||||
_resolve_io_paths,
|
||||
_setup_modifier,
|
||||
_text_result,
|
||||
_validate_filepath,
|
||||
format_timecode,
|
||||
)
|
||||
|
||||
TOOLS = [
|
||||
Tool(
|
||||
name="export_edl",
|
||||
description="Generate EDL (Edit Decision List) from timeline",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {"filepath": {"type": "string"}},
|
||||
"required": ["filepath"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="export_csv",
|
||||
description="Export timeline data to CSV format",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string"},
|
||||
"include": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
"required": ["filepath"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="export_resolve_xml",
|
||||
description="Export timeline as DaVinci Resolve compatible FCPXML (simplified v1.9)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to FCPXML file"},
|
||||
"flatten_compounds": {"type": "boolean", "default": True, "description": "Flatten compound clips for compatibility"},
|
||||
"output_path": {"type": "string", "description": "Output path (default: adds _resolve suffix)"},
|
||||
},
|
||||
"required": ["filepath"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="export_fcp7_xml",
|
||||
description="Export timeline as FCP7 XML (XMEML) for Premiere Pro, DaVinci Resolve, and Avid compatibility",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to FCPXML file"},
|
||||
"output_path": {"type": "string", "description": "Output path (default: adds _fcp7.xml suffix)"},
|
||||
},
|
||||
"required": ["filepath"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="relink_media",
|
||||
description="Bulk-rewrite media source paths (asset/media-rep src URLs) to relink moved or renamed media folders without opening FCP. Prefix-based: find='/Volumes/OldDrive/Media' replace='/Volumes/NewDrive/Media'. Use dry_run to preview.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filepath": {"type": "string", "description": "Path to FCPXML file or .fcpxmld bundle"},
|
||||
"find": {"type": "string", "description": "Old path prefix to match (plain path or file:// URL)"},
|
||||
"replace": {"type": "string", "description": "New path prefix to substitute"},
|
||||
"dry_run": {"type": "boolean", "description": "Preview changes without writing", "default": False},
|
||||
"output_path": {"type": "string", "description": "Output path (default: adds _relinked suffix)"},
|
||||
},
|
||||
"required": ["filepath", "find", "replace"]
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def handle_export_edl(arguments: dict) -> Sequence[TextContent]:
|
||||
project, tl = _require_timeline(arguments["filepath"])
|
||||
edl = f"TITLE: {tl.name}\nFCM: NON-DROP FRAME\n\n"
|
||||
for i, c in enumerate(tl.clips, 1):
|
||||
edl += f"{i:03d} AX V C {format_timecode(c.source_start)} {format_timecode(c.end)} {format_timecode(c.start)} {format_timecode(c.end)}\n"
|
||||
edl += f"* FROM CLIP NAME: {c.name}\n\n"
|
||||
return _text_result(f"```edl\n{edl}```")
|
||||
|
||||
|
||||
async def handle_export_csv(arguments: dict) -> Sequence[TextContent]:
|
||||
project, tl = _require_timeline(arguments["filepath"])
|
||||
csv = "Name,Start,End,Duration,Keywords\n"
|
||||
for c in tl.clips:
|
||||
kws = "|".join(k.value for k in c.keywords)
|
||||
csv += f'"{c.name}",{format_timecode(c.start)},{format_timecode(c.end)},{c.duration_seconds:.3f},"{kws}"\n'
|
||||
return _text_result(f"```csv\n{csv}```")
|
||||
|
||||
|
||||
async def handle_export_resolve_xml(arguments: dict) -> Sequence[TextContent]:
|
||||
filepath, output_path = _resolve_io_paths(arguments, "_resolve")
|
||||
exporter = DaVinciExporter(filepath)
|
||||
exporter.export_simplified_fcpxml(
|
||||
output_path,
|
||||
flatten_compounds=arguments.get("flatten_compounds", True),
|
||||
)
|
||||
return _text_result((
|
||||
f"# Exported for DaVinci Resolve\n\n"
|
||||
f"- **Format**: Simplified FCPXML v1.9\n"
|
||||
f"- **Compound clips flattened**: {arguments.get('flatten_compounds', True)}\n\n"
|
||||
f"Saved to: `{output_path}`\n\n"
|
||||
f"**Next step**: In DaVinci Resolve, go to File > Import > Timeline > Import AAF/EDL/XML"
|
||||
))
|
||||
|
||||
|
||||
async def handle_export_fcp7_xml(arguments: dict) -> Sequence[TextContent]:
|
||||
filepath, output_path = _resolve_io_paths(arguments, "_fcp7")
|
||||
exporter = DaVinciExporter(filepath)
|
||||
exporter.export_xmeml(output_path)
|
||||
return _text_result((
|
||||
f"# Exported as FCP7 XML (XMEML)\n\n"
|
||||
f"- **Format**: XMEML v5\n"
|
||||
f"- **Compatible with**: Premiere Pro, DaVinci Resolve, Avid Media Composer\n\n"
|
||||
f"Saved to: `{output_path}`\n\n"
|
||||
f"**Next step**: Import via File > Import in your target NLE"
|
||||
))
|
||||
|
||||
|
||||
async def handle_relink_media(arguments: dict) -> Sequence[TextContent]:
|
||||
dry_run = arguments.get("dry_run", False)
|
||||
if dry_run:
|
||||
filepath = _validate_filepath(arguments["filepath"], ('.fcpxml', '.fcpxmld'))
|
||||
modifier = FCPXMLModifier(filepath)
|
||||
result = modifier.relink_media(
|
||||
arguments["find"], arguments["replace"], dry_run=True
|
||||
)
|
||||
footer = "Dry run — no file written."
|
||||
else:
|
||||
filepath, output_path, modifier = _setup_modifier(arguments, "_relinked")
|
||||
result = modifier.relink_media(arguments["find"], arguments["replace"])
|
||||
saved = modifier.save(output_path)
|
||||
footer = f"Saved to: {saved}"
|
||||
|
||||
if not result["relinked"]:
|
||||
return _text_result(
|
||||
f"No media paths matched prefix '{arguments['find']}' "
|
||||
f"({result['total_assets']} assets scanned). Nothing to relink."
|
||||
)
|
||||
|
||||
lines = [
|
||||
f"{'Would relink' if dry_run else 'Relinked'} "
|
||||
f"{result['relinked']} media reference(s) "
|
||||
f"across {result['total_assets']} asset(s):",
|
||||
"",
|
||||
]
|
||||
missing = 0
|
||||
for change in result["changes"]:
|
||||
mark = "✓" if change["target_exists"] else "⚠ target missing"
|
||||
if not change["target_exists"]:
|
||||
missing += 1
|
||||
lines.append(f" {change['asset']}: {change['new']} [{mark}]")
|
||||
if missing:
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"⚠ {missing} new path(s) do not exist on this machine — "
|
||||
f"FCP will show those clips as missing until the media is present."
|
||||
)
|
||||
lines.append("")
|
||||
lines.append(footer)
|
||||
return _text_result("\n".join(lines))
|
||||
|
||||
|
||||
HANDLERS = {
|
||||
"export_edl": handle_export_edl,
|
||||
"export_csv": handle_export_csv,
|
||||
"export_resolve_xml": handle_export_resolve_xml,
|
||||
"export_fcp7_xml": handle_export_fcp7_xml,
|
||||
"relink_media": handle_relink_media,
|
||||
}
|
||||
Reference in New Issue
Block a user