Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve resolve export #500

Merged
merged 2 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions auto_editor/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from auto_editor.utils.chunks import Chunk, Chunks
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
from auto_editor.utils.container import Container, container_constructor
from auto_editor.utils.func import open_with_system_default
from auto_editor.utils.log import Log
from auto_editor.utils.types import Args

Expand Down Expand Up @@ -45,8 +46,9 @@ def set_output(

ext_map = {
"premiere": ".xml",
"resolve": ".fcpxml",
"resolve-fcp7": ".xml",
"final-cut-pro": ".fcpxml",
"resolve": ".fcpxml",
"shotcut": ".mlt",
"json": ".json",
"audio": ".wav",
Expand Down Expand Up @@ -122,8 +124,9 @@ def parse_export(export: str, log: Log) -> dict[str, Any]:
parsing: dict[str, pAttrs] = {
"default": pAttrs("default"),
"premiere": pAttrs("premiere", name_attr),
"resolve": pAttrs("resolve", name_attr),
"resolve-fcp7": pAttrs("resolve-fcp7", name_attr),
"final-cut-pro": pAttrs("final-cut-pro", name_attr),
"resolve": pAttrs("resolve", name_attr),
"shotcut": pAttrs("shotcut"),
"json": pAttrs("json", pAttr("api", 3, is_int)),
"timeline": pAttrs("json", pAttr("api", 3, is_int)),
Expand Down Expand Up @@ -176,10 +179,11 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:

del paths

output, export = set_output(args.output_file, args.export, src, log)
assert "export" in export
output, export_ops = set_output(args.output_file, args.export, src, log)
assert "export" in export_ops
export = export_ops["export"]

if export["export"] == "timeline":
if export == "timeline":
log.quiet = True

if not args.preview:
Expand All @@ -203,10 +207,10 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
if tl is None:
tl = make_timeline(sources, args, samplerate, bar, log)

if export["export"] == "timeline":
if export == "timeline":
from auto_editor.formats.json import make_json_timeline

make_json_timeline(export["api"], 0, tl, log)
make_json_timeline(export_ops["api"], 0, tl, log)
return

if args.preview:
Expand All @@ -215,25 +219,27 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
preview(tl, log)
return

if export["export"] == "json":
if export == "json":
from auto_editor.formats.json import make_json_timeline

make_json_timeline(export["api"], output, tl, log)
make_json_timeline(export_ops["api"], output, tl, log)
return

if export["export"] == "premiere":
if export in ("premiere", "resolve-fcp7"):
from auto_editor.formats.fcp7 import fcp7_write_xml

fcp7_write_xml(export["name"], output, tl, log)
is_resolve = export.startswith("resolve")
fcp7_write_xml(export_ops["name"], output, is_resolve, tl, log)
return

if export["export"] in ("final-cut-pro", "resolve"):
if export in ("final-cut-pro", "resolve"):
from auto_editor.formats.fcp11 import fcp11_write_xml

fcp11_write_xml(export["name"], ffmpeg, output, export["export"], tl, log)
is_resolve = export.startswith("resolve")
fcp11_write_xml(export_ops["name"], ffmpeg, output, is_resolve, tl, log)
return

if export["export"] == "shotcut":
if export == "shotcut":
from auto_editor.formats.shotcut import shotcut_write_mlt

shotcut_write_mlt(output, tl)
Expand Down Expand Up @@ -297,7 +303,7 @@ def make_media(tl: v3, output: str) -> None:
log,
)

if export["export"] == "clip-sequence":
if export == "clip-sequence":
if tl.v1 is None:
log.error("Timeline too complex to use clip-sequence export")

Expand Down Expand Up @@ -338,10 +344,8 @@ def pad_chunk(chunk: Chunk, total: int) -> Chunks:

log.stop_timer()

if not args.no_open and export["export"] in ("default", "audio", "clip-sequence"):
if not args.no_open and export in ("default", "audio", "clip-sequence"):
if args.player is None:
from auto_editor.utils.func import open_with_system_default

open_with_system_default(output, log)
else:
import subprocess
Expand Down
33 changes: 16 additions & 17 deletions auto_editor/formats/fcp11.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def make_name(src: FileInfo, tb: Fraction) -> str:


def fcp11_write_xml(
group_name: str, ffmpeg: FFmpeg, output: str, flavor: str, tl: v3, log: Log
group_name: str, ffmpeg: FFmpeg, output: str, resolve: bool, tl: v3, log: Log
) -> None:
def fraction(val: int) -> str:
if val == 0:
Expand All @@ -66,23 +66,22 @@ def fraction(val: int) -> str:

proj_name = src.path.stem
src_dur = int(src.duration * tl.tb)
tl_dur = src_dur if flavor == "resolve" else tl.out_len()
tl_dur = src_dur if resolve else tl.out_len()

all_srcs: list[FileInfo] = [src]
all_refs: list[str] = ["r2"]
if flavor == "resolve":
if len(src.audios) > 1:
fold = make_tracks_dir(src)

for i in range(1, len(src.audios)):
newtrack = fold / f"{i}.wav"
ffmpeg.run(
["-i", f"{src.path.resolve()}", "-map", f"0:a:{i}", f"{newtrack}"]
)
all_srcs.append(initFileInfo(f"{newtrack}", log))
all_refs.append(f"r{(i + 1) * 2}")

fcpxml = Element("fcpxml", version="1.10" if flavor == "resolve" else "1.11")
if resolve and len(src.audios) > 1:
fold = make_tracks_dir(src)

for i in range(1, len(src.audios)):
newtrack = fold / f"{i}.wav"
ffmpeg.run(
["-i", f"{src.path.resolve()}", "-map", f"0:a:{i}", f"{newtrack}"]
)
all_srcs.append(initFileInfo(f"{newtrack}", log))
all_refs.append(f"r{(i + 1) * 2}")

fcpxml = Element("fcpxml", version="1.10" if resolve else "1.11")
resources = SubElement(fcpxml, "resources")

for i, one_src in enumerate(all_srcs):
Expand Down Expand Up @@ -163,9 +162,9 @@ def make_clip(ref: str, clip: TlVideo | TlAudio, speed_warn: bool) -> bool:
warn = False
for my_ref in all_refs:
for clip in clips:
warn = make_clip(my_ref, clip, warn)
warn = make_clip(my_ref, clip, warn) or warn

if flavor == "resolve" and warn:
if resolve and warn:
log.warning(
"DaVinci Resolve may take a very long time when importing timelines with "
"speed effects. Consider switching to Premiere Pro, "
Expand Down
24 changes: 14 additions & 10 deletions auto_editor/formats/fcp7.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,10 @@ def speedup(speed: float) -> Element:

def read_filters(clipitem: Element, log: Log) -> float:
for effect_tag in clipitem:
if effect_tag.tag in ("enabled", "start", "end"):
continue
if len(effect_tag) < 3:
log.error("effect tag requires: <effectid> <name> and one <parameter>")
log.error("<effect> requires: <effectid> <name> and one <parameter>")
for i, effects in enumerate(effect_tag):
if i == 0 and effects.tag != "name":
log.error("<effect>: <name> must be first tag")
Expand Down Expand Up @@ -266,9 +268,10 @@ def xml_bool(val: str) -> bool:
if "video" in av:
tracks = valid.parse(av["video"], vclip_schema)

width = tracks["format"]["samplecharacteristics"]["width"]
height = tracks["format"]["samplecharacteristics"]["height"]
res = width, height
if "format" in tracks:
width = tracks["format"]["samplecharacteristics"]["width"]
height = tracks["format"]["samplecharacteristics"]["height"]
res = width, height

for t, track in enumerate(tracks["track"]):
if len(track["clipitem"]) > 0:
Expand Down Expand Up @@ -304,7 +307,8 @@ def xml_bool(val: str) -> bool:

if "audio" in av:
tracks = valid.parse(av["audio"], aclip_schema)
sr = tracks["format"]["samplecharacteristics"]["samplerate"]
if "format" in tracks:
sr = tracks["format"]["samplecharacteristics"]["samplerate"]

for t, track in enumerate(tracks["track"]):
if len(track["clipitem"]) > 0:
Expand Down Expand Up @@ -372,7 +376,7 @@ def media_def(
ET.SubElement(audiodef, "channelcount").text = f"{aud.channels}"


def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3, log: Log) -> None:
width, height = tl.res
timebase, ntsc = set_tb_ntsc(tl.tb)

Expand Down Expand Up @@ -437,22 +441,22 @@ def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
if clip.speed != 1:
clipitem.append(speedup(clip.speed * 100))

for i in range(len(src.audios) * 2 + 1): # `2` because stereo.
for i in range(1 + len(src.audios) * 2): # `2` because stereo.
link = ET.SubElement(clipitem, "link")
ET.SubElement(
link, "linkclipref"
).text = f"clipitem-{(i*(len(tl.v[0])))+j+1}"
ET.SubElement(link, "mediatype").text = "video" if i == 0 else "audio"
ET.SubElement(link, "trackindex").text = str(max(i, 1))
ET.SubElement(link, "clipindex").text = str(j + 1)
ET.SubElement(link, "trackindex").text = f"{max(i, 1)}"
ET.SubElement(link, "clipindex").text = f"{j + 1}"

# Audio definitions and clips
audio = ET.SubElement(media, "audio")
ET.SubElement(audio, "numOutputChannels").text = "2"
aformat = ET.SubElement(audio, "format")
aschar = ET.SubElement(aformat, "samplecharacteristics")
ET.SubElement(aschar, "depth").text = DEPTH
ET.SubElement(aschar, "samplerate").text = str(tl.sr)
ET.SubElement(aschar, "samplerate").text = f"{tl.sr}"

t = 0
for aclips in tl.a:
Expand Down