auto-editor 26.3.3__tar.gz → 27.1.0__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 (66) hide show
  1. {auto_editor-26.3.3 → auto_editor-27.1.0}/PKG-INFO +8 -7
  2. {auto_editor-26.3.3 → auto_editor-27.1.0}/README.md +2 -2
  3. auto_editor-27.1.0/auto_editor/__init__.py +1 -0
  4. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/__main__.py +17 -5
  5. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/analyze.py +30 -36
  6. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/desc.py +2 -2
  7. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/info.py +3 -3
  8. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/levels.py +5 -5
  9. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/repl.py +3 -8
  10. auto_editor-27.1.0/auto_editor/cmds/subdump.py +74 -0
  11. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/test.py +92 -42
  12. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/edit.py +59 -111
  13. auto_editor-27.1.0/auto_editor/ffwrapper.py +178 -0
  14. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/formats/fcp11.py +10 -8
  15. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/formats/fcp7.py +11 -12
  16. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/formats/json.py +10 -11
  17. {auto_editor-26.3.3/auto_editor/lang → auto_editor-27.1.0/auto_editor}/json.py +39 -43
  18. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lang/palet.py +2 -2
  19. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lang/stdenv.py +13 -0
  20. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/make_layers.py +18 -8
  21. auto_editor-27.1.0/auto_editor/render/audio.py +514 -0
  22. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/render/subtitle.py +10 -14
  23. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/render/video.py +41 -46
  24. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/timeline.py +60 -10
  25. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/container.py +21 -14
  26. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/func.py +21 -0
  27. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor.egg-info/PKG-INFO +8 -7
  28. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor.egg-info/SOURCES.txt +1 -3
  29. auto_editor-27.1.0/auto_editor.egg-info/requires.txt +2 -0
  30. {auto_editor-26.3.3 → auto_editor-27.1.0}/docs/build.py +16 -7
  31. {auto_editor-26.3.3 → auto_editor-27.1.0}/pyproject.toml +4 -4
  32. auto_editor-26.3.3/auto_editor/__init__.py +0 -1
  33. auto_editor-26.3.3/auto_editor/cmds/subdump.py +0 -20
  34. auto_editor-26.3.3/auto_editor/ffwrapper.py +0 -174
  35. auto_editor-26.3.3/auto_editor/output.py +0 -86
  36. auto_editor-26.3.3/auto_editor/render/audio.py +0 -377
  37. auto_editor-26.3.3/auto_editor/wavfile.py +0 -310
  38. auto_editor-26.3.3/auto_editor.egg-info/requires.txt +0 -2
  39. {auto_editor-26.3.3 → auto_editor-27.1.0}/LICENSE +0 -0
  40. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/__init__.py +0 -0
  41. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/cache.py +0 -0
  42. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/cmds/palet.py +0 -0
  43. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/formats/__init__.py +0 -0
  44. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/formats/shotcut.py +0 -0
  45. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/formats/utils.py +0 -0
  46. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/help.py +0 -0
  47. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lang/__init__.py +0 -0
  48. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lang/libintrospection.py +0 -0
  49. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lang/libmath.py +0 -0
  50. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lib/__init__.py +0 -0
  51. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lib/contracts.py +0 -0
  52. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lib/data_structs.py +0 -0
  53. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/lib/err.py +0 -0
  54. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/preview.py +0 -0
  55. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/render/__init__.py +0 -0
  56. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/__init__.py +0 -0
  57. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/bar.py +0 -0
  58. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/chunks.py +0 -0
  59. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/cmdkw.py +0 -0
  60. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/log.py +0 -0
  61. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/utils/types.py +0 -0
  62. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor/vanparse.py +0 -0
  63. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor.egg-info/dependency_links.txt +0 -0
  64. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor.egg-info/entry_points.txt +0 -0
  65. {auto_editor-26.3.3 → auto_editor-27.1.0}/auto_editor.egg-info/top_level.txt +0 -0
  66. {auto_editor-26.3.3 → auto_editor-27.1.0}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: auto-editor
3
- Version: 26.3.3
3
+ Version: 27.1.0
4
4
  Summary: Auto-Editor: Effort free video editing!
5
5
  Author-email: WyattBlue <wyattblue@auto-editor.com>
6
- License: Unlicense
6
+ License-Expression: Unlicense
7
7
  Project-URL: Bug Tracker, https://github.com/WyattBlue/auto-editor/issues
8
8
  Project-URL: Source Code, https://github.com/WyattBlue/auto-editor
9
9
  Project-URL: homepage, https://auto-editor.com
@@ -11,8 +11,9 @@ Keywords: video,audio,media,editor,editing,processing,nonlinear,automatic,silenc
11
11
  Requires-Python: <3.14,>=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: numpy<3.0,>=1.24
15
- Requires-Dist: pyav==14.2.*
14
+ Requires-Dist: numpy<3.0,>=2
15
+ Requires-Dist: basswood-av<16,>=15.0.0
16
+ Dynamic: license-file
16
17
 
17
18
  <p align="center"><img src="https://auto-editor.com/img/auto-editor-banner.webp" title="Auto-Editor" width="700"></p>
18
19
 
@@ -175,9 +176,9 @@ auto-editor --margin --help
175
176
 
176
177
  ## Articles
177
178
  - [How to Install Auto-Editor](https://auto-editor.com/installing)
178
- - [All the Options (And What They Do)](https://auto-editor.com/options)
179
+ - [All the Options (And What They Do)](https://auto-editor.com/ref/options)
179
180
  - [Docs](https://auto-editor.com/docs)
180
- - [Blog](https://auto-editor.com/blog)
181
+ - [Blog](https://basswood-io.com/blog/)
181
182
 
182
183
  ## Copyright
183
184
  Auto-Editor is under the [Public Domain](https://github.com/WyattBlue/auto-editor/blob/master/LICENSE) and includes all directories besides the ones listed below. Auto-Editor was created by [these people.](https://auto-editor.com/blog/thank-you-early-testers)
@@ -159,9 +159,9 @@ auto-editor --margin --help
159
159
 
160
160
  ## Articles
161
161
  - [How to Install Auto-Editor](https://auto-editor.com/installing)
162
- - [All the Options (And What They Do)](https://auto-editor.com/options)
162
+ - [All the Options (And What They Do)](https://auto-editor.com/ref/options)
163
163
  - [Docs](https://auto-editor.com/docs)
164
- - [Blog](https://auto-editor.com/blog)
164
+ - [Blog](https://basswood-io.com/blog/)
165
165
 
166
166
  ## Copyright
167
167
  Auto-Editor is under the [Public Domain](https://github.com/WyattBlue/auto-editor/blob/master/LICENSE) and includes all directories besides the ones listed below. Auto-Editor was created by [these people.](https://auto-editor.com/blog/thank-you-early-testers)
@@ -0,0 +1 @@
1
+ __version__ = "27.1.0"
@@ -75,7 +75,9 @@ class Args:
75
75
 
76
76
  # Audio Rendering
77
77
  audio_codec: str = "auto"
78
+ audio_layout: str | None = None
78
79
  audio_bitrate: str = "auto"
80
+ mix_audio_streams: bool = False
79
81
  keep_tracks_separate: bool = False
80
82
  audio_normalize: str = "#f"
81
83
 
@@ -335,16 +337,26 @@ def main_options(parser: ArgumentParser) -> ArgumentParser:
335
337
  metavar="ENCODER",
336
338
  help="Set audio codec for output media",
337
339
  )
340
+ parser.add_argument(
341
+ "--audio-layout",
342
+ "-channel-layout",
343
+ "-layout",
344
+ metavar="LAYOUT",
345
+ help="Set the audio layout for the output media/timeline",
346
+ )
338
347
  parser.add_argument(
339
348
  "--audio-bitrate",
340
349
  "-b:a",
341
350
  metavar="BITRATE",
342
351
  help="Set the number of bits per second for audio",
343
352
  )
353
+ parser.add_argument(
354
+ "--mix-audio-streams", flag=True, help="Mix all audio streams together into one"
355
+ )
344
356
  parser.add_argument(
345
357
  "--keep-tracks-separate",
346
358
  flag=True,
347
- help="Don't mix all audio tracks into one when exporting",
359
+ help="Don't mix all audio streams into one when exporting (default)",
348
360
  )
349
361
  parser.add_argument(
350
362
  "--audio-normalize",
@@ -448,15 +460,15 @@ def main() -> None:
448
460
  if args.debug and not args.input:
449
461
  buf = StringIO()
450
462
  buf.write(f"OS: {plat.system()} {plat.release()} {plat.machine().lower()}\n")
451
- buf.write(f"Python: {plat.python_version()}\nPyAV: ")
463
+ buf.write(f"Python: {plat.python_version()}\nAV: ")
452
464
  try:
453
- import av
465
+ import bv
454
466
  except (ModuleNotFoundError, ImportError):
455
467
  buf.write("not found")
456
468
  else:
457
469
  try:
458
- buf.write(f"{av.__version__} ")
459
- license = av._core.library_meta["libavcodec"]["license"]
470
+ buf.write(f"{bv.__version__} ")
471
+ license = bv._core.library_meta["libavcodec"]["license"]
460
472
  buf.write(f"({license})")
461
473
  except AttributeError:
462
474
  buf.write("error")
@@ -9,10 +9,10 @@ from math import ceil
9
9
  from tempfile import gettempdir
10
10
  from typing import TYPE_CHECKING
11
11
 
12
- import av
12
+ import bv
13
13
  import numpy as np
14
- from av.audio.fifo import AudioFifo
15
- from av.subtitles.subtitle import AssSubtitle
14
+ from bv.audio.fifo import AudioFifo
15
+ from bv.subtitles.subtitle import AssSubtitle
16
16
 
17
17
  from auto_editor import __version__
18
18
 
@@ -72,7 +72,7 @@ def mut_remove_large(
72
72
  active = False
73
73
 
74
74
 
75
- def iter_audio(audio_stream: av.AudioStream, tb: Fraction) -> Iterator[np.float32]:
75
+ def iter_audio(audio_stream: bv.AudioStream, tb: Fraction) -> Iterator[np.float32]:
76
76
  fifo = AudioFifo()
77
77
  sr = audio_stream.rate
78
78
 
@@ -80,10 +80,10 @@ def iter_audio(audio_stream: av.AudioStream, tb: Fraction) -> Iterator[np.float3
80
80
  accumulated_error = Fraction(0)
81
81
 
82
82
  # Resample so that audio data is between [-1, 1]
83
- resampler = av.AudioResampler(av.AudioFormat("flt"), audio_stream.layout, sr)
83
+ resampler = bv.AudioResampler(bv.AudioFormat("flt"), audio_stream.layout, sr)
84
84
 
85
85
  container = audio_stream.container
86
- assert isinstance(container, av.container.InputContainer)
86
+ assert isinstance(container, bv.container.InputContainer)
87
87
 
88
88
  for frame in container.decode(audio_stream):
89
89
  frame.pts = None # Skip time checks
@@ -103,7 +103,7 @@ def iter_audio(audio_stream: av.AudioStream, tb: Fraction) -> Iterator[np.float3
103
103
 
104
104
 
105
105
  def iter_motion(
106
- video: av.VideoStream, tb: Fraction, blur: int, width: int
106
+ video: bv.VideoStream, tb: Fraction, blur: int, width: int
107
107
  ) -> Iterator[np.float32]:
108
108
  video.thread_type = "AUTO"
109
109
 
@@ -113,7 +113,7 @@ def iter_motion(
113
113
  index = 0
114
114
  prev_index = -1
115
115
 
116
- graph = av.filter.Graph()
116
+ graph = bv.filter.Graph()
117
117
  graph.link_nodes(
118
118
  graph.add_buffer(template=video),
119
119
  graph.add("scale", f"{width}:-1"),
@@ -123,7 +123,7 @@ def iter_motion(
123
123
  ).configure()
124
124
 
125
125
  container = video.container
126
- assert isinstance(container, av.container.InputContainer)
126
+ assert isinstance(container, bv.container.InputContainer)
127
127
 
128
128
  for unframe in container.decode(video):
129
129
  if unframe.pts is None:
@@ -154,7 +154,7 @@ def iter_motion(
154
154
 
155
155
  @dataclass(slots=True)
156
156
  class Levels:
157
- container: av.container.InputContainer
157
+ container: bv.container.InputContainer
158
158
  name: str
159
159
  mod_time: int
160
160
  tb: Fraction
@@ -258,7 +258,7 @@ class Levels:
258
258
  if audio.duration is not None and audio.time_base is not None:
259
259
  inaccurate_dur = int(audio.duration * audio.time_base * self.tb)
260
260
  elif container.duration is not None:
261
- inaccurate_dur = int(container.duration / av.time_base * self.tb)
261
+ inaccurate_dur = int(container.duration / bv.time_base * self.tb)
262
262
  else:
263
263
  inaccurate_dur = 1024
264
264
 
@@ -343,15 +343,9 @@ class Levels:
343
343
  for packet in container.demux(subtitle_stream):
344
344
  if packet.pts is None or packet.duration is None:
345
345
  continue
346
- for subset in packet.decode():
347
- # See definition of `AVSubtitle`
348
- # in: https://ffmpeg.org/doxygen/trunk/avcodec_8h_source.html
349
- start = float(packet.pts * subtitle_stream.time_base)
350
- dur = float(packet.duration * subtitle_stream.time_base)
351
-
352
- end = round((start + dur) * self.tb)
353
- sub_length = max(sub_length, end)
346
+ sub_length = max(sub_length, packet.pts + packet.duration)
354
347
 
348
+ sub_length = round(sub_length * subtitle_stream.time_base * self.tb)
355
349
  result = np.zeros((sub_length), dtype=np.bool_)
356
350
  del sub_length
357
351
 
@@ -363,25 +357,25 @@ class Levels:
363
357
  continue
364
358
  if early_exit:
365
359
  break
366
- for subset in packet.decode():
367
- if max_count is not None and count >= max_count:
368
- early_exit = True
369
- break
370
360
 
371
- start = float(packet.pts * subtitle_stream.time_base)
372
- dur = float(packet.duration * subtitle_stream.time_base)
361
+ if max_count is not None and count >= max_count:
362
+ early_exit = True
363
+ break
364
+
365
+ start = float(packet.pts * subtitle_stream.time_base)
366
+ dur = float(packet.duration * subtitle_stream.time_base)
373
367
 
374
- san_start = round(start * self.tb)
375
- san_end = round((start + dur) * self.tb)
368
+ san_start = round(start * self.tb)
369
+ san_end = round((start + dur) * self.tb)
376
370
 
377
- for sub in subset:
378
- if not isinstance(sub, AssSubtitle):
379
- continue
371
+ for sub in packet.decode():
372
+ if not isinstance(sub, AssSubtitle):
373
+ continue
380
374
 
381
- line = sub.dialogue.decode(errors="ignore")
382
- if line and re.search(re_pattern, line):
383
- result[san_start:san_end] = 1
384
- count += 1
375
+ line = sub.dialogue.decode(errors="ignore")
376
+ if line and re.search(re_pattern, line):
377
+ result[san_start:san_end] = 1
378
+ count += 1
385
379
 
386
380
  container.seek(0)
387
381
  return result
@@ -391,8 +385,8 @@ def initLevels(
391
385
  src: FileInfo, tb: Fraction, bar: Bar, no_cache: bool, log: Log
392
386
  ) -> Levels:
393
387
  try:
394
- container = av.open(src.path)
395
- except av.FFmpegError as e:
388
+ container = bv.open(src.path)
389
+ except bv.FFmpegError as e:
396
390
  log.error(e)
397
391
 
398
392
  mod_time = int(src.path.stat().st_mtime)
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import sys
4
4
  from dataclasses import dataclass, field
5
5
 
6
- from auto_editor.ffwrapper import initFileInfo
6
+ from auto_editor.ffwrapper import FileInfo
7
7
  from auto_editor.utils.log import Log
8
8
  from auto_editor.vanparse import ArgumentParser
9
9
 
@@ -22,7 +22,7 @@ def desc_options(parser: ArgumentParser) -> ArgumentParser:
22
22
  def main(sys_args: list[str] = sys.argv[1:]) -> None:
23
23
  args = desc_options(ArgumentParser("desc")).parse_args(DescArgs, sys_args)
24
24
  for path in args.input:
25
- src = initFileInfo(path, Log())
25
+ src = FileInfo.init(path, Log())
26
26
  if src.description is not None:
27
27
  sys.stdout.write(f"\n{src.description}\n\n")
28
28
  else:
@@ -5,8 +5,8 @@ import sys
5
5
  from dataclasses import dataclass, field
6
6
  from typing import Any, Literal, TypedDict
7
7
 
8
- from auto_editor.ffwrapper import initFileInfo
9
- from auto_editor.lang.json import dump
8
+ from auto_editor.ffwrapper import FileInfo
9
+ from auto_editor.json import dump
10
10
  from auto_editor.make_layers import make_sane_timebase
11
11
  from auto_editor.timeline import v3
12
12
  from auto_editor.utils.func import aspect_ratio
@@ -102,7 +102,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
102
102
  file_info[file] = {"type": "timeline"}
103
103
  continue
104
104
 
105
- src = initFileInfo(file, log)
105
+ src = FileInfo.init(file, log)
106
106
 
107
107
  if len(src.videos) + len(src.audios) + len(src.subtitles) == 0:
108
108
  file_info[file] = {"type": "unknown"}
@@ -5,11 +5,11 @@ from dataclasses import dataclass, field
5
5
  from fractions import Fraction
6
6
  from typing import TYPE_CHECKING
7
7
 
8
- import av
8
+ import bv
9
9
  import numpy as np
10
10
 
11
11
  from auto_editor.analyze import *
12
- from auto_editor.ffwrapper import initFileInfo
12
+ from auto_editor.ffwrapper import FileInfo
13
13
  from auto_editor.lang.palet import env
14
14
  from auto_editor.lib.contracts import is_bool, is_nat, is_nat1, is_str, is_void, orc
15
15
  from auto_editor.utils.bar import initBar
@@ -87,7 +87,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
87
87
  bar = initBar("none")
88
88
  log = Log(quiet=True)
89
89
 
90
- sources = [initFileInfo(path, log) for path in args.input]
90
+ sources = [FileInfo.init(path, log) for path in args.input]
91
91
  if len(sources) < 1:
92
92
  log.error("levels needs at least one input file")
93
93
 
@@ -134,7 +134,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
134
134
  if (arr := levels.read_cache("audio", (obj["stream"],))) is not None:
135
135
  print_arr(arr)
136
136
  else:
137
- container = av.open(src.path, "r")
137
+ container = bv.open(src.path, "r")
138
138
  audio_stream = container.streams.audio[obj["stream"]]
139
139
 
140
140
  values = []
@@ -155,7 +155,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
155
155
  if (arr := levels.read_cache("motion", mobj)) is not None:
156
156
  print_arr(arr)
157
157
  else:
158
- container = av.open(src.path, "r")
158
+ container = bv.open(src.path, "r")
159
159
  video_stream = container.streams.video[obj["stream"]]
160
160
 
161
161
  values = []
@@ -7,7 +7,7 @@ from os import environ
7
7
 
8
8
  import auto_editor
9
9
  from auto_editor.analyze import initLevels
10
- from auto_editor.ffwrapper import initFileInfo
10
+ from auto_editor.ffwrapper import FileInfo
11
11
  from auto_editor.lang.palet import ClosingError, Lexer, Parser, env, interpret
12
12
  from auto_editor.lang.stdenv import make_standard_env
13
13
  from auto_editor.lib.data_structs import print_str
@@ -48,11 +48,6 @@ def repl_options(parser: ArgumentParser) -> ArgumentParser:
48
48
  type=frame_rate,
49
49
  help="Set custom timebase",
50
50
  )
51
- parser.add_argument(
52
- "--temp-dir",
53
- metavar="PATH",
54
- help="Set where the temporary directory is located",
55
- )
56
51
  return parser
57
52
 
58
53
 
@@ -60,8 +55,8 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
60
55
  args = repl_options(ArgumentParser(None)).parse_args(REPL_Args, sys_args)
61
56
 
62
57
  if args.input:
63
- log = Log(quiet=True, temp_dir=args.temp_dir)
64
- sources = [initFileInfo(path, log) for path in args.input]
58
+ log = Log(quiet=True)
59
+ sources = [FileInfo.init(path, log) for path in args.input]
65
60
  src = sources[0]
66
61
  tb = src.get_fps() if args.timebase is None else args.timebase
67
62
  env["timebase"] = tb
@@ -0,0 +1,74 @@
1
+ import sys
2
+ from dataclasses import dataclass, field
3
+
4
+ import bv
5
+ from bv.subtitles.subtitle import AssSubtitle
6
+
7
+ from auto_editor.json import dump
8
+ from auto_editor.vanparse import ArgumentParser
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class SubdumpArgs:
13
+ help: bool = False
14
+ input: list[str] = field(default_factory=list)
15
+ json: bool = False
16
+
17
+
18
+ def main(sys_args: list[str] = sys.argv[1:]) -> None:
19
+ parser = ArgumentParser("subdump")
20
+ parser.add_required("input", nargs="*")
21
+ parser.add_argument("--json", flag=True)
22
+ args = parser.parse_args(SubdumpArgs, sys_args)
23
+
24
+ do_filter = True
25
+
26
+ if args.json:
27
+ data = {}
28
+ for input_file in args.input:
29
+ container = bv.open(input_file)
30
+ for s in range(len(container.streams.subtitles)):
31
+ entry_data = []
32
+
33
+ input_stream = container.streams.subtitles[s]
34
+ assert input_stream.time_base is not None
35
+ for packet in container.demux(input_stream):
36
+ if (
37
+ packet.dts is None
38
+ or packet.pts is None
39
+ or packet.duration is None
40
+ ):
41
+ continue
42
+
43
+ start = packet.pts * input_stream.time_base
44
+ end = start + packet.duration * input_stream.time_base
45
+
46
+ startf = round(float(start), 3)
47
+ endf = round(float(end), 3)
48
+
49
+ if do_filter and endf - startf <= 0.02:
50
+ continue
51
+
52
+ for sub in packet.decode():
53
+ if isinstance(sub, AssSubtitle):
54
+ content = sub.dialogue.decode("utf-8", errors="ignore")
55
+ entry_data.append([startf, endf, content])
56
+
57
+ data[f"{input_file}:{s}"] = entry_data
58
+ container.close()
59
+
60
+ dump(data, sys.stdout, indent=4)
61
+ return
62
+
63
+ for i, input_file in enumerate(args.input):
64
+ with bv.open(input_file) as container:
65
+ for s in range(len(container.streams.subtitles)):
66
+ print(f"file: {input_file} ({s}:{container.streams.subtitles[s].name})")
67
+ for sub2 in container.decode(subtitles=s):
68
+ if isinstance(sub2, AssSubtitle):
69
+ print(sub2.ass.decode("utf-8", errors="ignore"))
70
+ print("------")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()