auto-editor 26.1.0__py3-none-any.whl → 26.1.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.
auto_editor/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "26.1.0"
1
+ __version__ = "26.1.1"
auto_editor/__main__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+ import platform as plat
3
4
  import re
4
5
  import sys
5
6
  from os import environ
@@ -7,7 +8,6 @@ from os.path import exists, isdir, isfile, lexists, splitext
7
8
  from subprocess import run
8
9
 
9
10
  import auto_editor
10
- from auto_editor.edit import edit_media
11
11
  from auto_editor.utils.func import get_stdout
12
12
  from auto_editor.utils.log import Log
13
13
  from auto_editor.utils.types import (
@@ -286,7 +286,16 @@ def download_video(my_input: str, args: Args, log: Log) -> str:
286
286
 
287
287
 
288
288
  def main() -> None:
289
- subcommands = ("test", "info", "levels", "subdump", "desc", "repl", "palet")
289
+ subcommands = (
290
+ "test",
291
+ "info",
292
+ "levels",
293
+ "subdump",
294
+ "desc",
295
+ "repl",
296
+ "palet",
297
+ "cache",
298
+ )
290
299
 
291
300
  if len(sys.argv) > 1 and sys.argv[1] in subcommands:
292
301
  obj = __import__(
@@ -320,15 +329,17 @@ def main() -> None:
320
329
  return
321
330
 
322
331
  if args.debug and not args.input:
323
- import platform as plat
332
+ print(f"OS: {plat.system()} {plat.release()} {plat.machine().lower()}")
333
+ print(f"Python: {plat.python_version()}")
324
334
 
325
- import av
335
+ try:
336
+ import av
326
337
 
327
- license = av._core.library_meta["libavcodec"]["license"]
338
+ license = av._core.library_meta["libavcodec"]["license"]
339
+ print(f"PyAV: {av.__version__} ({license})")
340
+ except (ModuleNotFoundError, ImportError):
341
+ print("PyAV: error")
328
342
 
329
- print(f"OS: {plat.system()} {plat.release()} {plat.machine().lower()}")
330
- print(f"Python: {plat.python_version()}")
331
- print(f"PyAV: {av.__version__} ({license})")
332
343
  print(f"Auto-Editor: {auto_editor.__version__}")
333
344
  return
334
345
 
@@ -354,6 +365,8 @@ def main() -> None:
354
365
  log.error(f"Option/Input file doesn't exist: {my_input}")
355
366
  paths.append(my_input)
356
367
 
368
+ from auto_editor.edit import edit_media
369
+
357
370
  try:
358
371
  edit_media(paths, args, log)
359
372
  except KeyboardInterrupt:
auto_editor/analyze.py CHANGED
@@ -4,6 +4,7 @@ import os
4
4
  import re
5
5
  from dataclasses import dataclass
6
6
  from fractions import Fraction
7
+ from hashlib import sha1
7
8
  from math import ceil
8
9
  from tempfile import gettempdir
9
10
  from typing import TYPE_CHECKING
@@ -154,8 +155,10 @@ def iter_motion(
154
155
 
155
156
  def obj_tag(path: Path, kind: str, tb: Fraction, obj: Sequence[object]) -> str:
156
157
  mod_time = int(path.stat().st_mtime)
157
- key = f"{path.name}:{mod_time:x}:{kind}:{tb}:"
158
- return key + ",".join(f"{v}" for v in obj)
158
+ key = f"{path}:{mod_time:x}:{tb}:" + ",".join(f"{v}" for v in obj)
159
+ part1 = sha1(key.encode()).hexdigest()[:16]
160
+
161
+ return f"{part1}{kind}"
159
162
 
160
163
 
161
164
  @dataclass(slots=True)
@@ -206,31 +209,47 @@ class Levels:
206
209
  if self.no_cache:
207
210
  return None
208
211
 
209
- workfile = os.path.join(gettempdir(), f"ae-{__version__}", "cache.npz")
212
+ key = obj_tag(self.src.path, kind, self.tb, obj)
213
+ cache_file = os.path.join(gettempdir(), f"ae-{__version__}", f"{key}.npz")
210
214
 
211
215
  try:
212
- npzfile = np.load(workfile, allow_pickle=False)
216
+ with np.load(cache_file, allow_pickle=False) as npzfile:
217
+ return npzfile["data"]
213
218
  except Exception as e:
214
219
  self.log.debug(e)
215
220
  return None
216
221
 
217
- key = obj_tag(self.src.path, kind, self.tb, obj)
218
- if key not in npzfile.files:
219
- return None
220
-
221
- self.log.debug("Using cache")
222
- return npzfile[key]
223
-
224
222
  def cache(self, arr: np.ndarray, kind: str, obj: Sequence[object]) -> np.ndarray:
225
223
  if self.no_cache:
226
224
  return arr
227
225
 
228
- workdur = os.path.join(gettempdir(), f"ae-{__version__}")
229
- if not os.path.exists(workdur):
230
- os.mkdir(workdur)
226
+ workdir = os.path.join(gettempdir(), f"ae-{__version__}")
227
+ if not os.path.exists(workdir):
228
+ os.mkdir(workdir)
231
229
 
232
230
  key = obj_tag(self.src.path, kind, self.tb, obj)
233
- np.savez(os.path.join(workdur, "cache.npz"), **{key: arr})
231
+ cache_file = os.path.join(workdir, f"{key}.npz")
232
+
233
+ try:
234
+ np.savez(cache_file, data=arr)
235
+ except Exception as e:
236
+ self.log.warning(f"Cache write failed: {e}")
237
+
238
+ cache_entries = []
239
+ with os.scandir(workdir) as entries:
240
+ for entry in entries:
241
+ if entry.name.endswith(".npz"):
242
+ cache_entries.append((entry.path, entry.stat().st_mtime))
243
+
244
+ if len(cache_entries) > 10:
245
+ # Sort by modification time, oldest first
246
+ cache_entries.sort(key=lambda x: x[1])
247
+ # Remove oldest files until we're back to 10
248
+ for filepath, _ in cache_entries[:-10]:
249
+ try:
250
+ os.remove(filepath)
251
+ except OSError:
252
+ pass
234
253
 
235
254
  return arr
236
255
 
@@ -257,14 +276,15 @@ class Levels:
257
276
  bar = self.bar
258
277
  bar.start(inaccurate_dur, "Analyzing audio volume")
259
278
 
260
- result = np.zeros((inaccurate_dur), dtype=np.float32)
279
+ result: NDArray[np.float32] = np.zeros(inaccurate_dur, dtype=np.float32)
261
280
  index = 0
262
281
 
263
282
  for value in iter_audio(audio, self.tb):
264
283
  if index > len(result) - 1:
265
284
  result = np.concatenate(
266
- (result, np.zeros((len(result)), dtype=np.float32))
285
+ (result, np.zeros(len(result), dtype=np.float32))
267
286
  )
287
+
268
288
  result[index] = value
269
289
  bar.tick(index)
270
290
  index += 1
@@ -296,13 +316,13 @@ class Levels:
296
316
  bar = self.bar
297
317
  bar.start(inaccurate_dur, "Analyzing motion")
298
318
 
299
- result = np.zeros((inaccurate_dur), dtype=np.float32)
319
+ result: NDArray[np.float32] = np.zeros(inaccurate_dur, dtype=np.float32)
300
320
  index = 0
301
321
 
302
322
  for value in iter_motion(video, self.tb, blur, width):
303
323
  if index > len(result) - 1:
304
324
  result = np.concatenate(
305
- (result, np.zeros((len(result)), dtype=np.float32))
325
+ (result, np.zeros(len(result), dtype=np.float32))
306
326
  )
307
327
  result[index] = value
308
328
  bar.tick(index)
@@ -0,0 +1,69 @@
1
+ import glob
2
+ import os
3
+ import sys
4
+ from shutil import rmtree
5
+ from tempfile import gettempdir
6
+
7
+ import numpy as np
8
+
9
+ from auto_editor import __version__
10
+
11
+
12
+ def main(sys_args: list[str] = sys.argv[1:]) -> None:
13
+ cache_dir = os.path.join(gettempdir(), f"ae-{__version__}")
14
+
15
+ if sys_args and sys_args[0] in ("clean", "clear"):
16
+ rmtree(cache_dir, ignore_errors=True)
17
+ return
18
+
19
+ if not os.path.exists(cache_dir):
20
+ print("Empty cache")
21
+ return
22
+
23
+ cache_files = glob.glob(os.path.join(cache_dir, "*.npz"))
24
+ if not cache_files:
25
+ print("Empty cache")
26
+ return
27
+
28
+ def format_bytes(size: float) -> str:
29
+ for unit in ("B", "KiB", "MiB", "GiB", "TiB"):
30
+ if size < 1024:
31
+ return f"{size:.2f} {unit}"
32
+ size /= 1024
33
+ return f"{size:.2f} PiB"
34
+
35
+ GRAY = "\033[90m"
36
+ GREEN = "\033[32m"
37
+ BLUE = "\033[34m"
38
+ YELLOW = "\033[33m"
39
+ RESET = "\033[0m"
40
+
41
+ total_size = 0
42
+ for cache_file in cache_files:
43
+ try:
44
+ with np.load(cache_file, allow_pickle=False) as npzfile:
45
+ array = npzfile["data"]
46
+ key = os.path.basename(cache_file)[:-4] # Remove .npz extension
47
+
48
+ hash_part = key[:16]
49
+ rest_part = key[16:]
50
+
51
+ size = array.nbytes
52
+ total_size += size
53
+ size_str = format_bytes(size)
54
+ size_num, size_unit = size_str.rsplit(" ", 1)
55
+
56
+ print(
57
+ f"{YELLOW}entry: {GRAY}{hash_part}{RESET}{rest_part} "
58
+ f"{YELLOW}size: {GREEN}{size_num} {BLUE}{size_unit}{RESET}"
59
+ )
60
+ except Exception as e:
61
+ print(f"Error reading {cache_file}: {e}")
62
+
63
+ total_str = format_bytes(total_size)
64
+ total_num, total_unit = total_str.rsplit(" ", 1)
65
+ print(f"\n{YELLOW}total cache size: {GREEN}{total_num} {BLUE}{total_unit}{RESET}")
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()
auto_editor/utils/log.py CHANGED
@@ -5,9 +5,10 @@ from datetime import timedelta
5
5
  from shutil import get_terminal_size, rmtree
6
6
  from tempfile import mkdtemp
7
7
  from time import perf_counter, sleep
8
- from typing import NoReturn
8
+ from typing import TYPE_CHECKING, NoReturn
9
9
 
10
- import av
10
+ if TYPE_CHECKING:
11
+ import av
11
12
 
12
13
 
13
14
  class Log:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 26.1.0
3
+ Version: 26.1.1
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -1,6 +1,6 @@
1
- auto_editor/__init__.py,sha256=8MQdwPYn_Y7GCbtRLrmuh9XSy5S52w2pxd3bulKs9Ag,23
2
- auto_editor/__main__.py,sha256=eAsNa1BP4Y6Oyp4l838YmcxEwsM0LUdbaGeNFELe4h0,11124
3
- auto_editor/analyze.py,sha256=HyRdnty3VW9ZTwwPwjsZp3bLVRLvII_1Y6NlEItDKfw,11947
1
+ auto_editor/__init__.py,sha256=2Ltcef2BVJgJx2W5ZkX7r21sdnzR3Zvtu1PYKRHEjLk,23
2
+ auto_editor/__main__.py,sha256=tc0M1MIPYjU5wCEU3EqmleOzaUgksU60qVHO0vRuC10,11310
3
+ auto_editor/analyze.py,sha256=Fv8NA99T1dZzrqlweJNlK7haKjgK13neR9CMw4t6rlY,12716
4
4
  auto_editor/edit.py,sha256=eEMRaQbn0jylfJ6D_egnUXjoMCbdQVsAu7MDrn-xlGo,15950
5
5
  auto_editor/ffwrapper.py,sha256=Tct_Q-uy5F51h8M7UFam50UzRFpgkBvUamJP1AoKVvc,4749
6
6
  auto_editor/help.py,sha256=CzfDTsL4GuGu596ySHKj_wKnxGR9h8B0KUdkZpo33oE,8044
@@ -31,6 +31,7 @@ auto_editor/render/audio.py,sha256=1iOQCeRXfRz28cqnHp2XeK-f3_UnPf80AKQAfifGvdE,1
31
31
  auto_editor/render/subtitle.py,sha256=lf2l1QWJgFiqlpQWWBwSlKJnSgW8Lkfi59WrJMbIDqM,6240
32
32
  auto_editor/render/video.py,sha256=dje0RNW2dKILfTzt0VAF0WR6REfGOsc6l17pP1Z4ooA,12215
33
33
  auto_editor/subcommands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ auto_editor/subcommands/cache.py,sha256=YW_5qH0q5TVzmfOLEO117uqcY7dF6DS619ltVTPIzHQ,1959
34
35
  auto_editor/subcommands/desc.py,sha256=GDrKJYiHMaeTrplZAceXl1JwoqD78XsV2_5lc0Xd7po,869
35
36
  auto_editor/subcommands/info.py,sha256=UDdoxd6_fqSoRPwthkWXqnpxHp7dJQ0Dn96lYX_ubWc,7010
36
37
  auto_editor/subcommands/levels.py,sha256=psSSIsGfzr9j0HGKp2yvK6nMlrkLwxkwsyI0uF2xb_c,4496
@@ -44,12 +45,12 @@ auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,5
44
45
  auto_editor/utils/cmdkw.py,sha256=aUGBvBel2Ko1o6Rwmr4rEL-BMc5hEnzYLbyZ1GeJdcY,5729
45
46
  auto_editor/utils/container.py,sha256=Wf1ZL0tvXWl6m1B9mK_SkgVl89ilV_LpwlQq0TVroCc,2704
46
47
  auto_editor/utils/func.py,sha256=kB-pNDn20M6YT7sljyd_auve5teK-E2G4TgwVOAIuJw,2754
47
- auto_editor/utils/log.py,sha256=C1b-vnszSsohMd5fyaRcCuf0OPobZVMkV77cP-_JNP4,3776
48
+ auto_editor/utils/log.py,sha256=n5dlJ2CdK_54eiYE02SPgkBdBWABV7tE2p8ONj_F6TM,3813
48
49
  auto_editor/utils/types.py,sha256=7BF7R7DA5eKmtI6f5ia7bOYNL0u_2sviiPsE1VmP0lc,10724
49
- docs/build.py,sha256=CM-ZWgQk8wSNjivx_-6wGIaG7cstrNKsX2d4TzFVivE,1642
50
- auto_editor-26.1.0.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
51
- auto_editor-26.1.0.dist-info/METADATA,sha256=QDfveFTnxTtnA2WqZTvgM4vNGQ1sTnziQ2MSAyqF5WQ,6109
52
- auto_editor-26.1.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
53
- auto_editor-26.1.0.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
54
- auto_editor-26.1.0.dist-info/top_level.txt,sha256=jBV5zlbWRbKOa-xaWPvTD45QL7lGExx2BDzv-Ji4dTw,17
55
- auto_editor-26.1.0.dist-info/RECORD,,
50
+ docs/build.py,sha256=POy8X8QOBYe_8A8HI_yiVI_Qg9E5mLpn1z7AHQr0_vQ,1888
51
+ auto_editor-26.1.1.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
52
+ auto_editor-26.1.1.dist-info/METADATA,sha256=Ovf6CjY_x-lyih-4c1xBZEkL_X0gvifVFthPcLSMOtk,6109
53
+ auto_editor-26.1.1.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
54
+ auto_editor-26.1.1.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
55
+ auto_editor-26.1.1.dist-info/top_level.txt,sha256=jBV5zlbWRbKOa-xaWPvTD45QL7lGExx2BDzv-Ji4dTw,17
56
+ auto_editor-26.1.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.7.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
docs/build.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import os
4
4
  import sys
5
+ from html import escape
5
6
 
6
7
  # Put 'auto_editor' in Python path
7
8
  sys.path.append(os.path.dirname(os.path.dirname(__file__)))
@@ -19,7 +20,7 @@ def main():
19
20
 
20
21
  with open("src/ref/options.html", "w") as file:
21
22
  file.write(
22
- '{{ header-desc "Options" "These are the options and flags that auto-editor uses." }}\n'
23
+ '{{ headerdesc "Options" "These are the options and flags that auto-editor uses." }}\n'
23
24
  "<body>\n"
24
25
  "{{ nav }}\n"
25
26
  '<section class="section">\n'
@@ -27,12 +28,18 @@ def main():
27
28
  )
28
29
  for op in parser.args:
29
30
  if isinstance(op, OptionText):
30
- file.write(f"<h2>{op.text}</h2>\n")
31
+ file.write(f"<h2>{escape(op.text)}</h2>\n")
31
32
  else:
32
- file.write(f"<h3><code>{op.names[0]}</code></h3>\n")
33
+ if op.metavar is None:
34
+ file.write(f"<h3><code>{op.names[0]}</code></h3>\n")
35
+ else:
36
+ file.write(
37
+ f"<h3><code>{op.names[0]} {escape(op.metavar)}</code></h3>\n"
38
+ )
39
+
33
40
  if len(op.names) > 1:
34
41
  file.write(
35
- "<h4><code>"
42
+ "<h4>Aliases: <code>"
36
43
  + "</code> <code>".join(op.names[1:])
37
44
  + "</code></h4>\n"
38
45
  )