auto-editor 26.3.3__py3-none-any.whl → 27.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.
@@ -3,15 +3,14 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from typing import TYPE_CHECKING
5
5
 
6
- import av
6
+ import bv
7
7
  import numpy as np
8
8
 
9
- from auto_editor.output import parse_bitrate
10
9
  from auto_editor.timeline import TlImage, TlRect, TlVideo
10
+ from auto_editor.utils.func import parse_bitrate
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from collections.abc import Iterator
14
- from typing import Any
15
14
 
16
15
  from auto_editor.__main__ import Args
17
16
  from auto_editor.ffwrapper import FileInfo
@@ -25,15 +24,12 @@ class VideoFrame:
25
24
  src: FileInfo
26
25
 
27
26
 
28
- allowed_pix_fmt = av.video.frame.supported_np_pix_fmts
29
-
30
-
31
- def make_solid(width: int, height: int, pix_fmt: str, bg: str) -> av.VideoFrame:
27
+ def make_solid(width: int, height: int, pix_fmt: str, bg: str) -> bv.VideoFrame:
32
28
  hex_color = bg.lstrip("#").upper()
33
29
  rgb_color = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
34
30
 
35
31
  rgb_array = np.full((height, width, 3), rgb_color, dtype=np.uint8)
36
- rgb_frame = av.VideoFrame.from_ndarray(rgb_array, format="rgb24")
32
+ rgb_frame = bv.VideoFrame.from_ndarray(rgb_array, format="rgb24")
37
33
  return rgb_frame.reformat(format=pix_fmt)
38
34
 
39
35
 
@@ -42,11 +38,11 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
42
38
  for clip in tl.v:
43
39
  for obj in clip:
44
40
  if isinstance(obj, TlImage) and (obj.src, obj.width) not in img_cache:
45
- with av.open(obj.src.path) as cn:
41
+ with bv.open(obj.src.path) as cn:
46
42
  my_stream = cn.streams.video[0]
47
43
  for frame in cn.decode(my_stream):
48
44
  if obj.width != 0:
49
- graph = av.filter.Graph()
45
+ graph = bv.filter.Graph()
50
46
  graph.link_nodes(
51
47
  graph.add_buffer(template=my_stream),
52
48
  graph.add("scale", f"{obj.width}:-1"),
@@ -61,17 +57,16 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
61
57
 
62
58
 
63
59
  def render_av(
64
- output: av.container.OutputContainer, tl: v3, args: Args, log: Log
65
- ) -> Any:
66
- from_ndarray = av.VideoFrame.from_ndarray
60
+ output: bv.container.OutputContainer, tl: v3, args: Args, log: Log
61
+ ) -> Iterator[tuple[int, bv.VideoFrame]]:
62
+ from_ndarray = bv.VideoFrame.from_ndarray
67
63
 
68
- src = tl.src
69
- cns: dict[FileInfo, av.container.InputContainer] = {}
70
- decoders: dict[FileInfo, Iterator[av.VideoFrame]] = {}
64
+ cns: dict[FileInfo, bv.container.InputContainer] = {}
65
+ decoders: dict[FileInfo, Iterator[bv.VideoFrame]] = {}
71
66
  seek_cost: dict[FileInfo, int] = {}
72
67
  tous: dict[FileInfo, int] = {}
73
68
 
74
- target_pix_fmt = "yuv420p" # Reasonable default
69
+ pix_fmt = "yuv420p" # Reasonable default
75
70
  target_fps = tl.tb # Always constant
76
71
  img_cache = make_image_cache(tl)
77
72
 
@@ -81,7 +76,7 @@ def render_av(
81
76
  first_src = src
82
77
 
83
78
  if src not in cns:
84
- cns[src] = av.open(f"{src.path}")
79
+ cns[src] = bv.open(f"{src.path}")
85
80
 
86
81
  for src, cn in cns.items():
87
82
  if len(cn.streams.video) > 0:
@@ -101,30 +96,30 @@ def render_av(
101
96
  decoders[src] = cn.decode(stream)
102
97
 
103
98
  if src == first_src and stream.pix_fmt is not None:
104
- target_pix_fmt = stream.pix_fmt
99
+ pix_fmt = stream.pix_fmt
105
100
 
106
101
  log.debug(f"Tous: {tous}")
107
102
  log.debug(f"Clips: {tl.v}")
108
103
 
109
- codec = av.Codec(args.video_codec, "w")
104
+ codec = bv.Codec(args.video_codec, "w")
105
+
106
+ need_valid_fmt = True
107
+ if codec.video_formats is not None:
108
+ for video_format in codec.video_formats:
109
+ if pix_fmt == video_format.name:
110
+ need_valid_fmt = False
111
+ break
110
112
 
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
114
- ):
115
- target_pix_fmt = target_pix_fmt
113
+ if need_valid_fmt:
114
+ if codec.canonical_name == "gif":
115
+ pix_fmt = "rgb8"
116
+ elif codec.canonical_name == "prores":
117
+ pix_fmt = "yuv422p10le"
116
118
  else:
117
- target_pix_fmt = "rgb8"
118
- elif codec.canonical_name == "prores":
119
- target_pix_fmt = "yuv422p10le"
120
- else:
121
- target_pix_fmt = (
122
- target_pix_fmt if target_pix_fmt in allowed_pix_fmt else "yuv420p"
123
- )
119
+ pix_fmt = "yuv420p"
124
120
 
125
121
  del codec
126
- ops = {"mov_flags": "faststart"}
127
- output_stream = output.add_stream(args.video_codec, rate=target_fps, options=ops)
122
+ output_stream = output.add_stream(args.video_codec, rate=target_fps)
128
123
 
129
124
  cc = output_stream.codec_context
130
125
  if args.vprofile is not None:
@@ -136,8 +131,8 @@ def render_av(
136
131
 
137
132
  cc.profile = args.vprofile.title()
138
133
 
139
- yield output_stream
140
- if not isinstance(output_stream, av.VideoStream):
134
+ yield output_stream # type: ignore
135
+ if not isinstance(output_stream, bv.VideoStream):
141
136
  log.error(f"Not a known video codec: {args.video_codec}")
142
137
  if src.videos and src.videos[0].lang is not None:
143
138
  output_stream.metadata["language"] = src.videos[0].lang
@@ -148,10 +143,10 @@ def render_av(
148
143
  else:
149
144
  target_width = max(round(tl.res[0] * args.scale), 2)
150
145
  target_height = max(round(tl.res[1] * args.scale), 2)
151
- scale_graph = av.filter.Graph()
146
+ scale_graph = bv.filter.Graph()
152
147
  scale_graph.link_nodes(
153
148
  scale_graph.add(
154
- "buffer", video_size="1x1", time_base="1/1", pix_fmt=target_pix_fmt
149
+ "buffer", video_size="1x1", time_base="1/1", pix_fmt=pix_fmt
155
150
  ),
156
151
  scale_graph.add("scale", f"{target_width}:{target_height}"),
157
152
  scale_graph.add("buffersink"),
@@ -159,7 +154,7 @@ def render_av(
159
154
 
160
155
  output_stream.width = target_width
161
156
  output_stream.height = target_height
162
- output_stream.pix_fmt = target_pix_fmt
157
+ output_stream.pix_fmt = pix_fmt
163
158
  output_stream.framerate = target_fps
164
159
 
165
160
  color_range = src.videos[0].color_range
@@ -191,7 +186,7 @@ def render_av(
191
186
  frames_saved = 0
192
187
 
193
188
  bg = args.background
194
- null_frame = make_solid(target_width, target_height, target_pix_fmt, bg)
189
+ null_frame = make_solid(target_width, target_height, pix_fmt, bg)
195
190
  frame_index = -1
196
191
 
197
192
  for index in range(tl.end):
@@ -250,7 +245,7 @@ def render_av(
250
245
 
251
246
  if (frame.width, frame.height) != tl.res:
252
247
  width, height = tl.res
253
- graph = av.filter.Graph()
248
+ graph = bv.filter.Graph()
254
249
  graph.link_nodes(
255
250
  graph.add_buffer(template=my_stream),
256
251
  graph.add(
@@ -262,7 +257,7 @@ def render_av(
262
257
  ).vpush(frame)
263
258
  frame = graph.vpull()
264
259
  elif isinstance(obj, TlRect):
265
- graph = av.filter.Graph()
260
+ graph = bv.filter.Graph()
266
261
  x, y = obj.x, obj.y
267
262
  graph.link_nodes(
268
263
  graph.add_buffer(template=my_stream),
@@ -308,9 +303,9 @@ def render_av(
308
303
  scale_graph.vpush(frame)
309
304
  frame = scale_graph.vpull()
310
305
 
311
- if frame.format.name != target_pix_fmt:
312
- frame = frame.reformat(format=target_pix_fmt)
313
-
314
- yield (index, from_ndarray(frame.to_ndarray(), format=frame.format.name))
306
+ frame = frame.reformat(format=pix_fmt)
307
+ frame.pts = None # type: ignore
308
+ frame.time_base = 0 # type: ignore
309
+ yield (index, frame)
315
310
 
316
311
  log.debug(f"Total frames saved seeking: {frames_saved}")
auto_editor/timeline.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from typing import TYPE_CHECKING
5
5
 
6
- from auto_editor.ffwrapper import initFileInfo, mux
6
+ from auto_editor.ffwrapper import FileInfo, mux
7
7
  from auto_editor.lib.contracts import *
8
8
  from auto_editor.utils.cmdkw import Required, pAttr, pAttrs
9
9
  from auto_editor.utils.types import CoerceError, natural, number, parse_color
@@ -187,13 +187,52 @@ ALayer = list[TlAudio]
187
187
  ASpace = list[ALayer]
188
188
 
189
189
 
190
+ @dataclass(slots=True)
191
+ class AudioTemplate:
192
+ lang: str | None
193
+
194
+
195
+ @dataclass(slots=True)
196
+ class SubtitleTemplate:
197
+ lang: str | None
198
+
199
+
200
+ @dataclass(slots=True)
201
+ class Template:
202
+ sr: int
203
+ layout: str
204
+ res: tuple[int, int]
205
+ audios: list[AudioTemplate]
206
+ subtitles: list[SubtitleTemplate]
207
+
208
+ @classmethod
209
+ def init(
210
+ self,
211
+ src: FileInfo,
212
+ sr: int | None = None,
213
+ layout: str | None = None,
214
+ res: tuple[int, int] | None = None,
215
+ ) -> Template:
216
+ alist = [AudioTemplate(x.lang) for x in src.audios]
217
+ slist = [SubtitleTemplate(x.lang) for x in src.subtitles]
218
+
219
+ if sr is None:
220
+ sr = src.get_sr()
221
+
222
+ if layout is None:
223
+ layout = "stereo" if not src.audios else src.audios[0].layout
224
+
225
+ if res is None:
226
+ res = src.get_res()
227
+
228
+ return Template(sr, layout, res, alist, slist)
229
+
230
+
190
231
  @dataclass
191
232
  class v3:
192
- src: FileInfo | None # Used as a template for timeline settings
193
233
  tb: Fraction
194
- sr: int
195
- res: tuple[int, int]
196
234
  background: str
235
+ template: Template
197
236
  v: VSpace
198
237
  a: ASpace
199
238
  v1: v1 | None # Is it v1 compatible (linear and only one source)?
@@ -286,14 +325,27 @@ video\n"""
286
325
 
287
326
  return {
288
327
  "version": "3",
289
- "resolution": self.res,
290
328
  "timebase": f"{self.tb.numerator}/{self.tb.denominator}",
291
- "samplerate": self.sr,
292
329
  "background": self.background,
330
+ "resolution": self.T.res,
331
+ "samplerate": self.T.sr,
332
+ "layout": self.T.layout,
293
333
  "v": v,
294
334
  "a": a,
295
335
  }
296
336
 
337
+ @property
338
+ def T(self) -> Template:
339
+ return self.template
340
+
341
+ @property
342
+ def res(self) -> tuple[int, int]:
343
+ return self.T.res
344
+
345
+ @property
346
+ def sr(self) -> int:
347
+ return self.T.sr
348
+
297
349
 
298
350
  def make_tracks_dir(path: Path) -> Path:
299
351
  from os import mkdir
@@ -310,9 +362,7 @@ def make_tracks_dir(path: Path) -> Path:
310
362
  return tracks_dir
311
363
 
312
364
 
313
- def set_stream_to_0(tl: v3, log: Log) -> None:
314
- src = tl.src
315
- assert src is not None
365
+ def set_stream_to_0(src: FileInfo, tl: v3, log: Log) -> None:
316
366
  fold = make_tracks_dir(src.path)
317
367
  cache: dict[Path, FileInfo] = {}
318
368
 
@@ -320,7 +370,7 @@ def set_stream_to_0(tl: v3, log: Log) -> None:
320
370
  newtrack = fold / f"{path.stem}_{i}.wav"
321
371
  if newtrack not in cache:
322
372
  mux(path, output=newtrack, stream=i)
323
- cache[newtrack] = initFileInfo(f"{newtrack}", log)
373
+ cache[newtrack] = FileInfo.init(f"{newtrack}", log)
324
374
  return cache[newtrack]
325
375
 
326
376
  for alayer in tl.a:
@@ -3,8 +3,10 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from typing import TypedDict
5
5
 
6
- import av
7
- from av.codec import Codec
6
+ import bv
7
+ from bv.codec import Codec
8
+
9
+ from auto_editor.utils.log import Log
8
10
 
9
11
 
10
12
  class DictContainer(TypedDict, total=False):
@@ -60,18 +62,23 @@ def codec_type(x: str) -> str:
60
62
  return ""
61
63
 
62
64
 
63
- def container_constructor(ext: str) -> Container:
64
- with av.open(f".{ext}", "w") as container:
65
- codecs = container.supported_codecs
66
- if ext == "webm":
67
- vdefault = "vp9"
68
- else:
69
- vdefault = container.default_video_codec
70
- adefault = container.default_audio_codec
71
- sdefault = container.default_subtitle_codec
72
- if sdefault == "none" and ext == "mp4":
73
- sdefault = "srt"
74
-
65
+ def container_constructor(ext: str, log: Log) -> Container:
66
+ try:
67
+ container = bv.open(f".{ext}", "w")
68
+ except ValueError:
69
+ log.error(f"Could not find a suitable format for extension: {ext}")
70
+
71
+ codecs = container.supported_codecs
72
+ if ext == "webm":
73
+ vdefault = "vp9"
74
+ else:
75
+ vdefault = container.default_video_codec
76
+ adefault = container.default_audio_codec
77
+ sdefault = container.default_subtitle_codec
78
+ if sdefault == "none" and ext == "mp4":
79
+ sdefault = "srt"
80
+
81
+ container.close()
75
82
  vcodecs = set()
76
83
  acodecs = set()
77
84
  scodecs = set()
auto_editor/utils/func.py CHANGED
@@ -4,6 +4,9 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  import numpy as np
6
6
 
7
+ from auto_editor.utils.log import Log
8
+ from auto_editor.utils.types import split_num_str
9
+
7
10
  if TYPE_CHECKING:
8
11
  from collections.abc import Callable
9
12
  from fractions import Fraction
@@ -105,3 +108,21 @@ def aspect_ratio(width: int, height: int) -> tuple[int, int]:
105
108
 
106
109
  c = gcd(width, height)
107
110
  return width // c, height // c
111
+
112
+
113
+ def parse_bitrate(input_: str, log: Log) -> int:
114
+ try:
115
+ val, unit = split_num_str(input_)
116
+ except Exception as e:
117
+ log.error(e)
118
+
119
+ if unit.lower() == "k":
120
+ return int(val * 1000)
121
+ if unit == "M":
122
+ return int(val * 1_000_000)
123
+ if unit == "G":
124
+ return int(val * 1_000_000_000)
125
+ if unit == "":
126
+ return int(val)
127
+
128
+ log.error(f"Unknown bitrate: {input_}")
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: auto-editor
3
- Version: 26.3.3
3
+ Version: 27.1.0
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
- License: Unlicense
6
+ License-Expression: Unlicense
7
7
  Project-URL: Bug Tracker, https://github.com/WyattBlue/auto-editor/issues
8
8
  Project-URL: Source Code, https://github.com/WyattBlue/auto-editor
9
9
  Project-URL: homepage, https://auto-editor.com
@@ -11,8 +11,9 @@ 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<3.0,>=1.24
15
- Requires-Dist: pyav==14.2.*
14
+ Requires-Dist: numpy<3.0,>=2
15
+ Requires-Dist: basswood-av<16,>=15.0.0
16
+ Dynamic: license-file
16
17
 
17
18
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
18
19
 
@@ -175,9 +176,9 @@ auto-editor --margin --help
175
176
 
176
177
  ## Articles
177
178
  - [How to Install Auto-Editor](https://auto-editor.com/installing)
178
- - [All the Options (And What They Do)](https://auto-editor.com/options)
179
+ - [All the Options (And What They Do)](https://auto-editor.com/ref/options)
179
180
  - [Docs](https://auto-editor.com/docs)
180
- - [Blog](https://auto-editor.com/blog)
181
+ - [Blog](https://basswood-io.com/blog/)
181
182
 
182
183
  ## Copyright
183
184
  Auto-Editor is under the [Public Domain](https://github.com/WyattBlue/auto-editor/blob/master/LICENSE) and includes all directories besides the ones listed below. Auto-Editor was created by [these people.](https://auto-editor.com/blog/thank-you-early-testers)
@@ -0,0 +1,54 @@
1
+ auto_editor/__init__.py,sha256=GyXI0pHyyPF89C9QtYg4zFjEDtSMUJMNUdrc74jh27A,23
2
+ auto_editor/__main__.py,sha256=WfNtjKwx5fDMPpfSNLarigXD3Jp0My98FpzQqSAxQZ8,15807
3
+ auto_editor/analyze.py,sha256=CeJG0LI9wXZk1R-QPrNGPS4za-_Avd8y7H-D437DqLg,12300
4
+ auto_editor/edit.py,sha256=UbKG5jhl5K3kCx9wIB_-Con6PF-4H0-Du0yVln3B5Qc,18921
5
+ auto_editor/ffwrapper.py,sha256=Wet6B5nohgnjpBX7o20Zq0rYr-H9mUuOqHrbQAPPj38,5128
6
+ auto_editor/help.py,sha256=CzfDTsL4GuGu596ySHKj_wKnxGR9h8B0KUdkZpo33oE,8044
7
+ auto_editor/json.py,sha256=8IVhZJSLx2IVqJsbR5YKDvbHOhgIOvdQmYNpMdMG_xA,9332
8
+ auto_editor/make_layers.py,sha256=nSEeCHysMot2eze23q05g2HFDuskN_4Jk108xlk2Rw8,10102
9
+ auto_editor/preview.py,sha256=cqQdozM2IB-5qXHNxeqiSrSdEIzlMfjD4SU-NX9sYZ0,3052
10
+ auto_editor/timeline.py,sha256=Ku6BdDDzdboO0DisO_KLDjAyxJLQVDybWVgFbGCcsmY,9416
11
+ auto_editor/vanparse.py,sha256=Ug5A2QaRqGiw4l55Z_h9T2QU1x0WqRibR7yY5rQ0WTk,10002
12
+ auto_editor/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ auto_editor/cmds/cache.py,sha256=bViYbtVXefTeEIUvSanDfA6cG35ep1N_Jvtz7ZjgIkY,1959
14
+ auto_editor/cmds/desc.py,sha256=DSAWPKt8PS9soBTgzpsFIEWoTe4gPTWwjXpNL-p3WsI,866
15
+ auto_editor/cmds/info.py,sha256=VA2WkTBbQPvrVHjDaJyqryoVMtiiN6cguiUMdWgBJfU,7002
16
+ auto_editor/cmds/levels.py,sha256=2Hbvoy6wMbRRoHdZf25PMqq1uvhRKyPcITNPMpZFo7s,5632
17
+ auto_editor/cmds/palet.py,sha256=ONzTqemaQq9YEfIOsDRNnwzfqnEMUMSXIQrETxyroRU,749
18
+ auto_editor/cmds/repl.py,sha256=HSUTDaVykPb5Bd-v_jz_8R7HvFmKOcT_ZVmP-d0AbUY,3247
19
+ auto_editor/cmds/subdump.py,sha256=kHg8nfUi6I6VeJjEgMxupPa666qsYUh7ZEUxint7Gqo,2443
20
+ auto_editor/cmds/test.py,sha256=UNN1r6J69-woqA6QU7TeY0WlPxukQzwCRl5DGBTLK8g,28837
21
+ auto_editor/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ auto_editor/formats/fcp11.py,sha256=uaJbLiwiVxqoVy5JCA14wZj-m1wqZIzWh7rS-BsnSQM,5219
23
+ auto_editor/formats/fcp7.py,sha256=rHjXXjpJ5YQQvCxor7FpUaqaAreqjCas4uLpiTdFclc,20255
24
+ auto_editor/formats/json.py,sha256=UUBhFR_79vn4Lxu73B0cVBFgw4qytrmMP-TiCmDFMd0,7666
25
+ auto_editor/formats/shotcut.py,sha256=-ES854LLFCMCBe100JRJedDmuk8zPev17aQMTrzPv-g,4923
26
+ auto_editor/formats/utils.py,sha256=LYXDiqOk9WwUorLGw2D0M7In9BNDkoKikNawuks7hqE,1648
27
+ auto_editor/lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ auto_editor/lang/libintrospection.py,sha256=6H1rGp0wqaCud5IPaoEmzULGnYt6ec7_0h32ATcw2oY,261
29
+ auto_editor/lang/libmath.py,sha256=z33A161Oe6vYYK7R6pgYjdZZe63dQkN38Qf36TL3prg,847
30
+ auto_editor/lang/palet.py,sha256=RQjyIZMJSWnzDkHTu-5mt74o9_4zO4VrcH-wLojCF7A,24113
31
+ auto_editor/lang/stdenv.py,sha256=o7kFu7EbaH71XPFGxJUXYGxSeZ8O3i1_C5Hmi9uya4Q,44150
32
+ auto_editor/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ auto_editor/lib/contracts.py,sha256=lExGQymcQUmwG5lC1lO4qm4GY8W0q_yzK_miTaAoPA4,7586
34
+ auto_editor/lib/data_structs.py,sha256=Hnzl5gWvo-geTU0g-lGejj6HQW3VvPv0NBEj2XoGskY,7089
35
+ auto_editor/lib/err.py,sha256=UlszQJdzMZwkbT8x3sY4GkCV_5x9yrd6uVVUzvA8iiI,35
36
+ auto_editor/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ auto_editor/render/audio.py,sha256=ejV_NIvrmxMJt6mf7nPNIzlPHOumNhcIO2J-irHO-k8,17427
38
+ auto_editor/render/subtitle.py,sha256=F27T8OsAojUIGTGBWqTdH36h0BnHXSExxIqzOtqyZoE,6129
39
+ auto_editor/render/video.py,sha256=uIrYzF4bDZ3vwfX2F6TdR6F73GI4yruGssto9xEQ-AA,11999
40
+ auto_editor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
+ auto_editor/utils/bar.py,sha256=Ky9JRf37JTgLyvNuIXDfucaUE8H1vBbCqKLjttmsmmo,4156
42
+ auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,52
43
+ auto_editor/utils/cmdkw.py,sha256=aUGBvBel2Ko1o6Rwmr4rEL-BMc5hEnzYLbyZ1GeJdcY,5729
44
+ auto_editor/utils/container.py,sha256=CNHChHbhzIrjmDdWw6UzMqscrr9u7A-ZqKWejGjJwYE,2628
45
+ auto_editor/utils/func.py,sha256=ODyjXnzSDatEu08w398K8_xBKYdXMY3IPHiJpGRZDyQ,3250
46
+ auto_editor/utils/log.py,sha256=wPNf6AabV-0cnoS_bPLv1Lh7llQBtNqPKeh07einOuc,3701
47
+ auto_editor/utils/types.py,sha256=j2hd4zMQ9EftDy41Ji2_PFru_7HEZObd9yKA0BJxFaY,7616
48
+ auto_editor-27.1.0.dist-info/licenses/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
49
+ docs/build.py,sha256=g1uc1H9T_naGaermUiVMMwUpbT0IWElRhjgT0fvCh8w,1914
50
+ auto_editor-27.1.0.dist-info/METADATA,sha256=JU6SgcQLr3thOG27aQRkt2r3INzU0q306BeK79qkwGs,6176
51
+ auto_editor-27.1.0.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
52
+ auto_editor-27.1.0.dist-info/entry_points.txt,sha256=UAsTc7qJQbnAzHd7KWg-ALo_X9Hj2yDs3M9I2DV3eyI,212
53
+ auto_editor-27.1.0.dist-info/top_level.txt,sha256=jBV5zlbWRbKOa-xaWPvTD45QL7lGExx2BDzv-Ji4dTw,17
54
+ auto_editor-27.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
docs/build.py CHANGED
@@ -19,13 +19,22 @@ def main():
19
19
  parser = main_options(parser)
20
20
 
21
21
  with open("src/ref/options.html", "w") as file:
22
- file.write(
23
- '{{ headerdesc "Options" "These are the options and flags that auto-editor uses." }}\n'
24
- "<body>\n"
25
- "{{ nav }}\n"
26
- '<section class="section">\n'
27
- '<div class="container">\n'
28
- )
22
+ file.write("""\
23
+ <!DOCTYPE html>
24
+ <html lang="en">
25
+ <head>
26
+ {{ init_head }}
27
+ {{ headerdesc "Options" "These are the options and flags that auto-editor uses." }}
28
+ {{ head_icon }}
29
+ <style>
30
+ {{ core_style }}
31
+ </style>
32
+ <body>
33
+ {{ nav }}
34
+ <section class="section">
35
+ <div class="container">
36
+ """)
37
+
29
38
  for op in parser.args:
30
39
  if isinstance(op, OptionText):
31
40
  file.write(f"<h2>{escape(op.text)}</h2>\n")
auto_editor/output.py DELETED
@@ -1,86 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os.path
4
- from dataclasses import dataclass, field
5
-
6
- import av
7
- from av.audio.resampler import AudioResampler
8
-
9
- from auto_editor.ffwrapper import FileInfo
10
- from auto_editor.utils.bar import Bar
11
- from auto_editor.utils.log import Log
12
- from auto_editor.utils.types import split_num_str
13
-
14
-
15
- def parse_bitrate(input_: str, log: Log) -> int:
16
- try:
17
- val, unit = split_num_str(input_)
18
- except Exception as e:
19
- log.error(e)
20
-
21
- if unit.lower() == "k":
22
- return int(val * 1000)
23
- if unit == "M":
24
- return int(val * 1_000_000)
25
- if unit == "G":
26
- return int(val * 1_000_000_000)
27
- if unit == "":
28
- return int(val)
29
-
30
- log.error(f"Unknown bitrate: {input_}")
31
-
32
-
33
- @dataclass(slots=True)
34
- class Ensure:
35
- _bar: Bar
36
- _sr: int
37
- log: Log
38
- _audios: list[tuple[FileInfo, int]] = field(default_factory=list)
39
-
40
- def audio(self, src: FileInfo, stream: int) -> str:
41
- try:
42
- label = self._audios.index((src, stream))
43
- first_time = False
44
- except ValueError:
45
- self._audios.append((src, stream))
46
- label = len(self._audios) - 1
47
- first_time = True
48
-
49
- out_path = os.path.join(self.log.temp, f"{label:x}.wav")
50
-
51
- if first_time:
52
- sample_rate = self._sr
53
- bar = self._bar
54
- self.log.debug(f"Making external audio: {out_path}")
55
-
56
- in_container = av.open(src.path, "r")
57
- out_container = av.open(
58
- out_path, "w", format="wav", options={"rf64": "always"}
59
- )
60
- astream = in_container.streams.audio[stream]
61
-
62
- if astream.duration is None or astream.time_base is None:
63
- dur = 1.0
64
- else:
65
- dur = float(astream.duration * astream.time_base)
66
-
67
- bar.start(dur, "Extracting audio")
68
-
69
- output_astream = out_container.add_stream(
70
- "pcm_s16le", layout="stereo", rate=sample_rate
71
- )
72
- resampler = AudioResampler(format="s16", layout="stereo", rate=sample_rate)
73
- for i, frame in enumerate(in_container.decode(astream)):
74
- if i % 1500 == 0 and frame.time is not None:
75
- bar.tick(frame.time)
76
-
77
- for new_frame in resampler.resample(frame):
78
- out_container.mux(output_astream.encode(new_frame))
79
-
80
- out_container.mux(output_astream.encode(None))
81
-
82
- out_container.close()
83
- in_container.close()
84
- bar.end()
85
-
86
- return out_path