Skip to content

BasswoodAV -> PyAV #781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions auto_editor/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,13 +454,13 @@ def main() -> None:
buf.write(f"OS: {plat.system()} {plat.release()} {plat.machine().lower()}\n")
buf.write(f"Python: {plat.python_version()}\nAV: ")
try:
import bv
import av
except (ModuleNotFoundError, ImportError):
buf.write("not found")
else:
try:
buf.write(f"{bv.__version__} ")
license = bv._core.library_meta["libavcodec"]["license"]
buf.write(f"{av.__version__} ")
license = av._core.library_meta["libavcodec"]["license"]
buf.write(f"({license})")
except AttributeError:
buf.write("error")
Expand Down
26 changes: 13 additions & 13 deletions auto_editor/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
from tempfile import gettempdir
from typing import TYPE_CHECKING

import bv
import av
import numpy as np
from bv.audio.fifo import AudioFifo
from bv.subtitles.subtitle import AssSubtitle
from av.audio.fifo import AudioFifo
from av.subtitles.subtitle import AssSubtitle

from auto_editor import __version__

Expand Down Expand Up @@ -72,18 +72,18 @@ def mut_remove_large(
active = False


def iter_audio(audio_stream: bv.AudioStream, tb: Fraction) -> Iterator[np.float32]:
def iter_audio(audio_stream: av.AudioStream, tb: Fraction) -> Iterator[np.float32]:
fifo = AudioFifo()
sr = audio_stream.rate

exact_size = (1 / tb) * sr
accumulated_error = Fraction(0)

# Resample so that audio data is between [-1, 1]
resampler = bv.AudioResampler(bv.AudioFormat("flt"), audio_stream.layout, sr)
resampler = av.AudioResampler(av.AudioFormat("flt"), audio_stream.layout, sr)

container = audio_stream.container
assert isinstance(container, bv.container.InputContainer)
assert isinstance(container, av.container.InputContainer)

for frame in container.decode(audio_stream):
frame.pts = None # Skip time checks
Expand All @@ -103,7 +103,7 @@ def iter_audio(audio_stream: bv.AudioStream, tb: Fraction) -> Iterator[np.float3


def iter_motion(
video: bv.VideoStream, tb: Fraction, blur: int, width: int
video: av.VideoStream, tb: Fraction, blur: int, width: int
) -> Iterator[np.float32]:
video.thread_type = "AUTO"

Expand All @@ -113,7 +113,7 @@ def iter_motion(
index = 0
prev_index = -1

graph = bv.filter.Graph()
graph = av.filter.Graph()
graph.link_nodes(
graph.add_buffer(template=video),
graph.add("scale", f"{width}:-1"),
Expand All @@ -123,7 +123,7 @@ def iter_motion(
).configure()

container = video.container
assert isinstance(container, bv.container.InputContainer)
assert isinstance(container, av.container.InputContainer)

for unframe in container.decode(video):
if unframe.pts is None:
Expand Down Expand Up @@ -154,7 +154,7 @@ def iter_motion(

@dataclass(slots=True)
class Levels:
container: bv.container.InputContainer
container: av.container.InputContainer
name: str
mod_time: int
tb: Fraction
Expand Down Expand Up @@ -258,7 +258,7 @@ def audio(self, stream: int) -> NDArray[np.float32]:
if audio.duration is not None and audio.time_base is not None:
inaccurate_dur = int(audio.duration * audio.time_base * self.tb)
elif container.duration is not None:
inaccurate_dur = int(container.duration / bv.time_base * self.tb)
inaccurate_dur = int(container.duration / av.time_base * self.tb)
else:
inaccurate_dur = 1024

Expand Down Expand Up @@ -385,8 +385,8 @@ def initLevels(
src: FileInfo, tb: Fraction, bar: Bar, no_cache: bool, log: Log
) -> Levels:
try:
container = bv.open(src.path)
except bv.FFmpegError as e:
container = av.open(src.path)
except av.FFmpegError as e:
log.error(e)

mod_time = int(src.path.stat().st_mtime)
Expand Down
4 changes: 2 additions & 2 deletions auto_editor/cmds/desc.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
from dataclasses import dataclass, field

import bv
import av

from auto_editor.vanparse import ArgumentParser

Expand All @@ -21,7 +21,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
args = desc_options(ArgumentParser("desc")).parse_args(DescArgs, sys_args)
for path in args.input:
try:
container = bv.open(path)
container = av.open(path)
desc = container.metadata.get("description", None)
except Exception:
desc = None
Expand Down
6 changes: 3 additions & 3 deletions auto_editor/cmds/levels.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fractions import Fraction
from typing import TYPE_CHECKING

import bv
import av
import numpy as np

from auto_editor.analyze import *
Expand Down Expand Up @@ -137,7 +137,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
):
print_arr(arr)
else:
container = bv.open(src.path, "r")
container = av.open(src.path, "r")
audio_stream = container.streams.audio[obj["stream"]]

values = []
Expand All @@ -162,7 +162,7 @@ def value_storing_generator() -> Iterator[np.float32]:
):
print_arr(arr)
else:
container = bv.open(src.path, "r")
container = av.open(src.path, "r")
video_stream = container.streams.video[obj["stream"]]

values = []
Expand Down
8 changes: 4 additions & 4 deletions auto_editor/cmds/subdump.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import sys
from dataclasses import dataclass, field

import bv
from bv.subtitles.subtitle import AssSubtitle
import av
from av.subtitles.subtitle import AssSubtitle

from auto_editor.json import dump
from auto_editor.vanparse import ArgumentParser
Expand All @@ -24,7 +24,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
if args.json:
data = {}
for input_file in args.input:
container = bv.open(input_file)
container = av.open(input_file)
for s in range(len(container.streams.subtitles)):
entry_data = []

Expand Down Expand Up @@ -59,7 +59,7 @@ def main(sys_args: list[str] = sys.argv[1:]) -> None:
return

for input_file in args.input:
with bv.open(input_file) as container:
with av.open(input_file) as container:
for s in range(len(container.streams.subtitles)):
print(f"file: {input_file} ({s}:{container.streams.subtitles[s].name})")
for sub2 in container.decode(subtitles=s):
Expand Down
45 changes: 23 additions & 22 deletions auto_editor/cmds/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from tempfile import mkdtemp
from time import perf_counter

import bv
import av
import numpy as np
from av import AudioStream, VideoStream

from auto_editor.ffwrapper import FileInfo
from auto_editor.lang.palet import Lexer, Parser, env, interpret
Expand Down Expand Up @@ -164,36 +165,36 @@ def test_movflags(self) -> None:
file = "resources/testsrc.mp4"
out = self.main([file], ["--faststart"]) + ".mp4"
fast = calculate_sha256(out)
with bv.open(out) as container:
assert isinstance(container.streams[0], bv.VideoStream)
assert isinstance(container.streams[1], bv.AudioStream)
with av.open(out) as container:
assert isinstance(container.streams[0], VideoStream)
assert isinstance(container.streams[1], AudioStream)

out = self.main([file], ["--no-faststart"]) + ".mp4"
nofast = calculate_sha256(out)
with bv.open(out) as container:
assert isinstance(container.streams[0], bv.VideoStream)
assert isinstance(container.streams[1], bv.AudioStream)
with av.open(out) as container:
assert isinstance(container.streams[0], VideoStream)
assert isinstance(container.streams[1], AudioStream)

out = self.main([file], ["--fragmented"]) + ".mp4"
frag = calculate_sha256(out)
with bv.open(out) as container:
assert isinstance(container.streams[0], bv.VideoStream)
assert isinstance(container.streams[1], bv.AudioStream)
with av.open(out) as container:
assert isinstance(container.streams[0], VideoStream)
assert isinstance(container.streams[1], AudioStream)

assert fast != nofast, "+faststart is not being applied"
assert frag not in (fast, nofast), "fragmented output should diff."

def test_example(self) -> None:
out = self.main(["example.mp4"], [], output="example_ALTERED.mp4")
with bv.open(out) as container:
with av.open(out) as container:
assert container.duration is not None
assert container.duration > 17300000 and container.duration < 2 << 24

assert len(container.streams) == 2
video = container.streams[0]
audio = container.streams[1]
assert isinstance(video, bv.VideoStream)
assert isinstance(audio, bv.AudioStream)
assert isinstance(video, VideoStream)
assert isinstance(audio, AudioStream)
assert video.base_rate == 30
assert video.average_rate is not None
assert video.average_rate == 30, video.average_rate
Expand All @@ -207,28 +208,28 @@ def test_example(self) -> None:

def test_video_to_mp3(self) -> None:
out = self.main(["example.mp4"], [], output="example_ALTERED.mp3")
with bv.open(out) as container:
with av.open(out) as container:
assert container.duration is not None
assert container.duration > 17300000 and container.duration < 2 << 24

assert len(container.streams) == 1
audio = container.streams[0]
assert isinstance(audio, bv.AudioStream)
assert isinstance(audio, AudioStream)
assert audio.codec.name in ("mp3", "mp3float")
assert audio.sample_rate == 48000
assert audio.layout.name == "stereo"

def test_to_mono(self) -> None:
out = self.main(["example.mp4"], ["-layout", "mono"], output="example_mono.mp4")
with bv.open(out) as container:
with av.open(out) as container:
assert container.duration is not None
assert container.duration > 17300000 and container.duration < 2 << 24

assert len(container.streams) == 2
video = container.streams[0]
audio = container.streams[1]
assert isinstance(video, bv.VideoStream)
assert isinstance(audio, bv.AudioStream)
assert isinstance(video, VideoStream)
assert isinstance(audio, AudioStream)
assert video.base_rate == 30
assert video.average_rate is not None
assert video.average_rate == 30, video.average_rate
Expand Down Expand Up @@ -305,18 +306,18 @@ def test_input_extension(self):
self.check([path, "--no-open"], "must have an extension")

def test_silent_threshold(self):
with bv.open("resources/new-commentary.mp3") as container:
with av.open("resources/new-commentary.mp3") as container:
assert container.duration is not None
assert container.duration / bv.time_base == 6.732
assert container.duration / av.time_base == 6.732

out = self.main(
["resources/new-commentary.mp3"], ["--edit", "audio:threshold=0.1"]
)
out += ".mp3"

with bv.open(out) as container:
with av.open(out) as container:
assert container.duration is not None
assert container.duration / bv.time_base == 6.552
assert container.duration / av.time_base == 6.552

def test_track(self):
out = self.main(["resources/multi-track.mov"], []) + ".mov"
Expand Down
Loading