auto-editor 24.9.1__tar.gz → 24.19.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.9.1/auto_editor.egg-info → auto_editor-24.19.1}/PKG-INFO +12 -37
- {auto-editor-24.9.1 → auto_editor-24.19.1}/README.md +9 -34
- {auto-editor-24.9.1 → auto_editor-24.19.1}/ae-ffmpeg/ae_ffmpeg/__init__.py +1 -1
- auto_editor-24.19.1/auto_editor/__init__.py +2 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/edit.py +2 -2
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/ffwrapper.py +23 -50
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/fcp11.py +7 -7
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/fcp7.py +7 -8
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/json.py +29 -8
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/shotcut.py +3 -3
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/make_layers.py +10 -3
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/output.py +13 -6
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/preview.py +2 -1
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/audio.py +2 -2
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/video.py +1 -2
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/desc.py +2 -4
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/info.py +24 -19
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/levels.py +1 -1
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/repl.py +1 -1
- auto_editor-24.19.1/auto_editor/subcommands/subdump.py +23 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/test.py +7 -6
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/container.py +7 -7
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/wavfile.py +3 -3
- {auto-editor-24.9.1 → auto_editor-24.19.1/auto_editor.egg-info}/PKG-INFO +12 -37
- auto_editor-24.19.1/auto_editor.egg-info/requires.txt +3 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/pyproject.toml +2 -2
- auto-editor-24.9.1/auto_editor/__init__.py +0 -2
- auto-editor-24.9.1/auto_editor/subcommands/subdump.py +0 -58
- auto-editor-24.9.1/auto_editor.egg-info/requires.txt +0 -3
- {auto-editor-24.9.1 → auto_editor-24.19.1}/LICENSE +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/ae-ffmpeg/ae_ffmpeg/py.typed +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/ae-ffmpeg/setup.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/__main__.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/analyze.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/__init__.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/utils.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/help.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/__init__.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/json.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/libmath.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/palet.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/__init__.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/contracts.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/data_structs.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/err.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/__init__.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/subtitle.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/__init__.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/palet.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/timeline.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/__init__.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/bar.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/chunks.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/cmdkw.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/encoder.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/func.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/log.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/types.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/validate_input.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/vanparse.py +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/SOURCES.txt +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/dependency_links.txt +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/entry_points.txt +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/top_level.txt +0 -0
- {auto-editor-24.9.1 → auto_editor-24.19.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.19.1
|
4
4
|
Summary: Auto-Editor: Effort free video editing!
|
5
5
|
Author-email: WyattBlue <wyattblue@auto-editor.com>
|
6
6
|
License: Unlicense
|
@@ -12,8 +12,8 @@ Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
13
13
|
License-File: LICENSE
|
14
14
|
Requires-Dist: numpy>=1.22.0
|
15
|
-
Requires-Dist: pyav==12.0.
|
16
|
-
Requires-Dist: ae-ffmpeg==1.
|
15
|
+
Requires-Dist: pyav==12.0.5
|
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>
|
19
19
|
|
@@ -60,10 +60,10 @@ The `--edit` option is how auto-editor makes automated cuts.
|
|
60
60
|
For example, edit out motionlessness in a video by setting `--edit motion`.
|
61
61
|
|
62
62
|
```
|
63
|
-
# cut out sections where
|
64
|
-
auto-editor example.mp4 --edit motion:threshold=
|
63
|
+
# cut out sections where the total motion is less than 2%.
|
64
|
+
auto-editor example.mp4 --edit motion:threshold=0.02
|
65
65
|
|
66
|
-
#
|
66
|
+
# `--edit audio:threshold=0.04,stream=all` is used by defaut.
|
67
67
|
auto-editor example.mp4
|
68
68
|
|
69
69
|
# Different tracks can be set with different attribute.
|
@@ -73,34 +73,14 @@ auto-editor multi-track.mov --edit "(or audio:stream=0 audio:threshold=10%,strea
|
|
73
73
|
Different editing methods can be used together.
|
74
74
|
```
|
75
75
|
# 'threshold' is always the first argument for edit-method objects
|
76
|
-
auto-editor example.mp4 --edit "(or audio:
|
76
|
+
auto-editor example.mp4 --edit "(or audio:0.03 motion:0.06)"
|
77
77
|
```
|
78
78
|
|
79
79
|
You can also use `dB` unit, a volume unit familiar to video-editors (case sensitive):
|
80
80
|
```
|
81
|
-
auto-editor example.mp4 --edit audio
|
81
|
+
auto-editor example.mp4 --edit audio:-19dB
|
82
82
|
auto-editor example.mp4 --edit audio:-7dB
|
83
83
|
auto-editor example.mp4 --edit motion:-19dB
|
84
|
-
|
85
|
-
# The `dB` unit is a just a macro that expands into an S-expression:
|
86
|
-
# '-19dB
|
87
|
-
# > '(pow 10 (/ -19 20))
|
88
|
-
# (eval '(pow 10 (/ -19 20)))
|
89
|
-
# > 0.11220184543019636
|
90
|
-
```
|
91
|
-
|
92
|
-
### Working With Multiple Audio Tracks
|
93
|
-
By default, only the first audio track will used for editing (track 0). You can change this with these commands.
|
94
|
-
|
95
|
-
Use all audio tracks for editing:
|
96
|
-
```
|
97
|
-
auto-editor multi-track.mov --edit audio:stream=all
|
98
|
-
```
|
99
|
-
|
100
|
-
Use only the second, fourth, and sixth audio track:
|
101
|
-
```
|
102
|
-
# track numbers start at 0
|
103
|
-
auto-editor so-many-tracks.mp4 --edit "(or audio:stream=1 audio:stream=3 audio:stream=5)"
|
104
84
|
```
|
105
85
|
|
106
86
|
### See What Auto-Editor Cuts Out
|
@@ -119,12 +99,10 @@ auto-editor example.mp4 --export premiere
|
|
119
99
|
```
|
120
100
|
|
121
101
|
Auto-Editor can also export to:
|
122
|
-
|
123
102
|
- DaVinci Resolve with `--export resolve`
|
124
103
|
- Final Cut Pro with `--export final-cut-pro`
|
125
104
|
- ShotCut with `--export shotcut`
|
126
|
-
|
127
|
-
Other editors, like Sony Vegas, can understand the `premiere` format. If your favorite editor doesn't, you can use ` --export clip-sequence` which creates many video clips that can be imported and manipulated like normal.
|
105
|
+
- Individual media clips with `--export clip-sequence`
|
128
106
|
|
129
107
|
### Naming Timelines
|
130
108
|
By default, auto-editor will name the timeline to "Auto-Editor Media Group" if the export supports naming.
|
@@ -188,14 +166,11 @@ List all available options:
|
|
188
166
|
auto-editor --help
|
189
167
|
```
|
190
168
|
|
191
|
-
Use `--help` with a specific option
|
169
|
+
Use `--help` with a specific option to learn more about it:
|
192
170
|
|
193
171
|
```
|
194
|
-
auto-editor
|
195
|
-
|
196
|
-
|
197
|
-
default: 1.0
|
198
|
-
Scale the output video's resolution by NUM factor
|
172
|
+
auto-editor -c:v --help
|
173
|
+
auto-editor --margin --help
|
199
174
|
```
|
200
175
|
|
201
176
|
<h3 align="center">Auto-Editor is available on all major platforms</h3>
|
@@ -43,10 +43,10 @@ The `--edit` option is how auto-editor makes automated cuts.
|
|
43
43
|
For example, edit out motionlessness in a video by setting `--edit motion`.
|
44
44
|
|
45
45
|
```
|
46
|
-
# cut out sections where
|
47
|
-
auto-editor example.mp4 --edit motion:threshold=
|
46
|
+
# cut out sections where the total motion is less than 2%.
|
47
|
+
auto-editor example.mp4 --edit motion:threshold=0.02
|
48
48
|
|
49
|
-
#
|
49
|
+
# `--edit audio:threshold=0.04,stream=all` is used by defaut.
|
50
50
|
auto-editor example.mp4
|
51
51
|
|
52
52
|
# Different tracks can be set with different attribute.
|
@@ -56,34 +56,14 @@ auto-editor multi-track.mov --edit "(or audio:stream=0 audio:threshold=10%,strea
|
|
56
56
|
Different editing methods can be used together.
|
57
57
|
```
|
58
58
|
# 'threshold' is always the first argument for edit-method objects
|
59
|
-
auto-editor example.mp4 --edit "(or audio:
|
59
|
+
auto-editor example.mp4 --edit "(or audio:0.03 motion:0.06)"
|
60
60
|
```
|
61
61
|
|
62
62
|
You can also use `dB` unit, a volume unit familiar to video-editors (case sensitive):
|
63
63
|
```
|
64
|
-
auto-editor example.mp4 --edit audio
|
64
|
+
auto-editor example.mp4 --edit audio:-19dB
|
65
65
|
auto-editor example.mp4 --edit audio:-7dB
|
66
66
|
auto-editor example.mp4 --edit motion:-19dB
|
67
|
-
|
68
|
-
# The `dB` unit is a just a macro that expands into an S-expression:
|
69
|
-
# '-19dB
|
70
|
-
# > '(pow 10 (/ -19 20))
|
71
|
-
# (eval '(pow 10 (/ -19 20)))
|
72
|
-
# > 0.11220184543019636
|
73
|
-
```
|
74
|
-
|
75
|
-
### Working With Multiple Audio Tracks
|
76
|
-
By default, only the first audio track will used for editing (track 0). You can change this with these commands.
|
77
|
-
|
78
|
-
Use all audio tracks for editing:
|
79
|
-
```
|
80
|
-
auto-editor multi-track.mov --edit audio:stream=all
|
81
|
-
```
|
82
|
-
|
83
|
-
Use only the second, fourth, and sixth audio track:
|
84
|
-
```
|
85
|
-
# track numbers start at 0
|
86
|
-
auto-editor so-many-tracks.mp4 --edit "(or audio:stream=1 audio:stream=3 audio:stream=5)"
|
87
67
|
```
|
88
68
|
|
89
69
|
### See What Auto-Editor Cuts Out
|
@@ -102,12 +82,10 @@ auto-editor example.mp4 --export premiere
|
|
102
82
|
```
|
103
83
|
|
104
84
|
Auto-Editor can also export to:
|
105
|
-
|
106
85
|
- DaVinci Resolve with `--export resolve`
|
107
86
|
- Final Cut Pro with `--export final-cut-pro`
|
108
87
|
- ShotCut with `--export shotcut`
|
109
|
-
|
110
|
-
Other editors, like Sony Vegas, can understand the `premiere` format. If your favorite editor doesn't, you can use ` --export clip-sequence` which creates many video clips that can be imported and manipulated like normal.
|
88
|
+
- Individual media clips with `--export clip-sequence`
|
111
89
|
|
112
90
|
### Naming Timelines
|
113
91
|
By default, auto-editor will name the timeline to "Auto-Editor Media Group" if the export supports naming.
|
@@ -171,14 +149,11 @@ List all available options:
|
|
171
149
|
auto-editor --help
|
172
150
|
```
|
173
151
|
|
174
|
-
Use `--help` with a specific option
|
152
|
+
Use `--help` with a specific option to learn more about it:
|
175
153
|
|
176
154
|
```
|
177
|
-
auto-editor
|
178
|
-
|
179
|
-
|
180
|
-
default: 1.0
|
181
|
-
Scale the output video's resolution by NUM factor
|
155
|
+
auto-editor -c:v --help
|
156
|
+
auto-editor --margin --help
|
182
157
|
```
|
183
158
|
|
184
159
|
<h3 align="center">Auto-Editor is available on all major platforms</h3>
|
@@ -177,11 +177,11 @@ def edit_media(
|
|
177
177
|
elif path_ext == ".json":
|
178
178
|
from auto_editor.formats.json import read_json
|
179
179
|
|
180
|
-
tl = read_json(paths[0],
|
180
|
+
tl = read_json(paths[0], log)
|
181
181
|
sources = [] if tl.src is None else [tl.src]
|
182
182
|
src = tl.src
|
183
183
|
else:
|
184
|
-
sources = [initFileInfo(path,
|
184
|
+
sources = [initFileInfo(path, log) for path in paths]
|
185
185
|
src = None if not sources else sources[0]
|
186
186
|
|
187
187
|
del paths
|
@@ -135,10 +135,10 @@ class VideoStream:
|
|
135
135
|
sar: Fraction
|
136
136
|
time_base: Fraction | None
|
137
137
|
pix_fmt: str | None
|
138
|
-
color_range:
|
139
|
-
color_space:
|
140
|
-
color_primaries:
|
141
|
-
color_transfer:
|
138
|
+
color_range: int
|
139
|
+
color_space: int
|
140
|
+
color_primaries: int
|
141
|
+
color_transfer: int
|
142
142
|
bitrate: int
|
143
143
|
lang: str | None
|
144
144
|
|
@@ -189,7 +189,7 @@ class FileInfo:
|
|
189
189
|
return f"@{self.path.name}"
|
190
190
|
|
191
191
|
|
192
|
-
def initFileInfo(path: str,
|
192
|
+
def initFileInfo(path: str, log: Log) -> FileInfo:
|
193
193
|
import av
|
194
194
|
|
195
195
|
av.logging.set_level(av.logging.PANIC)
|
@@ -203,14 +203,11 @@ def initFileInfo(path: str, ffmpeg: FFmpeg, log: Log) -> FileInfo:
|
|
203
203
|
audios: tuple[AudioStream, ...] = ()
|
204
204
|
subtitles: tuple[SubtitleStream, ...] = ()
|
205
205
|
|
206
|
-
|
207
|
-
_ext = os.path.splitext(ffmpeg.path)[1]
|
208
|
-
ffprobe = os.path.join(_dir, f"ffprobe{_ext}")
|
209
|
-
|
210
|
-
for i, v in enumerate(cont.streams.video):
|
211
|
-
vdur = 0.0
|
206
|
+
for v in cont.streams.video:
|
212
207
|
if v.duration is not None and v.time_base is not None:
|
213
208
|
vdur = float(v.duration * v.time_base)
|
209
|
+
else:
|
210
|
+
vdur = 0.0
|
214
211
|
|
215
212
|
fps = v.average_rate
|
216
213
|
if (fps is None or fps < 1) and v.name in ("png", "mjpeg", "webp"):
|
@@ -218,36 +215,11 @@ def initFileInfo(path: str, ffmpeg: FFmpeg, log: Log) -> FileInfo:
|
|
218
215
|
if fps is None or fps == 0:
|
219
216
|
fps = Fraction(30)
|
220
217
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
"-v",
|
227
|
-
"error",
|
228
|
-
"-select_streams",
|
229
|
-
f"v:{i}",
|
230
|
-
"-show_entries",
|
231
|
-
"stream=sample_aspect_ratio:stream=color_range:stream=color_space:stream=color_primaries:stream=color_transfer",
|
232
|
-
"-of",
|
233
|
-
"default=noprint_wrappers=1:nokey=1",
|
234
|
-
path,
|
235
|
-
]
|
236
|
-
)
|
237
|
-
_sar, c_range, c_space, c_primary, c_transfer = _raw.strip().split("\n")
|
238
|
-
except Exception:
|
239
|
-
log.debug("Unexpected ffprobe shape")
|
240
|
-
|
241
|
-
if v.sample_aspect_ratio is None:
|
242
|
-
if _sar is None:
|
243
|
-
sar = Fraction(1)
|
244
|
-
else:
|
245
|
-
try:
|
246
|
-
sar = Fraction(_sar.replace(":", "/"))
|
247
|
-
except Exception:
|
248
|
-
sar = Fraction(1)
|
249
|
-
else:
|
250
|
-
sar = v.sample_aspect_ratio
|
218
|
+
sar = Fraction(1) if v.sample_aspect_ratio is None else v.sample_aspect_ratio
|
219
|
+
cc = v.codec_context
|
220
|
+
|
221
|
+
if v.name is None:
|
222
|
+
log.error(f"Can't detect codec for video stream {v}")
|
251
223
|
|
252
224
|
videos += (
|
253
225
|
VideoStream(
|
@@ -258,11 +230,11 @@ def initFileInfo(path: str, ffmpeg: FFmpeg, log: Log) -> FileInfo:
|
|
258
230
|
vdur,
|
259
231
|
sar,
|
260
232
|
v.time_base,
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
233
|
+
cc.pix_fmt,
|
234
|
+
cc.color_range,
|
235
|
+
cc.colorspace,
|
236
|
+
cc.color_primaries,
|
237
|
+
cc.color_trc,
|
266
238
|
0 if v.bit_rate is None else v.bit_rate,
|
267
239
|
v.language,
|
268
240
|
),
|
@@ -273,13 +245,14 @@ def initFileInfo(path: str, ffmpeg: FFmpeg, log: Log) -> FileInfo:
|
|
273
245
|
if a.duration is not None and a.time_base is not None:
|
274
246
|
adur = float(a.duration * a.time_base)
|
275
247
|
|
248
|
+
a_cc = a.codec_context
|
276
249
|
audios += (
|
277
250
|
AudioStream(
|
278
|
-
|
279
|
-
0 if
|
280
|
-
|
251
|
+
a_cc.name,
|
252
|
+
0 if a_cc.sample_rate is None else a_cc.sample_rate,
|
253
|
+
a_cc.channels,
|
281
254
|
adur,
|
282
|
-
0 if
|
255
|
+
0 if a_cc.bit_rate is None else a_cc.bit_rate,
|
283
256
|
a.language,
|
284
257
|
),
|
285
258
|
)
|
@@ -32,13 +32,13 @@ def get_colorspace(src: FileInfo) -> str:
|
|
32
32
|
s = src.videos[0]
|
33
33
|
if s.pix_fmt == "rgb24":
|
34
34
|
return "sRGB IEC61966-2.1"
|
35
|
-
if s.color_space == "
|
36
|
-
return "6-1-6 (Rec. 601 NTSC)"
|
37
|
-
if s.color_space == "bt470bg":
|
35
|
+
if s.color_space == 5: # "bt470bg"
|
38
36
|
return "5-1-6 (Rec. 601 PAL)"
|
39
|
-
if s.
|
37
|
+
if s.color_space == 6: # "smpte170m"
|
38
|
+
return "6-1-6 (Rec. 601 NTSC)"
|
39
|
+
if s.color_primaries == 9: # "bt2020"
|
40
40
|
# See: https://video.stackexchange.com/questions/22059/how-to-identify-hdr-video
|
41
|
-
if s.color_transfer in ("arib-std-b67"
|
41
|
+
if s.color_transfer in (16, 18): # "smpte2084" "arib-std-b67"
|
42
42
|
return "9-18-9 (Rec. 2020 HLG)"
|
43
43
|
return "9-1-9 (Rec. 2020)"
|
44
44
|
|
@@ -79,7 +79,7 @@ def fcp11_write_xml(
|
|
79
79
|
ffmpeg.run(
|
80
80
|
["-i", f"{src.path.resolve()}", "-map", f"0:a:{i}", f"{newtrack}"]
|
81
81
|
)
|
82
|
-
all_srcs.append(initFileInfo(f"{newtrack}",
|
82
|
+
all_srcs.append(initFileInfo(f"{newtrack}", log))
|
83
83
|
all_refs.append(f"r{(i + 1) * 2}")
|
84
84
|
|
85
85
|
fcpxml = Element("fcpxml", version="1.10" if flavor == "resolve" else "1.11")
|
@@ -140,7 +140,7 @@ def fcp11_write_xml(
|
|
140
140
|
"ref": ref,
|
141
141
|
"offset": fraction(clip.start),
|
142
142
|
"duration": fraction(clip.dur),
|
143
|
-
"start": fraction(
|
143
|
+
"start": fraction(clip.offset),
|
144
144
|
"tcFormat": "NDF",
|
145
145
|
}
|
146
146
|
asset = SubElement(spine, "asset-clip", clip_properties)
|
@@ -281,7 +281,6 @@ def fcp7_read_xml(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
281
281
|
if "pathurl" in fileobj:
|
282
282
|
sources[file_id] = initFileInfo(
|
283
283
|
uri_to_path(fileobj["pathurl"]),
|
284
|
-
ffmpeg,
|
285
284
|
log,
|
286
285
|
)
|
287
286
|
else:
|
@@ -297,7 +296,7 @@ def fcp7_read_xml(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
297
296
|
|
298
297
|
start = clipitem["start"]
|
299
298
|
dur = clipitem["end"] - start
|
300
|
-
offset =
|
299
|
+
offset = clipitem["in"]
|
301
300
|
|
302
301
|
vobjs[t].append(
|
303
302
|
TlVideo(start, dur, sources[file_id], offset, speed, stream=0)
|
@@ -315,7 +314,7 @@ def fcp7_read_xml(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
315
314
|
if file_id not in sources:
|
316
315
|
fileobj = valid.parse(clipitem["file"], {"pathurl": str})
|
317
316
|
sources[file_id] = initFileInfo(
|
318
|
-
uri_to_path(fileobj["pathurl"]),
|
317
|
+
uri_to_path(fileobj["pathurl"]), log
|
319
318
|
)
|
320
319
|
|
321
320
|
if "filter" in clipitem:
|
@@ -325,7 +324,7 @@ def fcp7_read_xml(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
325
324
|
|
326
325
|
start = clipitem["start"]
|
327
326
|
dur = clipitem["end"] - start
|
328
|
-
offset =
|
327
|
+
offset = clipitem["in"]
|
329
328
|
|
330
329
|
aobjs[t].append(
|
331
330
|
TlAudio(
|
@@ -415,8 +414,8 @@ def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
|
|
415
414
|
|
416
415
|
_start = f"{clip.start}"
|
417
416
|
_end = f"{clip.start + clip.dur}"
|
418
|
-
_in = f"{
|
419
|
-
_out = f"{
|
417
|
+
_in = f"{clip.offset}"
|
418
|
+
_out = f"{clip.offset + clip.dur}"
|
420
419
|
|
421
420
|
clipitem = ET.SubElement(track, "clipitem", id=f"clipitem-{j+1}")
|
422
421
|
ET.SubElement(clipitem, "name").text = src.path.stem
|
@@ -474,8 +473,8 @@ def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
|
|
474
473
|
|
475
474
|
_start = f"{aclip.start}"
|
476
475
|
_end = f"{aclip.start + aclip.dur}"
|
477
|
-
_in = f"{
|
478
|
-
_out = f"{
|
476
|
+
_in = f"{aclip.offset}"
|
477
|
+
_out = f"{aclip.offset + aclip.dur}"
|
479
478
|
|
480
479
|
if not src.videos:
|
481
480
|
clip_item_num = j + 1
|
@@ -6,7 +6,7 @@ from difflib import get_close_matches
|
|
6
6
|
from fractions import Fraction
|
7
7
|
from typing import Any
|
8
8
|
|
9
|
-
from auto_editor.ffwrapper import
|
9
|
+
from auto_editor.ffwrapper import FileInfo, initFileInfo
|
10
10
|
from auto_editor.lang.json import Lexer, Parser, dump
|
11
11
|
from auto_editor.lib.err import MyError
|
12
12
|
from auto_editor.timeline import (
|
@@ -42,7 +42,7 @@ def check_file(path: str, log: Log) -> None:
|
|
42
42
|
log.error(f"Could not locate media file: '{path}'")
|
43
43
|
|
44
44
|
|
45
|
-
def read_v3(tl: Any,
|
45
|
+
def read_v3(tl: Any, log: Log) -> v3:
|
46
46
|
check_attrs(
|
47
47
|
tl,
|
48
48
|
log,
|
@@ -59,7 +59,7 @@ def read_v3(tl: Any, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
59
59
|
def make_src(v: str) -> FileInfo:
|
60
60
|
if v in srcs:
|
61
61
|
return srcs[v]
|
62
|
-
temp = initFileInfo(v,
|
62
|
+
temp = initFileInfo(v, log)
|
63
63
|
srcs[v] = temp
|
64
64
|
return temp
|
65
65
|
|
@@ -158,7 +158,7 @@ def read_v3(tl: Any, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
158
158
|
return v3(src, tb, sr, res, bg, v, a, v1=None)
|
159
159
|
|
160
160
|
|
161
|
-
def read_v1(tl: Any,
|
161
|
+
def read_v1(tl: Any, log: Log) -> v3:
|
162
162
|
from auto_editor.make_layers import clipify
|
163
163
|
|
164
164
|
check_attrs(tl, log, "source", "chunks")
|
@@ -168,11 +168,32 @@ def read_v1(tl: Any, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
168
168
|
|
169
169
|
check_file(path, log)
|
170
170
|
|
171
|
-
src = initFileInfo(path,
|
171
|
+
src = initFileInfo(path, log)
|
172
172
|
|
173
173
|
vtl: VSpace = []
|
174
174
|
atl: ASpace = [[] for _ in range(len(src.audios))]
|
175
175
|
|
176
|
+
# Verify chunks
|
177
|
+
last_end: int | None = None
|
178
|
+
if type(chunks) is not list:
|
179
|
+
log.error("chunks key must be an array")
|
180
|
+
|
181
|
+
for i, chunk in enumerate(chunks):
|
182
|
+
if type(chunk) is not list or len(chunk) != 3:
|
183
|
+
log.error(f"Invalid chunk at chunk {i}")
|
184
|
+
if type(chunk[0]) is not int or chunk[0] < 0:
|
185
|
+
log.error(f"Invalid start at chunk {i}")
|
186
|
+
if type(chunk[1]) is not int or chunk[1] <= chunk[0]:
|
187
|
+
log.error(f"Invalid end at chunk {i}")
|
188
|
+
if type(chunk[2]) is not float or chunk[2] < 0.0 or chunk[2] > 99999.0:
|
189
|
+
log.error(f"Invalid speed at chunk {i}")
|
190
|
+
|
191
|
+
if i == 0 and chunk[0] != 0:
|
192
|
+
log.error("First chunk must start with 0")
|
193
|
+
if i != 0 and chunk[0] != last_end:
|
194
|
+
log.error(f"Invalid start at chunk {i}")
|
195
|
+
last_end = chunk[1]
|
196
|
+
|
176
197
|
for c in clipify(chunks, src):
|
177
198
|
if src.videos:
|
178
199
|
if len(vtl) == 0:
|
@@ -194,7 +215,7 @@ def read_v1(tl: Any, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
194
215
|
)
|
195
216
|
|
196
217
|
|
197
|
-
def read_json(path: str,
|
218
|
+
def read_json(path: str, log: Log) -> v3:
|
198
219
|
with open(path, encoding="utf-8", errors="ignore") as f:
|
199
220
|
try:
|
200
221
|
tl = Parser(Lexer(path, f)).expr()
|
@@ -206,9 +227,9 @@ def read_json(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
|
|
206
227
|
ver = tl["version"]
|
207
228
|
|
208
229
|
if ver == "3":
|
209
|
-
return read_v3(tl,
|
230
|
+
return read_v3(tl, log)
|
210
231
|
if ver == "1":
|
211
|
-
return read_v1(tl,
|
232
|
+
return read_v1(tl, log)
|
212
233
|
if type(ver) is not str:
|
213
234
|
log.error("version needs to be a string")
|
214
235
|
log.error(f"Importing version {ver} timelines is not supported.")
|
@@ -92,7 +92,7 @@ def shotcut_write_mlt(output: str, tl: v3) -> None:
|
|
92
92
|
|
93
93
|
for clip in clips:
|
94
94
|
src = clip.src
|
95
|
-
length = to_timecode((clip.offset
|
95
|
+
length = to_timecode((clip.offset + clip.dur) / tb, "standard")
|
96
96
|
|
97
97
|
if clip.speed == 1:
|
98
98
|
resource = f"{src.path}"
|
@@ -127,8 +127,8 @@ def shotcut_write_mlt(output: str, tl: v3) -> None:
|
|
127
127
|
|
128
128
|
producers = 0
|
129
129
|
for i, clip in enumerate(clips):
|
130
|
-
_in = to_timecode(clip.offset /
|
131
|
-
_out = to_timecode((clip.offset
|
130
|
+
_in = to_timecode(clip.offset / tb, "standard")
|
131
|
+
_out = to_timecode((clip.offset + clip.dur) / tb, "standard")
|
132
132
|
|
133
133
|
tag_name = f"chain{i}"
|
134
134
|
if clip.speed != 1:
|
@@ -42,7 +42,7 @@ def clipify(chunks: Chunks, src: FileInfo, start: int = 0) -> list[Clip]:
|
|
42
42
|
if dur == 0:
|
43
43
|
continue
|
44
44
|
|
45
|
-
offset = chunk[0]
|
45
|
+
offset = int(chunk[0] / chunk[2])
|
46
46
|
|
47
47
|
if not (clips and clips[-1].start == round(start)):
|
48
48
|
clips.append(Clip(start, dur, offset, chunk[2], src))
|
@@ -125,7 +125,14 @@ def make_timeline(
|
|
125
125
|
if inp is None:
|
126
126
|
tb, res = Fraction(30), (1920, 1080)
|
127
127
|
else:
|
128
|
-
tb = inp.get_fps() if args.frame_rate is None else args.frame_rate
|
128
|
+
tb = round(inp.get_fps() if args.frame_rate is None else args.frame_rate, 2)
|
129
|
+
ntsc = Fraction(30_000, 1001)
|
130
|
+
film_ntsc = Fraction(24_000, 1001)
|
131
|
+
if tb == round(ntsc, 2):
|
132
|
+
tb = ntsc
|
133
|
+
elif tb == round(film_ntsc, 2):
|
134
|
+
tb = film_ntsc
|
135
|
+
|
129
136
|
res = inp.get_res() if args.resolution is None else args.resolution
|
130
137
|
|
131
138
|
try:
|
@@ -228,7 +235,7 @@ def make_timeline(
|
|
228
235
|
if dur == 0:
|
229
236
|
continue
|
230
237
|
|
231
|
-
offset = chunk[1]
|
238
|
+
offset = int(chunk[1] / chunk[3])
|
232
239
|
|
233
240
|
if not (clips and clips[-1].start == round(start)):
|
234
241
|
clips.append(Clip(start, dur, offset, chunk[3], chunk[0]))
|
@@ -182,12 +182,19 @@ def mux_quality_media(
|
|
182
182
|
cmd += _ffset("-c:a", args.audio_codec) + _ffset("-b:a", args.audio_bitrate)
|
183
183
|
|
184
184
|
if same_container and v_tracks > 0:
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
185
|
+
color_range = src.videos[0].color_range
|
186
|
+
colorspace = src.videos[0].color_space
|
187
|
+
color_prim = src.videos[0].color_primaries
|
188
|
+
color_trc = src.videos[0].color_transfer
|
189
|
+
|
190
|
+
if color_range == 1 or color_range == 2:
|
191
|
+
cmd.extend(["-color_range", f"{color_range}"])
|
192
|
+
if colorspace in (0, 1) or (colorspace >= 3 and colorspace < 16):
|
193
|
+
cmd.extend(["-colorspace", f"{colorspace}"])
|
194
|
+
if color_prim in (0, 1) or (color_prim >= 4 and color_prim < 17):
|
195
|
+
cmd.extend(["-color_primaries", f"{color_prim}"])
|
196
|
+
if color_trc == 1 or (color_trc >= 4 and color_trc < 22):
|
197
|
+
cmd.extend(["-color_trc", f"{color_trc}"])
|
191
198
|
|
192
199
|
if args.extras is not None:
|
193
200
|
cmd.extend(args.extras.split(" "))
|
@@ -32,7 +32,8 @@ def all_cuts(tl: v3, in_len: int) -> list[int]:
|
|
32
32
|
oe: list[tuple[int, int]] = []
|
33
33
|
|
34
34
|
for clip in tl.a[0]:
|
35
|
-
|
35
|
+
old_offset = clip.offset * clip.speed
|
36
|
+
oe.append((round(old_offset * clip.speed), round(old_offset + clip.dur)))
|
36
37
|
|
37
38
|
cut_lens = []
|
38
39
|
i = 0
|
@@ -208,8 +208,8 @@ def make_new_audio(
|
|
208
208
|
del leng
|
209
209
|
|
210
210
|
samp_list = samples[(clip.src, clip.stream)]
|
211
|
-
samp_start = clip.offset * sr
|
212
|
-
samp_end = round((clip.offset + clip.dur * clip.speed
|
211
|
+
samp_start = round(clip.offset * clip.speed * sr / tb)
|
212
|
+
samp_end = round((clip.offset + clip.dur) * clip.speed * sr / tb)
|
213
213
|
if samp_end > len(samp_list):
|
214
214
|
samp_end = len(samp_list)
|
215
215
|
|
@@ -97,7 +97,6 @@ def make_image_cache(tl: v3) -> dict[tuple[FileInfo, int], np.ndarray]:
|
|
97
97
|
for obj in clip:
|
98
98
|
if isinstance(obj, TlImage) and obj.src not in img_cache:
|
99
99
|
with av.open(obj.src.path) as cn:
|
100
|
-
assert isinstance(cn, av.InputContainer)
|
101
100
|
my_stream = cn.streams.video[0]
|
102
101
|
for frame in cn.decode(my_stream):
|
103
102
|
if obj.width != 0:
|
@@ -245,7 +244,7 @@ def render_av(
|
|
245
244
|
for lobj in layer:
|
246
245
|
if isinstance(lobj, TlVideo):
|
247
246
|
if index >= lobj.start and index < (lobj.start + lobj.dur):
|
248
|
-
_i = lobj.offset +
|
247
|
+
_i = round((lobj.offset + index - lobj.start) * lobj.speed)
|
249
248
|
obj_list.append(VideoFrame(_i, lobj.src))
|
250
249
|
elif index >= lobj.start and index < lobj.start + lobj.dur:
|
251
250
|
obj_list.append(lobj)
|
@@ -3,28 +3,26 @@ from __future__ import annotations
|
|
3
3
|
import sys
|
4
4
|
from dataclasses import dataclass, field
|
5
5
|
|
6
|
-
from auto_editor.ffwrapper import
|
6
|
+
from auto_editor.ffwrapper import initFileInfo
|
7
7
|
from auto_editor.utils.log import Log
|
8
8
|
from auto_editor.vanparse import ArgumentParser
|
9
9
|
|
10
10
|
|
11
11
|
@dataclass(slots=True)
|
12
12
|
class DescArgs:
|
13
|
-
ffmpeg_location: str | None = None
|
14
13
|
help: bool = False
|
15
14
|
input: list[str] = field(default_factory=list)
|
16
15
|
|
17
16
|
|
18
17
|
def desc_options(parser: ArgumentParser) -> ArgumentParser:
|
19
18
|
parser.add_required("input", nargs="*")
|
20
|
-
parser.add_argument("--ffmpeg-location", help="Point to your custom ffmpeg file")
|
21
19
|
return parser
|
22
20
|
|
23
21
|
|
24
22
|
def main(sys_args: list[str] = sys.argv[1:]) -> None:
|
25
23
|
args = desc_options(ArgumentParser("desc")).parse_args(DescArgs, sys_args)
|
26
24
|
for path in args.input:
|
27
|
-
src = initFileInfo(path,
|
25
|
+
src = initFileInfo(path, Log())
|
28
26
|
if src.description is not None:
|
29
27
|
sys.stdout.write(f"\n{src.description}\n\n")
|
30
28
|
else:
|