auto-editor 26.3.1__py3-none-any.whl → 26.3.2__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 +37 -22
- auto_editor/analyze.py +47 -45
- auto_editor/cmds/levels.py +1 -1
- auto_editor/cmds/repl.py +2 -3
- auto_editor/cmds/test.py +328 -384
- auto_editor/edit.py +19 -2
- auto_editor/lang/palet.py +23 -27
- auto_editor/make_layers.py +28 -17
- auto_editor/preview.py +3 -2
- auto_editor/utils/types.py +2 -0
- {auto_editor-26.3.1.dist-info → auto_editor-26.3.2.dist-info}/METADATA +1 -1
- {auto_editor-26.3.1.dist-info → auto_editor-26.3.2.dist-info}/RECORD +17 -17
- {auto_editor-26.3.1.dist-info → auto_editor-26.3.2.dist-info}/WHEEL +1 -1
- {auto_editor-26.3.1.dist-info → auto_editor-26.3.2.dist-info}/LICENSE +0 -0
- {auto_editor-26.3.1.dist-info → auto_editor-26.3.2.dist-info}/entry_points.txt +0 -0
- {auto_editor-26.3.1.dist-info → auto_editor-26.3.2.dist-info}/top_level.txt +0 -0
auto_editor/cmds/test.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import concurrent.futures
|
2
|
+
import hashlib
|
3
3
|
import os
|
4
4
|
import shutil
|
5
5
|
import subprocess
|
6
6
|
import sys
|
7
|
+
from collections.abc import Callable
|
7
8
|
from dataclasses import dataclass, field
|
8
9
|
from fractions import Fraction
|
10
|
+
from hashlib import sha256
|
11
|
+
from tempfile import mkdtemp
|
9
12
|
from time import perf_counter
|
10
|
-
from typing import TYPE_CHECKING
|
11
13
|
|
12
14
|
import av
|
13
15
|
import numpy as np
|
@@ -20,12 +22,6 @@ from auto_editor.lib.err import MyError
|
|
20
22
|
from auto_editor.utils.log import Log
|
21
23
|
from auto_editor.vanparse import ArgumentParser
|
22
24
|
|
23
|
-
if TYPE_CHECKING:
|
24
|
-
from collections.abc import Callable
|
25
|
-
from typing import Any
|
26
|
-
|
27
|
-
from auto_editor.vanparse import ArgumentParser
|
28
|
-
|
29
25
|
|
30
26
|
@dataclass(slots=True)
|
31
27
|
class TestArgs:
|
@@ -53,38 +49,53 @@ def pipe_to_console(cmd: list[str]) -> tuple[int, str, str]:
|
|
53
49
|
return process.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")
|
54
50
|
|
55
51
|
|
52
|
+
all_files = (
|
53
|
+
"aac.m4a",
|
54
|
+
"alac.m4a",
|
55
|
+
"wav/pcm-f32le.wav",
|
56
|
+
"wav/pcm-s32le.wav",
|
57
|
+
"multi-track.mov",
|
58
|
+
"mov_text.mp4",
|
59
|
+
"testsrc.mkv",
|
60
|
+
)
|
61
|
+
log = Log()
|
62
|
+
|
63
|
+
|
64
|
+
def fileinfo(path: str) -> FileInfo:
|
65
|
+
return initFileInfo(path, log)
|
66
|
+
|
67
|
+
|
68
|
+
def calculate_sha256(filename: str) -> str:
|
69
|
+
sha256_hash = hashlib.sha256()
|
70
|
+
with open(filename, "rb") as f:
|
71
|
+
for byte_block in iter(lambda: f.read(4096), b""):
|
72
|
+
sha256_hash.update(byte_block)
|
73
|
+
return sha256_hash.hexdigest()
|
74
|
+
|
75
|
+
|
56
76
|
class Runner:
|
57
77
|
def __init__(self) -> None:
|
58
78
|
self.program = [sys.executable, "-m", "auto_editor"]
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
cmd = self.program + inputs + cmd + ["--no-open"]
|
64
|
-
|
65
|
-
if
|
79
|
+
self.temp_dir = mkdtemp()
|
80
|
+
|
81
|
+
def main(self, inputs: list[str], cmd: list[str], output: str | None = None) -> str:
|
82
|
+
assert inputs
|
83
|
+
cmd = self.program + inputs + cmd + ["--no-open", "--progress", "none"]
|
84
|
+
temp_dir = self.temp_dir
|
85
|
+
if not os.path.exists(temp_dir):
|
86
|
+
raise ValueError("Where's the temp dir")
|
87
|
+
if output is None:
|
88
|
+
new_root = sha256("".join(cmd).encode()).hexdigest()[:16]
|
89
|
+
output = os.path.join(temp_dir, new_root)
|
90
|
+
else:
|
66
91
|
root, ext = os.path.splitext(output)
|
67
92
|
if inputs and ext == "":
|
68
93
|
output = root + os.path.splitext(inputs[0])[1]
|
69
|
-
|
70
|
-
|
71
|
-
if output is None and inputs:
|
72
|
-
root, ext = os.path.splitext(inputs[0])
|
73
|
-
|
74
|
-
if "--export_as_json" in cmd:
|
75
|
-
ext = ".json"
|
76
|
-
elif "-exp" in cmd:
|
77
|
-
ext = ".xml"
|
78
|
-
elif "-exf" in cmd:
|
79
|
-
ext = ".fcpxml"
|
80
|
-
elif "-exs" in cmd:
|
81
|
-
ext = ".mlt"
|
82
|
-
|
83
|
-
output = f"{root}_ALTERED{ext}"
|
94
|
+
output = os.path.join(temp_dir, output)
|
84
95
|
|
85
|
-
returncode, stdout, stderr = pipe_to_console(cmd)
|
96
|
+
returncode, stdout, stderr = pipe_to_console(cmd + ["--output", output])
|
86
97
|
if returncode > 0:
|
87
|
-
raise Exception(f"{stdout}\n{stderr}\n")
|
98
|
+
raise Exception(f"Test returned: {returncode}\n{stdout}\n{stderr}\n")
|
88
99
|
|
89
100
|
return output
|
90
101
|
|
@@ -106,182 +117,107 @@ class Runner:
|
|
106
117
|
else:
|
107
118
|
raise Exception("Program should not respond with code 0 but did!")
|
108
119
|
|
109
|
-
|
110
|
-
def run_tests(tests: list[Callable], args: TestArgs) -> None:
|
111
|
-
def clean_all() -> None:
|
112
|
-
def clean(the_dir: str) -> None:
|
113
|
-
for item in os.listdir(the_dir):
|
114
|
-
if "_ALTERED" in item:
|
115
|
-
os.remove(os.path.join(the_dir, item))
|
116
|
-
if item.endswith("_tracks"):
|
117
|
-
shutil.rmtree(os.path.join(the_dir, item))
|
118
|
-
|
119
|
-
clean("resources")
|
120
|
-
clean(os.getcwd())
|
121
|
-
|
122
|
-
if args.only != []:
|
123
|
-
tests = list(filter(lambda t: t.__name__ in args.only, tests))
|
124
|
-
|
125
|
-
total_time = 0.0
|
126
|
-
|
127
|
-
passed = 0
|
128
|
-
total = len(tests)
|
129
|
-
for index, test in enumerate(tests, start=1):
|
130
|
-
name = test.__name__
|
131
|
-
start = perf_counter()
|
132
|
-
outputs = None
|
133
|
-
|
134
|
-
try:
|
135
|
-
outputs = test()
|
136
|
-
dur = perf_counter() - start
|
137
|
-
total_time += dur
|
138
|
-
except KeyboardInterrupt:
|
139
|
-
print("Testing Interrupted by User.")
|
140
|
-
clean_all()
|
141
|
-
sys.exit(1)
|
142
|
-
except Exception as e:
|
143
|
-
dur = perf_counter() - start
|
144
|
-
total_time += dur
|
145
|
-
print(
|
146
|
-
f"{name:<24} ({index}/{total}) {round(dur, 2):<4} secs \033[1;31m[FAILED]\033[0m",
|
147
|
-
flush=True,
|
148
|
-
)
|
149
|
-
if args.no_fail_fast:
|
150
|
-
print(f"\n{e}")
|
151
|
-
else:
|
152
|
-
print("")
|
153
|
-
clean_all()
|
154
|
-
raise e
|
155
|
-
else:
|
156
|
-
passed += 1
|
157
|
-
print(
|
158
|
-
f"{name:<24} ({index}/{total}) {round(dur, 2):<4} secs [\033[1;32mPASSED\033[0m]",
|
159
|
-
flush=True,
|
160
|
-
)
|
161
|
-
if outputs is not None:
|
162
|
-
if isinstance(outputs, str):
|
163
|
-
outputs = [outputs]
|
164
|
-
|
165
|
-
for out in outputs:
|
166
|
-
try:
|
167
|
-
os.remove(out)
|
168
|
-
except FileNotFoundError:
|
169
|
-
pass
|
170
|
-
|
171
|
-
print(f"\nCompleted\n{passed}/{total}\n{round(total_time, 2)} secs")
|
172
|
-
clean_all()
|
173
|
-
|
174
|
-
|
175
|
-
def main(sys_args: list[str] | None = None):
|
176
|
-
if sys_args is None:
|
177
|
-
sys_args = sys.argv[1:]
|
178
|
-
|
179
|
-
args = test_options(ArgumentParser("test")).parse_args(TestArgs, sys_args)
|
180
|
-
|
181
|
-
run = Runner()
|
182
|
-
log = Log()
|
183
|
-
|
184
|
-
def fileinfo(path: str) -> FileInfo:
|
185
|
-
return initFileInfo(path, log)
|
186
|
-
|
187
|
-
### Tests ###
|
188
|
-
|
189
|
-
all_files = (
|
190
|
-
"aac.m4a",
|
191
|
-
"alac.m4a",
|
192
|
-
"wav/pcm-f32le.wav",
|
193
|
-
"wav/pcm-s32le.wav",
|
194
|
-
"multi-track.mov",
|
195
|
-
"mov_text.mp4",
|
196
|
-
"testsrc.mkv",
|
197
|
-
)
|
198
|
-
|
199
|
-
## API Tests ##
|
200
|
-
|
201
|
-
def help_tests():
|
120
|
+
def test_help(self):
|
202
121
|
"""check the help option, its short, and help on options and groups."""
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
def
|
122
|
+
self.raw(["--help"])
|
123
|
+
self.raw(["-h"])
|
124
|
+
self.raw(["--margin", "--help"])
|
125
|
+
self.raw(["--edit", "-h"])
|
126
|
+
self.raw(["--help", "--help"])
|
127
|
+
self.raw(["-h", "--help"])
|
128
|
+
self.raw(["--help", "-h"])
|
129
|
+
|
130
|
+
def test_version(self):
|
212
131
|
"""Test version flags and debug by itself."""
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
def parser_test():
|
217
|
-
run.check(["example.mp4", "--video-speed"], "needs argument")
|
132
|
+
self.raw(["--version"])
|
133
|
+
self.raw(["-V"])
|
218
134
|
|
219
|
-
def
|
220
|
-
|
221
|
-
run.raw(["info", "resources/only-video/man-on-green-screen.mp4"])
|
222
|
-
run.raw(["info", "resources/multi-track.mov"])
|
223
|
-
run.raw(["info", "resources/new-commentary.mp3"])
|
224
|
-
run.raw(["info", "resources/testsrc.mkv"])
|
135
|
+
def test_parser(self):
|
136
|
+
self.check(["example.mp4", "--video-speed"], "needs argument")
|
225
137
|
|
226
|
-
def
|
227
|
-
|
228
|
-
|
138
|
+
def info(self):
|
139
|
+
self.raw(["info", "example.mp4"])
|
140
|
+
self.raw(["info", "resources/only-video/man-on-green-screen.mp4"])
|
141
|
+
self.raw(["info", "resources/multi-track.mov"])
|
142
|
+
self.raw(["info", "resources/new-commentary.mp3"])
|
143
|
+
self.raw(["info", "resources/testsrc.mkv"])
|
229
144
|
|
230
|
-
def
|
231
|
-
|
232
|
-
|
145
|
+
def levels(self):
|
146
|
+
self.raw(["levels", "resources/multi-track.mov"])
|
147
|
+
self.raw(["levels", "resources/new-commentary.mp3"])
|
233
148
|
|
234
|
-
def
|
235
|
-
|
149
|
+
def subdump(self):
|
150
|
+
self.raw(["subdump", "resources/mov_text.mp4"])
|
151
|
+
self.raw(["subdump", "resources/webvtt.mkv"])
|
236
152
|
|
237
|
-
def
|
238
|
-
|
153
|
+
def desc(self):
|
154
|
+
self.raw(["desc", "example.mp4"])
|
239
155
|
|
156
|
+
def test_example(self) -> None:
|
157
|
+
out = self.main(["example.mp4"], [], output="example_ALTERED.mp4")
|
240
158
|
with av.open(out) as container:
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
159
|
+
video = container.streams[0]
|
160
|
+
audio = container.streams[1]
|
161
|
+
|
162
|
+
assert isinstance(video, av.VideoStream)
|
163
|
+
assert isinstance(audio, av.AudioStream)
|
164
|
+
assert video.base_rate == 30
|
165
|
+
assert video.average_rate is not None
|
166
|
+
assert video.average_rate == 30, video.average_rate
|
167
|
+
assert (video.width, video.height) == (1280, 720)
|
168
|
+
assert video.codec.name == "h264"
|
169
|
+
assert video.language == "eng"
|
170
|
+
assert audio.codec.name == "aac"
|
171
|
+
assert audio.sample_rate == 48000
|
172
|
+
assert audio.language == "eng"
|
173
|
+
|
174
|
+
out1_sha = calculate_sha256(out)
|
175
|
+
|
176
|
+
out = self.main(["example.mp4"], ["--fragmented"], output="example_ALTERED.mp4")
|
177
|
+
with av.open(out) as container:
|
178
|
+
video = container.streams[0]
|
179
|
+
audio = container.streams[1]
|
180
|
+
|
181
|
+
assert isinstance(video, av.VideoStream)
|
182
|
+
assert isinstance(audio, av.AudioStream)
|
183
|
+
assert video.base_rate == 30
|
184
|
+
assert video.average_rate is not None
|
185
|
+
assert round(video.average_rate) == 30, video.average_rate
|
186
|
+
assert (video.width, video.height) == (1280, 720)
|
187
|
+
assert video.codec.name == "h264"
|
188
|
+
assert video.language == "eng"
|
189
|
+
assert audio.codec.name == "aac"
|
190
|
+
assert audio.sample_rate == 48000
|
191
|
+
assert audio.language == "eng"
|
192
|
+
|
193
|
+
assert calculate_sha256(out) != out1_sha, "Fragmented output should be diff."
|
258
194
|
|
259
195
|
# PR #260
|
260
|
-
def
|
261
|
-
|
196
|
+
def test_high_speed(self):
|
197
|
+
self.check(["example.mp4", "--video-speed", "99998"], "empty")
|
262
198
|
|
263
199
|
# Issue #184
|
264
|
-
def
|
265
|
-
|
266
|
-
|
200
|
+
def test_units(self):
|
201
|
+
self.main(["example.mp4"], ["--edit", "all/e", "--set-speed", "125%,-30,end"])
|
202
|
+
self.main(["example.mp4"], ["--edit", "audio:threshold=4%"])
|
267
203
|
|
268
|
-
def
|
269
|
-
|
270
|
-
|
204
|
+
def test_sr_units(self):
|
205
|
+
self.main(["example.mp4"], ["--sample_rate", "44100 Hz"])
|
206
|
+
self.main(["example.mp4"], ["--sample_rate", "44.1 kHz"])
|
271
207
|
|
272
|
-
def
|
273
|
-
|
208
|
+
def test_video_speed(self):
|
209
|
+
self.main(["example.mp4"], ["--video-speed", "1.5"])
|
274
210
|
|
275
|
-
def
|
211
|
+
def test_backwards_range(self):
|
276
212
|
"""
|
277
213
|
Cut out the last 5 seconds of a media file by using negative number in the
|
278
214
|
range.
|
279
215
|
"""
|
280
|
-
|
281
|
-
|
216
|
+
self.main(["example.mp4"], ["--edit", "none", "--cut_out", "-5secs,end"])
|
217
|
+
self.main(["example.mp4"], ["--edit", "all/e", "--add_in", "-5secs,end"])
|
282
218
|
|
283
|
-
def
|
284
|
-
|
219
|
+
def test_cut_out(self):
|
220
|
+
self.main(
|
285
221
|
["example.mp4"],
|
286
222
|
[
|
287
223
|
"--edit",
|
@@ -294,112 +230,93 @@ def main(sys_args: list[str] | None = None):
|
|
294
230
|
"2secs,10secs",
|
295
231
|
],
|
296
232
|
)
|
297
|
-
|
233
|
+
self.main(
|
298
234
|
["example.mp4"],
|
299
235
|
["--edit", "all/e", "--video_speed", "2", "--add_in", "2secs,10secs"],
|
300
236
|
)
|
301
237
|
|
302
|
-
def
|
238
|
+
def test_gif(self):
|
303
239
|
"""
|
304
240
|
Feed auto-editor a gif file and make sure it can spit out a correctly formatted
|
305
241
|
gif. No editing is requested.
|
306
242
|
"""
|
307
|
-
|
308
|
-
|
309
|
-
)
|
243
|
+
input = ["resources/only-video/man-on-green-screen.gif"]
|
244
|
+
out = self.main(input, ["--edit", "none", "--cut-out", "2sec,end"], "out.gif")
|
310
245
|
assert fileinfo(out).videos[0].codec == "gif"
|
311
246
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
run.main(["example.mp4"], ["-m", "0.3sec"])
|
318
|
-
run.main(["example.mp4"], ["-m", "6,-3secs"])
|
319
|
-
return run.main(["example.mp4"], ["-m", "0.4 seconds", "--stats"])
|
247
|
+
def test_margin(self):
|
248
|
+
self.main(["example.mp4"], ["-m", "3"])
|
249
|
+
self.main(["example.mp4"], ["-m", "0.3sec"])
|
250
|
+
self.main(["example.mp4"], ["-m", "0.1 seconds"])
|
251
|
+
self.main(["example.mp4"], ["-m", "6,-3secs"])
|
320
252
|
|
321
|
-
def
|
253
|
+
def test_input_extension(self):
|
322
254
|
"""Input file must have an extension. Throw error if none is given."""
|
255
|
+
path = os.path.join(self.temp_dir, "example")
|
256
|
+
shutil.copy("example.mp4", path)
|
257
|
+
self.check([path, "--no-open"], "must have an extension")
|
323
258
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
return "example"
|
328
|
-
|
329
|
-
def output_extension():
|
330
|
-
# Add input extension to output name if no output extension is given.
|
331
|
-
out = run.main(inputs=["example.mp4"], cmd=[], output="out")
|
332
|
-
|
333
|
-
assert out == "out.mp4"
|
334
|
-
assert fileinfo(out).videos[0].codec == "h264"
|
335
|
-
|
336
|
-
out = run.main(inputs=["resources/testsrc.mkv"], cmd=[], output="out")
|
337
|
-
assert out == "out.mkv"
|
338
|
-
assert fileinfo(out).videos[0].codec == "h264"
|
339
|
-
|
340
|
-
return "out.mp4", "out.mkv"
|
341
|
-
|
342
|
-
def progress():
|
343
|
-
run.main(["example.mp4"], ["--progress", "machine"])
|
344
|
-
run.main(["example.mp4"], ["--progress", "none"])
|
345
|
-
return run.main(["example.mp4"], ["--progress", "ascii"])
|
259
|
+
def test_silent_threshold(self):
|
260
|
+
with av.open("resources/new-commentary.mp3") as container:
|
261
|
+
assert container.duration / av.time_base == 6.732
|
346
262
|
|
347
|
-
|
348
|
-
return run.main(
|
263
|
+
out = self.main(
|
349
264
|
["resources/new-commentary.mp3"], ["--edit", "audio:threshold=0.1"]
|
350
265
|
)
|
266
|
+
out += ".mp3"
|
351
267
|
|
352
|
-
|
353
|
-
|
354
|
-
assert len(fileinfo(out).audios) == 2
|
268
|
+
with av.open(out) as container:
|
269
|
+
assert container.duration / av.time_base == 6.552
|
355
270
|
|
356
|
-
|
271
|
+
def test_track(self):
|
272
|
+
out = self.main(["resources/multi-track.mov"], ["--keep_tracks_seperate"], "te")
|
273
|
+
assert len(fileinfo(out).audios) == 2
|
357
274
|
|
358
|
-
def
|
359
|
-
out =
|
360
|
-
|
361
|
-
return out, out2
|
275
|
+
def test_export_json(self):
|
276
|
+
out = self.main(["example.mp4"], ["--export_as_json"], "c77130d763d40e8.json")
|
277
|
+
self.main([out], [])
|
362
278
|
|
363
|
-
def
|
364
|
-
|
279
|
+
def test_import_v1(self):
|
280
|
+
path = os.path.join(self.temp_dir, "v1.json")
|
281
|
+
with open(path, "w") as file:
|
365
282
|
file.write(
|
366
283
|
"""{"version": "1", "source": "example.mp4", "chunks": [ [0, 26, 1.0], [26, 34, 0] ]}"""
|
367
284
|
)
|
368
285
|
|
369
|
-
|
286
|
+
self.main([path], [])
|
370
287
|
|
371
|
-
def
|
372
|
-
|
288
|
+
def test_premiere_named_export(self):
|
289
|
+
self.main(["example.mp4"], ["--export", 'premiere:name="Foo Bar"'])
|
373
290
|
|
374
|
-
def
|
375
|
-
# cn = fileinfo(
|
291
|
+
def test_export_subtitles(self):
|
292
|
+
# cn = fileinfo(self.main(["resources/mov_text.mp4"], [], "movtext_out.mp4"))
|
376
293
|
|
377
294
|
# assert len(cn.videos) == 1
|
378
295
|
# assert len(cn.audios) == 1
|
379
296
|
# assert len(cn.subtitles) == 1
|
380
297
|
|
381
|
-
cn = fileinfo(
|
382
|
-
|
298
|
+
cn = fileinfo(self.main(["resources/webvtt.mkv"], [], "webvtt_out.mkv"))
|
383
299
|
assert len(cn.videos) == 1
|
384
300
|
assert len(cn.audios) == 1
|
385
301
|
assert len(cn.subtitles) == 1
|
386
302
|
|
387
|
-
def
|
388
|
-
cn = fileinfo(
|
389
|
-
|
303
|
+
def test_scale(self):
|
304
|
+
cn = fileinfo(self.main(["example.mp4"], ["--scale", "1.5"], "scale.mp4"))
|
390
305
|
assert cn.videos[0].fps == 30
|
391
306
|
assert cn.videos[0].width == 1920
|
392
307
|
assert cn.videos[0].height == 1080
|
393
308
|
assert cn.audios[0].samplerate == 48000
|
394
309
|
|
395
|
-
cn = fileinfo(
|
396
|
-
|
310
|
+
cn = fileinfo(self.main(["example.mp4"], ["--scale", "0.2"], "scale.mp4"))
|
397
311
|
assert cn.videos[0].fps == 30
|
398
312
|
assert cn.videos[0].width == 256
|
399
313
|
assert cn.videos[0].height == 144
|
400
314
|
assert cn.audios[0].samplerate == 48000
|
401
315
|
|
402
|
-
|
316
|
+
def test_resolution(self):
|
317
|
+
out = self.main(
|
318
|
+
["example.mp4"], ["-res", "700,380", "-b", "darkgreen"], "green"
|
319
|
+
)
|
403
320
|
cn = fileinfo(out)
|
404
321
|
|
405
322
|
assert cn.videos[0].fps == 30
|
@@ -407,182 +324,159 @@ def main(sys_args: list[str] | None = None):
|
|
407
324
|
assert cn.videos[0].height == 380
|
408
325
|
assert cn.audios[0].samplerate == 48000
|
409
326
|
|
410
|
-
|
327
|
+
def test_premiere(self):
|
328
|
+
for test_name in all_files:
|
329
|
+
p_xml = self.main([f"resources/{test_name}"], ["-exp"], "out.xml")
|
330
|
+
self.main([p_xml], [])
|
411
331
|
|
412
|
-
def
|
413
|
-
results = set()
|
332
|
+
def test_export(self):
|
414
333
|
for test_name in all_files:
|
415
334
|
test_file = f"resources/{test_name}"
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
return tuple(results)
|
421
|
-
|
422
|
-
def export():
|
423
|
-
results = set()
|
335
|
+
self.main([test_file], ["--export", "final-cut-pro:version=10"])
|
336
|
+
self.main([test_file], ["--export", "final-cut-pro:version=11"])
|
337
|
+
self.main([test_file], ["-exs"])
|
338
|
+
self.main([test_file], ["--stats"])
|
424
339
|
|
340
|
+
def test_clip_sequence(self):
|
425
341
|
for test_name in all_files:
|
426
342
|
test_file = f"resources/{test_name}"
|
427
|
-
|
428
|
-
run.main([test_file], ["--edit", "none"])
|
429
|
-
results.add(run.main([test_file], ["--export", "final-cut-pro:version=10"]))
|
430
|
-
results.add(run.main([test_file], ["--export", "final-cut-pro:version=11"]))
|
431
|
-
results.add(run.main([test_file], ["-exs"]))
|
432
|
-
results.add(run.main([test_file], ["--export_as_clip_sequence"]))
|
433
|
-
run.main([test_file], ["--stats"])
|
434
|
-
|
435
|
-
return tuple(results)
|
343
|
+
self.main([test_file], ["--export_as_clip_sequence"])
|
436
344
|
|
437
|
-
def
|
438
|
-
|
439
|
-
|
345
|
+
def test_codecs(self):
|
346
|
+
self.main(["example.mp4"], ["--video_codec", "h264"])
|
347
|
+
self.main(["example.mp4"], ["--audio_codec", "ac3"])
|
440
348
|
|
441
349
|
# Issue #241
|
442
|
-
def
|
443
|
-
out =
|
350
|
+
def test_multi_track_edit(self):
|
351
|
+
out = self.main(
|
444
352
|
["example.mp4", "resources/multi-track.mov"],
|
445
353
|
["--edit", "audio:stream=1"],
|
446
|
-
"
|
354
|
+
"multi-track_ALTERED.mov",
|
447
355
|
)
|
448
356
|
assert len(fileinfo(out).audios) == 1
|
449
357
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
out = run.main(["example.mp4"], ["--cut-out", "0,171"], "hmm.mp4")
|
454
|
-
out2 = run.main(["example.mp4", "hmm.mp4"], ["--debug"])
|
455
|
-
return out, out2
|
358
|
+
def test_concat(self):
|
359
|
+
out = self.main(["example.mp4"], ["--cut-out", "0,171"], "hmm.mp4")
|
360
|
+
self.main(["example.mp4", out], ["--debug"])
|
456
361
|
|
457
|
-
def
|
458
|
-
out =
|
362
|
+
def test_concat_mux_tracks(self):
|
363
|
+
out = self.main(
|
364
|
+
["example.mp4", "resources/multi-track.mov"], [], "concat_mux.mov"
|
365
|
+
)
|
459
366
|
assert len(fileinfo(out).audios) == 1
|
460
367
|
|
461
|
-
|
462
|
-
|
463
|
-
def concat_multiple_tracks():
|
464
|
-
out = run.main(
|
368
|
+
def test_concat_multi_tracks(self):
|
369
|
+
out = self.main(
|
465
370
|
["resources/multi-track.mov", "resources/multi-track.mov"],
|
466
371
|
["--keep-tracks-separate"],
|
467
372
|
"out.mov",
|
468
373
|
)
|
469
374
|
assert len(fileinfo(out).audios) == 2
|
470
|
-
out =
|
375
|
+
out = self.main(
|
471
376
|
["example.mp4", "resources/multi-track.mov"],
|
472
377
|
["--keep-tracks-separate"],
|
473
378
|
"out.mov",
|
474
379
|
)
|
475
380
|
assert len(fileinfo(out).audios) == 2
|
476
381
|
|
477
|
-
|
478
|
-
|
479
|
-
def frame_rate():
|
480
|
-
cn = fileinfo(run.main(["example.mp4"], ["-r", "15", "--no-seek"]))
|
382
|
+
def test_frame_rate(self):
|
383
|
+
cn = fileinfo(self.main(["example.mp4"], ["-r", "15", "--no-seek"], "fr.mp4"))
|
481
384
|
video = cn.videos[0]
|
482
385
|
assert video.fps == 15, video.fps
|
483
386
|
assert video.duration - 17.33333333333333333333333 < 3, video.duration
|
484
387
|
|
485
|
-
cn = fileinfo(
|
388
|
+
cn = fileinfo(self.main(["example.mp4"], ["-r", "20"], "fr.mp4"))
|
486
389
|
video = cn.videos[0]
|
487
390
|
assert video.fps == 20, video.fps
|
488
391
|
assert video.duration - 17.33333333333333333333333 < 2
|
489
392
|
|
490
|
-
|
393
|
+
def test_frame_rate_60(self):
|
394
|
+
cn = fileinfo(self.main(["example.mp4"], ["-r", "60"], "fr60.mp4"))
|
491
395
|
video = cn.videos[0]
|
492
396
|
|
493
397
|
assert video.fps == 60, video.fps
|
494
398
|
assert video.duration - 17.33333333333333333333333 < 0.3
|
495
399
|
|
496
|
-
|
497
|
-
|
498
|
-
# def embedded_image():
|
499
|
-
# out1 = run.main(["resources/embedded-image/h264-png.mp4"], [])
|
400
|
+
# def embedded_image(self):
|
401
|
+
# out1 = self.main(["resources/embedded-image/h264-png.mp4"], [])
|
500
402
|
# cn = fileinfo(out1)
|
501
403
|
# assert cn.videos[0].codec == "h264"
|
502
404
|
# assert cn.videos[1].codec == "png"
|
503
405
|
|
504
|
-
# out2 =
|
406
|
+
# out2 = self.main(["resources/embedded-image/h264-mjpeg.mp4"], [])
|
505
407
|
# cn = fileinfo(out2)
|
506
408
|
# assert cn.videos[0].codec == "h264"
|
507
409
|
# assert cn.videos[1].codec == "mjpeg"
|
508
410
|
|
509
|
-
# out3 =
|
411
|
+
# out3 = self.main(["resources/embedded-image/h264-png.mkv"], [])
|
510
412
|
# cn = fileinfo(out3)
|
511
413
|
# assert cn.videos[0].codec == "h264"
|
512
414
|
# assert cn.videos[1].codec == "png"
|
513
415
|
|
514
|
-
# out4 =
|
416
|
+
# out4 = self.main(["resources/embedded-image/h264-mjpeg.mkv"], [])
|
515
417
|
# cn = fileinfo(out4)
|
516
418
|
# assert cn.videos[0].codec == "h264"
|
517
419
|
# assert cn.videos[1].codec == "mjpeg"
|
518
420
|
|
519
|
-
|
520
|
-
|
521
|
-
def motion():
|
522
|
-
out = run.main(
|
421
|
+
def test_motion(self):
|
422
|
+
self.main(
|
523
423
|
["resources/only-video/man-on-green-screen.mp4"],
|
524
424
|
["--edit", "motion", "--margin", "0"],
|
525
425
|
)
|
526
|
-
|
426
|
+
self.main(
|
527
427
|
["resources/only-video/man-on-green-screen.mp4"],
|
528
428
|
["--edit", "motion:threshold=0,width=200"],
|
529
429
|
)
|
530
|
-
return out, out2
|
531
430
|
|
532
|
-
def
|
533
|
-
|
534
|
-
|
535
|
-
|
431
|
+
def test_edit_positive(self):
|
432
|
+
self.main(["resources/multi-track.mov"], ["--edit", "audio:stream=all"])
|
433
|
+
self.main(["resources/multi-track.mov"], ["--edit", "not audio:stream=all"])
|
434
|
+
self.main(
|
536
435
|
["resources/multi-track.mov"],
|
537
436
|
["--edit", "(or (not audio:threshold=4%) audio:stream=1)"],
|
538
437
|
)
|
539
|
-
|
438
|
+
self.main(
|
540
439
|
["resources/multi-track.mov"],
|
541
440
|
["--edit", "(or (not audio:threshold=4%) (not audio:stream=1))"],
|
542
441
|
)
|
543
|
-
return out
|
544
442
|
|
545
|
-
def
|
546
|
-
|
443
|
+
def test_edit_negative(self):
|
444
|
+
self.check(
|
547
445
|
["resources/wav/example-cut-s16le.wav", "--edit", "motion"],
|
548
|
-
"video stream
|
446
|
+
"video stream",
|
549
447
|
)
|
550
|
-
|
448
|
+
self.check(
|
551
449
|
["resources/only-video/man-on-green-screen.gif", "--edit", "audio"],
|
552
|
-
"audio stream
|
450
|
+
"audio stream",
|
553
451
|
)
|
554
452
|
|
555
|
-
def
|
556
|
-
|
557
|
-
|
558
|
-
def prores():
|
559
|
-
run.main(["resources/testsrc.mp4", "-c:v", "prores", "-o", "out.mkv"], [])
|
560
|
-
assert fileinfo("out.mkv").videos[0].pix_fmt == "yuv422p10le"
|
453
|
+
def test_yuv442p(self):
|
454
|
+
self.main(["resources/test_yuv422p.mp4"], [])
|
561
455
|
|
562
|
-
|
563
|
-
|
456
|
+
def test_prores(self):
|
457
|
+
out = self.main(["resources/testsrc.mp4"], ["-c:v", "prores"], "prores.mkv")
|
458
|
+
assert fileinfo(out).videos[0].pix_fmt == "yuv422p10le"
|
564
459
|
|
565
|
-
|
460
|
+
out2 = self.main([out], ["-c:v", "prores"], "prores2.mkv")
|
461
|
+
assert fileinfo(out2).videos[0].pix_fmt == "yuv422p10le"
|
566
462
|
|
567
463
|
# Issue 280
|
568
|
-
def
|
569
|
-
out =
|
464
|
+
def test_SAR(self):
|
465
|
+
out = self.main(["resources/SAR-2by3.mp4"], [], "2by3_out.mp4")
|
570
466
|
assert fileinfo(out).videos[0].sar == Fraction(2, 3)
|
571
467
|
|
572
|
-
|
468
|
+
def test_audio_norm_f(self):
|
469
|
+
return self.main(["example.mp4"], ["--audio-normalize", "#f"])
|
573
470
|
|
574
|
-
def
|
575
|
-
return
|
576
|
-
|
577
|
-
def audio_norm_ebu():
|
578
|
-
return run.main(
|
471
|
+
def test_audio_norm_ebu(self):
|
472
|
+
return self.main(
|
579
473
|
["example.mp4"], ["--audio-normalize", "ebu:i=-5,lra=20,gain=5,tp=-1"]
|
580
474
|
)
|
581
475
|
|
582
|
-
def palet_python_bridge():
|
476
|
+
def palet_python_bridge(self):
|
583
477
|
env.update(make_standard_env())
|
584
478
|
|
585
|
-
def cases(*cases: tuple[str,
|
479
|
+
def cases(*cases: tuple[str, object]) -> None:
|
586
480
|
for text, expected in cases:
|
587
481
|
try:
|
588
482
|
parser = Parser(Lexer("repl", text))
|
@@ -730,65 +624,115 @@ def main(sys_args: list[str] | None = None):
|
|
730
624
|
('#(#("sym" "symbol?") "bool?")', [["sym", "symbol?"], "bool?"]),
|
731
625
|
)
|
732
626
|
|
733
|
-
def palet_scripts():
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
627
|
+
def palet_scripts(self):
|
628
|
+
self.raw(["palet", "resources/scripts/scope.pal"])
|
629
|
+
self.raw(["palet", "resources/scripts/maxcut.pal"])
|
630
|
+
self.raw(["palet", "resources/scripts/case.pal"])
|
631
|
+
self.raw(["palet", "resources/scripts/testmath.pal"])
|
632
|
+
|
738
633
|
|
634
|
+
def run_tests(runner: Runner, tests: list[Callable], args: TestArgs) -> None:
|
635
|
+
if args.only != []:
|
636
|
+
tests = list(filter(lambda t: t.__name__ in args.only, tests))
|
637
|
+
|
638
|
+
total_time = 0.0
|
639
|
+
real_time = perf_counter()
|
640
|
+
passed = 0
|
641
|
+
total = len(tests)
|
642
|
+
|
643
|
+
def timed_test(test_func):
|
644
|
+
start_time = perf_counter()
|
645
|
+
try:
|
646
|
+
test_func()
|
647
|
+
success = True
|
648
|
+
except Exception as e:
|
649
|
+
success = False
|
650
|
+
exception = e
|
651
|
+
end_time = perf_counter()
|
652
|
+
duration = end_time - start_time
|
653
|
+
|
654
|
+
if success:
|
655
|
+
return (True, duration, None)
|
656
|
+
else:
|
657
|
+
return (False, duration, exception)
|
658
|
+
|
659
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
660
|
+
future_to_data = {}
|
661
|
+
for test in tests:
|
662
|
+
future = executor.submit(timed_test, test)
|
663
|
+
future_to_data[future] = test
|
664
|
+
|
665
|
+
index = 0
|
666
|
+
for future in concurrent.futures.as_completed(future_to_data):
|
667
|
+
test = future_to_data[future]
|
668
|
+
name = test.__name__
|
669
|
+
success, dur, exception = future.result()
|
670
|
+
total_time += dur
|
671
|
+
index += 1
|
672
|
+
|
673
|
+
if success:
|
674
|
+
passed += 1
|
675
|
+
print(
|
676
|
+
f"{name:<26} ({index}/{total}) {round(dur, 2):<4} secs [\033[1;32mPASSED\033[0m]",
|
677
|
+
flush=True,
|
678
|
+
)
|
679
|
+
else:
|
680
|
+
print(
|
681
|
+
f"{name:<26} ({index}/{total}) {round(dur, 2):<4} secs \033[1;31m[FAILED]\033[0m",
|
682
|
+
flush=True,
|
683
|
+
)
|
684
|
+
if args.no_fail_fast:
|
685
|
+
print(f"\n{exception}")
|
686
|
+
else:
|
687
|
+
print("")
|
688
|
+
raise exception
|
689
|
+
|
690
|
+
real_time = round(perf_counter() - real_time, 2)
|
691
|
+
total_time = round(total_time, 2)
|
692
|
+
print(
|
693
|
+
f"\nCompleted {passed}/{total}\nreal time: {real_time} secs total: {total_time} secs"
|
694
|
+
)
|
695
|
+
|
696
|
+
|
697
|
+
def main(sys_args: list[str] | None = None) -> None:
|
698
|
+
if sys_args is None:
|
699
|
+
sys_args = sys.argv[1:]
|
700
|
+
|
701
|
+
args = test_options(ArgumentParser("test")).parse_args(TestArgs, sys_args)
|
702
|
+
run = Runner()
|
739
703
|
tests = []
|
740
704
|
|
705
|
+
test_methods = {
|
706
|
+
name: getattr(run, name)
|
707
|
+
for name in dir(Runner)
|
708
|
+
if callable(getattr(Runner, name)) and name not in ["main", "raw", "check"]
|
709
|
+
}
|
710
|
+
|
741
711
|
if args.category in {"palet", "all"}:
|
742
|
-
tests.extend(
|
712
|
+
tests.extend(
|
713
|
+
[test_methods[name] for name in ["palet_python_bridge", "palet_scripts"]]
|
714
|
+
)
|
743
715
|
|
744
716
|
if args.category in {"sub", "all"}:
|
745
|
-
tests.extend(
|
717
|
+
tests.extend(
|
718
|
+
[test_methods[name] for name in ["info", "levels", "subdump", "desc"]]
|
719
|
+
)
|
746
720
|
|
747
721
|
if args.category in {"cli", "all"}:
|
748
722
|
tests.extend(
|
749
723
|
[
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
prores,
|
754
|
-
edit_negative_tests,
|
755
|
-
edit_positive_tests,
|
756
|
-
audio_norm_f,
|
757
|
-
audio_norm_ebu,
|
758
|
-
export_json_tests,
|
759
|
-
import_v1_tests,
|
760
|
-
high_speed_test,
|
761
|
-
video_speed,
|
762
|
-
multi_track_edit,
|
763
|
-
concat_mux_tracks,
|
764
|
-
concat_multiple_tracks,
|
765
|
-
frame_rate,
|
766
|
-
help_tests,
|
767
|
-
version_test,
|
768
|
-
parser_test,
|
769
|
-
concat,
|
770
|
-
example,
|
771
|
-
units,
|
772
|
-
sr_units,
|
773
|
-
backwards_range,
|
774
|
-
cut_out,
|
775
|
-
gif,
|
776
|
-
margin_tests,
|
777
|
-
input_extension,
|
778
|
-
output_extension,
|
779
|
-
progress,
|
780
|
-
silent_threshold,
|
781
|
-
track_tests,
|
782
|
-
codec_tests,
|
783
|
-
premiere_named_export,
|
784
|
-
export_subtitles,
|
785
|
-
export,
|
786
|
-
motion,
|
787
|
-
resolution_and_scale,
|
724
|
+
getattr(run, name)
|
725
|
+
for name in dir(Runner)
|
726
|
+
if callable(getattr(Runner, name)) and name.startswith("test_")
|
788
727
|
]
|
789
728
|
)
|
790
|
-
|
791
|
-
|
729
|
+
try:
|
730
|
+
run_tests(run, tests, args)
|
731
|
+
except KeyboardInterrupt:
|
732
|
+
print("Testing Interrupted by User.")
|
733
|
+
shutil.rmtree(run.temp_dir)
|
734
|
+
sys.exit(1)
|
735
|
+
shutil.rmtree(run.temp_dir)
|
792
736
|
|
793
737
|
|
794
738
|
if __name__ == "__main__":
|