auto-editor 24.29.1__py3-none-any.whl → 24.31.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.
@@ -17,8 +17,6 @@ from auto_editor.utils.types import color
17
17
  if TYPE_CHECKING:
18
18
  from collections.abc import Iterator
19
19
 
20
- from av.filter import FilterContext
21
-
22
20
  from auto_editor.ffwrapper import FFmpeg, FileInfo
23
21
  from auto_editor.timeline import v3
24
22
  from auto_editor.utils.bar import Bar
@@ -33,11 +31,6 @@ class VideoFrame:
33
31
  src: FileInfo
34
32
 
35
33
 
36
- def link_nodes(*nodes: FilterContext) -> None:
37
- for c, n in zip(nodes, nodes[1:]):
38
- c.link_to(n)
39
-
40
-
41
34
  # From: github.com/PyAV-Org/PyAV/blob/main/av/video/frame.pyx
42
35
  allowed_pix_fmt = {
43
36
  "yuv420p",
@@ -98,12 +91,11 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
98
91
  for frame in cn.decode(my_stream):
99
92
  if obj.width != 0:
100
93
  graph = av.filter.Graph()
101
- link_nodes(
94
+ graph.link_nodes(
102
95
  graph.add_buffer(template=my_stream),
103
96
  graph.add("scale", f"{obj.width}:-1"),
104
97
  graph.add("buffersink"),
105
- )
106
- graph.vpush(frame)
98
+ ).vpush(frame)
107
99
  frame = graph.vpull()
108
100
  img_cache[(obj.src, obj.width)] = frame.to_ndarray(
109
101
  format="rgb24"
@@ -177,7 +169,7 @@ def render_av(
177
169
  target_width = max(round(tl.res[0] * args.scale), 2)
178
170
  target_height = max(round(tl.res[1] * args.scale), 2)
179
171
  scale_graph = av.filter.Graph()
180
- link_nodes(
172
+ scale_graph.link_nodes(
181
173
  scale_graph.add(
182
174
  "buffer", video_size="1x1", time_base="1/1", pix_fmt=target_pix_fmt
183
175
  ),
@@ -213,7 +205,7 @@ def render_av(
213
205
  if apply_video_later:
214
206
  cmd += ["-c:v", "mpeg4", "-qscale:v", "1"]
215
207
  else:
216
- cmd += video_quality(args, ctr)
208
+ cmd += video_quality(args)
217
209
 
218
210
  # Setting SAR requires re-encoding so we do it here.
219
211
  if src is not None and src.videos and (sar := src.videos[0].sar) is not None:
@@ -293,7 +285,7 @@ def render_av(
293
285
  if (frame.width, frame.height) != tl.res:
294
286
  width, height = tl.res
295
287
  graph = av.filter.Graph()
296
- link_nodes(
288
+ graph.link_nodes(
297
289
  graph.add_buffer(template=my_stream),
298
290
  graph.add(
299
291
  "scale",
@@ -301,21 +293,19 @@ def render_av(
301
293
  ),
302
294
  graph.add("pad", f"{width}:{height}:-1:-1:color={bg}"),
303
295
  graph.add("buffersink"),
304
- )
305
- graph.vpush(frame)
296
+ ).vpush(frame)
306
297
  frame = graph.vpull()
307
298
  elif isinstance(obj, TlRect):
308
299
  graph = av.filter.Graph()
309
300
  x, y = apply_anchor(obj.x, obj.y, obj.width, obj.height, obj.anchor)
310
- link_nodes(
301
+ graph.link_nodes(
311
302
  graph.add_buffer(template=my_stream),
312
303
  graph.add(
313
304
  "drawbox",
314
305
  f"x={x}:y={y}:w={obj.width}:h={obj.height}:color={obj.fill}:t=fill",
315
306
  ),
316
307
  graph.add("buffersink"),
317
- )
318
- graph.vpush(frame)
308
+ ).vpush(frame)
319
309
  frame = graph.vpull()
320
310
  elif isinstance(obj, TlImage):
321
311
  img = img_cache[(obj.src, obj.width)]
@@ -2,15 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  import sys
4
4
  from dataclasses import dataclass, field
5
+ from fractions import Fraction
5
6
  from typing import TYPE_CHECKING
6
7
 
7
8
  import numpy as np
8
9
 
9
- from auto_editor.analyze import LevelError, Levels
10
- from auto_editor.ffwrapper import FFmpeg, initFileInfo
10
+ from auto_editor.analyze import LevelError, Levels, iter_audio, iter_motion
11
+ from auto_editor.ffwrapper import initFileInfo
11
12
  from auto_editor.lang.palet import env
12
13
  from auto_editor.lib.contracts import is_bool, is_nat, is_nat1, is_str, is_void, orc
13
- from auto_editor.output import Ensure
14
14
  from auto_editor.utils.bar import Bar
15
15
  from auto_editor.utils.cmdkw import (
16
16
  ParserError,
@@ -25,6 +25,7 @@ from auto_editor.utils.types import frame_rate
25
25
  from auto_editor.vanparse import ArgumentParser
26
26
 
27
27
  if TYPE_CHECKING:
28
+ from collections.abc import Iterator
28
29
  from fractions import Fraction
29
30
 
30
31
  from numpy.typing import NDArray
@@ -35,8 +36,6 @@ class LevelArgs:
35
36
  input: list[str] = field(default_factory=list)
36
37
  edit: str = "audio"
37
38
  timebase: Fraction | None = None
38
- ffmpeg_location: str | None = None
39
- my_ffmpeg: bool = False
40
39
  help: bool = False
41
40
 
42
41
 
@@ -54,12 +53,6 @@ def levels_options(parser: ArgumentParser) -> ArgumentParser:
54
53
  type=frame_rate,
55
54
  help="Set custom timebase",
56
55
  )
57
- parser.add_argument("--ffmpeg-location", help="Point to your custom ffmpeg file")
58
- parser.add_argument(
59
- "--my-ffmpeg",
60
- flag=True,
61
- help="Use the ffmpeg on your PATH instead of the one packaged",
62
- )
63
56
  return parser
64
57
 
65
58
 
@@ -79,12 +72,21 @@ def print_arr(arr: NDArray) -> None:
79
72
  print("")
80
73
 
81
74
 
75
+ def print_arr_gen(arr: Iterator[int | float]) -> None:
76
+ print("")
77
+ print("@start")
78
+ for a in arr:
79
+ if isinstance(a, float):
80
+ print(f"{a:.20f}")
81
+ else:
82
+ print(a)
83
+ print("")
84
+
85
+
82
86
  def main(sys_args: list[str] = sys.argv[1:]) -> None:
83
87
  parser = levels_options(ArgumentParser("levels"))
84
88
  args = parser.parse_args(LevelArgs, sys_args)
85
89
 
86
- ffmpeg = FFmpeg(args.ffmpeg_location, args.my_ffmpeg)
87
-
88
90
  bar = Bar("none")
89
91
  temp = setup_tempdir(None, Log())
90
92
  log = Log(quiet=True, temp=temp)
@@ -96,7 +98,6 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
96
98
  src = sources[0]
97
99
 
98
100
  tb = src.get_fps() if args.timebase is None else args.timebase
99
- ensure = Ensure(ffmpeg, bar, src.get_sr(), temp, log)
100
101
 
101
102
  if ":" in args.edit:
102
103
  method, attrs = args.edit.split(":", 1)
@@ -131,12 +132,12 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
131
132
  except ParserError as e:
132
133
  log.error(e)
133
134
 
134
- levels = Levels(ensure, src, tb, bar, temp, log)
135
+ levels = Levels(src, tb, bar, temp, log)
135
136
  try:
136
137
  if method == "audio":
137
- print_arr(levels.audio(**obj))
138
+ print_arr_gen(iter_audio(src, tb, **obj))
138
139
  elif method == "motion":
139
- print_arr(levels.motion(**obj))
140
+ print_arr_gen(iter_motion(src, tb, **obj))
140
141
  elif method == "subtitle":
141
142
  print_arr(levels.subtitle(**obj))
142
143
  elif method == "none":
@@ -6,11 +6,10 @@ from fractions import Fraction
6
6
 
7
7
  import auto_editor
8
8
  from auto_editor.analyze import FileSetup, Levels
9
- from auto_editor.ffwrapper import FFmpeg, initFileInfo
9
+ from auto_editor.ffwrapper import initFileInfo
10
10
  from auto_editor.lang.palet import ClosingError, Lexer, Parser, env, interpret
11
11
  from auto_editor.lib.data_structs import print_str
12
12
  from auto_editor.lib.err import MyError
13
- from auto_editor.output import Ensure
14
13
  from auto_editor.utils.bar import Bar
15
14
  from auto_editor.utils.func import setup_tempdir
16
15
  from auto_editor.utils.log import Log
@@ -48,12 +47,6 @@ def repl_options(parser: ArgumentParser) -> ArgumentParser:
48
47
  type=frame_rate,
49
48
  help="Set custom timebase",
50
49
  )
51
- parser.add_argument("--ffmpeg-location", help="Point to your custom ffmpeg file")
52
- parser.add_argument(
53
- "--my-ffmpeg",
54
- flag=True,
55
- help="Use the ffmpeg on your PATH instead of the one packaged",
56
- )
57
50
  parser.add_argument(
58
51
  "--temp-dir",
59
52
  metavar="PATH",
@@ -68,16 +61,14 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
68
61
  if args.input:
69
62
  temp = setup_tempdir(args.temp_dir, Log())
70
63
  log = Log(quiet=True, temp=temp)
71
- ffmpeg = FFmpeg(args.ffmpeg_location, args.my_ffmpeg, False)
72
64
  strict = len(args.input) < 2
73
65
  sources = [initFileInfo(path, log) for path in args.input]
74
66
  src = sources[0]
75
67
  tb = src.get_fps() if args.timebase is None else args.timebase
76
68
  bar = Bar("modern")
77
- ensure = Ensure(ffmpeg, bar, src.get_sr(), temp, log)
78
69
  env["timebase"] = tb
79
- env["@levels"] = Levels(ensure, src, tb, bar, temp, log)
80
- env["@filesetup"] = FileSetup(src, ensure, strict, tb, bar, temp, log)
70
+ env["@levels"] = Levels(src, tb, bar, temp, log)
71
+ env["@filesetup"] = FileSetup(src, strict, tb, bar, temp, log)
81
72
 
82
73
  print(f"Auto-Editor {auto_editor.version} ({auto_editor.__version__})")
83
74
  text = None
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
 
3
3
  import av
4
- from av.subtitles.subtitle import AssSubtitle, TextSubtitle
4
+ from av.subtitles.subtitle import AssSubtitle
5
5
 
6
6
 
7
7
  def main(sys_args: list[str] = sys.argv[1:]) -> None:
@@ -9,13 +9,10 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
9
9
  with av.open(input_file) as container:
10
10
  for s in range(len(container.streams.subtitles)):
11
11
  print(f"file: {input_file} ({s}:{container.streams.subtitles[s].name})")
12
- for packet in container.demux(subtitles=s):
13
- for subset in packet.decode():
14
- for sub in subset.rects:
15
- if isinstance(sub, AssSubtitle):
16
- print(sub.ass.decode("utf-8", errors="ignore"))
17
- elif isinstance(sub, TextSubtitle):
18
- print(sub.text.decode("utf-8", errors="ignore"))
12
+ for subset in container.decode(subtitles=s):
13
+ for sub in subset:
14
+ if isinstance(sub, AssSubtitle):
15
+ print(sub.ass.decode("utf-8", errors="ignore"))
19
16
  print("------")
20
17
 
21
18
 
@@ -628,11 +628,11 @@ def main(sys_args: list[str] | None = None):
628
628
  ("(string #\\a #\\b)", "ab"),
629
629
  ("(string #\\a #\\b #\\c)", "abc"),
630
630
  (
631
- "(margin 0 (bool-array 0 0 0 1 0 0 0))",
631
+ "(margin (bool-array 0 0 0 1 0 0 0) 0)",
632
632
  np.array([0, 0, 0, 1, 0, 0, 0], dtype=np.bool_),
633
633
  ),
634
634
  (
635
- "(margin -2 2 (bool-array 0 0 1 1 0 0 0))",
635
+ "(margin (bool-array 0 0 1 1 0 0 0) -2 2)",
636
636
  np.array([0, 0, 0, 0, 1, 1, 0], dtype=np.bool_),
637
637
  ),
638
638
  ("(equal? 3 3)", True),
@@ -1,338 +1,96 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass, field
3
+ from dataclasses import dataclass
4
4
  from typing import TypedDict
5
5
 
6
+ import av
7
+ from av.codec import Codec
8
+
6
9
 
7
10
  class DictContainer(TypedDict, total=False):
8
- allow_video: bool
9
- allow_audio: bool
10
- allow_subtitle: bool
11
- allow_image: bool
12
11
  max_videos: int | None
13
12
  max_audios: int | None
14
13
  max_subtitles: int | None
15
- vcodecs: list[str] | None
16
- acodecs: list[str] | None
17
- scodecs: list[str] | None
18
- vstrict: bool
19
- sstrict: bool
20
- disallow_v: list[str]
21
14
  samplerate: list[int] | None
22
15
 
23
16
 
24
17
  @dataclass(slots=True)
25
18
  class Container:
26
- allow_video: bool = False
27
- allow_audio: bool = False
28
- allow_subtitle: bool = False
29
- allow_image: bool = False
19
+ allow_image: bool
20
+ vcodecs: set[str]
21
+ acodecs: set[str]
22
+ scodecs: set[str]
23
+ default_vid: str
24
+ default_aud: str
25
+ default_sub: str
30
26
  max_videos: int | None = None
31
27
  max_audios: int | None = None
32
28
  max_subtitles: int | None = None
33
- vcodecs: list[str] | None = None
34
- acodecs: list[str] | None = None
35
- scodecs: list[str] | None = None
36
- vstrict: bool = False
37
- sstrict: bool = False
38
- disallow_v: list[str] = field(default_factory=list)
39
29
  samplerate: list[int] | None = None # Any samplerate is allowed
40
30
 
41
31
 
42
- h264_en = [
43
- "h264",
44
- "libx264",
45
- "libx264rgb",
46
- "libopenh264",
47
- "h264_videotoolbox",
48
- "h264_amf",
49
- "h264_nvenc",
50
- "h264_qsv",
51
- ]
52
- hevc_en = ["hevc", "libx265", "hevc_videotoolbox", "hevc_amf", "hevc_nvenc", "hevc_qsv"]
53
- av1_en = ["av1", "libaom-av1", "av1_nvenc", "av1_amf"]
54
- prores_en = ["prores", "prores_videotoolbox", "prores_aw", "prores_ks"]
55
- aac_en = ["aac", "aac_at", "libfdk_aac"]
56
- opus_en = ["opus", "libopus"]
57
-
58
- h265: DictContainer = {
59
- "allow_video": True,
60
- "vcodecs": hevc_en + ["mpeg4"] + h264_en,
61
- }
62
- h264: DictContainer = {
63
- "allow_video": True,
64
- "vcodecs": h264_en + ["mpeg4"] + hevc_en,
65
- }
66
- aac: DictContainer = {
67
- "allow_audio": True,
68
- "max_audios": 1,
69
- "acodecs": aac_en,
70
- }
71
- ass: DictContainer = {
72
- "allow_subtitle": True,
73
- "scodecs": ["ass", "ssa"],
74
- "max_subtitles": 1,
75
- "sstrict": True,
76
- }
77
- mp4: DictContainer = {
78
- "allow_video": True,
79
- "allow_audio": True,
80
- "allow_subtitle": True,
81
- "allow_image": True,
82
- "vcodecs": h264_en + hevc_en + av1_en + ["vp9", "mpeg4", "mpeg2video", "mjpeg"],
83
- "acodecs": aac_en + opus_en + ["mp3", "flac", "vorbis", "libvorbis", "ac3", "mp2"],
84
- "vstrict": True,
85
- }
86
- ogg: DictContainer = {
87
- "allow_video": True,
88
- "allow_audio": True,
89
- "allow_subtitle": True,
90
- "vcodecs": ["libtheora", "theora"],
91
- "acodecs": opus_en + ["libvorbis", "vorbis", "flac", "speex"],
92
- "vstrict": True,
32
+ containers: dict[str, DictContainer] = {
33
+ "aac": {"max_audios": 1},
34
+ "adts": {"max_audios": 1},
35
+ "ass": {"max_subtitles": 1},
36
+ "ssa": {"max_subtitles": 1},
37
+ "apng": {"max_videos": 1},
38
+ "gif": {"max_videos": 1},
39
+ "wav": {"max_audios": 1},
40
+ "ast": {"max_audios": 1},
41
+ "mp3": {"max_audios": 1},
42
+ "flac": {"max_audios": 1},
43
+ "srt": {"max_subtitles": 1},
44
+ "vtt": {"max_subtitles": 1},
45
+ "swf": {"samplerate": [44100, 22050, 11025]},
93
46
  }
94
47
 
95
- mka_audio = (
96
- ["libvorbis", "vorbis"]
97
- + aac_en
98
- + opus_en
99
- + [
100
- "mp3",
101
- "flac",
102
- "ac3",
103
- "mp2",
104
- "wmav2",
105
- "pcm_s16le",
106
- "pcm_alaw",
107
- "pcm_f32le",
108
- "pcm_f64le",
109
- "pcm_mulaw",
110
- "pcm_s16be",
111
- "pcm_s24be",
112
- "pcm_s24le",
113
- "pcm_s32be",
114
- "pcm_s32le",
115
- "pcm_u8",
116
- ]
117
- )
118
48
 
119
- containers: dict[str, DictContainer] = {
120
- # Aliases section
121
- "aac": aac,
122
- "adts": aac,
123
- "ass": ass,
124
- "ssa": ass,
125
- "264": h264,
126
- "h264": h264,
127
- "265": h265,
128
- "h265": h265,
129
- "hevc": h265,
130
- "mp4": mp4,
131
- "m4a": mp4,
132
- "ogg": ogg,
133
- "ogv": ogg,
134
- "apng": {
135
- "allow_video": True,
136
- "max_videos": 1,
137
- "vcodecs": ["apng"],
138
- "vstrict": True,
139
- },
140
- "gif": {
141
- "allow_video": True,
142
- "max_videos": 1,
143
- "vcodecs": ["gif"],
144
- "vstrict": True,
145
- },
146
- "wav": {
147
- "allow_audio": True,
148
- "max_audios": 1,
149
- "acodecs": [
150
- "pcm_s16le",
151
- "mp3",
152
- "mp2",
153
- "wmav2",
154
- "pcm_alaw",
155
- "pcm_f32le",
156
- "pcm_f64le",
157
- "pcm_mulaw",
158
- "pcm_s24le",
159
- "pcm_s32le",
160
- "pcm_u8",
161
- ],
162
- },
163
- "ast": {
164
- "allow_audio": True,
165
- "max_audios": 1,
166
- "acodecs": ["pcm_s16be_planar"],
167
- },
168
- "mp3": {
169
- "allow_audio": True,
170
- "max_audios": 1,
171
- "acodecs": ["mp3"],
172
- },
173
- "opus": {
174
- "allow_audio": True,
175
- "acodecs": opus_en + ["flac", "libvorbis", "vorbis", "speex"],
176
- },
177
- "oga": {
178
- "allow_audio": True,
179
- "acodecs": opus_en + ["flac", "libvorbis", "vorbis", "speex"],
180
- },
181
- "flac": {
182
- "allow_audio": True,
183
- "max_audios": 1,
184
- "acodecs": ["flac"],
185
- },
186
- "webm": {
187
- "allow_video": True,
188
- "allow_audio": True,
189
- "allow_subtitle": True,
190
- "vcodecs": ["vp9", "vp8"] + av1_en,
191
- "acodecs": opus_en + ["vorbis", "libvorbis"],
192
- "scodecs": ["webvtt"],
193
- "vstrict": True,
194
- "sstrict": True,
195
- },
196
- "srt": {
197
- "allow_subtitle": True,
198
- "scodecs": ["srt"],
199
- "max_subtitles": 1,
200
- "sstrict": True,
201
- },
202
- "vtt": {
203
- "allow_subtitle": True,
204
- "scodecs": ["webvtt"],
205
- "max_subtitles": 1,
206
- "sstrict": True,
207
- },
208
- "avi": {
209
- "allow_video": True,
210
- "allow_audio": True,
211
- "vcodecs": ["mpeg4"] + h264_en + ["prores", "mjpeg", "mpeg2video", "rawvideo"],
212
- "acodecs": ["mp3"]
213
- + aac_en
214
- + [
215
- "flac",
216
- "vorbis",
217
- "libvorbis",
218
- "mp2",
219
- "wmav2",
220
- "pcm_s16le",
221
- "pcm_alaw",
222
- "pcm_f32le",
223
- "pcm_f64le",
224
- "pcm_mulaw",
225
- "pcm_s24le",
226
- "pcm_s32le",
227
- "pcm_u8",
228
- ],
229
- "disallow_v": hevc_en + ["apng", "gif"],
230
- },
231
- "wmv": {
232
- "allow_video": True,
233
- "allow_audio": True,
234
- "vcodecs": ["msmpeg4v3"]
235
- + h264_en
236
- + ["mpeg4", "mpeg2video", "mjpeg", "rawvideo"],
237
- "acodecs": ["wmav2"]
238
- + aac_en
239
- + [
240
- "mp3",
241
- "flac",
242
- "vorbis",
243
- "libvorbis",
244
- "ac3",
245
- "mp2",
246
- "pcm_s16le",
247
- "pcm_alaw",
248
- "pcm_f32le",
249
- "pcm_f64le",
250
- "pcm_mulaw",
251
- "pcm_s24le",
252
- "pcm_s32le",
253
- "pcm_u8",
254
- ],
255
- "vstrict": True,
256
- },
257
- "mkv": {
258
- "allow_video": True,
259
- "allow_audio": True,
260
- "allow_subtitle": True,
261
- "allow_image": True,
262
- "vcodecs": h264_en
263
- + hevc_en
264
- + prores_en
265
- + [
266
- "vp9",
267
- "vp8",
268
- "mpeg4",
269
- "mpeg2video",
270
- "msmpeg4v3",
271
- "mjpeg",
272
- "gif",
273
- "rawvideo",
274
- ],
275
- "acodecs": mka_audio,
276
- "disallow_v": ["apng"],
277
- },
278
- "mka": {
279
- "allow_audio": True,
280
- "acodecs": mka_audio,
281
- },
282
- "mov": {
283
- "allow_video": True,
284
- "allow_audio": True,
285
- "allow_subtitle": True,
286
- "vcodecs": h264_en
287
- + hevc_en
288
- + prores_en
289
- + [
290
- "mpeg4",
291
- "mpeg2video",
292
- "msmpeg4v3",
293
- "mjpeg",
294
- "gif",
295
- "flv1",
296
- "dvvideo",
297
- "rawvideo",
298
- ],
299
- "acodecs": aac_en
300
- + [
301
- "mp3",
302
- "vorbis",
303
- "libvorbis",
304
- "ac3",
305
- "mp2",
306
- "wmav2",
307
- "pcm_s16le",
308
- "pcm_alaw",
309
- "pcm_f32be",
310
- "pcm_f32le",
311
- "pcm_f64be",
312
- "pcm_f64le",
313
- "pcm_mulaw",
314
- "pcm_s16be",
315
- "pcm_s24be",
316
- "pcm_s24le",
317
- "pcm_s32be",
318
- "pcm_s32le",
319
- "pcm_s8",
320
- "pcm_u8",
321
- ],
322
- "disallow_v": ["apng", "vp9", "vp8"],
323
- },
324
- "swf": {
325
- "allow_video": True,
326
- "allow_audio": True,
327
- "vcodecs": ["flv1", "mjpeg"],
328
- "acodecs": ["mp3"],
329
- "vstrict": True,
330
- "samplerate": [44100, 22050, 11025],
331
- },
332
- }
49
+ def codec_type(x: str) -> str:
50
+ if x in ("vp9", "vp8", "h264", "hevc", "av1", "gif", "apng"):
51
+ return "video"
52
+ if x in ("aac", "flac", "mp3"):
53
+ return "audio"
54
+ if x in ("ass", "ssa", "srt"):
55
+ return "subtitle"
56
+
57
+ try:
58
+ return Codec(x, "r").type
59
+ except Exception:
60
+ try:
61
+ return Codec(x, "w").type
62
+ except Exception:
63
+ return ""
64
+
65
+
66
+ def container_constructor(ext: str) -> Container:
67
+ with av.open(f".{ext}", "w") as container:
68
+ codecs = container.supported_codecs
69
+ if ext == "webm":
70
+ vdefault = "vp9"
71
+ else:
72
+ vdefault = container.default_video_codec
73
+ adefault = container.default_audio_codec
74
+ sdefault = container.default_subtitle_codec
75
+
76
+ vcodecs = set()
77
+ acodecs = set()
78
+ scodecs = set()
79
+
80
+ for codec in codecs:
81
+ kind = codec_type(codec)
82
+ if kind == "video":
83
+ vcodecs.add(codec)
84
+ if codec == "h264":
85
+ vcodecs.add("libx264")
86
+ if kind == "audio":
87
+ acodecs.add(codec)
88
+ if kind == "subtitle":
89
+ scodecs.add(codec)
333
90
 
91
+ allow_image = ext in ("mp4", "mkv")
92
+ kwargs = containers[ext] if ext in containers else {}
334
93
 
335
- def container_constructor(key: str) -> Container:
336
- if key in containers:
337
- return Container(**containers[key])
338
- return Container(allow_video=True, allow_audio=True, allow_subtitle=True)
94
+ return Container(
95
+ allow_image, vcodecs, acodecs, scodecs, vdefault, adefault, sdefault, **kwargs
96
+ )