auto-editor 25.3.1__py3-none-any.whl → 26.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
auto_editor/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "25.3.1"
1
+ __version__ = "26.0.0"
auto_editor/__main__.py CHANGED
@@ -13,7 +13,6 @@ 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
16
  color,
18
17
  frame_rate,
19
18
  margin,
@@ -205,16 +204,8 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
205
204
  "--video-bitrate",
206
205
  "-b:v",
207
206
  metavar="BITRATE",
208
- type=bitrate,
209
207
  help="Set the number of bits per second for video",
210
208
  )
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
209
  parser.add_argument(
219
210
  "--scale",
220
211
  type=number,
@@ -238,7 +229,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
238
229
  "--audio-bitrate",
239
230
  "-b:a",
240
231
  metavar="BITRATE",
241
- type=bitrate,
242
232
  help="Set the number of bits per second for audio",
243
233
  )
244
234
  parser.add_argument(
@@ -281,7 +271,7 @@ def download_video(my_input: str, args: Args, ffmpeg: FFmpeg, log: Log) -> str:
281
271
  log.conwrite("Downloading video...")
282
272
 
283
273
  def get_domain(url: str) -> str:
284
- t = __import__("urllib").parse.urlparse(url).netloc
274
+ t = __import__("urllib.parse", fromlist=["parse"]).urlparse(url).netloc
285
275
  return ".".join(t.split(".")[-2:])
286
276
 
287
277
  download_format = args.download_format
auto_editor/edit.py CHANGED
@@ -2,13 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import sys
5
+ from fractions import Fraction
5
6
  from subprocess import run
6
7
  from typing import Any
7
8
 
9
+ import av
10
+ from av import AudioResampler
11
+
8
12
  from auto_editor.ffwrapper import FFmpeg, FileInfo, initFileInfo
9
13
  from auto_editor.lib.contracts import is_int, is_str
10
14
  from auto_editor.make_layers import make_timeline
11
- from auto_editor.output import Ensure, mux_quality_media
15
+ from auto_editor.output import Ensure, parse_bitrate
12
16
  from auto_editor.render.audio import make_new_audio
13
17
  from auto_editor.render.subtitle import make_new_subtitles
14
18
  from auto_editor.render.video import render_av
@@ -92,11 +96,19 @@ def set_audio_codec(
92
96
  codec: str, src: FileInfo | None, out_ext: str, ctr: Container, log: Log
93
97
  ) -> str:
94
98
  if codec == "auto":
95
- codec = "aac" if (src is None or not src.audios) else src.audios[0].codec
99
+ if src is None or not src.audios:
100
+ codec = "aac"
101
+ else:
102
+ codec = src.audios[0].codec
103
+ ctx = av.Codec(codec)
104
+ if ctx.audio_formats is None:
105
+ codec = "aac"
96
106
  if codec not in ctr.acodecs and ctr.default_aud != "none":
97
- return ctr.default_aud
107
+ codec = ctr.default_aud
98
108
  if codec == "mp3float":
99
- return "mp3"
109
+ codec = "mp3"
110
+ if codec is None:
111
+ codec = "aac"
100
112
  return codec
101
113
 
102
114
  if codec == "copy":
@@ -106,9 +118,8 @@ def set_audio_codec(
106
118
  log.error("Input file does not have an audio stream to copy codec from.")
107
119
  codec = src.audios[0].codec
108
120
 
109
- if codec != "unset":
110
- if ctr.acodecs is None or codec not in ctr.acodecs:
111
- log.error(codec_error.format(codec, out_ext))
121
+ if ctr.acodecs is None or codec not in ctr.acodecs:
122
+ log.error(codec_error.format(codec, out_ext))
112
123
 
113
124
  return codec
114
125
 
@@ -270,49 +281,150 @@ def edit_media(paths: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> None:
270
281
  if args.keep_tracks_separate and ctr.max_audios == 1:
271
282
  log.warning(f"'{out_ext}' container doesn't support multiple audio tracks.")
272
283
 
273
- def make_media(tl: v3, output: str) -> None:
284
+ def make_media(tl: v3, output_path: str) -> None:
274
285
  assert src is not None
275
286
 
276
- visual_output = []
277
- audio_output = []
278
- sub_output = []
279
- apply_later = False
287
+ output = av.open(output_path, "w")
280
288
 
281
- ensure = Ensure(ffmpeg, bar, samplerate, log)
282
289
  if ctr.default_sub != "none" and not args.sn:
283
- sub_output = make_new_subtitles(tl, ensure, log.temp)
290
+ sub_paths = make_new_subtitles(tl, log)
291
+ else:
292
+ sub_paths = []
284
293
 
285
294
  if ctr.default_aud != "none":
286
- audio_output = make_new_audio(tl, ensure, args, ffmpeg, bar, log)
287
-
288
- if ctr.default_vid != "none":
289
- if tl.v:
290
- out_path, apply_later = render_av(ffmpeg, tl, args, bar, ctr, log)
291
- visual_output.append((True, out_path))
292
-
293
- for v, vid in enumerate(src.videos, start=1):
294
- if ctr.allow_image and vid.codec in ("png", "mjpeg", "webp"):
295
- out_path = os.path.join(log.temp, f"{v}.{vid.codec}")
296
- # fmt: off
297
- ffmpeg.run(["-i", f"{src.path}", "-map", "0:v", "-map", "-0:V",
298
- "-c", "copy", out_path])
299
- # fmt: on
300
- visual_output.append((False, out_path))
301
-
302
- log.conwrite("Writing output file")
303
- mux_quality_media(
304
- ffmpeg,
305
- visual_output,
306
- audio_output,
307
- sub_output,
308
- apply_later,
309
- ctr,
310
- output,
311
- tl.tb,
312
- args,
313
- src,
314
- log,
315
- )
295
+ ensure = Ensure(bar, samplerate, log)
296
+ audio_paths = make_new_audio(tl, ensure, args, ffmpeg, bar, log)
297
+ if (
298
+ not (args.keep_tracks_separate and ctr.max_audios is None)
299
+ and len(audio_paths) > 1
300
+ ):
301
+ # Merge all the audio a_tracks into one.
302
+ new_a_file = os.path.join(log.temp, "new_audio.wav")
303
+ new_cmd = []
304
+ for path in audio_paths:
305
+ new_cmd.extend(["-i", path])
306
+ new_cmd.extend(
307
+ [
308
+ "-filter_complex",
309
+ f"amix=inputs={len(audio_paths)}:duration=longest",
310
+ "-ac",
311
+ "2",
312
+ new_a_file,
313
+ ]
314
+ )
315
+ ffmpeg.run(new_cmd)
316
+ audio_paths = [new_a_file]
317
+ else:
318
+ audio_paths = []
319
+
320
+ # Setup audio
321
+ if audio_paths:
322
+ try:
323
+ audio_encoder = av.Codec(args.audio_codec)
324
+ except av.FFmpegError as e:
325
+ log.error(e)
326
+ if audio_encoder.audio_formats is None:
327
+ log.error(f"{args.audio_codec}: No known audio formats avail.")
328
+ audio_format = audio_encoder.audio_formats[0]
329
+ resampler = AudioResampler(format=audio_format, layout="stereo", rate=tl.sr)
330
+
331
+ audio_streams: list[av.AudioStream] = []
332
+ audio_inputs = []
333
+ audio_gen_frames = []
334
+ for i, audio_path in enumerate(audio_paths):
335
+ audio_stream = output.add_stream(
336
+ args.audio_codec,
337
+ format=audio_format,
338
+ rate=tl.sr,
339
+ time_base=Fraction(1, tl.sr),
340
+ )
341
+ if not isinstance(audio_stream, av.AudioStream):
342
+ log.error(f"Not a known audio codec: {args.audio_codec}")
343
+
344
+ if args.audio_bitrate != "auto":
345
+ audio_stream.bit_rate = parse_bitrate(args.audio_bitrate, log)
346
+ log.debug(f"audio bitrate: {audio_stream.bit_rate}")
347
+ else:
348
+ log.debug(f"[auto] audio bitrate: {audio_stream.bit_rate}")
349
+ if i < len(src.audios) and src.audios[i].lang is not None:
350
+ audio_stream.metadata["language"] = src.audios[i].lang # type: ignore
351
+
352
+ audio_streams.append(audio_stream)
353
+ audio_input = av.open(audio_path)
354
+ audio_inputs.append(audio_input)
355
+ audio_gen_frames.append(audio_input.decode(audio=0))
356
+
357
+ # Setup subtitles
358
+ subtitle_streams = []
359
+ subtitle_inputs = []
360
+ sub_gen_frames = []
361
+
362
+ for i, sub_path in enumerate(sub_paths):
363
+ subtitle_input = av.open(sub_path)
364
+ subtitle_inputs.append(subtitle_input)
365
+ subtitle_stream = output.add_stream(
366
+ template=subtitle_input.streams.subtitles[0]
367
+ )
368
+ if i < len(src.subtitles) and src.subtitles[i].lang is not None:
369
+ subtitle_stream.metadata["language"] = src.subtitles[i].lang # type: ignore
370
+
371
+ subtitle_streams.append(subtitle_stream)
372
+ sub_gen_frames.append(subtitle_input.demux(subtitles=0))
373
+
374
+ # Setup video
375
+ if ctr.default_vid != "none" and tl.v:
376
+ vframes = render_av(output, tl, args, bar, log)
377
+ output_stream = next(vframes)
378
+ else:
379
+ output_stream, vframes = None, iter([])
380
+
381
+ # Process frames
382
+ while True:
383
+ audio_frames = [next(frames, None) for frames in audio_gen_frames]
384
+ video_frame = next(vframes, None)
385
+ subtitle_frames = [next(packet, None) for packet in sub_gen_frames]
386
+
387
+ if (
388
+ all(frame is None for frame in audio_frames)
389
+ and video_frame is None
390
+ and all(packet is None for packet in subtitle_frames)
391
+ ):
392
+ break
393
+
394
+ for audio_stream, audio_frame in zip(audio_streams, audio_frames):
395
+ if audio_frame:
396
+ for reframe in resampler.resample(audio_frame):
397
+ output.mux(audio_stream.encode(reframe))
398
+
399
+ for subtitle_stream, packet in zip(subtitle_streams, subtitle_frames):
400
+ if not packet or packet.dts is None:
401
+ continue
402
+ packet.stream = subtitle_stream
403
+ output.mux(packet)
404
+
405
+ if video_frame:
406
+ try:
407
+ output.mux(output_stream.encode(video_frame))
408
+ except av.error.ExternalError:
409
+ log.error(
410
+ f"Generic error for encoder: {output_stream.name}\n"
411
+ "Perhaps video quality settings are too low?"
412
+ )
413
+ except av.FFmpegError as e:
414
+ log.error(e)
415
+
416
+ # Flush streams
417
+ if output_stream is not None:
418
+ output.mux(output_stream.encode(None))
419
+ for audio_stream in audio_streams:
420
+ output.mux(audio_stream.encode(None))
421
+
422
+ # Close resources
423
+ for audio_input in audio_inputs:
424
+ audio_input.close()
425
+ for subtitle_input in subtitle_inputs:
426
+ subtitle_input.close()
427
+ output.close()
316
428
 
317
429
  if export == "clip-sequence":
318
430
  if tl.v1 is None:
auto_editor/ffwrapper.py CHANGED
@@ -1,11 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os.path
4
3
  import sys
5
4
  from dataclasses import dataclass
6
5
  from fractions import Fraction
7
6
  from pathlib import Path
8
- from re import search
9
7
  from shutil import which
10
8
  from subprocess import PIPE, Popen, run
11
9
  from typing import Any
@@ -52,41 +50,6 @@ class FFmpeg:
52
50
  sys.stderr.write(f"{' '.join(cmd)}\n\n")
53
51
  run(cmd)
54
52
 
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
- )
77
-
78
- if self.debug:
79
- print(f"stderr: {output}")
80
-
81
- for item in error_list:
82
- if check := search(item, output):
83
- self.log.error(check.group())
84
-
85
- if path is not None and not os.path.isfile(path):
86
- self.log.error(f"The file {path} was not created.")
87
- if show_out and not self.debug:
88
- print(f"stderr: {output}")
89
-
90
53
  def Popen(
91
54
  self, cmd: list[str], stdin: Any = None, stdout: Any = PIPE, stderr: Any = None
92
55
  ) -> Popen:
@@ -103,13 +66,9 @@ def mux(input: Path, output: Path, stream: int) -> None:
103
66
  output_audio_stream = output_container.add_stream("pcm_s16le")
104
67
 
105
68
  for frame in input_container.decode(input_audio_stream):
106
- packet = output_audio_stream.encode(frame)
107
- if packet:
108
- output_container.mux(packet)
69
+ output_container.mux(output_audio_stream.encode(frame))
109
70
 
110
- packet = output_audio_stream.encode(None)
111
- if packet:
112
- output_container.mux(packet)
71
+ output_container.mux(output_audio_stream.encode(None))
113
72
 
114
73
  output_container.close()
115
74
  input_container.close()
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
- The value accepts a natural number and the units: ``, `k`, `K`, and `M`.
152
- The special value `unset` may also be used, and means: Don't pass any value to ffmpeg, let it choose a default bitrate.
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` and the special `unset` value is allowed.
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/output.py CHANGED
@@ -2,26 +2,37 @@ from __future__ import annotations
2
2
 
3
3
  import os.path
4
4
  from dataclasses import dataclass, field
5
- from fractions import Fraction
6
5
 
7
6
  import av
8
7
  from av.audio.resampler import AudioResampler
9
8
 
10
- from auto_editor.ffwrapper import FFmpeg, FileInfo
9
+ from auto_editor.ffwrapper import FileInfo
11
10
  from auto_editor.utils.bar import Bar
12
- from auto_editor.utils.container import Container
13
11
  from auto_editor.utils.log import Log
14
- from auto_editor.utils.types import Args
12
+ from auto_editor.utils.types import _split_num_str
13
+
14
+
15
+ def parse_bitrate(input_: str, log: Log) -> int:
16
+ val, unit = _split_num_str(input_)
17
+
18
+ if unit.lower() == "k":
19
+ return int(val * 1000)
20
+ if unit == "M":
21
+ return int(val * 1_000_000)
22
+ if unit == "G":
23
+ return int(val * 1_000_000_000)
24
+ if unit == "":
25
+ return int(val)
26
+
27
+ log.error(f"Unknown bitrate: {input_}")
15
28
 
16
29
 
17
30
  @dataclass(slots=True)
18
31
  class Ensure:
19
- _ffmpeg: FFmpeg
20
32
  _bar: Bar
21
33
  _sr: int
22
34
  log: Log
23
35
  _audios: list[tuple[FileInfo, int]] = field(default_factory=list)
24
- _subtitles: list[tuple[FileInfo, int, str]] = field(default_factory=list)
25
36
 
26
37
  def audio(self, src: FileInfo, stream: int) -> str:
27
38
  try:
@@ -52,193 +63,21 @@ class Ensure:
52
63
 
53
64
  bar.start(dur, "Extracting audio")
54
65
 
55
- # PyAV always uses "stereo" layout, which is what we want.
56
- output_astream = out_container.add_stream("pcm_s16le", rate=sample_rate)
57
- assert isinstance(output_astream, av.audio.stream.AudioStream)
58
-
66
+ output_astream = out_container.add_stream(
67
+ "pcm_s16le", layout="stereo", rate=sample_rate
68
+ )
59
69
  resampler = AudioResampler(format="s16", layout="stereo", rate=sample_rate)
60
70
  for i, frame in enumerate(in_container.decode(astream)):
61
71
  if i % 1500 == 0 and frame.time is not None:
62
72
  bar.tick(frame.time)
63
73
 
64
74
  for new_frame in resampler.resample(frame):
65
- for packet in output_astream.encode(new_frame):
66
- out_container.mux_one(packet)
75
+ out_container.mux(output_astream.encode(new_frame))
67
76
 
68
- for packet in output_astream.encode():
69
- out_container.mux_one(packet)
77
+ out_container.mux(output_astream.encode(None))
70
78
 
71
79
  out_container.close()
72
80
  in_container.close()
73
81
  bar.end()
74
82
 
75
83
  return out_path
76
-
77
- def subtitle(self, src: FileInfo, stream: int, ext: str) -> str:
78
- try:
79
- self._subtitles.index((src, stream, ext))
80
- first_time = False
81
- except ValueError:
82
- self._subtitles.append((src, stream, ext))
83
- first_time = True
84
-
85
- out_path = os.path.join(self.log.temp, f"{stream}s.{ext}")
86
-
87
- if first_time:
88
- self.log.debug(f"Making external subtitle: {out_path}")
89
- self.log.conwrite("Extracting subtitle")
90
- self._ffmpeg.run(["-i", f"{src.path}", "-map", f"0:s:{stream}", out_path])
91
-
92
- return out_path
93
-
94
-
95
- def _ffset(option: str, value: str | None) -> list[str]:
96
- if value is None or value == "unset" or value == "reserved":
97
- return []
98
- return [option] + [value]
99
-
100
-
101
- def video_quality(args: Args) -> list[str]:
102
- return (
103
- _ffset("-b:v", args.video_bitrate)
104
- + ["-c:v", args.video_codec]
105
- + _ffset("-qscale:v", args.video_quality_scale)
106
- + ["-movflags", "faststart"]
107
- )
108
-
109
-
110
- def mux_quality_media(
111
- ffmpeg: FFmpeg,
112
- visual_output: list[tuple[bool, str]],
113
- audio_output: list[str],
114
- sub_output: list[str],
115
- apply_v: bool,
116
- ctr: Container,
117
- output_path: str,
118
- tb: Fraction,
119
- args: Args,
120
- src: FileInfo,
121
- log: Log,
122
- ) -> None:
123
- v_tracks = len(visual_output)
124
- a_tracks = len(audio_output)
125
- s_tracks = 0 if args.sn else len(sub_output)
126
-
127
- cmd = ["-hide_banner", "-y", "-i", f"{src.path}"]
128
-
129
- same_container = src.path.suffix == os.path.splitext(output_path)[1]
130
-
131
- for is_video, path in visual_output:
132
- if is_video or ctr.allow_image:
133
- cmd.extend(["-i", path])
134
- else:
135
- v_tracks -= 1
136
-
137
- if a_tracks > 0:
138
- if args.keep_tracks_separate and ctr.max_audios is None:
139
- for path in audio_output:
140
- cmd.extend(["-i", path])
141
- else:
142
- # Merge all the audio a_tracks into one.
143
- new_a_file = os.path.join(log.temp, "new_audio.wav")
144
- if a_tracks > 1:
145
- new_cmd = []
146
- for path in audio_output:
147
- new_cmd.extend(["-i", path])
148
- new_cmd.extend(
149
- [
150
- "-filter_complex",
151
- f"amix=inputs={a_tracks}:duration=longest",
152
- "-ac",
153
- "2",
154
- new_a_file,
155
- ]
156
- )
157
- ffmpeg.run(new_cmd)
158
- a_tracks = 1
159
- else:
160
- new_a_file = audio_output[0]
161
- cmd.extend(["-i", new_a_file])
162
-
163
- for subfile in sub_output:
164
- cmd.extend(["-i", subfile])
165
-
166
- for i in range(v_tracks + s_tracks + a_tracks):
167
- cmd.extend(["-map", f"{i+1}:0"])
168
-
169
- cmd.extend(["-map_metadata", "0"])
170
-
171
- track = 0
172
- for is_video, path in visual_output:
173
- if is_video:
174
- if apply_v:
175
- cmd += video_quality(args)
176
- else:
177
- # Real video is only allowed on track 0
178
- cmd += ["-c:v:0", "copy"]
179
-
180
- if float(tb).is_integer():
181
- cmd += ["-video_track_timescale", f"{tb}"]
182
-
183
- elif ctr.allow_image:
184
- ext = os.path.splitext(path)[1][1:]
185
- cmd += [f"-c:v:{track}", ext, f"-disposition:v:{track}", "attached_pic"]
186
-
187
- track += 1
188
- del track
189
-
190
- for i, vstream in enumerate(src.videos):
191
- if i > v_tracks:
192
- break
193
- if vstream.lang is not None:
194
- cmd.extend([f"-metadata:s:v:{i}", f"language={vstream.lang}"])
195
- for i, astream in enumerate(src.audios):
196
- if i > a_tracks:
197
- break
198
- if astream.lang is not None:
199
- cmd.extend([f"-metadata:s:a:{i}", f"language={astream.lang}"])
200
- for i, sstream in enumerate(src.subtitles):
201
- if i > s_tracks:
202
- break
203
- if sstream.lang is not None:
204
- cmd.extend([f"-metadata:s:s:{i}", f"language={sstream.lang}"])
205
-
206
- if s_tracks > 0:
207
- scodec = src.subtitles[0].codec
208
- if same_container:
209
- cmd.extend(["-c:s", scodec])
210
- elif ctr.scodecs is not None:
211
- if scodec not in ctr.scodecs:
212
- scodec = ctr.default_sub
213
- cmd.extend(["-c:s", scodec])
214
-
215
- if a_tracks > 0:
216
- cmd += _ffset("-c:a", args.audio_codec) + _ffset("-b:a", args.audio_bitrate)
217
-
218
- if same_container and v_tracks > 0:
219
- color_range = src.videos[0].color_range
220
- colorspace = src.videos[0].color_space
221
- color_prim = src.videos[0].color_primaries
222
- color_trc = src.videos[0].color_transfer
223
-
224
- if color_range == 1 or color_range == 2:
225
- cmd.extend(["-color_range", f"{color_range}"])
226
- if colorspace in (0, 1) or (colorspace >= 3 and colorspace < 16):
227
- cmd.extend(["-colorspace", f"{colorspace}"])
228
- if color_prim == 1 or (color_prim >= 4 and color_prim < 17):
229
- cmd.extend(["-color_primaries", f"{color_prim}"])
230
- if color_trc == 1 or (color_trc >= 4 and color_trc < 22):
231
- cmd.extend(["-color_trc", f"{color_trc}"])
232
-
233
- if args.extras is not None:
234
- cmd.extend(args.extras.split(" "))
235
- cmd.extend(["-strict", "-2"]) # Allow experimental codecs.
236
-
237
- if s_tracks > 0:
238
- cmd.extend(["-map", "0:t?"]) # Add input attachments to output.
239
-
240
- if not args.dn:
241
- cmd.extend(["-map", "0:d?"])
242
-
243
- cmd.append(output_path)
244
- ffmpeg.run_check_errors(cmd, path=output_path)