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.
- {auto_editor-24.25.1/auto_editor.egg-info → auto_editor-24.29.1}/PKG-INFO +3 -6
- {auto_editor-24.25.1 → auto_editor-24.29.1}/README.md +0 -3
- auto_editor-24.29.1/auto_editor/__init__.py +2 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/__main__.py +4 -4
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/analyze.py +30 -100
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/edit.py +3 -3
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/ffwrapper.py +6 -2
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/fcp7.py +2 -2
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/shotcut.py +1 -2
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/palet.py +29 -9
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/contracts.py +12 -6
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/make_layers.py +2 -20
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/output.py +39 -4
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/video.py +9 -9
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/levels.py +39 -25
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/repl.py +2 -2
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/test.py +12 -5
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/validate_input.py +9 -12
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/vanparse.py +3 -3
- {auto_editor-24.25.1 → auto_editor-24.29.1/auto_editor.egg-info}/PKG-INFO +3 -6
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/SOURCES.txt +0 -3
- auto_editor-24.29.1/auto_editor.egg-info/requires.txt +3 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/top_level.txt +0 -1
- {auto_editor-24.25.1 → auto_editor-24.29.1}/pyproject.toml +2 -2
- auto_editor-24.25.1/ae-ffmpeg/ae_ffmpeg/__init__.py +0 -16
- auto_editor-24.25.1/ae-ffmpeg/ae_ffmpeg/py.typed +0 -0
- auto_editor-24.25.1/ae-ffmpeg/setup.py +0 -65
- auto_editor-24.25.1/auto_editor/__init__.py +0 -2
- auto_editor-24.25.1/auto_editor.egg-info/requires.txt +0 -3
- {auto_editor-24.25.1 → auto_editor-24.29.1}/LICENSE +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/__init__.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/fcp11.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/json.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/formats/utils.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/help.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/__init__.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/json.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lang/libmath.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/__init__.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/data_structs.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/lib/err.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/preview.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/__init__.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/audio.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/render/subtitle.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/__init__.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/desc.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/info.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/palet.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/subcommands/subdump.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/timeline.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/__init__.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/bar.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/chunks.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/cmdkw.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/container.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/encoder.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/func.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/log.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/subtitle_tools.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/utils/types.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor/wavfile.py +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/dependency_links.txt +0 -0
- {auto_editor-24.25.1 → auto_editor-24.29.1}/auto_editor.egg-info/entry_points.txt +0 -0
- {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.
|
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.
|
15
|
-
Requires-Dist: pyav==12.
|
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
|
-
|
@@ -311,10 +311,10 @@ def main() -> None:
|
|
311
311
|
|
312
312
|
import av
|
313
313
|
|
314
|
-
print(f"
|
315
|
-
print(f"
|
316
|
-
print(f"PyAV
|
317
|
-
print(f"Auto-Editor
|
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.
|
147
|
+
os.path.dirname(self.temp), f"ae-{version}", "cache.npz"
|
197
148
|
)
|
198
149
|
|
199
150
|
try:
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
243
|
-
if
|
244
|
-
raise LevelError(f"audio: audio stream '{
|
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":
|
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,
|
180
|
+
sr, samples = read(self.ensure.audio(self.src, stream))
|
250
181
|
|
251
182
|
if len(samples) == 0:
|
252
|
-
raise LevelError(f"audio: stream '{
|
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 '{
|
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":
|
223
|
+
return self.cache("audio", {"stream": stream}, threshold_list)
|
293
224
|
|
294
225
|
def subtitle(
|
295
226
|
self,
|
296
|
-
|
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
|
-
|
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(
|
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,
|
303
|
+
def motion(self, stream: int, blur: int, width: int) -> NDArray[np.float64]:
|
374
304
|
import av
|
375
305
|
|
376
|
-
if
|
377
|
-
raise LevelError(f"motion: video stream '{
|
306
|
+
if stream >= len(self.src.videos):
|
307
|
+
raise LevelError(f"motion: video stream '{stream}' does not exist.")
|
378
308
|
|
379
|
-
mobj = {"stream":
|
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
|
-
|
386
|
-
|
315
|
+
video = container.streams.video[stream]
|
316
|
+
video.thread_type = "AUTO"
|
387
317
|
|
388
|
-
inaccurate_dur = 1 if
|
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=
|
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(
|
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],
|
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],
|
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
|
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,
|
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,
|
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 =
|
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
|
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"
|
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(
|
167
|
-
|
168
|
-
|
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(
|
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]
|
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
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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.
|
107
|
-
frame = graph.
|
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.
|
306
|
-
frame = graph.
|
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.
|
319
|
-
frame = graph.
|
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.
|
359
|
-
frame = scale_graph.
|
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
|
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
|
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
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
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,
|
130
|
+
obj = parse_with_palet(attrs, builder_map[method], env)
|
106
131
|
except ParserError as e:
|
107
132
|
log.error(e)
|
108
133
|
|
109
|
-
|
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
|
137
|
+
print_arr(levels.audio(**obj))
|
115
138
|
elif method == "motion":
|
116
|
-
print_arr(levels.motion(obj
|
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
|
-
|
77
|
-
|
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
|
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
|
-
|
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
|
-
|
70
|
+
result = []
|
71
71
|
|
72
72
|
for my_input in inputs:
|
73
|
-
if
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
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(
|
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
|
149
|
-
text.write(indent(data[
|
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.
|
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.
|
15
|
-
Requires-Dist: pyav==12.
|
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
|
-
|
@@ -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.
|
13
|
-
"pyav==12.
|
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
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|