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