auto-editor 25.3.1__py3-none-any.whl → 26.0.1__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 +6 -33
- auto_editor/edit.py +146 -52
- auto_editor/ffwrapper.py +19 -81
- auto_editor/formats/fcp7.py +1 -1
- auto_editor/help.py +4 -3
- auto_editor/lang/palet.py +3 -9
- auto_editor/lang/stdenv.py +0 -7
- auto_editor/output.py +25 -183
- auto_editor/render/audio.py +150 -58
- auto_editor/render/subtitle.py +71 -10
- auto_editor/render/video.py +167 -182
- auto_editor/subcommands/repl.py +12 -3
- auto_editor/subcommands/test.py +42 -38
- auto_editor/timeline.py +2 -2
- auto_editor/utils/cmdkw.py +5 -8
- auto_editor/utils/container.py +4 -5
- auto_editor/utils/func.py +2 -35
- auto_editor/utils/types.py +4 -30
- {auto_editor-25.3.1.dist-info → auto_editor-26.0.1.dist-info}/METADATA +1 -2
- {auto_editor-25.3.1.dist-info → auto_editor-26.0.1.dist-info}/RECORD +25 -26
- {auto_editor-25.3.1.dist-info → auto_editor-26.0.1.dist-info}/WHEEL +1 -1
- auto_editor/utils/encoder.py +0 -135
- {auto_editor-25.3.1.dist-info → auto_editor-26.0.1.dist-info}/LICENSE +0 -0
- {auto_editor-25.3.1.dist-info → auto_editor-26.0.1.dist-info}/entry_points.txt +0 -0
- {auto_editor-25.3.1.dist-info → auto_editor-26.0.1.dist-info}/top_level.txt +0 -0
auto_editor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "
|
1
|
+
__version__ = "26.0.1"
|
auto_editor/__main__.py
CHANGED
@@ -8,16 +8,15 @@ from subprocess import run
|
|
8
8
|
|
9
9
|
import auto_editor
|
10
10
|
from auto_editor.edit import edit_media
|
11
|
-
from auto_editor.ffwrapper import FFmpeg
|
11
|
+
from auto_editor.ffwrapper import FFmpeg
|
12
12
|
from auto_editor.utils.func import get_stdout
|
13
13
|
from auto_editor.utils.log import Log
|
14
14
|
from auto_editor.utils.types import (
|
15
15
|
Args,
|
16
|
-
bitrate,
|
17
|
-
color,
|
18
16
|
frame_rate,
|
19
17
|
margin,
|
20
18
|
number,
|
19
|
+
parse_color,
|
21
20
|
resolution,
|
22
21
|
sample_rate,
|
23
22
|
speed,
|
@@ -109,7 +108,7 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
109
108
|
parser.add_argument(
|
110
109
|
"--background",
|
111
110
|
"-b",
|
112
|
-
type=
|
111
|
+
type=parse_color,
|
113
112
|
metavar="COLOR",
|
114
113
|
help="Set the background as a solid RGB color",
|
115
114
|
)
|
@@ -167,11 +166,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
167
166
|
metavar="PATH",
|
168
167
|
help="Set a custom path to the ffmpeg location",
|
169
168
|
)
|
170
|
-
parser.add_argument(
|
171
|
-
"--my-ffmpeg",
|
172
|
-
flag=True,
|
173
|
-
help="Use the ffmpeg on your PATH instead of the one packaged",
|
174
|
-
)
|
175
169
|
parser.add_text("Display Options:")
|
176
170
|
parser.add_argument(
|
177
171
|
"--progress",
|
@@ -180,12 +174,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
180
174
|
help="Set what type of progress bar to use",
|
181
175
|
)
|
182
176
|
parser.add_argument("--debug", flag=True, help="Show debugging messages and values")
|
183
|
-
parser.add_argument(
|
184
|
-
"--show-ffmpeg-commands", flag=True, help="Show ffmpeg commands"
|
185
|
-
)
|
186
|
-
parser.add_argument(
|
187
|
-
"--show-ffmpeg-output", flag=True, help="Show ffmpeg stdout and stderr"
|
188
|
-
)
|
189
177
|
parser.add_argument("--quiet", "-q", flag=True, help="Display less output")
|
190
178
|
parser.add_argument(
|
191
179
|
"--preview",
|
@@ -205,16 +193,8 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
205
193
|
"--video-bitrate",
|
206
194
|
"-b:v",
|
207
195
|
metavar="BITRATE",
|
208
|
-
type=bitrate,
|
209
196
|
help="Set the number of bits per second for video",
|
210
197
|
)
|
211
|
-
parser.add_argument(
|
212
|
-
"--video-quality-scale",
|
213
|
-
"-qscale:v",
|
214
|
-
"-q:v",
|
215
|
-
metavar="SCALE",
|
216
|
-
help="Set a value to the ffmpeg option -qscale:v",
|
217
|
-
)
|
218
198
|
parser.add_argument(
|
219
199
|
"--scale",
|
220
200
|
type=number,
|
@@ -238,7 +218,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
238
218
|
"--audio-bitrate",
|
239
219
|
"-b:a",
|
240
220
|
metavar="BITRATE",
|
241
|
-
type=bitrate,
|
242
221
|
help="Set the number of bits per second for audio",
|
243
222
|
)
|
244
223
|
parser.add_argument(
|
@@ -281,7 +260,7 @@ def download_video(my_input: str, args: Args, ffmpeg: FFmpeg, log: Log) -> str:
|
|
281
260
|
log.conwrite("Downloading video...")
|
282
261
|
|
283
262
|
def get_domain(url: str) -> str:
|
284
|
-
t = __import__("urllib").
|
263
|
+
t = __import__("urllib.parse", fromlist=["parse"]).urlparse(url).netloc
|
285
264
|
return ".".join(t.split(".")[-2:])
|
286
265
|
|
287
266
|
download_format = args.download_format
|
@@ -295,7 +274,7 @@ def download_video(my_input: str, args: Args, ffmpeg: FFmpeg, log: Log) -> str:
|
|
295
274
|
|
296
275
|
yt_dlp_path = args.yt_dlp_location
|
297
276
|
|
298
|
-
cmd = ["--ffmpeg-location", ffmpeg.
|
277
|
+
cmd = ["--ffmpeg-location", ffmpeg.get_path("yt-dlp", log)]
|
299
278
|
|
300
279
|
if download_format is not None:
|
301
280
|
cmd.extend(["-f", download_format])
|
@@ -373,13 +352,7 @@ def main() -> None:
|
|
373
352
|
is_machine = args.progress == "machine"
|
374
353
|
log = Log(args.debug, args.quiet, args.temp_dir, is_machine, no_color)
|
375
354
|
|
376
|
-
ffmpeg =
|
377
|
-
log,
|
378
|
-
args.ffmpeg_location,
|
379
|
-
args.my_ffmpeg,
|
380
|
-
args.show_ffmpeg_commands,
|
381
|
-
args.show_ffmpeg_output,
|
382
|
-
)
|
355
|
+
ffmpeg = FFmpeg(args.ffmpeg_location)
|
383
356
|
paths = []
|
384
357
|
for my_input in args.input:
|
385
358
|
if my_input.startswith("http://") or my_input.startswith("https://"):
|
auto_editor/edit.py
CHANGED
@@ -2,13 +2,18 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
import sys
|
5
|
+
from fractions import Fraction
|
6
|
+
from os.path import splitext
|
5
7
|
from subprocess import run
|
6
8
|
from typing import Any
|
7
9
|
|
10
|
+
import av
|
11
|
+
from av import AudioResampler
|
12
|
+
|
8
13
|
from auto_editor.ffwrapper import FFmpeg, FileInfo, initFileInfo
|
9
14
|
from auto_editor.lib.contracts import is_int, is_str
|
10
|
-
from auto_editor.make_layers import make_timeline
|
11
|
-
from auto_editor.output import Ensure,
|
15
|
+
from auto_editor.make_layers import clipify, make_av, make_timeline
|
16
|
+
from auto_editor.output import Ensure, parse_bitrate
|
12
17
|
from auto_editor.render.audio import make_new_audio
|
13
18
|
from auto_editor.render.subtitle import make_new_subtitles
|
14
19
|
from auto_editor.render.video import render_av
|
@@ -27,7 +32,7 @@ def set_output(
|
|
27
32
|
if src is None:
|
28
33
|
root, ext = "out", ".mp4"
|
29
34
|
else:
|
30
|
-
root, ext =
|
35
|
+
root, ext = splitext(src.path if out is None else out)
|
31
36
|
if ext == "":
|
32
37
|
ext = src.path.suffix
|
33
38
|
|
@@ -92,11 +97,19 @@ def set_audio_codec(
|
|
92
97
|
codec: str, src: FileInfo | None, out_ext: str, ctr: Container, log: Log
|
93
98
|
) -> str:
|
94
99
|
if codec == "auto":
|
95
|
-
|
100
|
+
if src is None or not src.audios:
|
101
|
+
codec = "aac"
|
102
|
+
else:
|
103
|
+
codec = src.audios[0].codec
|
104
|
+
ctx = av.Codec(codec)
|
105
|
+
if ctx.audio_formats is None:
|
106
|
+
codec = "aac"
|
96
107
|
if codec not in ctr.acodecs and ctr.default_aud != "none":
|
97
|
-
|
108
|
+
codec = ctr.default_aud
|
98
109
|
if codec == "mp3float":
|
99
|
-
|
110
|
+
codec = "mp3"
|
111
|
+
if codec is None:
|
112
|
+
codec = "aac"
|
100
113
|
return codec
|
101
114
|
|
102
115
|
if codec == "copy":
|
@@ -106,9 +119,8 @@ def set_audio_codec(
|
|
106
119
|
log.error("Input file does not have an audio stream to copy codec from.")
|
107
120
|
codec = src.audios[0].codec
|
108
121
|
|
109
|
-
if codec
|
110
|
-
|
111
|
-
log.error(codec_error.format(codec, out_ext))
|
122
|
+
if ctr.acodecs is None or codec not in ctr.acodecs:
|
123
|
+
log.error(codec_error.format(codec, out_ext))
|
112
124
|
|
113
125
|
return codec
|
114
126
|
|
@@ -153,7 +165,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
153
165
|
tl = None
|
154
166
|
|
155
167
|
if paths:
|
156
|
-
path_ext =
|
168
|
+
path_ext = splitext(paths[0])[1].lower()
|
157
169
|
if path_ext == ".xml":
|
158
170
|
from auto_editor.formats.fcp7 import fcp7_read_xml
|
159
171
|
|
@@ -232,7 +244,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
232
244
|
from auto_editor.formats.fcp7 import fcp7_write_xml
|
233
245
|
|
234
246
|
is_resolve = export.startswith("resolve")
|
235
|
-
fcp7_write_xml(export_ops["name"], output, is_resolve, tl
|
247
|
+
fcp7_write_xml(export_ops["name"], output, is_resolve, tl)
|
236
248
|
return
|
237
249
|
|
238
250
|
if export == "final-cut-pro":
|
@@ -256,7 +268,7 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
256
268
|
shotcut_write_mlt(output, tl)
|
257
269
|
return
|
258
270
|
|
259
|
-
out_ext =
|
271
|
+
out_ext = splitext(output)[1].replace(".", "")
|
260
272
|
|
261
273
|
# Check if export options make sense.
|
262
274
|
ctr = container_constructor(out_ext.lower())
|
@@ -270,62 +282,144 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
|
|
270
282
|
if args.keep_tracks_separate and ctr.max_audios == 1:
|
271
283
|
log.warning(f"'{out_ext}' container doesn't support multiple audio tracks.")
|
272
284
|
|
273
|
-
def make_media(tl: v3,
|
285
|
+
def make_media(tl: v3, output_path: str) -> None:
|
274
286
|
assert src is not None
|
275
287
|
|
276
|
-
|
277
|
-
audio_output = []
|
278
|
-
sub_output = []
|
279
|
-
apply_later = False
|
288
|
+
output = av.open(output_path, "w")
|
280
289
|
|
281
|
-
ensure = Ensure(ffmpeg, bar, samplerate, log)
|
282
290
|
if ctr.default_sub != "none" and not args.sn:
|
283
|
-
|
291
|
+
sub_paths = make_new_subtitles(tl, log)
|
292
|
+
else:
|
293
|
+
sub_paths = []
|
284
294
|
|
285
295
|
if ctr.default_aud != "none":
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
296
|
+
ensure = Ensure(bar, samplerate, log)
|
297
|
+
audio_paths = make_new_audio(tl, ctr, ensure, args, ffmpeg, bar, log)
|
298
|
+
else:
|
299
|
+
audio_paths = []
|
300
|
+
|
301
|
+
# Setup audio
|
302
|
+
if audio_paths:
|
303
|
+
try:
|
304
|
+
audio_encoder = av.Codec(args.audio_codec)
|
305
|
+
except av.FFmpegError as e:
|
306
|
+
log.error(e)
|
307
|
+
if audio_encoder.audio_formats is None:
|
308
|
+
log.error(f"{args.audio_codec}: No known audio formats avail.")
|
309
|
+
audio_format = audio_encoder.audio_formats[0]
|
310
|
+
resampler = AudioResampler(format=audio_format, layout="stereo", rate=tl.sr)
|
311
|
+
|
312
|
+
audio_streams: list[av.AudioStream] = []
|
313
|
+
audio_inputs = []
|
314
|
+
audio_gen_frames = []
|
315
|
+
for i, audio_path in enumerate(audio_paths):
|
316
|
+
audio_stream = output.add_stream(
|
317
|
+
args.audio_codec,
|
318
|
+
format=audio_format,
|
319
|
+
rate=tl.sr,
|
320
|
+
time_base=Fraction(1, tl.sr),
|
321
|
+
)
|
322
|
+
if not isinstance(audio_stream, av.AudioStream):
|
323
|
+
log.error(f"Not a known audio codec: {args.audio_codec}")
|
324
|
+
|
325
|
+
if args.audio_bitrate != "auto":
|
326
|
+
audio_stream.bit_rate = parse_bitrate(args.audio_bitrate, log)
|
327
|
+
log.debug(f"audio bitrate: {audio_stream.bit_rate}")
|
328
|
+
else:
|
329
|
+
log.debug(f"[auto] audio bitrate: {audio_stream.bit_rate}")
|
330
|
+
if i < len(src.audios) and src.audios[i].lang is not None:
|
331
|
+
audio_stream.metadata["language"] = src.audios[i].lang # type: ignore
|
332
|
+
|
333
|
+
audio_streams.append(audio_stream)
|
334
|
+
audio_input = av.open(audio_path)
|
335
|
+
audio_inputs.append(audio_input)
|
336
|
+
audio_gen_frames.append(audio_input.decode(audio=0))
|
337
|
+
|
338
|
+
# Setup subtitles
|
339
|
+
subtitle_streams = []
|
340
|
+
subtitle_inputs = []
|
341
|
+
sub_gen_frames = []
|
342
|
+
|
343
|
+
for i, sub_path in enumerate(sub_paths):
|
344
|
+
subtitle_input = av.open(sub_path)
|
345
|
+
subtitle_inputs.append(subtitle_input)
|
346
|
+
subtitle_stream = output.add_stream(
|
347
|
+
template=subtitle_input.streams.subtitles[0]
|
348
|
+
)
|
349
|
+
if i < len(src.subtitles) and src.subtitles[i].lang is not None:
|
350
|
+
subtitle_stream.metadata["language"] = src.subtitles[i].lang # type: ignore
|
351
|
+
|
352
|
+
subtitle_streams.append(subtitle_stream)
|
353
|
+
sub_gen_frames.append(subtitle_input.demux(subtitles=0))
|
354
|
+
|
355
|
+
# Setup video
|
356
|
+
if ctr.default_vid != "none" and tl.v:
|
357
|
+
vframes = render_av(output, tl, args, bar, log)
|
358
|
+
output_stream = next(vframes)
|
359
|
+
else:
|
360
|
+
output_stream, vframes = None, iter([])
|
361
|
+
|
362
|
+
# Process frames
|
363
|
+
while True:
|
364
|
+
audio_frames = [next(frames, None) for frames in audio_gen_frames]
|
365
|
+
video_frame = next(vframes, None)
|
366
|
+
subtitle_frames = [next(packet, None) for packet in sub_gen_frames]
|
367
|
+
|
368
|
+
if (
|
369
|
+
all(frame is None for frame in audio_frames)
|
370
|
+
and video_frame is None
|
371
|
+
and all(packet is None for packet in subtitle_frames)
|
372
|
+
):
|
373
|
+
break
|
374
|
+
|
375
|
+
for audio_stream, audio_frame in zip(audio_streams, audio_frames):
|
376
|
+
if audio_frame:
|
377
|
+
for reframe in resampler.resample(audio_frame):
|
378
|
+
output.mux(audio_stream.encode(reframe))
|
379
|
+
|
380
|
+
for subtitle_stream, packet in zip(subtitle_streams, subtitle_frames):
|
381
|
+
if not packet or packet.dts is None:
|
382
|
+
continue
|
383
|
+
packet.stream = subtitle_stream
|
384
|
+
output.mux(packet)
|
385
|
+
|
386
|
+
if video_frame:
|
387
|
+
try:
|
388
|
+
output.mux(output_stream.encode(video_frame))
|
389
|
+
except av.error.ExternalError:
|
390
|
+
log.error(
|
391
|
+
f"Generic error for encoder: {output_stream.name}\n"
|
392
|
+
"Perhaps video quality settings are too low?"
|
393
|
+
)
|
394
|
+
except av.FFmpegError as e:
|
395
|
+
log.error(e)
|
396
|
+
|
397
|
+
# Flush streams
|
398
|
+
if output_stream is not None:
|
399
|
+
output.mux(output_stream.encode(None))
|
400
|
+
for audio_stream in audio_streams:
|
401
|
+
output.mux(audio_stream.encode(None))
|
402
|
+
|
403
|
+
# Close resources
|
404
|
+
for audio_input in audio_inputs:
|
405
|
+
audio_input.close()
|
406
|
+
for subtitle_input in subtitle_inputs:
|
407
|
+
subtitle_input.close()
|
408
|
+
output.close()
|
316
409
|
|
317
410
|
if export == "clip-sequence":
|
318
411
|
if tl.v1 is None:
|
319
412
|
log.error("Timeline too complex to use clip-sequence export")
|
320
413
|
|
321
|
-
from auto_editor.make_layers import clipify, make_av
|
322
|
-
from auto_editor.utils.func import append_filename
|
323
|
-
|
324
414
|
def pad_chunk(chunk: Chunk, total: int) -> Chunks:
|
325
415
|
start = [] if chunk[0] == 0 else [(0, chunk[0], 99999.0)]
|
326
416
|
end = [] if chunk[1] == total else [(chunk[1], total, 99999.0)]
|
327
417
|
return start + [chunk] + end
|
328
418
|
|
419
|
+
def append_filename(path: str, val: str) -> str:
|
420
|
+
root, ext = splitext(path)
|
421
|
+
return root + val + ext
|
422
|
+
|
329
423
|
total_frames = tl.v1.chunks[-1][1] - 1
|
330
424
|
clip_num = 0
|
331
425
|
for chunk in tl.v1.chunks:
|
auto_editor/ffwrapper.py
CHANGED
@@ -1,98 +1,40 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import os.path
|
4
|
-
import sys
|
5
3
|
from dataclasses import dataclass
|
6
4
|
from fractions import Fraction
|
7
5
|
from pathlib import Path
|
8
|
-
from re import search
|
9
6
|
from shutil import which
|
10
|
-
from subprocess import PIPE, Popen
|
11
|
-
from typing import Any
|
7
|
+
from subprocess import PIPE, Popen
|
12
8
|
|
13
9
|
import av
|
14
10
|
|
15
11
|
from auto_editor.utils.log import Log
|
16
12
|
|
17
13
|
|
18
|
-
def
|
19
|
-
|
20
|
-
)
|
21
|
-
|
22
|
-
|
23
|
-
elif my_ffmpeg:
|
24
|
-
program = "ffmpeg"
|
25
|
-
else:
|
26
|
-
try:
|
27
|
-
import ae_ffmpeg
|
28
|
-
|
29
|
-
program = ae_ffmpeg.get_path()
|
30
|
-
except ImportError:
|
31
|
-
program = "ffmpeg"
|
32
|
-
|
33
|
-
path: str | None = which(program)
|
34
|
-
if path is None:
|
35
|
-
log.error("Did not find ffmpeg on PATH.")
|
36
|
-
|
37
|
-
return FFmpeg(log, path, show_cmd, debug)
|
14
|
+
def _get_ffmpeg(reason: str, ffloc: str | None, log: Log) -> str:
|
15
|
+
program = "ffmpeg" if ffloc is None else ffloc
|
16
|
+
if (path := which(program)) is None:
|
17
|
+
log.error(f"{reason} needs ffmpeg cli but couldn't find ffmpeg on PATH.")
|
18
|
+
return path
|
38
19
|
|
39
20
|
|
40
21
|
@dataclass(slots=True)
|
41
22
|
class FFmpeg:
|
42
|
-
|
43
|
-
path: str
|
44
|
-
show_cmd: bool
|
45
|
-
debug: bool
|
46
|
-
|
47
|
-
def run(self, cmd: list[str]) -> None:
|
48
|
-
cmd = [self.path, "-hide_banner", "-y"] + cmd
|
49
|
-
if not self.debug:
|
50
|
-
cmd.extend(["-nostats", "-loglevel", "error"])
|
51
|
-
if self.show_cmd:
|
52
|
-
sys.stderr.write(f"{' '.join(cmd)}\n\n")
|
53
|
-
run(cmd)
|
54
|
-
|
55
|
-
def run_check_errors(
|
56
|
-
self, cmd: list[str], show_out: bool = False, path: str | None = None
|
57
|
-
) -> None:
|
58
|
-
process = self.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
59
|
-
_, stderr = process.communicate()
|
60
|
-
|
61
|
-
if process.stdin is not None:
|
62
|
-
process.stdin.close()
|
63
|
-
output = stderr.decode("utf-8", "replace")
|
64
|
-
|
65
|
-
error_list = (
|
66
|
-
r"Unknown encoder '.*'",
|
67
|
-
r"-q:v qscale not available for encoder\. Use -b:v bitrate instead\.",
|
68
|
-
r"Specified sample rate .* is not supported",
|
69
|
-
r'Unable to parse option value ".*"',
|
70
|
-
r"Error setting option .* to value .*\.",
|
71
|
-
r"Undefined constant or missing '.*' in '.*'",
|
72
|
-
r"DLL .* failed to open",
|
73
|
-
r"Incompatible pixel format '.*' for codec '[A-Za-z0-9_]*'",
|
74
|
-
r"Unrecognized option '.*'",
|
75
|
-
r"Permission denied",
|
76
|
-
)
|
23
|
+
ffmpeg_location: str | None
|
24
|
+
path: str | None = None
|
77
25
|
|
78
|
-
|
79
|
-
|
26
|
+
def get_path(self, reason: str, log: Log) -> str:
|
27
|
+
if self.path is not None:
|
28
|
+
return self.path
|
80
29
|
|
81
|
-
|
82
|
-
|
83
|
-
self.log.error(check.group())
|
30
|
+
self.path = _get_ffmpeg(reason, self.ffmpeg_location, log)
|
31
|
+
return self.path
|
84
32
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
print(f"stderr: {output}")
|
33
|
+
def Popen(self, reason: str, cmd: list[str], log: Log) -> Popen:
|
34
|
+
if self.path is None:
|
35
|
+
self.path = _get_ffmpeg(reason, self.ffmpeg_location, log)
|
89
36
|
|
90
|
-
|
91
|
-
self, cmd: list[str], stdin: Any = None, stdout: Any = PIPE, stderr: Any = None
|
92
|
-
) -> Popen:
|
93
|
-
if self.show_cmd:
|
94
|
-
sys.stderr.write(f"{self.path} {' '.join(cmd)}\n\n")
|
95
|
-
return Popen([self.path] + cmd, stdin=stdin, stdout=stdout, stderr=stderr)
|
37
|
+
return Popen([self.path] + cmd, stdout=PIPE, stderr=PIPE)
|
96
38
|
|
97
39
|
|
98
40
|
def mux(input: Path, output: Path, stream: int) -> None:
|
@@ -103,13 +45,9 @@ def mux(input: Path, output: Path, stream: int) -> None:
|
|
103
45
|
output_audio_stream = output_container.add_stream("pcm_s16le")
|
104
46
|
|
105
47
|
for frame in input_container.decode(input_audio_stream):
|
106
|
-
|
107
|
-
if packet:
|
108
|
-
output_container.mux(packet)
|
48
|
+
output_container.mux(output_audio_stream.encode(frame))
|
109
49
|
|
110
|
-
|
111
|
-
if packet:
|
112
|
-
output_container.mux(packet)
|
50
|
+
output_container.mux(output_audio_stream.encode(None))
|
113
51
|
|
114
52
|
output_container.close()
|
115
53
|
input_container.close()
|
auto_editor/formats/fcp7.py
CHANGED
@@ -485,7 +485,7 @@ def premiere_write_audio(audio: Element, make_filedef, src: FileInfo, tl: v3) ->
|
|
485
485
|
audio.append(track)
|
486
486
|
|
487
487
|
|
488
|
-
def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3
|
488
|
+
def fcp7_write_xml(name: str, output: str, resolve: bool, tl: v3) -> None:
|
489
489
|
width, height = tl.res
|
490
490
|
timebase, ntsc = set_tb_ntsc(tl.tb)
|
491
491
|
|
auto_editor/help.py
CHANGED
@@ -148,11 +148,12 @@ Beware that the temp directory can get quite big.
|
|
148
148
|
"--my-ffmpeg": "This is equivalent to `--ffmpeg-location ffmpeg`.",
|
149
149
|
"--audio-bitrate": """
|
150
150
|
`--audio-bitrate` sets the target bitrate for the audio encoder.
|
151
|
-
|
152
|
-
|
151
|
+
By default, the value is `auto` (let the encoder decide).
|
152
|
+
It can be set to a natural number with units: ``, `k`, `K`, `M`, or `G`.
|
153
|
+
|
153
154
|
""".strip(),
|
154
155
|
"--video-bitrate": """
|
155
|
-
`--video-bitrate` sets the target bitrate for the video encoder. It accepts the same format as `--audio-bitrate`
|
156
|
+
`--video-bitrate` sets the target bitrate for the video encoder. `auto` is set as the default. It accepts the same format as `--audio-bitrate`
|
156
157
|
""".strip(),
|
157
158
|
"--margin": """
|
158
159
|
Default value: 0.2s,0.2s
|
auto_editor/lang/palet.py
CHANGED
@@ -353,9 +353,7 @@ class Lexer:
|
|
353
353
|
if is_method:
|
354
354
|
from auto_editor.utils.cmdkw import parse_method
|
355
355
|
|
356
|
-
return Token(
|
357
|
-
M, parse_method(name, result, env), self.lineno, self.column
|
358
|
-
)
|
356
|
+
return Token(M, parse_method(name, result), self.lineno, self.column)
|
359
357
|
|
360
358
|
if self.char == ".": # handle `object.method` syntax
|
361
359
|
self.advance()
|
@@ -635,6 +633,8 @@ def edit_subtitle(pattern, stream=0, **kwargs):
|
|
635
633
|
|
636
634
|
|
637
635
|
class StackTraceManager:
|
636
|
+
__slots__ = ("stack",)
|
637
|
+
|
638
638
|
def __init__(self) -> None:
|
639
639
|
self.stack: list[Sym] = []
|
640
640
|
|
@@ -645,12 +645,6 @@ class StackTraceManager:
|
|
645
645
|
if self.stack:
|
646
646
|
self.stack.pop()
|
647
647
|
|
648
|
-
def get_stacktrace(self) -> str:
|
649
|
-
return "\n".join(
|
650
|
-
f" at {sym.val} ({sym.lineno}:{sym.column})"
|
651
|
-
for sym in reversed(self.stack)
|
652
|
-
)
|
653
|
-
|
654
648
|
|
655
649
|
stack_trace_manager = StackTraceManager()
|
656
650
|
|
auto_editor/lang/stdenv.py
CHANGED
@@ -14,7 +14,6 @@ if TYPE_CHECKING:
|
|
14
14
|
from numpy.typing import NDArray
|
15
15
|
|
16
16
|
Number = int | float | complex | Fraction
|
17
|
-
Real = int | float | Fraction
|
18
17
|
BoolList = NDArray[np.bool_]
|
19
18
|
Node = tuple
|
20
19
|
|
@@ -831,12 +830,6 @@ def make_standard_env() -> dict[str, Any]:
|
|
831
830
|
check_args("xor", vals, (2, None), (is_bool,))
|
832
831
|
return reduce(lambda a, b: a ^ b, vals)
|
833
832
|
|
834
|
-
def string_ref(s: str, ref: int) -> Char:
|
835
|
-
try:
|
836
|
-
return Char(s[ref])
|
837
|
-
except IndexError:
|
838
|
-
raise MyError(f"string index {ref} is out of range")
|
839
|
-
|
840
833
|
def number_to_string(val: Number) -> str:
|
841
834
|
if isinstance(val, complex):
|
842
835
|
join = "" if val.imag < 0 else "+"
|