auto-editor 24.25.1__tar.gz → 24.29.1__tar.gz

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.
Files changed (65) hide show
  1. {auto_editor-24.25.1/auto_editor.egg-info → auto_editor-24.29.1}/PKG-INFO +3 -6
  2. {auto_editor-24.25.1 → auto_editor-24.29.1}/README.md +0 -3
  3. auto_editor-24.29.1/auto_editor/__init__.py +2 -0
  4. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/__main__.py +4 -4
  5. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/analyze.py +30 -100
  6. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/edit.py +3 -3
  7. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/ffwrapper.py +6 -2
  8. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/fcp7.py +2 -2
  9. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/shotcut.py +1 -2
  10. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/palet.py +29 -9
  11. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/contracts.py +12 -6
  12. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/make_layers.py +2 -20
  13. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/output.py +39 -4
  14. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/video.py +9 -9
  15. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/levels.py +39 -25
  16. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/repl.py +2 -2
  17. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/test.py +12 -5
  18. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/validate_input.py +9 -12
  19. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/vanparse.py +3 -3
  20. {auto_editor-24.25.1 → auto_editor-24.29.1/auto_editor.egg-info}/PKG-INFO +3 -6
  21. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/SOURCES.txt +0 -3
  22. auto_editor-24.29.1/auto_editor.egg-info/requires.txt +3 -0
  23. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/top_level.txt +0 -1
  24. {auto_editor-24.25.1 → auto_editor-24.29.1}/pyproject.toml +2 -2
  25. auto_editor-24.25.1/ae-ffmpeg/ae_ffmpeg/__init__.py +0 -16
  26. auto_editor-24.25.1/ae-ffmpeg/ae_ffmpeg/py.typed +0 -0
  27. auto_editor-24.25.1/ae-ffmpeg/setup.py +0 -65
  28. auto_editor-24.25.1/auto_editor/__init__.py +0 -2
  29. auto_editor-24.25.1/auto_editor.egg-info/requires.txt +0 -3
  30. {auto_editor-24.25.1 → auto_editor-24.29.1}/LICENSE +0 -0
  31. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/__init__.py +0 -0
  32. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/fcp11.py +0 -0
  33. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/json.py +0 -0
  34. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/utils.py +0 -0
  35. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/help.py +0 -0
  36. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/__init__.py +0 -0
  37. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/json.py +0 -0
  38. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/libmath.py +0 -0
  39. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/__init__.py +0 -0
  40. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/data_structs.py +0 -0
  41. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/err.py +0 -0
  42. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/preview.py +0 -0
  43. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/__init__.py +0 -0
  44. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/audio.py +0 -0
  45. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/subtitle.py +0 -0
  46. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/__init__.py +0 -0
  47. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/desc.py +0 -0
  48. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/info.py +0 -0
  49. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/palet.py +0 -0
  50. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/subdump.py +0 -0
  51. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/timeline.py +0 -0
  52. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/__init__.py +0 -0
  53. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/bar.py +0 -0
  54. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/chunks.py +0 -0
  55. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/cmdkw.py +0 -0
  56. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/container.py +0 -0
  57. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/encoder.py +0 -0
  58. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/func.py +0 -0
  59. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/log.py +0 -0
  60. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/subtitle_tools.py +0 -0
  61. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/types.py +0 -0
  62. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/wavfile.py +0 -0
  63. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/dependency_links.txt +0 -0
  64. {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/entry_points.txt +0 -0
  65. {auto_editor-24.25.1 → auto_editor-24.29.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 24.25.1
3
+ Version: 24.29.1
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -11,8 +11,8 @@ Keywords: video,audio,media,editor,editing,processing,nonlinear,automatic,silenc
11
11
  Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: numpy>=1.22.0
15
- Requires-Dist: pyav==12.1.0
14
+ Requires-Dist: numpy>=1.23.0
15
+ Requires-Dist: pyav==12.2.*
16
16
  Requires-Dist: ae-ffmpeg==1.2.*
17
17
 
18
18
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
@@ -182,6 +182,3 @@ auto-editor --margin --help
182
182
 
183
183
  ## Copyright
184
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)
185
-
186
- ae-ffmpeg is under the [LGPLv3 License](https://github.com/WyattBlue/auto-editor/blob/master/ae-ffmpeg/LICENSE.txt). The ffmpeg and ffprobe programs were created by the FFmpeg team.
187
-
@@ -165,6 +165,3 @@ auto-editor --margin --help
165
165
 
166
166
  ## Copyright
167
167
  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)
168
-
169
- ae-ffmpeg is under the [LGPLv3 License](https://github.com/WyattBlue/auto-editor/blob/master/ae-ffmpeg/LICENSE.txt). The ffmpeg and ffprobe programs were created by the FFmpeg team.
170
-
@@ -0,0 +1,2 @@
1
+ __version__ = "24.29.1"
2
+ version = "24w29a"
@@ -311,10 +311,10 @@ def main() -> None:
311
311
 
312
312
  import av
313
313
 
314
- print(f"Python Version: {plat.python_version()}")
315
- print(f"Platform: {plat.system()} {plat.release()} {plat.machine().lower()}")
316
- print(f"PyAV Version: {av.__version__}")
317
- print(f"Auto-Editor Version: {auto_editor.version}")
314
+ print(f"OS: {plat.system()} {plat.release()} {plat.machine().lower()}")
315
+ print(f"Python: {plat.python_version()}")
316
+ print(f"PyAV: {av.__version__}")
317
+ print(f"Auto-Editor: {auto_editor.version}")
318
318
  return
319
319
 
320
320
  if not args.input:
@@ -9,22 +9,6 @@ from typing import TYPE_CHECKING
9
9
  import numpy as np
10
10
 
11
11
  from auto_editor import version
12
- from auto_editor.lang.json import Lexer, Parser, dump
13
- from auto_editor.lib.contracts import (
14
- is_bool,
15
- is_nat,
16
- is_nat1,
17
- is_str,
18
- is_threshold,
19
- is_void,
20
- orc,
21
- )
22
- from auto_editor.lib.data_structs import Sym
23
- from auto_editor.utils.cmdkw import (
24
- Required,
25
- pAttr,
26
- pAttrs,
27
- )
28
12
  from auto_editor.utils.subtitle_tools import convert_ass_to_text
29
13
  from auto_editor.wavfile import read
30
14
 
@@ -41,35 +25,6 @@ if TYPE_CHECKING:
41
25
  from auto_editor.utils.log import Log
42
26
 
43
27
 
44
- audio_builder = pAttrs(
45
- "audio",
46
- pAttr("threshold", 0.04, is_threshold),
47
- pAttr("stream", 0, orc(is_nat, Sym("all"), "all")),
48
- pAttr("mincut", 6, is_nat),
49
- pAttr("minclip", 3, is_nat),
50
- )
51
- motion_builder = pAttrs(
52
- "motion",
53
- pAttr("threshold", 0.02, is_threshold),
54
- pAttr("stream", 0, is_nat),
55
- pAttr("blur", 9, is_nat),
56
- pAttr("width", 400, is_nat1),
57
- )
58
- subtitle_builder = pAttrs(
59
- "subtitle",
60
- pAttr("pattern", Required, is_str),
61
- pAttr("stream", 0, is_nat),
62
- pAttr("ignore-case", False, is_bool),
63
- pAttr("max-count", None, orc(is_nat, is_void)),
64
- )
65
-
66
- builder_map = {
67
- "audio": audio_builder,
68
- "motion": motion_builder,
69
- "subtitle": subtitle_builder,
70
- }
71
-
72
-
73
28
  @dataclass(slots=True)
74
29
  class FileSetup:
75
30
  src: FileInfo
@@ -90,10 +45,6 @@ def link_nodes(*nodes: FilterContext) -> None:
90
45
  c.link_to(n)
91
46
 
92
47
 
93
- def to_threshold(arr: np.ndarray, t: int | float) -> NDArray[np.bool_]:
94
- return np.fromiter((x >= t for x in arr), dtype=np.bool_)
95
-
96
-
97
48
  def mut_remove_small(
98
49
  arr: NDArray[np.bool_], lim: int, replace: int, with_: int
99
50
  ) -> None:
@@ -193,63 +144,43 @@ class Levels:
193
144
 
194
145
  def read_cache(self, tag: str, obj: dict[str, Any]) -> None | np.ndarray:
195
146
  workfile = os.path.join(
196
- os.path.dirname(self.temp), f"ae-{version}", "cache.json"
147
+ os.path.dirname(self.temp), f"ae-{version}", "cache.npz"
197
148
  )
198
149
 
199
150
  try:
200
- with open(workfile, encoding="utf-8") as file:
201
- cache = Parser(Lexer(workfile, file)).expr()
202
- except Exception:
151
+ npzfile = np.load(workfile, allow_pickle=False)
152
+ except Exception as e:
153
+ self.log.debug(e)
203
154
  return None
204
155
 
205
- if f"{self.src.path.resolve()}" not in cache:
156
+ key = f"{self.src.path}:{obj_tag(tag, self.tb, obj)}"
157
+ if key not in npzfile.files:
206
158
  return None
207
159
 
208
- key = obj_tag(tag, self.tb, obj)
209
-
210
- if key not in (root := cache[f"{self.src.path.resolve()}"]):
211
- return None
212
-
213
- return np.asarray(root[key]["arr"], dtype=root[key]["type"])
160
+ self.log.debug("Using cache")
161
+ return npzfile[key]
214
162
 
215
163
  def cache(self, tag: str, obj: dict[str, Any], arr: np.ndarray) -> np.ndarray:
216
164
  workdur = os.path.join(os.path.dirname(self.temp), f"ae-{version}")
217
- workfile = os.path.join(workdur, "cache.json")
218
165
  if not os.path.exists(workdur):
219
166
  os.mkdir(workdur)
220
167
 
221
- key = obj_tag(tag, self.tb, obj)
222
-
223
- try:
224
- with open(workfile, encoding="utf-8") as file:
225
- json_object = Parser(Lexer(workfile, file)).expr()
226
- except Exception:
227
- json_object = {}
228
-
229
- entry = {"type": str(arr.dtype), "arr": arr.tolist()}
230
- src_key = f"{self.src.path}"
231
-
232
- if src_key in json_object:
233
- json_object[src_key][key] = entry
234
- else:
235
- json_object[src_key] = {key: entry}
236
-
237
- with open(os.path.join(workdur, "cache.json"), "w", encoding="utf-8") as file:
238
- dump(json_object, file)
168
+ tag = obj_tag(tag, self.tb, obj)
169
+ np.savez(os.path.join(workdur, "cache.npz"), **{f"{self.src.path}:{tag}": arr})
239
170
 
240
171
  return arr
241
172
 
242
- def audio(self, s: int) -> NDArray[np.float64]:
243
- if s > len(self.src.audios) - 1:
244
- raise LevelError(f"audio: audio stream '{s}' does not exist.")
173
+ def audio(self, stream: int) -> NDArray[np.float64]:
174
+ if stream > len(self.src.audios) - 1:
175
+ raise LevelError(f"audio: audio stream '{stream}' does not exist.")
245
176
 
246
- if (arr := self.read_cache("audio", {"stream": s})) is not None:
177
+ if (arr := self.read_cache("audio", {"stream": stream})) is not None:
247
178
  return arr
248
179
 
249
- sr, samples = read(self.ensure.audio(self.src, s))
180
+ sr, samples = read(self.ensure.audio(self.src, stream))
250
181
 
251
182
  if len(samples) == 0:
252
- raise LevelError(f"audio: stream '{s}' has no samples.")
183
+ raise LevelError(f"audio: stream '{stream}' has no samples.")
253
184
 
254
185
  def get_max_volume(s: np.ndarray) -> float:
255
186
  return max(float(np.max(s)), -float(np.min(s)))
@@ -262,7 +193,7 @@ class Levels:
262
193
 
263
194
  if samp_per_ticks < 1:
264
195
  self.log.error(
265
- f"audio: stream '{s}'\n Samplerate ({sr}) must be greater than "
196
+ f"audio: stream '{stream}'\n Samplerate ({sr}) must be greater than "
266
197
  f"or equal to timebase ({self.tb})\n"
267
198
  " Try `-fps 30` and/or `--sample-rate 48000`"
268
199
  )
@@ -289,11 +220,11 @@ class Levels:
289
220
  threshold_list[i] = get_max_volume(samples[start:end]) / max_volume
290
221
 
291
222
  self.bar.end()
292
- return self.cache("audio", {"stream": s}, threshold_list)
223
+ return self.cache("audio", {"stream": stream}, threshold_list)
293
224
 
294
225
  def subtitle(
295
226
  self,
296
- patterns: str,
227
+ pattern: str,
297
228
  stream: int,
298
229
  ignore_case: bool,
299
230
  max_count: int | None,
@@ -303,8 +234,7 @@ class Levels:
303
234
 
304
235
  try:
305
236
  flags = re.IGNORECASE if ignore_case else 0
306
- pattern = re.compile(patterns, flags)
307
- del patterns # make sure we don't accidentally use it
237
+ re_pattern = re.compile(pattern, flags)
308
238
  except re.error as e:
309
239
  self.log.error(e)
310
240
 
@@ -362,7 +292,7 @@ class Levels:
362
292
  else:
363
293
  continue
364
294
 
365
- if line and re.search(pattern, line):
295
+ if line and re.search(re_pattern, line):
366
296
  result[san_start:san_end] = 1
367
297
  count += 1
368
298
 
@@ -370,22 +300,22 @@ class Levels:
370
300
 
371
301
  return result
372
302
 
373
- def motion(self, s: int, blur: int, width: int) -> NDArray[np.float64]:
303
+ def motion(self, stream: int, blur: int, width: int) -> NDArray[np.float64]:
374
304
  import av
375
305
 
376
- if s >= len(self.src.videos):
377
- raise LevelError(f"motion: video stream '{s}' does not exist.")
306
+ if stream >= len(self.src.videos):
307
+ raise LevelError(f"motion: video stream '{stream}' does not exist.")
378
308
 
379
- mobj = {"stream": s, "width": width, "blur": blur}
309
+ mobj = {"stream": stream, "width": width, "blur": blur}
380
310
  if (arr := self.read_cache("motion", mobj)) is not None:
381
311
  return arr
382
312
 
383
313
  container = av.open(f"{self.src.path}", "r")
384
314
 
385
- stream = container.streams.video[s]
386
- stream.thread_type = "AUTO"
315
+ video = container.streams.video[stream]
316
+ video.thread_type = "AUTO"
387
317
 
388
- inaccurate_dur = 1 if stream.duration is None else stream.duration
318
+ inaccurate_dur = 1 if video.duration is None else video.duration
389
319
  self.bar.start(inaccurate_dur, "Analyzing motion")
390
320
 
391
321
  prev_frame = None
@@ -395,7 +325,7 @@ class Levels:
395
325
 
396
326
  graph = av.filter.Graph()
397
327
  link_nodes(
398
- graph.add_buffer(template=stream),
328
+ graph.add_buffer(template=video),
399
329
  graph.add("scale", f"{width}:-1"),
400
330
  graph.add("format", "gray"),
401
331
  graph.add("gblur", f"sigma={blur}"),
@@ -405,7 +335,7 @@ class Levels:
405
335
 
406
336
  threshold_list = np.zeros((1024), dtype=np.float64)
407
337
 
408
- for unframe in container.decode(stream):
338
+ for unframe in container.decode(video):
409
339
  graph.push(unframe)
410
340
  frame = graph.pull()
411
341
 
@@ -160,7 +160,7 @@ def edit_media(
160
160
  if path_ext == ".xml":
161
161
  from auto_editor.formats.fcp7 import fcp7_read_xml
162
162
 
163
- tl = fcp7_read_xml(paths[0], ffmpeg, log)
163
+ tl = fcp7_read_xml(paths[0], log)
164
164
  assert tl.src is not None
165
165
  sources: list[FileInfo] = [tl.src]
166
166
  src: FileInfo | None = tl.src
@@ -168,7 +168,7 @@ def edit_media(
168
168
  elif path_ext == ".mlt":
169
169
  from auto_editor.formats.shotcut import shotcut_read_mlt
170
170
 
171
- tl = shotcut_read_mlt(paths[0], ffmpeg, log)
171
+ tl = shotcut_read_mlt(paths[0], log)
172
172
  assert tl.src is not None
173
173
  sources = [tl.src]
174
174
  src = tl.src
@@ -209,7 +209,7 @@ def edit_media(
209
209
  else:
210
210
  samplerate = args.sample_rate
211
211
 
212
- ensure = Ensure(ffmpeg, samplerate, temp, log)
212
+ ensure = Ensure(ffmpeg, bar, samplerate, temp, log)
213
213
 
214
214
  if tl is None:
215
215
  tl = make_timeline(sources, ensure, args, samplerate, bar, temp, log)
@@ -11,6 +11,8 @@ from shutil import which
11
11
  from subprocess import PIPE, Popen
12
12
  from typing import Any
13
13
 
14
+ import av
15
+
14
16
  from auto_editor.utils.func import get_stdout
15
17
  from auto_editor.utils.log import Log
16
18
 
@@ -190,10 +192,12 @@ class FileInfo:
190
192
 
191
193
 
192
194
  def initFileInfo(path: str, log: Log) -> FileInfo:
193
- import av
194
-
195
195
  try:
196
196
  cont = av.open(path, "r")
197
+ except av.error.FileNotFoundError:
198
+ log.error(f"Could not find '{path}'")
199
+ except av.error.IsADirectoryError:
200
+ log.error(f"Expected a media file, but got a directory: {path}")
197
201
  except av.error.InvalidDataError:
198
202
  log.error(f"Invalid data when processing: {path}")
199
203
 
@@ -7,7 +7,7 @@ from math import ceil
7
7
  from typing import TYPE_CHECKING
8
8
  from xml.etree.ElementTree import Element
9
9
 
10
- from auto_editor.ffwrapper import FFmpeg, FileInfo, initFileInfo
10
+ from auto_editor.ffwrapper import FileInfo, initFileInfo
11
11
  from auto_editor.timeline import ASpace, TlAudio, TlVideo, VSpace, v3
12
12
 
13
13
  from .utils import Validator, show
@@ -177,7 +177,7 @@ def read_filters(clipitem: Element, log: Log) -> float:
177
177
  return 1.0
178
178
 
179
179
 
180
- def fcp7_read_xml(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
180
+ def fcp7_read_xml(path: str, log: Log) -> v3:
181
181
  def xml_bool(val: str) -> bool:
182
182
  if val == "TRUE":
183
183
  return True
@@ -9,7 +9,6 @@ from auto_editor.utils.func import aspect_ratio, to_timecode
9
9
  if TYPE_CHECKING:
10
10
  from collections.abc import Sequence
11
11
 
12
- from auto_editor.ffwrapper import FFmpeg
13
12
  from auto_editor.timeline import TlAudio, TlVideo
14
13
  from auto_editor.utils.log import Log
15
14
 
@@ -22,7 +21,7 @@ https://mltframework.org/docs/mltxml/
22
21
  """
23
22
 
24
23
 
25
- def shotcut_read_mlt(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
24
+ def shotcut_read_mlt(path: str, log: Log) -> v3:
26
25
  raise NotImplementedError
27
26
 
28
27
 
@@ -18,12 +18,7 @@ from typing import TYPE_CHECKING
18
18
  import numpy as np
19
19
  from numpy import logical_and, logical_not, logical_or, logical_xor
20
20
 
21
- from auto_editor.analyze import (
22
- LevelError,
23
- mut_remove_large,
24
- mut_remove_small,
25
- to_threshold,
26
- )
21
+ from auto_editor.analyze import LevelError, mut_remove_large, mut_remove_small
27
22
  from auto_editor.lib.contracts import *
28
23
  from auto_editor.lib.data_structs import *
29
24
  from auto_editor.lib.err import MyError
@@ -690,6 +685,9 @@ def palet_map(proc: Proc, seq: Any) -> Any:
690
685
  return Quoted(tuple(map(proc, seq.val)))
691
686
  if isinstance(seq, list | range):
692
687
  return list(map(proc, seq))
688
+ elif isinstance(seq, np.ndarray):
689
+ vectorized_proc = np.vectorize(proc)
690
+ return vectorized_proc(seq)
693
691
  return proc(seq)
694
692
 
695
693
 
@@ -1469,6 +1467,26 @@ def edit_all() -> np.ndarray:
1469
1467
  return env["@levels"].all()
1470
1468
 
1471
1469
 
1470
+ def audio_levels(stream: int) -> np.ndarray:
1471
+ if "@levels" not in env:
1472
+ raise MyError("Can't use `audio` if there's no input media")
1473
+
1474
+ try:
1475
+ return env["@levels"].audio(stream)
1476
+ except LevelError as e:
1477
+ raise MyError(e)
1478
+
1479
+
1480
+ def motion_levels(stream: int, blur: int = 9, width: int = 400) -> np.ndarray:
1481
+ if "@levels" not in env:
1482
+ raise MyError("Can't use `motion` if there's no input media")
1483
+
1484
+ try:
1485
+ return env["@levels"].motion(stream, blur, width)
1486
+ except LevelError as e:
1487
+ raise MyError(e)
1488
+
1489
+
1472
1490
  def edit_audio(
1473
1491
  threshold: float = 0.04,
1474
1492
  stream: object = Sym("all"),
@@ -1491,7 +1509,7 @@ def edit_audio(
1491
1509
 
1492
1510
  try:
1493
1511
  for s in stream_range:
1494
- audio_list = to_threshold(levels.audio(s), threshold)
1512
+ audio_list = levels.audio(s) >= threshold
1495
1513
  if stream_data is None:
1496
1514
  stream_data = audio_list
1497
1515
  else:
@@ -1521,7 +1539,7 @@ def edit_motion(
1521
1539
  levels = env["@levels"]
1522
1540
  strict = env["@filesetup"].strict
1523
1541
  try:
1524
- return to_threshold(levels.motion(stream, blur, width), threshold)
1542
+ return levels.motion(stream, blur, width) >= threshold
1525
1543
  except LevelError as e:
1526
1544
  return raise_(e) if strict else levels.all()
1527
1545
 
@@ -1582,7 +1600,7 @@ def my_eval(env: Env, node: object) -> Any:
1582
1600
  return ref(oper, my_eval(env, node[1]))
1583
1601
 
1584
1602
  raise MyError(
1585
- f"Tried to run: {print_str(oper)} with args: {print_str(node[1:])}"
1603
+ f"{print_str(oper)} is not a function. Tried to run with args: {print_str(node[1:])}"
1586
1604
  )
1587
1605
 
1588
1606
  if type(oper) is Syntax:
@@ -1617,10 +1635,12 @@ env.update({
1617
1635
  # edit procedures
1618
1636
  "none": Proc("none", edit_none, (0, 0)),
1619
1637
  "all/e": Proc("all/e", edit_all, (0, 0)),
1638
+ "audio-levels": Proc("audio-levels", audio_levels, (1, 1), is_nat),
1620
1639
  "audio": Proc("audio", edit_audio, (0, 4),
1621
1640
  is_threshold, orc(is_nat, Sym("all")), is_nat,
1622
1641
  {"threshold": 0, "stream": 1, "minclip": 2, "mincut": 2}
1623
1642
  ),
1643
+ "motion-levels": Proc("motion-levels", motion_levels, (1, 3), is_nat, is_nat1, {"blur": 1, "width": 2}),
1624
1644
  "motion": Proc("motion", edit_motion, (0, 4),
1625
1645
  is_threshold, is_nat, is_nat1,
1626
1646
  {"threshold": 0, "stream": 1, "blur": 1, "width": 2}
@@ -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
 
@@ -38,7 +38,7 @@ def clipify(chunks: Chunks, src: FileInfo, start: int = 0) -> list[Clip]:
38
38
  clips: list[Clip] = []
39
39
  i = 0
40
40
  for chunk in chunks:
41
- if chunk[2] != 99999:
41
+ if chunk[2] > 0 and chunk[2] < 99999.0:
42
42
  dur = round((chunk[1] - chunk[0]) / chunk[2])
43
43
  if dur == 0:
44
44
  continue
@@ -296,22 +296,4 @@ def make_timeline(
296
296
  else:
297
297
  v1_compatiable = None
298
298
 
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
299
+ return v3(inp, tb, sr, res, args.background, vtl, atl, v1_compatiable)
@@ -4,7 +4,11 @@ import os.path
4
4
  from dataclasses import dataclass, field
5
5
  from fractions import Fraction
6
6
 
7
+ import av
8
+ from av.audio.resampler import AudioResampler
9
+
7
10
  from auto_editor.ffwrapper import FFmpeg, FileInfo
11
+ from auto_editor.utils.bar import Bar
8
12
  from auto_editor.utils.container import Container
9
13
  from auto_editor.utils.log import Log
10
14
  from auto_editor.utils.types import Args
@@ -13,6 +17,7 @@ from auto_editor.utils.types import Args
13
17
  @dataclass(slots=True)
14
18
  class Ensure:
15
19
  _ffmpeg: FFmpeg
20
+ _bar: Bar
16
21
  _sr: int
17
22
  temp: str
18
23
  log: Log
@@ -31,12 +36,42 @@ class Ensure:
31
36
  out_path = os.path.join(self.temp, f"{label:x}.wav")
32
37
 
33
38
  if first_time:
39
+ sample_rate = self._sr
40
+ bar = self._bar
34
41
  self.log.debug(f"Making external audio: {out_path}")
35
- self.log.conwrite("Extracting audio")
36
42
 
37
- cmd = ["-i", f"{src.path}", "-map", f"0:a:{stream}"]
38
- cmd += ["-ac", "2", "-ar", f"{self._sr}", "-rf64", "always", out_path]
39
- self._ffmpeg.run(cmd)
43
+ in_container = av.open(src.path, "r")
44
+ out_container = av.open(
45
+ out_path, "w", format="wav", options={"rf64": "always"}
46
+ )
47
+ astream = in_container.streams.audio[stream]
48
+
49
+ if astream.duration is None or astream.time_base is None:
50
+ dur = 1
51
+ else:
52
+ dur = int(astream.duration * astream.time_base)
53
+
54
+ bar.start(dur, "Extracting audio")
55
+
56
+ # PyAV always uses "stereo" layout, which is what we want.
57
+ output_astream = out_container.add_stream("pcm_s16le", rate=sample_rate)
58
+ assert isinstance(output_astream, av.audio.stream.AudioStream)
59
+
60
+ resampler = AudioResampler(format="s16", layout="stereo", rate=sample_rate) # type: ignore
61
+ for i, frame in enumerate(in_container.decode(astream)):
62
+ if i % 1500 == 0:
63
+ bar.tick(0 if frame.time is None else frame.time)
64
+
65
+ for new_frame in resampler.resample(frame):
66
+ for packet in output_astream.encode(new_frame):
67
+ out_container.mux_one(packet)
68
+
69
+ for packet in output_astream.encode():
70
+ out_container.mux_one(packet)
71
+
72
+ out_container.close()
73
+ in_container.close()
74
+ bar.end()
40
75
 
41
76
  return out_path
42
77
 
@@ -103,8 +103,8 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
103
103
  graph.add("scale", f"{obj.width}:-1"),
104
104
  graph.add("buffersink"),
105
105
  )
106
- graph.push(frame)
107
- frame = graph.pull()
106
+ graph.vpush(frame)
107
+ frame = graph.vpull()
108
108
  img_cache[(obj.src, obj.width)] = frame.to_ndarray(
109
109
  format="rgb24"
110
110
  )
@@ -122,7 +122,7 @@ def render_av(
122
122
  log: Log,
123
123
  ) -> tuple[str, bool]:
124
124
  src = tl.src
125
- cns: dict[FileInfo, av.InputContainer] = {}
125
+ cns: dict[FileInfo, av.container.InputContainer] = {}
126
126
  decoders: dict[FileInfo, Iterator[av.VideoFrame]] = {}
127
127
  seek_cost: dict[FileInfo, int] = {}
128
128
  tous: dict[FileInfo, int] = {}
@@ -302,8 +302,8 @@ def render_av(
302
302
  graph.add("pad", f"{width}:{height}:-1:-1:color={bg}"),
303
303
  graph.add("buffersink"),
304
304
  )
305
- graph.push(frame)
306
- frame = graph.pull()
305
+ graph.vpush(frame)
306
+ frame = graph.vpull()
307
307
  elif isinstance(obj, TlRect):
308
308
  graph = av.filter.Graph()
309
309
  x, y = apply_anchor(obj.x, obj.y, obj.width, obj.height, obj.anchor)
@@ -315,8 +315,8 @@ def render_av(
315
315
  ),
316
316
  graph.add("buffersink"),
317
317
  )
318
- graph.push(frame)
319
- frame = graph.pull()
318
+ graph.vpush(frame)
319
+ frame = graph.vpull()
320
320
  elif isinstance(obj, TlImage):
321
321
  img = img_cache[(obj.src, obj.width)]
322
322
  array = frame.to_ndarray(format="rgb24")
@@ -355,8 +355,8 @@ def render_av(
355
355
  frame = av.VideoFrame.from_ndarray(array, format="rgb24")
356
356
 
357
357
  if scale_graph is not None and frame.width != target_width:
358
- scale_graph.push(frame)
359
- frame = scale_graph.pull()
358
+ scale_graph.vpush(frame)
359
+ frame = scale_graph.vpull()
360
360
 
361
361
  if frame.format.name != target_pix_fmt:
362
362
  frame = frame.reformat(format=target_pix_fmt)
@@ -6,12 +6,19 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  import numpy as np
8
8
 
9
- from auto_editor.analyze import LevelError, Levels, builder_map
9
+ from auto_editor.analyze import LevelError, Levels
10
10
  from auto_editor.ffwrapper import FFmpeg, initFileInfo
11
11
  from auto_editor.lang.palet import env
12
+ from auto_editor.lib.contracts import is_bool, is_nat, is_nat1, is_str, is_void, orc
12
13
  from auto_editor.output import Ensure
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
@@ -57,6 +64,8 @@ def levels_options(parser: ArgumentParser) -> ArgumentParser:
57
64
 
58
65
 
59
66
  def print_arr(arr: NDArray) -> None:
67
+ print("")
68
+ print("@start")
60
69
  if arr.dtype == np.float64:
61
70
  for a in arr:
62
71
  sys.stdout.write(f"{a:.20f}\n")
@@ -66,6 +75,8 @@ def print_arr(arr: NDArray) -> None:
66
75
  else:
67
76
  for a in arr:
68
77
  sys.stdout.write(f"{a}\n")
78
+ sys.stdout.flush()
79
+ print("")
69
80
 
70
81
 
71
82
  def main(sys_args: list[str] = sys.argv[1:]) -> None:
@@ -85,44 +96,49 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
85
96
  src = sources[0]
86
97
 
87
98
  tb = src.get_fps() if args.timebase is None else args.timebase
88
- ensure = Ensure(ffmpeg, src.get_sr(), temp, log)
99
+ ensure = Ensure(ffmpeg, bar, src.get_sr(), temp, log)
89
100
 
90
101
  if ":" in args.edit:
91
102
  method, attrs = args.edit.split(":", 1)
92
103
  else:
93
104
  method, attrs = args.edit, ""
94
105
 
95
- for src in sources:
96
- print("")
97
- print("@start")
106
+ audio_builder = pAttrs("audio", pAttr("stream", 0, is_nat))
107
+ motion_builder = pAttrs(
108
+ "motion",
109
+ pAttr("stream", 0, is_nat),
110
+ pAttr("blur", 9, is_nat),
111
+ pAttr("width", 400, is_nat1),
112
+ )
113
+ subtitle_builder = pAttrs(
114
+ "subtitle",
115
+ pAttr("pattern", Required, is_str),
116
+ pAttr("stream", 0, is_nat),
117
+ pAttr("ignore-case", False, is_bool),
118
+ pAttr("max-count", None, orc(is_nat, is_void)),
119
+ )
98
120
 
99
- levels = Levels(ensure, src, tb, bar, temp, log)
121
+ builder_map = {
122
+ "audio": audio_builder,
123
+ "motion": motion_builder,
124
+ "subtitle": subtitle_builder,
125
+ }
100
126
 
127
+ for src in sources:
101
128
  if method in builder_map:
102
- builder = builder_map[method]
103
-
104
129
  try:
105
- obj = parse_with_palet(attrs, builder, env)
130
+ obj = parse_with_palet(attrs, builder_map[method], env)
106
131
  except ParserError as e:
107
132
  log.error(e)
108
133
 
109
- if "threshold" in obj:
110
- del obj["threshold"]
111
-
134
+ levels = Levels(ensure, src, tb, bar, temp, log)
112
135
  try:
113
136
  if method == "audio":
114
- print_arr(levels.audio(obj["stream"]))
137
+ print_arr(levels.audio(**obj))
115
138
  elif method == "motion":
116
- print_arr(levels.motion(obj["stream"], obj["blur"], obj["width"]))
139
+ print_arr(levels.motion(**obj))
117
140
  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
- )
141
+ print_arr(levels.subtitle(**obj))
126
142
  elif method == "none":
127
143
  print_arr(levels.none())
128
144
  elif method == "all/e":
@@ -132,8 +148,6 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
132
148
  except LevelError as e:
133
149
  log.error(e)
134
150
 
135
- sys.stdout.flush()
136
- print("")
137
151
  log.cleanup()
138
152
 
139
153
 
@@ -73,8 +73,8 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
73
73
  sources = [initFileInfo(path, log) for path in args.input]
74
74
  src = sources[0]
75
75
  tb = src.get_fps() if args.timebase is None else args.timebase
76
- ensure = Ensure(ffmpeg, src.get_sr(), temp, log)
77
- bar = Bar("none")
76
+ bar = Bar("modern")
77
+ ensure = Ensure(ffmpeg, bar, src.get_sr(), temp, log)
78
78
  env["timebase"] = tb
79
79
  env["@levels"] = Levels(ensure, src, tb, bar, temp, log)
80
80
  env["@filesetup"] = FileSetup(src, ensure, strict, tb, bar, temp, log)
@@ -340,11 +340,19 @@ def main(sys_args: list[str] | None = None):
340
340
  def track_tests():
341
341
  return run.main(["resources/multi-track.mov"], ["--keep_tracks_seperate"])
342
342
 
343
- def json_tests():
343
+ def export_json_tests():
344
344
  out = run.main(["example.mp4"], ["--export_as_json"])
345
345
  out2 = run.main([out], [])
346
346
  return out, out2
347
347
 
348
+ def import_v1_tests():
349
+ with open("v1.json", "w") as file:
350
+ file.write(
351
+ """{"version": "1", "source": "example.mp4", "chunks": [ [0, 26, 1.0], [26, 34, 0] ]}"""
352
+ )
353
+
354
+ return run.main(["v1.json"], [])
355
+
348
356
  def premiere_named_export():
349
357
  run.main(["example.mp4"], ["--export", 'premiere:name="Foo Bar"'])
350
358
 
@@ -524,9 +532,7 @@ def main(sys_args: list[str] | None = None):
524
532
  # Issue 280
525
533
  def SAR():
526
534
  out = run.main(["resources/SAR-2by3.mp4"], [])
527
-
528
- # It's working, PyAV just can't detect the changes.
529
- # assert checker.check(out).videos[0].sar == Fraction(2, 3)
535
+ assert checker.check(out).videos[0].sar == Fraction(2, 3)
530
536
 
531
537
  return out
532
538
 
@@ -711,7 +717,8 @@ def main(sys_args: list[str] | None = None):
711
717
  edit_positive_tests,
712
718
  audio_norm_f,
713
719
  audio_norm_ebu,
714
- json_tests,
720
+ export_json_tests,
721
+ import_v1_tests,
715
722
  high_speed_test,
716
723
  video_speed,
717
724
  multi_track_edit,
@@ -67,20 +67,17 @@ def download_video(my_input: str, args: Args, ffmpeg: FFmpeg, log: Log) -> str:
67
67
 
68
68
 
69
69
  def valid_input(inputs: list[str], ffmpeg: FFmpeg, args: Args, log: Log) -> list[str]:
70
- new_inputs = []
70
+ result = []
71
71
 
72
72
  for my_input in inputs:
73
- if os.path.isfile(my_input):
73
+ if my_input.startswith("http://") or my_input.startswith("https://"):
74
+ result.append(download_video(my_input, args, ffmpeg, log))
75
+ else:
74
76
  _, ext = os.path.splitext(my_input)
75
77
  if ext == "":
76
- log.error("File must have an extension.")
77
- new_inputs.append(my_input)
78
-
79
- elif my_input.startswith("http://") or my_input.startswith("https://"):
80
- new_inputs.append(download_video(my_input, args, ffmpeg, log))
81
- else:
82
- if os.path.isdir(my_input):
83
- log.error("Input must be a file or a URL, not a directory.")
84
- log.error(f"Could not find '{my_input}'")
78
+ if os.path.isdir(my_input):
79
+ log.error("Input must be a file or a URL, not a directory.")
80
+ log.error("Input file must have an extension.")
81
+ result.append(my_input)
85
82
 
86
- return new_inputs
83
+ return result
@@ -119,7 +119,7 @@ def to_key(op: Options | Required) -> str:
119
119
  return op.names[0][:2].replace("-", "") + op.names[0][2:].replace("-", "_")
120
120
 
121
121
 
122
- def print_option_help(program_name: str | None, ns_obj: T, option: Options) -> None:
122
+ def print_option_help(name: str | None, ns_obj: object, option: Options) -> None:
123
123
  text = StringIO()
124
124
  text.write(
125
125
  f" {', '.join(option.names)} {'' if option.metavar is None else option.metavar}\n\n"
@@ -145,8 +145,8 @@ def print_option_help(program_name: str | None, ns_obj: T, option: Options) -> N
145
145
 
146
146
  from auto_editor.help import data
147
147
 
148
- if program_name is not None and option.names[0] in data[program_name]:
149
- text.write(indent(data[program_name][option.names[0]], " ") + "\n")
148
+ if name is not None and option.names[0] in data[name]:
149
+ text.write(indent(data[name][option.names[0]], " ") + "\n")
150
150
  else:
151
151
  text.write(f" {option.help}\n\n")
152
152
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 24.25.1
3
+ Version: 24.29.1
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -11,8 +11,8 @@ Keywords: video,audio,media,editor,editing,processing,nonlinear,automatic,silenc
11
11
  Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: numpy>=1.22.0
15
- Requires-Dist: pyav==12.1.0
14
+ Requires-Dist: numpy>=1.23.0
15
+ Requires-Dist: pyav==12.2.*
16
16
  Requires-Dist: ae-ffmpeg==1.2.*
17
17
 
18
18
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
@@ -182,6 +182,3 @@ auto-editor --margin --help
182
182
 
183
183
  ## Copyright
184
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)
185
-
186
- ae-ffmpeg is under the [LGPLv3 License](https://github.com/WyattBlue/auto-editor/blob/master/ae-ffmpeg/LICENSE.txt). The ffmpeg and ffprobe programs were created by the FFmpeg team.
187
-
@@ -1,9 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- ae-ffmpeg/setup.py
5
- ae-ffmpeg/ae_ffmpeg/__init__.py
6
- ae-ffmpeg/ae_ffmpeg/py.typed
7
4
  auto_editor/__init__.py
8
5
  auto_editor/__main__.py
9
6
  auto_editor/analyze.py
@@ -0,0 +1,3 @@
1
+ numpy>=1.23.0
2
+ pyav==12.2.*
3
+ ae-ffmpeg==1.2.*
@@ -1,4 +1,3 @@
1
- ae-ffmpeg
2
1
  auto_editor
3
2
  changelogs
4
3
  dist
@@ -9,8 +9,8 @@ license = { text = "Unlicense" }
9
9
  authors = [{ name = "WyattBlue", email = "wyattblue@auto-editor.com" }]
10
10
  requires-python = ">=3.10"
11
11
  dependencies = [
12
- "numpy>=1.22.0",
13
- "pyav==12.1.0",
12
+ "numpy>=1.23.0",
13
+ "pyav==12.2.*",
14
14
  "ae-ffmpeg==1.2.*",
15
15
  ]
16
16
  keywords = [
@@ -1,16 +0,0 @@
1
- __version__ = "1.2.0"
2
-
3
- import os.path
4
- from platform import machine, system
5
-
6
-
7
- def get_path() -> str:
8
- _os = system()
9
- _arch = machine().lower()
10
- _interdir = _os if _os != "Darwin" else f"{_os}-{_arch}"
11
- program = "ffmpeg.exe" if _os == "Windows" else "ffmpeg"
12
-
13
- dirpath = os.path.dirname(os.path.realpath(__file__))
14
- file_path = os.path.join(dirpath, _interdir, program)
15
-
16
- return file_path if os.path.isfile(file_path) else "ffmpeg"
File without changes
@@ -1,65 +0,0 @@
1
- import re
2
-
3
- from setuptools import find_packages, setup
4
-
5
-
6
- def pip_version():
7
- with open("ae_ffmpeg/__init__.py") as f:
8
- version_content = f.read()
9
-
10
- version_match = re.search(
11
- r"^__version__ = ['\"]([^'\"]*)['\"]", version_content, re.M
12
- )
13
-
14
- if version_match:
15
- return version_match.group(1)
16
-
17
- raise ValueError("Unable to find version string.")
18
-
19
-
20
- with open("README.md") as f:
21
- long_description = f.read()
22
-
23
- setup(
24
- name="ae-ffmpeg",
25
- version=pip_version(),
26
- description="Static FFmpeg binaries for Auto-Editor",
27
- long_description=long_description,
28
- long_description_content_type="text/markdown",
29
- license="LGPLv3",
30
- url="https://auto-editor.com",
31
- project_urls={
32
- "Bug Tracker": "https://github.com/WyattBlue/auto-editor/issues",
33
- "Source Code": "https://github.com/WyattBlue/auto-editor",
34
- },
35
- author="WyattBlue",
36
- author_email="wyattblue@auto-editor.com",
37
- keywords="video audio media",
38
- packages=find_packages(),
39
- package_data={
40
- "ae_ffmpeg": [
41
- "LICENSE.txt",
42
- "Windows/ffmpeg.exe",
43
- "Windows/ffprobe.exe",
44
- "Windows/libopenh264.dll",
45
- "Darwin-x86_64/ffmpeg",
46
- "Darwin-x86_64/ffprobe",
47
- "Darwin-arm64/ffmpeg",
48
- "Darwin-arm64/ffprobe",
49
- "py.typed",
50
- ],
51
- },
52
- include_package_data=True,
53
- zip_safe=False,
54
- python_requires=">=3.8",
55
- classifiers=[
56
- "Topic :: Multimedia :: Sound/Audio",
57
- "Topic :: Multimedia :: Video",
58
- "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
59
- "Intended Audience :: Developers",
60
- "Operating System :: MacOS :: MacOS X",
61
- "Operating System :: Microsoft :: Windows",
62
- "Development Status :: 5 - Production/Stable",
63
- "Programming Language :: Python :: 3",
64
- ],
65
- )
@@ -1,2 +0,0 @@
1
- __version__ = "24.25.1"
2
- version = "24w25a"
@@ -1,3 +0,0 @@
1
- numpy>=1.22.0
2
- pyav==12.1.0
3
- ae-ffmpeg==1.2.*
File without changes
File without changes