auto-editor 26.1.1__py3-none-any.whl → 26.3.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 +10 -15
- auto_editor/analyze.py +1 -1
- auto_editor/{subcommands → cmds}/cache.py +2 -2
- auto_editor/{subcommands → cmds}/info.py +4 -4
- auto_editor/{subcommands → cmds}/levels.py +42 -11
- auto_editor/{subcommands → cmds}/test.py +13 -3
- auto_editor/edit.py +46 -13
- auto_editor/ffwrapper.py +2 -2
- auto_editor/formats/fcp11.py +4 -4
- auto_editor/formats/fcp7.py +6 -6
- auto_editor/formats/json.py +1 -1
- auto_editor/lang/json.py +2 -2
- auto_editor/lang/palet.py +5 -5
- auto_editor/lang/stdenv.py +2 -2
- auto_editor/lib/data_structs.py +0 -2
- auto_editor/render/audio.py +4 -4
- auto_editor/render/subtitle.py +2 -2
- auto_editor/render/video.py +26 -42
- auto_editor/utils/bar.py +2 -2
- auto_editor/utils/container.py +4 -10
- auto_editor/utils/func.py +1 -1
- auto_editor/utils/log.py +4 -0
- auto_editor/utils/types.py +4 -3
- auto_editor/vanparse.py +1 -1
- auto_editor/wavfile.py +3 -3
- {auto_editor-26.1.1.dist-info → auto_editor-26.3.0.dist-info}/METADATA +3 -3
- auto_editor-26.3.0.dist-info/RECORD +56 -0
- {auto_editor-26.1.1.dist-info → auto_editor-26.3.0.dist-info}/WHEEL +1 -1
- auto_editor-26.3.0.dist-info/entry_points.txt +6 -0
- auto_editor-26.1.1.dist-info/RECORD +0 -56
- auto_editor-26.1.1.dist-info/entry_points.txt +0 -6
- /auto_editor/{subcommands → cmds}/__init__.py +0 -0
- /auto_editor/{subcommands → cmds}/desc.py +0 -0
- /auto_editor/{subcommands → cmds}/palet.py +0 -0
- /auto_editor/{subcommands → cmds}/repl.py +0 -0
- /auto_editor/{subcommands → cmds}/subdump.py +0 -0
- {auto_editor-26.1.1.dist-info → auto_editor-26.3.0.dist-info}/LICENSE +0 -0
- {auto_editor-26.1.1.dist-info → auto_editor-26.3.0.dist-info}/top_level.txt +0 -0
auto_editor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "26.
|
1
|
+
__version__ = "26.3.0"
|
auto_editor/__main__.py
CHANGED
@@ -188,6 +188,12 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
188
188
|
metavar="BITRATE",
|
189
189
|
help="Set the number of bits per second for video",
|
190
190
|
)
|
191
|
+
parser.add_argument(
|
192
|
+
"-vprofile",
|
193
|
+
"-profile:v",
|
194
|
+
metavar="PROFILE",
|
195
|
+
help="Set the video profile. For h264: high, main, or baseline",
|
196
|
+
)
|
191
197
|
parser.add_argument(
|
192
198
|
"--scale",
|
193
199
|
type=number,
|
@@ -286,21 +292,10 @@ def download_video(my_input: str, args: Args, log: Log) -> str:
|
|
286
292
|
|
287
293
|
|
288
294
|
def main() -> None:
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
"
|
293
|
-
"subdump",
|
294
|
-
"desc",
|
295
|
-
"repl",
|
296
|
-
"palet",
|
297
|
-
"cache",
|
298
|
-
)
|
299
|
-
|
300
|
-
if len(sys.argv) > 1 and sys.argv[1] in subcommands:
|
301
|
-
obj = __import__(
|
302
|
-
f"auto_editor.subcommands.{sys.argv[1]}", fromlist=["subcommands"]
|
303
|
-
)
|
295
|
+
commands = ("test", "info", "levels", "subdump", "desc", "repl", "palet", "cache")
|
296
|
+
|
297
|
+
if len(sys.argv) > 1 and sys.argv[1] in commands:
|
298
|
+
obj = __import__(f"auto_editor.cmds.{sys.argv[1]}", fromlist=["cmds"])
|
304
299
|
obj.main(sys.argv[2:])
|
305
300
|
return
|
306
301
|
|
auto_editor/analyze.py
CHANGED
@@ -155,7 +155,7 @@ def iter_motion(
|
|
155
155
|
|
156
156
|
def obj_tag(path: Path, kind: str, tb: Fraction, obj: Sequence[object]) -> str:
|
157
157
|
mod_time = int(path.stat().st_mtime)
|
158
|
-
key = f"{path}:{mod_time:x}:{tb}:" + ",".join(f"{v}" for v in obj)
|
158
|
+
key = f"{path.name}:{mod_time:x}:{tb}:" + ",".join(f"{v}" for v in obj)
|
159
159
|
part1 = sha1(key.encode()).hexdigest()[:16]
|
160
160
|
|
161
161
|
return f"{part1}{kind}"
|
@@ -12,7 +12,7 @@ from auto_editor import __version__
|
|
12
12
|
def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
13
13
|
cache_dir = os.path.join(gettempdir(), f"ae-{__version__}")
|
14
14
|
|
15
|
-
if sys_args and sys_args[0] in
|
15
|
+
if sys_args and sys_args[0] in {"clean", "clear"}:
|
16
16
|
rmtree(cache_dir, ignore_errors=True)
|
17
17
|
return
|
18
18
|
|
@@ -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
|
@@ -98,7 +98,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
98
98
|
|
99
99
|
continue
|
100
100
|
|
101
|
-
if ext in
|
101
|
+
if ext in {".xml", ".fcpxml", ".mlt"}:
|
102
102
|
file_info[file] = {"type": "timeline"}
|
103
103
|
continue
|
104
104
|
|
@@ -169,7 +169,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
169
169
|
return
|
170
170
|
|
171
171
|
def is_null(key: str, val: object) -> bool:
|
172
|
-
return val is None or (key in
|
172
|
+
return val is None or (key in {"bitrate", "duration"} and val == 0.0)
|
173
173
|
|
174
174
|
def stream_to_text(text: str, label: str, streams: list[dict[str, Any]]) -> str:
|
175
175
|
if len(streams) > 0:
|
@@ -183,12 +183,12 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
183
183
|
sep = "x" if key == "resolution" else ":"
|
184
184
|
value = sep.join(f"{x}" for x in value)
|
185
185
|
|
186
|
-
if key in
|
186
|
+
if key in {
|
187
187
|
"color_range",
|
188
188
|
"color_space",
|
189
189
|
"color_transfer",
|
190
190
|
"color_primaries",
|
191
|
-
|
191
|
+
}:
|
192
192
|
if key == "color_range":
|
193
193
|
if value == 1:
|
194
194
|
text += " - color range: 1 (tv)\n"
|
@@ -59,7 +59,7 @@ def levels_options(parser: ArgumentParser) -> ArgumentParser:
|
|
59
59
|
def print_arr(arr: NDArray) -> None:
|
60
60
|
print("")
|
61
61
|
print("@start")
|
62
|
-
if arr.dtype
|
62
|
+
if arr.dtype in {np.float64, np.float32, np.float16}:
|
63
63
|
for a in arr:
|
64
64
|
sys.stdout.write(f"{a:.20f}\n")
|
65
65
|
elif arr.dtype == np.bool_:
|
@@ -131,18 +131,49 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
131
131
|
levels = Levels(src, tb, bar, False, log, strict=True)
|
132
132
|
try:
|
133
133
|
if method == "audio":
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
134
|
+
if (arr := levels.read_cache("audio", (obj["stream"],))) is not None:
|
135
|
+
print_arr(arr)
|
136
|
+
else:
|
137
|
+
container = av.open(src.path, "r")
|
138
|
+
audio_stream = container.streams.audio[obj["stream"]]
|
139
|
+
log.experimental(audio_stream.codec)
|
140
|
+
|
141
|
+
values = []
|
142
|
+
|
143
|
+
def value_storing_generator() -> Iterator[np.float32]:
|
144
|
+
for value in iter_audio(audio_stream, tb):
|
145
|
+
values.append(value)
|
146
|
+
yield value
|
147
|
+
|
148
|
+
print_arr_gen(value_storing_generator())
|
149
|
+
container.close()
|
150
|
+
|
151
|
+
cache_array = np.array(values, dtype=np.float32)
|
152
|
+
levels.cache(cache_array, "audio", (obj["stream"],))
|
139
153
|
|
140
154
|
elif method == "motion":
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
155
|
+
mobj = (obj["stream"], obj["width"], obj["blur"])
|
156
|
+
if (arr := levels.read_cache("motion", mobj)) is not None:
|
157
|
+
print_arr(arr)
|
158
|
+
else:
|
159
|
+
container = av.open(src.path, "r")
|
160
|
+
video_stream = container.streams.video[obj["stream"]]
|
161
|
+
log.experimental(video_stream.codec)
|
162
|
+
|
163
|
+
values = []
|
164
|
+
|
165
|
+
def value_storing_generator() -> Iterator[np.float32]:
|
166
|
+
for value in iter_motion(
|
167
|
+
video_stream, tb, obj["blur"], obj["width"]
|
168
|
+
):
|
169
|
+
values.append(value)
|
170
|
+
yield value
|
171
|
+
|
172
|
+
print_arr_gen(value_storing_generator())
|
173
|
+
container.close()
|
174
|
+
|
175
|
+
cache_array = np.array(values, dtype=np.float32)
|
176
|
+
levels.cache(cache_array, "motion", mobj)
|
146
177
|
|
147
178
|
elif method == "subtitle":
|
148
179
|
print_arr(levels.subtitle(**obj))
|
@@ -549,6 +549,15 @@ def main(sys_args: list[str] | None = None):
|
|
549
549
|
def yuv442p():
|
550
550
|
return run.main(["resources/test_yuv422p.mp4"], [])
|
551
551
|
|
552
|
+
def prores():
|
553
|
+
run.main(["resources/testsrc.mp4", "-c:v", "prores", "-o", "out.mkv"], [])
|
554
|
+
assert fileinfo("out.mkv").videos[0].pix_fmt == "yuv422p10le"
|
555
|
+
|
556
|
+
run.main(["out.mkv", "-c:v", "prores", "-o", "out2.mkv"], [])
|
557
|
+
assert fileinfo("out2.mkv").videos[0].pix_fmt == "yuv422p10le"
|
558
|
+
|
559
|
+
return "out.mkv", "out2.mkv"
|
560
|
+
|
552
561
|
# Issue 280
|
553
562
|
def SAR():
|
554
563
|
out = run.main(["resources/SAR-2by3.mp4"], [])
|
@@ -723,18 +732,19 @@ def main(sys_args: list[str] | None = None):
|
|
723
732
|
|
724
733
|
tests = []
|
725
734
|
|
726
|
-
if args.category in
|
735
|
+
if args.category in {"palet", "all"}:
|
727
736
|
tests.extend([palet_python_bridge, palet_scripts])
|
728
737
|
|
729
|
-
if args.category in
|
738
|
+
if args.category in {"sub", "all"}:
|
730
739
|
tests.extend([info, levels, subdump, desc])
|
731
740
|
|
732
|
-
if args.category in
|
741
|
+
if args.category in {"cli", "all"}:
|
733
742
|
tests.extend(
|
734
743
|
[
|
735
744
|
premiere,
|
736
745
|
SAR,
|
737
746
|
yuv442p,
|
747
|
+
prores,
|
738
748
|
edit_negative_tests,
|
739
749
|
edit_positive_tests,
|
740
750
|
audio_norm_f,
|
auto_editor/edit.py
CHANGED
@@ -8,7 +8,7 @@ from subprocess import run
|
|
8
8
|
from typing import Any
|
9
9
|
|
10
10
|
import av
|
11
|
-
from av import AudioResampler
|
11
|
+
from av import AudioResampler, Codec
|
12
12
|
|
13
13
|
from auto_editor.ffwrapper import FileInfo, initFileInfo
|
14
14
|
from auto_editor.lib.contracts import is_int, is_str
|
@@ -81,6 +81,7 @@ def set_video_codec(
|
|
81
81
|
return codec
|
82
82
|
|
83
83
|
if codec == "copy":
|
84
|
+
log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
|
84
85
|
if src is None:
|
85
86
|
log.error("No input to copy its codec from.")
|
86
87
|
if not src.videos:
|
@@ -88,7 +89,13 @@ def set_video_codec(
|
|
88
89
|
codec = src.videos[0].codec
|
89
90
|
|
90
91
|
if ctr.vcodecs is not None and codec not in ctr.vcodecs:
|
91
|
-
|
92
|
+
try:
|
93
|
+
cobj = Codec(codec, "w")
|
94
|
+
except av.codec.codec.UnknownCodecError:
|
95
|
+
log.error(f"Unknown encoder: {codec}")
|
96
|
+
# Normalize encoder names
|
97
|
+
if cobj.id not in (Codec(x, "w").id for x in ctr.vcodecs):
|
98
|
+
log.error(codec_error.format(codec, out_ext))
|
92
99
|
|
93
100
|
return codec
|
94
101
|
|
@@ -101,18 +108,16 @@ def set_audio_codec(
|
|
101
108
|
codec = "aac"
|
102
109
|
else:
|
103
110
|
codec = src.audios[0].codec
|
104
|
-
|
105
|
-
if ctx.audio_formats is None:
|
111
|
+
if av.Codec(codec, "w").audio_formats is None:
|
106
112
|
codec = "aac"
|
107
113
|
if codec not in ctr.acodecs and ctr.default_aud != "none":
|
108
114
|
codec = ctr.default_aud
|
109
|
-
if codec == "mp3float":
|
110
|
-
codec = "mp3"
|
111
115
|
if codec is None:
|
112
116
|
codec = "aac"
|
113
117
|
return codec
|
114
118
|
|
115
119
|
if codec == "copy":
|
120
|
+
log.deprecated("The `copy` codec is deprecated. auto-editor always re-encodes")
|
116
121
|
if src is None:
|
117
122
|
log.error("No input to copy its codec from.")
|
118
123
|
if not src.audios:
|
@@ -120,7 +125,13 @@ def set_audio_codec(
|
|
120
125
|
codec = src.audios[0].codec
|
121
126
|
|
122
127
|
if ctr.acodecs is None or codec not in ctr.acodecs:
|
123
|
-
|
128
|
+
try:
|
129
|
+
cobj = Codec(codec, "w")
|
130
|
+
except av.codec.codec.UnknownCodecError:
|
131
|
+
log.error(f"Unknown encoder: {codec}")
|
132
|
+
# Normalize encoder names
|
133
|
+
if cobj.id not in (Codec(x, "w").id for x in ctr.acodecs):
|
134
|
+
log.error(codec_error.format(codec, out_ext))
|
124
135
|
|
125
136
|
return codec
|
126
137
|
|
@@ -240,7 +251,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
240
251
|
make_json_timeline(export_ops["api"], output, tl, log)
|
241
252
|
return
|
242
253
|
|
243
|
-
if export in
|
254
|
+
if export in {"premiere", "resolve-fcp7"}:
|
244
255
|
from auto_editor.formats.fcp7 import fcp7_write_xml
|
245
256
|
|
246
257
|
is_resolve = export.startswith("resolve")
|
@@ -301,7 +312,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
301
312
|
# Setup audio
|
302
313
|
if audio_paths:
|
303
314
|
try:
|
304
|
-
audio_encoder = av.Codec(args.audio_codec)
|
315
|
+
audio_encoder = av.Codec(args.audio_codec, "w")
|
305
316
|
except av.FFmpegError as e:
|
306
317
|
log.error(e)
|
307
318
|
if audio_encoder.audio_formats is None:
|
@@ -354,17 +365,35 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
354
365
|
|
355
366
|
# Setup video
|
356
367
|
if ctr.default_vid != "none" and tl.v:
|
357
|
-
vframes = render_av(output, tl, args,
|
368
|
+
vframes = render_av(output, tl, args, log)
|
358
369
|
output_stream = next(vframes)
|
359
370
|
else:
|
360
371
|
output_stream, vframes = None, iter([])
|
361
372
|
|
373
|
+
no_color = log.no_color or log.machine
|
374
|
+
encoder_titles = []
|
375
|
+
if output_stream is not None:
|
376
|
+
name = output_stream.codec.canonical_name
|
377
|
+
encoder_titles.append(name if no_color else f"\033[95m{name}")
|
378
|
+
if audio_streams:
|
379
|
+
name = audio_streams[0].name
|
380
|
+
encoder_titles.append(name if no_color else f"\033[96m{name}")
|
381
|
+
if subtitle_streams:
|
382
|
+
name = subtitle_streams[0].name
|
383
|
+
encoder_titles.append(name if no_color else f"\033[32m{name}")
|
384
|
+
|
385
|
+
title = f"({os.path.splitext(output_path)[1][1:]}) "
|
386
|
+
if no_color:
|
387
|
+
title += "+".join(encoder_titles)
|
388
|
+
else:
|
389
|
+
title += "\033[0m+".join(encoder_titles) + "\033[0m"
|
390
|
+
bar.start(tl.end, title)
|
391
|
+
|
362
392
|
# Process frames
|
363
393
|
while True:
|
364
394
|
audio_frames = [next(frames, None) for frames in audio_gen_frames]
|
365
|
-
video_frame = next(vframes, None)
|
395
|
+
index, video_frame = next(vframes, (0, None))
|
366
396
|
subtitle_frames = [next(packet, None) for packet in sub_gen_frames]
|
367
|
-
|
368
397
|
if (
|
369
398
|
all(frame is None for frame in audio_frames)
|
370
399
|
and video_frame is None
|
@@ -394,12 +423,16 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
394
423
|
except av.FFmpegError as e:
|
395
424
|
log.error(e)
|
396
425
|
|
426
|
+
bar.tick(index)
|
427
|
+
|
397
428
|
# Flush streams
|
398
429
|
if output_stream is not None:
|
399
430
|
output.mux(output_stream.encode(None))
|
400
431
|
for audio_stream in audio_streams:
|
401
432
|
output.mux(audio_stream.encode(None))
|
402
433
|
|
434
|
+
bar.end()
|
435
|
+
|
403
436
|
# Close resources
|
404
437
|
for audio_input in audio_inputs:
|
405
438
|
audio_input.close()
|
@@ -449,7 +482,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
449
482
|
|
450
483
|
log.stop_timer()
|
451
484
|
|
452
|
-
if not args.no_open and export in
|
485
|
+
if not args.no_open and export in {"default", "audio"}:
|
453
486
|
if args.player is None:
|
454
487
|
if sys.platform == "win32":
|
455
488
|
try:
|
auto_editor/ffwrapper.py
CHANGED
@@ -111,7 +111,7 @@ def initFileInfo(path: str, log: Log) -> FileInfo:
|
|
111
111
|
vdur = 0.0
|
112
112
|
|
113
113
|
fps = v.average_rate
|
114
|
-
if (fps is None or fps < 1) and v.name in
|
114
|
+
if (fps is None or fps < 1) and v.name in {"png", "mjpeg", "webp"}:
|
115
115
|
fps = Fraction(25)
|
116
116
|
if fps is None or fps == 0:
|
117
117
|
fps = Fraction(30)
|
@@ -149,7 +149,7 @@ def initFileInfo(path: str, log: Log) -> FileInfo:
|
|
149
149
|
a_cc = a.codec_context
|
150
150
|
audios += (
|
151
151
|
AudioStream(
|
152
|
-
a_cc.
|
152
|
+
a_cc.codec.canonical_name,
|
153
153
|
0 if a_cc.sample_rate is None else a_cc.sample_rate,
|
154
154
|
a.layout.name,
|
155
155
|
a_cc.channels,
|
auto_editor/formats/fcp11.py
CHANGED
@@ -36,7 +36,7 @@ def get_colorspace(src: FileInfo) -> str:
|
|
36
36
|
return "6-1-6 (Rec. 601 NTSC)"
|
37
37
|
if s.color_primaries == 9: # "bt2020"
|
38
38
|
# See: https://video.stackexchange.com/questions/22059/how-to-identify-hdr-video
|
39
|
-
if s.color_transfer in
|
39
|
+
if s.color_transfer in {16, 18}: # "smpte2084" "arib-std-b67"
|
40
40
|
return "9-18-9 (Rec. 2020 HLG)"
|
41
41
|
return "9-1-9 (Rec. 2020)"
|
42
42
|
|
@@ -80,7 +80,7 @@ def fcp11_write_xml(
|
|
80
80
|
SubElement(
|
81
81
|
resources,
|
82
82
|
"format",
|
83
|
-
id=f"r{i*2+1}",
|
83
|
+
id=f"r{i * 2 + 1}",
|
84
84
|
name=make_name(one_src, tl.tb),
|
85
85
|
frameDuration=fraction(1),
|
86
86
|
width=f"{tl.res[0]}",
|
@@ -90,11 +90,11 @@ def fcp11_write_xml(
|
|
90
90
|
r2 = SubElement(
|
91
91
|
resources,
|
92
92
|
"asset",
|
93
|
-
id=f"r{i*2+2}",
|
93
|
+
id=f"r{i * 2 + 2}",
|
94
94
|
name=one_src.path.stem,
|
95
95
|
start="0s",
|
96
96
|
hasVideo="1" if one_src.videos else "0",
|
97
|
-
format=f"r{i*2+1}",
|
97
|
+
format=f"r{i * 2 + 1}",
|
98
98
|
hasAudio="1" if one_src.audios else "0",
|
99
99
|
audioSources="1",
|
100
100
|
audioChannels=f"{2 if not one_src.audios else one_src.audios[0].channels}",
|
auto_editor/formats/fcp7.py
CHANGED
@@ -99,7 +99,7 @@ def set_tb_ntsc(tb: Fraction) -> tuple[int, str]:
|
|
99
99
|
return 60, "TRUE"
|
100
100
|
|
101
101
|
ctb = ceil(tb)
|
102
|
-
if ctb not in
|
102
|
+
if ctb not in {24, 30, 60} and ctb * Fraction(999, 1000) == tb:
|
103
103
|
return ctb, "TRUE"
|
104
104
|
|
105
105
|
return int(tb), "FALSE"
|
@@ -151,7 +151,7 @@ SUPPORTED_EFFECTS = ("timeremap",)
|
|
151
151
|
|
152
152
|
def read_filters(clipitem: Element, log: Log) -> float:
|
153
153
|
for effect_tag in clipitem:
|
154
|
-
if effect_tag.tag in
|
154
|
+
if effect_tag.tag in {"enabled", "start", "end"}:
|
155
155
|
continue
|
156
156
|
if len(effect_tag) < 3:
|
157
157
|
log.error("<effect> requires: <effectid> <name> and one <parameter>")
|
@@ -495,7 +495,7 @@ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
|
|
495
495
|
file_defs: set[str] = set() # Contains urls
|
496
496
|
|
497
497
|
for src in set(tl.sources):
|
498
|
-
the_id = f"file-{len(src_to_id)+1}"
|
498
|
+
the_id = f"file-{len(src_to_id) + 1}"
|
499
499
|
src_to_url[src] = f"{src.path.resolve()}"
|
500
500
|
src_to_id[src] = the_id
|
501
501
|
|
@@ -541,7 +541,7 @@ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
|
|
541
541
|
_in = f"{clip.offset}"
|
542
542
|
_out = f"{clip.offset + clip.dur}"
|
543
543
|
|
544
|
-
this_clipid = f"clipitem-{j+1}"
|
544
|
+
this_clipid = f"clipitem-{j + 1}"
|
545
545
|
clipitem = ET.SubElement(track, "clipitem", id=this_clipid)
|
546
546
|
ET.SubElement(clipitem, "name").text = src.path.stem
|
547
547
|
ET.SubElement(clipitem, "enabled").text = "TRUE"
|
@@ -562,14 +562,14 @@ def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
|
|
562
562
|
link = ET.SubElement(clipitem, "link")
|
563
563
|
ET.SubElement(
|
564
564
|
link, "linkclipref"
|
565
|
-
).text = f"clipitem-{(len(tl.v[0]))+j+1}"
|
565
|
+
).text = f"clipitem-{(len(tl.v[0])) + j + 1}"
|
566
566
|
continue
|
567
567
|
|
568
568
|
for i in range(1 + len(src.audios) * 2): # `2` because stereo.
|
569
569
|
link = ET.SubElement(clipitem, "link")
|
570
570
|
ET.SubElement(
|
571
571
|
link, "linkclipref"
|
572
|
-
).text = f"clipitem-{(i*(len(tl.v[0])))+j+1}"
|
572
|
+
).text = f"clipitem-{(i * (len(tl.v[0]))) + j + 1}"
|
573
573
|
ET.SubElement(link, "mediatype").text = "video" if i == 0 else "audio"
|
574
574
|
ET.SubElement(link, "trackindex").text = f"{max(i, 1)}"
|
575
575
|
ET.SubElement(link, "clipindex").text = f"{j + 1}"
|
auto_editor/formats/json.py
CHANGED
@@ -239,7 +239,7 @@ def read_json(path: str, log: Log) -> v3:
|
|
239
239
|
|
240
240
|
|
241
241
|
def make_json_timeline(ver: int, out: str | int, tl: v3, log: Log) -> None:
|
242
|
-
if ver not in
|
242
|
+
if ver not in {3, 1}:
|
243
243
|
log.error(f"Version {ver} is not supported!")
|
244
244
|
|
245
245
|
if isinstance(out, str):
|
auto_editor/lang/json.py
CHANGED
@@ -204,7 +204,7 @@ class Parser:
|
|
204
204
|
def expr(self) -> Any:
|
205
205
|
self.current_token
|
206
206
|
|
207
|
-
if self.current_token.type in
|
207
|
+
if self.current_token.type in {STR, VAL}:
|
208
208
|
val = self.current_token.value
|
209
209
|
self.eat()
|
210
210
|
return val
|
@@ -215,7 +215,7 @@ class Parser:
|
|
215
215
|
my_dic = {}
|
216
216
|
while self.current_token.type != RCUR:
|
217
217
|
if self.current_token.type != STR:
|
218
|
-
if self.current_token.type in
|
218
|
+
if self.current_token.type in {LBRAC, VAL}:
|
219
219
|
self.lexer.error("JSON Objects only allow strings as keys")
|
220
220
|
self.lexer.error("Expected closing `}`")
|
221
221
|
key = self.current_token.value
|
auto_editor/lang/palet.py
CHANGED
@@ -218,10 +218,10 @@ class Lexer:
|
|
218
218
|
self.advance()
|
219
219
|
|
220
220
|
result = buf.getvalue()
|
221
|
-
if result in
|
221
|
+
if result in {"t", "T", "true"}:
|
222
222
|
return Token(VAL, True, self.lineno, self.column)
|
223
223
|
|
224
|
-
if result in
|
224
|
+
if result in {"f", "F", "false"}:
|
225
225
|
return Token(VAL, False, self.lineno, self.column)
|
226
226
|
|
227
227
|
self.error(f"Unknown hash literal `#{result}`")
|
@@ -451,7 +451,7 @@ class Parser:
|
|
451
451
|
|
452
452
|
self.eat()
|
453
453
|
childs = []
|
454
|
-
while self.current_token.type not in
|
454
|
+
while self.current_token.type not in {RPAREN, RBRAC, RCUR, EOF}:
|
455
455
|
childs.append(self.expr())
|
456
456
|
return tuple(childs)
|
457
457
|
|
@@ -512,7 +512,7 @@ def p_slice(
|
|
512
512
|
|
513
513
|
is_iterable = Contract(
|
514
514
|
"iterable?",
|
515
|
-
lambda v: type(v) in
|
515
|
+
lambda v: type(v) in {str, range, list, tuple, dict, Quoted}
|
516
516
|
or isinstance(v, np.ndarray),
|
517
517
|
)
|
518
518
|
is_boolarr = Contract(
|
@@ -689,7 +689,7 @@ def my_eval(env: Env, node: object) -> Any:
|
|
689
689
|
length = len(node[1:])
|
690
690
|
if length > 3:
|
691
691
|
raise MyError(f"{print_str(node[0])}: slice expects 1 argument")
|
692
|
-
if length in
|
692
|
+
if length in {2, 3}:
|
693
693
|
return p_slice(oper, *(my_eval(env, c) for c in node[1:]))
|
694
694
|
if length == 1:
|
695
695
|
return ref(oper, my_eval(env, node[1]))
|
auto_editor/lang/stdenv.py
CHANGED
@@ -283,7 +283,7 @@ def make_standard_env() -> dict[str, Any]:
|
|
283
283
|
type(node[2]) is tuple
|
284
284
|
and node[2]
|
285
285
|
and type(node[2][0]) is Sym
|
286
|
-
and node[2][0].val in
|
286
|
+
and node[2][0].val in {"lambda", "λ"}
|
287
287
|
):
|
288
288
|
terms = node[2][1]
|
289
289
|
body = node[2][2:]
|
@@ -1045,7 +1045,7 @@ def make_standard_env() -> dict[str, Any]:
|
|
1045
1045
|
# reals
|
1046
1046
|
"pow": Proc("pow", pow, (2, 2), is_real),
|
1047
1047
|
"abs": Proc("abs", abs, (1, 1), is_real),
|
1048
|
-
"round": Proc("round", round, (1,
|
1048
|
+
"round": Proc("round", round, (1, 2), is_real, is_int),
|
1049
1049
|
"max": Proc("max", lambda *v: max(v), (1, None), is_real),
|
1050
1050
|
"min": Proc("min", lambda *v: min(v), (1, None), is_real),
|
1051
1051
|
"max-seq": Proc("max-seq", max, (1, 1), is_sequence),
|
auto_editor/lib/data_structs.py
CHANGED
@@ -191,7 +191,6 @@ def display_str(val: object) -> str:
|
|
191
191
|
return f"{float(val)}"
|
192
192
|
if type(val) is Fraction:
|
193
193
|
return f"{val.numerator}/{val.denominator}"
|
194
|
-
|
195
194
|
if type(val) is Quoted or type(val) is tuple:
|
196
195
|
if not val:
|
197
196
|
return "()"
|
@@ -201,7 +200,6 @@ def display_str(val: object) -> str:
|
|
201
200
|
result.write(f" {display_str(item)}")
|
202
201
|
result.write(")")
|
203
202
|
return result.getvalue()
|
204
|
-
|
205
203
|
if type(val) is list:
|
206
204
|
if not val:
|
207
205
|
return "#()"
|
auto_editor/render/audio.py
CHANGED
@@ -62,7 +62,7 @@ def parse_ebu_bytes(norm: dict, stat: bytes, log: Log) -> tuple[str, str]:
|
|
62
62
|
except MyError:
|
63
63
|
log.error(f"Invalid loudnorm stats.\n{stat!r}")
|
64
64
|
|
65
|
-
for key in
|
65
|
+
for key in {"input_i", "input_tp", "input_lra", "input_thresh", "target_offset"}:
|
66
66
|
val = float(parsed[key])
|
67
67
|
if val == float("-inf"):
|
68
68
|
parsed[key] = -99
|
@@ -91,7 +91,7 @@ def apply_audio_normalization(
|
|
91
91
|
) -> None:
|
92
92
|
if norm["tag"] == "ebu":
|
93
93
|
first_pass = (
|
94
|
-
f"i={norm['i']}:lra={norm['lra']}:tp={norm['tp']}:
|
94
|
+
f"i={norm['i']}:lra={norm['lra']}:tp={norm['tp']}:offset={norm['gain']}"
|
95
95
|
)
|
96
96
|
log.debug(f"audio norm first pass: {first_pass}")
|
97
97
|
with av.open(f"{pre_master}") as container:
|
@@ -167,10 +167,10 @@ def process_audio_clip(
|
|
167
167
|
if clip.speed != 1:
|
168
168
|
if clip.speed > 10_000:
|
169
169
|
for _ in range(3):
|
170
|
-
args.append(graph.add("atempo", f"{clip.speed ** (1/3)}"))
|
170
|
+
args.append(graph.add("atempo", f"{clip.speed ** (1 / 3)}"))
|
171
171
|
elif clip.speed > 100:
|
172
172
|
for _ in range(2):
|
173
|
-
args.append(graph.add("atempo", f"{clip.speed
|
173
|
+
args.append(graph.add("atempo", f"{clip.speed**0.5}"))
|
174
174
|
elif clip.speed >= 0.5:
|
175
175
|
args.append(graph.add("atempo", f"{clip.speed}"))
|
176
176
|
else:
|
auto_editor/render/subtitle.py
CHANGED
@@ -53,7 +53,7 @@ class SubtitleParser:
|
|
53
53
|
self.codec = codec
|
54
54
|
self.contents = []
|
55
55
|
|
56
|
-
if codec
|
56
|
+
if codec in {"ass", "ssa"}:
|
57
57
|
time_code = re.compile(r"(.*)(\d+:\d+:[\d.]+)(.*)(\d+:\d+:[\d.]+)(.*)")
|
58
58
|
elif codec == "webvtt":
|
59
59
|
time_code = re.compile(r"()(\d+:[\d.]+)( --> )(\d+:[\d.]+)(\n.*)")
|
@@ -189,7 +189,7 @@ def make_new_subtitles(tl: v3, log: Log) -> list[str]:
|
|
189
189
|
parser = SubtitleParser(tl.tb)
|
190
190
|
if sub.codec == "ssa":
|
191
191
|
format = "ass"
|
192
|
-
elif sub.codec in
|
192
|
+
elif sub.codec in {"webvtt", "ass"}:
|
193
193
|
format = sub.codec
|
194
194
|
else:
|
195
195
|
log.error(f"Unknown subtitle codec: {sub.codec}")
|
auto_editor/render/video.py
CHANGED
@@ -15,7 +15,6 @@ if TYPE_CHECKING:
|
|
15
15
|
|
16
16
|
from auto_editor.ffwrapper import FileInfo
|
17
17
|
from auto_editor.timeline import v3
|
18
|
-
from auto_editor.utils.bar import Bar
|
19
18
|
from auto_editor.utils.log import Log
|
20
19
|
from auto_editor.utils.types import Args
|
21
20
|
|
@@ -26,30 +25,7 @@ class VideoFrame:
|
|
26
25
|
src: FileInfo
|
27
26
|
|
28
27
|
|
29
|
-
|
30
|
-
allowed_pix_fmt = {
|
31
|
-
"yuv420p",
|
32
|
-
"yuvj420p",
|
33
|
-
"yuv444p",
|
34
|
-
"yuvj444p",
|
35
|
-
"rgb48be",
|
36
|
-
"rgb48le",
|
37
|
-
"rgb64be",
|
38
|
-
"rgb64le",
|
39
|
-
"rgb24",
|
40
|
-
"bgr24",
|
41
|
-
"argb",
|
42
|
-
"rgba",
|
43
|
-
"abgr",
|
44
|
-
"bgra",
|
45
|
-
"gray",
|
46
|
-
"gray8",
|
47
|
-
"gray16be",
|
48
|
-
"gray16le",
|
49
|
-
"rgb8",
|
50
|
-
"bgr8",
|
51
|
-
"pal8",
|
52
|
-
}
|
28
|
+
allowed_pix_fmt = av.video.frame.supported_np_pix_fmts
|
53
29
|
|
54
30
|
|
55
31
|
def make_solid(width: int, height: int, pix_fmt: str, bg: str) -> av.VideoFrame:
|
@@ -65,7 +41,7 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
|
|
65
41
|
img_cache = {}
|
66
42
|
for clip in tl.v:
|
67
43
|
for obj in clip:
|
68
|
-
if isinstance(obj, TlImage) and obj.src not in img_cache:
|
44
|
+
if isinstance(obj, TlImage) and (obj.src, obj.width) not in img_cache:
|
69
45
|
with av.open(obj.src.path) as cn:
|
70
46
|
my_stream = cn.streams.video[0]
|
71
47
|
for frame in cn.decode(my_stream):
|
@@ -85,7 +61,7 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
|
|
85
61
|
|
86
62
|
|
87
63
|
def render_av(
|
88
|
-
output: av.container.OutputContainer, tl: v3, args: Args,
|
64
|
+
output: av.container.OutputContainer, tl: v3, args: Args, log: Log
|
89
65
|
) -> Any:
|
90
66
|
from_ndarray = av.VideoFrame.from_ndarray
|
91
67
|
|
@@ -130,22 +106,36 @@ def render_av(
|
|
130
106
|
log.debug(f"Tous: {tous}")
|
131
107
|
log.debug(f"Clips: {tl.v}")
|
132
108
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
109
|
+
codec = av.Codec(args.video_codec, "w")
|
110
|
+
|
111
|
+
if codec.canonical_name == "gif":
|
112
|
+
if codec.video_formats is not None and target_pix_fmt in (
|
113
|
+
f.name for f in codec.video_formats
|
137
114
|
):
|
138
115
|
target_pix_fmt = target_pix_fmt
|
139
116
|
else:
|
140
117
|
target_pix_fmt = "rgb8"
|
141
|
-
|
118
|
+
elif codec.canonical_name == "prores":
|
119
|
+
target_pix_fmt = "yuv422p10le"
|
142
120
|
else:
|
143
121
|
target_pix_fmt = (
|
144
122
|
target_pix_fmt if target_pix_fmt in allowed_pix_fmt else "yuv420p"
|
145
123
|
)
|
146
124
|
|
125
|
+
del codec
|
147
126
|
ops = {"mov_flags": "faststart"}
|
148
127
|
output_stream = output.add_stream(args.video_codec, rate=target_fps, options=ops)
|
128
|
+
|
129
|
+
cc = output_stream.codec_context
|
130
|
+
if args.vprofile is not None:
|
131
|
+
if args.vprofile.title() not in cc.profiles:
|
132
|
+
b = " ".join([f'"{x.lower()}"' for x in cc.profiles])
|
133
|
+
log.error(
|
134
|
+
f"`{args.vprofile}` is not a valid profile.\nprofiles supported: {b}"
|
135
|
+
)
|
136
|
+
|
137
|
+
cc.profile = args.vprofile.title()
|
138
|
+
|
149
139
|
yield output_stream
|
150
140
|
if not isinstance(output_stream, av.VideoStream):
|
151
141
|
log.error(f"Not a known video codec: {args.video_codec}")
|
@@ -177,9 +167,9 @@ def render_av(
|
|
177
167
|
color_prim = src.videos[0].color_primaries
|
178
168
|
color_trc = src.videos[0].color_transfer
|
179
169
|
|
180
|
-
if color_range
|
170
|
+
if color_range in {1, 2}:
|
181
171
|
output_stream.color_range = color_range
|
182
|
-
if colorspace in
|
172
|
+
if colorspace in {0, 1} or (colorspace >= 3 and colorspace < 16):
|
183
173
|
output_stream.colorspace = colorspace
|
184
174
|
if color_prim == 1 or (color_prim >= 4 and color_prim < 17):
|
185
175
|
output_stream.color_primaries = color_prim
|
@@ -200,8 +190,6 @@ def render_av(
|
|
200
190
|
seek_frame = None
|
201
191
|
frames_saved = 0
|
202
192
|
|
203
|
-
bar.start(tl.end, "Creating new video")
|
204
|
-
|
205
193
|
bg = args.background
|
206
194
|
null_frame = make_solid(target_width, target_height, target_pix_fmt, bg)
|
207
195
|
frame_index = -1
|
@@ -310,7 +298,7 @@ def render_av(
|
|
310
298
|
roi = array[y_start:y_end, x_start:x_end]
|
311
299
|
|
312
300
|
# Blend the overlay image with the ROI based on the opacity
|
313
|
-
roi = (1 - obj.opacity) * roi + obj.opacity * clipped_overlay
|
301
|
+
roi = (1 - obj.opacity) * roi + obj.opacity * clipped_overlay # type: ignore
|
314
302
|
array[y_start:y_end, x_start:x_end] = roi
|
315
303
|
array = np.clip(array, 0, 255).astype(np.uint8)
|
316
304
|
|
@@ -322,11 +310,7 @@ def render_av(
|
|
322
310
|
|
323
311
|
if frame.format.name != target_pix_fmt:
|
324
312
|
frame = frame.reformat(format=target_pix_fmt)
|
325
|
-
bar.tick(index)
|
326
|
-
elif index % 3 == 0:
|
327
|
-
bar.tick(index)
|
328
313
|
|
329
|
-
yield from_ndarray(frame.to_ndarray(), format=frame.format.name)
|
314
|
+
yield (index, from_ndarray(frame.to_ndarray(), format=frame.format.name))
|
330
315
|
|
331
|
-
bar.end()
|
332
316
|
log.debug(f"Total frames saved seeking: {frames_saved}")
|
auto_editor/utils/bar.py
CHANGED
@@ -31,7 +31,7 @@ def initBar(bar_type: str) -> Bar:
|
|
31
31
|
part_width = len(chars) - 1
|
32
32
|
|
33
33
|
ampm = True
|
34
|
-
if sys.platform == "darwin" and bar_type in
|
34
|
+
if sys.platform == "darwin" and bar_type in {"modern", "classic", "ascii"}:
|
35
35
|
try:
|
36
36
|
date_format = get_stdout_bytes(
|
37
37
|
["defaults", "read", "com.apple.menuextra.clock", "Show24Hour"]
|
@@ -89,7 +89,7 @@ class Bar:
|
|
89
89
|
percent = round(progress * 100, 1)
|
90
90
|
p_pad = " " * (4 - len(str(percent)))
|
91
91
|
columns = get_terminal_size().columns
|
92
|
-
bar_len = max(1, columns -
|
92
|
+
bar_len = max(1, columns - len_title - 32)
|
93
93
|
bar_str = self._bar_str(progress, bar_len)
|
94
94
|
|
95
95
|
bar = f" {self.icon}{title} {bar_str} {p_pad}{percent}% ETA {new_time}"
|
auto_editor/utils/container.py
CHANGED
@@ -47,11 +47,11 @@ containers: dict[str, DictContainer] = {
|
|
47
47
|
|
48
48
|
|
49
49
|
def codec_type(x: str) -> str:
|
50
|
-
if x in
|
50
|
+
if x in {"vp9", "vp8", "h264", "hevc", "av1", "gif", "apng"}:
|
51
51
|
return "video"
|
52
|
-
if x in
|
52
|
+
if x in {"aac", "flac", "mp3"}:
|
53
53
|
return "audio"
|
54
|
-
if x in
|
54
|
+
if x in {"ass", "ssa", "srt"}:
|
55
55
|
return "subtitle"
|
56
56
|
|
57
57
|
try:
|
@@ -82,18 +82,12 @@ def container_constructor(ext: str) -> Container:
|
|
82
82
|
kind = codec_type(codec)
|
83
83
|
if kind == "video":
|
84
84
|
vcodecs.add(codec)
|
85
|
-
if codec == "h264":
|
86
|
-
vcodecs.add("libx264")
|
87
|
-
if codec == "av1":
|
88
|
-
vcodecs.add("libsvtav1")
|
89
|
-
if codec == "hevc":
|
90
|
-
vcodecs.add("hevc_nvenc")
|
91
85
|
if kind == "audio":
|
92
86
|
acodecs.add(codec)
|
93
87
|
if kind == "subtitle":
|
94
88
|
scodecs.add(codec)
|
95
89
|
|
96
|
-
allow_image = ext in
|
90
|
+
allow_image = ext in {"mp4", "mkv"}
|
97
91
|
kwargs = containers[ext] if ext in containers else {}
|
98
92
|
|
99
93
|
return Container(
|
auto_editor/utils/func.py
CHANGED
@@ -41,7 +41,7 @@ def to_timecode(secs: float | Fraction, fmt: str) -> str:
|
|
41
41
|
if h == 0:
|
42
42
|
return f"{sign}{m:02d}:{s:06.3f}"
|
43
43
|
return f"{sign}{h:02d}:{m:02d}:{s:06.3f}"
|
44
|
-
if fmt
|
44
|
+
if fmt in {"srt", "mov_text"}:
|
45
45
|
return f"{sign}{h:02d}:{m:02d}:" + f"{s:06.3f}".replace(".", ",", 1)
|
46
46
|
if fmt == "standard":
|
47
47
|
return f"{sign}{h:02d}:{m:02d}:{s:06.3f}"
|
auto_editor/utils/log.py
CHANGED
@@ -104,6 +104,10 @@ class Log:
|
|
104
104
|
if codec.experimental:
|
105
105
|
self.error(f"`{codec.name}` is an experimental codec")
|
106
106
|
|
107
|
+
@staticmethod
|
108
|
+
def deprecated(message: str) -> None:
|
109
|
+
sys.stderr.write(f"\033[1m\033[33m{message}\033[0m\n")
|
110
|
+
|
107
111
|
def error(self, message: str | Exception) -> NoReturn:
|
108
112
|
if self.is_debug and isinstance(message, Exception):
|
109
113
|
self.cleanup()
|
auto_editor/utils/types.py
CHANGED
@@ -104,7 +104,7 @@ def frame_rate(val: str) -> Fraction:
|
|
104
104
|
|
105
105
|
def sample_rate(val: str) -> int:
|
106
106
|
num, unit = _split_num_str(val)
|
107
|
-
if unit in
|
107
|
+
if unit in {"kHz", "KHz"}:
|
108
108
|
return natural(num * 1000)
|
109
109
|
_unit_check(unit, ("", "Hz"))
|
110
110
|
return natural(num)
|
@@ -122,9 +122,9 @@ def time(val: str, tb: Fraction) -> int:
|
|
122
122
|
raise CoerceError(f"'{val}': Invalid time format")
|
123
123
|
|
124
124
|
num, unit = _split_num_str(val)
|
125
|
-
if unit in
|
125
|
+
if unit in {"s", "sec", "secs", "second", "seconds"}:
|
126
126
|
return round(num * tb)
|
127
|
-
if unit in
|
127
|
+
if unit in {"min", "mins", "minute", "minutes"}:
|
128
128
|
return round(num * tb * 60)
|
129
129
|
if unit == "hour":
|
130
130
|
return round(num * tb * 3600)
|
@@ -198,6 +198,7 @@ class Args:
|
|
198
198
|
video_codec: str = "auto"
|
199
199
|
audio_codec: str = "auto"
|
200
200
|
video_bitrate: str = "auto"
|
201
|
+
vprofile: str | None = None
|
201
202
|
audio_bitrate: str = "auto"
|
202
203
|
scale: float = 1.0
|
203
204
|
sn: bool = False
|
auto_editor/vanparse.py
CHANGED
@@ -289,7 +289,7 @@ class ArgumentParser:
|
|
289
289
|
key = to_key(option)
|
290
290
|
|
291
291
|
next_arg = None if i == len(sys_args) - 1 else sys_args[i + 1]
|
292
|
-
if next_arg in
|
292
|
+
if next_arg in {"-h", "--help"}:
|
293
293
|
print_option_help(program_name, ns_obj, option)
|
294
294
|
sys.exit()
|
295
295
|
|
auto_editor/wavfile.py
CHANGED
@@ -90,7 +90,7 @@ def _read_data_chunk(
|
|
90
90
|
|
91
91
|
bytes_per_sample = block_align // channels
|
92
92
|
|
93
|
-
if bytes_per_sample in
|
93
|
+
if bytes_per_sample in {3, 5, 6, 7}:
|
94
94
|
raise WavError(f"Unsupported bytes per sample: {bytes_per_sample}")
|
95
95
|
|
96
96
|
if format_tag == PCM:
|
@@ -103,7 +103,7 @@ def _read_data_chunk(
|
|
103
103
|
f"Unsupported bit depth: the WAV file has {bit_depth}-bit integer data."
|
104
104
|
)
|
105
105
|
elif format_tag == IEEE_FLOAT:
|
106
|
-
if bit_depth in
|
106
|
+
if bit_depth in {32, 64}:
|
107
107
|
dtype = f"{en}f{bytes_per_sample}"
|
108
108
|
else:
|
109
109
|
raise WavError(
|
@@ -201,7 +201,7 @@ def _handle_pad_byte(fid: Reader, size: int) -> None:
|
|
201
201
|
|
202
202
|
def read(fid: Reader) -> tuple[int, AudioData]:
|
203
203
|
file_sig = fid.read(4)
|
204
|
-
if file_sig in
|
204
|
+
if file_sig in {b"RIFF", b"RIFX"}:
|
205
205
|
data_size, file_size, en = _read_riff_chunk(file_sig, fid)
|
206
206
|
elif file_sig == b"RF64":
|
207
207
|
data_size, file_size, en = _read_rf64_chunk(fid)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: auto-editor
|
3
|
-
Version: 26.
|
3
|
+
Version: 26.3.0
|
4
4
|
Summary: Auto-Editor: Effort free video editing!
|
5
5
|
Author-email: WyattBlue <wyattblue@auto-editor.com>
|
6
6
|
License: 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,>=1.24
|
15
|
-
Requires-Dist: pyav==14
|
15
|
+
Requires-Dist: pyav==14.2.0
|
16
16
|
|
17
17
|
<p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
|
18
18
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
auto_editor/__init__.py,sha256=foFMRrw-Br8Dq0ZIes1siNTsxZj5nQywX_cfWHfB380,23
|
2
|
+
auto_editor/__main__.py,sha256=g-9q3i6oFeNPeFMeNJEwZua6ZloOPLcaLN-B0FlxWXo,11371
|
3
|
+
auto_editor/analyze.py,sha256=7p_SDRRKQzlc6wtoW6MXYvJkDV4NO4DSOtJPFAuYcwM,12721
|
4
|
+
auto_editor/edit.py,sha256=gz2MocM5s1uz-hLrFWmiNl1TwOm4S_1EkYn-ZBuu_sE,17401
|
5
|
+
auto_editor/ffwrapper.py,sha256=1lYYfq8gVgMVkYWeAEYDPAHCwFCYbKQwy0FxYBxMzk8,4765
|
6
|
+
auto_editor/help.py,sha256=CzfDTsL4GuGu596ySHKj_wKnxGR9h8B0KUdkZpo33oE,8044
|
7
|
+
auto_editor/make_layers.py,sha256=vEeJt0PnE1vc9-cQZ_AlXVDjvWhObRCWJSCQGraoMvU,9016
|
8
|
+
auto_editor/output.py,sha256=ho8Lpqz4Sv_Gw0Vj2OvG39s83xHpyZlvtRNryTPbXqc,2563
|
9
|
+
auto_editor/preview.py,sha256=HUsjmV9Fx73rZ26BXrpz9z-z_e4oiui3u9e7qbbGoBY,3037
|
10
|
+
auto_editor/timeline.py,sha256=XfaH9cH-RB-MObOpMr5IfLcqJcjmabO1XwkUkT3_FQM,8186
|
11
|
+
auto_editor/vanparse.py,sha256=Ug5A2QaRqGiw4l55Z_h9T2QU1x0WqRibR7yY5rQ0WTk,10002
|
12
|
+
auto_editor/wavfile.py,sha256=afFfje8cK9lFjIkYoBbQHfvQIpsEPxWvspCtFhUKlAw,9499
|
13
|
+
auto_editor/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
+
auto_editor/cmds/cache.py,sha256=bViYbtVXefTeEIUvSanDfA6cG35ep1N_Jvtz7ZjgIkY,1959
|
15
|
+
auto_editor/cmds/desc.py,sha256=GDrKJYiHMaeTrplZAceXl1JwoqD78XsV2_5lc0Xd7po,869
|
16
|
+
auto_editor/cmds/info.py,sha256=vYa1hYdE8kDTE8AS3kwXlnd59X6CrE2GtIEQ7UmlpRY,7010
|
17
|
+
auto_editor/cmds/levels.py,sha256=qMxTOlj4ezna0K2jYWDFyZ0Srn4pT6nIdR-479IOEvw,5758
|
18
|
+
auto_editor/cmds/palet.py,sha256=ONzTqemaQq9YEfIOsDRNnwzfqnEMUMSXIQrETxyroRU,749
|
19
|
+
auto_editor/cmds/repl.py,sha256=TF_I7zsFY7-KdgidrqjafTz7o_eluVbLvgTcOBG-UWQ,3449
|
20
|
+
auto_editor/cmds/subdump.py,sha256=af_XBf7kaevqHn1A71z8C-7x8pS5WKD9FE_ugkCw6rk,665
|
21
|
+
auto_editor/cmds/test.py,sha256=Wih-KQnv5Ld1CgbOUdtrp_fMnINsr_khzONvlrJ-bhw,26163
|
22
|
+
auto_editor/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
+
auto_editor/formats/fcp11.py,sha256=sqjC36jI47ICPLjZJYiqGwY7foOnWOiNjkPFLdgSnI4,5208
|
24
|
+
auto_editor/formats/fcp7.py,sha256=x5cagTzGCAW3i3M6m7TZC1h8gLfSmX1UK-iiDuCpdfs,20289
|
25
|
+
auto_editor/formats/json.py,sha256=kpYioIgFaVvhmS4I2NKi4ax574sRATe0Xfs0Wht9GUY,7693
|
26
|
+
auto_editor/formats/shotcut.py,sha256=-ES854LLFCMCBe100JRJedDmuk8zPev17aQMTrzPv-g,4923
|
27
|
+
auto_editor/formats/utils.py,sha256=LYXDiqOk9WwUorLGw2D0M7In9BNDkoKikNawuks7hqE,1648
|
28
|
+
auto_editor/lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
|
+
auto_editor/lang/json.py,sha256=D84vwyLtX5t5xl8S4r4jFXGVkbIg1L4IcexlS_a_k6w,9231
|
30
|
+
auto_editor/lang/libintrospection.py,sha256=6H1rGp0wqaCud5IPaoEmzULGnYt6ec7_0h32ATcw2oY,261
|
31
|
+
auto_editor/lang/libmath.py,sha256=z33A161Oe6vYYK7R6pgYjdZZe63dQkN38Qf36TL3prg,847
|
32
|
+
auto_editor/lang/palet.py,sha256=ZOzNxLfzY8FTCYlF_mZtU2XoNzdg-eseUVkMtnWkVaI,24139
|
33
|
+
auto_editor/lang/stdenv.py,sha256=sQyOD3bttjcieWMtTMNpE6blEZriAfHtSZHbuvC09R8,43762
|
34
|
+
auto_editor/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
|
+
auto_editor/lib/contracts.py,sha256=lExGQymcQUmwG5lC1lO4qm4GY8W0q_yzK_miTaAoPA4,7586
|
36
|
+
auto_editor/lib/data_structs.py,sha256=Hnzl5gWvo-geTU0g-lGejj6HQW3VvPv0NBEj2XoGskY,7089
|
37
|
+
auto_editor/lib/err.py,sha256=UlszQJdzMZwkbT8x3sY4GkCV_5x9yrd6uVVUzvA8iiI,35
|
38
|
+
auto_editor/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
+
auto_editor/render/audio.py,sha256=_GuX0WNY1YeumgBN3bWqgwVXiuhpvx7sijABxqyO2ag,12580
|
40
|
+
auto_editor/render/subtitle.py,sha256=jtNRKvgo1fpHTrAfGZqdkNeNgGgasw-K-4PwIKiWwfM,6231
|
41
|
+
auto_editor/render/video.py,sha256=JBVl8w-hQ6zrs97iA527LPsBZ9s601SVSJs2bSMCq88,12185
|
42
|
+
auto_editor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
|
+
auto_editor/utils/bar.py,sha256=0ZSfuWdBA1zySwodkb4mw5uahC6UwCk2umaGnB8A7n0,3996
|
44
|
+
auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,52
|
45
|
+
auto_editor/utils/cmdkw.py,sha256=aUGBvBel2Ko1o6Rwmr4rEL-BMc5hEnzYLbyZ1GeJdcY,5729
|
46
|
+
auto_editor/utils/container.py,sha256=C_Ahh7nlMEX4DNQ2M_cITPPbYcIL68r4I_AgFy0OD6o,2487
|
47
|
+
auto_editor/utils/func.py,sha256=C8ucgsSEzPyBc-8obhsCXd_uQW0cnCdBn1KVxB7FHjU,2747
|
48
|
+
auto_editor/utils/log.py,sha256=8fOdyTG3vjKhA1tJTMKRjXVqhrY2q3tFnXU8tKm_twA,3937
|
49
|
+
auto_editor/utils/types.py,sha256=r5f6QB81xH7NRwGntITIOCVx-fupOl8l3X3LSFkt3nE,10756
|
50
|
+
docs/build.py,sha256=POy8X8QOBYe_8A8HI_yiVI_Qg9E5mLpn1z7AHQr0_vQ,1888
|
51
|
+
auto_editor-26.3.0.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
|
52
|
+
auto_editor-26.3.0.dist-info/METADATA,sha256=YOFNFBSIMQveLah-PSIrBtlfY-xwEveHxKNAneUAs_0,6111
|
53
|
+
auto_editor-26.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
54
|
+
auto_editor-26.3.0.dist-info/entry_points.txt,sha256=UAsTc7qJQbnAzHd7KWg-ALo_X9Hj2yDs3M9I2DV3eyI,212
|
55
|
+
auto_editor-26.3.0.dist-info/top_level.txt,sha256=jBV5zlbWRbKOa-xaWPvTD45QL7lGExx2BDzv-Ji4dTw,17
|
56
|
+
auto_editor-26.3.0.dist-info/RECORD,,
|
@@ -1,56 +0,0 @@
|
|
1
|
-
auto_editor/__init__.py,sha256=2Ltcef2BVJgJx2W5ZkX7r21sdnzR3Zvtu1PYKRHEjLk,23
|
2
|
-
auto_editor/__main__.py,sha256=tc0M1MIPYjU5wCEU3EqmleOzaUgksU60qVHO0vRuC10,11310
|
3
|
-
auto_editor/analyze.py,sha256=Fv8NA99T1dZzrqlweJNlK7haKjgK13neR9CMw4t6rlY,12716
|
4
|
-
auto_editor/edit.py,sha256=eEMRaQbn0jylfJ6D_egnUXjoMCbdQVsAu7MDrn-xlGo,15950
|
5
|
-
auto_editor/ffwrapper.py,sha256=Tct_Q-uy5F51h8M7UFam50UzRFpgkBvUamJP1AoKVvc,4749
|
6
|
-
auto_editor/help.py,sha256=CzfDTsL4GuGu596ySHKj_wKnxGR9h8B0KUdkZpo33oE,8044
|
7
|
-
auto_editor/make_layers.py,sha256=vEeJt0PnE1vc9-cQZ_AlXVDjvWhObRCWJSCQGraoMvU,9016
|
8
|
-
auto_editor/output.py,sha256=ho8Lpqz4Sv_Gw0Vj2OvG39s83xHpyZlvtRNryTPbXqc,2563
|
9
|
-
auto_editor/preview.py,sha256=HUsjmV9Fx73rZ26BXrpz9z-z_e4oiui3u9e7qbbGoBY,3037
|
10
|
-
auto_editor/timeline.py,sha256=XfaH9cH-RB-MObOpMr5IfLcqJcjmabO1XwkUkT3_FQM,8186
|
11
|
-
auto_editor/vanparse.py,sha256=f0vViZ-aYtDxEyVrFHJ5X2pPTQAfqtw3N2gZgzn51kU,10002
|
12
|
-
auto_editor/wavfile.py,sha256=1HbZ4L8IBD6Fbg3pd5MQG4ZXy48YZA05t8XllSplhWk,9499
|
13
|
-
auto_editor/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
auto_editor/formats/fcp11.py,sha256=qzo-qpHYsiHjOPjGBWjBeJUAACDmo8ijJkjslHTQH6Q,5196
|
15
|
-
auto_editor/formats/fcp7.py,sha256=LYkGtZC_dmbHQDg1wYP7XQYS74NEon6ws8c5MDdTd90,20275
|
16
|
-
auto_editor/formats/json.py,sha256=Br-xHVHj59C0OPP2FwfJeht_fImepRXsaw0iDFvK7-w,7693
|
17
|
-
auto_editor/formats/shotcut.py,sha256=-ES854LLFCMCBe100JRJedDmuk8zPev17aQMTrzPv-g,4923
|
18
|
-
auto_editor/formats/utils.py,sha256=LYXDiqOk9WwUorLGw2D0M7In9BNDkoKikNawuks7hqE,1648
|
19
|
-
auto_editor/lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
-
auto_editor/lang/json.py,sha256=OsNcYlfEj8ZLlzLK-gkLcrCCKI7mJz9rpe-6XLr4f9U,9231
|
21
|
-
auto_editor/lang/libintrospection.py,sha256=6H1rGp0wqaCud5IPaoEmzULGnYt6ec7_0h32ATcw2oY,261
|
22
|
-
auto_editor/lang/libmath.py,sha256=z33A161Oe6vYYK7R6pgYjdZZe63dQkN38Qf36TL3prg,847
|
23
|
-
auto_editor/lang/palet.py,sha256=jHSO8R4eAbMeiQaGwCLih6z0pPGoJEcmPgSvsTpF8EA,24139
|
24
|
-
auto_editor/lang/stdenv.py,sha256=3UnVgLaTT7hQMhquDmv-mQx82jtr_YOYstwZAwvXfJY,43754
|
25
|
-
auto_editor/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
|
-
auto_editor/lib/contracts.py,sha256=lExGQymcQUmwG5lC1lO4qm4GY8W0q_yzK_miTaAoPA4,7586
|
27
|
-
auto_editor/lib/data_structs.py,sha256=dcsXgsLLzbmFDUZucoirzewPALsKzoxz7z5L22_QJM8,7091
|
28
|
-
auto_editor/lib/err.py,sha256=UlszQJdzMZwkbT8x3sY4GkCV_5x9yrd6uVVUzvA8iiI,35
|
29
|
-
auto_editor/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
-
auto_editor/render/audio.py,sha256=1iOQCeRXfRz28cqnHp2XeK-f3_UnPf80AKQAfifGvdE,12584
|
31
|
-
auto_editor/render/subtitle.py,sha256=lf2l1QWJgFiqlpQWWBwSlKJnSgW8Lkfi59WrJMbIDqM,6240
|
32
|
-
auto_editor/render/video.py,sha256=dje0RNW2dKILfTzt0VAF0WR6REfGOsc6l17pP1Z4ooA,12215
|
33
|
-
auto_editor/subcommands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
34
|
-
auto_editor/subcommands/cache.py,sha256=YW_5qH0q5TVzmfOLEO117uqcY7dF6DS619ltVTPIzHQ,1959
|
35
|
-
auto_editor/subcommands/desc.py,sha256=GDrKJYiHMaeTrplZAceXl1JwoqD78XsV2_5lc0Xd7po,869
|
36
|
-
auto_editor/subcommands/info.py,sha256=UDdoxd6_fqSoRPwthkWXqnpxHp7dJQ0Dn96lYX_ubWc,7010
|
37
|
-
auto_editor/subcommands/levels.py,sha256=psSSIsGfzr9j0HGKp2yvK6nMlrkLwxkwsyI0uF2xb_c,4496
|
38
|
-
auto_editor/subcommands/palet.py,sha256=ONzTqemaQq9YEfIOsDRNnwzfqnEMUMSXIQrETxyroRU,749
|
39
|
-
auto_editor/subcommands/repl.py,sha256=TF_I7zsFY7-KdgidrqjafTz7o_eluVbLvgTcOBG-UWQ,3449
|
40
|
-
auto_editor/subcommands/subdump.py,sha256=af_XBf7kaevqHn1A71z8C-7x8pS5WKD9FE_ugkCw6rk,665
|
41
|
-
auto_editor/subcommands/test.py,sha256=bR3MQvW1P_cKDwd1hRa2t-n3GqgRGGv5D3IBnfrH8-0,25787
|
42
|
-
auto_editor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
|
-
auto_editor/utils/bar.py,sha256=hG_NiYeuM90TdILzAJORft-UOS5grwWN3SbRuj6upsI,3998
|
44
|
-
auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,52
|
45
|
-
auto_editor/utils/cmdkw.py,sha256=aUGBvBel2Ko1o6Rwmr4rEL-BMc5hEnzYLbyZ1GeJdcY,5729
|
46
|
-
auto_editor/utils/container.py,sha256=Wf1ZL0tvXWl6m1B9mK_SkgVl89ilV_LpwlQq0TVroCc,2704
|
47
|
-
auto_editor/utils/func.py,sha256=kB-pNDn20M6YT7sljyd_auve5teK-E2G4TgwVOAIuJw,2754
|
48
|
-
auto_editor/utils/log.py,sha256=n5dlJ2CdK_54eiYE02SPgkBdBWABV7tE2p8ONj_F6TM,3813
|
49
|
-
auto_editor/utils/types.py,sha256=7BF7R7DA5eKmtI6f5ia7bOYNL0u_2sviiPsE1VmP0lc,10724
|
50
|
-
docs/build.py,sha256=POy8X8QOBYe_8A8HI_yiVI_Qg9E5mLpn1z7AHQr0_vQ,1888
|
51
|
-
auto_editor-26.1.1.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
|
52
|
-
auto_editor-26.1.1.dist-info/METADATA,sha256=Ovf6CjY_x-lyih-4c1xBZEkL_X0gvifVFthPcLSMOtk,6109
|
53
|
-
auto_editor-26.1.1.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
|
54
|
-
auto_editor-26.1.1.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
|
55
|
-
auto_editor-26.1.1.dist-info/top_level.txt,sha256=jBV5zlbWRbKOa-xaWPvTD45QL7lGExx2BDzv-Ji4dTw,17
|
56
|
-
auto_editor-26.1.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|