auto-editor 24.13.1__py3-none-any.whl → 24.24.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
auto_editor/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "24.13.1"
2
- version = "24w13a"
1
+ __version__ = "24.24.1"
2
+ version = "24w24a"
auto_editor/__main__.py CHANGED
@@ -4,7 +4,7 @@ import sys
4
4
 
5
5
  import auto_editor
6
6
  from auto_editor.utils.func import setup_tempdir
7
- from auto_editor.utils.log import Log
7
+ from auto_editor.utils.log import Log, Timer
8
8
  from auto_editor.utils.types import (
9
9
  Args,
10
10
  bitrate,
@@ -312,8 +312,7 @@ def main() -> None:
312
312
  if args.debug and args.input == []:
313
313
  import platform as plat
314
314
 
315
- is64bit = "64-bit" if sys.maxsize > 2**32 else "32-bit"
316
- print(f"Python Version: {plat.python_version()} {is64bit}")
315
+ print(f"Python Version: {plat.python_version()}")
317
316
  print(f"Platform: {plat.system()} {plat.release()} {plat.machine().lower()}")
318
317
  print(f"FFmpeg Version: {ffmpeg.version}\nFFmpeg Path: {ffmpeg.path}")
319
318
  print(f"Auto-Editor Version: {auto_editor.version}")
@@ -323,13 +322,15 @@ def main() -> None:
323
322
  log.error("You need to give auto-editor an input file.")
324
323
 
325
324
  temp = setup_tempdir(args.temp_dir, Log())
326
- log = Log(args.debug, args.quiet, temp=temp)
325
+ log = Log(args.debug, args.quiet, temp)
326
+ log.machine = args.progress == "machine"
327
327
  log.debug(f"Temp Directory: {temp}")
328
328
 
329
329
  paths = valid_input(args.input, ffmpeg, args, log)
330
+ timer = Timer(args.quiet or log.machine)
330
331
 
331
332
  try:
332
- edit_media(paths, ffmpeg, args, temp, log)
333
+ edit_media(paths, ffmpeg, args, temp, timer, log)
333
334
  except KeyboardInterrupt:
334
335
  log.error("Keyboard Interrupt")
335
336
  log.cleanup()
auto_editor/analyze.py CHANGED
@@ -170,8 +170,6 @@ class Levels:
170
170
  # If there's no audio, get length in video metadata.
171
171
  import av
172
172
 
173
- av.logging.set_level(av.logging.PANIC)
174
-
175
173
  with av.open(f"{self.src.path}") as cn:
176
174
  if len(cn.streams.video) < 1:
177
175
  self.log.error("Could not get media duration")
@@ -227,11 +225,7 @@ class Levels:
227
225
  except Exception:
228
226
  json_object = {}
229
227
 
230
- entry = {
231
- "type": str(arr.dtype),
232
- "arr": arr.tolist(),
233
- }
234
-
228
+ entry = {"type": str(arr.dtype), "arr": arr.tolist()}
235
229
  src_key = f"{self.src.path}"
236
230
 
237
231
  if src_key in json_object:
@@ -344,13 +338,10 @@ class Levels:
344
338
  def motion(self, s: int, blur: int, width: int) -> NDArray[np.float64]:
345
339
  import av
346
340
 
347
- av.logging.set_level(av.logging.PANIC)
348
-
349
- mobj = {"stream": s, "width": width, "blur": blur}
350
-
351
341
  if s >= len(self.src.videos):
352
342
  raise LevelError(f"motion: video stream '{s}' does not exist.")
353
343
 
344
+ mobj = {"stream": s, "width": width, "blur": blur}
354
345
  if (arr := self.read_cache("motion", mobj)) is not None:
355
346
  return arr
356
347
 
auto_editor/edit.py CHANGED
@@ -150,9 +150,8 @@ def parse_export(export: str, log: Log) -> dict[str, Any]:
150
150
 
151
151
 
152
152
  def edit_media(
153
- paths: list[str], ffmpeg: FFmpeg, args: Args, temp: str, log: Log
153
+ paths: list[str], ffmpeg: FFmpeg, args: Args, temp: str, timer: Timer, log: Log
154
154
  ) -> None:
155
- timer = Timer(args.quiet)
156
155
  bar = Bar(args.progress)
157
156
  tl = None
158
157
 
@@ -223,7 +222,7 @@ def edit_media(
223
222
  cmd.extend([os.path.join(temp, f"{s}s.{sub.ext}")])
224
223
  ffmpeg.run(cmd)
225
224
 
226
- tl = make_timeline(sources, ffmpeg, ensure, args, samplerate, bar, temp, log)
225
+ tl = make_timeline(sources, ensure, args, samplerate, bar, temp, log)
227
226
 
228
227
  if export["export"] == "timeline":
229
228
  from auto_editor.formats.json import make_json_timeline
auto_editor/ffwrapper.py CHANGED
@@ -192,8 +192,6 @@ class FileInfo:
192
192
  def initFileInfo(path: str, log: Log) -> FileInfo:
193
193
  import av
194
194
 
195
- av.logging.set_level(av.logging.PANIC)
196
-
197
195
  try:
198
196
  cont = av.open(path, "r")
199
197
  except av.error.InvalidDataError:
@@ -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)
@@ -296,7 +296,7 @@ def fcp7_read_xml(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
296
296
 
297
297
  start = clipitem["start"]
298
298
  dur = clipitem["end"] - start
299
- offset = int(clipitem["in"] * speed)
299
+ offset = clipitem["in"]
300
300
 
301
301
  vobjs[t].append(
302
302
  TlVideo(start, dur, sources[file_id], offset, speed, stream=0)
@@ -324,7 +324,7 @@ def fcp7_read_xml(path: str, ffmpeg: FFmpeg, log: Log) -> v3:
324
324
 
325
325
  start = clipitem["start"]
326
326
  dur = clipitem["end"] - start
327
- offset = int(clipitem["in"] * speed)
327
+ offset = clipitem["in"]
328
328
 
329
329
  aobjs[t].append(
330
330
  TlAudio(
@@ -414,8 +414,8 @@ def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
414
414
 
415
415
  _start = f"{clip.start}"
416
416
  _end = f"{clip.start + clip.dur}"
417
- _in = f"{int(clip.offset / clip.speed)}"
418
- _out = f"{int(clip.offset / clip.speed) + clip.dur}"
417
+ _in = f"{clip.offset}"
418
+ _out = f"{clip.offset + clip.dur}"
419
419
 
420
420
  clipitem = ET.SubElement(track, "clipitem", id=f"clipitem-{j+1}")
421
421
  ET.SubElement(clipitem, "name").text = src.path.stem
@@ -473,8 +473,8 @@ def fcp7_write_xml(name: str, output: str, tl: v3, log: Log) -> None:
473
473
 
474
474
  _start = f"{aclip.start}"
475
475
  _end = f"{aclip.start + aclip.dur}"
476
- _in = f"{int(aclip.offset / aclip.speed)}"
477
- _out = f"{int(aclip.offset / aclip.speed) + aclip.dur}"
476
+ _in = f"{aclip.offset}"
477
+ _out = f"{aclip.offset + aclip.dur}"
478
478
 
479
479
  if not src.videos:
480
480
  clip_item_num = j + 1
@@ -173,6 +173,30 @@ def read_v1(tl: Any, log: Log) -> v3:
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]) not in (int, float) or chunk[0] < 0:
185
+ log.error(f"Invalid start at chunk {i}")
186
+ if type(chunk[1]) not in (int, float) or chunk[1] <= chunk[0]:
187
+ log.error(f"Invalid end at chunk {i}")
188
+ if type(chunk[2]) not in (int, 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
+
197
+ if type(chunk[0]) is float or type(chunk[1]) is float or type(chunk[2]) is int:
198
+ chunks[i] = (int(chunk[0]), int(chunk[1]), float(chunk[2]))
199
+
176
200
  for c in clipify(chunks, src):
177
201
  if src.videos:
178
202
  if len(vtl) == 0:
@@ -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:
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, NamedTuple
6
6
  import numpy as np
7
7
 
8
8
  from auto_editor.analyze import FileSetup, Levels
9
- from auto_editor.ffwrapper import FFmpeg, FileInfo
9
+ from auto_editor.ffwrapper import FileInfo
10
10
  from auto_editor.lang.palet import Lexer, Parser, env, interpret, is_boolarr
11
11
  from auto_editor.lib.data_structs import print_str
12
12
  from auto_editor.lib.err import MyError
@@ -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))
@@ -110,9 +110,29 @@ def run_interpreter_for_edit_option(
110
110
  return result
111
111
 
112
112
 
113
+ def make_sane_timebase(fps: Fraction) -> Fraction:
114
+ tb = round(fps, 2)
115
+ ntsc = Fraction(30_000, 1001)
116
+ film_ntsc = Fraction(24_000, 1001)
117
+ if tb == round(ntsc, 2):
118
+ return ntsc
119
+ if tb == round(film_ntsc, 2):
120
+ return film_ntsc
121
+ return tb
122
+
123
+
124
+ def parse_time(val: str, arr: NDArray, tb: Fraction) -> int: # raises: `CoerceError`
125
+ if val == "start":
126
+ return 0
127
+ if val == "end":
128
+ return len(arr)
129
+
130
+ num = time(val, tb)
131
+ return num if num >= 0 else num + len(arr)
132
+
133
+
113
134
  def make_timeline(
114
135
  sources: list[FileInfo],
115
- ffmpeg: FFmpeg,
116
136
  ensure: Ensure,
117
137
  args: Args,
118
138
  sr: int,
@@ -125,7 +145,9 @@ def make_timeline(
125
145
  if inp is None:
126
146
  tb, res = Fraction(30), (1920, 1080)
127
147
  else:
128
- tb = inp.get_fps() if args.frame_rate is None else args.frame_rate
148
+ tb = make_sane_timebase(
149
+ inp.get_fps() if args.frame_rate is None else args.frame_rate
150
+ )
129
151
  res = inp.get_res() if args.resolution is None else args.resolution
130
152
 
131
153
  try:
@@ -164,36 +186,21 @@ def make_timeline(
164
186
  speed_hash[len(speed_map) - 1] = speed
165
187
  return len(speed_map) - 1
166
188
 
167
- def parse_time(val: str, arr: NDArray) -> int:
168
- if val == "start":
169
- return 0
170
- if val == "end":
171
- return len(arr)
172
- try:
173
- num = time(val, tb)
174
- return num if num >= 0 else num + len(arr)
175
- except CoerceError as e:
176
- log.error(e)
177
-
178
- def mut_set_range(arr: NDArray, _ranges: list[list[str]], index: float) -> None:
179
- for _range in _ranges:
180
- assert len(_range) == 2
181
- pair = [parse_time(val, arr) for val in _range]
182
- arr[pair[0] : pair[1]] = index
183
-
184
189
  try:
185
- if len(args.cut_out) > 0:
190
+ for _range in args.cut_out:
186
191
  # always cut out even if 'silent_speed' is not 99,999
187
- mut_set_range(speed_index, args.cut_out, get_speed_index(99_999))
192
+ pair = [parse_time(val, speed_index, tb) for val in _range]
193
+ speed_index[pair[0] : pair[1]] = get_speed_index(99_999)
188
194
 
189
- if len(args.add_in) > 0:
195
+ for _range in args.add_in:
190
196
  # set to 'video_speed' index
191
- mut_set_range(speed_index, args.add_in, 1.0)
197
+ pair = [parse_time(val, speed_index, tb) for val in _range]
198
+ speed_index[pair[0] : pair[1]] = 1
192
199
 
193
200
  for speed_range in args.set_speed_for_range:
194
- speed = speed_range[0]
195
- _range = list(speed_range[1:])
196
- mut_set_range(speed_index, [_range], get_speed_index(speed))
201
+ start_in = parse_time(speed_range[1], speed_index, tb)
202
+ end_in = parse_time(speed_range[2], speed_index, tb)
203
+ speed_index[start_in:end_in] = get_speed_index(speed_range[0])
197
204
  except CoerceError as e:
198
205
  log.error(e)
199
206
 
@@ -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]))
auto_editor/preview.py CHANGED
@@ -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
 
@@ -27,9 +27,6 @@ if TYPE_CHECKING:
27
27
  from auto_editor.utils.types import Args
28
28
 
29
29
 
30
- av.logging.set_level(av.logging.PANIC)
31
-
32
-
33
30
  @dataclass(slots=True)
34
31
  class VideoFrame:
35
32
  index: int
@@ -244,7 +241,7 @@ def render_av(
244
241
  for lobj in layer:
245
242
  if isinstance(lobj, TlVideo):
246
243
  if index >= lobj.start and index < (lobj.start + lobj.dur):
247
- _i = lobj.offset + round((index - lobj.start) * lobj.speed)
244
+ _i = round((lobj.offset + index - lobj.start) * lobj.speed)
248
245
  obj_list.append(VideoFrame(_i, lobj.src))
249
246
  elif index >= lobj.start and index < lobj.start + lobj.dur:
250
247
  obj_list.append(lobj)
@@ -7,6 +7,7 @@ from typing import Any, Literal, TypedDict
7
7
 
8
8
  from auto_editor.ffwrapper import initFileInfo
9
9
  from auto_editor.lang.json import dump
10
+ from auto_editor.make_layers import make_sane_timebase
10
11
  from auto_editor.timeline import v3
11
12
  from auto_editor.utils.func import aspect_ratio
12
13
  from auto_editor.utils.log import Log
@@ -68,6 +69,7 @@ class MediaJson(TypedDict, total=False):
68
69
  subtitle: list[SubtitleJson]
69
70
  container: ContainerJson
70
71
  type: Literal["media", "timeline", "unknown"]
72
+ recommendedTimebase: str
71
73
  version: Literal["v1", "v3"]
72
74
  clips: int
73
75
 
@@ -108,6 +110,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
108
110
 
109
111
  file_info[file] = {
110
112
  "type": "media",
113
+ "recommendedTimebase": "30/1",
111
114
  "video": [],
112
115
  "audio": [],
113
116
  "subtitle": [],
@@ -117,6 +120,12 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
117
120
  },
118
121
  }
119
122
 
123
+ if src.videos:
124
+ recTb = make_sane_timebase(src.videos[0].fps)
125
+ file_info[file]["recommendedTimebase"] = (
126
+ f"{recTb.numerator}/{recTb.denominator}"
127
+ )
128
+
120
129
  for track, v in enumerate(src.videos):
121
130
  w, h = v.width, v.height
122
131
 
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
 
3
3
  import av
4
- from av.subtitles.subtitle import SubtitleSet
4
+ from av.subtitles.subtitle import AssSubtitle, TextSubtitle
5
5
 
6
6
 
7
7
  def main(sys_args: list[str] = sys.argv[1:]) -> None:
@@ -10,12 +10,12 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
10
10
  for s in range(len(container.streams.subtitles)):
11
11
  print(f"file: {input_file} ({s}:{container.streams.subtitles[s].name})")
12
12
  for packet in container.demux(subtitles=s):
13
- for item in packet.decode():
14
- if type(item) is SubtitleSet and item:
15
- if item[0].type == b"ass":
16
- print(item[0].ass.decode("utf-8"))
17
- elif item[0].type == b"text":
18
- print(item[0].text)
13
+ for subset in packet.decode():
14
+ for sub in subset.rects:
15
+ if isinstance(sub, AssSubtitle):
16
+ print(sub.ass.decode("utf-8", errors="ignore"))
17
+ elif isinstance(sub, TextSubtitle):
18
+ print(sub.text.decode("utf-8", errors="ignore"))
19
19
  print("------")
20
20
 
21
21
 
auto_editor/utils/bar.py CHANGED
@@ -67,9 +67,9 @@ class Bar:
67
67
 
68
68
  if self.machine:
69
69
  index = min(index, self.total)
70
- raw = int(self.begin_time + rate)
70
+ secs_til_eta = round(self.begin_time + rate - time(), 2)
71
71
  print(
72
- f"{self.title}~{index}~{self.total}~{self.begin_time}~{raw}",
72
+ f"{self.title}~{index}~{self.total}~{secs_til_eta}",
73
73
  end="\r",
74
74
  flush=True,
75
75
  )
@@ -53,7 +53,7 @@ hevc_en = ["hevc", "libx265", "hevc_videotoolbox", "hevc_amf", "hevc_nvenc", "he
53
53
  av1_en = ["av1", "libaom-av1", "av1_nvenc", "av1_amf"]
54
54
  prores_en = ["prores", "prores_videotoolbox", "prores_aw", "prores_ks"]
55
55
  aac_en = ["aac", "aac_at", "libfdk_aac"]
56
-
56
+ opus_en = ["opus", "libopus"]
57
57
 
58
58
  h265: DictContainer = {
59
59
  "allow_video": True,
@@ -80,7 +80,7 @@ mp4: DictContainer = {
80
80
  "allow_subtitle": True,
81
81
  "allow_image": True,
82
82
  "vcodecs": h264_en + hevc_en + av1_en + ["vp9", "mpeg4", "mpeg2video", "mjpeg"],
83
- "acodecs": aac_en + ["mp3", "opus", "flac", "vorbis", "libvorbis", "ac3", "mp2"],
83
+ "acodecs": aac_en + opus_en + ["mp3", "flac", "vorbis", "libvorbis", "ac3", "mp2"],
84
84
  "vstrict": True,
85
85
  }
86
86
  ogg: DictContainer = {
@@ -88,16 +88,16 @@ ogg: DictContainer = {
88
88
  "allow_audio": True,
89
89
  "allow_subtitle": True,
90
90
  "vcodecs": ["libtheora", "theora"],
91
- "acodecs": ["libvorbis", "vorbis", "flac", "opus", "speex"],
91
+ "acodecs": opus_en + ["libvorbis", "vorbis", "flac", "speex"],
92
92
  "vstrict": True,
93
93
  }
94
94
 
95
95
  mka_audio = (
96
96
  ["libvorbis", "vorbis"]
97
97
  + aac_en
98
+ + opus_en
98
99
  + [
99
100
  "mp3",
100
- "opus",
101
101
  "flac",
102
102
  "ac3",
103
103
  "mp2",
@@ -172,11 +172,11 @@ containers: dict[str, DictContainer] = {
172
172
  },
173
173
  "opus": {
174
174
  "allow_audio": True,
175
- "acodecs": ["opus", "flac", "libvorbis", "vorbis", "speex"],
175
+ "acodecs": opus_en + ["flac", "libvorbis", "vorbis", "speex"],
176
176
  },
177
177
  "oga": {
178
178
  "allow_audio": True,
179
- "acodecs": ["flac", "libvorbis", "vorbis", "opus", "speex"],
179
+ "acodecs": opus_en + ["flac", "libvorbis", "vorbis", "speex"],
180
180
  },
181
181
  "flac": {
182
182
  "allow_audio": True,
@@ -188,7 +188,7 @@ containers: dict[str, DictContainer] = {
188
188
  "allow_audio": True,
189
189
  "allow_subtitle": True,
190
190
  "vcodecs": ["vp9", "vp8"] + av1_en,
191
- "acodecs": ["opus", "vorbis", "libvorbis"],
191
+ "acodecs": opus_en + ["vorbis", "libvorbis"],
192
192
  "scodecs": ["webvtt"],
193
193
  "vstrict": True,
194
194
  "sstrict": True,
auto_editor/utils/log.py CHANGED
@@ -24,7 +24,7 @@ class Timer:
24
24
 
25
25
 
26
26
  class Log:
27
- __slots__ = ("is_debug", "quiet", "temp")
27
+ __slots__ = ("is_debug", "quiet", "temp", "machine")
28
28
 
29
29
  def __init__(
30
30
  self, show_debug: bool = False, quiet: bool = False, temp: str | None = None
@@ -32,6 +32,7 @@ class Log:
32
32
  self.is_debug = show_debug
33
33
  self.quiet = quiet
34
34
  self.temp = temp
35
+ self.machine = False
35
36
 
36
37
  def debug(self, message: object) -> None:
37
38
  if self.is_debug:
@@ -55,7 +56,9 @@ class Log:
55
56
  self.debug(f"Failed to delete temp dir:\n{e}")
56
57
 
57
58
  def conwrite(self, message: str) -> None:
58
- if not self.quiet:
59
+ if self.machine:
60
+ print(message, flush=True)
61
+ elif not self.quiet:
59
62
  buffer = " " * (get_terminal_size().columns - len(message) - 3)
60
63
  sys.stdout.write(f" {message}{buffer}\r")
61
64
 
@@ -164,8 +164,9 @@ def margin(val: str) -> tuple[str, str]:
164
164
  return vals[0], vals[1]
165
165
 
166
166
 
167
- def time_range(val: str) -> list[str]:
168
- return _comma_coerce("time_range", val, 2)
167
+ def time_range(val: str) -> tuple[str, str]:
168
+ a = _comma_coerce("time_range", val, 2)
169
+ return a[0], a[1]
169
170
 
170
171
 
171
172
  def speed_range(val: str) -> tuple[float, str, str]:
@@ -231,8 +232,8 @@ class Args:
231
232
  extras: str | None = None
232
233
  sn: bool = False
233
234
  no_seek: bool = False
234
- cut_out: list[list[str]] = field(default_factory=list)
235
- add_in: list[list[str]] = field(default_factory=list)
235
+ cut_out: list[tuple[str, str]] = field(default_factory=list)
236
+ add_in: list[tuple[str, str]] = field(default_factory=list)
236
237
  set_speed_for_range: list[tuple[float, str, str]] = field(default_factory=list)
237
238
  frame_rate: Fraction | None = None
238
239
  sample_rate: int | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-editor
3
- Version: 24.13.1
3
+ Version: 24.24.1
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
6
  License: Unlicense
@@ -12,7 +12,7 @@ Requires-Python: >=3.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.5
15
+ Requires-Dist: pyav ==12.1.0
16
16
  Requires-Dist: ae-ffmpeg ==1.2.*
17
17
 
18
18
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
@@ -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>
@@ -1,24 +1,24 @@
1
1
  ae-ffmpeg/setup.py,sha256=HeORyrs8OyJ32lSnMaIhI2B7U1lkk3QP6wOjxpoiF3Y,1891
2
2
  ae-ffmpeg/ae_ffmpeg/__init__.py,sha256=Fd2YsCINa0dB3tf9VVKDTPT9P6MDH-ve3RT2pqArImI,453
3
3
  ae-ffmpeg/ae_ffmpeg/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- auto_editor/__init__.py,sha256=oSC_QYd59SbGta8dUWWTRxcFAzDKetEOu5kGPq6yXFE,43
5
- auto_editor/__main__.py,sha256=Lb_0h7Zop0SHK-nLWgwp7MWFrznuir8Ilo17Vx_0aKs,9827
6
- auto_editor/analyze.py,sha256=zvN4hXyEGXdUUVkfnYlyrCXPgBRl3DoQtBwIfHo7q68,11938
7
- auto_editor/edit.py,sha256=ZH0AgGBCTv_0KTqKbybZWbMCDx_OV_i15PklTGpEBC4,11997
8
- auto_editor/ffwrapper.py,sha256=af6j5257npuwJAyHMVKM7BbHGpjZ0bYkN1SGvIGLLu4,7662
4
+ auto_editor/__init__.py,sha256=2fihE0IJI1TOaKjbK47zmlNVGPFXtfaph4E4BM9SOh0,43
5
+ auto_editor/__main__.py,sha256=YgupapPwIuOo93Csgn7a674v8g_shDthvrZbXdd8WkE,9852
6
+ auto_editor/analyze.py,sha256=kKXXm_EffOrxFpYNa-fn1m5J012SOFaJ8SHBimvqtZ0,11805
7
+ auto_editor/edit.py,sha256=KwzNMQRDCD_9ckgLE9qM5MxS44S1NyBB0aKSpaXf0jM,11973
8
+ auto_editor/ffwrapper.py,sha256=tZv12az2HLGjVjLfUukhjiuC75D_Ga6JJJppCrwJmMc,7618
9
9
  auto_editor/help.py,sha256=BFiP7vBz42TUzum4-zaQIrV1OY7kHeN0pe0MPE0T5xw,7997
10
- auto_editor/make_layers.py,sha256=_YyuV7JvF9sneu3FJQPDkvRqzja8Fzscr4624bXN4iI,8214
10
+ auto_editor/make_layers.py,sha256=0Vwawcx-MvfqM9yNcBPDIiTd2nv0LMWUdZ1KZhqE37c,8394
11
11
  auto_editor/output.py,sha256=ySTt0WiU4-VszsATLxpsz5HIIL-7FzoOm-yJrJRqi3E,6714
12
- auto_editor/preview.py,sha256=K10TyP0_LWD4yEIUHzqSHs_97hKl0VIaIuA1xGHI8ZI,3023
12
+ auto_editor/preview.py,sha256=fo2BDIkvl96q_ssq8AAu1tl6FN_j23h8987aDPSmjDs,3094
13
13
  auto_editor/timeline.py,sha256=JwcS-8AS5vsoTL_m03aosYijScQef4AGa2lyutQ8wbI,7069
14
14
  auto_editor/validate_input.py,sha256=G4LzUdt0fSrIPRd-wvP7x9cOzXmHTd7-BPrFk2ZNEWk,2671
15
15
  auto_editor/vanparse.py,sha256=kHvGK7itqt37q0MPTSriPljB7ilFpjG5LuEVdulUbyg,9902
16
16
  auto_editor/wavfile.py,sha256=7N2LX_WfFVRnoXrKveLvuyTYpIz2htpGqfCD8tR4kJ8,9168
17
17
  auto_editor/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- auto_editor/formats/fcp11.py,sha256=H_zDaKFLUCEgRZGQjDZxqY4kmx0aIvoxWB3PMQXDsiE,5876
19
- auto_editor/formats/fcp7.py,sha256=ASmg9m94jaF_bq4uMawShyDi1ztptJHda6uf6SsX1zM,17713
20
- auto_editor/formats/json.py,sha256=RG5991SWaBSdbTCg49wJHvKMQM3UG2fM36xzc9aZrX0,6653
21
- auto_editor/formats/shotcut.py,sha256=9XM-NGDVSrHyG05Tsq6RR6WacyatQxGa6wPuRu-QtXU,5023
18
+ auto_editor/formats/fcp11.py,sha256=VwJWJs1qNDIVC8-pswipmKCk0e4V3LnE5fAMA0pPWVg,5857
19
+ auto_editor/formats/fcp7.py,sha256=i6MKTErzROu0VveHfZSTIJXDrH3VL8u_IXD9pblXsIk,17613
20
+ auto_editor/formats/json.py,sha256=Br-xHVHj59C0OPP2FwfJeht_fImepRXsaw0iDFvK7-w,7693
21
+ auto_editor/formats/shotcut.py,sha256=pbBQwOZ8Kqfm5ns0k_rBUX0XH_urIGfp77GORrzoW5Y,4984
22
22
  auto_editor/formats/utils.py,sha256=GIZw28WHuCIaZ_zMI0v6Kxbq0QaIpbLsdSegdYwQxQ8,1990
23
23
  auto_editor/lang/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  auto_editor/lang/json.py,sha256=OsNcYlfEj8ZLlzLK-gkLcrCCKI7mJz9rpe-6XLr4f9U,9231
@@ -29,29 +29,29 @@ auto_editor/lib/contracts.py,sha256=CTay7wMWnMSbTD1QAwE-b6yvIcSPvkpxkFQRXoJiqZE,
29
29
  auto_editor/lib/data_structs.py,sha256=EXNcdMsdmZxMRlpbXmIbRoC-YfGzvPZi7EdBQGwvpP4,6887
30
30
  auto_editor/lib/err.py,sha256=UlszQJdzMZwkbT8x3sY4GkCV_5x9yrd6uVVUzvA8iiI,35
31
31
  auto_editor/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- auto_editor/render/audio.py,sha256=lEmPuRKW5QipJV6ncCx8CHjg7wyK1knwqRM5p7zKvBY,8816
32
+ auto_editor/render/audio.py,sha256=pUhD4rQZfUnyzKgpuxNxl_2CUGwbkAWo2356HUAW7VM,8835
33
33
  auto_editor/render/subtitle.py,sha256=D4WDiY4iM9HsNfJvZay7zv_gvZPvyd12nd9Fi9vbPjQ,4646
34
- auto_editor/render/video.py,sha256=ov_AgFbF_6HWuOf1j-7g6sJBNuKyOyPMTAbcftxotaQ,13225
34
+ auto_editor/render/video.py,sha256=eSklzWvIdoagjJ7r4yTJMgHWUs2xqxAoF4gZtv90yIg,13184
35
35
  auto_editor/subcommands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  auto_editor/subcommands/desc.py,sha256=GDrKJYiHMaeTrplZAceXl1JwoqD78XsV2_5lc0Xd7po,869
37
- auto_editor/subcommands/info.py,sha256=N6dXeJf8WXAJjVtfY99fQNu2d-kiX86X6SH0QblWkOg,6571
37
+ auto_editor/subcommands/info.py,sha256=Xq4dVPOC44lmzkvuMJg0INtN_n-hlQ8Nu9mVGRehtC8,6906
38
38
  auto_editor/subcommands/levels.py,sha256=XHMG3jsdoXBvG0TlP1bBbtjD0m5EgWnOMBTIYx8VAnA,4001
39
39
  auto_editor/subcommands/palet.py,sha256=tbQoRWoT4jR3yu0etGApfprM-oQgXIjC-rIY-QG3nM0,655
40
40
  auto_editor/subcommands/repl.py,sha256=xoNq88PtbvX3r1-FLStOb5jNoJ_rFzrl7R3Tk8a7zyI,3717
41
- auto_editor/subcommands/subdump.py,sha256=Bm1PI1Gd2kQR2FFdgG9kXSXSZsAEOsSToSE5_BGF8UA,836
41
+ auto_editor/subcommands/subdump.py,sha256=2rIaGVtWWMBbPJ0NouPD7fY5lhk0QD_XKE_4EnAeWPw,892
42
42
  auto_editor/subcommands/test.py,sha256=2N1Hk03Oofs9WssvrUfDMaDFEp9ngcu9IwIgXkYfcGk,24810
43
43
  auto_editor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- auto_editor/utils/bar.py,sha256=eWpiXZpRc2v2LW-EaoAgG_cTtMh5275or8Ttda3Ei-I,3974
44
+ auto_editor/utils/bar.py,sha256=RJqkJ8gNr8op_Z-2hh48ExjSonmTPX-RshctK_itv14,3988
45
45
  auto_editor/utils/chunks.py,sha256=J-eGKtEz68gFtRrj1kOSgH4Tj_Yz6prNQ7Xr-d9NQJw,52
46
46
  auto_editor/utils/cmdkw.py,sha256=XApxw7FZBOEJV9N4LHhdw1GVfHbFfCjr-zCZ1gJsSvY,6002
47
- auto_editor/utils/container.py,sha256=WOMlUJ5pxVmeYsy79uCWMU5fCzM4tBRupAx-_Q5-PLg,7939
47
+ auto_editor/utils/container.py,sha256=cl8wN5w-PjShPabnppil56r2dykQCfWdsR45jBbCkuo,7976
48
48
  auto_editor/utils/encoder.py,sha256=auNYo7HXbcU4iTUCc0LE5lpwFmSvdWvBm6-5KIaRK8w,2983
49
49
  auto_editor/utils/func.py,sha256=H38xO6Wxg1TZILVrx-nCowCzj_mqBUtJuOFp4DV3Hsc,4843
50
- auto_editor/utils/log.py,sha256=6j2EWE97_urQijBvxhk2Gr2-VO_KNR1XbEobcAtTG-w,2668
51
- auto_editor/utils/types.py,sha256=aWyJpVBjmctxlxiL5o8r6lplKnaFSjVNQlcoXFgfmSk,11533
52
- auto_editor-24.13.1.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
53
- auto_editor-24.13.1.dist-info/METADATA,sha256=JTShKvxQqosFX0sXVjMyPrYx_OVJJ5zkXnc8Na8wAX0,7093
54
- auto_editor-24.13.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
55
- auto_editor-24.13.1.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
56
- auto_editor-24.13.1.dist-info/top_level.txt,sha256=xwV1JV1ZeRmlH9VeBRZXgXtWHpWSD4w1mY5II56D3ns,22
57
- auto_editor-24.13.1.dist-info/RECORD,,
50
+ auto_editor/utils/log.py,sha256=edfQPmdfBJMBFeCfxVjitXb74vk2o07xBrbgEccJ00U,2774
51
+ auto_editor/utils/types.py,sha256=zWbU_VkcdP4yHHzKyaSiXu560n5U53i0x5SPkUDsCZU,11570
52
+ auto_editor-24.24.1.dist-info/LICENSE,sha256=yiq99pWITHfqS0pbZMp7cy2dnbreTuvBwudsU-njvIM,1210
53
+ auto_editor-24.24.1.dist-info/METADATA,sha256=7iZzk70Se4J3tk0cafCj9J6PLuza2JF0gOtPi5guV3A,6284
54
+ auto_editor-24.24.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
55
+ auto_editor-24.24.1.dist-info/entry_points.txt,sha256=-H7zdTw4MqnAcwrN5xTNkGIhzZtJMxS9r6lTMeR9-aA,240
56
+ auto_editor-24.24.1.dist-info/top_level.txt,sha256=xwV1JV1ZeRmlH9VeBRZXgXtWHpWSD4w1mY5II56D3ns,22
57
+ auto_editor-24.24.1.dist-info/RECORD,,