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 +1 -1
- auto_editor/__main__.py +21 -8
- auto_editor/analyze.py +39 -19
- auto_editor/subcommands/cache.py +69 -0
- auto_editor/utils/log.py +3 -2
- {auto_editor-26.1.0.dist-info → auto_editor-26.1.1.dist-info}/METADATA +1 -1
- {auto_editor-26.1.0.dist-info → auto_editor-26.1.1.dist-info}/RECORD +12 -11
- {auto_editor-26.1.0.dist-info → auto_editor-26.1.1.dist-info}/WHEEL +1 -1
- docs/build.py +11 -4
- {auto_editor-26.1.0.dist-info → auto_editor-26.1.1.dist-info}/LICENSE +0 -0
- {auto_editor-26.1.0.dist-info → auto_editor-26.1.1.dist-info}/entry_points.txt +0 -0
- {auto_editor-26.1.0.dist-info → auto_editor-26.1.1.dist-info}/top_level.txt +0 -0
auto_editor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "26.1.
|
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 = (
|
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
|
-
|
332
|
+
print(f"OS: {plat.system()} {plat.release()} {plat.machine().lower()}")
|
333
|
+
print(f"Python: {plat.python_version()}")
|
324
334
|
|
325
|
-
|
335
|
+
try:
|
336
|
+
import av
|
326
337
|
|
327
|
-
|
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
|
158
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
229
|
-
if not os.path.exists(
|
230
|
-
os.mkdir(
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
import av
|
11
12
|
|
12
13
|
|
13
14
|
class Log:
|
@@ -1,6 +1,6 @@
|
|
1
|
-
auto_editor/__init__.py,sha256=
|
2
|
-
auto_editor/__main__.py,sha256=
|
3
|
-
auto_editor/analyze.py,sha256=
|
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=
|
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=
|
50
|
-
auto_editor-26.1.
|
51
|
-
auto_editor-26.1.
|
52
|
-
auto_editor-26.1.
|
53
|
-
auto_editor-26.1.
|
54
|
-
auto_editor-26.1.
|
55
|
-
auto_editor-26.1.
|
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,,
|
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
|
-
'{{
|
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
|
-
|
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
|
42
|
+
"<h4>Aliases: <code>"
|
36
43
|
+ "</code> <code>".join(op.names[1:])
|
37
44
|
+ "</code></h4>\n"
|
38
45
|
)
|
File without changes
|
File without changes
|
File without changes
|