auto-editor 26.0.0__py3-none-any.whl → 26.1.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 +11 -43
- auto_editor/analyze.py +62 -49
- auto_editor/edit.py +15 -33
- auto_editor/ffwrapper.py +0 -49
- auto_editor/formats/fcp7.py +1 -1
- auto_editor/help.py +14 -16
- auto_editor/lang/palet.py +3 -9
- auto_editor/lang/stdenv.py +0 -7
- auto_editor/make_layers.py +3 -1
- auto_editor/output.py +4 -1
- auto_editor/render/audio.py +92 -42
- auto_editor/render/subtitle.py +7 -5
- auto_editor/render/video.py +1 -2
- auto_editor/subcommands/info.py +2 -0
- auto_editor/subcommands/levels.py +14 -3
- auto_editor/subcommands/test.py +1 -1
- auto_editor/timeline.py +2 -2
- auto_editor/utils/cmdkw.py +5 -8
- auto_editor/utils/container.py +2 -5
- auto_editor/utils/func.py +1 -34
- auto_editor/utils/log.py +6 -0
- auto_editor/utils/types.py +3 -17
- {auto_editor-26.0.0.dist-info → auto_editor-26.1.0.dist-info}/METADATA +3 -4
- auto_editor-26.1.0.dist-info/RECORD +55 -0
- {auto_editor-26.0.0.dist-info → auto_editor-26.1.0.dist-info}/WHEEL +1 -1
- auto_editor/utils/encoder.py +0 -135
- auto_editor-26.0.0.dist-info/RECORD +0 -56
- {auto_editor-26.0.0.dist-info → auto_editor-26.1.0.dist-info}/LICENSE +0 -0
- {auto_editor-26.0.0.dist-info → auto_editor-26.1.0.dist-info}/entry_points.txt +0 -0
- {auto_editor-26.0.0.dist-info → auto_editor-26.1.0.dist-info}/top_level.txt +0 -0
auto_editor/render/audio.py
CHANGED
@@ -2,13 +2,12 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import io
|
4
4
|
from pathlib import Path
|
5
|
-
from platform import system
|
6
|
-
from subprocess import PIPE
|
7
5
|
|
8
6
|
import av
|
9
7
|
import numpy as np
|
8
|
+
from av.filter.loudnorm import stats
|
10
9
|
|
11
|
-
from auto_editor.ffwrapper import
|
10
|
+
from auto_editor.ffwrapper import FileInfo
|
12
11
|
from auto_editor.lang.json import Lexer, Parser
|
13
12
|
from auto_editor.lang.palet import env
|
14
13
|
from auto_editor.lib.contracts import andc, between_c, is_int_or_float
|
@@ -17,6 +16,7 @@ from auto_editor.output import Ensure
|
|
17
16
|
from auto_editor.timeline import TlAudio, v3
|
18
17
|
from auto_editor.utils.bar import Bar
|
19
18
|
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
|
19
|
+
from auto_editor.utils.container import Container
|
20
20
|
from auto_editor.utils.log import Log
|
21
21
|
from auto_editor.utils.types import Args
|
22
22
|
from auto_editor.wavfile import AudioData, read, write
|
@@ -56,25 +56,11 @@ def parse_norm(norm: str, log: Log) -> dict | None:
|
|
56
56
|
log.error(e)
|
57
57
|
|
58
58
|
|
59
|
-
def parse_ebu_bytes(norm: dict,
|
60
|
-
start = end = 0
|
61
|
-
lines = stderr.splitlines()
|
62
|
-
|
63
|
-
for index, line in enumerate(lines):
|
64
|
-
if line.startswith(b"[Parsed_loudnorm"):
|
65
|
-
start = index + 1
|
66
|
-
continue
|
67
|
-
if start != 0 and line.startswith(b"}"):
|
68
|
-
end = index + 1
|
69
|
-
break
|
70
|
-
|
71
|
-
if start == 0 or end == 0:
|
72
|
-
log.error(f"Invalid loudnorm stats.\n{stderr!r}")
|
73
|
-
|
59
|
+
def parse_ebu_bytes(norm: dict, stat: bytes, log: Log) -> tuple[str, str]:
|
74
60
|
try:
|
75
|
-
parsed = Parser(Lexer("loudnorm",
|
61
|
+
parsed = Parser(Lexer("loudnorm", stat)).expr()
|
76
62
|
except MyError:
|
77
|
-
log.error(f"Invalid loudnorm stats.\n{
|
63
|
+
log.error(f"Invalid loudnorm stats.\n{stat!r}")
|
78
64
|
|
79
65
|
for key in ("input_i", "input_tp", "input_lra", "input_thresh", "target_offset"):
|
80
66
|
val = float(parsed[key])
|
@@ -101,30 +87,17 @@ def parse_ebu_bytes(norm: dict, stderr: bytes, log: Log) -> tuple[str, str]:
|
|
101
87
|
|
102
88
|
|
103
89
|
def apply_audio_normalization(
|
104
|
-
|
90
|
+
norm: dict, pre_master: Path, path: Path, log: Log
|
105
91
|
) -> None:
|
106
92
|
if norm["tag"] == "ebu":
|
107
93
|
first_pass = (
|
108
|
-
f"
|
109
|
-
f"offset={norm['gain']}:print_format=json"
|
94
|
+
f"i={norm['i']}:lra={norm['lra']}:tp={norm['tp']}:" f"offset={norm['gain']}"
|
110
95
|
)
|
111
96
|
log.debug(f"audio norm first pass: {first_pass}")
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
f"{pre_master}",
|
117
|
-
"-af",
|
118
|
-
first_pass,
|
119
|
-
"-vn",
|
120
|
-
"-sn",
|
121
|
-
"-f",
|
122
|
-
"null",
|
123
|
-
file_null,
|
124
|
-
]
|
125
|
-
process = ffmpeg.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
126
|
-
stderr = process.communicate()[1]
|
127
|
-
name, filter_args = parse_ebu_bytes(norm, stderr, log)
|
97
|
+
with av.open(f"{pre_master}") as container:
|
98
|
+
stats_ = stats(first_pass, container.streams.audio[0])
|
99
|
+
|
100
|
+
name, filter_args = parse_ebu_bytes(norm, stats_, log)
|
128
101
|
else:
|
129
102
|
assert "t" in norm
|
130
103
|
|
@@ -238,12 +211,84 @@ def process_audio_clip(
|
|
238
211
|
return read(output_bytes)[1]
|
239
212
|
|
240
213
|
|
214
|
+
def mix_audio_files(sr: int, audio_paths: list[str], output_path: str) -> None:
|
215
|
+
mixed_audio = None
|
216
|
+
max_length = 0
|
217
|
+
|
218
|
+
# First pass: determine the maximum length
|
219
|
+
for path in audio_paths:
|
220
|
+
container = av.open(path)
|
221
|
+
stream = container.streams.audio[0]
|
222
|
+
|
223
|
+
# Calculate duration in samples
|
224
|
+
assert stream.duration is not None
|
225
|
+
assert stream.time_base is not None
|
226
|
+
duration_samples = int(stream.duration * sr / stream.time_base.denominator)
|
227
|
+
max_length = max(max_length, duration_samples)
|
228
|
+
container.close()
|
229
|
+
|
230
|
+
# Second pass: read and mix audio
|
231
|
+
for path in audio_paths:
|
232
|
+
container = av.open(path)
|
233
|
+
stream = container.streams.audio[0]
|
234
|
+
|
235
|
+
resampler = av.audio.resampler.AudioResampler(
|
236
|
+
format="s16", layout="mono", rate=sr
|
237
|
+
)
|
238
|
+
|
239
|
+
audio_array: list[np.ndarray] = []
|
240
|
+
for frame in container.decode(audio=0):
|
241
|
+
frame.pts = None
|
242
|
+
resampled = resampler.resample(frame)[0]
|
243
|
+
audio_array.extend(resampled.to_ndarray().flatten())
|
244
|
+
|
245
|
+
# Pad or truncate to max_length
|
246
|
+
current_audio = np.array(audio_array[:max_length])
|
247
|
+
if len(current_audio) < max_length:
|
248
|
+
current_audio = np.pad(
|
249
|
+
current_audio, (0, max_length - len(current_audio)), "constant"
|
250
|
+
)
|
251
|
+
|
252
|
+
if mixed_audio is None:
|
253
|
+
mixed_audio = current_audio.astype(np.float32)
|
254
|
+
else:
|
255
|
+
mixed_audio += current_audio.astype(np.float32)
|
256
|
+
|
257
|
+
container.close()
|
258
|
+
|
259
|
+
if mixed_audio is None:
|
260
|
+
raise ValueError("mixed_audio is None")
|
261
|
+
|
262
|
+
# Normalize the mixed audio
|
263
|
+
max_val = np.max(np.abs(mixed_audio))
|
264
|
+
if max_val > 0:
|
265
|
+
mixed_audio = mixed_audio * (32767 / max_val)
|
266
|
+
mixed_audio = mixed_audio.astype(np.int16) # type: ignore
|
267
|
+
|
268
|
+
output_container = av.open(output_path, mode="w")
|
269
|
+
output_stream = output_container.add_stream("pcm_s16le", rate=sr)
|
270
|
+
|
271
|
+
chunk_size = sr # Process 1 second at a time
|
272
|
+
for i in range(0, len(mixed_audio), chunk_size):
|
273
|
+
# Shape becomes (1, samples) for mono
|
274
|
+
chunk = np.array([mixed_audio[i : i + chunk_size]])
|
275
|
+
|
276
|
+
frame = av.AudioFrame.from_ndarray(chunk, format="s16", layout="mono")
|
277
|
+
frame.rate = sr
|
278
|
+
frame.pts = i # Set presentation timestamp
|
279
|
+
|
280
|
+
output_container.mux(output_stream.encode(frame))
|
281
|
+
|
282
|
+
output_container.mux(output_stream.encode(None))
|
283
|
+
output_container.close()
|
284
|
+
|
285
|
+
|
241
286
|
def make_new_audio(
|
242
|
-
tl: v3, ensure: Ensure, args: Args,
|
287
|
+
tl: v3, ctr: Container, ensure: Ensure, args: Args, bar: Bar, log: Log
|
243
288
|
) -> list[str]:
|
244
289
|
sr = tl.sr
|
245
290
|
tb = tl.tb
|
246
|
-
output = []
|
291
|
+
output: list[str] = []
|
247
292
|
samples: dict[tuple[FileInfo, int], AudioData] = {}
|
248
293
|
|
249
294
|
norm = parse_norm(args.audio_normalize, log)
|
@@ -313,7 +358,7 @@ def make_new_audio(
|
|
313
358
|
with open(pre_master, "wb") as fid:
|
314
359
|
write(fid, sr, arr)
|
315
360
|
|
316
|
-
apply_audio_normalization(
|
361
|
+
apply_audio_normalization(norm, pre_master, path, log)
|
317
362
|
|
318
363
|
bar.end()
|
319
364
|
|
@@ -321,4 +366,9 @@ def make_new_audio(
|
|
321
366
|
Path(temp, "asdf.map").unlink(missing_ok=True)
|
322
367
|
except PermissionError:
|
323
368
|
pass
|
369
|
+
|
370
|
+
if not (args.keep_tracks_separate and ctr.max_audios is None) and len(output) > 1:
|
371
|
+
new_a_file = f"{Path(temp, 'new_audio.wav')}"
|
372
|
+
mix_audio_files(sr, output, new_a_file)
|
373
|
+
return [new_a_file]
|
324
374
|
return output
|
auto_editor/render/subtitle.py
CHANGED
@@ -157,12 +157,12 @@ def make_srt(input_: Input, stream: int) -> str:
|
|
157
157
|
return output_bytes.getvalue()
|
158
158
|
|
159
159
|
|
160
|
-
def _ensure(input_: Input, format: str, stream: int
|
160
|
+
def _ensure(input_: Input, format: str, stream: int) -> str:
|
161
161
|
output_bytes = io.BytesIO()
|
162
162
|
output = av.open(output_bytes, "w", format=format)
|
163
163
|
|
164
164
|
in_stream = input_.streams.subtitles[stream]
|
165
|
-
out_stream = output.
|
165
|
+
out_stream = output.add_stream_from_template(in_stream)
|
166
166
|
|
167
167
|
for packet in input_.demux(in_stream):
|
168
168
|
if packet.dts is None:
|
@@ -187,7 +187,9 @@ def make_new_subtitles(tl: v3, log: Log) -> list[str]:
|
|
187
187
|
continue
|
188
188
|
|
189
189
|
parser = SubtitleParser(tl.tb)
|
190
|
-
if sub.codec
|
190
|
+
if sub.codec == "ssa":
|
191
|
+
format = "ass"
|
192
|
+
elif sub.codec in ("webvtt", "ass"):
|
191
193
|
format = sub.codec
|
192
194
|
else:
|
193
195
|
log.error(f"Unknown subtitle codec: {sub.codec}")
|
@@ -195,8 +197,8 @@ def make_new_subtitles(tl: v3, log: Log) -> list[str]:
|
|
195
197
|
if sub.codec == "mov_text":
|
196
198
|
ret = make_srt(input_, s)
|
197
199
|
else:
|
198
|
-
ret = _ensure(input_, format, s
|
199
|
-
parser.parse(ret,
|
200
|
+
ret = _ensure(input_, format, s)
|
201
|
+
parser.parse(ret, format)
|
200
202
|
parser.edit(tl.v1.chunks)
|
201
203
|
|
202
204
|
new_path = os.path.join(log.temp, f"new{s}s.{sub.ext}")
|
auto_editor/render/video.py
CHANGED
@@ -8,7 +8,6 @@ import numpy as np
|
|
8
8
|
|
9
9
|
from auto_editor.output import parse_bitrate
|
10
10
|
from auto_editor.timeline import TlImage, TlRect, TlVideo
|
11
|
-
from auto_editor.utils.types import color
|
12
11
|
|
13
12
|
if TYPE_CHECKING:
|
14
13
|
from collections.abc import Iterator
|
@@ -203,7 +202,7 @@ def render_av(
|
|
203
202
|
|
204
203
|
bar.start(tl.end, "Creating new video")
|
205
204
|
|
206
|
-
bg =
|
205
|
+
bg = args.background
|
207
206
|
null_frame = make_solid(target_width, target_height, target_pix_fmt, bg)
|
208
207
|
frame_index = -1
|
209
208
|
|
auto_editor/subcommands/info.py
CHANGED
@@ -163,6 +163,8 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
163
163
|
file_info[file]["subtitle"].append(sub)
|
164
164
|
|
165
165
|
if args.json:
|
166
|
+
if sys.platform == "win32":
|
167
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
166
168
|
dump(file_info, sys.stdout, indent=4)
|
167
169
|
return
|
168
170
|
|
@@ -5,9 +5,10 @@ from dataclasses import dataclass, field
|
|
5
5
|
from fractions import Fraction
|
6
6
|
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
+
import av
|
8
9
|
import numpy as np
|
9
10
|
|
10
|
-
from auto_editor.analyze import
|
11
|
+
from auto_editor.analyze import *
|
11
12
|
from auto_editor.ffwrapper import initFileInfo
|
12
13
|
from auto_editor.lang.palet import env
|
13
14
|
from auto_editor.lib.contracts import is_bool, is_nat, is_nat1, is_str, is_void, orc
|
@@ -130,9 +131,19 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
130
131
|
levels = Levels(src, tb, bar, False, log, strict=True)
|
131
132
|
try:
|
132
133
|
if method == "audio":
|
133
|
-
|
134
|
+
container = av.open(src.path, "r")
|
135
|
+
audio_stream = container.streams.audio[obj["stream"]]
|
136
|
+
log.experimental(audio_stream.codec)
|
137
|
+
print_arr_gen(iter_audio(audio_stream, tb))
|
138
|
+
container.close()
|
139
|
+
|
134
140
|
elif method == "motion":
|
135
|
-
|
141
|
+
container = av.open(src.path, "r")
|
142
|
+
video_stream = container.streams.video[obj["stream"]]
|
143
|
+
log.experimental(video_stream.codec)
|
144
|
+
print_arr_gen(iter_motion(video_stream, tb, obj["blur"], obj["width"]))
|
145
|
+
container.close()
|
146
|
+
|
136
147
|
elif method == "subtitle":
|
137
148
|
print_arr(levels.subtitle(**obj))
|
138
149
|
elif method == "none":
|
auto_editor/subcommands/test.py
CHANGED
@@ -360,7 +360,7 @@ def main(sys_args: list[str] | None = None):
|
|
360
360
|
"""{"version": "1", "source": "example.mp4", "chunks": [ [0, 26, 1.0], [26, 34, 0] ]}"""
|
361
361
|
)
|
362
362
|
|
363
|
-
return run.main(["v1.json"], [])
|
363
|
+
return "v1.json", run.main(["v1.json"], [])
|
364
364
|
|
365
365
|
def premiere_named_export():
|
366
366
|
run.main(["example.mp4"], ["--export", 'premiere:name="Foo Bar"'])
|
auto_editor/timeline.py
CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
|
|
6
6
|
from auto_editor.ffwrapper import initFileInfo, mux
|
7
7
|
from auto_editor.lib.contracts import *
|
8
8
|
from auto_editor.utils.cmdkw import Required, pAttr, pAttrs
|
9
|
-
from auto_editor.utils.types import
|
9
|
+
from auto_editor.utils.types import natural, number, parse_color, threshold
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from collections.abc import Iterator
|
@@ -165,7 +165,7 @@ rect_builder = pAttrs(
|
|
165
165
|
pAttr("y", Required, is_int, int),
|
166
166
|
pAttr("width", Required, is_int, int),
|
167
167
|
pAttr("height", Required, is_int, int),
|
168
|
-
pAttr("fill", "#c4c4c4", is_str,
|
168
|
+
pAttr("fill", "#c4c4c4", is_str, parse_color),
|
169
169
|
)
|
170
170
|
visual_objects = {
|
171
171
|
"rect": (TlRect, rect_builder),
|
auto_editor/utils/cmdkw.py
CHANGED
@@ -35,11 +35,6 @@ class pAttrs:
|
|
35
35
|
self.attrs = attrs
|
36
36
|
|
37
37
|
|
38
|
-
def _norm_name(s: str) -> str:
|
39
|
-
# Python does not allow - in variable names
|
40
|
-
return s.replace("-", "_")
|
41
|
-
|
42
|
-
|
43
38
|
class PLexer:
|
44
39
|
__slots__ = ("text", "pos", "char")
|
45
40
|
|
@@ -101,6 +96,10 @@ def parse_with_palet(
|
|
101
96
|
KEYWORD_SEP = "="
|
102
97
|
kwargs: dict[str, Any] = {}
|
103
98
|
|
99
|
+
def _norm_name(s: str) -> str:
|
100
|
+
# Python does not allow - in variable names
|
101
|
+
return s.replace("-", "_")
|
102
|
+
|
104
103
|
def go(text: str, c: Any) -> Any:
|
105
104
|
try:
|
106
105
|
env = _env if isinstance(_env, Env) else Env(_env)
|
@@ -174,9 +173,7 @@ def parse_with_palet(
|
|
174
173
|
return kwargs
|
175
174
|
|
176
175
|
|
177
|
-
def parse_method(
|
178
|
-
name: str, text: str, env: Env
|
179
|
-
) -> tuple[str, list[Any], dict[str, Any]]:
|
176
|
+
def parse_method(name: str, text: str) -> tuple[str, list[Any], dict[str, Any]]:
|
180
177
|
from auto_editor.lang.palet import Lexer, Parser
|
181
178
|
|
182
179
|
# Positional Arguments
|
auto_editor/utils/container.py
CHANGED
@@ -55,12 +55,9 @@ def codec_type(x: str) -> str:
|
|
55
55
|
return "subtitle"
|
56
56
|
|
57
57
|
try:
|
58
|
-
return Codec(x, "
|
58
|
+
return Codec(x, "w").type
|
59
59
|
except Exception:
|
60
|
-
|
61
|
-
return Codec(x, "w").type
|
62
|
-
except Exception:
|
63
|
-
return ""
|
60
|
+
return ""
|
64
61
|
|
65
62
|
|
66
63
|
def container_constructor(ext: str) -> Container:
|
auto_editor/utils/func.py
CHANGED
@@ -81,21 +81,10 @@ def mut_margin(arr: BoolList, start_m: int, end_m: int) -> None:
|
|
81
81
|
arr[max(i + end_m, 0) : i] = False
|
82
82
|
|
83
83
|
|
84
|
-
def merge(start_list: np.ndarray, end_list: np.ndarray) -> BoolList:
|
85
|
-
result = np.zeros((len(start_list)), dtype=np.bool_)
|
86
|
-
|
87
|
-
for i, item in enumerate(start_list):
|
88
|
-
if item == True:
|
89
|
-
where = np.where(end_list[i:])[0]
|
90
|
-
if len(where) > 0:
|
91
|
-
result[i : where[0]] = True
|
92
|
-
return result
|
93
|
-
|
94
|
-
|
95
84
|
def get_stdout(cmd: list[str]) -> str:
|
96
85
|
from subprocess import DEVNULL, PIPE, Popen
|
97
86
|
|
98
|
-
stdout
|
87
|
+
stdout = Popen(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE).communicate()[0]
|
99
88
|
return stdout.decode("utf-8", "replace")
|
100
89
|
|
101
90
|
|
@@ -116,25 +105,3 @@ def aspect_ratio(width: int, height: int) -> tuple[int, int]:
|
|
116
105
|
|
117
106
|
c = gcd(width, height)
|
118
107
|
return width // c, height // c
|
119
|
-
|
120
|
-
|
121
|
-
def human_readable_time(time_in_secs: float) -> str:
|
122
|
-
units = "seconds"
|
123
|
-
if time_in_secs >= 3600:
|
124
|
-
time_in_secs = round(time_in_secs / 3600, 1)
|
125
|
-
if time_in_secs % 1 == 0:
|
126
|
-
time_in_secs = round(time_in_secs)
|
127
|
-
units = "hours"
|
128
|
-
if time_in_secs >= 60:
|
129
|
-
time_in_secs = round(time_in_secs / 60, 1)
|
130
|
-
if time_in_secs >= 10 or time_in_secs % 1 == 0:
|
131
|
-
time_in_secs = round(time_in_secs)
|
132
|
-
units = "minutes"
|
133
|
-
return f"{time_in_secs} {units}"
|
134
|
-
|
135
|
-
|
136
|
-
def append_filename(path: str, val: str) -> str:
|
137
|
-
from os.path import splitext
|
138
|
-
|
139
|
-
root, ext = splitext(path)
|
140
|
-
return root + val + ext
|
auto_editor/utils/log.py
CHANGED
@@ -7,6 +7,8 @@ from tempfile import mkdtemp
|
|
7
7
|
from time import perf_counter, sleep
|
8
8
|
from typing import NoReturn
|
9
9
|
|
10
|
+
import av
|
11
|
+
|
10
12
|
|
11
13
|
class Log:
|
12
14
|
__slots__ = ("is_debug", "quiet", "machine", "no_color", "_temp", "_ut", "_s")
|
@@ -97,6 +99,10 @@ class Log:
|
|
97
99
|
|
98
100
|
sys.stdout.write(f"Finished. took {second_len} seconds ({minute_len})\n")
|
99
101
|
|
102
|
+
def experimental(self, codec: av.Codec) -> None:
|
103
|
+
if codec.experimental:
|
104
|
+
self.error(f"`{codec.name}` is an experimental codec")
|
105
|
+
|
100
106
|
def error(self, message: str | Exception) -> NoReturn:
|
101
107
|
if self.is_debug and isinstance(message, Exception):
|
102
108
|
self.cleanup()
|
auto_editor/utils/types.py
CHANGED
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
3
3
|
import re
|
4
4
|
from dataclasses import dataclass, field
|
5
5
|
from fractions import Fraction
|
6
|
-
from typing import Literal
|
7
6
|
|
8
7
|
|
9
8
|
class CoerceError(Exception):
|
@@ -156,16 +155,7 @@ def speed_range(val: str) -> tuple[float, str, str]:
|
|
156
155
|
return number(a[0]), a[1], a[2]
|
157
156
|
|
158
157
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
def stream(val: str) -> Stream:
|
163
|
-
if val == "all" or val == "'all":
|
164
|
-
return "all"
|
165
|
-
return natural(val)
|
166
|
-
|
167
|
-
|
168
|
-
def color(val: str) -> str:
|
158
|
+
def parse_color(val: str) -> str:
|
169
159
|
"""
|
170
160
|
Convert a color str into an RGB tuple
|
171
161
|
|
@@ -219,22 +209,18 @@ class Args:
|
|
219
209
|
frame_rate: Fraction | None = None
|
220
210
|
sample_rate: int | None = None
|
221
211
|
resolution: tuple[int, int] | None = None
|
222
|
-
background: str = "#
|
223
|
-
|
212
|
+
background: str = "#000000"
|
213
|
+
edit: str = "audio"
|
224
214
|
keep_tracks_separate: bool = False
|
225
215
|
audio_normalize: str = "#f"
|
226
216
|
export: str | None = None
|
227
217
|
player: str | None = None
|
228
218
|
no_open: bool = False
|
229
219
|
temp_dir: str | None = None
|
230
|
-
ffmpeg_location: str | None = None
|
231
|
-
my_ffmpeg: bool = False
|
232
220
|
progress: str = "modern"
|
233
221
|
version: bool = False
|
234
222
|
debug: bool = False
|
235
223
|
config: bool = False
|
236
|
-
show_ffmpeg_commands: bool = False
|
237
|
-
show_ffmpeg_output: bool = False
|
238
224
|
quiet: bool = False
|
239
225
|
preview: bool = False
|
240
226
|
no_cache: bool = False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: auto-editor
|
3
|
-
Version: 26.
|
3
|
+
Version: 26.1.0
|
4
4
|
Summary: Auto-Editor: Effort free video editing!
|
5
5
|
Author-email: WyattBlue <wyattblue@auto-editor.com>
|
6
6
|
License: Unlicense
|
@@ -11,9 +11,8 @@ Keywords: video,audio,media,editor,editing,processing,nonlinear,automatic,silenc
|
|
11
11
|
Requires-Python: <3.14,>=3.10
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
License-File: LICENSE
|
14
|
-
Requires-Dist: numpy
|
15
|
-
Requires-Dist: pyav
|
16
|
-
Requires-Dist: ae-ffmpeg ==1.2.*
|
14
|
+
Requires-Dist: numpy<3.0,>=1.24
|
15
|
+
Requires-Dist: pyav==14.*
|
17
16
|
|
18
17
|
<p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
|
19
18
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
auto_editor/__init__.py,sha256=8MQdwPYn_Y7GCbtRLrmuh9XSy5S52w2pxd3bulKs9Ag,23
|
2
|
+
auto_editor/__main__.py,sha256=eAsNa1BP4Y6Oyp4l838YmcxEwsM0LUdbaGeNFELe4h0,11124
|
3
|
+
auto_editor/analyze.py,sha256=HyRdnty3VW9ZTwwPwjsZp3bLVRLvII_1Y6NlEItDKfw,11947
|
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/desc.py,sha256=GDrKJYiHMaeTrplZAceXl1JwoqD78XsV2_5lc0Xd7po,869
|
35
|
+
auto_editor/subcommands/info.py,sha256=UDdoxd6_fqSoRPwthkWXqnpxHp7dJQ0Dn96lYX_ubWc,7010
|
36
|
+
auto_editor/subcommands/levels.py,sha256=psSSIsGfzr9j0HGKp2yvK6nMlrkLwxkwsyI0uF2xb_c,4496
|
37
|
+
auto_editor/subcommands/palet.py,sha256=ONzTqemaQq9YEfIOsDRNnwzfqnEMUMSXIQrETxyroRU,749
|
38
|
+
auto_editor/subcommands/repl.py,sha256=TF_I7zsFY7-KdgidrqjafTz7o_eluVbLvgTcOBG-UWQ,3449
|
39
|
+
auto_editor/subcommands/subdump.py,sha256=af_XBf7kaevqHn1A71z8C-7x8pS5WKD9FE_ugkCw6rk,665
|
40
|
+
auto_editor/subcommands/test.py,sha256=bR3MQvW1P_cKDwd1hRa2t-n3GqgRGGv5D3IBnfrH8-0,25787
|
41
|
+
auto_editor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
42
|
+
auto_editor/utils/bar.py,sha256=hG_NiYeuM90TdILzAJORft-UOS5grwWN3SbRuj6upsI,3998
|
43
|
+
auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,52
|
44
|
+
auto_editor/utils/cmdkw.py,sha256=aUGBvBel2Ko1o6Rwmr4rEL-BMc5hEnzYLbyZ1GeJdcY,5729
|
45
|
+
auto_editor/utils/container.py,sha256=Wf1ZL0tvXWl6m1B9mK_SkgVl89ilV_LpwlQq0TVroCc,2704
|
46
|
+
auto_editor/utils/func.py,sha256=kB-pNDn20M6YT7sljyd_auve5teK-E2G4TgwVOAIuJw,2754
|
47
|
+
auto_editor/utils/log.py,sha256=C1b-vnszSsohMd5fyaRcCuf0OPobZVMkV77cP-_JNP4,3776
|
48
|
+
auto_editor/utils/types.py,sha256=7BF7R7DA5eKmtI6f5ia7bOYNL0u_2sviiPsE1VmP0lc,10724
|
49
|
+
docs/build.py,sha256=CM-ZWgQk8wSNjivx_-6wGIaG7cstrNKsX2d4TzFVivE,1642
|
50
|
+
auto_editor-26.1.0.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
|
51
|
+
auto_editor-26.1.0.dist-info/METADATA,sha256=QDfveFTnxTtnA2WqZTvgM4vNGQ1sTnziQ2MSAyqF5WQ,6109
|
52
|
+
auto_editor-26.1.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
53
|
+
auto_editor-26.1.0.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
|
54
|
+
auto_editor-26.1.0.dist-info/top_level.txt,sha256=jBV5zlbWRbKOa-xaWPvTD45QL7lGExx2BDzv-Ji4dTw,17
|
55
|
+
auto_editor-26.1.0.dist-info/RECORD,,
|