From 5dd54ea3986bdcc507650e8ad6a398847f7a0584 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Mon, 19 Jan 2026 05:18:34 -0500 Subject: [PATCH] Make audio/layout pure --- av/audio/layout.pxd | 1 - av/audio/layout.py | 101 ++++++++++++++++++++++++++++++++++++++++++++ av/audio/layout.pyx | 82 ----------------------------------- av/dictionary.py | 2 +- 4 files changed, 102 insertions(+), 84 deletions(-) create mode 100644 av/audio/layout.py delete mode 100644 av/audio/layout.pyx diff --git a/av/audio/layout.pxd b/av/audio/layout.pxd index c7a2368f1..d7e991e95 100644 --- a/av/audio/layout.pxd +++ b/av/audio/layout.pxd @@ -3,6 +3,5 @@ cimport libav as lib cdef class AudioLayout: cdef lib.AVChannelLayout layout - cdef _init(self, lib.AVChannelLayout layout) cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout) diff --git a/av/audio/layout.py b/av/audio/layout.py new file mode 100644 index 000000000..43bedb9c7 --- /dev/null +++ b/av/audio/layout.py @@ -0,0 +1,101 @@ +from dataclasses import dataclass + +import cython +from cython.cimports import libav as lib +from cython.cimports.cpython.bytes import PyBytes_FromStringAndSize + + +@dataclass +class AudioChannel: + name: str + description: str + + def __repr__(self): + return f"" + + +_cinit_bypass_sentinel = cython.declare(object, object()) + + +@cython.cfunc +def get_audio_layout(c_layout: lib.AVChannelLayout) -> AudioLayout: + """Get an AudioLayout from Cython land.""" + layout: AudioLayout = AudioLayout(_cinit_bypass_sentinel) + layout.layout = c_layout + return layout + + +@cython.cclass +class AudioLayout: + def __cinit__(self, layout): + if layout is _cinit_bypass_sentinel: + return + + if type(layout) is str: + ret = lib.av_channel_layout_from_string(cython.address(c_layout), layout) + if ret != 0: + raise ValueError(f"Invalid layout: {layout}") + elif isinstance(layout, AudioLayout): + c_layout = cython.cast(AudioLayout, layout).layout + else: + raise TypeError( + f"layout must be of type: string | av.AudioLayout, got {type(layout)}" + ) + + self.layout = c_layout + + def __repr__(self): + return f"" + + def __eq__(self, other): + return ( + isinstance(other, AudioLayout) + and self.name == other.name + and self.nb_channels == other.nb_channels + ) + + @property + def nb_channels(self): + return self.layout.nb_channels + + @property + def channels(self): + buf: cython.char[16] + buf2: cython.char[128] + + results: list = [] + for index in range(self.layout.nb_channels): + size = lib.av_channel_name( + buf, + cython.sizeof(buf), + lib.av_channel_layout_channel_from_index( + cython.address(self.layout), index + ), + ) + size2 = lib.av_channel_description( + buf2, + cython.sizeof(buf2), + lib.av_channel_layout_channel_from_index( + cython.address(self.layout), index + ), + ) + results.append( + AudioChannel( + PyBytes_FromStringAndSize(buf, size - 1).decode("utf-8"), + PyBytes_FromStringAndSize(buf2, size2 - 1).decode("utf-8"), + ) + ) + + return tuple(results) + + @property + def name(self) -> str: + """The canonical name of the audio layout.""" + layout_name: cython.char[129] + ret: cython.int = lib.av_channel_layout_describe( + cython.address(self.layout), layout_name, cython.sizeof(layout_name) + ) + if ret < 0: + raise RuntimeError(f"Failed to get layout name: {ret}") + + return layout_name diff --git a/av/audio/layout.pyx b/av/audio/layout.pyx deleted file mode 100644 index 0936075ef..000000000 --- a/av/audio/layout.pyx +++ /dev/null @@ -1,82 +0,0 @@ -cimport libav as lib -from cpython.bytes cimport PyBytes_FromStringAndSize - -from dataclasses import dataclass - - -@dataclass -class AudioChannel: - name: str - description: str - - def __repr__(self): - return f"" - -cdef object _cinit_bypass_sentinel - -cdef AudioLayout get_audio_layout(lib.AVChannelLayout c_layout): - """Get an AudioLayout from Cython land.""" - cdef AudioLayout layout = AudioLayout.__new__(AudioLayout, _cinit_bypass_sentinel) - layout._init(c_layout) - return layout - - -cdef class AudioLayout: - def __init__(self, layout): - if layout is _cinit_bypass_sentinel: - return - - if type(layout) is str: - ret = lib.av_channel_layout_from_string(&c_layout, layout) - if ret != 0: - raise ValueError(f"Invalid layout: {layout}") - elif isinstance(layout, AudioLayout): - c_layout = (layout).layout - else: - raise TypeError(f"layout must be of type: string | av.AudioLayout, got {type(layout)}") - - self._init(c_layout) - - cdef _init(self, lib.AVChannelLayout layout): - self.layout = layout - - def __repr__(self): - return f"" - - def __eq__(self, other): - return isinstance(other, AudioLayout) and self.name == other.name and self.nb_channels == other.nb_channels - - @property - def nb_channels(self): - return self.layout.nb_channels - - @property - def channels(self): - cdef char buf[16] - cdef char buf2[128] - - results = [] - - for index in range(self.layout.nb_channels): - size = lib.av_channel_name(buf, sizeof(buf), lib.av_channel_layout_channel_from_index(&self.layout, index)) - 1 - size2 = lib.av_channel_description(buf2, sizeof(buf2), lib.av_channel_layout_channel_from_index(&self.layout, index)) - 1 - results.append( - AudioChannel( - PyBytes_FromStringAndSize(buf, size).decode("utf-8"), - PyBytes_FromStringAndSize(buf2, size2).decode("utf-8"), - ) - ) - - return tuple(results) - - @property - def name(self) -> str: - """The canonical name of the audio layout.""" - cdef char layout_name[128] - cdef int ret - - ret = lib.av_channel_layout_describe(&self.layout, layout_name, sizeof(layout_name)) - if ret < 0: - raise RuntimeError(f"Failed to get layout name: {ret}") - - return layout_name \ No newline at end of file diff --git a/av/dictionary.py b/av/dictionary.py index 2080266ed..2c08be63b 100644 --- a/av/dictionary.py +++ b/av/dictionary.py @@ -43,7 +43,7 @@ def __iter__(self): yield element.key def __repr__(self): - return f"bv.Dictionary({dict(self)!r})" + return f"av.Dictionary({dict(self)!r})" def copy(self): other = cython.declare(_Dictionary, Dictionary())