auto-editor 24.27.1__py3-none-any.whl → 24.30.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.
@@ -5,6 +5,8 @@ from dataclasses import dataclass
5
5
  from fractions import Fraction
6
6
  from typing import Any
7
7
 
8
+ from numpy import float64
9
+
8
10
  from .data_structs import Sym, print_str
9
11
  from .err import MyError
10
12
 
@@ -41,7 +43,7 @@ def check_contract(c: object, val: object) -> bool:
41
43
  return val is True
42
44
  if c is False:
43
45
  return val is False
44
- if type(c) in (int, float, Fraction, complex, str, Sym):
46
+ if type(c) in (int, float, float64, Fraction, complex, str, Sym):
45
47
  return val == c
46
48
  raise MyError(f"Invalid contract, got: {print_str(c)}")
47
49
 
@@ -163,17 +165,21 @@ is_int = Contract("int?", lambda v: type(v) is int)
163
165
  is_nat = Contract("nat?", lambda v: type(v) is int and v > -1)
164
166
  is_nat1 = Contract("nat1?", lambda v: type(v) is int and v > 0)
165
167
  int_not_zero = Contract("(or/c (not/c 0) int?)", lambda v: v != 0 and is_int(v))
166
- is_num = Contract("number?", lambda v: type(v) in (int, float, Fraction, complex))
167
- is_real = Contract("real?", lambda v: type(v) in (int, float, Fraction))
168
- is_float = Contract("float?", lambda v: type(v) is float)
168
+ is_num = Contract(
169
+ "number?", lambda v: type(v) in (int, float, float64, Fraction, complex)
170
+ )
171
+ is_real = Contract("real?", lambda v: type(v) in (int, float, float64, Fraction))
172
+ is_float = Contract("float?", lambda v: type(v) in (float, float64))
169
173
  is_frac = Contract("frac?", lambda v: type(v) is Fraction)
170
174
  is_str = Contract("string?", lambda v: type(v) is str)
171
175
  any_p = Contract("any", lambda v: True)
172
176
  is_void = Contract("void?", lambda v: v is None)
173
- is_int_or_float = Contract("(or/c int? float?)", lambda v: type(v) in (int, float))
177
+ is_int_or_float = Contract(
178
+ "(or/c int? float?)", lambda v: type(v) in (int, float, float64)
179
+ )
174
180
  is_threshold = Contract(
175
181
  "threshold?",
176
- lambda v: type(v) in (int, float) and v >= 0 and v <= 1, # type: ignore
182
+ lambda v: type(v) in (int, float, float64) and v >= 0 and v <= 1, # type: ignore
177
183
  )
178
184
  is_proc = Contract("procedure?", lambda v: isinstance(v, Proc | Contract))
179
185
 
@@ -18,7 +18,6 @@ from auto_editor.utils.types import Args, CoerceError, time
18
18
  if TYPE_CHECKING:
19
19
  from numpy.typing import NDArray
20
20
 
21
- from auto_editor.output import Ensure
22
21
  from auto_editor.utils.bar import Bar
23
22
  from auto_editor.utils.chunks import Chunks
24
23
  from auto_editor.utils.log import Log
@@ -75,7 +74,6 @@ def make_av(src: FileInfo, all_clips: list[list[Clip]]) -> tuple[VSpace, ASpace]
75
74
  def run_interpreter_for_edit_option(
76
75
  text: str, filesetup: FileSetup
77
76
  ) -> NDArray[np.bool_]:
78
- ensure = filesetup.ensure
79
77
  src = filesetup.src
80
78
  tb = filesetup.tb
81
79
  bar = filesetup.bar
@@ -87,8 +85,8 @@ def run_interpreter_for_edit_option(
87
85
  if log.is_debug:
88
86
  log.debug(f"edit: {parser}")
89
87
 
90
- env["timebase"] = filesetup.tb
91
- env["@levels"] = Levels(ensure, src, tb, bar, temp, log)
88
+ env["timebase"] = tb
89
+ env["@levels"] = Levels(src, tb, bar, temp, log)
92
90
  env["@filesetup"] = filesetup
93
91
 
94
92
  results = interpret(env, parser)
@@ -139,7 +137,6 @@ def parse_time(val: str, arr: NDArray, tb: Fraction) -> int: # raises: `CoerceE
139
137
 
140
138
  def make_timeline(
141
139
  sources: list[FileInfo],
142
- ensure: Ensure,
143
140
  args: Args,
144
141
  sr: int,
145
142
  bar: Bar,
@@ -169,7 +166,7 @@ def make_timeline(
169
166
  concat = np.concatenate
170
167
 
171
168
  for i, src in enumerate(sources):
172
- filesetup = FileSetup(src, ensure, len(sources) < 2, tb, bar, temp, log)
169
+ filesetup = FileSetup(src, len(sources) < 2, tb, bar, temp, log)
173
170
 
174
171
  edit_result = run_interpreter_for_edit_option(method, filesetup)
175
172
  mut_margin(edit_result, start_margin, end_margin)
@@ -296,22 +293,4 @@ def make_timeline(
296
293
  else:
297
294
  v1_compatiable = None
298
295
 
299
- tl = v3(inp, tb, sr, res, args.background, vtl, atl, v1_compatiable)
300
-
301
- # Additional monotonic check, o(n^2) time complexity so disable by default.
302
-
303
- # if len(sources) != 1:
304
- # return tl
305
-
306
- # last_i = 0
307
- # for index in range(tl.end):
308
- # for layer in tl.v:
309
- # for lobj in layer:
310
- # if index >= lobj.start and index < (lobj.start + lobj.dur):
311
- # _i = round((lobj.offset + index - lobj.start) * lobj.speed)
312
- # if (_i < last_i):
313
- # print(_i, last_i)
314
- # raise ValueError("not monotonic")
315
- # last_i = _i
316
-
317
- return tl
296
+ return v3(inp, tb, sr, res, args.background, vtl, atl, v1_compatiable)
auto_editor/output.py CHANGED
@@ -57,7 +57,7 @@ class Ensure:
57
57
  output_astream = out_container.add_stream("pcm_s16le", rate=sample_rate)
58
58
  assert isinstance(output_astream, av.audio.stream.AudioStream)
59
59
 
60
- resampler = AudioResampler(format="s16", layout="stereo", rate=sample_rate) # type: ignore
60
+ resampler = AudioResampler(format="s16", layout="stereo", rate=sample_rate)
61
61
  for i, frame in enumerate(in_container.decode(astream)):
62
62
  if i % 1500 == 0:
63
63
  bar.tick(0 if frame.time is None else frame.time)
@@ -99,7 +99,7 @@ def _ffset(option: str, value: str | None) -> list[str]:
99
99
  return [option] + [value]
100
100
 
101
101
 
102
- def video_quality(args: Args, ctr: Container) -> list[str]:
102
+ def video_quality(args: Args) -> list[str]:
103
103
  return (
104
104
  _ffset("-b:v", args.video_bitrate)
105
105
  + ["-c:v", args.video_codec]
@@ -174,7 +174,7 @@ def mux_quality_media(
174
174
  for is_video, path in visual_output:
175
175
  if is_video:
176
176
  if apply_v:
177
- cmd += video_quality(args, ctr)
177
+ cmd += video_quality(args)
178
178
  else:
179
179
  # Real video is only allowed on track 0
180
180
  cmd += ["-c:v:0", "copy"]
@@ -211,7 +211,7 @@ def mux_quality_media(
211
211
  cmd.extend(["-c:s", scodec])
212
212
  elif ctr.scodecs is not None:
213
213
  if scodec not in ctr.scodecs:
214
- scodec = ctr.scodecs[0]
214
+ scodec = ctr.default_sub
215
215
  cmd.extend(["-c:s", scodec])
216
216
 
217
217
  if a_tracks > 0:
auto_editor/preview.py CHANGED
@@ -6,7 +6,6 @@ from statistics import fmean, median
6
6
  from typing import TextIO
7
7
 
8
8
  from auto_editor.analyze import Levels
9
- from auto_editor.output import Ensure
10
9
  from auto_editor.timeline import v3
11
10
  from auto_editor.utils.bar import Bar
12
11
  from auto_editor.utils.func import to_timecode
@@ -49,7 +48,7 @@ def all_cuts(tl: v3, in_len: int) -> list[int]:
49
48
  return cut_lens
50
49
 
51
50
 
52
- def preview(ensure: Ensure, tl: v3, temp: str, log: Log) -> None:
51
+ def preview(tl: v3, temp: str, log: Log) -> None:
53
52
  log.conwrite("")
54
53
  tb = tl.tb
55
54
 
@@ -66,7 +65,7 @@ def preview(ensure: Ensure, tl: v3, temp: str, log: Log) -> None:
66
65
 
67
66
  in_len = 0
68
67
  for src in all_sources:
69
- in_len += Levels(ensure, src, tb, Bar("none"), temp, log).media_length
68
+ in_len += Levels(src, tb, Bar("none"), temp, log).media_length
70
69
 
71
70
  out_len = tl.out_len()
72
71
 
@@ -49,7 +49,7 @@ class SubtitleParser:
49
49
  self.codec = codec
50
50
  self.contents = []
51
51
 
52
- if codec == "ass":
52
+ if codec == "ass" or codec == "ssa":
53
53
  time_code = re.compile(r"(.*)(\d+:\d+:[\d.]+)(.*)(\d+:\d+:[\d.]+)(.*)")
54
54
  elif codec == "webvtt":
55
55
  time_code = re.compile(r"()(\d+:[\d.]+)( --> )(\d+:[\d.]+)(\n.*)")
@@ -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,22 +2,30 @@ 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, builder_map
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
- from auto_editor.output import Ensure
13
+ from auto_editor.lib.contracts import is_bool, is_nat, is_nat1, is_str, is_void, orc
13
14
  from auto_editor.utils.bar import Bar
14
- from auto_editor.utils.cmdkw import ParserError, parse_with_palet
15
+ from auto_editor.utils.cmdkw import (
16
+ ParserError,
17
+ Required,
18
+ parse_with_palet,
19
+ pAttr,
20
+ pAttrs,
21
+ )
15
22
  from auto_editor.utils.func import setup_tempdir
16
23
  from auto_editor.utils.log import Log
17
24
  from auto_editor.utils.types import frame_rate
18
25
  from auto_editor.vanparse import ArgumentParser
19
26
 
20
27
  if TYPE_CHECKING:
28
+ from collections.abc import Iterator
21
29
  from fractions import Fraction
22
30
 
23
31
  from numpy.typing import NDArray
@@ -28,8 +36,6 @@ class LevelArgs:
28
36
  input: list[str] = field(default_factory=list)
29
37
  edit: str = "audio"
30
38
  timebase: Fraction | None = None
31
- ffmpeg_location: str | None = None
32
- my_ffmpeg: bool = False
33
39
  help: bool = False
34
40
 
35
41
 
@@ -47,16 +53,12 @@ def levels_options(parser: ArgumentParser) -> ArgumentParser:
47
53
  type=frame_rate,
48
54
  help="Set custom timebase",
49
55
  )
50
- parser.add_argument("--ffmpeg-location", help="Point to your custom ffmpeg file")
51
- parser.add_argument(
52
- "--my-ffmpeg",
53
- flag=True,
54
- help="Use the ffmpeg on your PATH instead of the one packaged",
55
- )
56
56
  return parser
57
57
 
58
58
 
59
59
  def print_arr(arr: NDArray) -> None:
60
+ print("")
61
+ print("@start")
60
62
  if arr.dtype == np.float64:
61
63
  for a in arr:
62
64
  sys.stdout.write(f"{a:.20f}\n")
@@ -66,14 +68,25 @@ def print_arr(arr: NDArray) -> None:
66
68
  else:
67
69
  for a in arr:
68
70
  sys.stdout.write(f"{a}\n")
71
+ sys.stdout.flush()
72
+ print("")
73
+
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("")
69
84
 
70
85
 
71
86
  def main(sys_args: list[str] = sys.argv[1:]) -> None:
72
87
  parser = levels_options(ArgumentParser("levels"))
73
88
  args = parser.parse_args(LevelArgs, sys_args)
74
89
 
75
- ffmpeg = FFmpeg(args.ffmpeg_location, args.my_ffmpeg)
76
-
77
90
  bar = Bar("none")
78
91
  temp = setup_tempdir(None, Log())
79
92
  log = Log(quiet=True, temp=temp)
@@ -85,44 +98,48 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
85
98
  src = sources[0]
86
99
 
87
100
  tb = src.get_fps() if args.timebase is None else args.timebase
88
- ensure = Ensure(ffmpeg, bar, src.get_sr(), temp, log)
89
101
 
90
102
  if ":" in args.edit:
91
103
  method, attrs = args.edit.split(":", 1)
92
104
  else:
93
105
  method, attrs = args.edit, ""
94
106
 
95
- for src in sources:
96
- print("")
97
- print("@start")
107
+ audio_builder = pAttrs("audio", pAttr("stream", 0, is_nat))
108
+ motion_builder = pAttrs(
109
+ "motion",
110
+ pAttr("stream", 0, is_nat),
111
+ pAttr("blur", 9, is_nat),
112
+ pAttr("width", 400, is_nat1),
113
+ )
114
+ subtitle_builder = pAttrs(
115
+ "subtitle",
116
+ pAttr("pattern", Required, is_str),
117
+ pAttr("stream", 0, is_nat),
118
+ pAttr("ignore-case", False, is_bool),
119
+ pAttr("max-count", None, orc(is_nat, is_void)),
120
+ )
98
121
 
99
- levels = Levels(ensure, src, tb, bar, temp, log)
122
+ builder_map = {
123
+ "audio": audio_builder,
124
+ "motion": motion_builder,
125
+ "subtitle": subtitle_builder,
126
+ }
100
127
 
128
+ for src in sources:
101
129
  if method in builder_map:
102
- builder = builder_map[method]
103
-
104
130
  try:
105
- obj = parse_with_palet(attrs, builder, env)
131
+ obj = parse_with_palet(attrs, builder_map[method], env)
106
132
  except ParserError as e:
107
133
  log.error(e)
108
134
 
109
- if "threshold" in obj:
110
- del obj["threshold"]
111
-
135
+ levels = Levels(src, tb, bar, temp, log)
112
136
  try:
113
137
  if method == "audio":
114
- print_arr(levels.audio(obj["stream"]))
138
+ print_arr_gen(iter_audio(src, tb, **obj))
115
139
  elif method == "motion":
116
- print_arr(levels.motion(obj["stream"], obj["blur"], obj["width"]))
140
+ print_arr_gen(iter_motion(src, tb, **obj))
117
141
  elif method == "subtitle":
118
- print_arr(
119
- levels.subtitle(
120
- obj["pattern"],
121
- obj["stream"],
122
- obj["ignore_case"],
123
- obj["max_count"],
124
- )
125
- )
142
+ print_arr(levels.subtitle(**obj))
126
143
  elif method == "none":
127
144
  print_arr(levels.none())
128
145
  elif method == "all/e":
@@ -132,8 +149,6 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
132
149
  except LevelError as e:
133
150
  log.error(e)
134
151
 
135
- sys.stdout.flush()
136
- print("")
137
152
  log.cleanup()
138
153
 
139
154
 
@@ -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
- bar = Bar("none")
77
- ensure = Ensure(ffmpeg, bar, src.get_sr(), temp, log)
68
+ bar = Bar("modern")
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