auto-editor 26.3.2__py3-none-any.whl → 27.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- auto_editor/__init__.py +1 -1
- auto_editor/__main__.py +155 -40
- auto_editor/analyze.py +30 -36
- auto_editor/cmds/info.py +1 -1
- auto_editor/cmds/levels.py +3 -3
- auto_editor/cmds/subdump.py +62 -8
- auto_editor/cmds/test.py +73 -53
- auto_editor/edit.py +50 -58
- auto_editor/ffwrapper.py +9 -9
- auto_editor/formats/json.py +2 -2
- auto_editor/{lang/json.py → json.py} +39 -43
- auto_editor/lang/palet.py +2 -2
- auto_editor/lang/stdenv.py +12 -0
- auto_editor/make_layers.py +2 -1
- auto_editor/output.py +6 -6
- auto_editor/render/audio.py +30 -25
- auto_editor/render/subtitle.py +10 -14
- auto_editor/render/video.py +41 -45
- auto_editor/timeline.py +8 -1
- auto_editor/utils/container.py +3 -3
- auto_editor/utils/types.py +7 -118
- {auto_editor-26.3.2.dist-info → auto_editor-27.0.0.dist-info}/METADATA +8 -7
- {auto_editor-26.3.2.dist-info → auto_editor-27.0.0.dist-info}/RECORD +28 -28
- {auto_editor-26.3.2.dist-info → auto_editor-27.0.0.dist-info}/WHEEL +1 -1
- docs/build.py +16 -7
- {auto_editor-26.3.2.dist-info → auto_editor-27.0.0.dist-info}/entry_points.txt +0 -0
- {auto_editor-26.3.2.dist-info → auto_editor-27.0.0.dist-info/licenses}/LICENSE +0 -0
- {auto_editor-26.3.2.dist-info → auto_editor-27.0.0.dist-info}/top_level.txt +0 -0
auto_editor/cmds/test.py
CHANGED
@@ -11,7 +11,7 @@ from hashlib import sha256
|
|
11
11
|
from tempfile import mkdtemp
|
12
12
|
from time import perf_counter
|
13
13
|
|
14
|
-
import
|
14
|
+
import bv
|
15
15
|
import numpy as np
|
16
16
|
|
17
17
|
from auto_editor.ffwrapper import FileInfo, initFileInfo
|
@@ -73,6 +73,10 @@ def calculate_sha256(filename: str) -> str:
|
|
73
73
|
return sha256_hash.hexdigest()
|
74
74
|
|
75
75
|
|
76
|
+
class SkipTest(Exception):
|
77
|
+
pass
|
78
|
+
|
79
|
+
|
76
80
|
class Runner:
|
77
81
|
def __init__(self) -> None:
|
78
82
|
self.program = [sys.executable, "-m", "auto_editor"]
|
@@ -153,36 +157,42 @@ class Runner:
|
|
153
157
|
def desc(self):
|
154
158
|
self.raw(["desc", "example.mp4"])
|
155
159
|
|
160
|
+
def test_movflags(self) -> None:
|
161
|
+
file = "resources/testsrc.mp4"
|
162
|
+
out = self.main([file], ["--faststart"]) + ".mp4"
|
163
|
+
fast = calculate_sha256(out)
|
164
|
+
with bv.open(out) as container:
|
165
|
+
assert isinstance(container.streams[0], bv.VideoStream)
|
166
|
+
assert isinstance(container.streams[1], bv.AudioStream)
|
167
|
+
|
168
|
+
out = self.main([file], ["--no-faststart"]) + ".mp4"
|
169
|
+
nofast = calculate_sha256(out)
|
170
|
+
with bv.open(out) as container:
|
171
|
+
assert isinstance(container.streams[0], bv.VideoStream)
|
172
|
+
assert isinstance(container.streams[1], bv.AudioStream)
|
173
|
+
|
174
|
+
out = self.main([file], ["--fragmented"]) + ".mp4"
|
175
|
+
frag = calculate_sha256(out)
|
176
|
+
with bv.open(out) as container:
|
177
|
+
assert isinstance(container.streams[0], bv.VideoStream)
|
178
|
+
assert isinstance(container.streams[1], bv.AudioStream)
|
179
|
+
|
180
|
+
assert fast != nofast, "+faststart is not being applied"
|
181
|
+
assert frag not in (fast, nofast), "fragmented output should diff."
|
182
|
+
|
156
183
|
def test_example(self) -> None:
|
157
184
|
out = self.main(["example.mp4"], [], output="example_ALTERED.mp4")
|
158
|
-
with
|
159
|
-
|
160
|
-
|
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)
|
185
|
+
with bv.open(out) as container:
|
186
|
+
assert container.duration is not None
|
187
|
+
assert container.duration > 17300000 and container.duration < 2 << 24
|
175
188
|
|
176
|
-
out = self.main(["example.mp4"], ["--fragmented"], output="example_ALTERED.mp4")
|
177
|
-
with av.open(out) as container:
|
178
189
|
video = container.streams[0]
|
179
190
|
audio = container.streams[1]
|
180
|
-
|
181
|
-
assert isinstance(
|
182
|
-
assert isinstance(audio, av.AudioStream)
|
191
|
+
assert isinstance(video, bv.VideoStream)
|
192
|
+
assert isinstance(audio, bv.AudioStream)
|
183
193
|
assert video.base_rate == 30
|
184
194
|
assert video.average_rate is not None
|
185
|
-
assert
|
195
|
+
assert video.average_rate == 30, video.average_rate
|
186
196
|
assert (video.width, video.height) == (1280, 720)
|
187
197
|
assert video.codec.name == "h264"
|
188
198
|
assert video.language == "eng"
|
@@ -190,8 +200,6 @@ class Runner:
|
|
190
200
|
assert audio.sample_rate == 48000
|
191
201
|
assert audio.language == "eng"
|
192
202
|
|
193
|
-
assert calculate_sha256(out) != out1_sha, "Fragmented output should be diff."
|
194
|
-
|
195
203
|
# PR #260
|
196
204
|
def test_high_speed(self):
|
197
205
|
self.check(["example.mp4", "--video-speed", "99998"], "empty")
|
@@ -257,19 +265,19 @@ class Runner:
|
|
257
265
|
self.check([path, "--no-open"], "must have an extension")
|
258
266
|
|
259
267
|
def test_silent_threshold(self):
|
260
|
-
with
|
261
|
-
assert container.duration /
|
268
|
+
with bv.open("resources/new-commentary.mp3") as container:
|
269
|
+
assert container.duration / bv.time_base == 6.732
|
262
270
|
|
263
271
|
out = self.main(
|
264
272
|
["resources/new-commentary.mp3"], ["--edit", "audio:threshold=0.1"]
|
265
273
|
)
|
266
274
|
out += ".mp3"
|
267
275
|
|
268
|
-
with
|
269
|
-
assert container.duration /
|
276
|
+
with bv.open(out) as container:
|
277
|
+
assert container.duration / bv.time_base == 6.552
|
270
278
|
|
271
279
|
def test_track(self):
|
272
|
-
out = self.main(["resources/multi-track.mov"], [
|
280
|
+
out = self.main(["resources/multi-track.mov"], []) + ".mov"
|
273
281
|
assert len(fileinfo(out).audios) == 2
|
274
282
|
|
275
283
|
def test_export_json(self):
|
@@ -353,30 +361,24 @@ class Runner:
|
|
353
361
|
["--edit", "audio:stream=1"],
|
354
362
|
"multi-track_ALTERED.mov",
|
355
363
|
)
|
356
|
-
assert len(fileinfo(out).audios) ==
|
364
|
+
assert len(fileinfo(out).audios) == 2
|
357
365
|
|
358
366
|
def test_concat(self):
|
359
367
|
out = self.main(["example.mp4"], ["--cut-out", "0,171"], "hmm.mp4")
|
360
368
|
self.main(["example.mp4", out], ["--debug"])
|
361
369
|
|
362
370
|
def test_concat_mux_tracks(self):
|
363
|
-
|
364
|
-
|
365
|
-
)
|
371
|
+
inputs = ["example.mp4", "resources/multi-track.mov"]
|
372
|
+
out = self.main(inputs, ["--mix-audio-streams"], "concat_mux.mov")
|
366
373
|
assert len(fileinfo(out).audios) == 1
|
367
374
|
|
368
375
|
def test_concat_multi_tracks(self):
|
369
376
|
out = self.main(
|
370
|
-
["resources/multi-track.mov", "resources/multi-track.mov"],
|
371
|
-
["--keep-tracks-separate"],
|
372
|
-
"out.mov",
|
377
|
+
["resources/multi-track.mov", "resources/multi-track.mov"], [], "out.mov"
|
373
378
|
)
|
374
379
|
assert len(fileinfo(out).audios) == 2
|
375
|
-
|
376
|
-
|
377
|
-
["--keep-tracks-separate"],
|
378
|
-
"out.mov",
|
379
|
-
)
|
380
|
+
inputs = ["example.mp4", "resources/multi-track.mov"]
|
381
|
+
out = self.main(inputs, [], "out.mov")
|
380
382
|
assert len(fileinfo(out).audios) == 2
|
381
383
|
|
382
384
|
def test_frame_rate(self):
|
@@ -460,6 +462,21 @@ class Runner:
|
|
460
462
|
out2 = self.main([out], ["-c:v", "prores"], "prores2.mkv")
|
461
463
|
assert fileinfo(out2).videos[0].pix_fmt == "yuv422p10le"
|
462
464
|
|
465
|
+
def test_decode_hevc(self):
|
466
|
+
out = self.main(["resources/testsrc-hevc.mp4"], ["-c:v", "h264"]) + ".mp4"
|
467
|
+
output = fileinfo(out)
|
468
|
+
assert output.videos[0].codec == "h264"
|
469
|
+
assert output.videos[0].pix_fmt == "yuv420p"
|
470
|
+
|
471
|
+
def test_encode_hevc(self):
|
472
|
+
if os.environ.get("GITHUB_ACTIONS") == "true":
|
473
|
+
raise SkipTest()
|
474
|
+
|
475
|
+
out = self.main(["resources/testsrc.mp4"], ["-c:v", "hevc"], "out.mkv")
|
476
|
+
output = fileinfo(out)
|
477
|
+
assert output.videos[0].codec == "hevc"
|
478
|
+
assert output.videos[0].pix_fmt == "yuv420p"
|
479
|
+
|
463
480
|
# Issue 280
|
464
481
|
def test_SAR(self):
|
465
482
|
out = self.main(["resources/SAR-2by3.mp4"], [], "2by3_out.mp4")
|
@@ -642,16 +659,21 @@ def run_tests(runner: Runner, tests: list[Callable], args: TestArgs) -> None:
|
|
642
659
|
|
643
660
|
def timed_test(test_func):
|
644
661
|
start_time = perf_counter()
|
662
|
+
skipped = False
|
645
663
|
try:
|
646
664
|
test_func()
|
647
665
|
success = True
|
666
|
+
except SkipTest:
|
667
|
+
skipped = True
|
648
668
|
except Exception as e:
|
649
669
|
success = False
|
650
670
|
exception = e
|
651
671
|
end_time = perf_counter()
|
652
672
|
duration = end_time - start_time
|
653
673
|
|
654
|
-
if
|
674
|
+
if skipped:
|
675
|
+
return (SkipTest, duration, None)
|
676
|
+
elif success:
|
655
677
|
return (True, duration, None)
|
656
678
|
else:
|
657
679
|
return (False, duration, exception)
|
@@ -670,17 +692,15 @@ def run_tests(runner: Runner, tests: list[Callable], args: TestArgs) -> None:
|
|
670
692
|
total_time += dur
|
671
693
|
index += 1
|
672
694
|
|
673
|
-
|
695
|
+
msg = f"{name:<26} ({index}/{total}) {round(dur, 2):<5} secs "
|
696
|
+
if success == SkipTest:
|
674
697
|
passed += 1
|
675
|
-
print(
|
676
|
-
|
677
|
-
|
678
|
-
)
|
698
|
+
print(f"{msg}[\033[38;2;125;125;125;mSKIPPED\033[0m]", flush=True)
|
699
|
+
elif success:
|
700
|
+
passed += 1
|
701
|
+
print(f"{msg}[\033[1;32mPASSED\033[0m]", flush=True)
|
679
702
|
else:
|
680
|
-
print(
|
681
|
-
f"{name:<26} ({index}/{total}) {round(dur, 2):<4} secs \033[1;31m[FAILED]\033[0m",
|
682
|
-
flush=True,
|
683
|
-
)
|
703
|
+
print(f"{msg}\033[1;31m[FAILED]\033[0m", flush=True)
|
684
704
|
if args.no_fail_fast:
|
685
705
|
print(f"\n{exception}")
|
686
706
|
else:
|
auto_editor/edit.py
CHANGED
@@ -6,10 +6,10 @@ from fractions import Fraction
|
|
6
6
|
from heapq import heappop, heappush
|
7
7
|
from os.path import splitext
|
8
8
|
from subprocess import run
|
9
|
-
from typing import Any
|
9
|
+
from typing import TYPE_CHECKING, Any
|
10
10
|
|
11
|
-
import
|
12
|
-
from
|
11
|
+
import bv
|
12
|
+
from bv import AudioResampler, Codec
|
13
13
|
|
14
14
|
from auto_editor.ffwrapper import FileInfo, initFileInfo
|
15
15
|
from auto_editor.lib.contracts import is_int, is_str
|
@@ -24,7 +24,9 @@ from auto_editor.utils.chunks import Chunk, Chunks
|
|
24
24
|
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
|
25
25
|
from auto_editor.utils.container import Container, container_constructor
|
26
26
|
from auto_editor.utils.log import Log
|
27
|
-
|
27
|
+
|
28
|
+
if TYPE_CHECKING:
|
29
|
+
from auto_editor.__main__ import Args
|
28
30
|
|
29
31
|
|
30
32
|
def set_output(
|
@@ -81,18 +83,10 @@ def set_video_codec(
|
|
81
83
|
return ctr.default_vid
|
82
84
|
return codec
|
83
85
|
|
84
|
-
if codec == "copy":
|
85
|
-
log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
|
86
|
-
if src is None:
|
87
|
-
log.error("No input to copy its codec from.")
|
88
|
-
if not src.videos:
|
89
|
-
log.error("Input file does not have a video stream to copy codec from.")
|
90
|
-
codec = src.videos[0].codec
|
91
|
-
|
92
86
|
if ctr.vcodecs is not None and codec not in ctr.vcodecs:
|
93
87
|
try:
|
94
88
|
cobj = Codec(codec, "w")
|
95
|
-
except
|
89
|
+
except bv.codec.codec.UnknownCodecError:
|
96
90
|
log.error(f"Unknown encoder: {codec}")
|
97
91
|
# Normalize encoder names
|
98
92
|
if cobj.id not in (Codec(x, "w").id for x in ctr.vcodecs):
|
@@ -109,7 +103,7 @@ def set_audio_codec(
|
|
109
103
|
codec = "aac"
|
110
104
|
else:
|
111
105
|
codec = src.audios[0].codec
|
112
|
-
if
|
106
|
+
if bv.Codec(codec, "w").audio_formats is None:
|
113
107
|
codec = "aac"
|
114
108
|
if codec not in ctr.acodecs and ctr.default_aud != "none":
|
115
109
|
codec = ctr.default_aud
|
@@ -117,18 +111,10 @@ def set_audio_codec(
|
|
117
111
|
codec = "aac"
|
118
112
|
return codec
|
119
113
|
|
120
|
-
if codec == "copy":
|
121
|
-
log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
|
122
|
-
if src is None:
|
123
|
-
log.error("No input to copy its codec from.")
|
124
|
-
if not src.audios:
|
125
|
-
log.error("Input file does not have an audio stream to copy codec from.")
|
126
|
-
codec = src.audios[0].codec
|
127
|
-
|
128
114
|
if ctr.acodecs is None or codec not in ctr.acodecs:
|
129
115
|
try:
|
130
116
|
cobj = Codec(codec, "w")
|
131
|
-
except
|
117
|
+
except bv.codec.codec.UnknownCodecError:
|
132
118
|
log.error(f"Unknown encoder: {codec}")
|
133
119
|
# Normalize encoder names
|
134
120
|
if cobj.id not in (Codec(x, "w").id for x in ctr.acodecs):
|
@@ -176,6 +162,10 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
176
162
|
bar = initBar(args.progress)
|
177
163
|
tl = None
|
178
164
|
|
165
|
+
if args.keep_tracks_separate:
|
166
|
+
log.deprecated("--keep-tracks-separate is deprecated.")
|
167
|
+
args.keep_tracks_separate = False
|
168
|
+
|
179
169
|
if paths:
|
180
170
|
path_ext = splitext(paths[0])[1].lower()
|
181
171
|
if path_ext == ".xml":
|
@@ -206,7 +196,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
206
196
|
|
207
197
|
del paths
|
208
198
|
|
209
|
-
output, export_ops = set_output(args.
|
199
|
+
output, export_ops = set_output(args.output, args.export, src, log)
|
210
200
|
assert "export" in export_ops
|
211
201
|
export = export_ops["export"]
|
212
202
|
|
@@ -219,10 +209,6 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
219
209
|
if os.path.isdir(output):
|
220
210
|
log.error("Output path already has an existing directory!")
|
221
211
|
|
222
|
-
if os.path.isfile(output) and src is not None and src.path != output: # type: ignore
|
223
|
-
log.debug(f"Removing already existing file: {output}")
|
224
|
-
os.remove(output)
|
225
|
-
|
226
212
|
if args.sample_rate is None:
|
227
213
|
if tl is None:
|
228
214
|
samplerate = 48000 if src is None else src.get_sr()
|
@@ -291,52 +277,53 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
291
277
|
args.video_codec = set_video_codec(args.video_codec, src, out_ext, ctr, log)
|
292
278
|
args.audio_codec = set_audio_codec(args.audio_codec, src, out_ext, ctr, log)
|
293
279
|
|
294
|
-
if args.keep_tracks_separate and ctr.max_audios == 1:
|
295
|
-
log.warning(f"'{out_ext}' container doesn't support multiple audio tracks.")
|
296
|
-
|
297
280
|
def make_media(tl: v3, output_path: str) -> None:
|
298
281
|
assert src is not None
|
299
282
|
|
283
|
+
options = {}
|
284
|
+
mov_flags = []
|
300
285
|
if args.fragmented and not args.no_fragmented:
|
301
|
-
|
302
|
-
options =
|
303
|
-
|
304
|
-
"
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
286
|
+
mov_flags.extend(["default_base_moof", "frag_keyframe", "separate_moof"])
|
287
|
+
options["frag_duration"] = "0.2"
|
288
|
+
if args.faststart:
|
289
|
+
log.warning("Fragmented is enabled, will not apply faststart.")
|
290
|
+
elif not args.no_faststart:
|
291
|
+
mov_flags.append("faststart")
|
292
|
+
if mov_flags:
|
293
|
+
options["movflags"] = "+".join(mov_flags)
|
309
294
|
|
310
|
-
|
311
|
-
|
295
|
+
output = bv.open(output_path, "w", container_options=options)
|
296
|
+
|
297
|
+
# Setup video
|
298
|
+
if ctr.default_vid != "none" and tl.v:
|
299
|
+
vframes = render_av(output, tl, args, log)
|
300
|
+
output_stream: bv.VideoStream | None
|
301
|
+
output_stream = next(vframes) # type: ignore
|
312
302
|
else:
|
313
|
-
|
303
|
+
output_stream, vframes = None, iter([])
|
314
304
|
|
305
|
+
# Setup audio
|
315
306
|
if ctr.default_aud != "none":
|
316
307
|
ensure = Ensure(bar, samplerate, log)
|
317
308
|
audio_paths = make_new_audio(tl, ctr, ensure, args, bar, log)
|
318
309
|
else:
|
319
310
|
audio_paths = []
|
320
311
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
output_stream = next(vframes)
|
325
|
-
else:
|
326
|
-
output_stream, vframes = None, iter([])
|
312
|
+
if len(audio_paths) > 1 and ctr.max_audios == 1:
|
313
|
+
log.warning("Dropping extra audio streams (container only allows one)")
|
314
|
+
audio_paths = audio_paths[0:1]
|
327
315
|
|
328
|
-
# Setup audio
|
329
316
|
if audio_paths:
|
330
317
|
try:
|
331
|
-
audio_encoder =
|
332
|
-
except
|
318
|
+
audio_encoder = bv.Codec(args.audio_codec, "w")
|
319
|
+
except bv.FFmpegError as e:
|
333
320
|
log.error(e)
|
334
321
|
if audio_encoder.audio_formats is None:
|
335
322
|
log.error(f"{args.audio_codec}: No known audio formats avail.")
|
336
323
|
audio_format = audio_encoder.audio_formats[0]
|
337
324
|
resampler = AudioResampler(format=audio_format, layout="stereo", rate=tl.sr)
|
338
325
|
|
339
|
-
audio_streams: list[
|
326
|
+
audio_streams: list[bv.AudioStream] = []
|
340
327
|
audio_inputs = []
|
341
328
|
audio_gen_frames = []
|
342
329
|
for i, audio_path in enumerate(audio_paths):
|
@@ -346,7 +333,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
346
333
|
rate=tl.sr,
|
347
334
|
time_base=Fraction(1, tl.sr),
|
348
335
|
)
|
349
|
-
if not isinstance(audio_stream,
|
336
|
+
if not isinstance(audio_stream, bv.AudioStream):
|
350
337
|
log.error(f"Not a known audio codec: {args.audio_codec}")
|
351
338
|
|
352
339
|
if args.audio_bitrate != "auto":
|
@@ -358,17 +345,22 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
358
345
|
audio_stream.metadata["language"] = src.audios[i].lang # type: ignore
|
359
346
|
|
360
347
|
audio_streams.append(audio_stream)
|
361
|
-
audio_input =
|
348
|
+
audio_input = bv.open(audio_path)
|
362
349
|
audio_inputs.append(audio_input)
|
363
350
|
audio_gen_frames.append(audio_input.decode(audio=0))
|
364
351
|
|
365
352
|
# Setup subtitles
|
353
|
+
if ctr.default_sub != "none" and not args.sn:
|
354
|
+
sub_paths = make_new_subtitles(tl, log)
|
355
|
+
else:
|
356
|
+
sub_paths = []
|
357
|
+
|
366
358
|
subtitle_streams = []
|
367
359
|
subtitle_inputs = []
|
368
360
|
sub_gen_frames = []
|
369
361
|
|
370
362
|
for i, sub_path in enumerate(sub_paths):
|
371
|
-
subtitle_input =
|
363
|
+
subtitle_input = bv.open(sub_path)
|
372
364
|
subtitle_inputs.append(subtitle_input)
|
373
365
|
subtitle_stream = output.add_stream_from_template(
|
374
366
|
subtitle_input.streams.subtitles[0]
|
@@ -497,14 +489,14 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
497
489
|
output.mux(item.stream.encode(item.frame))
|
498
490
|
elif frame_type == "subtitle":
|
499
491
|
output.mux(item.frame)
|
500
|
-
except
|
492
|
+
except bv.error.ExternalError:
|
501
493
|
log.error(
|
502
494
|
f"Generic error for encoder: {item.stream.name}\n"
|
503
495
|
f"at {item.index} time_base\nPerhaps video quality settings are too low?"
|
504
496
|
)
|
505
|
-
except
|
497
|
+
except bv.FileNotFoundError:
|
506
498
|
log.error(f"File not found: {output_path}")
|
507
|
-
except
|
499
|
+
except bv.FFmpegError as e:
|
508
500
|
log.error(e)
|
509
501
|
|
510
502
|
if bar_index:
|
auto_editor/ffwrapper.py
CHANGED
@@ -4,14 +4,14 @@ from dataclasses import dataclass
|
|
4
4
|
from fractions import Fraction
|
5
5
|
from pathlib import Path
|
6
6
|
|
7
|
-
import
|
7
|
+
import bv
|
8
8
|
|
9
9
|
from auto_editor.utils.log import Log
|
10
10
|
|
11
11
|
|
12
12
|
def mux(input: Path, output: Path, stream: int) -> None:
|
13
|
-
input_container =
|
14
|
-
output_container =
|
13
|
+
input_container = bv.open(input, "r")
|
14
|
+
output_container = bv.open(output, "w")
|
15
15
|
|
16
16
|
input_audio_stream = input_container.streams.audio[stream]
|
17
17
|
output_audio_stream = output_container.add_stream("pcm_s16le")
|
@@ -92,12 +92,12 @@ class FileInfo:
|
|
92
92
|
|
93
93
|
def initFileInfo(path: str, log: Log) -> FileInfo:
|
94
94
|
try:
|
95
|
-
cont =
|
96
|
-
except
|
95
|
+
cont = bv.open(path, "r")
|
96
|
+
except bv.error.FileNotFoundError:
|
97
97
|
log.error(f"Input file doesn't exist: {path}")
|
98
|
-
except
|
98
|
+
except bv.error.IsADirectoryError:
|
99
99
|
log.error(f"Expected a media file, but got a directory: {path}")
|
100
|
-
except
|
100
|
+
except bv.error.InvalidDataError:
|
101
101
|
log.error(f"Invalid data when processing: {path}")
|
102
102
|
|
103
103
|
videos: tuple[VideoStream, ...] = ()
|
@@ -126,7 +126,7 @@ def initFileInfo(path: str, log: Log) -> FileInfo:
|
|
126
126
|
VideoStream(
|
127
127
|
v.width,
|
128
128
|
v.height,
|
129
|
-
v.
|
129
|
+
v.codec.canonical_name,
|
130
130
|
fps,
|
131
131
|
vdur,
|
132
132
|
sar,
|
@@ -167,7 +167,7 @@ def initFileInfo(path: str, log: Log) -> FileInfo:
|
|
167
167
|
|
168
168
|
desc = cont.metadata.get("description", None)
|
169
169
|
bitrate = 0 if cont.bit_rate is None else cont.bit_rate
|
170
|
-
dur = 0 if cont.duration is None else cont.duration /
|
170
|
+
dur = 0 if cont.duration is None else cont.duration / bv.time_base
|
171
171
|
|
172
172
|
cont.close()
|
173
173
|
|
auto_editor/formats/json.py
CHANGED
@@ -7,7 +7,7 @@ from fractions import Fraction
|
|
7
7
|
from typing import Any
|
8
8
|
|
9
9
|
from auto_editor.ffwrapper import FileInfo, initFileInfo
|
10
|
-
from auto_editor.
|
10
|
+
from auto_editor.json import dump, load
|
11
11
|
from auto_editor.lib.err import MyError
|
12
12
|
from auto_editor.timeline import (
|
13
13
|
ASpace,
|
@@ -221,7 +221,7 @@ def read_v1(tl: Any, log: Log) -> v3:
|
|
221
221
|
def read_json(path: str, log: Log) -> v3:
|
222
222
|
with open(path, encoding="utf-8", errors="ignore") as f:
|
223
223
|
try:
|
224
|
-
tl =
|
224
|
+
tl = load(path, f)
|
225
225
|
except MyError as e:
|
226
226
|
log.error(e)
|
227
227
|
|