auto-editor 26.3.0__py3-none-any.whl → 26.3.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/analyze.py +0 -7
- auto_editor/cmds/levels.py +0 -2
- auto_editor/cmds/test.py +6 -0
- auto_editor/edit.py +94 -26
- auto_editor/utils/bar.py +16 -10
- auto_editor/utils/log.py +1 -10
- {auto_editor-26.3.0.dist-info → auto_editor-26.3.1.dist-info}/METADATA +2 -2
- {auto_editor-26.3.0.dist-info → auto_editor-26.3.1.dist-info}/RECORD +13 -13
- {auto_editor-26.3.0.dist-info → auto_editor-26.3.1.dist-info}/LICENSE +0 -0
- {auto_editor-26.3.0.dist-info → auto_editor-26.3.1.dist-info}/WHEEL +0 -0
- {auto_editor-26.3.0.dist-info → auto_editor-26.3.1.dist-info}/entry_points.txt +0 -0
- {auto_editor-26.3.0.dist-info → auto_editor-26.3.1.dist-info}/top_level.txt +0 -0
auto_editor/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "26.3.
|
1
|
+
__version__ = "26.3.1"
|
auto_editor/analyze.py
CHANGED
@@ -178,7 +178,6 @@ class Levels:
|
|
178
178
|
|
179
179
|
with av.open(self.src.path, "r") as container:
|
180
180
|
audio_stream = container.streams.audio[0]
|
181
|
-
self.log.experimental(audio_stream.codec)
|
182
181
|
result = sum(1 for _ in iter_audio(audio_stream, self.tb))
|
183
182
|
|
184
183
|
self.log.debug(f"Audio Length: {result}")
|
@@ -263,9 +262,6 @@ class Levels:
|
|
263
262
|
container = av.open(self.src.path, "r")
|
264
263
|
audio = container.streams.audio[stream]
|
265
264
|
|
266
|
-
if audio.codec.experimental:
|
267
|
-
self.log.error(f"`{audio.codec.name}` is an experimental codec")
|
268
|
-
|
269
265
|
if audio.duration is not None and audio.time_base is not None:
|
270
266
|
inaccurate_dur = int(audio.duration * audio.time_base * self.tb)
|
271
267
|
elif container.duration is not None:
|
@@ -304,9 +300,6 @@ class Levels:
|
|
304
300
|
container = av.open(self.src.path, "r")
|
305
301
|
video = container.streams.video[stream]
|
306
302
|
|
307
|
-
if video.codec.experimental:
|
308
|
-
self.log.experimental(video.codec)
|
309
|
-
|
310
303
|
inaccurate_dur = (
|
311
304
|
1024
|
312
305
|
if video.duration is None or video.time_base is None
|
auto_editor/cmds/levels.py
CHANGED
@@ -136,7 +136,6 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
136
136
|
else:
|
137
137
|
container = av.open(src.path, "r")
|
138
138
|
audio_stream = container.streams.audio[obj["stream"]]
|
139
|
-
log.experimental(audio_stream.codec)
|
140
139
|
|
141
140
|
values = []
|
142
141
|
|
@@ -158,7 +157,6 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
|
158
157
|
else:
|
159
158
|
container = av.open(src.path, "r")
|
160
159
|
video_stream = container.streams.video[obj["stream"]]
|
161
|
-
log.experimental(video_stream.codec)
|
162
160
|
|
163
161
|
values = []
|
164
162
|
|
auto_editor/cmds/test.py
CHANGED
@@ -9,6 +9,7 @@ from fractions import Fraction
|
|
9
9
|
from time import perf_counter
|
10
10
|
from typing import TYPE_CHECKING
|
11
11
|
|
12
|
+
import av
|
12
13
|
import numpy as np
|
13
14
|
|
14
15
|
from auto_editor.ffwrapper import FileInfo, initFileInfo
|
@@ -235,6 +236,11 @@ def main(sys_args: list[str] | None = None):
|
|
235
236
|
|
236
237
|
def example():
|
237
238
|
out = run.main(inputs=["example.mp4"], cmd=[])
|
239
|
+
|
240
|
+
with av.open(out) as container:
|
241
|
+
assert container.streams[0].type == "video"
|
242
|
+
assert container.streams[1].type == "audio"
|
243
|
+
|
238
244
|
cn = fileinfo(out)
|
239
245
|
video = cn.videos[0]
|
240
246
|
|
auto_editor/edit.py
CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import os
|
4
4
|
import sys
|
5
5
|
from fractions import Fraction
|
6
|
+
from heapq import heappop, heappush
|
6
7
|
from os.path import splitext
|
7
8
|
from subprocess import run
|
8
9
|
from typing import Any
|
@@ -309,6 +310,13 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
309
310
|
else:
|
310
311
|
audio_paths = []
|
311
312
|
|
313
|
+
# Setup video
|
314
|
+
if ctr.default_vid != "none" and tl.v:
|
315
|
+
vframes = render_av(output, tl, args, log)
|
316
|
+
output_stream = next(vframes)
|
317
|
+
else:
|
318
|
+
output_stream, vframes = None, iter([])
|
319
|
+
|
312
320
|
# Setup audio
|
313
321
|
if audio_paths:
|
314
322
|
try:
|
@@ -363,23 +371,16 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
363
371
|
subtitle_streams.append(subtitle_stream)
|
364
372
|
sub_gen_frames.append(subtitle_input.demux(subtitles=0))
|
365
373
|
|
366
|
-
# Setup video
|
367
|
-
if ctr.default_vid != "none" and tl.v:
|
368
|
-
vframes = render_av(output, tl, args, log)
|
369
|
-
output_stream = next(vframes)
|
370
|
-
else:
|
371
|
-
output_stream, vframes = None, iter([])
|
372
|
-
|
373
374
|
no_color = log.no_color or log.machine
|
374
375
|
encoder_titles = []
|
375
376
|
if output_stream is not None:
|
376
377
|
name = output_stream.codec.canonical_name
|
377
378
|
encoder_titles.append(name if no_color else f"\033[95m{name}")
|
378
379
|
if audio_streams:
|
379
|
-
name = audio_streams[0].
|
380
|
+
name = audio_streams[0].codec.canonical_name
|
380
381
|
encoder_titles.append(name if no_color else f"\033[96m{name}")
|
381
382
|
if subtitle_streams:
|
382
|
-
name = subtitle_streams[0].
|
383
|
+
name = subtitle_streams[0].codec.canonical_name
|
383
384
|
encoder_titles.append(name if no_color else f"\033[32m{name}")
|
384
385
|
|
385
386
|
title = f"({os.path.splitext(output_path)[1][1:]}) "
|
@@ -389,11 +390,68 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
389
390
|
title += "\033[0m+".join(encoder_titles) + "\033[0m"
|
390
391
|
bar.start(tl.end, title)
|
391
392
|
|
392
|
-
#
|
393
|
+
MAX_AUDIO_AHEAD = 30 # In timebase, how far audio can be ahead of video.
|
394
|
+
MAX_SUB_AHEAD = 30
|
395
|
+
|
396
|
+
class Priority:
|
397
|
+
__slots__ = ("index", "frame_type", "frame", "stream")
|
398
|
+
|
399
|
+
def __init__(self, value: int | Fraction, frame, stream):
|
400
|
+
self.frame_type: str = stream.type
|
401
|
+
assert self.frame_type in ("audio", "subtitle", "video")
|
402
|
+
if self.frame_type in {"audio", "subtitle"}:
|
403
|
+
self.index: int | float = round(value * frame.time_base * tl.tb)
|
404
|
+
else:
|
405
|
+
self.index = float("inf") if value is None else int(value)
|
406
|
+
self.frame = frame
|
407
|
+
self.stream = stream
|
408
|
+
|
409
|
+
def __lt__(self, other):
|
410
|
+
return self.index < other.index
|
411
|
+
|
412
|
+
def __eq__(self, other):
|
413
|
+
return self.index == other.index
|
414
|
+
|
415
|
+
# Priority queue for ordered frames by time_base.
|
416
|
+
frame_queue: list[Priority] = []
|
417
|
+
latest_audio_index = float("-inf")
|
418
|
+
latest_sub_index = float("-inf")
|
419
|
+
earliest_video_index = None
|
420
|
+
|
393
421
|
while True:
|
394
|
-
|
422
|
+
if earliest_video_index is None:
|
423
|
+
should_get_audio = True
|
424
|
+
should_get_sub = True
|
425
|
+
else:
|
426
|
+
for item in frame_queue:
|
427
|
+
if item.frame_type == "audio":
|
428
|
+
latest_audio_index = max(latest_audio_index, item.index)
|
429
|
+
elif item.frame_type == "subtitle":
|
430
|
+
latest_sub_index = max(latest_sub_index, item.index)
|
431
|
+
|
432
|
+
should_get_audio = (
|
433
|
+
latest_audio_index <= earliest_video_index + MAX_AUDIO_AHEAD
|
434
|
+
)
|
435
|
+
should_get_sub = (
|
436
|
+
latest_sub_index <= earliest_video_index + MAX_SUB_AHEAD
|
437
|
+
)
|
438
|
+
|
395
439
|
index, video_frame = next(vframes, (0, None))
|
396
|
-
|
440
|
+
|
441
|
+
if video_frame:
|
442
|
+
earliest_video_index = index
|
443
|
+
heappush(frame_queue, Priority(index, video_frame, output_stream))
|
444
|
+
|
445
|
+
if should_get_audio:
|
446
|
+
audio_frames = [next(frames, None) for frames in audio_gen_frames]
|
447
|
+
else:
|
448
|
+
audio_frames = [None]
|
449
|
+
if should_get_sub:
|
450
|
+
subtitle_frames = [next(packet, None) for packet in sub_gen_frames]
|
451
|
+
else:
|
452
|
+
subtitle_frames = [None]
|
453
|
+
|
454
|
+
# Break if no more frames
|
397
455
|
if (
|
398
456
|
all(frame is None for frame in audio_frames)
|
399
457
|
and video_frame is None
|
@@ -401,24 +459,34 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
401
459
|
):
|
402
460
|
break
|
403
461
|
|
404
|
-
|
405
|
-
|
462
|
+
if should_get_audio:
|
463
|
+
for audio_stream, audio_frame in zip(audio_streams, audio_frames):
|
406
464
|
for reframe in resampler.resample(audio_frame):
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
465
|
+
assert reframe.pts is not None
|
466
|
+
heappush(
|
467
|
+
frame_queue,
|
468
|
+
Priority(reframe.pts, reframe, audio_stream),
|
469
|
+
)
|
470
|
+
if should_get_sub:
|
471
|
+
for subtitle_stream, packet in zip(subtitle_streams, subtitle_frames):
|
472
|
+
if packet and packet.pts is not None:
|
473
|
+
packet.stream = subtitle_stream
|
474
|
+
heappush(
|
475
|
+
frame_queue, Priority(packet.pts, packet, subtitle_stream)
|
476
|
+
)
|
477
|
+
|
478
|
+
while frame_queue and frame_queue[0].index <= index:
|
479
|
+
item = heappop(frame_queue)
|
480
|
+
frame_type = item.frame_type
|
416
481
|
try:
|
417
|
-
|
482
|
+
if frame_type in {"video", "audio"}:
|
483
|
+
output.mux(item.stream.encode(item.frame))
|
484
|
+
elif frame_type == "subtitle":
|
485
|
+
output.mux(item.frame)
|
418
486
|
except av.error.ExternalError:
|
419
487
|
log.error(
|
420
|
-
f"Generic error for encoder: {
|
421
|
-
"
|
488
|
+
f"Generic error for encoder: {item.stream.name}\n"
|
489
|
+
f"at {item.index} time_base\nPerhaps video quality settings are too low?"
|
422
490
|
)
|
423
491
|
except av.FFmpegError as e:
|
424
492
|
log.error(e)
|
auto_editor/utils/bar.py
CHANGED
@@ -89,20 +89,26 @@ class Bar:
|
|
89
89
|
percent = round(progress * 100, 1)
|
90
90
|
p_pad = " " * (4 - len(str(percent)))
|
91
91
|
columns = get_terminal_size().columns
|
92
|
-
bar_len = max(1, columns - len_title -
|
92
|
+
bar_len = max(1, columns - len_title - 35)
|
93
93
|
bar_str = self._bar_str(progress, bar_len)
|
94
94
|
|
95
|
-
bar = f" {self.icon}{title} {bar_str} {p_pad}{percent}% ETA {new_time}"
|
96
|
-
|
97
|
-
if len(bar) > columns - 2:
|
98
|
-
bar = bar[: columns - 2]
|
99
|
-
else:
|
100
|
-
bar += " " * (columns - len(bar) - 4)
|
101
|
-
|
102
|
-
sys.stdout.write(bar + "\r")
|
95
|
+
bar = f" {self.icon}{title} {bar_str} {p_pad}{percent}% ETA {new_time} \r"
|
96
|
+
sys.stdout.write(bar)
|
103
97
|
|
104
98
|
def start(self, total: float, title: str = "Please wait") -> None:
|
105
|
-
|
99
|
+
len_title = 0
|
100
|
+
in_escape = False
|
101
|
+
|
102
|
+
for char in title:
|
103
|
+
if not in_escape:
|
104
|
+
if char == "\033":
|
105
|
+
in_escape = True
|
106
|
+
else:
|
107
|
+
len_title += 1
|
108
|
+
elif char == "m":
|
109
|
+
in_escape = False
|
110
|
+
|
111
|
+
self.stack.append((title, len_title, total, time()))
|
106
112
|
|
107
113
|
try:
|
108
114
|
self.tick(0)
|
auto_editor/utils/log.py
CHANGED
@@ -1,14 +1,9 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
1
|
import sys
|
4
2
|
from datetime import timedelta
|
5
3
|
from shutil import get_terminal_size, rmtree
|
6
4
|
from tempfile import mkdtemp
|
7
5
|
from time import perf_counter, sleep
|
8
|
-
from typing import
|
9
|
-
|
10
|
-
if TYPE_CHECKING:
|
11
|
-
import av
|
6
|
+
from typing import NoReturn
|
12
7
|
|
13
8
|
|
14
9
|
class Log:
|
@@ -100,10 +95,6 @@ class Log:
|
|
100
95
|
|
101
96
|
sys.stdout.write(f"Finished. took {second_len} seconds ({minute_len})\n")
|
102
97
|
|
103
|
-
def experimental(self, codec: av.Codec) -> None:
|
104
|
-
if codec.experimental:
|
105
|
-
self.error(f"`{codec.name}` is an experimental codec")
|
106
|
-
|
107
98
|
@staticmethod
|
108
99
|
def deprecated(message: str) -> None:
|
109
100
|
sys.stderr.write(f"\033[1m\033[33m{message}\033[0m\n")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: auto-editor
|
3
|
-
Version: 26.3.
|
3
|
+
Version: 26.3.1
|
4
4
|
Summary: Auto-Editor: Effort free video editing!
|
5
5
|
Author-email: WyattBlue <wyattblue@auto-editor.com>
|
6
6
|
License: Unlicense
|
@@ -12,7 +12,7 @@ Requires-Python: <3.14,>=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
License-File: LICENSE
|
14
14
|
Requires-Dist: numpy<3.0,>=1.24
|
15
|
-
Requires-Dist: pyav==14.2
|
15
|
+
Requires-Dist: pyav==14.2.*
|
16
16
|
|
17
17
|
<p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
|
18
18
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
auto_editor/__init__.py,sha256=
|
1
|
+
auto_editor/__init__.py,sha256=cbuXE-xYiZp6P9Ipk0EAUgPxPvaOpg-ve_HurO96fCM,23
|
2
2
|
auto_editor/__main__.py,sha256=g-9q3i6oFeNPeFMeNJEwZua6ZloOPLcaLN-B0FlxWXo,11371
|
3
|
-
auto_editor/analyze.py,sha256=
|
4
|
-
auto_editor/edit.py,sha256=
|
3
|
+
auto_editor/analyze.py,sha256=tkNZdRz1Nf1mQfC3jKHaiLbHRkacX_7-2TrKTKvwpxY,12463
|
4
|
+
auto_editor/edit.py,sha256=unOxLPiPUMoLgM_ScdRA3rNnxOicUUu7iLO1lG0zcfc,20260
|
5
5
|
auto_editor/ffwrapper.py,sha256=1lYYfq8gVgMVkYWeAEYDPAHCwFCYbKQwy0FxYBxMzk8,4765
|
6
6
|
auto_editor/help.py,sha256=CzfDTsL4GuGu596ySHKj_wKnxGR9h8B0KUdkZpo33oE,8044
|
7
7
|
auto_editor/make_layers.py,sha256=vEeJt0PnE1vc9-cQZ_AlXVDjvWhObRCWJSCQGraoMvU,9016
|
@@ -14,11 +14,11 @@ auto_editor/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
14
14
|
auto_editor/cmds/cache.py,sha256=bViYbtVXefTeEIUvSanDfA6cG35ep1N_Jvtz7ZjgIkY,1959
|
15
15
|
auto_editor/cmds/desc.py,sha256=GDrKJYiHMaeTrplZAceXl1JwoqD78XsV2_5lc0Xd7po,869
|
16
16
|
auto_editor/cmds/info.py,sha256=vYa1hYdE8kDTE8AS3kwXlnd59X6CrE2GtIEQ7UmlpRY,7010
|
17
|
-
auto_editor/cmds/levels.py,sha256=
|
17
|
+
auto_editor/cmds/levels.py,sha256=RBBs0YM8Qb7XteCy7Pr4QpU9b5maMsDe6xEkAuyGs1c,5644
|
18
18
|
auto_editor/cmds/palet.py,sha256=ONzTqemaQq9YEfIOsDRNnwzfqnEMUMSXIQrETxyroRU,749
|
19
19
|
auto_editor/cmds/repl.py,sha256=TF_I7zsFY7-KdgidrqjafTz7o_eluVbLvgTcOBG-UWQ,3449
|
20
20
|
auto_editor/cmds/subdump.py,sha256=af_XBf7kaevqHn1A71z8C-7x8pS5WKD9FE_ugkCw6rk,665
|
21
|
-
auto_editor/cmds/test.py,sha256=
|
21
|
+
auto_editor/cmds/test.py,sha256=D8l4Zr0UjrUtFkqsADqlVQ1wvNP3s97ifNnyf1UBTFU,26327
|
22
22
|
auto_editor/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
23
|
auto_editor/formats/fcp11.py,sha256=sqjC36jI47ICPLjZJYiqGwY7foOnWOiNjkPFLdgSnI4,5208
|
24
24
|
auto_editor/formats/fcp7.py,sha256=x5cagTzGCAW3i3M6m7TZC1h8gLfSmX1UK-iiDuCpdfs,20289
|
@@ -40,17 +40,17 @@ auto_editor/render/audio.py,sha256=_GuX0WNY1YeumgBN3bWqgwVXiuhpvx7sijABxqyO2ag,1
|
|
40
40
|
auto_editor/render/subtitle.py,sha256=jtNRKvgo1fpHTrAfGZqdkNeNgGgasw-K-4PwIKiWwfM,6231
|
41
41
|
auto_editor/render/video.py,sha256=JBVl8w-hQ6zrs97iA527LPsBZ9s601SVSJs2bSMCq88,12185
|
42
42
|
auto_editor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
|
-
auto_editor/utils/bar.py,sha256=
|
43
|
+
auto_editor/utils/bar.py,sha256=Ky9JRf37JTgLyvNuIXDfucaUE8H1vBbCqKLjttmsmmo,4156
|
44
44
|
auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,52
|
45
45
|
auto_editor/utils/cmdkw.py,sha256=aUGBvBel2Ko1o6Rwmr4rEL-BMc5hEnzYLbyZ1GeJdcY,5729
|
46
46
|
auto_editor/utils/container.py,sha256=C_Ahh7nlMEX4DNQ2M_cITPPbYcIL68r4I_AgFy0OD6o,2487
|
47
47
|
auto_editor/utils/func.py,sha256=C8ucgsSEzPyBc-8obhsCXd_uQW0cnCdBn1KVxB7FHjU,2747
|
48
|
-
auto_editor/utils/log.py,sha256=
|
48
|
+
auto_editor/utils/log.py,sha256=wPNf6AabV-0cnoS_bPLv1Lh7llQBtNqPKeh07einOuc,3701
|
49
49
|
auto_editor/utils/types.py,sha256=r5f6QB81xH7NRwGntITIOCVx-fupOl8l3X3LSFkt3nE,10756
|
50
50
|
docs/build.py,sha256=POy8X8QOBYe_8A8HI_yiVI_Qg9E5mLpn1z7AHQr0_vQ,1888
|
51
|
-
auto_editor-26.3.
|
52
|
-
auto_editor-26.3.
|
53
|
-
auto_editor-26.3.
|
54
|
-
auto_editor-26.3.
|
55
|
-
auto_editor-26.3.
|
56
|
-
auto_editor-26.3.
|
51
|
+
auto_editor-26.3.1.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
|
52
|
+
auto_editor-26.3.1.dist-info/METADATA,sha256=4r9rtkLWEDaVMcJauMcljdAw-cnz2h6xS9sixAnWVlQ,6111
|
53
|
+
auto_editor-26.3.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
54
|
+
auto_editor-26.3.1.dist-info/entry_points.txt,sha256=UAsTc7qJQbnAzHd7KWg-ALo_X9Hj2yDs3M9I2DV3eyI,212
|
55
|
+
auto_editor-26.3.1.dist-info/top_level.txt,sha256=jBV5zlbWRbKOa-xaWPvTD45QL7lGExx2BDzv-Ji4dTw,17
|
56
|
+
auto_editor-26.3.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|