auto-editor 27.1.1__tar.gz → 28.0.1__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.1 → auto_editor-28.0.1}/PKG-INFO +2 -2
- auto_editor-28.0.1/auto_editor/__init__.py +1 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/__main__.py +0 -8
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/cache.py +1 -1
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/desc.py +8 -9
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/info.py +4 -17
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/palet.py +1 -1
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/subdump.py +2 -4
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/test.py +50 -31
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/edit.py +52 -52
- {auto_editor-27.1.1/auto_editor/formats → auto_editor-28.0.1/auto_editor/exports}/fcp11.py +11 -7
- {auto_editor-27.1.1/auto_editor/formats → auto_editor-28.0.1/auto_editor/exports}/fcp7.py +7 -239
- auto_editor-28.0.1/auto_editor/exports/json.py +32 -0
- {auto_editor-27.1.1/auto_editor/formats → auto_editor-28.0.1/auto_editor/exports}/shotcut.py +8 -17
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/ffwrapper.py +1 -3
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/help.py +6 -17
- auto_editor-28.0.1/auto_editor/imports/fcp7.py +275 -0
- {auto_editor-27.1.1/auto_editor/formats → auto_editor-28.0.1/auto_editor/imports}/json.py +16 -43
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/json.py +2 -2
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/lang/palet.py +6 -43
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/lang/stdenv.py +8 -20
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/lib/contracts.py +4 -6
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/lib/data_structs.py +0 -3
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/make_layers.py +13 -13
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/preview.py +1 -2
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/render/audio.py +9 -6
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/render/video.py +3 -2
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/timeline.py +35 -52
- auto_editor-28.0.1/auto_editor/utils/__init__.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor.egg-info/PKG-INFO +2 -2
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor.egg-info/SOURCES.txt +8 -6
- auto_editor-28.0.1/auto_editor.egg-info/requires.txt +2 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/pyproject.toml +5 -1
- auto_editor-27.1.1/auto_editor/__init__.py +0 -1
- auto_editor-27.1.1/auto_editor/formats/utils.py +0 -56
- auto_editor-27.1.1/auto_editor.egg-info/requires.txt +0 -2
- {auto_editor-27.1.1 → auto_editor-28.0.1}/LICENSE +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/README.md +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/analyze.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/__init__.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/levels.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/cmds/repl.py +0 -0
- {auto_editor-27.1.1/auto_editor/formats → auto_editor-28.0.1/auto_editor/exports}/__init__.py +0 -0
- {auto_editor-27.1.1/auto_editor/lang → auto_editor-28.0.1/auto_editor/imports}/__init__.py +0 -0
- {auto_editor-27.1.1/auto_editor/lib → auto_editor-28.0.1/auto_editor/lang}/__init__.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/lang/libintrospection.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/lang/libmath.py +0 -0
- {auto_editor-27.1.1/auto_editor/render → auto_editor-28.0.1/auto_editor/lib}/__init__.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/lib/err.py +0 -0
- {auto_editor-27.1.1/auto_editor/utils → auto_editor-28.0.1/auto_editor/render}/__init__.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/render/subtitle.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/utils/bar.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/utils/chunks.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/utils/cmdkw.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/utils/container.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/utils/func.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/utils/log.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/utils/types.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor/vanparse.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor.egg-info/dependency_links.txt +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor.egg-info/entry_points.txt +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/auto_editor.egg-info/top_level.txt +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/docs/build.py +0 -0
- {auto_editor-27.1.1 → auto_editor-28.0.1}/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.1
|
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.1"
|
@@ -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
|
)
|
@@ -26,7 +26,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
26
26
|
return
|
27
27
|
|
28
28
|
def format_bytes(size: float) -> str:
|
29
|
-
for unit in
|
29
|
+
for unit in ("B", "KiB", "MiB", "GiB", "TiB"):
|
30
30
|
if size < 1024:
|
31
31
|
return f"{size:.2f} {unit}"
|
32
32
|
size /= 1024
|
@@ -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
|
|
@@ -126,7 +113,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
126
113
|
f"{recTb.numerator}/{recTb.denominator}"
|
127
114
|
)
|
128
115
|
|
129
|
-
for
|
116
|
+
for v in src.videos:
|
130
117
|
w, h = v.width, v.height
|
131
118
|
|
132
119
|
vid: VideoJson = {
|
@@ -147,7 +134,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
147
134
|
}
|
148
135
|
file_info[file]["video"].append(vid)
|
149
136
|
|
150
|
-
for
|
137
|
+
for a in src.audios:
|
151
138
|
aud: AudioJson = {
|
152
139
|
"codec": a.codec,
|
153
140
|
"layout": a.layout,
|
@@ -158,7 +145,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
158
145
|
}
|
159
146
|
file_info[file]["audio"].append(aud)
|
160
147
|
|
161
|
-
for
|
148
|
+
for s_stream in src.subtitles:
|
162
149
|
sub: SubtitleJson = {"codec": s_stream.codec, "lang": s_stream.lang}
|
163
150
|
file_info[file]["subtitle"].append(sub)
|
164
151
|
|
@@ -14,7 +14,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
14
14
|
|
15
15
|
env.update(make_standard_env())
|
16
16
|
try:
|
17
|
-
interpret(env, Parser(Lexer(sys_args[0], program_text
|
17
|
+
interpret(env, Parser(Lexer(sys_args[0], program_text)))
|
18
18
|
except (MyError, ZeroDivisionError) as e:
|
19
19
|
sys.stderr.write(f"error: {e}\n")
|
20
20
|
sys.exit(1)
|
@@ -21,8 +21,6 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
21
21
|
parser.add_argument("--json", flag=True)
|
22
22
|
args = parser.parse_args(SubdumpArgs, sys_args)
|
23
23
|
|
24
|
-
do_filter = True
|
25
|
-
|
26
24
|
if args.json:
|
27
25
|
data = {}
|
28
26
|
for input_file in args.input:
|
@@ -46,7 +44,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
46
44
|
startf = round(float(start), 3)
|
47
45
|
endf = round(float(end), 3)
|
48
46
|
|
49
|
-
if
|
47
|
+
if endf - startf <= 0.02:
|
50
48
|
continue
|
51
49
|
|
52
50
|
for sub in packet.decode():
|
@@ -60,7 +58,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
60
58
|
dump(data, sys.stdout, indent=4)
|
61
59
|
return
|
62
60
|
|
63
|
-
for
|
61
|
+
for input_file in args.input:
|
64
62
|
with bv.open(input_file) as container:
|
65
63
|
for s in range(len(container.streams.subtitles)):
|
66
64
|
print(f"file: {input_file} ({s}:{container.streams.subtitles[s].name})")
|
@@ -202,6 +202,19 @@ class Runner:
|
|
202
202
|
assert audio.language == "eng"
|
203
203
|
assert audio.layout.name == "stereo"
|
204
204
|
|
205
|
+
def test_video_to_mp3(self) -> None:
|
206
|
+
out = self.main(["example.mp4"], [], output="example_ALTERED.mp3")
|
207
|
+
with bv.open(out) as container:
|
208
|
+
assert container.duration is not None
|
209
|
+
assert container.duration > 17300000 and container.duration < 2 << 24
|
210
|
+
|
211
|
+
assert len(container.streams) == 1
|
212
|
+
audio = container.streams[0]
|
213
|
+
assert isinstance(audio, bv.AudioStream)
|
214
|
+
assert audio.codec.name in ("mp3", "mp3float")
|
215
|
+
assert audio.sample_rate == 48000
|
216
|
+
assert audio.layout.name == "stereo"
|
217
|
+
|
205
218
|
def test_to_mono(self) -> None:
|
206
219
|
out = self.main(["example.mp4"], ["-layout", "mono"], output="example_mono.mp4")
|
207
220
|
with bv.open(out) as container:
|
@@ -290,6 +303,7 @@ class Runner:
|
|
290
303
|
|
291
304
|
def test_silent_threshold(self):
|
292
305
|
with bv.open("resources/new-commentary.mp3") as container:
|
306
|
+
assert container.duration is not None
|
293
307
|
assert container.duration / bv.time_base == 6.732
|
294
308
|
|
295
309
|
out = self.main(
|
@@ -298,6 +312,7 @@ class Runner:
|
|
298
312
|
out += ".mp3"
|
299
313
|
|
300
314
|
with bv.open(out) as container:
|
315
|
+
assert container.duration is not None
|
301
316
|
assert container.duration / bv.time_base == 6.552
|
302
317
|
|
303
318
|
def test_track(self):
|
@@ -305,7 +320,9 @@ class Runner:
|
|
305
320
|
assert len(fileinfo(out).audios) == 2
|
306
321
|
|
307
322
|
def test_export_json(self):
|
308
|
-
out = self.main(["example.mp4"], ["--
|
323
|
+
out = self.main(["example.mp4"], ["--export", "v1"], "c77130d763d40e8.json")
|
324
|
+
self.main([out], [])
|
325
|
+
out = self.main(["example.mp4"], ["--export", "v1"], "c77130d763d40e8.v1")
|
309
326
|
self.main([out], [])
|
310
327
|
|
311
328
|
def test_import_v1(self):
|
@@ -317,10 +334,19 @@ class Runner:
|
|
317
334
|
|
318
335
|
self.main([path], [])
|
319
336
|
|
320
|
-
def
|
337
|
+
def test_res_with_v1(self):
|
338
|
+
v1 = self.main(["example.mp4"], ["--export", "v1"], "input.v1")
|
339
|
+
out = self.main([v1], ["-res", "720,720"], "output.mp4")
|
340
|
+
|
341
|
+
output = fileinfo(out)
|
342
|
+
assert output.videos[0].width == 720
|
343
|
+
assert output.videos[0].height == 720
|
344
|
+
assert len(output.audios) == 1
|
345
|
+
|
346
|
+
def test_premiere_named_export(self) -> None:
|
321
347
|
self.main(["example.mp4"], ["--export", 'premiere:name="Foo Bar"'])
|
322
348
|
|
323
|
-
def test_export_subtitles(self):
|
349
|
+
def test_export_subtitles(self) -> None:
|
324
350
|
# cn = fileinfo(self.main(["resources/mov_text.mp4"], [], "movtext_out.mp4"))
|
325
351
|
|
326
352
|
# assert len(cn.videos) == 1
|
@@ -332,7 +358,7 @@ class Runner:
|
|
332
358
|
assert len(cn.audios) == 1
|
333
359
|
assert len(cn.subtitles) == 1
|
334
360
|
|
335
|
-
def test_scale(self):
|
361
|
+
def test_scale(self) -> None:
|
336
362
|
cn = fileinfo(self.main(["example.mp4"], ["--scale", "1.5"], "scale.mp4"))
|
337
363
|
assert cn.videos[0].fps == 30
|
338
364
|
assert cn.videos[0].width == 1920
|
@@ -363,7 +389,7 @@ class Runner:
|
|
363
389
|
# assert len(cn.videos) == 1
|
364
390
|
# assert len(cn.audios) == 2
|
365
391
|
|
366
|
-
def test_premiere(self):
|
392
|
+
def test_premiere(self) -> None:
|
367
393
|
for test_name in all_files:
|
368
394
|
if test_name == "multi-track.mov":
|
369
395
|
continue
|
@@ -379,14 +405,14 @@ class Runner:
|
|
379
405
|
self.main([test_file], ["-exs"])
|
380
406
|
self.main([test_file], ["--stats"])
|
381
407
|
|
382
|
-
def test_clip_sequence(self):
|
408
|
+
def test_clip_sequence(self) -> None:
|
383
409
|
for test_name in all_files:
|
384
410
|
test_file = f"resources/{test_name}"
|
385
|
-
self.main([test_file], ["--
|
411
|
+
self.main([test_file], ["--export", "clip-sequence"])
|
386
412
|
|
387
|
-
def test_codecs(self):
|
388
|
-
self.main(["example.mp4"], ["--
|
389
|
-
self.main(["example.mp4"], ["--
|
413
|
+
def test_codecs(self) -> None:
|
414
|
+
self.main(["example.mp4"], ["--video-codec", "h264"])
|
415
|
+
self.main(["example.mp4"], ["--audio-codec", "ac3"])
|
390
416
|
|
391
417
|
# Issue #241
|
392
418
|
def test_multi_track_edit(self):
|
@@ -503,24 +529,21 @@ class Runner:
|
|
503
529
|
assert output.videos[0].pix_fmt == "yuv420p"
|
504
530
|
|
505
531
|
def test_encode_hevc(self):
|
506
|
-
if os.environ.get("GITHUB_ACTIONS") == "true":
|
507
|
-
raise SkipTest()
|
508
|
-
|
509
532
|
out = self.main(["resources/testsrc.mp4"], ["-c:v", "hevc"], "out.mkv")
|
510
533
|
output = fileinfo(out)
|
511
534
|
assert output.videos[0].codec == "hevc"
|
512
535
|
assert output.videos[0].pix_fmt == "yuv420p"
|
513
536
|
|
514
537
|
# Issue 280
|
515
|
-
def test_SAR(self):
|
538
|
+
def test_SAR(self) -> None:
|
516
539
|
out = self.main(["resources/SAR-2by3.mp4"], [], "2by3_out.mp4")
|
517
540
|
assert fileinfo(out).videos[0].sar == Fraction(2, 3)
|
518
541
|
|
519
|
-
def test_audio_norm_f(self):
|
520
|
-
|
542
|
+
def test_audio_norm_f(self) -> None:
|
543
|
+
self.main(["example.mp4"], ["--audio-normalize", "#f"])
|
521
544
|
|
522
|
-
def test_audio_norm_ebu(self):
|
523
|
-
|
545
|
+
def test_audio_norm_ebu(self) -> None:
|
546
|
+
self.main(
|
524
547
|
["example.mp4"], ["--audio-normalize", "ebu:i=-5,lra=20,gain=5,tp=-1"]
|
525
548
|
)
|
526
549
|
|
@@ -536,26 +559,26 @@ class Runner:
|
|
536
559
|
except MyError as e:
|
537
560
|
raise ValueError(f"{text}\nMyError: {e}")
|
538
561
|
|
562
|
+
result_val = results[-1]
|
539
563
|
if isinstance(expected, np.ndarray):
|
540
|
-
if not
|
564
|
+
if not isinstance(result_val, np.ndarray):
|
565
|
+
raise ValueError(f"{text}: Result is not an ndarray")
|
566
|
+
if not np.array_equal(expected, result_val):
|
541
567
|
raise ValueError(f"{text}: Numpy arrays don't match")
|
542
|
-
elif expected !=
|
543
|
-
raise ValueError(f"{text}: Expected: {expected}, got {
|
568
|
+
elif expected != result_val:
|
569
|
+
raise ValueError(f"{text}: Expected: {expected}, got {result_val}")
|
544
570
|
|
545
571
|
cases(
|
546
572
|
("345", 345),
|
547
573
|
("238.5", 238.5),
|
548
574
|
("-34", -34),
|
549
575
|
("-98.3", -98.3),
|
550
|
-
("+3i", 3j),
|
551
576
|
("3sec", 90),
|
552
577
|
("-3sec", -90),
|
553
578
|
("0.2sec", 6),
|
554
579
|
("(+ 4 3)", 7),
|
555
580
|
("(+ 4 3 2)", 9),
|
556
581
|
("(+ 10.5 3)", 13.5),
|
557
|
-
("(+ 3+4i -2-2i)", 1 + 2j),
|
558
|
-
("(+ 3+4i -2-2i 5)", 6 + 2j),
|
559
582
|
("(- 4 3)", 1),
|
560
583
|
("(- 3)", -3),
|
561
584
|
("(- 10.5 3)", 7.5),
|
@@ -564,7 +587,6 @@ class Runner:
|
|
564
587
|
("(/ 5)", 0.2),
|
565
588
|
("(/ 6 1)", 6.0),
|
566
589
|
("30/1", Fraction(30)),
|
567
|
-
("(sqrt -4)", 2j),
|
568
590
|
("(pow 2 3)", 8),
|
569
591
|
("(pow 4 0.5)", 2.0),
|
570
592
|
("(abs 1.0)", 1.0),
|
@@ -579,7 +601,6 @@ class Runner:
|
|
579
601
|
("(int? #t)", False),
|
580
602
|
("(int? #f)", False),
|
581
603
|
("(int? 4/5)", False),
|
582
|
-
("(int? 0+2i)", False),
|
583
604
|
('(int? "hello")', False),
|
584
605
|
('(int? "3")', False),
|
585
606
|
("(float? -23.4)", True),
|
@@ -593,7 +614,6 @@ class Runner:
|
|
593
614
|
('(define apple "Red Wood") apple', "Red Wood"),
|
594
615
|
("(= 1 1.0)", True),
|
595
616
|
("(= 1 2)", False),
|
596
|
-
("(= 2+3i 2+3i 2+3i)", True),
|
597
617
|
("(= 1)", True),
|
598
618
|
("(+)", 0),
|
599
619
|
("(*)", 1),
|
@@ -602,7 +622,6 @@ class Runner:
|
|
602
622
|
('(if #f mango "Hi")', "Hi"),
|
603
623
|
('{if (= [+ 3 4] 7) "yes" "no"}', "yes"),
|
604
624
|
("((if #t + -) 3 4)", 7),
|
605
|
-
("((if #t + oops) 3+3i 4-2i)", 7 + 1j),
|
606
625
|
("((if #f + -) 3 4)", -1),
|
607
626
|
("(when (positive? 3) 17)", 17),
|
608
627
|
("(string)", ""),
|
@@ -675,14 +694,14 @@ class Runner:
|
|
675
694
|
('#(#("sym" "symbol?") "bool?")', [["sym", "symbol?"], "bool?"]),
|
676
695
|
)
|
677
696
|
|
678
|
-
def palet_scripts(self):
|
697
|
+
def palet_scripts(self) -> None:
|
679
698
|
self.raw(["palet", "resources/scripts/scope.pal"])
|
680
699
|
self.raw(["palet", "resources/scripts/maxcut.pal"])
|
681
700
|
self.raw(["palet", "resources/scripts/case.pal"])
|
682
701
|
self.raw(["palet", "resources/scripts/testmath.pal"])
|
683
702
|
|
684
703
|
|
685
|
-
def run_tests(
|
704
|
+
def run_tests(tests: list[Callable], args: TestArgs) -> None:
|
686
705
|
if args.only != []:
|
687
706
|
tests = list(filter(lambda t: t.__name__ in args.only, tests))
|
688
707
|
|
@@ -781,7 +800,7 @@ def main(sys_args: list[str] | None = None) -> None:
|
|
781
800
|
]
|
782
801
|
)
|
783
802
|
try:
|
784
|
-
run_tests(
|
803
|
+
run_tests(tests, args)
|
785
804
|
except KeyboardInterrupt:
|
786
805
|
print("Testing Interrupted by User.")
|
787
806
|
shutil.rmtree(run.temp_dir)
|
@@ -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,41 +224,41 @@ 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
|
-
|
252
|
-
set_stream_to_0(src, tl, log)
|
250
|
+
set_stream_to_0(tl, log)
|
253
251
|
fcp11_write_xml(export_ops["name"], 10, output, True, tl, log)
|
254
252
|
return
|
255
253
|
|
256
254
|
if export == "shotcut":
|
257
|
-
from auto_editor.
|
255
|
+
from auto_editor.exports.shotcut import shotcut_write_mlt
|
258
256
|
|
259
257
|
shotcut_write_mlt(output, tl)
|
260
258
|
return
|
261
259
|
|
260
|
+
if output == "-":
|
261
|
+
log.error("Exporting media files to stdout is not supported.")
|
262
262
|
out_ext = splitext(output)[1].replace(".", "")
|
263
263
|
|
264
264
|
# Check if export options make sense.
|
@@ -286,7 +286,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
286
286
|
output = bv.open(output_path, "w", container_options=options)
|
287
287
|
|
288
288
|
# Setup video
|
289
|
-
if ctr.default_vid
|
289
|
+
if ctr.default_vid not in ("none", "png") and tl.v:
|
290
290
|
vframes = render_av(output, tl, args, log)
|
291
291
|
output_stream: bv.VideoStream | None
|
292
292
|
output_stream = next(vframes) # type: ignore
|
@@ -522,7 +522,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
522
522
|
|
523
523
|
log.stop_timer()
|
524
524
|
|
525
|
-
if not args.no_open and export
|
525
|
+
if not args.no_open and export == "default":
|
526
526
|
if args.player is None:
|
527
527
|
if sys.platform == "win32":
|
528
528
|
try:
|