auto-editor 27.1.0__tar.gz → 28.0.0__tar.gz
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-27.1.0 → auto_editor-28.0.0}/PKG-INFO +2 -2
- auto_editor-28.0.0/auto_editor/__init__.py +1 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/__main__.py +0 -8
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/desc.py +8 -9
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/info.py +1 -14
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/test.py +35 -19
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/edit.py +64 -58
- {auto_editor-27.1.0/auto_editor/formats → auto_editor-28.0.0/auto_editor/exports}/fcp11.py +6 -2
- {auto_editor-27.1.0/auto_editor/formats → auto_editor-28.0.0/auto_editor/exports}/fcp7.py +6 -241
- auto_editor-28.0.0/auto_editor/exports/json.py +32 -0
- {auto_editor-27.1.0/auto_editor/formats → auto_editor-28.0.0/auto_editor/exports}/shotcut.py +6 -11
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/ffwrapper.py +1 -3
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/help.py +6 -17
- auto_editor-28.0.0/auto_editor/imports/fcp7.py +277 -0
- {auto_editor-27.1.0/auto_editor/formats → auto_editor-28.0.0/auto_editor/imports}/json.py +11 -37
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/lang/palet.py +1 -1
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/lang/stdenv.py +5 -2
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/lib/contracts.py +1 -1
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/preview.py +1 -2
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/render/audio.py +1 -1
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/timeline.py +16 -18
- auto_editor-28.0.0/auto_editor/utils/__init__.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor.egg-info/PKG-INFO +2 -2
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor.egg-info/SOURCES.txt +8 -6
- auto_editor-28.0.0/auto_editor.egg-info/requires.txt +2 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/pyproject.toml +1 -1
- auto_editor-27.1.0/auto_editor/__init__.py +0 -1
- auto_editor-27.1.0/auto_editor/formats/utils.py +0 -56
- auto_editor-27.1.0/auto_editor.egg-info/requires.txt +0 -2
- {auto_editor-27.1.0 → auto_editor-28.0.0}/LICENSE +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/README.md +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/analyze.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/__init__.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/cache.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/levels.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/palet.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/repl.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/cmds/subdump.py +0 -0
- {auto_editor-27.1.0/auto_editor/formats → auto_editor-28.0.0/auto_editor/exports}/__init__.py +0 -0
- {auto_editor-27.1.0/auto_editor/lang → auto_editor-28.0.0/auto_editor/imports}/__init__.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/json.py +0 -0
- {auto_editor-27.1.0/auto_editor/lib → auto_editor-28.0.0/auto_editor/lang}/__init__.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/lang/libintrospection.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/lang/libmath.py +0 -0
- {auto_editor-27.1.0/auto_editor/render → auto_editor-28.0.0/auto_editor/lib}/__init__.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/lib/data_structs.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/lib/err.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/make_layers.py +0 -0
- {auto_editor-27.1.0/auto_editor/utils → auto_editor-28.0.0/auto_editor/render}/__init__.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/render/subtitle.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/render/video.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/utils/bar.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/utils/chunks.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/utils/cmdkw.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/utils/container.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/utils/func.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/utils/log.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/utils/types.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor/vanparse.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor.egg-info/dependency_links.txt +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor.egg-info/entry_points.txt +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/auto_editor.egg-info/top_level.txt +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/docs/build.py +0 -0
- {auto_editor-27.1.0 → auto_editor-28.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: auto-editor
|
3
|
-
Version:
|
3
|
+
Version: 28.0.0
|
4
4
|
Summary: Auto-Editor: Effort free video editing!
|
5
5
|
Author-email: WyattBlue <wyattblue@auto-editor.com>
|
6
6
|
License-Expression: Unlicense
|
@@ -12,7 +12,7 @@ Requires-Python: <3.14,>=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
License-File: LICENSE
|
14
14
|
Requires-Dist: numpy<3.0,>=2
|
15
|
-
Requires-Dist: basswood-av<16,>=15.
|
15
|
+
Requires-Dist: basswood-av<16,>=15.2.1
|
16
16
|
Dynamic: license-file
|
17
17
|
|
18
18
|
<p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "28.0.0"
|
@@ -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
|
)
|
@@ -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__":
|
@@ -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
|
|
@@ -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"])
|
@@ -5,6 +5,7 @@ import sys
|
|
5
5
|
from fractions import Fraction
|
6
6
|
from heapq import heappop, heappush
|
7
7
|
from os.path import splitext
|
8
|
+
from pathlib import Path
|
8
9
|
from subprocess import run
|
9
10
|
from typing import TYPE_CHECKING, Any
|
10
11
|
|
@@ -16,7 +17,7 @@ from auto_editor.make_layers import clipify, make_av, make_timeline
|
|
16
17
|
from auto_editor.render.audio import make_new_audio
|
17
18
|
from auto_editor.render.subtitle import make_new_subtitles
|
18
19
|
from auto_editor.render.video import render_av
|
19
|
-
from auto_editor.timeline import v1, v3
|
20
|
+
from auto_editor.timeline import set_stream_to_0, v1, v3
|
20
21
|
from auto_editor.utils.bar import initBar
|
21
22
|
from auto_editor.utils.chunks import Chunk, Chunks
|
22
23
|
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
|
@@ -28,26 +29,33 @@ if TYPE_CHECKING:
|
|
28
29
|
|
29
30
|
|
30
31
|
def set_output(
|
31
|
-
out: str | None, _export: str | None,
|
32
|
+
out: str | None, _export: str | None, path: Path | None, log: Log
|
32
33
|
) -> tuple[str, dict[str, Any]]:
|
33
|
-
if
|
34
|
-
|
34
|
+
if out is None or out == "-":
|
35
|
+
if path is None:
|
36
|
+
log.error("`--output` must be set.") # When a timeline file is the input.
|
37
|
+
root, ext = splitext(path)
|
35
38
|
else:
|
36
|
-
root, ext = splitext(
|
37
|
-
|
38
|
-
|
39
|
+
root, ext = splitext(out)
|
40
|
+
|
41
|
+
if ext == "":
|
42
|
+
# Use `mp4` as the default, because it is most compatible.
|
43
|
+
ext = ".mp4" if path is None else path.suffix
|
39
44
|
|
40
45
|
if _export is None:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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"}
|
51
59
|
else:
|
52
60
|
export = parse_export(_export, log)
|
53
61
|
|
@@ -58,11 +66,13 @@ def set_output(
|
|
58
66
|
"resolve": ".fcpxml",
|
59
67
|
"shotcut": ".mlt",
|
60
68
|
"json": ".json",
|
61
|
-
"audio": ".wav",
|
62
69
|
}
|
63
70
|
if export["export"] in ext_map:
|
64
71
|
ext = ext_map[export["export"]]
|
65
72
|
|
73
|
+
if out == "-":
|
74
|
+
return "-", export
|
75
|
+
|
66
76
|
if out is None:
|
67
77
|
return f"{root}_ALTERED{ext}", export
|
68
78
|
|
@@ -129,65 +139,57 @@ def parse_export(export: str, log: Log) -> dict[str, Any]:
|
|
129
139
|
name, text = exploded
|
130
140
|
|
131
141
|
name_attr = pAttr("name", "Auto-Editor Media Group", is_str)
|
132
|
-
|
133
|
-
parsing: dict[str, pAttrs] = {
|
142
|
+
parsing = {
|
134
143
|
"default": pAttrs("default"),
|
135
144
|
"premiere": pAttrs("premiere", name_attr),
|
136
|
-
"resolve-fcp7": pAttrs("resolve-fcp7", name_attr),
|
137
145
|
"final-cut-pro": pAttrs(
|
138
146
|
"final-cut-pro", name_attr, pAttr("version", 11, is_int)
|
139
147
|
),
|
140
148
|
"resolve": pAttrs("resolve", name_attr),
|
149
|
+
"resolve-fcp7": pAttrs("resolve-fcp7", name_attr),
|
141
150
|
"shotcut": pAttrs("shotcut"),
|
142
|
-
"
|
143
|
-
"
|
144
|
-
"audio": pAttrs("audio"),
|
151
|
+
"v1": pAttrs("v1"),
|
152
|
+
"v3": pAttrs("v3"),
|
145
153
|
"clip-sequence": pAttrs("clip-sequence"),
|
146
154
|
}
|
147
155
|
|
148
156
|
if name in parsing:
|
149
157
|
try:
|
150
|
-
|
151
|
-
_tmp["export"] = name
|
152
|
-
return _tmp
|
158
|
+
return {"export": name} | parse_with_palet(text, parsing[name], {})
|
153
159
|
except ParserError as e:
|
154
160
|
log.error(e)
|
155
161
|
|
156
|
-
|
162
|
+
valid_choices = " ".join(f'"{s}"' for s in parsing.keys())
|
163
|
+
log.error(f'Invalid export format: "{name}"\nValid choices: {valid_choices}')
|
157
164
|
|
158
165
|
|
159
166
|
def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
160
167
|
bar = initBar(args.progress)
|
161
|
-
tl = None
|
162
|
-
src = None
|
163
|
-
|
164
|
-
if args.keep_tracks_separate:
|
165
|
-
log.deprecated("--keep-tracks-separate is deprecated.")
|
166
|
-
args.keep_tracks_separate = False
|
168
|
+
tl = src = use_path = None
|
167
169
|
|
168
170
|
if paths:
|
169
171
|
path_ext = splitext(paths[0])[1].lower()
|
170
172
|
if path_ext == ".xml":
|
171
|
-
from auto_editor.
|
173
|
+
from auto_editor.imports.fcp7 import fcp7_read_xml
|
172
174
|
|
173
175
|
tl = fcp7_read_xml(paths[0], log)
|
174
176
|
elif path_ext == ".mlt":
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
elif path_ext == ".json":
|
179
|
-
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
|
180
180
|
|
181
181
|
tl = read_json(paths[0], log)
|
182
182
|
else:
|
183
183
|
sources = [FileInfo.init(path, log) for path in paths]
|
184
|
-
src =
|
184
|
+
src = sources[0]
|
185
|
+
use_path = src.path
|
185
186
|
|
186
|
-
output, export_ops = set_output(args.output, args.export,
|
187
|
+
output, export_ops = set_output(args.output, args.export, use_path, log)
|
187
188
|
assert "export" in export_ops
|
188
189
|
export = export_ops["export"]
|
189
190
|
|
190
|
-
if
|
191
|
+
if output == "-":
|
192
|
+
# When printing to stdout, silence all logs.
|
191
193
|
log.quiet = True
|
192
194
|
|
193
195
|
if not args.preview:
|
@@ -206,12 +208,15 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
206
208
|
|
207
209
|
if tl is None:
|
208
210
|
tl = make_timeline(sources, args, samplerate, bar, log)
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
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
|
+
)
|
215
220
|
|
216
221
|
if args.preview:
|
217
222
|
from auto_editor.preview import preview
|
@@ -219,29 +224,28 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
219
224
|
preview(tl, log)
|
220
225
|
return
|
221
226
|
|
222
|
-
if export
|
223
|
-
from auto_editor.
|
227
|
+
if export in {"v1", "v3"}:
|
228
|
+
from auto_editor.exports.json import make_json_timeline
|
224
229
|
|
225
|
-
make_json_timeline(
|
230
|
+
make_json_timeline(export, output, tl, log)
|
226
231
|
return
|
227
232
|
|
228
233
|
if export in {"premiere", "resolve-fcp7"}:
|
229
|
-
from auto_editor.
|
234
|
+
from auto_editor.exports.fcp7 import fcp7_write_xml
|
230
235
|
|
231
236
|
is_resolve = export.startswith("resolve")
|
232
237
|
fcp7_write_xml(export_ops["name"], output, is_resolve, tl)
|
233
238
|
return
|
234
239
|
|
235
240
|
if export == "final-cut-pro":
|
236
|
-
from auto_editor.
|
241
|
+
from auto_editor.exports.fcp11 import fcp11_write_xml
|
237
242
|
|
238
243
|
ver = export_ops["version"]
|
239
244
|
fcp11_write_xml(export_ops["name"], ver, output, False, tl, log)
|
240
245
|
return
|
241
246
|
|
242
247
|
if export == "resolve":
|
243
|
-
from auto_editor.
|
244
|
-
from auto_editor.timeline import set_stream_to_0
|
248
|
+
from auto_editor.exports.fcp11 import fcp11_write_xml
|
245
249
|
|
246
250
|
assert src is not None
|
247
251
|
set_stream_to_0(src, tl, log)
|
@@ -249,11 +253,13 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
249
253
|
return
|
250
254
|
|
251
255
|
if export == "shotcut":
|
252
|
-
from auto_editor.
|
256
|
+
from auto_editor.exports.shotcut import shotcut_write_mlt
|
253
257
|
|
254
258
|
shotcut_write_mlt(output, tl)
|
255
259
|
return
|
256
260
|
|
261
|
+
if output == "-":
|
262
|
+
log.error("Exporting media files to stdout is not supported.")
|
257
263
|
out_ext = splitext(output)[1].replace(".", "")
|
258
264
|
|
259
265
|
# Check if export options make sense.
|
@@ -517,7 +523,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
517
523
|
|
518
524
|
log.stop_timer()
|
519
525
|
|
520
|
-
if not args.no_open and export
|
526
|
+
if not args.no_open and export == "default":
|
521
527
|
if args.player is None:
|
522
528
|
if sys.platform == "win32":
|
523
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")
|