auto-editor 25.0.1__py3-none-any.whl → 25.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- auto_editor/__init__.py +1 -1
- auto_editor/__main__.py +3 -0
- auto_editor/edit.py +22 -18
- auto_editor/formats/fcp11.py +16 -26
- auto_editor/formats/fcp7.py +144 -74
- auto_editor/lang/libintrospection.py +10 -0
- auto_editor/lang/palet.py +140 -1240
- auto_editor/lang/stdenv.py +1190 -0
- auto_editor/lib/contracts.py +4 -0
- auto_editor/lib/data_structs.py +4 -2
- auto_editor/make_layers.py +14 -0
- auto_editor/render/subtitle.py +3 -1
- auto_editor/render/video.py +2 -19
- auto_editor/subcommands/palet.py +2 -0
- auto_editor/subcommands/repl.py +3 -2
- auto_editor/subcommands/test.py +3 -0
- auto_editor/timeline.py +1 -7
- auto_editor/utils/cmdkw.py +5 -11
- auto_editor/utils/types.py +1 -7
- {auto_editor-25.0.1.dist-info → auto_editor-25.2.0.dist-info}/METADATA +4 -4
- {auto_editor-25.0.1.dist-info → auto_editor-25.2.0.dist-info}/RECORD +26 -23
- {auto_editor-25.0.1.dist-info → auto_editor-25.2.0.dist-info}/WHEEL +1 -1
- {auto_editor-25.0.1.dist-info → auto_editor-25.2.0.dist-info}/top_level.txt +1 -0
- docs/build.py +53 -0
- {auto_editor-25.0.1.dist-info → auto_editor-25.2.0.dist-info}/LICENSE +0 -0
- {auto_editor-25.0.1.dist-info → auto_editor-25.2.0.dist-info}/entry_points.txt +0 -0
auto_editor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "25.0
|
1
|
+
__version__ = "25.2.0"
|
auto_editor/__main__.py
CHANGED
@@ -259,6 +259,9 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
259
259
|
metavar="CMD",
|
260
260
|
help="Add extra options for ffmpeg. Must be in quotes",
|
261
261
|
)
|
262
|
+
parser.add_argument(
|
263
|
+
"--config", flag=True, help="When set, look for `config.pal` and run it"
|
264
|
+
)
|
262
265
|
parser.add_argument(
|
263
266
|
"--no-cache", flag=True, help="Don't look for or write a cache file"
|
264
267
|
)
|
auto_editor/edit.py
CHANGED
@@ -15,6 +15,7 @@ from auto_editor.utils.bar import Bar
|
|
15
15
|
from auto_editor.utils.chunks import Chunk, Chunks
|
16
16
|
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
|
17
17
|
from auto_editor.utils.container import Container, container_constructor
|
18
|
+
from auto_editor.utils.func import open_with_system_default
|
18
19
|
from auto_editor.utils.log import Log
|
19
20
|
from auto_editor.utils.types import Args
|
20
21
|
|
@@ -45,8 +46,9 @@ def set_output(
|
|
45
46
|
|
46
47
|
ext_map = {
|
47
48
|
"premiere": ".xml",
|
48
|
-
"resolve": ".
|
49
|
+
"resolve-fcp7": ".xml",
|
49
50
|
"final-cut-pro": ".fcpxml",
|
51
|
+
"resolve": ".fcpxml",
|
50
52
|
"shotcut": ".mlt",
|
51
53
|
"json": ".json",
|
52
54
|
"audio": ".wav",
|
@@ -122,8 +124,9 @@ def parse_export(export: str, log: Log) -> dict[str, Any]:
|
|
122
124
|
parsing: dict[str, pAttrs] = {
|
123
125
|
"default": pAttrs("default"),
|
124
126
|
"premiere": pAttrs("premiere", name_attr),
|
125
|
-
"resolve": pAttrs("resolve", name_attr),
|
127
|
+
"resolve-fcp7": pAttrs("resolve-fcp7", name_attr),
|
126
128
|
"final-cut-pro": pAttrs("final-cut-pro", name_attr),
|
129
|
+
"resolve": pAttrs("resolve", name_attr),
|
127
130
|
"shotcut": pAttrs("shotcut"),
|
128
131
|
"json": pAttrs("json", pAttr("api", 3, is_int)),
|
129
132
|
"timeline": pAttrs("json", pAttr("api", 3, is_int)),
|
@@ -176,10 +179,11 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
176
179
|
|
177
180
|
del paths
|
178
181
|
|
179
|
-
output,
|
180
|
-
assert "export" in
|
182
|
+
output, export_ops = set_output(args.output_file, args.export, src, log)
|
183
|
+
assert "export" in export_ops
|
184
|
+
export = export_ops["export"]
|
181
185
|
|
182
|
-
if export
|
186
|
+
if export == "timeline":
|
183
187
|
log.quiet = True
|
184
188
|
|
185
189
|
if not args.preview:
|
@@ -203,10 +207,10 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
203
207
|
if tl is None:
|
204
208
|
tl = make_timeline(sources, args, samplerate, bar, log)
|
205
209
|
|
206
|
-
if export
|
210
|
+
if export == "timeline":
|
207
211
|
from auto_editor.formats.json import make_json_timeline
|
208
212
|
|
209
|
-
make_json_timeline(
|
213
|
+
make_json_timeline(export_ops["api"], 0, tl, log)
|
210
214
|
return
|
211
215
|
|
212
216
|
if args.preview:
|
@@ -215,25 +219,27 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
215
219
|
preview(tl, log)
|
216
220
|
return
|
217
221
|
|
218
|
-
if export
|
222
|
+
if export == "json":
|
219
223
|
from auto_editor.formats.json import make_json_timeline
|
220
224
|
|
221
|
-
make_json_timeline(
|
225
|
+
make_json_timeline(export_ops["api"], output, tl, log)
|
222
226
|
return
|
223
227
|
|
224
|
-
if export
|
228
|
+
if export in ("premiere", "resolve-fcp7"):
|
225
229
|
from auto_editor.formats.fcp7 import fcp7_write_xml
|
226
230
|
|
227
|
-
|
231
|
+
is_resolve = export.startswith("resolve")
|
232
|
+
fcp7_write_xml(export_ops["name"], output, is_resolve, tl, log)
|
228
233
|
return
|
229
234
|
|
230
|
-
if export
|
235
|
+
if export in ("final-cut-pro", "resolve"):
|
231
236
|
from auto_editor.formats.fcp11 import fcp11_write_xml
|
232
237
|
|
233
|
-
|
238
|
+
is_resolve = export.startswith("resolve")
|
239
|
+
fcp11_write_xml(export_ops["name"], ffmpeg, output, is_resolve, tl, log)
|
234
240
|
return
|
235
241
|
|
236
|
-
if export
|
242
|
+
if export == "shotcut":
|
237
243
|
from auto_editor.formats.shotcut import shotcut_write_mlt
|
238
244
|
|
239
245
|
shotcut_write_mlt(output, tl)
|
@@ -297,7 +303,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
297
303
|
log,
|
298
304
|
)
|
299
305
|
|
300
|
-
if export
|
306
|
+
if export == "clip-sequence":
|
301
307
|
if tl.v1 is None:
|
302
308
|
log.error("Timeline too complex to use clip-sequence export")
|
303
309
|
|
@@ -338,10 +344,8 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
338
344
|
|
339
345
|
log.stop_timer()
|
340
346
|
|
341
|
-
if not args.no_open and export
|
347
|
+
if not args.no_open and export in ("default", "audio", "clip-sequence"):
|
342
348
|
if args.player is None:
|
343
|
-
from auto_editor.utils.func import open_with_system_default
|
344
|
-
|
345
349
|
open_with_system_default(output, log)
|
346
350
|
else:
|
347
351
|
import subprocess
|
auto_editor/formats/fcp11.py
CHANGED
@@ -54,7 +54,7 @@ def make_name(src: FileInfo, tb: Fraction) -> str:
|
|
54
54
|
|
55
55
|
|
56
56
|
def fcp11_write_xml(
|
57
|
-
group_name: str, ffmpeg: FFmpeg, output: str,
|
57
|
+
group_name: str, ffmpeg: FFmpeg, output: str, resolve: bool, tl: v3, log: Log
|
58
58
|
) -> None:
|
59
59
|
def fraction(val: int) -> str:
|
60
60
|
if val == 0:
|
@@ -66,23 +66,22 @@ def fcp11_write_xml(
|
|
66
66
|
|
67
67
|
proj_name = src.path.stem
|
68
68
|
src_dur = int(src.duration * tl.tb)
|
69
|
-
tl_dur = src_dur if
|
69
|
+
tl_dur = src_dur if resolve else tl.out_len()
|
70
70
|
|
71
71
|
all_srcs: list[FileInfo] = [src]
|
72
72
|
all_refs: list[str] = ["r2"]
|
73
|
-
if
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
fcpxml = Element("fcpxml", version="1.10" if flavor == "resolve" else "1.11")
|
73
|
+
if resolve and len(src.audios) > 1:
|
74
|
+
fold = make_tracks_dir(src)
|
75
|
+
|
76
|
+
for i in range(1, len(src.audios)):
|
77
|
+
newtrack = fold / f"{i}.wav"
|
78
|
+
ffmpeg.run(
|
79
|
+
["-i", f"{src.path.resolve()}", "-map", f"0:a:{i}", f"{newtrack}"]
|
80
|
+
)
|
81
|
+
all_srcs.append(initFileInfo(f"{newtrack}", log))
|
82
|
+
all_refs.append(f"r{(i + 1) * 2}")
|
83
|
+
|
84
|
+
fcpxml = Element("fcpxml", version="1.10" if resolve else "1.11")
|
86
85
|
resources = SubElement(fcpxml, "resources")
|
87
86
|
|
88
87
|
for i, one_src in enumerate(all_srcs):
|
@@ -134,7 +133,7 @@ def fcp11_write_xml(
|
|
134
133
|
else:
|
135
134
|
clips = []
|
136
135
|
|
137
|
-
def make_clip(ref: str, clip: TlVideo | TlAudio
|
136
|
+
def make_clip(ref: str, clip: TlVideo | TlAudio) -> None:
|
138
137
|
clip_properties = {
|
139
138
|
"name": proj_name,
|
140
139
|
"ref": ref,
|
@@ -148,7 +147,6 @@ def fcp11_write_xml(
|
|
148
147
|
# See the "Time Maps" section.
|
149
148
|
# https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference/story_elements/timemap/
|
150
149
|
|
151
|
-
speed_warn = True
|
152
150
|
timemap = SubElement(asset, "timeMap")
|
153
151
|
SubElement(timemap, "timept", time="0s", value="0s", interp="smooth2")
|
154
152
|
SubElement(
|
@@ -158,19 +156,11 @@ def fcp11_write_xml(
|
|
158
156
|
value=fraction(src_dur),
|
159
157
|
interp="smooth2",
|
160
158
|
)
|
161
|
-
return speed_warn
|
162
159
|
|
163
|
-
warn = False
|
164
160
|
for my_ref in all_refs:
|
165
161
|
for clip in clips:
|
166
|
-
|
162
|
+
make_clip(my_ref, clip)
|
167
163
|
|
168
|
-
if flavor == "resolve" and warn:
|
169
|
-
log.warning(
|
170
|
-
"DaVinci Resolve may take a very long time when importing timelines with "
|
171
|
-
"speed effects. Consider switching to Premiere Pro, "
|
172
|
-
"Final Cut Pro, or ShotCut (free)"
|
173
|
-
)
|
174
164
|
tree = ElementTree(fcpxml)
|
175
165
|
indent(tree, space="\t", level=0)
|
176
166
|
tree.write(output, xml_declaration=True, encoding="utf-8")
|
auto_editor/formats/fcp7.py
CHANGED
@@ -151,8 +151,10 @@ SUPPORTED_EFFECTS = ("timeremap",)
|
|
151
151
|
|
152
152
|
def read_filters(clipitem: Element, log: Log) -> float:
|
153
153
|
for effect_tag in clipitem:
|
154
|
+
if effect_tag.tag in ("enabled", "start", "end"):
|
155
|
+
continue
|
154
156
|
if len(effect_tag) < 3:
|
155
|
-
log.error("effect
|
157
|
+
log.error("<effect> requires: <effectid> <name> and one <parameter>")
|
156
158
|
for i, effects in enumerate(effect_tag):
|
157
159
|
if i == 0 and effects.tag != "name":
|
158
160
|
log.error("<effect>: <name> must be first tag")
|
@@ -266,9 +268,10 @@ def fcp7_read_xml(path: str, log: Log) -> v3:
|
|
266
268
|
if "video" in av:
|
267
269
|
tracks = valid.parse(av["video"], vclip_schema)
|
268
270
|
|
269
|
-
|
270
|
-
|
271
|
-
|
271
|
+
if "format" in tracks:
|
272
|
+
width = tracks["format"]["samplecharacteristics"]["width"]
|
273
|
+
height = tracks["format"]["samplecharacteristics"]["height"]
|
274
|
+
res = width, height
|
272
275
|
|
273
276
|
for t, track in enumerate(tracks["track"]):
|
274
277
|
if len(track["clipitem"]) > 0:
|
@@ -304,7 +307,8 @@ def fcp7_read_xml(path: str, log: Log) -> v3:
|
|
304
307
|
|
305
308
|
if "audio" in av:
|
306
309
|
tracks = valid.parse(av["audio"], aclip_schema)
|
307
|
-
|
310
|
+
if "format" in tracks:
|
311
|
+
sr = tracks["format"]["samplecharacteristics"]["samplerate"]
|
308
312
|
|
309
313
|
for t, track in enumerate(tracks["track"]):
|
310
314
|
if len(track["clipitem"]) > 0:
|
@@ -344,6 +348,13 @@ def media_def(
|
|
344
348
|
ET.SubElement(filedef, "name").text = src.path.stem
|
345
349
|
ET.SubElement(filedef, "pathurl").text = url
|
346
350
|
|
351
|
+
timecode = ET.SubElement(filedef, "timecode")
|
352
|
+
ET.SubElement(timecode, "string").text = "00:00:00:00"
|
353
|
+
ET.SubElement(timecode, "displayformat").text = "NDF"
|
354
|
+
rate = ET.SubElement(timecode, "rate")
|
355
|
+
ET.SubElement(rate, "timebase").text = f"{tb}"
|
356
|
+
ET.SubElement(rate, "ntsc").text = ntsc
|
357
|
+
|
347
358
|
rate = ET.SubElement(filedef, "rate")
|
348
359
|
ET.SubElement(rate, "timebase").text = f"{tb}"
|
349
360
|
ET.SubElement(rate, "ntsc").text = ntsc
|
@@ -372,87 +383,53 @@ def media_def(
|
|
372
383
|
ET.SubElement(audiodef, "channelcount").text = f"{aud.channels}"
|
373
384
|
|
374
385
|
|
375
|
-
def
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
src_to_id: dict[FileInfo, str] = {}
|
381
|
-
|
382
|
-
file_defs: set[str] = set() # Contains urls
|
383
|
-
|
384
|
-
for src in set(tl.sources):
|
385
|
-
the_id = f"file-{len(src_to_id)+1}"
|
386
|
-
src_to_url[src] = f"{src.path.resolve()}"
|
387
|
-
src_to_id[src] = the_id
|
388
|
-
|
389
|
-
xmeml = ET.Element("xmeml", version="5")
|
390
|
-
sequence = ET.SubElement(xmeml, "sequence", explodedTracks="true")
|
391
|
-
ET.SubElement(sequence, "name").text = name
|
392
|
-
ET.SubElement(sequence, "duration").text = f"{int(tl.out_len())}"
|
393
|
-
rate = ET.SubElement(sequence, "rate")
|
394
|
-
ET.SubElement(rate, "timebase").text = f"{timebase}"
|
395
|
-
ET.SubElement(rate, "ntsc").text = ntsc
|
396
|
-
media = ET.SubElement(sequence, "media")
|
397
|
-
video = ET.SubElement(media, "video")
|
398
|
-
vformat = ET.SubElement(video, "format")
|
399
|
-
vschar = ET.SubElement(vformat, "samplecharacteristics")
|
400
|
-
|
401
|
-
ET.SubElement(vschar, "width").text = f"{width}"
|
402
|
-
ET.SubElement(vschar, "height").text = f"{height}"
|
403
|
-
ET.SubElement(vschar, "pixelaspectratio").text = "square"
|
404
|
-
|
405
|
-
rate = ET.SubElement(vschar, "rate")
|
406
|
-
ET.SubElement(rate, "timebase").text = f"{timebase}"
|
407
|
-
ET.SubElement(rate, "ntsc").text = ntsc
|
408
|
-
|
409
|
-
if len(tl.v) > 0 and len(tl.v[0]) > 0:
|
410
|
-
track = ET.SubElement(video, "track")
|
386
|
+
def resolve_write_audio(audio: Element, make_filedef, tl: v3) -> None:
|
387
|
+
for t, aclips in enumerate(tl.a):
|
388
|
+
track = ET.SubElement(audio, "track")
|
389
|
+
for j, aclip in enumerate(aclips):
|
390
|
+
src = aclip.src
|
411
391
|
|
412
|
-
|
413
|
-
|
392
|
+
_start = f"{aclip.start}"
|
393
|
+
_end = f"{aclip.start + aclip.dur}"
|
394
|
+
_in = f"{aclip.offset}"
|
395
|
+
_out = f"{aclip.offset + aclip.dur}"
|
414
396
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
397
|
+
if not src.videos:
|
398
|
+
clip_item_num = j + 1
|
399
|
+
else:
|
400
|
+
clip_item_num = len(aclips) + 1 + j
|
419
401
|
|
420
|
-
clipitem = ET.SubElement(track, "clipitem", id=f"clipitem-{
|
402
|
+
clipitem = ET.SubElement(track, "clipitem", id=f"clipitem-{clip_item_num}")
|
421
403
|
ET.SubElement(clipitem, "name").text = src.path.stem
|
422
|
-
ET.SubElement(clipitem, "enabled").text = "TRUE"
|
423
404
|
ET.SubElement(clipitem, "start").text = _start
|
424
405
|
ET.SubElement(clipitem, "end").text = _end
|
406
|
+
ET.SubElement(clipitem, "enabled").text = "TRUE"
|
425
407
|
ET.SubElement(clipitem, "in").text = _in
|
426
408
|
ET.SubElement(clipitem, "out").text = _out
|
427
409
|
|
428
|
-
|
429
|
-
filedef = ET.SubElement(clipitem, "file", id=_id)
|
410
|
+
make_filedef(clipitem, aclip.src)
|
430
411
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
file_defs.add(pathurl)
|
435
|
-
|
436
|
-
ET.SubElement(clipitem, "compositemode").text = "normal"
|
437
|
-
if clip.speed != 1:
|
438
|
-
clipitem.append(speedup(clip.speed * 100))
|
412
|
+
sourcetrack = ET.SubElement(clipitem, "sourcetrack")
|
413
|
+
ET.SubElement(sourcetrack, "mediatype").text = "audio"
|
414
|
+
ET.SubElement(sourcetrack, "trackindex").text = f"{t + 1}"
|
439
415
|
|
440
|
-
|
416
|
+
if src.videos:
|
441
417
|
link = ET.SubElement(clipitem, "link")
|
442
|
-
ET.SubElement(
|
443
|
-
|
444
|
-
|
445
|
-
ET.SubElement(link, "
|
446
|
-
ET.SubElement(link, "trackindex").text = str(max(i, 1))
|
447
|
-
ET.SubElement(link, "clipindex").text = str(j + 1)
|
418
|
+
ET.SubElement(link, "linkclipref").text = f"clipitem-{j + 1}"
|
419
|
+
ET.SubElement(link, "mediatype").text = "video"
|
420
|
+
link = ET.SubElement(clipitem, "link")
|
421
|
+
ET.SubElement(link, "linkclipref").text = f"clipitem-{clip_item_num}"
|
448
422
|
|
449
|
-
|
450
|
-
|
423
|
+
if aclip.speed != 1:
|
424
|
+
clipitem.append(speedup(aclip.speed * 100))
|
425
|
+
|
426
|
+
|
427
|
+
def premiere_write_audio(audio: Element, make_filedef, src: FileInfo, tl: v3) -> None:
|
451
428
|
ET.SubElement(audio, "numOutputChannels").text = "2"
|
452
429
|
aformat = ET.SubElement(audio, "format")
|
453
430
|
aschar = ET.SubElement(aformat, "samplecharacteristics")
|
454
431
|
ET.SubElement(aschar, "depth").text = DEPTH
|
455
|
-
ET.SubElement(aschar, "samplerate").text =
|
432
|
+
ET.SubElement(aschar, "samplerate").text = f"{tl.sr}"
|
456
433
|
|
457
434
|
t = 0
|
458
435
|
for aclips in tl.a:
|
@@ -494,11 +471,7 @@ def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
|
|
494
471
|
ET.SubElement(clipitem, "in").text = _in
|
495
472
|
ET.SubElement(clipitem, "out").text = _out
|
496
473
|
|
497
|
-
|
498
|
-
filedef = ET.SubElement(clipitem, "file", id=src_to_id[aclip.src])
|
499
|
-
if pathurl not in file_defs:
|
500
|
-
media_def(filedef, pathurl, aclip.src, tl, timebase, ntsc)
|
501
|
-
file_defs.add(pathurl)
|
474
|
+
make_filedef(clipitem, aclip.src)
|
502
475
|
|
503
476
|
sourcetrack = ET.SubElement(clipitem, "sourcetrack")
|
504
477
|
ET.SubElement(sourcetrack, "mediatype").text = "audio"
|
@@ -511,6 +484,103 @@ def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
|
|
511
484
|
|
512
485
|
audio.append(track)
|
513
486
|
|
487
|
+
|
488
|
+
def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3, log: Log) -> None:
|
489
|
+
width, height = tl.res
|
490
|
+
timebase, ntsc = set_tb_ntsc(tl.tb)
|
491
|
+
|
492
|
+
src_to_url: dict[FileInfo, str] = {}
|
493
|
+
src_to_id: dict[FileInfo, str] = {}
|
494
|
+
|
495
|
+
file_defs: set[str] = set() # Contains urls
|
496
|
+
|
497
|
+
for src in set(tl.sources):
|
498
|
+
the_id = f"file-{len(src_to_id)+1}"
|
499
|
+
src_to_url[src] = f"{src.path.resolve()}"
|
500
|
+
src_to_id[src] = the_id
|
501
|
+
|
502
|
+
def make_filedef(clipitem: Element, src: FileInfo) -> None:
|
503
|
+
pathurl = src_to_url[src]
|
504
|
+
filedef = ET.SubElement(clipitem, "file", id=src_to_id[src])
|
505
|
+
if pathurl not in file_defs:
|
506
|
+
media_def(filedef, pathurl, src, tl, timebase, ntsc)
|
507
|
+
file_defs.add(pathurl)
|
508
|
+
|
509
|
+
xmeml = ET.Element("xmeml", version="5")
|
510
|
+
if resolve:
|
511
|
+
sequence = ET.SubElement(xmeml, "sequence")
|
512
|
+
else:
|
513
|
+
sequence = ET.SubElement(xmeml, "sequence", explodedTracks="true")
|
514
|
+
|
515
|
+
ET.SubElement(sequence, "name").text = name
|
516
|
+
ET.SubElement(sequence, "duration").text = f"{int(tl.out_len())}"
|
517
|
+
rate = ET.SubElement(sequence, "rate")
|
518
|
+
ET.SubElement(rate, "timebase").text = f"{timebase}"
|
519
|
+
ET.SubElement(rate, "ntsc").text = ntsc
|
520
|
+
media = ET.SubElement(sequence, "media")
|
521
|
+
video = ET.SubElement(media, "video")
|
522
|
+
vformat = ET.SubElement(video, "format")
|
523
|
+
vschar = ET.SubElement(vformat, "samplecharacteristics")
|
524
|
+
|
525
|
+
ET.SubElement(vschar, "width").text = f"{width}"
|
526
|
+
ET.SubElement(vschar, "height").text = f"{height}"
|
527
|
+
ET.SubElement(vschar, "pixelaspectratio").text = "square"
|
528
|
+
|
529
|
+
rate = ET.SubElement(vschar, "rate")
|
530
|
+
ET.SubElement(rate, "timebase").text = f"{timebase}"
|
531
|
+
ET.SubElement(rate, "ntsc").text = ntsc
|
532
|
+
|
533
|
+
if len(tl.v) > 0 and len(tl.v[0]) > 0:
|
534
|
+
track = ET.SubElement(video, "track")
|
535
|
+
|
536
|
+
for j, clip in enumerate(tl.v[0]):
|
537
|
+
assert isinstance(clip, TlVideo)
|
538
|
+
|
539
|
+
_start = f"{clip.start}"
|
540
|
+
_end = f"{clip.start + clip.dur}"
|
541
|
+
_in = f"{clip.offset}"
|
542
|
+
_out = f"{clip.offset + clip.dur}"
|
543
|
+
|
544
|
+
this_clipid = f"clipitem-{j+1}"
|
545
|
+
clipitem = ET.SubElement(track, "clipitem", id=this_clipid)
|
546
|
+
ET.SubElement(clipitem, "name").text = src.path.stem
|
547
|
+
ET.SubElement(clipitem, "enabled").text = "TRUE"
|
548
|
+
ET.SubElement(clipitem, "start").text = _start
|
549
|
+
ET.SubElement(clipitem, "end").text = _end
|
550
|
+
ET.SubElement(clipitem, "in").text = _in
|
551
|
+
ET.SubElement(clipitem, "out").text = _out
|
552
|
+
|
553
|
+
make_filedef(clipitem, clip.src)
|
554
|
+
|
555
|
+
ET.SubElement(clipitem, "compositemode").text = "normal"
|
556
|
+
if clip.speed != 1:
|
557
|
+
clipitem.append(speedup(clip.speed * 100))
|
558
|
+
|
559
|
+
if resolve:
|
560
|
+
link = ET.SubElement(clipitem, "link")
|
561
|
+
ET.SubElement(link, "linkclipref").text = this_clipid
|
562
|
+
link = ET.SubElement(clipitem, "link")
|
563
|
+
ET.SubElement(
|
564
|
+
link, "linkclipref"
|
565
|
+
).text = f"clipitem-{(len(tl.v[0]))+j+1}"
|
566
|
+
continue
|
567
|
+
|
568
|
+
for i in range(1 + len(src.audios) * 2): # `2` because stereo.
|
569
|
+
link = ET.SubElement(clipitem, "link")
|
570
|
+
ET.SubElement(
|
571
|
+
link, "linkclipref"
|
572
|
+
).text = f"clipitem-{(i*(len(tl.v[0])))+j+1}"
|
573
|
+
ET.SubElement(link, "mediatype").text = "video" if i == 0 else "audio"
|
574
|
+
ET.SubElement(link, "trackindex").text = f"{max(i, 1)}"
|
575
|
+
ET.SubElement(link, "clipindex").text = f"{j + 1}"
|
576
|
+
|
577
|
+
# Audio definitions and clips
|
578
|
+
audio = ET.SubElement(media, "audio")
|
579
|
+
if resolve:
|
580
|
+
resolve_write_audio(audio, make_filedef, tl)
|
581
|
+
else:
|
582
|
+
premiere_write_audio(audio, make_filedef, src, tl)
|
583
|
+
|
514
584
|
tree = ET.ElementTree(xmeml)
|
515
585
|
ET.indent(tree, space=" ", level=0)
|
516
586
|
tree.write(output, xml_declaration=True, encoding="utf-8")
|