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.
Files changed (65) hide show
  1. {auto-editor-24.9.1/auto_editor.egg-info → auto_editor-24.19.1}/PKG-INFO +12 -37
  2. {auto-editor-24.9.1 → auto_editor-24.19.1}/README.md +9 -34
  3. {auto-editor-24.9.1 → auto_editor-24.19.1}/ae-ffmpeg/ae_ffmpeg/__init__.py +1 -1
  4. auto_editor-24.19.1/auto_editor/__init__.py +2 -0
  5. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/edit.py +2 -2
  6. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/ffwrapper.py +23 -50
  7. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/fcp11.py +7 -7
  8. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/fcp7.py +7 -8
  9. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/json.py +29 -8
  10. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/shotcut.py +3 -3
  11. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/make_layers.py +10 -3
  12. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/output.py +13 -6
  13. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/preview.py +2 -1
  14. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/audio.py +2 -2
  15. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/video.py +1 -2
  16. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/desc.py +2 -4
  17. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/info.py +24 -19
  18. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/levels.py +1 -1
  19. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/repl.py +1 -1
  20. auto_editor-24.19.1/auto_editor/subcommands/subdump.py +23 -0
  21. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/test.py +7 -6
  22. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/container.py +7 -7
  23. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/wavfile.py +3 -3
  24. {auto-editor-24.9.1 → auto_editor-24.19.1/auto_editor.egg-info}/PKG-INFO +12 -37
  25. auto_editor-24.19.1/auto_editor.egg-info/requires.txt +3 -0
  26. {auto-editor-24.9.1 → auto_editor-24.19.1}/pyproject.toml +2 -2
  27. auto-editor-24.9.1/auto_editor/__init__.py +0 -2
  28. auto-editor-24.9.1/auto_editor/subcommands/subdump.py +0 -58
  29. auto-editor-24.9.1/auto_editor.egg-info/requires.txt +0 -3
  30. {auto-editor-24.9.1 → auto_editor-24.19.1}/LICENSE +0 -0
  31. {auto-editor-24.9.1 → auto_editor-24.19.1}/ae-ffmpeg/ae_ffmpeg/py.typed +0 -0
  32. {auto-editor-24.9.1 → auto_editor-24.19.1}/ae-ffmpeg/setup.py +0 -0
  33. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/__main__.py +0 -0
  34. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/analyze.py +0 -0
  35. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/__init__.py +0 -0
  36. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/formats/utils.py +0 -0
  37. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/help.py +0 -0
  38. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/__init__.py +0 -0
  39. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/json.py +0 -0
  40. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/libmath.py +0 -0
  41. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lang/palet.py +0 -0
  42. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/__init__.py +0 -0
  43. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/contracts.py +0 -0
  44. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/data_structs.py +0 -0
  45. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/lib/err.py +0 -0
  46. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/__init__.py +0 -0
  47. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/render/subtitle.py +0 -0
  48. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/__init__.py +0 -0
  49. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/subcommands/palet.py +0 -0
  50. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/timeline.py +0 -0
  51. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/__init__.py +0 -0
  52. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/bar.py +0 -0
  53. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/chunks.py +0 -0
  54. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/cmdkw.py +0 -0
  55. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/encoder.py +0 -0
  56. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/func.py +0 -0
  57. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/log.py +0 -0
  58. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/utils/types.py +0 -0
  59. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/validate_input.py +0 -0
  60. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor/vanparse.py +0 -0
  61. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/SOURCES.txt +0 -0
  62. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/dependency_links.txt +0 -0
  63. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/entry_points.txt +0 -0
  64. {auto-editor-24.9.1 → auto_editor-24.19.1}/auto_editor.egg-info/top_level.txt +0 -0
  65. {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.9.1
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.2
16
- Requires-Dist: ae-ffmpeg==1.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 percentage of motion is less than 2.
64
- auto-editor example.mp4 --edit motion:threshold=2%
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
- # --edit is set to "audio:threshold=4%" by default.
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:3% motion:6%)"
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:threshold=-19dB
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 for more information:
169
+ Use `--help` with a specific option to learn more about it:
192
170
 
193
171
  ```
194
- auto-editor --scale --help
195
- --scale NUM
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 percentage of motion is less than 2.
47
- auto-editor example.mp4 --edit motion:threshold=2%
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
- # --edit is set to "audio:threshold=4%" by default.
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:3% motion:6%)"
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:threshold=-19dB
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 for more information:
152
+ Use `--help` with a specific option to learn more about it:
175
153
 
176
154
  ```
177
- auto-editor --scale --help
178
- --scale NUM
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>
@@ -1,4 +1,4 @@
1
- __version__ = "1.1.2"
1
+ __version__ = "1.2.0"
2
2
 
3
3
  import os.path
4
4
  from platform import machine, system
@@ -0,0 +1,2 @@
1
+ __version__ = "24.19.1"
2
+ version = "24w19a"
@@ -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], ffmpeg, log)
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, ffmpeg, log) for path in paths]
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: str | None
139
- color_space: str | None
140
- color_primaries: str | None
141
- color_transfer: str | None
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, ffmpeg: FFmpeg, log: Log) -> FileInfo:
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
- _dir = os.path.dirname(ffmpeg.path)
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
- _sar = c_range = c_space = c_primary = c_transfer = None
222
- try:
223
- _raw = get_stdout(
224
- [
225
- ffprobe,
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
- v.codec_context.pix_fmt,
262
- c_range,
263
- c_space,
264
- c_primary,
265
- c_transfer,
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
- a.codec_context.name,
279
- 0 if a.sample_rate is None else a.sample_rate,
280
- a.channels,
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 a.bit_rate is None else a.bit_rate,
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 == "smpte170m":
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.color_primaries == "bt2020":
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", "smpte2084"):
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}", ffmpeg, log))
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(int(clip.offset // clip.speed)),
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 = int(clipitem["in"] * speed)
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"]), ffmpeg, log
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 = int(clipitem["in"] * speed)
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"{int(clip.offset / clip.speed)}"
419
- _out = f"{int(clip.offset / clip.speed) + clip.dur}"
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"{int(aclip.offset / aclip.speed)}"
478
- _out = f"{int(aclip.offset / aclip.speed) + aclip.dur}"
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 FFmpeg, FileInfo, initFileInfo
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, ffmpeg: FFmpeg, log: Log) -> v3:
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, ffmpeg, log)
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, ffmpeg: FFmpeg, log: Log) -> v3:
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, ffmpeg, log)
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, ffmpeg: FFmpeg, log: Log) -> v3:
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, ffmpeg, log)
230
+ return read_v3(tl, log)
210
231
  if ver == "1":
211
- return read_v1(tl, ffmpeg, log)
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 / clip.speed + clip.dur) / tb, "standard")
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 / clip.speed / tb, "standard")
131
- _out = to_timecode((clip.offset / clip.speed + clip.dur) / tb, "standard")
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
- cmd += (
186
- _ffset("-color_range", src.videos[0].color_range)
187
- + _ffset("-colorspace", src.videos[0].color_space)
188
- + _ffset("-color_primaries", src.videos[0].color_primaries)
189
- + _ffset("-color_trc", src.videos[0].color_transfer)
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
- oe.append((clip.offset, clip.offset + clip.dur))
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 // tb
212
- samp_end = round((clip.offset + clip.dur * clip.speed) * sr / tb)
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 + round((index - lobj.start) * lobj.speed)
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 FFmpeg, initFileInfo
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, FFmpeg(args.ffmpeg_location), Log())
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: