auto-editor 27.1.1__py3-none-any.whl → 28.0.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 +0 -8
- auto_editor/cmds/desc.py +8 -9
- auto_editor/cmds/info.py +1 -14
- auto_editor/cmds/test.py +35 -19
- auto_editor/edit.py +50 -49
- auto_editor/{formats → exports}/fcp11.py +6 -2
- auto_editor/{formats → exports}/fcp7.py +6 -201
- auto_editor/exports/json.py +32 -0
- auto_editor/{formats → exports}/shotcut.py +6 -11
- auto_editor/ffwrapper.py +1 -3
- auto_editor/help.py +6 -17
- auto_editor/imports/__init__.py +0 -0
- auto_editor/imports/fcp7.py +277 -0
- auto_editor/{formats → imports}/json.py +11 -37
- auto_editor/lang/palet.py +1 -1
- auto_editor/lang/stdenv.py +5 -2
- auto_editor/lib/contracts.py +1 -1
- auto_editor/preview.py +1 -2
- auto_editor/render/audio.py +1 -1
- auto_editor/timeline.py +8 -13
- {auto_editor-27.1.1.dist-info → auto_editor-28.0.0.dist-info}/METADATA +2 -2
- {auto_editor-27.1.1.dist-info → auto_editor-28.0.0.dist-info}/RECORD +28 -26
- {auto_editor-27.1.1.dist-info → auto_editor-28.0.0.dist-info}/WHEEL +1 -1
- auto_editor/formats/utils.py +0 -56
- /auto_editor/{formats → exports}/__init__.py +0 -0
- {auto_editor-27.1.1.dist-info → auto_editor-28.0.0.dist-info}/entry_points.txt +0 -0
- {auto_editor-27.1.1.dist-info → auto_editor-28.0.0.dist-info}/licenses/LICENSE +0 -0
- {auto_editor-27.1.1.dist-info → auto_editor-28.0.0.dist-info}/top_level.txt +0 -0
auto_editor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "
|
1
|
+
__version__ = "28.0.0"
|
auto_editor/__main__.py
CHANGED
@@ -78,7 +78,6 @@ class Args:
|
|
78
78
|
audio_layout: str | None = None
|
79
79
|
audio_bitrate: str = "auto"
|
80
80
|
mix_audio_streams: bool = False
|
81
|
-
keep_tracks_separate: bool = False
|
82
81
|
audio_normalize: str = "#f"
|
83
82
|
|
84
83
|
# Misc.
|
@@ -353,11 +352,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
353
352
|
parser.add_argument(
|
354
353
|
"--mix-audio-streams", flag=True, help="Mix all audio streams together into one"
|
355
354
|
)
|
356
|
-
parser.add_argument(
|
357
|
-
"--keep-tracks-separate",
|
358
|
-
flag=True,
|
359
|
-
help="Don't mix all audio streams into one when exporting (default)",
|
360
|
-
)
|
361
355
|
parser.add_argument(
|
362
356
|
"--audio-normalize",
|
363
357
|
metavar="NORM-TYPE",
|
@@ -447,9 +441,7 @@ def main() -> None:
|
|
447
441
|
({"--export-to-resolve", "-exr"}, ["--export", "resolve"]),
|
448
442
|
({"--export-to-final-cut-pro", "-exf"}, ["--export", "final-cut-pro"]),
|
449
443
|
({"--export-to-shotcut", "-exs"}, ["--export", "shotcut"]),
|
450
|
-
({"--export-as-json"}, ["--export", "json"]),
|
451
444
|
({"--export-as-clip-sequence", "-excs"}, ["--export", "clip-sequence"]),
|
452
|
-
({"--keep-tracks-seperate"}, ["--keep-tracks-separate"]),
|
453
445
|
({"--edit-based-on"}, ["--edit"]),
|
454
446
|
],
|
455
447
|
)
|
auto_editor/cmds/desc.py
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
1
|
import sys
|
4
2
|
from dataclasses import dataclass, field
|
5
3
|
|
6
|
-
|
7
|
-
|
4
|
+
import bv
|
5
|
+
|
8
6
|
from auto_editor.vanparse import ArgumentParser
|
9
7
|
|
10
8
|
|
@@ -22,11 +20,12 @@ def desc_options(parser: ArgumentParser) -> ArgumentParser:
|
|
22
20
|
def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
23
21
|
args = desc_options(ArgumentParser("desc")).parse_args(DescArgs, sys_args)
|
24
22
|
for path in args.input:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
try:
|
24
|
+
container = bv.open(path)
|
25
|
+
desc = container.metadata.get("description", None)
|
26
|
+
except Exception:
|
27
|
+
desc = None
|
28
|
+
sys.stdout.write("\nNo description.\n\n" if desc is None else f"\n{desc}\n\n")
|
30
29
|
|
31
30
|
|
32
31
|
if __name__ == "__main__":
|
auto_editor/cmds/info.py
CHANGED
@@ -8,7 +8,6 @@ from typing import Any, Literal, TypedDict
|
|
8
8
|
from auto_editor.ffwrapper import FileInfo
|
9
9
|
from auto_editor.json import dump
|
10
10
|
from auto_editor.make_layers import make_sane_timebase
|
11
|
-
from auto_editor.timeline import v3
|
12
11
|
from auto_editor.utils.func import aspect_ratio
|
13
12
|
from auto_editor.utils.log import Log
|
14
13
|
from auto_editor.vanparse import ArgumentParser
|
@@ -86,19 +85,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
86
85
|
log.error(f"Could not find '{file}'")
|
87
86
|
|
88
87
|
ext = os.path.splitext(file)[1]
|
89
|
-
if ext
|
90
|
-
from auto_editor.formats.json import read_json
|
91
|
-
|
92
|
-
tl = read_json(file, log)
|
93
|
-
file_info[file] = {"type": "timeline"}
|
94
|
-
file_info[file]["version"] = "v3" if isinstance(tl, v3) else "v1"
|
95
|
-
|
96
|
-
clip_lens = [clip.dur / clip.speed for clip in tl.a[0]]
|
97
|
-
file_info[file]["clips"] = len(clip_lens)
|
98
|
-
|
99
|
-
continue
|
100
|
-
|
101
|
-
if ext in {".xml", ".fcpxml", ".mlt"}:
|
88
|
+
if ext in {".v1", ".v3", ".json", ".xml", ".fcpxml", ".mlt"}:
|
102
89
|
file_info[file] = {"type": "timeline"}
|
103
90
|
continue
|
104
91
|
|
auto_editor/cmds/test.py
CHANGED
@@ -290,6 +290,7 @@ class Runner:
|
|
290
290
|
|
291
291
|
def test_silent_threshold(self):
|
292
292
|
with bv.open("resources/new-commentary.mp3") as container:
|
293
|
+
assert container.duration is not None
|
293
294
|
assert container.duration / bv.time_base == 6.732
|
294
295
|
|
295
296
|
out = self.main(
|
@@ -298,6 +299,7 @@ class Runner:
|
|
298
299
|
out += ".mp3"
|
299
300
|
|
300
301
|
with bv.open(out) as container:
|
302
|
+
assert container.duration is not None
|
301
303
|
assert container.duration / bv.time_base == 6.552
|
302
304
|
|
303
305
|
def test_track(self):
|
@@ -305,7 +307,9 @@ class Runner:
|
|
305
307
|
assert len(fileinfo(out).audios) == 2
|
306
308
|
|
307
309
|
def test_export_json(self):
|
308
|
-
out = self.main(["example.mp4"], ["--
|
310
|
+
out = self.main(["example.mp4"], ["--export", "v1"], "c77130d763d40e8.json")
|
311
|
+
self.main([out], [])
|
312
|
+
out = self.main(["example.mp4"], ["--export", "v1"], "c77130d763d40e8.v1")
|
309
313
|
self.main([out], [])
|
310
314
|
|
311
315
|
def test_import_v1(self):
|
@@ -317,10 +321,19 @@ class Runner:
|
|
317
321
|
|
318
322
|
self.main([path], [])
|
319
323
|
|
320
|
-
def
|
324
|
+
def test_res_with_v1(self):
|
325
|
+
v1 = self.main(["example.mp4"], ["--export", "v1"], "input.v1")
|
326
|
+
out = self.main([v1], ["-res", "720,720"], "output.mp4")
|
327
|
+
|
328
|
+
output = fileinfo(out)
|
329
|
+
assert output.videos[0].width == 720
|
330
|
+
assert output.videos[0].height == 720
|
331
|
+
assert len(output.audios) == 1
|
332
|
+
|
333
|
+
def test_premiere_named_export(self) -> None:
|
321
334
|
self.main(["example.mp4"], ["--export", 'premiere:name="Foo Bar"'])
|
322
335
|
|
323
|
-
def test_export_subtitles(self):
|
336
|
+
def test_export_subtitles(self) -> None:
|
324
337
|
# cn = fileinfo(self.main(["resources/mov_text.mp4"], [], "movtext_out.mp4"))
|
325
338
|
|
326
339
|
# assert len(cn.videos) == 1
|
@@ -332,7 +345,7 @@ class Runner:
|
|
332
345
|
assert len(cn.audios) == 1
|
333
346
|
assert len(cn.subtitles) == 1
|
334
347
|
|
335
|
-
def test_scale(self):
|
348
|
+
def test_scale(self) -> None:
|
336
349
|
cn = fileinfo(self.main(["example.mp4"], ["--scale", "1.5"], "scale.mp4"))
|
337
350
|
assert cn.videos[0].fps == 30
|
338
351
|
assert cn.videos[0].width == 1920
|
@@ -363,7 +376,7 @@ class Runner:
|
|
363
376
|
# assert len(cn.videos) == 1
|
364
377
|
# assert len(cn.audios) == 2
|
365
378
|
|
366
|
-
def test_premiere(self):
|
379
|
+
def test_premiere(self) -> None:
|
367
380
|
for test_name in all_files:
|
368
381
|
if test_name == "multi-track.mov":
|
369
382
|
continue
|
@@ -379,14 +392,14 @@ class Runner:
|
|
379
392
|
self.main([test_file], ["-exs"])
|
380
393
|
self.main([test_file], ["--stats"])
|
381
394
|
|
382
|
-
def test_clip_sequence(self):
|
395
|
+
def test_clip_sequence(self) -> None:
|
383
396
|
for test_name in all_files:
|
384
397
|
test_file = f"resources/{test_name}"
|
385
|
-
self.main([test_file], ["--
|
398
|
+
self.main([test_file], ["--export", "clip-sequence"])
|
386
399
|
|
387
|
-
def test_codecs(self):
|
388
|
-
self.main(["example.mp4"], ["--
|
389
|
-
self.main(["example.mp4"], ["--
|
400
|
+
def test_codecs(self) -> None:
|
401
|
+
self.main(["example.mp4"], ["--video-codec", "h264"])
|
402
|
+
self.main(["example.mp4"], ["--audio-codec", "ac3"])
|
390
403
|
|
391
404
|
# Issue #241
|
392
405
|
def test_multi_track_edit(self):
|
@@ -512,15 +525,15 @@ class Runner:
|
|
512
525
|
assert output.videos[0].pix_fmt == "yuv420p"
|
513
526
|
|
514
527
|
# Issue 280
|
515
|
-
def test_SAR(self):
|
528
|
+
def test_SAR(self) -> None:
|
516
529
|
out = self.main(["resources/SAR-2by3.mp4"], [], "2by3_out.mp4")
|
517
530
|
assert fileinfo(out).videos[0].sar == Fraction(2, 3)
|
518
531
|
|
519
|
-
def test_audio_norm_f(self):
|
520
|
-
|
532
|
+
def test_audio_norm_f(self) -> None:
|
533
|
+
self.main(["example.mp4"], ["--audio-normalize", "#f"])
|
521
534
|
|
522
|
-
def test_audio_norm_ebu(self):
|
523
|
-
|
535
|
+
def test_audio_norm_ebu(self) -> None:
|
536
|
+
self.main(
|
524
537
|
["example.mp4"], ["--audio-normalize", "ebu:i=-5,lra=20,gain=5,tp=-1"]
|
525
538
|
)
|
526
539
|
|
@@ -536,11 +549,14 @@ class Runner:
|
|
536
549
|
except MyError as e:
|
537
550
|
raise ValueError(f"{text}\nMyError: {e}")
|
538
551
|
|
552
|
+
result_val = results[-1]
|
539
553
|
if isinstance(expected, np.ndarray):
|
540
|
-
if not
|
554
|
+
if not isinstance(result_val, np.ndarray):
|
555
|
+
raise ValueError(f"{text}: Result is not an ndarray")
|
556
|
+
if not np.array_equal(expected, result_val):
|
541
557
|
raise ValueError(f"{text}: Numpy arrays don't match")
|
542
|
-
elif expected !=
|
543
|
-
raise ValueError(f"{text}: Expected: {expected}, got {
|
558
|
+
elif expected != result_val:
|
559
|
+
raise ValueError(f"{text}: Expected: {expected}, got {result_val}")
|
544
560
|
|
545
561
|
cases(
|
546
562
|
("345", 345),
|
@@ -675,7 +691,7 @@ class Runner:
|
|
675
691
|
('#(#("sym" "symbol?") "bool?")', [["sym", "symbol?"], "bool?"]),
|
676
692
|
)
|
677
693
|
|
678
|
-
def palet_scripts(self):
|
694
|
+
def palet_scripts(self) -> None:
|
679
695
|
self.raw(["palet", "resources/scripts/scope.pal"])
|
680
696
|
self.raw(["palet", "resources/scripts/maxcut.pal"])
|
681
697
|
self.raw(["palet", "resources/scripts/case.pal"])
|
auto_editor/edit.py
CHANGED
@@ -17,7 +17,7 @@ from auto_editor.make_layers import clipify, make_av, make_timeline
|
|
17
17
|
from auto_editor.render.audio import make_new_audio
|
18
18
|
from auto_editor.render.subtitle import make_new_subtitles
|
19
19
|
from auto_editor.render.video import render_av
|
20
|
-
from auto_editor.timeline import v1, v3
|
20
|
+
from auto_editor.timeline import set_stream_to_0, v1, v3
|
21
21
|
from auto_editor.utils.bar import initBar
|
22
22
|
from auto_editor.utils.chunks import Chunk, Chunks
|
23
23
|
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
|
@@ -31,7 +31,7 @@ if TYPE_CHECKING:
|
|
31
31
|
def set_output(
|
32
32
|
out: str | None, _export: str | None, path: Path | None, log: Log
|
33
33
|
) -> tuple[str, dict[str, Any]]:
|
34
|
-
if out is None:
|
34
|
+
if out is None or out == "-":
|
35
35
|
if path is None:
|
36
36
|
log.error("`--output` must be set.") # When a timeline file is the input.
|
37
37
|
root, ext = splitext(path)
|
@@ -43,16 +43,19 @@ def set_output(
|
|
43
43
|
ext = ".mp4" if path is None else path.suffix
|
44
44
|
|
45
45
|
if _export is None:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
46
|
+
match ext:
|
47
|
+
case ".xml":
|
48
|
+
export: dict[str, Any] = {"export": "premiere"}
|
49
|
+
case ".fcpxml":
|
50
|
+
export = {"export": "final-cut-pro"}
|
51
|
+
case ".mlt":
|
52
|
+
export = {"export": "shotcut"}
|
53
|
+
case ".json" | ".v1":
|
54
|
+
export = {"export": "v1"}
|
55
|
+
case ".v3":
|
56
|
+
export = {"export": "v3"}
|
57
|
+
case _:
|
58
|
+
export = {"export": "default"}
|
56
59
|
else:
|
57
60
|
export = parse_export(_export, log)
|
58
61
|
|
@@ -63,11 +66,13 @@ def set_output(
|
|
63
66
|
"resolve": ".fcpxml",
|
64
67
|
"shotcut": ".mlt",
|
65
68
|
"json": ".json",
|
66
|
-
"audio": ".wav",
|
67
69
|
}
|
68
70
|
if export["export"] in ext_map:
|
69
71
|
ext = ext_map[export["export"]]
|
70
72
|
|
73
|
+
if out == "-":
|
74
|
+
return "-", export
|
75
|
+
|
71
76
|
if out is None:
|
72
77
|
return f"{root}_ALTERED{ext}", export
|
73
78
|
|
@@ -134,53 +139,44 @@ def parse_export(export: str, log: Log) -> dict[str, Any]:
|
|
134
139
|
name, text = exploded
|
135
140
|
|
136
141
|
name_attr = pAttr("name", "Auto-Editor Media Group", is_str)
|
137
|
-
|
138
|
-
parsing: dict[str, pAttrs] = {
|
142
|
+
parsing = {
|
139
143
|
"default": pAttrs("default"),
|
140
144
|
"premiere": pAttrs("premiere", name_attr),
|
141
|
-
"resolve-fcp7": pAttrs("resolve-fcp7", name_attr),
|
142
145
|
"final-cut-pro": pAttrs(
|
143
146
|
"final-cut-pro", name_attr, pAttr("version", 11, is_int)
|
144
147
|
),
|
145
148
|
"resolve": pAttrs("resolve", name_attr),
|
149
|
+
"resolve-fcp7": pAttrs("resolve-fcp7", name_attr),
|
146
150
|
"shotcut": pAttrs("shotcut"),
|
147
|
-
"
|
148
|
-
"
|
149
|
-
"audio": pAttrs("audio"),
|
151
|
+
"v1": pAttrs("v1"),
|
152
|
+
"v3": pAttrs("v3"),
|
150
153
|
"clip-sequence": pAttrs("clip-sequence"),
|
151
154
|
}
|
152
155
|
|
153
156
|
if name in parsing:
|
154
157
|
try:
|
155
|
-
|
156
|
-
_tmp["export"] = name
|
157
|
-
return _tmp
|
158
|
+
return {"export": name} | parse_with_palet(text, parsing[name], {})
|
158
159
|
except ParserError as e:
|
159
160
|
log.error(e)
|
160
161
|
|
161
|
-
|
162
|
+
valid_choices = " ".join(f'"{s}"' for s in parsing.keys())
|
163
|
+
log.error(f'Invalid export format: "{name}"\nValid choices: {valid_choices}')
|
162
164
|
|
163
165
|
|
164
166
|
def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
165
167
|
bar = initBar(args.progress)
|
166
168
|
tl = src = use_path = None
|
167
169
|
|
168
|
-
if args.keep_tracks_separate:
|
169
|
-
log.deprecated("--keep-tracks-separate is deprecated.")
|
170
|
-
args.keep_tracks_separate = False
|
171
|
-
|
172
170
|
if paths:
|
173
171
|
path_ext = splitext(paths[0])[1].lower()
|
174
172
|
if path_ext == ".xml":
|
175
|
-
from auto_editor.
|
173
|
+
from auto_editor.imports.fcp7 import fcp7_read_xml
|
176
174
|
|
177
175
|
tl = fcp7_read_xml(paths[0], log)
|
178
176
|
elif path_ext == ".mlt":
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
elif path_ext == ".json":
|
183
|
-
from auto_editor.formats.json import read_json
|
177
|
+
log.error("Reading mlt files not implemented")
|
178
|
+
elif path_ext in {".v1", ".v3", ".json"}:
|
179
|
+
from auto_editor.imports.json import read_json
|
184
180
|
|
185
181
|
tl = read_json(paths[0], log)
|
186
182
|
else:
|
@@ -192,7 +188,8 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
192
188
|
assert "export" in export_ops
|
193
189
|
export = export_ops["export"]
|
194
190
|
|
195
|
-
if
|
191
|
+
if output == "-":
|
192
|
+
# When printing to stdout, silence all logs.
|
196
193
|
log.quiet = True
|
197
194
|
|
198
195
|
if not args.preview:
|
@@ -211,12 +208,15 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
211
208
|
|
212
209
|
if tl is None:
|
213
210
|
tl = make_timeline(sources, args, samplerate, bar, log)
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
211
|
+
else:
|
212
|
+
if args.resolution is not None:
|
213
|
+
tl.T.res = args.resolution
|
214
|
+
if args.background is not None:
|
215
|
+
tl.background = args.background
|
216
|
+
if args.frame_rate is not None:
|
217
|
+
log.warning(
|
218
|
+
"Setting timebase/framerate is not supported when importing timelines"
|
219
|
+
)
|
220
220
|
|
221
221
|
if args.preview:
|
222
222
|
from auto_editor.preview import preview
|
@@ -224,29 +224,28 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
224
224
|
preview(tl, log)
|
225
225
|
return
|
226
226
|
|
227
|
-
if export
|
228
|
-
from auto_editor.
|
227
|
+
if export in {"v1", "v3"}:
|
228
|
+
from auto_editor.exports.json import make_json_timeline
|
229
229
|
|
230
|
-
make_json_timeline(
|
230
|
+
make_json_timeline(export, output, tl, log)
|
231
231
|
return
|
232
232
|
|
233
233
|
if export in {"premiere", "resolve-fcp7"}:
|
234
|
-
from auto_editor.
|
234
|
+
from auto_editor.exports.fcp7 import fcp7_write_xml
|
235
235
|
|
236
236
|
is_resolve = export.startswith("resolve")
|
237
237
|
fcp7_write_xml(export_ops["name"], output, is_resolve, tl)
|
238
238
|
return
|
239
239
|
|
240
240
|
if export == "final-cut-pro":
|
241
|
-
from auto_editor.
|
241
|
+
from auto_editor.exports.fcp11 import fcp11_write_xml
|
242
242
|
|
243
243
|
ver = export_ops["version"]
|
244
244
|
fcp11_write_xml(export_ops["name"], ver, output, False, tl, log)
|
245
245
|
return
|
246
246
|
|
247
247
|
if export == "resolve":
|
248
|
-
from auto_editor.
|
249
|
-
from auto_editor.timeline import set_stream_to_0
|
248
|
+
from auto_editor.exports.fcp11 import fcp11_write_xml
|
250
249
|
|
251
250
|
assert src is not None
|
252
251
|
set_stream_to_0(src, tl, log)
|
@@ -254,11 +253,13 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
254
253
|
return
|
255
254
|
|
256
255
|
if export == "shotcut":
|
257
|
-
from auto_editor.
|
256
|
+
from auto_editor.exports.shotcut import shotcut_write_mlt
|
258
257
|
|
259
258
|
shotcut_write_mlt(output, tl)
|
260
259
|
return
|
261
260
|
|
261
|
+
if output == "-":
|
262
|
+
log.error("Exporting media files to stdout is not supported.")
|
262
263
|
out_ext = splitext(output)[1].replace(".", "")
|
263
264
|
|
264
265
|
# Check if export options make sense.
|
@@ -522,7 +523,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
522
523
|
|
523
524
|
log.stop_timer()
|
524
525
|
|
525
|
-
if not args.no_open and export
|
526
|
+
if not args.no_open and export == "default":
|
526
527
|
if args.player is None:
|
527
528
|
if sys.platform == "win32":
|
528
529
|
try:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import xml.etree.ElementTree as ET
|
3
4
|
from typing import TYPE_CHECKING, Any, cast
|
4
5
|
from xml.etree.ElementTree import Element, ElementTree, SubElement, indent
|
5
6
|
|
@@ -70,7 +71,7 @@ def fcp11_write_xml(
|
|
70
71
|
resources = SubElement(fcpxml, "resources")
|
71
72
|
|
72
73
|
src_dur = 0
|
73
|
-
tl_dur = 0 if resolve else tl
|
74
|
+
tl_dur = 0 if resolve else len(tl)
|
74
75
|
|
75
76
|
for i, one_src in enumerate(tl.unique_sources()):
|
76
77
|
if i == 0:
|
@@ -162,4 +163,7 @@ def fcp11_write_xml(
|
|
162
163
|
|
163
164
|
tree = ElementTree(fcpxml)
|
164
165
|
indent(tree, space="\t", level=0)
|
165
|
-
|
166
|
+
if output == "-":
|
167
|
+
print(ET.tostring(fcpxml, encoding="unicode"))
|
168
|
+
else:
|
169
|
+
tree.write(output, xml_declaration=True, encoding="utf-8")
|