auto-editor 26.3.2__tar.gz → 26.3.3__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-26.3.2 → auto_editor-26.3.3}/PKG-INFO +2 -2
- {auto_editor-26.3.2 → auto_editor-26.3.3}/README.md +1 -1
- auto_editor-26.3.3/auto_editor/__init__.py +1 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/__main__.py +146 -35
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/test.py +25 -21
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/edit.py +17 -15
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/make_layers.py +2 -1
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/output.py +2 -2
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/render/audio.py +4 -1
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/render/video.py +1 -1
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/timeline.py +8 -1
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/types.py +7 -118
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor.egg-info/PKG-INFO +2 -2
- auto_editor-26.3.2/auto_editor/__init__.py +0 -1
- {auto_editor-26.3.2 → auto_editor-26.3.3}/LICENSE +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/analyze.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/__init__.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/cache.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/desc.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/info.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/levels.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/palet.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/repl.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/cmds/subdump.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/ffwrapper.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/formats/__init__.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/formats/fcp11.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/formats/fcp7.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/formats/json.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/formats/shotcut.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/formats/utils.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/help.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lang/__init__.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lang/json.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lang/libintrospection.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lang/libmath.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lang/palet.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lang/stdenv.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lib/__init__.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lib/contracts.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lib/data_structs.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/lib/err.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/preview.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/render/__init__.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/render/subtitle.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/__init__.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/bar.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/chunks.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/cmdkw.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/container.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/func.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/utils/log.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/vanparse.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor/wavfile.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor.egg-info/SOURCES.txt +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor.egg-info/dependency_links.txt +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor.egg-info/entry_points.txt +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor.egg-info/requires.txt +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/auto_editor.egg-info/top_level.txt +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/docs/build.py +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/pyproject.toml +0 -0
- {auto_editor-26.3.2 → auto_editor-26.3.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: auto-editor
|
3
|
-
Version: 26.3.
|
3
|
+
Version: 26.3.3
|
4
4
|
Summary: Auto-Editor: Effort free video editing!
|
5
5
|
Author-email: WyattBlue <wyattblue@auto-editor.com>
|
6
6
|
License: Unlicense
|
@@ -21,7 +21,7 @@ Requires-Dist: pyav==14.2.*
|
|
21
21
|
---
|
22
22
|
|
23
23
|
[](https://github.com/wyattblue/auto-editor/actions)
|
24
|
-
|
24
|
+
[](https://github.com/astral-sh/ruff)
|
25
25
|
|
26
26
|
Before doing the real editing, you first cut out the "dead space" which is typically silence. This is known as a "first pass". Cutting these is a boring task, especially if the video is very long.
|
27
27
|
|
@@ -5,7 +5,7 @@
|
|
5
5
|
---
|
6
6
|
|
7
7
|
[](https://github.com/wyattblue/auto-editor/actions)
|
8
|
-
|
8
|
+
[](https://github.com/astral-sh/ruff)
|
9
9
|
|
10
10
|
Before doing the real editing, you first cut out the "dead space" which is typically silence. This is known as a "first pass". Cutting these is a boring task, especially if the video is very long.
|
11
11
|
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "26.3.3"
|
@@ -3,6 +3,8 @@
|
|
3
3
|
import platform as plat
|
4
4
|
import re
|
5
5
|
import sys
|
6
|
+
from dataclasses import dataclass, field
|
7
|
+
from fractions import Fraction
|
6
8
|
from io import StringIO
|
7
9
|
from os import environ
|
8
10
|
from os.path import exists, isdir, isfile, lexists, splitext
|
@@ -12,21 +14,127 @@ import auto_editor
|
|
12
14
|
from auto_editor.utils.func import get_stdout
|
13
15
|
from auto_editor.utils.log import Log
|
14
16
|
from auto_editor.utils.types import (
|
15
|
-
|
17
|
+
CoerceError,
|
16
18
|
frame_rate,
|
17
|
-
|
19
|
+
natural,
|
18
20
|
number,
|
19
21
|
parse_color,
|
20
|
-
|
21
|
-
sample_rate,
|
22
|
-
speed,
|
23
|
-
speed_range,
|
24
|
-
time_range,
|
22
|
+
split_num_str,
|
25
23
|
)
|
26
24
|
from auto_editor.vanparse import ArgumentParser
|
27
25
|
|
28
26
|
|
27
|
+
@dataclass(slots=True)
|
28
|
+
class Args:
|
29
|
+
input: list[str] = field(default_factory=list)
|
30
|
+
help: bool = False
|
31
|
+
|
32
|
+
# Editing Options
|
33
|
+
margin: tuple[str, str] = ("0.2s", "0.2s")
|
34
|
+
edit: str = "audio"
|
35
|
+
export: str | None = None
|
36
|
+
output: str | None = None
|
37
|
+
silent_speed: float = 99999.0
|
38
|
+
video_speed: float = 1.0
|
39
|
+
cut_out: list[tuple[str, str]] = field(default_factory=list)
|
40
|
+
add_in: list[tuple[str, str]] = field(default_factory=list)
|
41
|
+
set_speed_for_range: list[tuple[float, str, str]] = field(default_factory=list)
|
42
|
+
|
43
|
+
# Timeline Options
|
44
|
+
frame_rate: Fraction | None = None
|
45
|
+
sample_rate: int | None = None
|
46
|
+
resolution: tuple[int, int] | None = None
|
47
|
+
background: str = "#000000"
|
48
|
+
|
49
|
+
# URL download Options
|
50
|
+
yt_dlp_location: str = "yt-dlp"
|
51
|
+
download_format: str | None = None
|
52
|
+
output_format: str | None = None
|
53
|
+
yt_dlp_extras: str | None = None
|
54
|
+
|
55
|
+
# Display Options
|
56
|
+
progress: str = "modern"
|
57
|
+
debug: bool = False
|
58
|
+
quiet: bool = False
|
59
|
+
preview: bool = False
|
60
|
+
|
61
|
+
# Container Settings
|
62
|
+
sn: bool = False
|
63
|
+
dn: bool = False
|
64
|
+
faststart: bool = False
|
65
|
+
no_faststart: bool = False
|
66
|
+
fragmented: bool = False
|
67
|
+
no_fragmented: bool = False
|
68
|
+
|
69
|
+
# Video Rendering
|
70
|
+
video_codec: str = "auto"
|
71
|
+
video_bitrate: str = "auto"
|
72
|
+
vprofile: str | None = None
|
73
|
+
scale: float = 1.0
|
74
|
+
no_seek: bool = False
|
75
|
+
|
76
|
+
# Audio Rendering
|
77
|
+
audio_codec: str = "auto"
|
78
|
+
audio_bitrate: str = "auto"
|
79
|
+
keep_tracks_separate: bool = False
|
80
|
+
audio_normalize: str = "#f"
|
81
|
+
|
82
|
+
# Misc.
|
83
|
+
config: bool = False
|
84
|
+
no_cache: bool = False
|
85
|
+
no_open: bool = False
|
86
|
+
temp_dir: str | None = None
|
87
|
+
player: str | None = None
|
88
|
+
version: bool = False
|
89
|
+
|
90
|
+
|
29
91
|
def main_options(parser: ArgumentParser) -> ArgumentParser:
|
92
|
+
def margin(val: str) -> tuple[str, str]:
|
93
|
+
vals = val.strip().split(",")
|
94
|
+
if len(vals) == 1:
|
95
|
+
vals.append(vals[0])
|
96
|
+
if len(vals) != 2:
|
97
|
+
raise CoerceError("--margin has too many arguments.")
|
98
|
+
return vals[0], vals[1]
|
99
|
+
|
100
|
+
def speed(val: str) -> float:
|
101
|
+
_s = number(val)
|
102
|
+
if _s <= 0 or _s > 99999:
|
103
|
+
return 99999.0
|
104
|
+
return _s
|
105
|
+
|
106
|
+
def resolution(val: str | None) -> tuple[int, int] | None:
|
107
|
+
if val is None:
|
108
|
+
return None
|
109
|
+
vals = val.strip().split(",")
|
110
|
+
if len(vals) != 2:
|
111
|
+
raise CoerceError(f"'{val}': Resolution takes two numbers")
|
112
|
+
return natural(vals[0]), natural(vals[1])
|
113
|
+
|
114
|
+
def sample_rate(val: str) -> int:
|
115
|
+
num, unit = split_num_str(val)
|
116
|
+
if unit in {"kHz", "KHz"}:
|
117
|
+
return natural(num * 1000)
|
118
|
+
if unit not in {"", "Hz"}:
|
119
|
+
raise CoerceError(f"Unknown unit: '{unit}'")
|
120
|
+
return natural(num)
|
121
|
+
|
122
|
+
def _comma_coerce(name: str, val: str, num_args: int) -> list[str]:
|
123
|
+
vals = val.strip().split(",")
|
124
|
+
if num_args > len(vals):
|
125
|
+
raise CoerceError(f"Too few arguments for {name}.")
|
126
|
+
if len(vals) > num_args:
|
127
|
+
raise CoerceError(f"Too many arguments for {name}.")
|
128
|
+
return vals
|
129
|
+
|
130
|
+
def time_range(val: str) -> tuple[str, str]:
|
131
|
+
a = _comma_coerce("time_range", val, 2)
|
132
|
+
return a[0], a[1]
|
133
|
+
|
134
|
+
def speed_range(val: str) -> tuple[float, str, str]:
|
135
|
+
a = _comma_coerce("speed_range", val, 3)
|
136
|
+
return number(a[0]), a[1], a[2]
|
137
|
+
|
30
138
|
parser.add_required("input", nargs="*", metavar="[file | url ...] [options]")
|
31
139
|
parser.add_text("Editing Options:")
|
32
140
|
parser.add_argument(
|
@@ -41,6 +149,16 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
41
149
|
metavar="METHOD",
|
42
150
|
help="Set an expression which determines how to make auto edits",
|
43
151
|
)
|
152
|
+
parser.add_argument(
|
153
|
+
"--export", "-ex", metavar="EXPORT:ATTRS?", help="Choose the export mode"
|
154
|
+
)
|
155
|
+
parser.add_argument(
|
156
|
+
"--output",
|
157
|
+
"--output-file",
|
158
|
+
"-o",
|
159
|
+
metavar="FILE",
|
160
|
+
help="Set the name/path of the new output file",
|
161
|
+
)
|
44
162
|
parser.add_argument(
|
45
163
|
"--silent-speed",
|
46
164
|
"-s",
|
@@ -111,12 +229,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
111
229
|
metavar="COLOR",
|
112
230
|
help="Set the background as a solid RGB color",
|
113
231
|
)
|
114
|
-
parser.add_argument(
|
115
|
-
"--add",
|
116
|
-
nargs="*",
|
117
|
-
metavar="OBJ:START,DUR,ATTRS?",
|
118
|
-
help="Insert an audio/video object to the timeline",
|
119
|
-
)
|
120
232
|
parser.add_text("URL Download Options:")
|
121
233
|
parser.add_argument(
|
122
234
|
"--yt-dlp-location",
|
@@ -138,28 +250,6 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
138
250
|
metavar="CMD",
|
139
251
|
help="Add extra options for yt-dlp. Must be in quotes",
|
140
252
|
)
|
141
|
-
parser.add_text("Utility Options:")
|
142
|
-
parser.add_argument(
|
143
|
-
"--export", "-ex", metavar="EXPORT:ATTRS?", help="Choose the export mode"
|
144
|
-
)
|
145
|
-
parser.add_argument(
|
146
|
-
"--output-file",
|
147
|
-
"--output",
|
148
|
-
"-o",
|
149
|
-
metavar="FILE",
|
150
|
-
help="Set the name/path of the new output file",
|
151
|
-
)
|
152
|
-
parser.add_argument(
|
153
|
-
"--player", "-p", metavar="CMD", help="Set player to open output media files"
|
154
|
-
)
|
155
|
-
parser.add_argument(
|
156
|
-
"--no-open", flag=True, help="Do not open the output file after editing is done"
|
157
|
-
)
|
158
|
-
parser.add_argument(
|
159
|
-
"--temp-dir",
|
160
|
-
metavar="PATH",
|
161
|
-
help="Set where the temporary directory is located",
|
162
|
-
)
|
163
253
|
parser.add_text("Display Options:")
|
164
254
|
parser.add_argument(
|
165
255
|
"--progress",
|
@@ -186,6 +276,16 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
186
276
|
flag=True,
|
187
277
|
help="Disable the inclusion of data streams in the output file",
|
188
278
|
)
|
279
|
+
parser.add_argument(
|
280
|
+
"--faststart",
|
281
|
+
flag=True,
|
282
|
+
help="Enable movflags +faststart, recommended for web (default)",
|
283
|
+
)
|
284
|
+
parser.add_argument(
|
285
|
+
"--no-faststart",
|
286
|
+
flag=True,
|
287
|
+
help="Disable movflags +faststart, will be faster for large files",
|
288
|
+
)
|
189
289
|
parser.add_argument(
|
190
290
|
"--fragmented",
|
191
291
|
flag=True,
|
@@ -258,6 +358,17 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
|
|
258
358
|
parser.add_argument(
|
259
359
|
"--no-cache", flag=True, help="Don't look for or write a cache file"
|
260
360
|
)
|
361
|
+
parser.add_argument(
|
362
|
+
"--no-open", flag=True, help="Do not open the output file after editing is done"
|
363
|
+
)
|
364
|
+
parser.add_argument(
|
365
|
+
"--temp-dir",
|
366
|
+
metavar="PATH",
|
367
|
+
help="Set where the temporary directory is located",
|
368
|
+
)
|
369
|
+
parser.add_argument(
|
370
|
+
"--player", "-p", metavar="CMD", help="Set player to open output media files"
|
371
|
+
)
|
261
372
|
parser.add_argument("--version", "-V", flag=True, help="Display version and halt")
|
262
373
|
return parser
|
263
374
|
|
@@ -153,36 +153,42 @@ class Runner:
|
|
153
153
|
def desc(self):
|
154
154
|
self.raw(["desc", "example.mp4"])
|
155
155
|
|
156
|
-
def
|
157
|
-
|
156
|
+
def test_movflags(self) -> None:
|
157
|
+
file = "resources/testsrc.mp4"
|
158
|
+
out = self.main([file], ["--faststart"]) + ".mp4"
|
159
|
+
fast = calculate_sha256(out)
|
158
160
|
with av.open(out) as container:
|
159
|
-
|
160
|
-
|
161
|
+
assert isinstance(container.streams[0], av.VideoStream)
|
162
|
+
assert isinstance(container.streams[1], av.AudioStream)
|
161
163
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
assert
|
166
|
-
assert
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
assert
|
172
|
-
assert
|
164
|
+
out = self.main([file], ["--no-faststart"]) + ".mp4"
|
165
|
+
nofast = calculate_sha256(out)
|
166
|
+
with av.open(out) as container:
|
167
|
+
assert isinstance(container.streams[0], av.VideoStream)
|
168
|
+
assert isinstance(container.streams[1], av.AudioStream)
|
169
|
+
|
170
|
+
out = self.main([file], ["--fragmented"]) + ".mp4"
|
171
|
+
frag = calculate_sha256(out)
|
172
|
+
with av.open(out) as container:
|
173
|
+
assert isinstance(container.streams[0], av.VideoStream)
|
174
|
+
assert isinstance(container.streams[1], av.AudioStream)
|
173
175
|
|
174
|
-
|
176
|
+
assert fast != nofast, "+faststart is not being applied"
|
177
|
+
assert frag not in (fast, nofast), "fragmented output should diff."
|
175
178
|
|
176
|
-
|
179
|
+
def test_example(self) -> None:
|
180
|
+
out = self.main(["example.mp4"], [], output="example_ALTERED.mp4")
|
177
181
|
with av.open(out) as container:
|
182
|
+
assert container.duration is not None
|
183
|
+
assert container.duration > 17300000 and container.duration < 2 << 24
|
184
|
+
|
178
185
|
video = container.streams[0]
|
179
186
|
audio = container.streams[1]
|
180
|
-
|
181
187
|
assert isinstance(video, av.VideoStream)
|
182
188
|
assert isinstance(audio, av.AudioStream)
|
183
189
|
assert video.base_rate == 30
|
184
190
|
assert video.average_rate is not None
|
185
|
-
assert
|
191
|
+
assert video.average_rate == 30, video.average_rate
|
186
192
|
assert (video.width, video.height) == (1280, 720)
|
187
193
|
assert video.codec.name == "h264"
|
188
194
|
assert video.language == "eng"
|
@@ -190,8 +196,6 @@ class Runner:
|
|
190
196
|
assert audio.sample_rate == 48000
|
191
197
|
assert audio.language == "eng"
|
192
198
|
|
193
|
-
assert calculate_sha256(out) != out1_sha, "Fragmented output should be diff."
|
194
|
-
|
195
199
|
# PR #260
|
196
200
|
def test_high_speed(self):
|
197
201
|
self.check(["example.mp4", "--video-speed", "99998"], "empty")
|
@@ -6,7 +6,7 @@ from fractions import Fraction
|
|
6
6
|
from heapq import heappop, heappush
|
7
7
|
from os.path import splitext
|
8
8
|
from subprocess import run
|
9
|
-
from typing import Any
|
9
|
+
from typing import TYPE_CHECKING, Any
|
10
10
|
|
11
11
|
import av
|
12
12
|
from av import AudioResampler, Codec
|
@@ -24,7 +24,9 @@ from auto_editor.utils.chunks import Chunk, Chunks
|
|
24
24
|
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
|
25
25
|
from auto_editor.utils.container import Container, container_constructor
|
26
26
|
from auto_editor.utils.log import Log
|
27
|
-
|
27
|
+
|
28
|
+
if TYPE_CHECKING:
|
29
|
+
from auto_editor.__main__ import Args
|
28
30
|
|
29
31
|
|
30
32
|
def set_output(
|
@@ -206,7 +208,7 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
206
208
|
|
207
209
|
del paths
|
208
210
|
|
209
|
-
output, export_ops = set_output(args.
|
211
|
+
output, export_ops = set_output(args.output, args.export, src, log)
|
210
212
|
assert "export" in export_ops
|
211
213
|
export = export_ops["export"]
|
212
214
|
|
@@ -219,10 +221,6 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
219
221
|
if os.path.isdir(output):
|
220
222
|
log.error("Output path already has an existing directory!")
|
221
223
|
|
222
|
-
if os.path.isfile(output) and src is not None and src.path != output: # type: ignore
|
223
|
-
log.debug(f"Removing already existing file: {output}")
|
224
|
-
os.remove(output)
|
225
|
-
|
226
224
|
if args.sample_rate is None:
|
227
225
|
if tl is None:
|
228
226
|
samplerate = 48000 if src is None else src.get_sr()
|
@@ -297,15 +295,19 @@ def edit_media(paths: list[str], args: Args, log: Log) -> None:
|
|
297
295
|
def make_media(tl: v3, output_path: str) -> None:
|
298
296
|
assert src is not None
|
299
297
|
|
298
|
+
options = {}
|
299
|
+
mov_flags = []
|
300
300
|
if args.fragmented and not args.no_fragmented:
|
301
|
-
|
302
|
-
options =
|
303
|
-
|
304
|
-
"
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
301
|
+
mov_flags.extend(["default_base_moof", "frag_keyframe", "separate_moof"])
|
302
|
+
options["frag_duration"] = "0.2"
|
303
|
+
if args.faststart:
|
304
|
+
log.warning("Fragmented is enabled, will not apply faststart.")
|
305
|
+
elif not args.no_faststart:
|
306
|
+
mov_flags.append("faststart")
|
307
|
+
if mov_flags:
|
308
|
+
options["movflags"] = "+".join(mov_flags)
|
309
|
+
|
310
|
+
output = av.open(output_path, "w", container_options=options)
|
309
311
|
|
310
312
|
if ctr.default_sub != "none" and not args.sn:
|
311
313
|
sub_paths = make_new_subtitles(tl, log)
|
@@ -13,11 +13,12 @@ from auto_editor.lib.data_structs import print_str
|
|
13
13
|
from auto_editor.lib.err import MyError
|
14
14
|
from auto_editor.timeline import ASpace, TlAudio, TlVideo, VSpace, v1, v3
|
15
15
|
from auto_editor.utils.func import mut_margin
|
16
|
-
from auto_editor.utils.types import
|
16
|
+
from auto_editor.utils.types import CoerceError, time
|
17
17
|
|
18
18
|
if TYPE_CHECKING:
|
19
19
|
from numpy.typing import NDArray
|
20
20
|
|
21
|
+
from auto_editor.__main__ import Args
|
21
22
|
from auto_editor.utils.bar import Bar
|
22
23
|
from auto_editor.utils.chunks import Chunks
|
23
24
|
from auto_editor.utils.log import Log
|
@@ -9,12 +9,12 @@ from av.audio.resampler import AudioResampler
|
|
9
9
|
from auto_editor.ffwrapper import FileInfo
|
10
10
|
from auto_editor.utils.bar import Bar
|
11
11
|
from auto_editor.utils.log import Log
|
12
|
-
from auto_editor.utils.types import
|
12
|
+
from auto_editor.utils.types import split_num_str
|
13
13
|
|
14
14
|
|
15
15
|
def parse_bitrate(input_: str, log: Log) -> int:
|
16
16
|
try:
|
17
|
-
val, unit =
|
17
|
+
val, unit = split_num_str(input_)
|
18
18
|
except Exception as e:
|
19
19
|
log.error(e)
|
20
20
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import io
|
4
4
|
from pathlib import Path
|
5
|
+
from typing import TYPE_CHECKING
|
5
6
|
|
6
7
|
import av
|
7
8
|
import numpy as np
|
@@ -18,9 +19,11 @@ from auto_editor.utils.bar import Bar
|
|
18
19
|
from auto_editor.utils.cmdkw import ParserError, parse_with_palet, pAttr, pAttrs
|
19
20
|
from auto_editor.utils.container import Container
|
20
21
|
from auto_editor.utils.log import Log
|
21
|
-
from auto_editor.utils.types import Args
|
22
22
|
from auto_editor.wavfile import AudioData, read, write
|
23
23
|
|
24
|
+
if TYPE_CHECKING:
|
25
|
+
from auto_editor.__main__ import Args
|
26
|
+
|
24
27
|
norm_types = {
|
25
28
|
"ebu": pAttrs(
|
26
29
|
"ebu",
|
@@ -13,10 +13,10 @@ if TYPE_CHECKING:
|
|
13
13
|
from collections.abc import Iterator
|
14
14
|
from typing import Any
|
15
15
|
|
16
|
+
from auto_editor.__main__ import Args
|
16
17
|
from auto_editor.ffwrapper import FileInfo
|
17
18
|
from auto_editor.timeline import v3
|
18
19
|
from auto_editor.utils.log import Log
|
19
|
-
from auto_editor.utils.types import Args
|
20
20
|
|
21
21
|
|
22
22
|
@dataclass(slots=True)
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
|
|
6
6
|
from auto_editor.ffwrapper import initFileInfo, mux
|
7
7
|
from auto_editor.lib.contracts import *
|
8
8
|
from auto_editor.utils.cmdkw import Required, pAttr, pAttrs
|
9
|
-
from auto_editor.utils.types import natural, number, parse_color
|
9
|
+
from auto_editor.utils.types import CoerceError, natural, number, parse_color
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from collections.abc import Iterator
|
@@ -128,6 +128,13 @@ class TlRect:
|
|
128
128
|
}
|
129
129
|
|
130
130
|
|
131
|
+
def threshold(val: str | float) -> float:
|
132
|
+
num = number(val)
|
133
|
+
if num > 1 or num < 0:
|
134
|
+
raise CoerceError(f"'{val}': Threshold must be between 0 and 1 (0%-100%)")
|
135
|
+
return num
|
136
|
+
|
137
|
+
|
131
138
|
video_builder = pAttrs(
|
132
139
|
"video",
|
133
140
|
pAttr("start", Required, is_nat, natural),
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import re
|
4
|
-
from dataclasses import dataclass, field
|
5
4
|
from fractions import Fraction
|
6
5
|
|
7
6
|
|
@@ -9,16 +8,7 @@ class CoerceError(Exception):
|
|
9
8
|
pass
|
10
9
|
|
11
10
|
|
12
|
-
def
|
13
|
-
vals = val.strip().split(",")
|
14
|
-
if num_args > len(vals):
|
15
|
-
raise CoerceError(f"Too few arguments for {name}.")
|
16
|
-
if len(vals) > num_args:
|
17
|
-
raise CoerceError(f"Too many arguments for {name}.")
|
18
|
-
return vals
|
19
|
-
|
20
|
-
|
21
|
-
def _split_num_str(val: str | float) -> tuple[float, str]:
|
11
|
+
def split_num_str(val: str | float) -> tuple[float, str]:
|
22
12
|
if isinstance(val, float | int):
|
23
13
|
return val, ""
|
24
14
|
|
@@ -35,14 +25,9 @@ def _split_num_str(val: str | float) -> tuple[float, str]:
|
|
35
25
|
return float(num), unit
|
36
26
|
|
37
27
|
|
38
|
-
def _unit_check(unit: str, allowed_units: tuple[str, ...]) -> None:
|
39
|
-
if unit not in allowed_units:
|
40
|
-
raise CoerceError(f"Unknown unit: '{unit}'")
|
41
|
-
|
42
|
-
|
43
28
|
# Numbers: 0, 1, 2, 3, ...
|
44
29
|
def natural(val: str | float) -> int:
|
45
|
-
num, unit =
|
30
|
+
num, unit = split_num_str(val)
|
46
31
|
if unit != "":
|
47
32
|
raise CoerceError(f"'{val}': Natural does not allow units.")
|
48
33
|
if not isinstance(num, int) and not num.is_integer():
|
@@ -69,25 +54,12 @@ def number(val: str | float) -> float:
|
|
69
54
|
raise CoerceError(f"'{val}': Denominator must not be zero.")
|
70
55
|
return vs[0] / vs[1]
|
71
56
|
|
72
|
-
num, unit =
|
57
|
+
num, unit = split_num_str(val)
|
73
58
|
if unit == "%":
|
74
59
|
return num / 100
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
def speed(val: str) -> float:
|
80
|
-
_s = number(val)
|
81
|
-
if _s <= 0 or _s > 99999:
|
82
|
-
return 99999.0
|
83
|
-
return _s
|
84
|
-
|
85
|
-
|
86
|
-
def threshold(val: str | float) -> float:
|
87
|
-
num = number(val)
|
88
|
-
if num > 1 or num < 0:
|
89
|
-
raise CoerceError(f"'{val}': Threshold must be between 0 and 1 (0%-100%)")
|
90
|
-
return num
|
60
|
+
if unit == "":
|
61
|
+
return num
|
62
|
+
raise CoerceError(f"Unknown unit: '{unit}'")
|
91
63
|
|
92
64
|
|
93
65
|
def frame_rate(val: str) -> Fraction:
|
@@ -102,14 +74,6 @@ def frame_rate(val: str) -> Fraction:
|
|
102
74
|
return Fraction(val)
|
103
75
|
|
104
76
|
|
105
|
-
def sample_rate(val: str) -> int:
|
106
|
-
num, unit = _split_num_str(val)
|
107
|
-
if unit in {"kHz", "KHz"}:
|
108
|
-
return natural(num * 1000)
|
109
|
-
_unit_check(unit, ("", "Hz"))
|
110
|
-
return natural(num)
|
111
|
-
|
112
|
-
|
113
77
|
def time(val: str, tb: Fraction) -> int:
|
114
78
|
if ":" in val:
|
115
79
|
boxes = val.split(":")
|
@@ -121,7 +85,7 @@ def time(val: str, tb: Fraction) -> int:
|
|
121
85
|
)
|
122
86
|
raise CoerceError(f"'{val}': Invalid time format")
|
123
87
|
|
124
|
-
num, unit =
|
88
|
+
num, unit = split_num_str(val)
|
125
89
|
if unit in {"s", "sec", "secs", "second", "seconds"}:
|
126
90
|
return round(num * tb)
|
127
91
|
if unit in {"min", "mins", "minute", "minutes"}:
|
@@ -136,25 +100,6 @@ def time(val: str, tb: Fraction) -> int:
|
|
136
100
|
return int(num)
|
137
101
|
|
138
102
|
|
139
|
-
def margin(val: str) -> tuple[str, str]:
|
140
|
-
vals = val.strip().split(",")
|
141
|
-
if len(vals) == 1:
|
142
|
-
vals.append(vals[0])
|
143
|
-
if len(vals) != 2:
|
144
|
-
raise CoerceError("--margin has too many arguments.")
|
145
|
-
return vals[0], vals[1]
|
146
|
-
|
147
|
-
|
148
|
-
def time_range(val: str) -> tuple[str, str]:
|
149
|
-
a = _comma_coerce("time_range", val, 2)
|
150
|
-
return a[0], a[1]
|
151
|
-
|
152
|
-
|
153
|
-
def speed_range(val: str) -> tuple[float, str, str]:
|
154
|
-
a = _comma_coerce("speed_range", val, 3)
|
155
|
-
return number(a[0]), a[1], a[2]
|
156
|
-
|
157
|
-
|
158
103
|
def parse_color(val: str) -> str:
|
159
104
|
"""
|
160
105
|
Convert a color str into an RGB tuple
|
@@ -179,62 +124,6 @@ def parse_color(val: str) -> str:
|
|
179
124
|
raise ValueError(f"Invalid Color: '{color}'")
|
180
125
|
|
181
126
|
|
182
|
-
def resolution(val: str | None) -> tuple[int, int] | None:
|
183
|
-
if val is None:
|
184
|
-
return None
|
185
|
-
vals = val.strip().split(",")
|
186
|
-
if len(vals) != 2:
|
187
|
-
raise CoerceError(f"'{val}': Resolution takes two numbers")
|
188
|
-
|
189
|
-
return natural(vals[0]), natural(vals[1])
|
190
|
-
|
191
|
-
|
192
|
-
@dataclass(slots=True)
|
193
|
-
class Args:
|
194
|
-
yt_dlp_location: str = "yt-dlp"
|
195
|
-
download_format: str | None = None
|
196
|
-
output_format: str | None = None
|
197
|
-
yt_dlp_extras: str | None = None
|
198
|
-
video_codec: str = "auto"
|
199
|
-
audio_codec: str = "auto"
|
200
|
-
video_bitrate: str = "auto"
|
201
|
-
vprofile: str | None = None
|
202
|
-
audio_bitrate: str = "auto"
|
203
|
-
scale: float = 1.0
|
204
|
-
fragmented: bool = False
|
205
|
-
no_fragmented: bool = False
|
206
|
-
sn: bool = False
|
207
|
-
dn: bool = False
|
208
|
-
no_seek: bool = False
|
209
|
-
cut_out: list[tuple[str, str]] = field(default_factory=list)
|
210
|
-
add_in: list[tuple[str, str]] = field(default_factory=list)
|
211
|
-
set_speed_for_range: list[tuple[float, str, str]] = field(default_factory=list)
|
212
|
-
frame_rate: Fraction | None = None
|
213
|
-
sample_rate: int | None = None
|
214
|
-
resolution: tuple[int, int] | None = None
|
215
|
-
background: str = "#000000"
|
216
|
-
edit: str = "audio"
|
217
|
-
keep_tracks_separate: bool = False
|
218
|
-
audio_normalize: str = "#f"
|
219
|
-
export: str | None = None
|
220
|
-
player: str | None = None
|
221
|
-
no_open: bool = False
|
222
|
-
temp_dir: str | None = None
|
223
|
-
progress: str = "modern"
|
224
|
-
version: bool = False
|
225
|
-
debug: bool = False
|
226
|
-
config: bool = False
|
227
|
-
quiet: bool = False
|
228
|
-
preview: bool = False
|
229
|
-
no_cache: bool = False
|
230
|
-
margin: tuple[str, str] = ("0.2s", "0.2s")
|
231
|
-
silent_speed: float = 99999.0
|
232
|
-
video_speed: float = 1.0
|
233
|
-
output_file: str | None = None
|
234
|
-
help: bool = False
|
235
|
-
input: list[str] = field(default_factory=list)
|
236
|
-
|
237
|
-
|
238
127
|
colormap = {
|
239
128
|
# Taken from https://www.w3.org/TR/css-color-4/#named-color
|
240
129
|
"aliceblue": "#f0f8ff",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: auto-editor
|
3
|
-
Version: 26.3.
|
3
|
+
Version: 26.3.3
|
4
4
|
Summary: Auto-Editor: Effort free video editing!
|
5
5
|
Author-email: WyattBlue <wyattblue@auto-editor.com>
|
6
6
|
License: Unlicense
|
@@ -21,7 +21,7 @@ Requires-Dist: pyav==14.2.*
|
|
21
21
|
---
|
22
22
|
|
23
23
|
[](https://github.com/wyattblue/auto-editor/actions)
|
24
|
-
|
24
|
+
[](https://github.com/astral-sh/ruff)
|
25
25
|
|
26
26
|
Before doing the real editing, you first cut out the "dead space" which is typically silence. This is known as a "first pass". Cutting these is a boring task, especially if the video is very long.
|
27
27
|
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "26.3.2"
|
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
|
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
|