diff --git a/sbapp/pyogg/__init__.py b/sbapp/pyogg/__init__.py new file mode 100644 index 0000000..a97b0d2 --- /dev/null +++ b/sbapp/pyogg/__init__.py @@ -0,0 +1,108 @@ +import ctypes + +from .pyogg_error import PyOggError +from .ogg import PYOGG_OGG_AVAIL +from .vorbis import PYOGG_VORBIS_AVAIL, PYOGG_VORBIS_FILE_AVAIL, PYOGG_VORBIS_ENC_AVAIL +from .opus import PYOGG_OPUS_AVAIL, PYOGG_OPUS_FILE_AVAIL, PYOGG_OPUS_ENC_AVAIL +from .flac import PYOGG_FLAC_AVAIL + + +#: PyOgg version number. Versions should comply with PEP440. +__version__ = '0.7' + + +if (PYOGG_OGG_AVAIL and PYOGG_VORBIS_AVAIL and PYOGG_VORBIS_FILE_AVAIL): + # VorbisFile + from .vorbis_file import VorbisFile + # VorbisFileStream + from .vorbis_file_stream import VorbisFileStream + +else: + class VorbisFile: # type: ignore + def __init__(*args, **kw): + if not PYOGG_OGG_AVAIL: + raise PyOggError("The Ogg library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + raise PyOggError("The Vorbis libraries weren't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + + class VorbisFileStream: # type: ignore + def __init__(*args, **kw): + if not PYOGG_OGG_AVAIL: + raise PyOggError("The Ogg library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + raise PyOggError("The Vorbis libraries weren't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + + + +if (PYOGG_OGG_AVAIL and PYOGG_OPUS_AVAIL and PYOGG_OPUS_FILE_AVAIL): + # OpusFile + from .opus_file import OpusFile + # OpusFileStream + from .opus_file_stream import OpusFileStream + +else: + class OpusFile: # type: ignore + def __init__(*args, **kw): + if not PYOGG_OGG_AVAIL: + raise PyOggError("The Ogg library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + if not PYOGG_OPUS_AVAIL: + raise PyOggError("The Opus library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + if not PYOGG_OPUS_FILE_AVAIL: + raise PyOggError("The OpusFile library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + raise PyOggError("Unknown initialisation error") + + class OpusFileStream: # type: ignore + def __init__(*args, **kw): + if not PYOGG_OGG_AVAIL: + raise PyOggError("The Ogg library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + if not PYOGG_OPUS_AVAIL: + raise PyOggError("The Opus library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + if not PYOGG_OPUS_FILE_AVAIL: + raise PyOggError("The OpusFile library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + raise PyOggError("Unknown initialisation error") + + +if PYOGG_OPUS_AVAIL: + # OpusEncoder + from .opus_encoder import OpusEncoder + # OpusBufferedEncoder + from .opus_buffered_encoder import OpusBufferedEncoder + # OpusDecoder + from .opus_decoder import OpusDecoder + +else: + class OpusEncoder: # type: ignore + def __init__(*args, **kw): + raise PyOggError("The Opus library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + + class OpusBufferedEncoder: # type: ignore + def __init__(*args, **kw): + raise PyOggError("The Opus library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + + class OpusDecoder: # type: ignore + def __init__(*args, **kw): + raise PyOggError("The Opus library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + +if (PYOGG_OGG_AVAIL and PYOGG_OPUS_AVAIL): + # OggOpusWriter + from .ogg_opus_writer import OggOpusWriter + +else: + class OggOpusWriter: # type: ignore + def __init__(*args, **kw): + if not PYOGG_OGG_AVAIL: + raise PyOggError("The Ogg library wasn't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + raise PyOggError("The Opus library was't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + + +if PYOGG_FLAC_AVAIL: + # FlacFile + from .flac_file import FlacFile + # FlacFileStream + from .flac_file_stream import FlacFileStream +else: + class FlacFile: # type: ignore + def __init__(*args, **kw): + raise PyOggError("The FLAC libraries weren't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") + + class FlacFileStream: # type: ignore + def __init__(*args, **kw): + raise PyOggError("The FLAC libraries weren't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)") diff --git a/sbapp/pyogg/audio_file.py b/sbapp/pyogg/audio_file.py new file mode 100644 index 0000000..4fb77a2 --- /dev/null +++ b/sbapp/pyogg/audio_file.py @@ -0,0 +1,59 @@ +from .pyogg_error import PyOggError + +class AudioFile: + """Abstract base class for audio files. + + This class is a base class for audio files (such as Vorbis, Opus, + and FLAC). It should not be instatiated directly. + """ + + def __init__(self): + raise PyOggError("AudioFile is an Abstract Base Class "+ + "and should not be instantiated") + + def as_array(self): + """Returns the buffer as a NumPy array. + + The shape of the returned array is in units of (number of + samples per channel, number of channels). + + The data type is either 8-bit or 16-bit signed integers, + depending on bytes_per_sample. + + The buffer is not copied, but rather the NumPy array + shares the memory with the buffer. + + """ + # Assumes that self.buffer is a one-dimensional array of + # bytes and that channels are interleaved. + + import numpy # type: ignore + + assert self.buffer is not None + assert self.channels is not None + + # The following code assumes that the bytes in the buffer + # represent 8-bit or 16-bit signed ints. Ensure the number of + # bytes per sample matches that assumption. + assert self.bytes_per_sample == 1 or self.bytes_per_sample == 2 + + # Create a dictionary mapping bytes per sample to numpy data + # types + dtype = { + 1: numpy.int8, + 2: numpy.int16 + } + + # Convert the ctypes buffer to a NumPy array + array = numpy.frombuffer( + self.buffer, + dtype=dtype[self.bytes_per_sample] + ) + + # Reshape the array + return array.reshape( + (len(self.buffer) + // self.bytes_per_sample + // self.channels, + self.channels) + ) diff --git a/sbapp/pyogg/flac.py b/sbapp/pyogg/flac.py new file mode 100644 index 0000000..d44509e --- /dev/null +++ b/sbapp/pyogg/flac.py @@ -0,0 +1,2061 @@ +############################################################ +# Flac license: # +############################################################ +""" +Copyright (C) 2000-2009 Josh Coalson +Copyright (C) 2011-2016 Xiph.Org Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import ctypes +from ctypes import c_int, c_int8, c_int16, c_int32, c_int64, c_uint, c_uint8, c_uint16, c_uint32, c_uint64, c_float, c_long, c_ulong, c_char, c_bool, c_char_p, c_ubyte, c_longlong, c_ulonglong, c_size_t, c_void_p, c_double, POINTER, pointer, cast, CFUNCTYPE, Structure, Union +import ctypes.util +import sys +from traceback import print_exc as _print_exc +import os + +from .ogg import * + +from .library_loader import ExternalLibrary, ExternalLibraryError + +__here = os.getcwd() + +libflac = None + +try: + names = { + "Windows": "libFLAC.dll", + "Darwin": "libFLAC.8.dylib", + "external": "FLAC" + } + libflac = Library.load(names, tests = [lambda lib: hasattr(lib, "FLAC__EntropyCodingMethodTypeString")]) +except ExternalLibraryError: + pass +except: + _print_exc() + +if libflac: + PYOGG_FLAC_AVAIL = True +else: + PYOGG_FLAC_AVAIL = False + +# ctypes +c_ubyte_p = POINTER(c_ubyte) +c_uchar_p = c_ubyte_p +c_uint_p = POINTER(c_uint) +c_size_t_p = POINTER(c_size_t) +c_off_t = c_int32 +# /ctypes + +if PYOGG_FLAC_AVAIL: + # Sanity check also satisfies mypy type checking + assert libflac is not None + + # ordinals + + FLAC__int8 = c_int8 + FLAC__uint8 = c_uint8 + + FLAC__int16 = c_int16 + + FLAC__int32 = c_int32 + FLAC__int32_p = POINTER(FLAC__int32) + + FLAC__int64 = c_int64 + FLAC__uint16 = c_uint16 + FLAC__uint32 = c_uint32 + FLAC__uint64 = c_uint64 + + FLAC__uint64_p = POINTER(FLAC__uint64) + + FLAC__bool = c_bool + + FLAC__byte = c_uint8 + + FLAC__byte_p = POINTER(FLAC__byte) + + c_char_p_p = POINTER(c_char_p) + + # /ordinals + + # callback + + FLAC__IOHandle = CFUNCTYPE(c_void_p) + + FLAC__IOCallback_Read = CFUNCTYPE(c_size_t, + c_void_p, + c_size_t, + c_size_t, + FLAC__IOHandle) + + FLAC__IOCallback_Write = CFUNCTYPE(c_size_t, c_void_p, c_size_t, c_size_t, FLAC__IOHandle) + + FLAC__IOCallback_Seek = CFUNCTYPE(c_int, FLAC__IOHandle, FLAC__int64, c_int) + + FLAC__IOCallback_Tell = CFUNCTYPE(FLAC__int64, FLAC__IOHandle) + + FLAC__IOCallback_Eof = CFUNCTYPE(c_int, FLAC__IOHandle) + + FLAC__IOCallback_Close = CFUNCTYPE(c_int, FLAC__IOHandle) + + class FLAC__IOCallbacks(Structure): + _fields_ = [("read", FLAC__IOCallback_Read), + ("write", FLAC__IOCallback_Write), + ("seek", FLAC__IOCallback_Seek), + ("tell", FLAC__IOCallback_Tell), + ("eof", FLAC__IOCallback_Eof), + ("close", FLAC__IOCallback_Close)] + + # /callback + + # format + + FLAC__MAX_METADATA_TYPE_CODE =(126) + FLAC__MIN_BLOCK_SIZE =(16) + FLAC__MAX_BLOCK_SIZE =(65535) + FLAC__SUBSET_MAX_BLOCK_SIZE_48000HZ =(4608) + FLAC__MAX_CHANNELS =(8) + FLAC__MIN_BITS_PER_SAMPLE =(4) + FLAC__MAX_BITS_PER_SAMPLE =(32) + FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE =(24) + FLAC__MAX_SAMPLE_RATE =(655350) + FLAC__MAX_LPC_ORDER =(32) + FLAC__SUBSET_MAX_LPC_ORDER_48000HZ =(12) + FLAC__MIN_QLP_COEFF_PRECISION =(5) + FLAC__MAX_QLP_COEFF_PRECISION =(15) + FLAC__MAX_FIXED_ORDER =(4) + FLAC__MAX_RICE_PARTITION_ORDER =(15) + FLAC__SUBSET_MAX_RICE_PARTITION_ORDER =(8) + + FLAC__VERSION_STRING = c_char_p.in_dll(libflac, "FLAC__VERSION_STRING") + + FLAC__VENDOR_STRING = c_char_p.in_dll(libflac, "FLAC__VENDOR_STRING") + + FLAC__STREAM_SYNC_STRING = (FLAC__byte * 4).in_dll(libflac, "FLAC__STREAM_SYNC_STRING") + + FLAC__STREAM_SYNC = c_uint.in_dll(libflac, "FLAC__STREAM_SYNC") + + FLAC__STREAM_SYNC_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_SYNC_LEN") + + FLAC__STREAM_SYNC_LENGTH =(4) + + + + FLAC__EntropyCodingMethodType = c_int + + FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE = 0 + + FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2 = 1 + + + + libflac.FLAC__EntropyCodingMethodTypeString.restype = c_char_p + libflac.FLAC__EntropyCodingMethodTypeString.argtypes = [] + + def FLAC__EntropyCodingMethodTypeString(): + return libflac.FLAC__EntropyCodingMethodTypeString() + + + + class FLAC__EntropyCodingMethod_PartitionedRiceContents(Structure): + _fields_ = [("parameters", c_uint_p), + ("raw_bits", c_uint_p), + ("capacity_by_order", c_uint)] + + class FLAC__EntropyCodingMethod_PartitionedRice(Structure): + _fields_ = [("order", c_uint), + ("contents", POINTER(FLAC__EntropyCodingMethod_PartitionedRiceContents))] + + + FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ORDER_LEN = c_uint.in_dll(libflac, "FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ORDER_LEN") + + FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_PARAMETER_LEN = c_uint.in_dll(libflac, "FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_PARAMETER_LEN") + + FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_PARAMETER_LEN = c_uint.in_dll(libflac, "FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE2_PARAMETER_LEN") + + FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_RAW_LEN = c_uint.in_dll(libflac, "FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_RAW_LEN") + + FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ESCAPE_PARAMETER = c_uint.in_dll(libflac, "FLAC__ENTROPY_CODING_METHOD_PARTITIONED_RICE_ESCAPE_PARAMETER") + + + class FLAC__EntropyCodingMethod_data(Union): + _fields_ = [("partitioned_rice", FLAC__EntropyCodingMethod_PartitionedRice)] + + class FLAC__EntropyCodingMethod(Structure): + _fields_ = [("type", POINTER(FLAC__EntropyCodingMethodType)), + ("data", FLAC__EntropyCodingMethod_data)] + + FLAC__ENTROPY_CODING_METHOD_TYPE_LEN = c_uint.in_dll(libflac, "FLAC__ENTROPY_CODING_METHOD_TYPE_LEN") + + + + FLAC__SubframeType = c_int + FLAC__SUBFRAME_TYPE_CONSTANT = 0 + FLAC__SUBFRAME_TYPE_VERBATIM = 1 + FLAC__SUBFRAME_TYPE_FIXED = 2 + FLAC__SUBFRAME_TYPE_LPC = 3 + + + + libflac.FLAC__SubframeTypeString.restype = c_char_p + libflac.FLAC__SubframeTypeString.argtypes = [] + + def FLAC__SubframeTypeString(): + return libflac.FLAC__SubframeTypeString() + + + + class FLAC__Subframe_Constant(Structure): + _fields_ = [("value", FLAC__int32)] + + + class FLAC__Subframe_Verbatim(Structure): + _fields_ = [("data", FLAC__int32_p)] + + + class FLAC__Subframe_Fixed(Structure): + _fields_ = [("entropy_coding_method", FLAC__EntropyCodingMethod), + ("order", c_uint), + ("warmup", FLAC__int32 * FLAC__MAX_FIXED_ORDER), + ("residual", FLAC__int32_p)] + + + class FLAC__Subframe_LPC(Structure): + _fields_ = [("entropy_coding_method", FLAC__EntropyCodingMethod), + ("order", c_uint), + ("qlp_coeff_precision", c_uint), + ("quantization_level", c_int), + ("qlp_coeff", FLAC__int32 * FLAC__MAX_LPC_ORDER), + ("warmup", FLAC__int32 * FLAC__MAX_LPC_ORDER), + ("residual", FLAC__int32_p)] + + + FLAC__SUBFRAME_LPC_QLP_COEFF_PRECISION_LEN = c_uint.in_dll(libflac, "FLAC__SUBFRAME_LPC_QLP_COEFF_PRECISION_LEN") + + FLAC__SUBFRAME_LPC_QLP_SHIFT_LEN = c_uint.in_dll(libflac, "FLAC__SUBFRAME_LPC_QLP_SHIFT_LEN") + + + + class FLAC__Subframe_data(Union): + _fields_ = [("constant", FLAC__Subframe_Constant), + ("fixed", FLAC__Subframe_Fixed), + ("lpc", FLAC__Subframe_LPC), + ("verbatim", FLAC__Subframe_Verbatim)] + + class FLAC__Subframe(Structure): + _fields_ = [("type", FLAC__SubframeType), + ("data", FLAC__Subframe_data), + ("wasted_bits", c_uint)] + + + FLAC__SUBFRAME_ZERO_PAD_LEN = c_uint.in_dll(libflac, "FLAC__SUBFRAME_ZERO_PAD_LEN") + + FLAC__SUBFRAME_TYPE_LEN = c_uint.in_dll(libflac, "FLAC__SUBFRAME_TYPE_LEN") + + FLAC__SUBFRAME_WASTED_BITS_FLAG_LEN = c_uint.in_dll(libflac, "FLAC__SUBFRAME_WASTED_BITS_FLAG_LEN") + + FLAC__SUBFRAME_TYPE_CONSTANT_BYTE_ALIGNED_MASK = c_uint.in_dll(libflac, "FLAC__SUBFRAME_TYPE_CONSTANT_BYTE_ALIGNED_MASK") + + FLAC__SUBFRAME_TYPE_VERBATIM_BYTE_ALIGNED_MASK = c_uint.in_dll(libflac, "FLAC__SUBFRAME_TYPE_VERBATIM_BYTE_ALIGNED_MASK") + + FLAC__SUBFRAME_TYPE_FIXED_BYTE_ALIGNED_MASK = c_uint.in_dll(libflac, "FLAC__SUBFRAME_TYPE_FIXED_BYTE_ALIGNED_MASK") + + FLAC__SUBFRAME_TYPE_LPC_BYTE_ALIGNED_MASK = c_uint.in_dll(libflac, "FLAC__SUBFRAME_TYPE_LPC_BYTE_ALIGNED_MASK") + + + FLAC__ChannelAssignment = c_int + + FLAC__CHANNEL_ASSIGNMENT_INDEPENDENT = 0 + FLAC__CHANNEL_ASSIGNMENT_LEFT_SIDE = 1 + FLAC__CHANNEL_ASSIGNMENT_RIGHT_SIDE = 2 + FLAC__CHANNEL_ASSIGNMENT_MID_SIDE = 3 + + + + libflac.FLAC__ChannelAssignmentString.restype = c_char_p + libflac.FLAC__ChannelAssignmentString.argtypes = [] + + def FLAC__ChannelAssignmentString(): + return libflac.FLAC__ChannelAssignmentString() + + FLAC__FrameNumberType = c_int + + + libflac.FLAC__FrameNumberTypeString.restype = c_char_p + libflac.FLAC__FrameNumberTypeString.argtypes = [] + + def FLAC__FrameNumberTypeString(): + return libflac.FLAC__FrameNumberTypeString() + + + class FLAC__FrameHeader_number(Union): + _fields_ =[("frame_number", FLAC__uint32), + ("sample_number", FLAC__uint64)] + + class FLAC__FrameHeader(Structure): + _fields_ = [("blocksize", c_uint), + ("sample_rate", c_uint), + ("channels", c_uint), + ("channel_assignment", FLAC__ChannelAssignment), + ("bits_per_sample", c_uint), + ("number_type", FLAC__FrameNumberType), + ("number", FLAC__FrameHeader_number), + ("crc", FLAC__uint8)] + + + FLAC__FRAME_HEADER_SYNC = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_SYNC") + + FLAC__FRAME_HEADER_RESERVED_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_RESERVED_LEN") + + FLAC__FRAME_HEADER_BLOCKING_STRATEGY_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_BLOCKING_STRATEGY_LEN") + + FLAC__FRAME_HEADER_BLOCK_SIZE_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_BLOCK_SIZE_LEN") + + FLAC__FRAME_HEADER_SAMPLE_RATE_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_SAMPLE_RATE_LEN") + + FLAC__FRAME_HEADER_CHANNEL_ASSIGNMENT_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_CHANNEL_ASSIGNMENT_LEN") + + FLAC__FRAME_HEADER_BITS_PER_SAMPLE_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_BITS_PER_SAMPLE_LEN") + + FLAC__FRAME_HEADER_ZERO_PAD_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_ZERO_PAD_LEN") + + FLAC__FRAME_HEADER_CRC_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_HEADER_CRC_LEN") + + + + class FLAC__FrameFooter(Structure): + _fields_ = [("crc", FLAC__uint16)] + + FLAC__FRAME_FOOTER_CRC_LEN = c_uint.in_dll(libflac, "FLAC__FRAME_FOOTER_CRC_LEN") + + + + class FLAC__Frame(Structure): + _fields_ = [("header", FLAC__FrameHeader), + ("subframes", FLAC__Subframe * FLAC__MAX_CHANNELS), + ("footer", FLAC__FrameFooter)] + + + FLAC__MetadataType = c_int + + FLAC__METADATA_TYPE_STREAMINFO = 0 + + FLAC__METADATA_TYPE_PADDING = 1 + + FLAC__METADATA_TYPE_APPLICATION = 2 + + FLAC__METADATA_TYPE_SEEKTABLE = 3 + + FLAC__METADATA_TYPE_VORBIS_COMMENT = 4 + + FLAC__METADATA_TYPE_CUESHEET = 5 + + FLAC__METADATA_TYPE_PICTURE = 6 + + FLAC__METADATA_TYPE_UNDEFINED = 7 + + FLAC__MAX_METADATA_TYPE = FLAC__MAX_METADATA_TYPE_CODE + + + + libflac.FLAC__MetadataTypeString.restype = c_char_p + libflac.FLAC__MetadataTypeString.argtypes = [] + + def FLAC__MetadataTypeString(): + return libflac.FLAC__MetadataTypeString() + + + + class FLAC__StreamMetadata_StreamInfo(Structure): + _fields_ = [("min_blocksize", c_uint), + ("max_framesize", c_uint), + ("min_framesize", c_uint), + ("max_framesize", c_uint), + ("sample_rate", c_uint), + ("channels", c_uint), + ("bits_per_sample", c_uint), + ("total_samples", FLAC__uint64), + ("md5sum", FLAC__byte*16)] + + FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN") + + + FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN") + + FLAC__STREAM_METADATA_STREAMINFO_LENGTH =(34) + + + class FLAC__StreamMetadata_Padding(Structure): + _fields_ = [("dummy", c_int)] + + + + class FLAC__StreamMetadata_Application(Structure): + _fields_ = [("id", FLAC__byte*4), + ("data", FLAC__byte_p)] + + FLAC__STREAM_METADATA_APPLICATION_ID_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_APPLICATION_ID_LEN") + + + class FLAC__StreamMetadata_SeekPoint(Structure): + _fields_ = [("sample_number", FLAC__uint64), + ("stream_offset", FLAC__uint64), + ("frame_samples", c_uint)] + + FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN") + + FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN") + + FLAC__STREAM_METADATA_SEEKPOINT_FRAME_SAMPLES_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_SEEKPOINT_FRAME_SAMPLES_LEN") + + FLAC__STREAM_METADATA_SEEKPOINT_LENGTH =(18) + + + FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER = FLAC__uint64.in_dll(libflac, "FLAC__STREAM_METADATA_SEEKPOINT_PLACEHOLDER") + + class FLAC__StreamMetadata_SeekTable(Structure): + _fields_ = [("num_points", c_uint), + ("points", POINTER(FLAC__StreamMetadata_SeekPoint))] + + class FLAC__StreamMetadata_VorbisComment_Entry(Structure): + _fields_ = [("length", FLAC__uint32), + ("entry", FLAC__byte_p)] + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN") + + + class FLAC__StreamMetadata_VorbisComment(Structure): + _fields_ = [("vendor_string", FLAC__StreamMetadata_VorbisComment_Entry), + ("num_comments", FLAC__uint32), + ("comments", POINTER(FLAC__StreamMetadata_VorbisComment_Entry))] + + FLAC__STREAM_METADATA_VORBIS_COMMENT_NUM_COMMENTS_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_VORBIS_COMMENT_NUM_COMMENTS_LEN") + + + class FLAC__StreamMetadata_CueSheet_Index(Structure): + _fields_ = [("offset", FLAC__uint64), + ("number", FLAC__byte)] + + + FLAC__STREAM_METADATA_CUESHEET_INDEX_OFFSET_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_INDEX_OFFSET_LEN") + + FLAC__STREAM_METADATA_CUESHEET_INDEX_NUMBER_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_INDEX_NUMBER_LEN") + + FLAC__STREAM_METADATA_CUESHEET_INDEX_RESERVED_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_INDEX_RESERVED_LEN") + + + class FLAC__StreamMetadata_CueSheet_Track(Structure): + _fields_ = [("offset", FLAC__uint64), + ("number", FLAC__byte), + ("isrc", c_char*13), + ("type", c_uint), + ("pre_emphasis", c_uint), + ("num_indices", FLAC__byte), + ("indices", POINTER(FLAC__StreamMetadata_CueSheet_Index))] + + FLAC__STREAM_METADATA_CUESHEET_TRACK_OFFSET_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_TRACK_OFFSET_LEN") + + FLAC__STREAM_METADATA_CUESHEET_TRACK_NUMBER_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_TRACK_NUMBER_LEN") + + FLAC__STREAM_METADATA_CUESHEET_TRACK_ISRC_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_TRACK_ISRC_LEN") + + FLAC__STREAM_METADATA_CUESHEET_TRACK_TYPE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_TRACK_TYPE_LEN") + + FLAC__STREAM_METADATA_CUESHEET_TRACK_PRE_EMPHASIS_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_TRACK_PRE_EMPHASIS_LEN") + + FLAC__STREAM_METADATA_CUESHEET_TRACK_RESERVED_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_TRACK_RESERVED_LEN") + + FLAC__STREAM_METADATA_CUESHEET_TRACK_NUM_INDICES_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_TRACK_NUM_INDICES_LEN") + + + class FLAC__StreamMetadata_CueSheet(Structure): + _fields_ = [("media_catalog_number", c_char*129), + ("lead_in", FLAC__uint64), + ("is_cd", FLAC__bool), + ("num_tracks", c_uint), + ("tracks", POINTER(FLAC__StreamMetadata_CueSheet_Track))] + + FLAC__STREAM_METADATA_CUESHEET_MEDIA_CATALOG_NUMBER_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_MEDIA_CATALOG_NUMBER_LEN") + + + FLAC__STREAM_METADATA_CUESHEET_LEAD_IN_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_LEAD_IN_LEN") + + FLAC__STREAM_METADATA_CUESHEET_IS_CD_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_IS_CD_LEN") + + FLAC__STREAM_METADATA_CUESHEET_RESERVED_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_RESERVED_LEN") + + FLAC__STREAM_METADATA_CUESHEET_NUM_TRACKS_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_CUESHEET_NUM_TRACKS_LEN") + + + FLAC__StreamMetadata_Picture_Type = c_int + FLAC__STREAM_METADATA_PICTURE_TYPE_OTHER = 0 + FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD = 1 + FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON = 2 + FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER = 3 + FLAC__STREAM_METADATA_PICTURE_TYPE_BACK_COVER = 4 + FLAC__STREAM_METADATA_PICTURE_TYPE_LEAFLET_PAGE = 5 + FLAC__STREAM_METADATA_PICTURE_TYPE_MEDIA = 6 + FLAC__STREAM_METADATA_PICTURE_TYPE_LEAD_ARTIST = 7 + FLAC__STREAM_METADATA_PICTURE_TYPE_ARTIST = 8 + FLAC__STREAM_METADATA_PICTURE_TYPE_CONDUCTOR = 9 + FLAC__STREAM_METADATA_PICTURE_TYPE_BAND = 10 + FLAC__STREAM_METADATA_PICTURE_TYPE_COMPOSER = 11 + FLAC__STREAM_METADATA_PICTURE_TYPE_LYRICIST = 12 + FLAC__STREAM_METADATA_PICTURE_TYPE_RECORDING_LOCATION = 13 + FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_RECORDING = 14 + FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_PERFORMANCE = 15 + FLAC__STREAM_METADATA_PICTURE_TYPE_VIDEO_SCREEN_CAPTURE = 16 + FLAC__STREAM_METADATA_PICTURE_TYPE_FISH = 17 + FLAC__STREAM_METADATA_PICTURE_TYPE_ILLUSTRATION = 18 + FLAC__STREAM_METADATA_PICTURE_TYPE_BAND_LOGOTYPE = 19 + FLAC__STREAM_METADATA_PICTURE_TYPE_PUBLISHER_LOGOTYPE = 20 + + + libflac.FLAC__StreamMetadata_Picture_TypeString.restype = c_char_p + libflac.FLAC__StreamMetadata_Picture_TypeString.argtypes = [] + + def FLAC__StreamMetadata_Picture_TypeString(): + return libflac.FLAC__StreamMetadata_Picture_TypeString() + + + class FLAC__StreamMetadata_Picture(Structure): + _fields_ = [("type", FLAC__StreamMetadata_Picture_Type), + ("mime_type", c_char_p), + ("description", FLAC__byte_p), + ("width", FLAC__uint32), + ("height", FLAC__uint32), + ("depth", FLAC__uint32), + ("colors", FLAC__uint32), + ("data_length", FLAC__uint32), + ("data", FLAC__byte)] + + FLAC__STREAM_METADATA_PICTURE_TYPE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_TYPE_LEN") + + FLAC__STREAM_METADATA_PICTURE_MIME_TYPE_LENGTH_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_MIME_TYPE_LENGTH_LEN") + + FLAC__STREAM_METADATA_PICTURE_DESCRIPTION_LENGTH_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_DESCRIPTION_LENGTH_LEN") + + FLAC__STREAM_METADATA_PICTURE_WIDTH_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_WIDTH_LEN") + + FLAC__STREAM_METADATA_PICTURE_HEIGHT_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_HEIGHT_LEN") + + + FLAC__STREAM_METADATA_PICTURE_DEPTH_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_DEPTH_LEN") + + FLAC__STREAM_METADATA_PICTURE_COLORS_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_COLORS_LEN") + + FLAC__STREAM_METADATA_PICTURE_DATA_LENGTH_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_PICTURE_DATA_LENGTH_LEN") + + + class FLAC__StreamMetadata_Unknown(Structure): + _fields_ = [("data", FLAC__byte_p)] + + + class FLAC__StreamMetadata_data(Union): + _fields_ = [("stream_info", FLAC__StreamMetadata_StreamInfo), + ("padding", FLAC__StreamMetadata_Padding), + ("application", FLAC__StreamMetadata_Application), + ("seek_table", FLAC__StreamMetadata_SeekTable), + ("vorbis_comment", FLAC__StreamMetadata_VorbisComment), + ("cue_sheet", FLAC__StreamMetadata_CueSheet), + ("picture", FLAC__StreamMetadata_Picture), + ("unknown", FLAC__StreamMetadata_Unknown)] + + class FLAC__StreamMetadata(Structure): + _fields_ = [("type", FLAC__MetadataType), + ("is_last", FLAC__bool), + ("length", c_uint), + ("data", FLAC__StreamMetadata_data)] + + FLAC__STREAM_METADATA_IS_LAST_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_IS_LAST_LEN") + + FLAC__STREAM_METADATA_TYPE_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_TYPE_LEN") + + FLAC__STREAM_METADATA_LENGTH_LEN = c_uint.in_dll(libflac, "FLAC__STREAM_METADATA_LENGTH_LEN") + + FLAC__STREAM_METADATA_HEADER_LENGTH =(4) + + + + libflac.FLAC__format_sample_rate_is_valid.restype = FLAC__bool + libflac.FLAC__format_sample_rate_is_valid.argtypes = [c_uint] + + def FLAC__format_sample_rate_is_valid(sample_rate): + return libflac.FLAC__format_sample_rate_is_valid(sample_rate) + + + libflac.FLAC__format_blocksize_is_subset.restype = FLAC__bool + libflac.FLAC__format_blocksize_is_subset.argtypes = [c_uint, c_uint] + + def FLAC__format_blocksize_is_subset(blocksize, sample_rate): + return libflac.FLAC__format_blocksize_is_subset(blocksize, sample_rate) + + + libflac.FLAC__format_sample_rate_is_subset.restype = FLAC__bool + libflac.FLAC__format_sample_rate_is_subset.argtypes = [c_uint] + + def FLAC__format_sample_rate_is_subset(sample_rate): + return libflac.FLAC__format_sample_rate_is_subset(sample_rate) + + + libflac.FLAC__format_vorbiscomment_entry_name_is_legal.restype = FLAC__bool + libflac.FLAC__format_vorbiscomment_entry_name_is_legal.argtypes = [c_char_p] + + def FLAC__format_vorbiscomment_entry_name_is_legal(name): + return libflac.FLAC__format_vorbiscomment_entry_name_is_legal(name) + + libflac.FLAC__format_vorbiscomment_entry_value_is_legal.restype = FLAC__bool + libflac.FLAC__format_vorbiscomment_entry_value_is_legal.argtypes = [FLAC__byte_p, c_uint] + + def FLAC__format_vorbiscomment_entry_value_is_legal(value, length): + return libflac.FLAC__format_vorbiscomment_entry_value_is_legal(value, length) + + libflac.FLAC__format_vorbiscomment_entry_is_legal.restype = FLAC__bool + libflac.FLAC__format_vorbiscomment_entry_is_legal.argtypes = [FLAC__byte_p, c_uint] + + def FLAC__format_vorbiscomment_entry_is_legal(entry, length): + return libflac.FLAC__format_vorbiscomment_entry_is_legal(entry, length) + + libflac.FLAC__format_seektable_is_legal.restype = FLAC__bool + libflac.FLAC__format_seektable_is_legal.argtypes = [POINTER(FLAC__StreamMetadata_SeekTable)] + + def FLAC__format_seektable_is_legal(seek_table): + return libflac.FLAC__format_seektable_is_legal(seek_table) + + + libflac.FLAC__format_seektable_sort.restype = FLAC__bool + libflac.FLAC__format_seektable_sort.argtypes = [POINTER(FLAC__StreamMetadata_SeekTable)] + + def FLAC__format_seektable_sort(seek_table): + return libflac.FLAC__format_seektable_sort(seek_table) + + libflac.FLAC__format_cuesheet_is_legal.restype = FLAC__bool + libflac.FLAC__format_cuesheet_is_legal.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet), FLAC__bool, c_char_p_p] + + def FLAC__format_cuesheet_is_legal(cue_sheet, check_cd_da_subset, violation): + return libflac.FLAC__format_cuesheet_is_legal(cue_sheet, check_cd_da_subset, violation) + + # /format + + # metadata + + libflac.FLAC__metadata_get_streaminfo.restype = FLAC__bool + libflac.FLAC__metadata_get_streaminfo.argtypes = [c_char_p, POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_get_streaminfo(filename, streaminfo): + return libflac.FLAC__metadata_get_streaminfo(filename, streaminfo) + + libflac.FLAC__metadata_get_tags.restype = FLAC__bool + libflac.FLAC__metadata_get_tags.argtypes = [c_char_p, POINTER(POINTER(FLAC__StreamMetadata))] + + def FLAC__metadata_get_tags(filename, tags): + return libflac.FLAC__metadata_get_tags(filename, tags) + + libflac.FLAC__metadata_get_cuesheet.restype = FLAC__bool + libflac.FLAC__metadata_get_cuesheet.argtypes = [c_char_p, POINTER(POINTER(FLAC__StreamMetadata))] + + def FLAC__metadata_get_cuesheet(filename, cuesheet): + return libflac.FLAC__metadata_get_cuesheet(filename, cuesheet) + + libflac.FLAC__metadata_get_picture.restype = FLAC__bool + libflac.FLAC__metadata_get_picture.argtypes = [c_char_p, POINTER(POINTER(FLAC__StreamMetadata)), FLAC__StreamMetadata_Picture_Type, c_char_p, FLAC__byte_p, c_uint, c_uint, c_uint, c_uint] + + def FLAC__metadata_get_picture(filename, picture, type, mime_type, description, max_width, max_height, max_depth, max_colors): + return libflac.FLAC__metadata_get_picture(filename, picture, type, mime_type, description, max_width, max_height, max_depth, max_colors) + + + class FLAC__Metadata_SimpleIterator(Structure): + _fields_ = [("dummy", c_int)] + + FLAC__Metadata_SimpleIteratorStatus = c_int + + FLAC__METADATA_SIMPLE_ITERATOR_STATUS_OK = 0 + + + libflac.FLAC__Metadata_SimpleIteratorStatusString.restype = c_char_p + libflac.FLAC__Metadata_SimpleIteratorStatusString.argtypes = [] + + def FLAC__Metadata_SimpleIteratorStatusString(): + return libflac.FLAC__Metadata_SimpleIteratorStatusString() + + + libflac.FLAC__metadata_simple_iterator_new.restype = POINTER(FLAC__Metadata_SimpleIterator) + libflac.FLAC__metadata_simple_iterator_new.argtypes = [] + + def FLAC__metadata_simple_iterator_new(): + return libflac.FLAC__metadata_simple_iterator_new() + + + libflac.FLAC__metadata_simple_iterator_delete.restype = None + libflac.FLAC__metadata_simple_iterator_delete.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_delete(iterator): + return libflac.FLAC__metadata_simple_iterator_delete(iterator) + + + libflac.FLAC__metadata_simple_iterator_status.restype = FLAC__Metadata_SimpleIteratorStatus + libflac.FLAC__metadata_simple_iterator_status.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_status(iterator): + return libflac.FLAC__metadata_simple_iterator_status(iterator) + + libflac.FLAC__metadata_simple_iterator_init.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_init.argtypes = [POINTER(FLAC__Metadata_SimpleIterator), c_char_p, FLAC__bool, FLAC__bool] + + def FLAC__metadata_simple_iterator_init(iterator, filename, read_only, preserve_file_stats): + return libflac.FLAC__metadata_simple_iterator_init(iterator, filename, read_only, preserve_file_stats) + + libflac.FLAC__metadata_simple_iterator_is_writable.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_is_writable.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_is_writable(iterator): + return libflac.FLAC__metadata_simple_iterator_is_writable(iterator) + + libflac.FLAC__metadata_simple_iterator_next.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_next.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_next(iterator): + return libflac.FLAC__metadata_simple_iterator_next(iterator) + + libflac.FLAC__metadata_simple_iterator_prev.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_prev.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_prev(iterator): + return libflac.FLAC__metadata_simple_iterator_prev(iterator) + + libflac.FLAC__metadata_simple_iterator_is_last.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_is_last.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_is_last(iterator): + return libflac.FLAC__metadata_simple_iterator_is_last(iterator) + + libflac.FLAC__metadata_simple_iterator_get_block_offset.restype = c_off_t + libflac.FLAC__metadata_simple_iterator_get_block_offset.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_get_block_offset(iterator): + return libflac.FLAC__metadata_simple_iterator_get_block_offset(iterator) + + libflac.FLAC__metadata_simple_iterator_get_block_type.restype = FLAC__MetadataType + libflac.FLAC__metadata_simple_iterator_get_block_type.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_get_block_type(iterator): + return libflac.FLAC__metadata_simple_iterator_get_block_type(iterator) + + libflac.FLAC__metadata_simple_iterator_get_block_length.restype = c_uint + libflac.FLAC__metadata_simple_iterator_get_block_length.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_get_block_length(iterator): + return libflac.FLAC__metadata_simple_iterator_get_block_length(iterator) + + libflac.FLAC__metadata_simple_iterator_get_application_id.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_get_application_id.argtypes = [POINTER(FLAC__Metadata_SimpleIterator), FLAC__byte_p] + + def FLAC__metadata_simple_iterator_get_application_id(iterator, id): + return libflac.FLAC__metadata_simple_iterator_get_application_id(iterator, id) + + libflac.FLAC__metadata_simple_iterator_get_block.restype = POINTER(FLAC__StreamMetadata) + libflac.FLAC__metadata_simple_iterator_get_block.argtypes = [POINTER(FLAC__Metadata_SimpleIterator)] + + def FLAC__metadata_simple_iterator_get_block(iterator): + return libflac.FLAC__metadata_simple_iterator_get_block(iterator) + + libflac.FLAC__metadata_simple_iterator_set_block.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_set_block.argtypes = [POINTER(FLAC__Metadata_SimpleIterator), POINTER(FLAC__StreamMetadata), FLAC__bool] + + def FLAC__metadata_simple_iterator_set_block(iterator, block, use_padding): + return libflac.FLAC__metadata_simple_iterator_set_block(iterator, block, use_padding) + + libflac.FLAC__metadata_simple_iterator_insert_block_after.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_insert_block_after.argtypes = [POINTER(FLAC__Metadata_SimpleIterator), POINTER(FLAC__StreamMetadata), FLAC__bool] + + def FLAC__metadata_simple_iterator_insert_block_after(iterator, block, use_padding): + return libflac.FLAC__metadata_simple_iterator_insert_block_after(iterator, block, use_padding) + + libflac.FLAC__metadata_simple_iterator_delete_block.restype = FLAC__bool + libflac.FLAC__metadata_simple_iterator_delete_block.argtypes = [POINTER(FLAC__Metadata_SimpleIterator), FLAC__bool] + + def FLAC__metadata_simple_iterator_delete_block(iterator, use_padding): + return libflac.FLAC__metadata_simple_iterator_delete_block(iterator, use_padding) + + class FLAC__Metadata_Chain(Structure): + _fields_ = [("dummy", c_int)] + + class FLAC__Metadata_Iterator(Structure): + _fields_ = [("dummy", c_int)] + + FLAC__Metadata_ChainStatus = c_int + + FLAC__METADATA_CHAIN_STATUS_OK = 0 + + libflac.FLAC__Metadata_ChainStatusString.restype = c_char_p + libflac.FLAC__Metadata_ChainStatusString.argtypes = [] + + def FLAC__Metadata_ChainStatusString(): + return libflac.FLAC__Metadata_ChainStatusString() + + libflac.FLAC__metadata_chain_new.restype = POINTER(FLAC__Metadata_Chain) + libflac.FLAC__metadata_chain_new.argtypes = [] + + def FLAC__metadata_chain_new(): + return libflac.FLAC__metadata_chain_new() + + libflac.FLAC__metadata_chain_delete.restype = None + libflac.FLAC__metadata_chain_delete.argtypes = [POINTER(FLAC__Metadata_Chain)] + + def FLAC__metadata_chain_delete(chain): + return libflac.FLAC__metadata_chain_delete(chain) + + libflac.FLAC__metadata_chain_status.restype = FLAC__Metadata_ChainStatus + libflac.FLAC__metadata_chain_status.argtypes = [POINTER(FLAC__Metadata_Chain)] + + def FLAC__metadata_chain_status(chain): + return libflac.FLAC__metadata_chain_status(chain) + + libflac.FLAC__metadata_chain_read.restype = FLAC__bool + libflac.FLAC__metadata_chain_read.argtypes = [POINTER(FLAC__Metadata_Chain), c_char_p] + + def FLAC__metadata_chain_read(chain, filename): + return libflac.FLAC__metadata_chain_read(chain, filename) + + libflac.FLAC__metadata_chain_read_ogg.restype = FLAC__bool + libflac.FLAC__metadata_chain_read_ogg.argtypes = [POINTER(FLAC__Metadata_Chain), c_char_p] + + def FLAC__metadata_chain_read_ogg(chain, filename): + return libflac.FLAC__metadata_chain_read_ogg(chain, filename) + + libflac.FLAC__metadata_chain_read_with_callbacks.restype = FLAC__bool + libflac.FLAC__metadata_chain_read_with_callbacks.argtypes = [POINTER(FLAC__Metadata_Chain), FLAC__IOHandle, FLAC__IOCallbacks] + + def FLAC__metadata_chain_read_with_callbacks(chain, handle, callbacks): + return libflac.FLAC__metadata_chain_read_with_callbacks(chain, handle, callbacks) + + libflac.FLAC__metadata_chain_read_ogg_with_callbacks.restype = FLAC__bool + libflac.FLAC__metadata_chain_read_ogg_with_callbacks.argtypes = [POINTER(FLAC__Metadata_Chain), FLAC__IOHandle, FLAC__IOCallbacks] + + def FLAC__metadata_chain_read_ogg_with_callbacks(chain, handle, callbacks): + return libflac.FLAC__metadata_chain_read_ogg_with_callbacks(chain, handle, callbacks) + + libflac.FLAC__metadata_chain_check_if_tempfile_needed.restype = FLAC__bool + libflac.FLAC__metadata_chain_check_if_tempfile_needed.argtypes = [POINTER(FLAC__Metadata_Chain), FLAC__bool] + + def FLAC__metadata_chain_check_if_tempfile_needed(chain, use_padding): + return libflac.FLAC__metadata_chain_check_if_tempfile_needed(chain, use_padding) + + libflac.FLAC__metadata_chain_write.restype = FLAC__bool + libflac.FLAC__metadata_chain_write.argtypes = [POINTER(FLAC__Metadata_Chain), FLAC__bool, FLAC__bool] + + def FLAC__metadata_chain_write(chain, use_padding, preserve_file_stats): + return libflac.FLAC__metadata_chain_write(chain, use_padding, preserve_file_stats) + + libflac.FLAC__metadata_chain_write_with_callbacks.restype = FLAC__bool + libflac.FLAC__metadata_chain_write_with_callbacks.argtypes = [POINTER(FLAC__Metadata_Chain), FLAC__bool, FLAC__IOHandle, FLAC__IOCallbacks] + + def FLAC__metadata_chain_write_with_callbacks(chain, use_padding, handle, callbacks): + return libflac.FLAC__metadata_chain_write_with_callbacks(chain, use_padding, handle, callbacks) + + libflac.FLAC__metadata_chain_write_with_callbacks_and_tempfile.restype = FLAC__bool + libflac.FLAC__metadata_chain_write_with_callbacks_and_tempfile.argtypes = [POINTER(FLAC__Metadata_Chain), FLAC__bool, FLAC__IOHandle, FLAC__IOCallbacks, FLAC__IOHandle, FLAC__IOCallbacks] + + def FLAC__metadata_chain_write_with_callbacks_and_tempfile(chain, use_padding, handle, callbacks, temp_handle, temp_callbacks): + return libflac.FLAC__metadata_chain_write_with_callbacks_and_tempfile(chain, use_padding, handle, callbacks, temp_handle, temp_callbacks) + + libflac.FLAC__metadata_chain_merge_padding.restype = None + libflac.FLAC__metadata_chain_merge_padding.argtypes = [POINTER(FLAC__Metadata_Chain)] + + def FLAC__metadata_chain_merge_padding(chain): + return libflac.FLAC__metadata_chain_merge_padding(chain) + + libflac.FLAC__metadata_chain_sort_padding.restype = None + libflac.FLAC__metadata_chain_sort_padding.argtypes = [POINTER(FLAC__Metadata_Chain)] + + def FLAC__metadata_chain_sort_padding(chain): + return libflac.FLAC__metadata_chain_sort_padding(chain) + + libflac.FLAC__metadata_iterator_new.restype = POINTER(FLAC__Metadata_Iterator) + libflac.FLAC__metadata_iterator_new.argtypes = [] + + def FLAC__metadata_iterator_new(): + return libflac.FLAC__metadata_iterator_new() + + libflac.FLAC__metadata_iterator_delete.restype = None + libflac.FLAC__metadata_iterator_delete.argtypes = [POINTER(FLAC__Metadata_Iterator)] + + def FLAC__metadata_iterator_delete(iterator): + return libflac.FLAC__metadata_iterator_delete(iterator) + + libflac.FLAC__metadata_iterator_init.restype = None + libflac.FLAC__metadata_iterator_init.argtypes = [POINTER(FLAC__Metadata_Iterator), POINTER(FLAC__Metadata_Chain)] + + def FLAC__metadata_iterator_init(iterator, chain): + return libflac.FLAC__metadata_iterator_init(iterator, chain) + + libflac.FLAC__metadata_iterator_next.restype = FLAC__bool + libflac.FLAC__metadata_iterator_next.argtypes = [POINTER(FLAC__Metadata_Iterator)] + + def FLAC__metadata_iterator_next(iterator): + return libflac.FLAC__metadata_iterator_next(iterator) + + libflac.FLAC__metadata_iterator_prev.restype = FLAC__bool + libflac.FLAC__metadata_iterator_prev.argtypes = [POINTER(FLAC__Metadata_Iterator)] + + def FLAC__metadata_iterator_prev(iterator): + return libflac.FLAC__metadata_iterator_prev(iterator) + + libflac.FLAC__metadata_iterator_get_block_type.restype = FLAC__MetadataType + libflac.FLAC__metadata_iterator_get_block_type.argtypes = [POINTER(FLAC__Metadata_Iterator)] + + def FLAC__metadata_iterator_get_block_type(iterator): + return libflac.FLAC__metadata_iterator_get_block_type(iterator) + + libflac.FLAC__metadata_iterator_get_block_type.restype = POINTER(FLAC__StreamMetadata) + libflac.FLAC__metadata_iterator_get_block_type.argtypes = [POINTER(FLAC__Metadata_Iterator)] + + def FLAC__metadata_iterator_get_block_type(iterator): + return libflac.FLAC__metadata_iterator_get_block_type(iterator) + + libflac.FLAC__metadata_iterator_set_block.restype = FLAC__bool + libflac.FLAC__metadata_iterator_set_block.argtypes = [POINTER(FLAC__Metadata_Iterator), POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_iterator_set_block(iterator, block): + return libflac.FLAC__metadata_iterator_set_block(iterator, block) + + libflac.FLAC__metadata_iterator_delete_block.restype = FLAC__bool + libflac.FLAC__metadata_iterator_delete_block.argtypes = [POINTER(FLAC__Metadata_Iterator), FLAC__bool] + + def FLAC__metadata_iterator_delete_block(iterator, replace_with_padding): + return libflac.FLAC__metadata_iterator_delete_block(iterator, replace_with_padding) + + libflac.FLAC__metadata_iterator_insert_block_before.restype = FLAC__bool + libflac.FLAC__metadata_iterator_insert_block_before.argtypes = [POINTER(FLAC__Metadata_Iterator), POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_iterator_insert_block_before(iterator, block): + return libflac.FLAC__metadata_iterator_insert_block_before(iterator, block) + + libflac.FLAC__metadata_iterator_insert_block_after.restype = FLAC__bool + libflac.FLAC__metadata_iterator_insert_block_after.argtypes = [POINTER(FLAC__Metadata_Iterator), POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_iterator_insert_block_after(iterator, block): + return libflac.FLAC__metadata_iterator_insert_block_after(iterator, block) + + libflac.FLAC__metadata_object_new.restype = POINTER(FLAC__StreamMetadata) + libflac.FLAC__metadata_object_new.argtypes = [POINTER(FLAC__MetadataType)] + + def FLAC__metadata_object_new(type): + return libflac.FLAC__metadata_object_new(type) + + libflac.FLAC__metadata_object_clone.restype = POINTER(FLAC__StreamMetadata) + libflac.FLAC__metadata_object_clone.argtypes = [POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_object_clone(object): + return libflac.FLAC__metadata_object_clone(object) + + libflac.FLAC__metadata_object_delete.restype = None + libflac.FLAC__metadata_object_delete.argtypes = [POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_object_delete(object): + return libflac.FLAC__metadata_object_delete(object) + + libflac.FLAC__metadata_object_is_equal.restype = FLAC__bool + libflac.FLAC__metadata_object_is_equal.argtypes = [POINTER(FLAC__StreamMetadata), POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_object_is_equal(block1, block2): + return libflac.FLAC__metadata_object_is_equal(block1, block2) + + libflac.FLAC__metadata_object_application_set_data.restype = FLAC__bool + libflac.FLAC__metadata_object_application_set_data.argtypes = [POINTER(FLAC__StreamMetadata), FLAC__byte_p, c_uint, FLAC__bool] + + def FLAC__metadata_object_application_set_data(object, data, length, copy): + return libflac.FLAC__metadata_object_application_set_data(object, data, length, copy) + + libflac.FLAC__metadata_object_seektable_resize_points.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_resize_points.argtypes = [POINTER(FLAC__StreamMetadata),c_uint] + + def FLAC__metadata_object_seektable_resize_points(object, new_num_points): + return libflac.FLAC__metadata_object_seektable_resize_points(object, new_num_points) + + libflac.FLAC__metadata_object_seektable_set_point.restype = None + libflac.FLAC__metadata_object_seektable_set_point.argtypes = [POINTER(FLAC__StreamMetadata),c_uint, FLAC__StreamMetadata_SeekPoint] + + def FLAC__metadata_object_seektable_set_point(object, point_num, point): + return libflac.FLAC__metadata_object_seektable_set_point(object, point_num, point) + + libflac.FLAC__metadata_object_seektable_insert_point.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_insert_point.argtypes = [POINTER(FLAC__StreamMetadata),c_uint, FLAC__StreamMetadata_SeekPoint] + + def FLAC__metadata_object_seektable_insert_point(object, point_num, point): + return libflac.FLAC__metadata_object_seektable_insert_point(object, point_num, point) + + libflac.FLAC__metadata_object_seektable_delete_point.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_delete_point.argtypes = [POINTER(FLAC__StreamMetadata),c_uint] + + def FLAC__metadata_object_seektable_delete_point(object, point_num): + return libflac.FLAC__metadata_object_seektable_delete_point(object, point_num) + + libflac.FLAC__metadata_object_seektable_is_legal.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_is_legal.argtypes = [POINTER(FLAC__StreamMetadata)] + + def FLAC__metadata_object_seektable_is_legal(object): + return libflac.FLAC__metadata_object_seektable_is_legal(object) + + libflac.FLAC__metadata_object_seektable_template_append_placeholders.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_template_append_placeholders.argtypes = [POINTER(FLAC__StreamMetadata), c_uint] + + def FLAC__metadata_object_seektable_template_append_placeholders(object, num): + return libflac.FLAC__metadata_object_seektable_template_append_placeholders(object, num) + + libflac.FLAC__metadata_object_seektable_template_append_point.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_template_append_point.argtypes = [POINTER(FLAC__StreamMetadata), FLAC__uint64] + + def FLAC__metadata_object_seektable_template_append_point(object, sample_number): + return libflac.FLAC__metadata_object_seektable_template_append_point(object, sample_number) + + libflac.FLAC__metadata_object_seektable_template_append_points.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_template_append_points.argtypes = [POINTER(FLAC__StreamMetadata), POINTER(FLAC__uint64*0), c_uint] + + def FLAC__metadata_object_seektable_template_append_points(object, sample_numbers, num): + return libflac.FLAC__metadata_object_seektable_template_append_points(object, sample_numbers, num) + + libflac.FLAC__metadata_object_seektable_template_append_spaced_points.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_template_append_spaced_points.argtypes = [POINTER(FLAC__StreamMetadata), c_uint, FLAC__uint64] + + def FLAC__metadata_object_seektable_template_append_spaced_points(object, num, total_samples): + return libflac.FLAC__metadata_object_seektable_template_append_spaced_points(object, num, total_samples) + + libflac.FLAC__metadata_object_seektable_template_append_spaced_points_by_samples.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_template_append_spaced_points_by_samples.argtypes = [POINTER(FLAC__StreamMetadata), c_uint, FLAC__uint64] + + def FLAC__metadata_object_seektable_template_append_spaced_points_by_samples(object, samples, total_samples): + return libflac.FLAC__metadata_object_seektable_template_append_spaced_points_by_samples(object, samples, total_samples) + + libflac.FLAC__metadata_object_seektable_template_sort.restype = FLAC__bool + libflac.FLAC__metadata_object_seektable_template_sort.argtypes = [POINTER(FLAC__StreamMetadata), FLAC__bool] + + def FLAC__metadata_object_seektable_template_sort(object, compact): + return libflac.FLAC__metadata_object_seektable_template_sort(object, compact) + + libflac.FLAC__metadata_object_vorbiscomment_set_vendor_string.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_set_vendor_string.argtypes = [POINTER(FLAC__StreamMetadata), FLAC__StreamMetadata_VorbisComment_Entry, FLAC__bool] + + def FLAC__metadata_object_vorbiscomment_set_vendor_string(object, entry, copy): + return libflac.FLAC__metadata_object_vorbiscomment_set_vendor_string(object, entry, copy) + + libflac.FLAC__metadata_object_vorbiscomment_resize_comments.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_resize_comments.argtypes = [POINTER(FLAC__StreamMetadata), c_uint] + + def FLAC__metadata_object_vorbiscomment_resize_comments(object, new_num_comments): + return libflac.FLAC__metadata_object_vorbiscomment_resize_comments(object, new_num_comments) + + libflac.FLAC__metadata_object_vorbiscomment_set_comment.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_set_comment.argtypes = [POINTER(FLAC__StreamMetadata), c_uint, FLAC__StreamMetadata_VorbisComment_Entry, FLAC__bool] + + def FLAC__metadata_object_vorbiscomment_set_comment(object, comment_num, entry, copy): + return libflac.FLAC__metadata_object_vorbiscomment_set_comment(object, comment_num, entry, copy) + + libflac.FLAC__metadata_object_vorbiscomment_insert_comment.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_insert_comment.argtypes = [POINTER(FLAC__StreamMetadata), c_uint, FLAC__StreamMetadata_VorbisComment_Entry, FLAC__bool] + + def FLAC__metadata_object_vorbiscomment_insert_comment(object, comment_num, entry, copy): + return libflac.FLAC__metadata_object_vorbiscomment_insert_comment(object, comment_num, entry, copy) + + libflac.FLAC__metadata_object_vorbiscomment_append_comment.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_append_comment.argtypes = [POINTER(FLAC__StreamMetadata), c_uint, FLAC__StreamMetadata_VorbisComment_Entry, FLAC__bool] + + def FLAC__metadata_object_vorbiscomment_append_comment(object, entry, copy): + return libflac.FLAC__metadata_object_vorbiscomment_append_comment(object,entry, copy) + + libflac.FLAC__metadata_object_vorbiscomment_replace_comment.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_replace_comment.argtypes = [POINTER(FLAC__StreamMetadata), c_uint, FLAC__StreamMetadata_VorbisComment_Entry, FLAC__bool, FLAC__bool] + + def FLAC__metadata_object_vorbiscomment_replace_comment(object, entry, all, copy): + return libflac.FLAC__metadata_object_vorbiscomment_replace_comment(object,entry, all, copy) + + libflac.FLAC__metadata_object_vorbiscomment_delete_comment.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_delete_comment.argtypes = [POINTER(FLAC__StreamMetadata), c_uint] + + def FLAC__metadata_object_vorbiscomment_delete_comment(object, comment_num): + return libflac.FLAC__metadata_object_vorbiscomment_delete_comment(object,comment_num) + + libflac.FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair.argtypes = [POINTER(FLAC__StreamMetadata_VorbisComment_Entry), c_char_p, c_char_p] + + def FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(entry, field_name, field_value): + return libflac.FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(entry, field_name, field_value) + + libflac.FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair.argtypes = [POINTER(FLAC__StreamMetadata_VorbisComment_Entry), c_char_p_p, c_char_p_p] + + def FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, field_name, field_value): + return libflac.FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, field_name, field_value) + + libflac.FLAC__metadata_object_vorbiscomment_entry_matches.restype = FLAC__bool + libflac.FLAC__metadata_object_vorbiscomment_entry_matches.argtypes = [POINTER(FLAC__StreamMetadata_VorbisComment_Entry), c_char_p, c_uint] + + def FLAC__metadata_object_vorbiscomment_entry_matches(entry, field_name, field_value): + return libflac.FLAC__metadata_object_vorbiscomment_entry_matches(entry, field_name, field_value) + + libflac.FLAC__metadata_object_vorbiscomment_find_entry_from.restype = c_int + libflac.FLAC__metadata_object_vorbiscomment_find_entry_from.argtypes = [POINTER(FLAC__StreamMetadata), c_uint, c_char_p] + + def FLAC__metadata_object_vorbiscomment_find_entry_from(object, offset, field_name): + return libflac.FLAC__metadata_object_vorbiscomment_find_entry_from(object, offset, field_name) + + libflac.FLAC__metadata_object_vorbiscomment_remove_entry_matching.restype = c_int + libflac.FLAC__metadata_object_vorbiscomment_remove_entry_matching.argtypes = [POINTER(FLAC__StreamMetadata), c_char_p] + + def FLAC__metadata_object_vorbiscomment_remove_entry_matching(object, field_name): + return libflac.FLAC__metadata_object_vorbiscomment_remove_entry_matching(object, field_name) + + libflac.FLAC__metadata_object_vorbiscomment_remove_entries_matching.restype = c_int + libflac.FLAC__metadata_object_vorbiscomment_remove_entries_matching.argtypes = [POINTER(FLAC__StreamMetadata), c_char_p] + + def FLAC__metadata_object_vorbiscomment_remove_entries_matching(object, field_name): + return libflac.FLAC__metadata_object_vorbiscomment_remove_entries_matching(object, field_name) + + libflac.FLAC__metadata_object_cuesheet_track_new.restype = POINTER(FLAC__StreamMetadata_CueSheet_Track) + libflac.FLAC__metadata_object_cuesheet_track_new.argtypes = [] + + def FLAC__metadata_object_cuesheet_track_new(): + return libflac.FLAC__metadata_object_cuesheet_track_new() + + libflac.FLAC__metadata_object_cuesheet_track_delete.restype = None + libflac.FLAC__metadata_object_cuesheet_track_delete.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track)] + + def FLAC__metadata_object_cuesheet_track_delete(object): + return libflac.FLAC__metadata_object_cuesheet_track_delete(object) + + libflac.FLAC__metadata_object_cuesheet_track_resize_indices.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_track_resize_indices.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint, c_uint] + + def FLAC__metadata_object_cuesheet_track_resize_indices(object, track_num, new_num_indices): + return libflac.FLAC__metadata_object_cuesheet_track_resize_indices(object, track_num, new_num_indices) + + libflac.FLAC__metadata_object_cuesheet_track_insert_index.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_track_insert_index.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint, c_uint, FLAC__StreamMetadata_CueSheet_Index] + + def FLAC__metadata_object_cuesheet_track_insert_index(object, track_num, index_num, index): + return libflac.FLAC__metadata_object_cuesheet_track_insert_index(object, track_num, index_num, index) + + libflac.FLAC__metadata_object_cuesheet_track_insert_blank_index.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_track_insert_blank_index.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint, c_uint] + + def FLAC__metadata_object_cuesheet_track_insert_blank_index(object, track_num, index_num): + return libflac.FLAC__metadata_object_cuesheet_track_insert_blank_index(object, track_num, index_num) + + libflac.FLAC__metadata_object_cuesheet_track_delete_index.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_track_delete_index.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint, c_uint] + + def FLAC__metadata_object_cuesheet_track_delete_index(object, track_num, index_num): + return libflac.FLAC__metadata_object_cuesheet_track_delete_index(object, track_num, index_num) + + libflac.FLAC__metadata_object_cuesheet_resize_tracks.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_resize_tracks.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint] + + def FLAC__metadata_object_cuesheet_resize_tracks(object, new_num_tracks): + return libflac.FLAC__metadata_object_cuesheet_resize_tracks(object, new_num_tracks) + + libflac.FLAC__metadata_object_cuesheet_set_track.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_set_track.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint, POINTER(FLAC__StreamMetadata_CueSheet_Track), FLAC__bool] + + def FLAC__metadata_object_cuesheet_set_track(object, new_num_tracks, track, copy): + return libflac.FLAC__metadata_object_cuesheet_set_track(object, new_num_tracks, track, copy) + + libflac.FLAC__metadata_object_cuesheet_insert_track.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_insert_track.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint, POINTER(FLAC__StreamMetadata_CueSheet_Track), FLAC__bool] + + def FLAC__metadata_object_cuesheet_insert_track(object, track_num, track, copy): + return libflac.FLAC__metadata_object_cuesheet_insert_track(object, track_num, track, copy) + + libflac.FLAC__metadata_object_cuesheet_insert_blank_track.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_insert_blank_track.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint] + + def FLAC__metadata_object_cuesheet_insert_blank_track(object, track_num): + return libflac.FLAC__metadata_object_cuesheet_insert_blank_track(object, track_num) + + libflac.FLAC__metadata_object_cuesheet_delete_track.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_delete_track.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_uint] + + def FLAC__metadata_object_cuesheet_delete_track(object, track_num): + return libflac.FLAC__metadata_object_cuesheet_delete_track(object, track_num) + + libflac.FLAC__metadata_object_cuesheet_is_legal.restype = FLAC__bool + libflac.FLAC__metadata_object_cuesheet_is_legal.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), FLAC__bool, c_char_p_p] + + def FLAC__metadata_object_cuesheet_is_legal(object, check_cd_da_subset, violation): + return libflac.FLAC__metadata_object_cuesheet_is_legal(object, check_cd_da_subset, violation) + + libflac.FLAC__metadata_object_cuesheet_calculate_cddb_id.restype = FLAC__uint32 + libflac.FLAC__metadata_object_cuesheet_calculate_cddb_id.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track)] + + def FLAC__metadata_object_cuesheet_calculate_cddb_id(object): + return libflac.FLAC__metadata_object_cuesheet_calculate_cddb_id(object) + + libflac.FLAC__metadata_object_picture_set_mime_type.restype = FLAC__bool + libflac.FLAC__metadata_object_picture_set_mime_type.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_char_p, FLAC__bool] + + def FLAC__metadata_object_picture_set_mime_type(object, mime_type, copy): + return libflac.FLAC__metadata_object_picture_set_mime_type(object, mime_type, copy) + + libflac.FLAC__metadata_object_picture_set_description.restype = FLAC__bool + libflac.FLAC__metadata_object_picture_set_description.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), FLAC__byte_p, FLAC__bool] + + def FLAC__metadata_object_picture_set_description(object, description, copy): + return libflac.FLAC__metadata_object_picture_set_description(object, mime_type, copy) + + libflac.FLAC__metadata_object_picture_set_data.restype = FLAC__bool + libflac.FLAC__metadata_object_picture_set_data.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), FLAC__byte_p,FLAC__uint32, FLAC__bool] + + def FLAC__metadata_object_picture_set_data(object, data, length, copy): + return libflac.FLAC__metadata_object_picture_set_data(object, mime_type, copy) + + libflac.FLAC__metadata_object_picture_is_legal.restype = FLAC__bool + libflac.FLAC__metadata_object_picture_is_legal.argtypes = [POINTER(FLAC__StreamMetadata_CueSheet_Track), c_char_p] + + def FLAC__metadata_object_picture_is_legal(object, violation): + return libflac.FLAC__metadata_object_picture_is_legal(object, violation) + + # /metadata + + # stream_decoder + + FLAC__StreamDecoderState = c_int + FLAC__StreamDecoderStateEnum = ["FLAC__STREAM_DECODER_SEARCH_FOR_METADATA", + "FLAC__STREAM_DECODER_READ_METADATA", + "FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC", + "FLAC__STREAM_DECODER_READ_FRAME", + "FLAC__STREAM_DECODER_END_OF_STREAM", + "FLAC__STREAM_DECODER_OGG_ERROR", + "FLAC__STREAM_DECODER_SEEK_ERROR", + "FLAC__STREAM_DECODER_ABORTED", + "FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR", + "FLAC__STREAM_DECODER_UNINITIALIZED"] + + libflac.FLAC__StreamDecoderStateString.restype = c_char_p + libflac.FLAC__StreamDecoderStateString.argtypes = [] + + def FLAC__StreamDecoderStateString(): + return libflac.FLAC__StreamDecoderStateString() + + + FLAC__StreamDecoderInitStatus = c_int + FLAC__StreamDecoderInitStatusEnum = ["FLAC__STREAM_DECODER_INIT_STATUS_OK", + "FLAC__STREAM_DECODER_INIT_STATUS_UNSUPPORTED_CONTAINER", + "FLAC__STREAM_DECODER_INIT_STATUS_INVALID_CALLBACKS", + "FLAC__STREAM_DECODER_INIT_STATUS_MEMORY_ALLOCATION_ERROR", + "FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE", + "FLAC__STREAM_DECODER_INIT_STATUS_ALREADY_INITIALIZED"] + + libflac.FLAC__StreamDecoderInitStatusString.restype = c_char_p + libflac.FLAC__StreamDecoderInitStatusString.argtypes = [] + + def FLAC__StreamDecoderInitStatusString(): + return libflac.FLAC__StreamDecoderInitStatusString() + + + FLAC__StreamDecoderReadStatus = c_int + FLAC__StreamDecoderReadStatusEnum = ["FLAC__STREAM_DECODER_READ_STATUS_CONTINUE", + "FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM", + "FLAC__STREAM_DECODER_READ_STATUS_ABORT"] + + libflac.FLAC__StreamDecoderReadStatusString.restype = c_char_p + libflac.FLAC__StreamDecoderReadStatusString.argtypes = [] + + def FLAC__StreamDecoderReadStatusString(): + return libflac.FLAC__StreamDecoderReadStatusString() + + + FLAC__StreamDecoderSeekStatus = c_int + FLAC__StreamDecoderSeekStatusEnum = ["FLAC__STREAM_DECODER_SEEK_STATUS_OK", + "FLAC__STREAM_DECODER_SEEK_STATUS_ERROR", + "FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED"] + + libflac.FLAC__StreamDecoderSeekStatusString.restype = c_char_p + libflac.FLAC__StreamDecoderSeekStatusString.argtypes = [] + + def FLAC__StreamDecoderSeekStatusString(): + return libflac.FLAC__StreamDecoderSeekStatusString() + + + FLAC__StreamDecoderTellStatus = c_int + FLAC__StreamDecoderTellStatusEnum = ["FLAC__STREAM_DECODER_TELL_STATUS_OK", + "FLAC__STREAM_DECODER_TELL_STATUS_ERROR", + "FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED"] + + libflac.FLAC__StreamDecoderTellStatusString.restype = c_char_p + libflac.FLAC__StreamDecoderTellStatusString.argtypes = [] + + def FLAC__StreamDecoderTellStatusString(): + return libflac.FLAC__StreamDecoderTellStatusString() + + + FLAC__StreamDecoderLengthStatus = c_int + FLAC__StreamDecoderLengthStatusEnum = ["FLAC__STREAM_DECODER_LENGTH_STATUS_OK", + "FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR", + "FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED"] + + libflac.FLAC__StreamDecoderLengthStatusString.restype = c_char_p + libflac.FLAC__StreamDecoderLengthStatusString.argtypes = [] + + def FLAC__StreamDecoderLengthStatusString(): + return libflac.FLAC__StreamDecoderLengthStatusString() + + + FLAC__StreamDecoderWriteStatus = c_int + FLAC__StreamDecoderWriteStatusEnum = ["FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE", "FLAC__STREAM_DECODER_WRITE_STATUS_ABORT"] + + libflac.FLAC__StreamDecoderWriteStatusString.restype = c_char_p + libflac.FLAC__StreamDecoderWriteStatusString.argtypes = [] + + def FLAC__StreamDecoderWriteStatusString(): + return libflac.FLAC__StreamDecoderWriteStatusString() + + FLAC__StreamDecoderErrorStatus = c_int + FLAC__StreamDecoderErrorStatusEnum = ["FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC", + "FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER", + "FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH", + "FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM"] + + libflac.FLAC__StreamDecoderErrorStatusString.restype = c_char_p + libflac.FLAC__StreamDecoderErrorStatusString.argtypes = [] + + def FLAC__StreamDecoderErrorStatusString(): + return libflac.FLAC__StreamDecoderErrorStatusString() + + + + class FLAC__StreamDecoderProtected(Structure): + _fields_ = [("dummy", c_int)] + + class FLAC__StreamDecoderPrivate(Structure): + _fields_ = [("dummy", c_int)] + + class FLAC__StreamDecoder(Structure): + _fields_ = [("protected_", POINTER(FLAC__StreamDecoderProtected)), + ("private_", POINTER(FLAC__StreamDecoderPrivate))] + + FLAC__StreamDecoderReadCallback = CFUNCTYPE( + FLAC__StreamDecoderReadStatus, + POINTER(FLAC__StreamDecoder), + POINTER(FLAC__byte*0), + c_size_t_p, + c_void_p + ) + + FLAC__StreamDecoderSeekCallback = CFUNCTYPE( + FLAC__StreamDecoderSeekStatus, + POINTER(FLAC__StreamDecoder), + FLAC__uint64, + c_void_p + ) + + FLAC__StreamDecoderTellCallback = CFUNCTYPE( + FLAC__StreamDecoderTellStatus, + POINTER(FLAC__StreamDecoder), + FLAC__uint64_p, + c_void_p + ) + + FLAC__StreamDecoderLengthCallback = CFUNCTYPE( + FLAC__StreamDecoderLengthStatus, + POINTER(FLAC__StreamDecoder), + FLAC__uint64_p, + c_void_p + ) + + FLAC__StreamDecoderEofCallback = CFUNCTYPE( + FLAC__bool, + POINTER(FLAC__StreamDecoder), + c_void_p + ) + + FLAC__StreamDecoderWriteCallback = CFUNCTYPE( + FLAC__StreamDecoderWriteStatus, + POINTER(FLAC__StreamDecoder), + POINTER(FLAC__Frame), + POINTER(FLAC__int32_p*0), + c_void_p + ) + + FLAC__StreamDecoderMetadataCallback = CFUNCTYPE( + None, + POINTER(FLAC__StreamDecoder), + POINTER(FLAC__StreamMetadata), + c_void_p + ) + + FLAC__StreamDecoderErrorCallback = CFUNCTYPE( + None, + POINTER(FLAC__StreamDecoder), + FLAC__StreamDecoderErrorStatus, + c_void_p + ) + + + libflac.FLAC__stream_decoder_new.restype = POINTER(FLAC__StreamDecoder) + libflac.FLAC__stream_decoder_new.argtypes = [] + + def FLAC__stream_decoder_new(): + return libflac.FLAC__stream_decoder_new() + + libflac.FLAC__stream_decoder_delete.restype = None + libflac.FLAC__stream_decoder_delete.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_delete(decoder): + return libflac.FLAC__stream_decoder_delete(decoder) + + + libflac.FLAC__stream_decoder_set_ogg_serial_number.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_ogg_serial_number.argtypes = [POINTER(FLAC__StreamDecoder), c_long] + + def FLAC__stream_decoder_set_ogg_serial_number(decoder, serial_number): + return libflac.FLAC__stream_decoder_set_ogg_serial_number(decoder, serial_number) + + libflac.FLAC__stream_decoder_set_md5_checking.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_md5_checking.argtypes = [POINTER(FLAC__StreamDecoder), FLAC__bool] + + def FLAC__stream_decoder_set_md5_checking(decoder, value): + return libflac.FLAC__stream_decoder_set_md5_checking(decoder, value) + + libflac.FLAC__stream_decoder_set_metadata_respond.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_metadata_respond.argtypes = [POINTER(FLAC__StreamDecoder), FLAC__MetadataType] + + def FLAC__stream_decoder_set_metadata_respond(decoder, type): + return libflac.FLAC__stream_decoder_set_metadata_respond(decoder, type) + + libflac.FLAC__stream_decoder_set_metadata_respond_application.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_metadata_respond_application.argtypes = [POINTER(FLAC__StreamDecoder), FLAC__byte*4] + + def FLAC__stream_decoder_set_metadata_respond_application(decoder, id): + return libflac.FLAC__stream_decoder_set_metadata_respond_application(decoder, id) + + libflac.FLAC__stream_decoder_set_metadata_respond_all.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_metadata_respond_all.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_set_metadata_respond_all(decoder): + return libflac.FLAC__stream_decoder_set_metadata_respond_all(decoder) + + libflac.FLAC__stream_decoder_set_metadata_ignore.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_metadata_ignore.argtypes = [POINTER(FLAC__StreamDecoder), FLAC__MetadataType] + + def FLAC__stream_decoder_set_metadata_ignore(decoder, type): + return libflac.FLAC__stream_decoder_set_metadata_ignore(decoder, type) + + libflac.FLAC__stream_decoder_set_metadata_ignore_application.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_metadata_ignore_application.argtypes = [POINTER(FLAC__StreamDecoder), FLAC__byte*4] + + def FLAC__stream_decoder_set_metadata_ignore_application(decoder, id): + return libflac.FLAC__stream_decoder_set_metadata_ignore_application(decoder, id) + + libflac.FLAC__stream_decoder_set_metadata_ignore_all.restype = FLAC__bool + libflac.FLAC__stream_decoder_set_metadata_ignore_all.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_set_metadata_ignore_all(decoder): + return libflac.FLAC__stream_decoder_set_metadata_ignore_all(decoder) + + libflac.FLAC__stream_decoder_get_state.restype = FLAC__StreamDecoderState + libflac.FLAC__stream_decoder_get_state.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_state(decoder): + return libflac.FLAC__stream_decoder_get_state(decoder) + + libflac.FLAC__stream_decoder_get_resolved_state_string.restype = c_char_p + libflac.FLAC__stream_decoder_get_resolved_state_string.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_resolved_state_string(decoder): + return libflac.FLAC__stream_decoder_get_resolved_state_string(decoder) + + libflac.FLAC__stream_decoder_get_md5_checking.restype = FLAC__bool + libflac.FLAC__stream_decoder_get_md5_checking.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_md5_checking(decoder): + return libflac.FLAC__stream_decoder_get_md5_checking(decoder) + + libflac.FLAC__stream_decoder_get_total_samples.restype = FLAC__uint64 + libflac.FLAC__stream_decoder_get_total_samples.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_total_samples(decoder): + return libflac.FLAC__stream_decoder_get_total_samples(decoder) + + libflac.FLAC__stream_decoder_get_channels.restype = c_uint + libflac.FLAC__stream_decoder_get_channels.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_channels(decoder): + return libflac.FLAC__stream_decoder_get_channels(decoder) + + libflac.FLAC__stream_decoder_get_channel_assignment.restype = FLAC__ChannelAssignment + libflac.FLAC__stream_decoder_get_channel_assignment.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_channel_assignment(decoder): + return libflac.FLAC__stream_decoder_get_channel_assignment(decoder) + + libflac.FLAC__stream_decoder_get_bits_per_sample.restype = c_uint + libflac.FLAC__stream_decoder_get_bits_per_sample.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_bits_per_sample(decoder): + return libflac.FLAC__stream_decoder_get_bits_per_sample(decoder) + + libflac.FLAC__stream_decoder_get_sample_rate.restype = c_uint + libflac.FLAC__stream_decoder_get_sample_rate.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_sample_rate(decoder): + return libflac.FLAC__stream_decoder_get_sample_rate(decoder) + + libflac.FLAC__stream_decoder_get_blocksize.restype = c_uint + libflac.FLAC__stream_decoder_get_blocksize.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_get_blocksize(decoder): + return libflac.FLAC__stream_decoder_get_blocksize(decoder) + + libflac.FLAC__stream_decoder_get_decode_position.restype = FLAC__bool + libflac.FLAC__stream_decoder_get_decode_position.argtypes = [POINTER(FLAC__StreamDecoder), FLAC__uint64_p] + + def FLAC__stream_decoder_get_decode_position(decoder, position): + return libflac.FLAC__stream_decoder_get_decode_position(decoder, position) + + libflac.FLAC__stream_decoder_init_stream.restype = FLAC__StreamDecoderInitStatus + libflac.FLAC__stream_decoder_init_stream.argtypes = [POINTER(FLAC__StreamDecoder), + FLAC__StreamDecoderReadCallback, + FLAC__StreamDecoderSeekCallback, + FLAC__StreamDecoderTellCallback, + FLAC__StreamDecoderLengthCallback, + FLAC__StreamDecoderEofCallback, + FLAC__StreamDecoderWriteCallback, + FLAC__StreamDecoderMetadataCallback, + FLAC__StreamDecoderErrorCallback, + c_void_p] + + def FLAC__stream_decoder_init_stream(decoder, read_callback, seek_callback, tell_callback, length_callback, eof_callback, write_callback, metadata_callback, error_callback, client_data): + return libflac.FLAC__stream_decoder_init_stream(decoder, read_callback, seek_callback, tell_callback, length_callback, eof_callback, write_callback, metadata_callback, error_callback, client_data) + + + libflac.FLAC__stream_decoder_init_ogg_stream.restype = FLAC__StreamDecoderInitStatus + libflac.FLAC__stream_decoder_init_ogg_stream.argtypes = [POINTER(FLAC__StreamDecoder), + FLAC__StreamDecoderReadCallback, + FLAC__StreamDecoderSeekCallback, + FLAC__StreamDecoderTellCallback, + FLAC__StreamDecoderLengthCallback, + FLAC__StreamDecoderEofCallback, + FLAC__StreamDecoderWriteCallback, + FLAC__StreamDecoderMetadataCallback, + FLAC__StreamDecoderErrorCallback, + c_void_p] + + def FLAC__stream_decoder_init_ogg_stream(decoder, read_callback, seek_callback, tell_callback, length_callback, eof_callback, write_callback, metadata_callback, error_callback, client_data): + return libflac.FLAC__stream_decoder_init_ogg_stream(decoder, read_callback, seek_callback, tell_callback, length_callback, eof_callback, write_callback, metadata_callback, error_callback, client_data) + + libflac.FLAC__stream_decoder_init_file.restype = FLAC__StreamDecoderInitStatus + libflac.FLAC__stream_decoder_init_file.argtypes = [POINTER(FLAC__StreamDecoder), + c_char_p, + FLAC__StreamDecoderWriteCallback, + FLAC__StreamDecoderMetadataCallback, + FLAC__StreamDecoderErrorCallback, + c_void_p] + + def FLAC__stream_decoder_init_file(decoder, filename, write_callback, metadata_callback, error_callback, client_data): + return libflac.FLAC__stream_decoder_init_file(decoder, filename, write_callback, metadata_callback, error_callback, client_data) + + libflac.FLAC__stream_decoder_init_ogg_file.restype = FLAC__StreamDecoderInitStatus + libflac.FLAC__stream_decoder_init_ogg_file.argtypes = [POINTER(FLAC__StreamDecoder), + c_char_p, + FLAC__StreamDecoderWriteCallback, + FLAC__StreamDecoderMetadataCallback, + FLAC__StreamDecoderErrorCallback, + c_void_p] + + def FLAC__stream_decoder_init_ogg_file(decoder, filename, write_callback, metadata_callback, error_callback, client_data): + return libflac.FLAC__stream_decoder_init_ogg_file(decoder, filename, write_callback, metadata_callback, error_callback, client_data) + + libflac.FLAC__stream_decoder_finish.restype = FLAC__bool + libflac.FLAC__stream_decoder_finish.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_finish(decoder): + return libflac.FLAC__stream_decoder_finish(decoder) + + libflac.FLAC__stream_decoder_flush.restype = FLAC__bool + libflac.FLAC__stream_decoder_flush.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_flush(decoder): + return libflac.FLAC__stream_decoder_flush(decoder) + + libflac.FLAC__stream_decoder_reset.restype = FLAC__bool + libflac.FLAC__stream_decoder_reset.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_reset(decoder): + return libflac.FLAC__stream_decoder_reset(decoder) + + libflac.FLAC__stream_decoder_process_single.restype = FLAC__bool + libflac.FLAC__stream_decoder_process_single.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_process_single(decoder): + return libflac.FLAC__stream_decoder_process_single(decoder) + + libflac.FLAC__stream_decoder_process_until_end_of_metadata.restype = FLAC__bool + libflac.FLAC__stream_decoder_process_until_end_of_metadata.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_process_until_end_of_metadata(decoder): + return libflac.FLAC__stream_decoder_process_until_end_of_metadata(decoder) + + libflac.FLAC__stream_decoder_process_until_end_of_stream.restype = FLAC__bool + libflac.FLAC__stream_decoder_process_until_end_of_stream.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_process_until_end_of_stream(decoder): + return libflac.FLAC__stream_decoder_process_until_end_of_stream(decoder) + + libflac.FLAC__stream_decoder_skip_single_frame.restype = FLAC__bool + libflac.FLAC__stream_decoder_skip_single_frame.argtypes = [POINTER(FLAC__StreamDecoder)] + + def FLAC__stream_decoder_skip_single_frame(decoder): + return libflac.FLAC__stream_decoder_skip_single_frame(decoder) + + libflac.FLAC__stream_decoder_seek_absolute.restype = FLAC__bool + libflac.FLAC__stream_decoder_seek_absolute.argtypes = [POINTER(FLAC__StreamDecoder), FLAC__uint64] + + def FLAC__stream_decoder_seek_absolute(decoder, sample): + return libflac.FLAC__stream_decoder_seek_absolute(decoder, sample) + + # /stream_decoder + + # stream_encoder + + FLAC__StreamEncoderState = c_int + + libflac.FLAC__StreamEncoderStateString.restype = c_char_p + libflac.FLAC__StreamEncoderStateString.argtypes = [] + + def FLAC__StreamEncoderStateString(): + return libflac.FLAC__StreamEncoderStateString() + + + FLAC__StreamEncoderInitStatus = c_int + + libflac.FLAC__StreamEncoderInitStatusString.restype = c_char_p + libflac.FLAC__StreamEncoderInitStatusString.argtypes = [] + + def FLAC__StreamEncoderInitStatusString(): + return libflac.FLAC__StreamEncoderInitStatusString() + + + FLAC__StreamEncoderReadStatus = c_int + + libflac.FLAC__StreamEncoderReadStatusString.restype = c_char_p + libflac.FLAC__StreamEncoderReadStatusString.argtypes = [] + + def FLAC__StreamEncoderReadStatusString(): + return libflac.FLAC__StreamEncoderReadStatusString() + + + FLAC__StreamEncoderWriteStatus = c_int + + libflac.FLAC__StreamEncoderWriteStatusString.restype = c_char_p + libflac.FLAC__StreamEncoderWriteStatusString.argtypes = [] + + def FLAC__StreamEncoderWriteStatusString(): + return libflac.FLAC__StreamEncoderWriteStatusString() + + + FLAC__StreamEncoderSeekStatus = c_int + + libflac.FLAC__StreamEncoderSeekStatusString.restype = c_char_p + libflac.FLAC__StreamEncoderSeekStatusString.argtypes = [] + + def FLAC__StreamEncoderSeekStatusString(): + return libflac.FLAC__StreamEncoderSeekStatusString() + + + FLAC__StreamEncoderTellStatus = c_int + + libflac.FLAC__StreamEncoderTellStatusString.restype = c_char_p + libflac.FLAC__StreamEncoderTellStatusString.argtypes = [] + + def FLAC__StreamEncoderTellStatusString(): + return libflac.FLAC__StreamEncoderTellStatusString() + + + class FLAC__StreamEncoderProtected(Structure): + _fields_ = [("dummy", c_int)] + + class FLAC__StreamEncoderPrivate(Structure): + _fields_ = [("dummy", c_int)] + + class FLAC__StreamEncoder(Structure): + _fields_ = [("protected_", POINTER(FLAC__StreamEncoderProtected)), + ("private_", POINTER(FLAC__StreamEncoderPrivate))] + + FLAC__StreamEncoderReadCallback = CFUNCTYPE(FLAC__StreamEncoderReadStatus, POINTER(FLAC__StreamEncoder), POINTER(FLAC__byte*0), c_size_t_p, c_void_p) + + FLAC__StreamEncoderWriteCallback = CFUNCTYPE(FLAC__StreamEncoderWriteStatus, POINTER(FLAC__StreamEncoder), POINTER(FLAC__byte*0), c_size_t, c_uint, c_uint, c_void_p) + + FLAC__StreamEncoderSeekCallback = CFUNCTYPE(FLAC__StreamEncoderSeekStatus, POINTER(FLAC__StreamEncoder), FLAC__uint64, c_void_p) + + FLAC__StreamEncoderTellCallback = CFUNCTYPE(FLAC__StreamEncoderTellStatus, POINTER(FLAC__StreamEncoder), FLAC__uint64_p, c_void_p) + + FLAC__StreamEncoderMetadataCallback = CFUNCTYPE(None, POINTER(FLAC__StreamEncoder), POINTER(FLAC__StreamMetadata), c_void_p) + + FLAC__StreamEncoderProgressCallback = CFUNCTYPE(None, POINTER(FLAC__StreamEncoder), FLAC__uint64,FLAC__uint64, c_uint, c_uint, c_void_p) + + + libflac.FLAC__stream_encoder_new.restype = POINTER(FLAC__StreamEncoder) + libflac.FLAC__stream_encoder_new.argtypes = [] + + def FLAC__stream_encoder_new(): + return libflac.FLAC__stream_encoder_new() + + libflac.FLAC__stream_encoder_delete.restype = None + libflac.FLAC__stream_encoder_delete.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_delete(encoder): + return libflac.FLAC__stream_encoder_delete(encoder) + + + libflac.FLAC__stream_encoder_set_ogg_serial_number.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_ogg_serial_number.argtypes = [POINTER(FLAC__StreamEncoder), c_long] + + def FLAC__stream_encoder_set_ogg_serial_number(encoder, serial_number): + return libflac.FLAC__stream_encoder_set_ogg_serial_number(encoder, serial_number) + + libflac.FLAC__stream_encoder_set_verify.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_verify.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_verify(encoder, value): + return libflac.FLAC__stream_encoder_set_verify(encoder, value) + + libflac.FLAC__stream_encoder_set_streamable_subset.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_streamable_subset.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_streamable_subset(encoder, value): + return libflac.FLAC__stream_encoder_set_streamable_subset(encoder, value) + + libflac.FLAC__stream_encoder_set_channels.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_channels.argtypes = [POINTER(FLAC__StreamEncoder), c_uint] + + def FLAC__stream_encoder_set_channels(encoder, value): + return libflac.FLAC__stream_encoder_set_channels(encoder, value) + + libflac.FLAC__stream_encoder_set_bits_per_sample.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_bits_per_sample.argtypes = [POINTER(FLAC__StreamEncoder), c_uint] + + def FLAC__stream_encoder_set_bits_per_sample(encoder, value): + return libflac.FLAC__stream_encoder_set_bits_per_sample(encoder, value) + + libflac.FLAC__stream_encoder_set_sample_rate.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_sample_rate.argtypes = [POINTER(FLAC__StreamEncoder), c_uint] + + def FLAC__stream_encoder_set_sample_rate(encoder, value): + return libflac.FLAC__stream_encoder_set_sample_rate(encoder, value) + + libflac.FLAC__stream_encoder_set_compression_level.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_compression_level.argtypes = [POINTER(FLAC__StreamEncoder), c_uint] + + def FLAC__stream_encoder_set_compression_level(encoder, value): + return libflac.FLAC__stream_encoder_set_compression_level(encoder, value) + + libflac.FLAC__stream_encoder_set_blocksize.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_blocksize.argtypes = [POINTER(FLAC__StreamEncoder), c_uint] + + def FLAC__stream_encoder_set_blocksize(encoder, value): + return libflac.FLAC__stream_encoder_set_blocksize(encoder, value) + + libflac.FLAC__stream_encoder_set_do_mid_side_stereo.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_do_mid_side_stereo.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_do_mid_side_stereo(encoder, value): + return libflac.FLAC__stream_encoder_set_do_mid_side_stereo(encoder, value) + + libflac.FLAC__stream_encoder_set_loose_mid_side_stereo.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_loose_mid_side_stereo.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_loose_mid_side_stereo(encoder, value): + return libflac.FLAC__stream_encoder_set_loose_mid_side_stereo(encoder, value) + + libflac.FLAC__stream_encoder_set_apodization.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_apodization.argtypes = [POINTER(FLAC__StreamEncoder), c_char_p] + + def FLAC__stream_encoder_set_apodization(encoder, specification): + return libflac.FLAC__stream_encoder_set_apodization(encoder, specification) + + libflac.FLAC__stream_encoder_set_max_lpc_order.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_max_lpc_order.argtypes = [POINTER(FLAC__StreamEncoder), c_uint] + + def FLAC__stream_encoder_set_max_lpc_order(encoder, value): + return libflac.FLAC__stream_encoder_set_max_lpc_order(encoder, value) + + libflac.FLAC__stream_encoder_set_qlp_coeff_precision.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_qlp_coeff_precision.argtypes = [POINTER(FLAC__StreamEncoder), c_uint] + + def FLAC__stream_encoder_set_qlp_coeff_precision(encoder, value): + return libflac.FLAC__stream_encoder_set_qlp_coeff_precision(encoder, value) + + libflac.FLAC__stream_encoder_set_do_qlp_coeff_prec_search.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_do_qlp_coeff_prec_search.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_do_qlp_coeff_prec_search(encoder, value): + return libflac.FLAC__stream_encoder_set_do_qlp_coeff_prec_search(encoder, value) + + libflac.FLAC__stream_encoder_set_do_escape_coding.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_do_escape_coding.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_do_escape_coding(encoder, value): + return libflac.FLAC__stream_encoder_set_do_escape_coding(encoder, value) + + libflac.FLAC__stream_encoder_set_do_exhaustive_model_search.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_do_exhaustive_model_search.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_do_exhaustive_model_search(encoder, value): + return libflac.FLAC__stream_encoder_set_do_exhaustive_model_search(encoder, value) + + libflac.FLAC__stream_encoder_set_min_residual_partition_order.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_min_residual_partition_order.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_min_residual_partition_order(encoder, value): + return libflac.FLAC__stream_encoder_set_min_residual_partition_order(encoder, value) + + libflac.FLAC__stream_encoder_set_max_residual_partition_order.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_max_residual_partition_order.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_max_residual_partition_order(encoder, value): + return libflac.FLAC__stream_encoder_set_max_residual_partition_order(encoder, value) + + libflac.FLAC__stream_encoder_set_rice_parameter_search_dist.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_rice_parameter_search_dist.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__bool] + + def FLAC__stream_encoder_set_rice_parameter_search_dist(encoder, value): + return libflac.FLAC__stream_encoder_set_rice_parameter_search_dist(encoder, value) + + libflac.FLAC__stream_encoder_set_total_samples_estimate.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_total_samples_estimate.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__uint64] + + def FLAC__stream_encoder_set_total_samples_estimate(encoder, value): + return libflac.FLAC__stream_encoder_set_total_samples_estimate(encoder, value) + + libflac.FLAC__stream_encoder_set_metadata.restype = FLAC__bool + libflac.FLAC__stream_encoder_set_metadata.argtypes = [POINTER(FLAC__StreamEncoder), POINTER(POINTER(FLAC__StreamMetadata)), c_uint] + + def FLAC__stream_encoder_set_metadata(encoder, metadata, num_blocks): + return libflac.FLAC__stream_encoder_set_metadata(encoder, metadata, num_blocks) + + libflac.FLAC__stream_encoder_get_state.restype = FLAC__StreamEncoderState + libflac.FLAC__stream_encoder_get_state.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_state(encoder): + return libflac.FLAC__stream_encoder_get_state(encoder) + + libflac.FLAC__stream_encoder_get_verify_decoder_state.restype = FLAC__StreamEncoderState + libflac.FLAC__stream_encoder_get_verify_decoder_state.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_verify_decoder_state(encoder): + return libflac.FLAC__stream_encoder_get_verify_decoder_state(encoder) + + libflac.FLAC__stream_encoder_get_resolved_state_string.restype = c_char_p + libflac.FLAC__stream_encoder_get_resolved_state_string.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_resolved_state_string(encoder): + return libflac.FLAC__stream_encoder_get_resolved_state_string(encoder) + + libflac.FLAC__stream_encoder_get_verify_decoder_error_stats.restype = None + libflac.FLAC__stream_encoder_get_verify_decoder_error_stats.argtypes = [POINTER(FLAC__StreamEncoder), FLAC__uint64_p, c_uint_p, c_uint_p, c_uint_p, FLAC__int32_p, FLAC__int32_p] + + def FLAC__stream_encoder_get_verify_decoder_error_stats(encoder, absolute_sample, frame_number, channel, sample, expected, got): + return libflac.FLAC__stream_encoder_get_verify_decoder_error_stats(encoder, absolute_sample, frame_number, channel, sample, expected, got) + + libflac.FLAC__stream_encoder_get_verify.restype = FLAC__bool + libflac.FLAC__stream_encoder_get_verify.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_verify(encoder): + return libflac.FLAC__stream_encoder_get_verify(encoder) + + libflac.FLAC__stream_encoder_get_streamable_subset.restype = FLAC__bool + libflac.FLAC__stream_encoder_get_streamable_subset.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_streamable_subset(encoder): + return libflac.FLAC__stream_encoder_get_streamable_subset(encoder) + + libflac.FLAC__stream_encoder_get_channels.restype = c_uint + libflac.FLAC__stream_encoder_get_channels.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_channels(encoder): + return libflac.FLAC__stream_encoder_get_channels(encoder) + + libflac.FLAC__stream_encoder_get_bits_per_sample.restype = c_uint + libflac.FLAC__stream_encoder_get_bits_per_sample.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_bits_per_sample(encoder): + return libflac.FLAC__stream_encoder_get_bits_per_sample(encoder) + + libflac.FLAC__stream_encoder_get_sample_rate.restype = c_uint + libflac.FLAC__stream_encoder_get_sample_rate.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_sample_rate(encoder): + return libflac.FLAC__stream_encoder_get_sample_rate(encoder) + + libflac.FLAC__stream_encoder_get_blocksize.restype = c_uint + libflac.FLAC__stream_encoder_get_blocksize.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_blocksize(encoder): + return libflac.FLAC__stream_encoder_get_blocksize(encoder) + + libflac.FLAC__stream_encoder_get_do_mid_side_stereo.restype = FLAC__bool + libflac.FLAC__stream_encoder_get_do_mid_side_stereo.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_do_mid_side_stereo(encoder): + return libflac.FLAC__stream_encoder_get_do_mid_side_stereo(encoder) + + libflac.FLAC__stream_encoder_get_loose_mid_side_stereo.restype = FLAC__bool + libflac.FLAC__stream_encoder_get_loose_mid_side_stereo.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_loose_mid_side_stereo(encoder): + return libflac.FLAC__stream_encoder_get_loose_mid_side_stereo(encoder) + + libflac.FLAC__stream_encoder_get_max_lpc_order.restype = c_uint + libflac.FLAC__stream_encoder_get_max_lpc_order.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_max_lpc_order(encoder): + return libflac.FLAC__stream_encoder_get_max_lpc_order(encoder) + + libflac.FLAC__stream_encoder_get_qlp_coeff_precision.restype = c_uint + libflac.FLAC__stream_encoder_get_qlp_coeff_precision.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_qlp_coeff_precision(encoder): + return libflac.FLAC__stream_encoder_get_qlp_coeff_precision(encoder) + + libflac.FLAC__stream_encoder_get_do_qlp_coeff_prec_search.restype = FLAC__bool + libflac.FLAC__stream_encoder_get_do_qlp_coeff_prec_search.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_do_qlp_coeff_prec_search(encoder): + return libflac.FLAC__stream_encoder_get_do_qlp_coeff_prec_search(encoder) + + libflac.FLAC__stream_encoder_get_do_escape_coding.restype = FLAC__bool + libflac.FLAC__stream_encoder_get_do_escape_coding.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_do_escape_coding(encoder): + return libflac.FLAC__stream_encoder_get_do_escape_coding(encoder) + + libflac.FLAC__stream_encoder_get_do_exhaustive_model_search.restype = FLAC__bool + libflac.FLAC__stream_encoder_get_do_exhaustive_model_search.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_do_exhaustive_model_search(encoder): + return libflac.FLAC__stream_encoder_get_do_exhaustive_model_search(encoder) + + libflac.FLAC__stream_encoder_get_min_residual_partition_order.restype = c_uint + libflac.FLAC__stream_encoder_get_min_residual_partition_order.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_min_residual_partition_order(encoder): + return libflac.FLAC__stream_encoder_get_min_residual_partition_order(encoder) + + libflac.FLAC__stream_encoder_get_max_residual_partition_order.restype = c_uint + libflac.FLAC__stream_encoder_get_max_residual_partition_order.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_max_residual_partition_order(encoder): + return libflac.FLAC__stream_encoder_get_max_residual_partition_order(encoder) + + libflac.FLAC__stream_encoder_get_rice_parameter_search_dist.restype = c_uint + libflac.FLAC__stream_encoder_get_rice_parameter_search_dist.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_rice_parameter_search_dist(encoder): + return libflac.FLAC__stream_encoder_get_rice_parameter_search_dist(encoder) + + libflac.FLAC__stream_encoder_get_total_samples_estimate.restype = FLAC__uint64 + libflac.FLAC__stream_encoder_get_total_samples_estimate.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_get_total_samples_estimate(encoder): + return libflac.FLAC__stream_encoder_get_total_samples_estimate(encoder) + + libflac.FLAC__stream_encoder_init_stream.restype = FLAC__StreamEncoderInitStatus + libflac.FLAC__stream_encoder_init_stream.argtypes = [POINTER(FLAC__StreamEncoder), + FLAC__StreamEncoderWriteCallback, + FLAC__StreamEncoderSeekCallback, + FLAC__StreamEncoderTellCallback, + FLAC__StreamEncoderMetadataCallback, + c_void_p] + + def FLAC__stream_encoder_init_stream(encoder, write_callback, seek_callback, tell_callback, metadata_callback,client_data): + return libflac.FLAC__stream_encoder_init_stream(encoder, write_callback, seek_callback, tell_callback, metadata_callback,client_data) + + libflac.FLAC__stream_encoder_init_ogg_stream.restype = FLAC__StreamEncoderInitStatus + libflac.FLAC__stream_encoder_init_ogg_stream.argtypes = [POINTER(FLAC__StreamEncoder), + FLAC__StreamEncoderReadCallback, + FLAC__StreamEncoderWriteCallback, + FLAC__StreamEncoderSeekCallback, + FLAC__StreamEncoderTellCallback, + FLAC__StreamEncoderMetadataCallback, + c_void_p] + + def FLAC__stream_encoder_init_ogg_stream(encoder, read_callback, write_callback, seek_callback, tell_callback, metadata_callback,client_data): + return libflac.FLAC__stream_encoder_init_ogg_stream(encoder, read_callback, write_callback, seek_callback, tell_callback, metadata_callback,client_data) + + libflac.FLAC__stream_encoder_init_file.restype = FLAC__StreamEncoderInitStatus + libflac.FLAC__stream_encoder_init_file.argtypes = [POINTER(FLAC__StreamEncoder), + c_char_p, + FLAC__StreamEncoderProgressCallback, + c_void_p] + + def FLAC__stream_encoder_init_file(encoder, filename, progress_callback,client_data): + return libflac.FLAC__stream_encoder_init_file(encoder, filename, progress_callback,client_data) + + + libflac.FLAC__stream_encoder_init_ogg_file.restype = FLAC__StreamEncoderInitStatus + libflac.FLAC__stream_encoder_init_ogg_file.argtypes = [POINTER(FLAC__StreamEncoder), + c_char_p, + FLAC__StreamEncoderProgressCallback, + c_void_p] + + def FLAC__stream_encoder_init_ogg_file(encoder, filename, progress_callback,client_data): + return libflac.FLAC__stream_encoder_init_ogg_file(encoder, filename, progress_callback,client_data) + + libflac.FLAC__stream_encoder_finish.restype = FLAC__bool + libflac.FLAC__stream_encoder_finish.argtypes = [POINTER(FLAC__StreamEncoder)] + + def FLAC__stream_encoder_finish(encoder): + return libflac.FLAC__stream_encoder_finish(encoder) + + libflac.FLAC__stream_encoder_process.restype = FLAC__bool + libflac.FLAC__stream_encoder_process.argtypes = [POINTER(FLAC__StreamEncoder), POINTER(FLAC__int32_p*0), c_uint] + + def FLAC__stream_encoder_process(encoder, buffer, samples): + return libflac.FLAC__stream_encoder_process(encoder, buffer, samples) + + libflac.FLAC__stream_encoder_process_interleaved.restype = FLAC__bool + libflac.FLAC__stream_encoder_process_interleaved.argtypes = [POINTER(FLAC__StreamEncoder), POINTER(FLAC__int32*0), c_uint] + + def FLAC__stream_encoder_process_interleaved(encoder, buffer, samples): + return libflac.FLAC__stream_encoder_process_interleaved(encoder, buffer, samples) + + # /stream_encoder diff --git a/sbapp/pyogg/flac_file.py b/sbapp/pyogg/flac_file.py new file mode 100644 index 0000000..7e97ca7 --- /dev/null +++ b/sbapp/pyogg/flac_file.py @@ -0,0 +1,114 @@ +import ctypes +from itertools import chain + +from . import flac +from .audio_file import AudioFile +from .pyogg_error import PyOggError + +def _to_char_p(string): + try: + return ctypes.c_char_p(string.encode("utf-8")) + except: + return ctypes.c_char_p(string) + +def _resize_array(array, new_size): + return (array._type_*new_size).from_address(ctypes.addressof(array)) + + +class FlacFile(AudioFile): + def write_callback(self, decoder, frame, buffer, client_data): + multi_channel_buf = _resize_array(buffer.contents, self.channels) + arr_size = frame.contents.header.blocksize + if frame.contents.header.channels >= 2: + arrays = [] + for i in range(frame.contents.header.channels): + arr = ctypes.cast(multi_channel_buf[i], ctypes.POINTER(flac.FLAC__int32*arr_size)).contents + arrays.append(arr[:]) + + arr = list(chain.from_iterable(zip(*arrays))) + + self.buffer[self.buffer_pos : self.buffer_pos + len(arr)] = arr[:] + self.buffer_pos += len(arr) + + else: + arr = ctypes.cast(multi_channel_buf[0], ctypes.POINTER(flac.FLAC__int32*arr_size)).contents + self.buffer[self.buffer_pos : self.buffer_pos + arr_size] = arr[:] + self.buffer_pos += arr_size + return 0 + + def metadata_callback(self,decoder, metadata, client_data): + if not self.buffer: + self.total_samples = metadata.contents.data.stream_info.total_samples + self.channels = metadata.contents.data.stream_info.channels + Buffer = flac.FLAC__int16*(self.total_samples * self.channels) + self.buffer = Buffer() + self.frequency = metadata.contents.data.stream_info.sample_rate + + def error_callback(self,decoder, status, client_data): + raise PyOggError("An error occured during the process of decoding. Status enum: {}".format(flac.FLAC__StreamDecoderErrorStatusEnum[status])) + + def __init__(self, path): + self.decoder = flac.FLAC__stream_decoder_new() + + self.client_data = ctypes.c_void_p() + + #: Number of channels in audio file. + self.channels = None + + #: Number of samples per second (per channel). For + # example, 44100. + self.frequency = None + + self.total_samples = None + + #: Raw PCM data from audio file. + self.buffer = None + + self.buffer_pos = 0 + + write_callback_ = flac.FLAC__StreamDecoderWriteCallback(self.write_callback) + + metadata_callback_ = flac.FLAC__StreamDecoderMetadataCallback(self.metadata_callback) + + error_callback_ = flac.FLAC__StreamDecoderErrorCallback(self.error_callback) + + init_status = flac.FLAC__stream_decoder_init_file( + self.decoder, + _to_char_p(path), # This will have an issue with Unicode filenames + write_callback_, + metadata_callback_, + error_callback_, + self.client_data + ) + + if init_status: # error + error = flac.FLAC__StreamDecoderInitStatusEnum[init_status] + raise PyOggError( + "An error occured when trying to open '{}': {}".format(path, error) + ) + + metadata_status = (flac.FLAC__stream_decoder_process_until_end_of_metadata(self.decoder)) + if not metadata_status: # error + raise PyOggError("An error occured when trying to decode the metadata of {}".format(path)) + + stream_status = (flac.FLAC__stream_decoder_process_until_end_of_stream(self.decoder)) + if not stream_status: # error + raise PyOggError("An error occured when trying to decode the audio stream of {}".format(path)) + + flac.FLAC__stream_decoder_finish(self.decoder) + + #: Length of buffer + self.buffer_length = len(self.buffer) + + self.bytes_per_sample = ctypes.sizeof(flac.FLAC__int16) # See definition of Buffer in metadata_callback() + + # Cast buffer to one-dimensional array of chars + CharBuffer = ( + ctypes.c_byte * + (self.bytes_per_sample * len(self.buffer)) + ) + self.buffer = CharBuffer.from_buffer(self.buffer) + + # FLAC audio is always signed. See + # https://xiph.org/flac/api/group__flac__stream__decoder.html#gaf98a4f9e2cac5747da6018c3dfc8dde1 + self.signed = True diff --git a/sbapp/pyogg/flac_file_stream.py b/sbapp/pyogg/flac_file_stream.py new file mode 100644 index 0000000..f832c31 --- /dev/null +++ b/sbapp/pyogg/flac_file_stream.py @@ -0,0 +1,141 @@ +import ctypes +from itertools import chain + +from . import flac +from .pyogg_error import PyOggError + +def _to_char_p(string): + try: + return ctypes.c_char_p(string.encode("utf-8")) + except: + return ctypes.c_char_p(string) + +def _resize_array(array, new_size): + return (array._type_*new_size).from_address(ctypes.addressof(array)) + + +class FlacFileStream: + def write_callback(self,decoder, frame, buffer, client_data): + multi_channel_buf = _resize_array(buffer.contents, self.channels) + arr_size = frame.contents.header.blocksize + if frame.contents.header.channels >= 2: + arrays = [] + for i in range(frame.contents.header.channels): + arr = ctypes.cast(multi_channel_buf[i], ctypes.POINTER(flac.FLAC__int32*arr_size)).contents + arrays.append(arr[:]) + + arr = list(chain.from_iterable(zip(*arrays))) + + self.buffer = (flac.FLAC__int16*len(arr))(*arr) + self.bytes_written = len(arr) * 2 + + else: + arr = ctypes.cast(multi_channel_buf[0], ctypes.POINTER(flac.FLAC__int32*arr_size)).contents + self.buffer = (flac.FLAC__int16*len(arr))(*arr[:]) + self.bytes_written = arr_size * 2 + return 0 + + def metadata_callback(self,decoder, metadata, client_data): + self.total_samples = metadata.contents.data.stream_info.total_samples + self.channels = metadata.contents.data.stream_info.channels + self.frequency = metadata.contents.data.stream_info.sample_rate + + def error_callback(self,decoder, status, client_data): + raise PyOggError("An error occured during the process of decoding. Status enum: {}".format(flac.FLAC__StreamDecoderErrorStatusEnum[status])) + + def __init__(self, path): + self.decoder = flac.FLAC__stream_decoder_new() + + self.client_data = ctypes.c_void_p() + + #: Number of channels in audio file. + self.channels = None + + #: Number of samples per second (per channel). For + # example, 44100. + self.frequency = None + + self.total_samples = None + + self.buffer = None + + self.bytes_written = None + + self.write_callback_ = flac.FLAC__StreamDecoderWriteCallback(self.write_callback) + + self.metadata_callback_ = flac.FLAC__StreamDecoderMetadataCallback(self.metadata_callback) + + self.error_callback_ = flac.FLAC__StreamDecoderErrorCallback(self.error_callback) + + init_status = flac.FLAC__stream_decoder_init_file(self.decoder, + _to_char_p(path), + self.write_callback_, + self.metadata_callback_, + self.error_callback_, + self.client_data) + + if init_status: # error + raise PyOggError("An error occured when trying to open '{}': {}".format(path, flac.FLAC__StreamDecoderInitStatusEnum[init_status])) + + metadata_status = (flac.FLAC__stream_decoder_process_until_end_of_metadata(self.decoder)) + if not metadata_status: # error + raise PyOggError("An error occured when trying to decode the metadata of {}".format(path)) + + #: Bytes per sample + self.bytes_per_sample = 2 + + def get_buffer(self): + """Returns the buffer. + + Returns buffer (a bytes object) or None if all data has + been read from the file. + + """ + # Attempt to read a single frame of audio + stream_status = (flac.FLAC__stream_decoder_process_single(self.decoder)) + if not stream_status: # error + raise PyOggError("An error occured when trying to decode the audio stream of {}".format(path)) + + # Check if we encountered the end of the stream + if (flac.FLAC__stream_decoder_get_state(self.decoder) == 4): # end of stream + return None + + buffer_as_bytes = bytes(self.buffer) + return buffer_as_bytes + + def clean_up(self): + flac.FLAC__stream_decoder_finish(self.decoder) + + def get_buffer_as_array(self): + """Provides the buffer as a NumPy array. + + Note that the underlying data type is 16-bit signed + integers. + + Does not copy the underlying data, so the returned array + should either be processed or copied before the next call + to get_buffer() or get_buffer_as_array(). + + """ + import numpy # type: ignore + + # Read the next samples from the stream + buf = self.get_buffer() + + # Check if we've come to the end of the stream + if buf is None: + return None + + # Convert the bytes buffer to a NumPy array + array = numpy.frombuffer( + buf, + dtype=numpy.int16 + ) + + # Reshape the array + return array.reshape( + (len(buf) + // self.bytes_per_sample + // self.channels, + self.channels) + ) diff --git a/sbapp/pyogg/library_loader.py b/sbapp/pyogg/library_loader.py new file mode 100644 index 0000000..711b1ba --- /dev/null +++ b/sbapp/pyogg/library_loader.py @@ -0,0 +1,147 @@ +import ctypes +import ctypes.util +import os +import sys +import platform +from typing import ( + Optional, + Dict, + List +) + +_here = os.path.dirname(__file__) + +class ExternalLibraryError(Exception): + pass + +architecture = platform.architecture()[0] + +_windows_styles = ["{}", "lib{}", "lib{}_dynamic", "{}_dynamic"] + +_other_styles = ["{}", "lib{}"] + +if architecture == "32bit": + for arch_style in ["32bit", "32" "86", "win32", "x86", "_x86", "_32", "_win32", "_32bit"]: + for style in ["{}", "lib{}"]: + _windows_styles.append(style.format("{}"+arch_style)) + +elif architecture == "64bit": + for arch_style in ["64bit", "64" "86_64", "amd64", "win_amd64", "x86_64", "_x86_64", "_64", "_amd64", "_64bit"]: + for style in ["{}", "lib{}"]: + _windows_styles.append(style.format("{}"+arch_style)) + + +run_tests = lambda lib, tests: [f(lib) for f in tests] + +# Get the appropriate directory for the shared libraries depending +# on the current platform and architecture +platform_ = platform.system() +lib_dir = None +if platform_ == "Darwin": + lib_dir = "libs/macos" +elif platform_ == "Windows": + if architecture == "32bit": + lib_dir = "libs/win32" + elif architecture == "64bit": + lib_dir = "libs/win_amd64" + + +class Library: + @staticmethod + def load(names: Dict[str, str], paths: Optional[List[str]] = None, tests = []) -> Optional[ctypes.CDLL]: + lib = InternalLibrary.load(names, tests) + if lib is None: + lib = ExternalLibrary.load(names["external"], paths, tests) + return lib + + +class InternalLibrary: + @staticmethod + def load(names: Dict[str, str], tests) -> Optional[ctypes.CDLL]: + # If we do not have a library directory, give up immediately + if lib_dir is None: + return None + + # Get the appropriate library filename given the platform + try: + name = names[platform_] + except KeyError: + return None + + # Attempt to load the library from here + path = _here + "/" + lib_dir + "/" + name + try: + lib = ctypes.CDLL(path) + except OSError as e: + return None + + # Check that the library passes the tests + if tests and all(run_tests(lib, tests)): + return lib + + # Library failed tests + return None + +# Cache of libraries that have already been loaded +_loaded_libraries: Dict[str, ctypes.CDLL] = {} + +class ExternalLibrary: + @staticmethod + def load(name, paths = None, tests = []): + if name in _loaded_libraries: + return _loaded_libraries[name] + if sys.platform == "win32": + lib = ExternalLibrary.load_windows(name, paths, tests) + _loaded_libraries[name] = lib + return lib + else: + lib = ExternalLibrary.load_other(name, paths, tests) + _loaded_libraries[name] = lib + return lib + + @staticmethod + def load_other(name, paths = None, tests = []): + os.environ["PATH"] += ";" + ";".join((os.getcwd(), _here)) + if paths: os.environ["PATH"] += ";" + ";".join(paths) + + for style in _other_styles: + candidate = style.format(name) + library = ctypes.util.find_library(candidate) + if library: + try: + lib = ctypes.CDLL(library) + if tests and all(run_tests(lib, tests)): + return lib + except: + pass + + @staticmethod + def load_windows(name, paths = None, tests = []): + os.environ["PATH"] += ";" + ";".join((os.getcwd(), _here)) + if paths: os.environ["PATH"] += ";" + ";".join(paths) + + not_supported = [] # libraries that were found, but are not supported + for style in _windows_styles: + candidate = style.format(name) + library = ctypes.util.find_library(candidate) + if library: + try: + lib = ctypes.CDLL(library) + if tests and all(run_tests(lib, tests)): + return lib + not_supported.append(library) + except WindowsError: + pass + except OSError: + not_supported.append(library) + + + if not_supported: + raise ExternalLibraryError("library '{}' couldn't be loaded, because the following candidates were not supported:".format(name) + + ("\n{}" * len(not_supported)).format(*not_supported)) + + raise ExternalLibraryError("library '{}' couldn't be loaded".format(name)) + + + + diff --git a/sbapp/pyogg/ogg.py b/sbapp/pyogg/ogg.py new file mode 100644 index 0000000..08a944b --- /dev/null +++ b/sbapp/pyogg/ogg.py @@ -0,0 +1,672 @@ +############################################################ +# Ogg license: # +############################################################ +""" +Copyright (c) 2002, Xiph.org Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import ctypes +from ctypes import c_int, c_int8, c_int16, c_int32, c_int64, c_uint, c_uint8, c_uint16, c_uint32, c_uint64, c_float, c_long, c_ulong, c_char, c_char_p, c_ubyte, c_longlong, c_ulonglong, c_size_t, c_void_p, c_double, POINTER, pointer, cast +import ctypes.util +import sys +from traceback import print_exc as _print_exc +import os + +from .library_loader import Library, ExternalLibrary, ExternalLibraryError + + +def get_raw_libname(name): + name = os.path.splitext(name)[0].lower() + for x in "0123456789._- ":name=name.replace(x,"") + return name + +# Define a function to convert strings to char-pointers. In Python 3 +# all strings are Unicode, while in Python 2 they were ASCII-encoded. +# FIXME: Does PyOgg even support Python 2? +if sys.version_info.major > 2: + to_char_p = lambda s: s.encode('utf-8') +else: + to_char_p = lambda s: s + +__here = os.getcwd() + +libogg = None + +try: + names = { + "Windows": "ogg.dll", + "Darwin": "libogg.0.dylib", + "external": "ogg" + } + libogg = Library.load(names, tests = [lambda lib: hasattr(lib, "oggpack_writeinit")]) +except ExternalLibraryError: + pass +except: + _print_exc() + +if libogg is not None: + PYOGG_OGG_AVAIL = True +else: + PYOGG_OGG_AVAIL = False + +if PYOGG_OGG_AVAIL: + # Sanity check also satisfies mypy type checking + assert libogg is not None + + # ctypes + c_ubyte_p = POINTER(c_ubyte) + c_uchar = c_ubyte + c_uchar_p = c_ubyte_p + c_float_p = POINTER(c_float) + c_float_p_p = POINTER(c_float_p) + c_float_p_p_p = POINTER(c_float_p_p) + c_char_p_p = POINTER(c_char_p) + c_int_p = POINTER(c_int) + c_long_p = POINTER(c_long) + + # os_types + ogg_int16_t = c_int16 + ogg_uint16_t = c_uint16 + ogg_int32_t = c_int32 + ogg_uint32_t = c_uint32 + ogg_int64_t = c_int64 + ogg_uint64_t = c_uint64 + ogg_int64_t_p = POINTER(ogg_int64_t) + + # ogg + class ogg_iovec_t(ctypes.Structure): + """ + Wrapper for: + typedef struct ogg_iovec_t; + """ + _fields_ = [("iov_base", c_void_p), + ("iov_len", c_size_t)] + + class oggpack_buffer(ctypes.Structure): + """ + Wrapper for: + typedef struct oggpack_buffer; + """ + _fields_ = [("endbyte", c_long), + ("endbit", c_int), + ("buffer", c_uchar_p), + ("ptr", c_uchar_p), + ("storage", c_long)] + + class ogg_page(ctypes.Structure): + """ + Wrapper for: + typedef struct ogg_page; + """ + _fields_ = [("header", c_uchar_p), + ("header_len", c_long), + ("body", c_uchar_p), + ("body_len", c_long)] + + class ogg_stream_state(ctypes.Structure): + """ + Wrapper for: + typedef struct ogg_stream_state; + """ + _fields_ = [("body_data", c_uchar_p), + ("body_storage", c_long), + ("body_fill", c_long), + ("body_returned", c_long), + + ("lacing_vals", c_int), + ("granule_vals", ogg_int64_t), + + ("lacing_storage", c_long), + ("lacing_fill", c_long), + ("lacing_packet", c_long), + ("lacing_returned", c_long), + + ("header", c_uchar*282), + ("header_fill", c_int), + + ("e_o_s", c_int), + ("b_o_s", c_int), + + ("serialno", c_long), + ("pageno", c_long), + ("packetno", ogg_int64_t), + ("granulepos", ogg_int64_t)] + + class ogg_packet(ctypes.Structure): + """ + Wrapper for: + typedef struct ogg_packet; + """ + _fields_ = [("packet", c_uchar_p), + ("bytes", c_long), + ("b_o_s", c_long), + ("e_o_s", c_long), + + ("granulepos", ogg_int64_t), + + ("packetno", ogg_int64_t)] + + def __str__(self): + bos = "" + if self.b_o_s: + bos = "beginning of stream, " + eos = "" + if self.e_o_s: + eos = "end of stream, " + + # Converting the data will cause a seg-fault if the memory isn't valid + data = bytes(self.packet[0:self.bytes]) + value = ( + f"Ogg Packet <{hex(id(self))}>: " + + f"number {self.packetno}, " + + f"granule position {self.granulepos}, " + + bos + eos + + f"{self.bytes} bytes" + ) + return value + + class ogg_sync_state(ctypes.Structure): + """ + Wrapper for: + typedef struct ogg_sync_state; + """ + _fields_ = [("data", c_uchar_p), + ("storage", c_int), + ("fill", c_int), + ("returned", c_int), + + ("unsynched", c_int), + ("headerbytes", c_int), + ("bodybytes", c_int)] + + b_p = POINTER(oggpack_buffer) + oy_p = POINTER(ogg_sync_state) + op_p = POINTER(ogg_packet) + og_p = POINTER(ogg_page) + os_p = POINTER(ogg_stream_state) + iov_p = POINTER(ogg_iovec_t) + + libogg.oggpack_writeinit.restype = None + libogg.oggpack_writeinit.argtypes = [b_p] + + def oggpack_writeinit(b): + libogg.oggpack_writeinit(b) + + try: + libogg.oggpack_writecheck.restype = c_int + libogg.oggpack_writecheck.argtypes = [b_p] + def oggpack_writecheck(b): + libogg.oggpack_writecheck(b) + except: + pass + + libogg.oggpack_writetrunc.restype = None + libogg.oggpack_writetrunc.argtypes = [b_p, c_long] + + def oggpack_writetrunc(b, bits): + libogg.oggpack_writetrunc(b, bits) + + libogg.oggpack_writealign.restype = None + libogg.oggpack_writealign.argtypes = [b_p] + + def oggpack_writealign(b): + libogg.oggpack_writealign(b) + + libogg.oggpack_writecopy.restype = None + libogg.oggpack_writecopy.argtypes = [b_p, c_void_p, c_long] + + def oggpack_writecopy(b, source, bits): + libogg.oggpack_writecopy(b, source, bits) + + libogg.oggpack_reset.restype = None + libogg.oggpack_reset.argtypes = [b_p] + + def oggpack_reset(b): + libogg.oggpack_reset(b) + + libogg.oggpack_writeclear.restype = None + libogg.oggpack_writeclear.argtypes = [b_p] + + def oggpack_writeclear(b): + libogg.oggpack_writeclear(b) + + libogg.oggpack_readinit.restype = None + libogg.oggpack_readinit.argtypes = [b_p, c_uchar_p, c_int] + + def oggpack_readinit(b, buf, bytes): + libogg.oggpack_readinit(b, buf, bytes) + + libogg.oggpack_write.restype = None + libogg.oggpack_write.argtypes = [b_p, c_ulong, c_int] + + def oggpack_write(b, value, bits): + libogg.oggpack_write(b, value, bits) + + libogg.oggpack_look.restype = c_long + libogg.oggpack_look.argtypes = [b_p, c_int] + + def oggpack_look(b, bits): + return libogg.oggpack_look(b, bits) + + libogg.oggpack_look1.restype = c_long + libogg.oggpack_look1.argtypes = [b_p] + + def oggpack_look1(b): + return libogg.oggpack_look1(b) + + libogg.oggpack_adv.restype = None + libogg.oggpack_adv.argtypes = [b_p, c_int] + + def oggpack_adv(b, bits): + libogg.oggpack_adv(b, bits) + + libogg.oggpack_adv1.restype = None + libogg.oggpack_adv1.argtypes = [b_p] + + def oggpack_adv1(b): + libogg.oggpack_adv1(b) + + libogg.oggpack_read.restype = c_long + libogg.oggpack_read.argtypes = [b_p, c_int] + + def oggpack_read(b, bits): + return libogg.oggpack_read(b, bits) + + libogg.oggpack_read1.restype = c_long + libogg.oggpack_read1.argtypes = [b_p] + + def oggpack_read1(b): + return libogg.oggpack_read1(b) + + libogg.oggpack_bytes.restype = c_long + libogg.oggpack_bytes.argtypes = [b_p] + + def oggpack_bytes(b): + return libogg.oggpack_bytes(b) + + libogg.oggpack_bits.restype = c_long + libogg.oggpack_bits.argtypes = [b_p] + + def oggpack_bits(b): + return libogg.oggpack_bits(b) + + libogg.oggpack_get_buffer.restype = c_uchar_p + libogg.oggpack_get_buffer.argtypes = [b_p] + + def oggpack_get_buffer(b): + return libogg.oggpack_get_buffer(b) + + + + libogg.oggpackB_writeinit.restype = None + libogg.oggpackB_writeinit.argtypes = [b_p] + + def oggpackB_writeinit(b): + libogg.oggpackB_writeinit(b) + + try: + libogg.oggpackB_writecheck.restype = c_int + libogg.oggpackB_writecheck.argtypes = [b_p] + + def oggpackB_writecheck(b): + return libogg.oggpackB_writecheck(b) + except: + pass + + libogg.oggpackB_writetrunc.restype = None + libogg.oggpackB_writetrunc.argtypes = [b_p, c_long] + + def oggpackB_writetrunc(b, bits): + libogg.oggpackB_writetrunc(b, bits) + + libogg.oggpackB_writealign.restype = None + libogg.oggpackB_writealign.argtypes = [b_p] + + def oggpackB_writealign(b): + libogg.oggpackB_writealign(b) + + libogg.oggpackB_writecopy.restype = None + libogg.oggpackB_writecopy.argtypes = [b_p, c_void_p, c_long] + + def oggpackB_writecopy(b, source, bits): + libogg.oggpackB_writecopy(b, source, bits) + + libogg.oggpackB_reset.restype = None + libogg.oggpackB_reset.argtypes = [b_p] + + def oggpackB_reset(b): + libogg.oggpackB_reset(b) + + libogg.oggpackB_reset.restype = None + libogg.oggpackB_writeclear.argtypes = [b_p] + + def oggpackB_reset(b): + libogg.oggpackB_reset(b) + + libogg.oggpackB_readinit.restype = None + libogg.oggpackB_readinit.argtypes = [b_p, c_uchar_p, c_int] + + def oggpackB_readinit(b, buf, bytes): + libogg.oggpackB_readinit(b, buf, bytes) + + libogg.oggpackB_write.restype = None + libogg.oggpackB_write.argtypes = [b_p, c_ulong, c_int] + + def oggpackB_write(b, value, bits): + libogg.oggpackB_write(b, value, bits) + + libogg.oggpackB_look.restype = c_long + libogg.oggpackB_look.argtypes = [b_p, c_int] + + def oggpackB_look(b, bits): + return libogg.oggpackB_look(b, bits) + + libogg.oggpackB_look1.restype = c_long + libogg.oggpackB_look1.argtypes = [b_p] + + def oggpackB_look1(b): + return libogg.oggpackB_look1(b) + + libogg.oggpackB_adv.restype = None + libogg.oggpackB_adv.argtypes = [b_p, c_int] + + def oggpackB_adv(b, bits): + libogg.oggpackB_adv(b, bits) + + libogg.oggpackB_adv1.restype = None + libogg.oggpackB_adv1.argtypes = [b_p] + + def oggpackB_adv1(b): + libogg.oggpackB_adv1(b) + + libogg.oggpackB_read.restype = c_long + libogg.oggpackB_read.argtypes = [b_p, c_int] + + def oggpackB_read(b, bits): + return libogg.oggpackB_read(b, bits) + + libogg.oggpackB_read1.restype = c_long + libogg.oggpackB_read1.argtypes = [b_p] + + def oggpackB_read1(b): + return libogg.oggpackB_read1(b) + + libogg.oggpackB_bytes.restype = c_long + libogg.oggpackB_bytes.argtypes = [b_p] + + def oggpackB_bytes(b): + return libogg.oggpackB_bytes(b) + + libogg.oggpackB_bits.restype = c_long + libogg.oggpackB_bits.argtypes = [b_p] + + def oggpackB_bits(b): + return libogg.oggpackB_bits(b) + + libogg.oggpackB_get_buffer.restype = c_uchar_p + libogg.oggpackB_get_buffer.argtypes = [b_p] + + def oggpackB_get_buffer(b): + return libogg.oggpackB_get_buffer(b) + + + + libogg.ogg_stream_packetin.restype = c_int + libogg.ogg_stream_packetin.argtypes = [os_p, op_p] + + def ogg_stream_packetin(os, op): + return libogg.ogg_stream_packetin(os, op) + + try: + libogg.ogg_stream_iovecin.restype = c_int + libogg.ogg_stream_iovecin.argtypes = [os_p, iov_p, c_int, c_long, ogg_int64_t] + + def ogg_stream_iovecin(os, iov, count, e_o_s, granulepos): + return libogg.ogg_stream_iovecin(os, iov, count, e_o_s, granulepos) + except: + pass + + libogg.ogg_stream_pageout.restype = c_int + libogg.ogg_stream_pageout.argtypes = [os_p, og_p] + + def ogg_stream_pageout(os, og): + return libogg.ogg_stream_pageout(os, og) + + try: + libogg.ogg_stream_pageout_fill.restype = c_int + libogg.ogg_stream_pageout_fill.argtypes = [os_p, og_p, c_int] + def ogg_stream_pageout_fill(os, og, nfill): + return libogg.ogg_stream_pageout_fill(os, og, nfill) + except: + pass + + libogg.ogg_stream_flush.restype = c_int + libogg.ogg_stream_flush.argtypes = [os_p, og_p] + + def ogg_stream_flush(os, og): + return libogg.ogg_stream_flush(os, og) + + try: + libogg.ogg_stream_flush_fill.restype = c_int + libogg.ogg_stream_flush_fill.argtypes = [os_p, og_p, c_int] + def ogg_stream_flush_fill(os, og, nfill): + return libogg.ogg_stream_flush_fill(os, og, nfill) + except: + pass + + + + libogg.ogg_sync_init.restype = c_int + libogg.ogg_sync_init.argtypes = [oy_p] + + def ogg_sync_init(oy): + return libogg.ogg_sync_init(oy) + + libogg.ogg_sync_clear.restype = c_int + libogg.ogg_sync_clear.argtypes = [oy_p] + + def ogg_sync_clear(oy): + return libogg.ogg_sync_clear(oy) + + libogg.ogg_sync_reset.restype = c_int + libogg.ogg_sync_reset.argtypes = [oy_p] + + def ogg_sync_reset(oy): + return libogg.ogg_sync_reset(oy) + + libogg.ogg_sync_destroy.restype = c_int + libogg.ogg_sync_destroy.argtypes = [oy_p] + + def ogg_sync_destroy(oy): + return libogg.ogg_sync_destroy(oy) + + try: + libogg.ogg_sync_check.restype = c_int + libogg.ogg_sync_check.argtypes = [oy_p] + def ogg_sync_check(oy): + return libogg.ogg_sync_check(oy) + except: + pass + + + + libogg.ogg_sync_buffer.restype = c_char_p + libogg.ogg_sync_buffer.argtypes = [oy_p, c_long] + + def ogg_sync_buffer(oy, size): + return libogg.ogg_sync_buffer(oy, size) + + libogg.ogg_sync_wrote.restype = c_int + libogg.ogg_sync_wrote.argtypes = [oy_p, c_long] + + def ogg_sync_wrote(oy, bytes): + return libogg.ogg_sync_wrote(oy, bytes) + + libogg.ogg_sync_pageseek.restype = c_int + libogg.ogg_sync_pageseek.argtypes = [oy_p, og_p] + + def ogg_sync_pageseek(oy, og): + return libogg.ogg_sync_pageseek(oy, og) + + libogg.ogg_sync_pageout.restype = c_long + libogg.ogg_sync_pageout.argtypes = [oy_p, og_p] + + def ogg_sync_pageout(oy, og): + return libogg.ogg_sync_pageout(oy, og) + + libogg.ogg_stream_pagein.restype = c_int + libogg.ogg_stream_pagein.argtypes = [os_p, og_p] + + def ogg_stream_pagein(os, og): + return libogg.ogg_stream_pagein(oy, og) + + libogg.ogg_stream_packetout.restype = c_int + libogg.ogg_stream_packetout.argtypes = [os_p, op_p] + + def ogg_stream_packetout(os, op): + return libogg.ogg_stream_packetout(oy, op) + + libogg.ogg_stream_packetpeek.restype = c_int + libogg.ogg_stream_packetpeek.argtypes = [os_p, op_p] + + def ogg_stream_packetpeek(os, op): + return libogg.ogg_stream_packetpeek(os, op) + + + + libogg.ogg_stream_init.restype = c_int + libogg.ogg_stream_init.argtypes = [os_p, c_int] + + def ogg_stream_init(os, serialno): + return libogg.ogg_stream_init(os, serialno) + + libogg.ogg_stream_clear.restype = c_int + libogg.ogg_stream_clear.argtypes = [os_p] + + def ogg_stream_clear(os): + return libogg.ogg_stream_clear(os) + + libogg.ogg_stream_reset.restype = c_int + libogg.ogg_stream_reset.argtypes = [os_p] + + def ogg_stream_reset(os): + return libogg.ogg_stream_reset(os) + + libogg.ogg_stream_reset_serialno.restype = c_int + libogg.ogg_stream_reset_serialno.argtypes = [os_p, c_int] + + def ogg_stream_reset_serialno(os, serialno): + return libogg.ogg_stream_reset_serialno(os, serialno) + + libogg.ogg_stream_destroy.restype = c_int + libogg.ogg_stream_destroy.argtypes = [os_p] + + def ogg_stream_destroy(os): + return libogg.ogg_stream_destroy(os) + + try: + libogg.ogg_stream_check.restype = c_int + libogg.ogg_stream_check.argtypes = [os_p] + def ogg_stream_check(os): + return libogg.ogg_stream_check(os) + except: + pass + + libogg.ogg_stream_eos.restype = c_int + libogg.ogg_stream_eos.argtypes = [os_p] + + def ogg_stream_eos(os): + return libogg.ogg_stream_eos(os) + + + + libogg.ogg_page_checksum_set.restype = None + libogg.ogg_page_checksum_set.argtypes = [og_p] + + def ogg_page_checksum_set(og): + libogg.ogg_page_checksum_set(og) + + + + libogg.ogg_page_version.restype = c_int + libogg.ogg_page_version.argtypes = [og_p] + + def ogg_page_version(og): + return libogg.ogg_page_version(og) + + libogg.ogg_page_continued.restype = c_int + libogg.ogg_page_continued.argtypes = [og_p] + + def ogg_page_continued(og): + return libogg.ogg_page_continued(og) + + libogg.ogg_page_bos.restype = c_int + libogg.ogg_page_bos.argtypes = [og_p] + + def ogg_page_bos(og): + return libogg.ogg_page_bos(og) + + libogg.ogg_page_eos.restype = c_int + libogg.ogg_page_eos.argtypes = [og_p] + + def ogg_page_eos(og): + return libogg.ogg_page_eos(og) + + libogg.ogg_page_granulepos.restype = ogg_int64_t + libogg.ogg_page_granulepos.argtypes = [og_p] + + def ogg_page_granulepos(og): + return libogg.ogg_page_granulepos(og) + + libogg.ogg_page_serialno.restype = c_int + libogg.ogg_page_serialno.argtypes = [og_p] + + def ogg_page_serialno(og): + return libogg.ogg_page_serialno(og) + + libogg.ogg_page_pageno.restype = c_long + libogg.ogg_page_pageno.argtypes = [og_p] + + def ogg_page_pageno(og): + return libogg.ogg_page_pageno(og) + + libogg.ogg_page_packets.restype = c_int + libogg.ogg_page_packets.argtypes = [og_p] + + def ogg_page_packets(og): + return libogg.ogg_page_packets(og) + + + + libogg.ogg_packet_clear.restype = None + libogg.ogg_packet_clear.argtypes = [op_p] + + def ogg_packet_clear(op): + libogg.ogg_packet_clear(op) diff --git a/sbapp/pyogg/ogg_opus_writer.py b/sbapp/pyogg/ogg_opus_writer.py new file mode 100644 index 0000000..547d0f5 --- /dev/null +++ b/sbapp/pyogg/ogg_opus_writer.py @@ -0,0 +1,421 @@ +import builtins +import copy +import ctypes +import random +import struct +from typing import ( + Optional, + Union, + BinaryIO +) + +from . import ogg +from . import opus +from .opus_buffered_encoder import OpusBufferedEncoder +#from .opus_encoder import OpusEncoder +from .pyogg_error import PyOggError + +class OggOpusWriter(): + """Encodes PCM data into an OggOpus file.""" + + def __init__(self, + f: Union[BinaryIO, str], + encoder: OpusBufferedEncoder, + custom_pre_skip: Optional[int] = None) -> None: + """Construct an OggOpusWriter. + + f may be either a string giving the path to the file, or + an already-opened file handle. + + If f is an already-opened file handle, then it is the + user's responsibility to close the file when they are + finished with it. The file should be opened for writing + in binary (not text) mode. + + The encoder should be a + OpusBufferedEncoder and should be fully configured before the + first call to the `write()` method. + + The Opus encoder requires an amount of "warm up" and when + stored in an Ogg container that warm up can be skipped. When + `custom_pre_skip` is None, the required amount of warm up + silence is automatically calculated and inserted. If a custom + (non-silent) pre-skip is desired, then `custom_pre_skip` + should be specified as the number of samples (per channel). + It is then the user's responsibility to pass the non-silent + pre-skip samples to `encode()`. + + """ + # Store the Opus encoder + self._encoder = encoder + + # Store the custom pre skip + self._custom_pre_skip = custom_pre_skip + + # Create a new stream state with a random serial number + self._stream_state = self._create_stream_state() + + # Create a packet (reused for each pass) + self._ogg_packet = ogg.ogg_packet() + self._packet_valid = False + + # Create a page (reused for each pass) + self._ogg_page = ogg.ogg_page() + + # Counter for the number of packets written into Ogg stream + self._count_packets = 0 + + # Counter for the number of samples encoded into Opus + # packets + self._count_samples = 0 + + # Flag to indicate if the headers have been written + self._headers_written = False + + # Flag to indicate that the stream has been finished (the + # EOS bit was set in a final packet) + self._finished = False + + # Reference to the current encoded packet (written only + # when we know if it the last) + self._current_encoded_packet: Optional[bytes] = None + + # Open file if required. Given this may raise an exception, + # it should be the last step of initialisation. + self._i_opened_the_file = False + if isinstance(f, str): + self._file = builtins.open(f, 'wb') + self._i_opened_the_file = True + else: + # Assume it's already opened file + self._file = f + + def __del__(self) -> None: + if not self._finished: + self.close() + + # + # User visible methods + # + + def write(self, pcm: memoryview) -> None: + """Encode the PCM and write out the Ogg Opus stream. + + Encoders the PCM using the provided encoder. + + """ + # Check that the stream hasn't already been finished + if self._finished: + raise PyOggError( + "Stream has already ended. Perhaps close() was "+ + "called too early?") + + # If we haven't already written out the headers, do so + # now. Then, write a frame of silence to warm up the + # encoder. + if not self._headers_written: + pre_skip = self._write_headers(self._custom_pre_skip) + if self._custom_pre_skip is None: + self._write_silence(pre_skip) + + # Call the internal method to encode the bytes + self._write_to_oggopus(pcm) + + + def _write_to_oggopus(self, pcm: memoryview, flush: bool = False) -> None: + assert self._encoder is not None + + def handle_encoded_packet(encoded_packet: memoryview, + samples: int, + end_of_stream: bool) -> None: + # Cast memoryview to ctypes Array + Buffer = ctypes.c_ubyte * len(encoded_packet) + encoded_packet_ctypes = Buffer.from_buffer(encoded_packet) + + # Obtain a pointer to the encoded packet + encoded_packet_ptr = ctypes.cast( + encoded_packet_ctypes, + ctypes.POINTER(ctypes.c_ubyte) + ) + + # Increase the count of the number of samples written + self._count_samples += samples + + # Place data into the packet + self._ogg_packet.packet = encoded_packet_ptr + self._ogg_packet.bytes = len(encoded_packet) + self._ogg_packet.b_o_s = 0 + self._ogg_packet.e_o_s = end_of_stream + self._ogg_packet.granulepos = self._count_samples + self._ogg_packet.packetno = self._count_packets + + # Increase the counter of the number of packets + # in the stream + self._count_packets += 1 + + # Write the packet into the stream + self._write_packet() + + + # Encode the PCM data into an Opus packet + self._encoder.buffered_encode( + pcm, + flush=flush, + callback=handle_encoded_packet + ) + + def close(self) -> None: + # Check we haven't already closed this stream + if self._finished: + # We're attempting to close an already closed stream, + # do nothing more. + return + + # Flush the underlying buffered encoder + self._write_to_oggopus(memoryview(bytearray(b"")), flush=True) + + # The current packet must be the end of the stream, update + # the packet's details + self._ogg_packet.e_o_s = 1 + + # Write the packet to the stream + if self._packet_valid: + self._write_packet() + + # Flush the stream of any unwritten pages + self._flush() + + # Mark the stream as finished + self._finished = True + + # Close the file if we opened it + if self._i_opened_the_file: + self._file.close() + self._i_opened_the_file = False + + # Clean up the Ogg-related memory + ogg.ogg_stream_clear(self._stream_state) + + # Clean up the reference to the encoded packet (as it must + # now have been written) + del self._current_encoded_packet + + # + # Internal methods + # + + def _create_random_serial_no(self) -> ctypes.c_int: + sizeof_c_int = ctypes.sizeof(ctypes.c_int) + min_int = -2**(sizeof_c_int*8-1) + max_int = 2**(sizeof_c_int*8-1)-1 + serial_no = ctypes.c_int(random.randint(min_int, max_int)) + + return serial_no + + def _create_stream_state(self) -> ogg.ogg_stream_state: + # Create a random serial number + serial_no = self._create_random_serial_no() + + # Create an ogg_stream_state + ogg_stream_state = ogg.ogg_stream_state() + + # Initialise the stream state + ogg.ogg_stream_init( + ctypes.pointer(ogg_stream_state), + serial_no + ) + + return ogg_stream_state + + def _make_identification_header(self, pre_skip: int, input_sampling_rate: int = 0) -> bytes: + """Make the OggOpus identification header. + + An input_sampling rate may be set to zero to mean 'unspecified'. + + Only channel mapping family 0 is currently supported. + This allows mono and stereo signals. + + See https://tools.ietf.org/html/rfc7845#page-12 for more + details. + + """ + signature = b"OpusHead" + version = 1 + output_channels = self._encoder._channels + output_gain = 0 + channel_mapping_family = 0 + data = struct.pack( + " int: + """ Returns pre-skip. """ + if custom_pre_skip is not None: + # Use the user-specified amount of pre-skip + pre_skip = custom_pre_skip + else: + # Obtain the algorithmic delay of the Opus encoder. See + # https://tools.ietf.org/html/rfc7845#page-27 + delay_samples = self._encoder.get_algorithmic_delay() + + # Extra samples are recommended. See + # https://tools.ietf.org/html/rfc7845#page-27 + extra_samples = 120 + + # We will just fill a whole frame with silence. Calculate + # the minimum frame length, which we'll use as the + # pre-skip. + frame_durations = [2.5, 5, 10, 20, 40, 60] # milliseconds + frame_lengths = [ + x * self._encoder._samples_per_second // 1000 + for x in frame_durations + ] + for frame_length in frame_lengths: + if frame_length > delay_samples + extra_samples: + pre_skip = frame_length + break + + # Create the identification header + id_header = self._make_identification_header( + pre_skip = pre_skip + ) + + # Specify the packet containing the identification header + self._ogg_packet.packet = ctypes.cast(id_header, ogg.c_uchar_p) # type: ignore + self._ogg_packet.bytes = len(id_header) + self._ogg_packet.b_o_s = 1 + self._ogg_packet.e_o_s = 0 + self._ogg_packet.granulepos = 0 + self._ogg_packet.packetno = self._count_packets + self._count_packets += 1 + + # Write the identification header + result = ogg.ogg_stream_packetin( + self._stream_state, + self._ogg_packet + ) + + if result != 0: + raise PyOggError( + "Failed to write Opus identification header" + ) + + return pre_skip + + def _make_comment_header(self): + """Make the OggOpus comment header. + + See https://tools.ietf.org/html/rfc7845#page-22 for more + details. + + """ + signature = b"OpusTags" + vendor_string = b"ENCODER=PyOgg" + vendor_string_length = struct.pack(" None: + super().__init__() + + self._frame_size_ms: Optional[float] = None + self._frame_size_bytes: Optional[int] = None + + # Buffer contains the bytes required for the next + # frame. + self._buffer: Optional[ctypes.Array] = None + + # Location of the next free byte in the buffer + self._buffer_index = 0 + + + def set_frame_size(self, frame_size: float) -> None: + """ Set the desired frame duration (in milliseconds). + + Valid options are 2.5, 5, 10, 20, 40, or 60ms. + + """ + + # Ensure the frame size is valid. Compare frame size in + # units of 0.1ms to avoid floating point comparison + if int(frame_size*10) not in [25, 50, 100, 200, 400, 600]: + raise PyOggError( + "Frame size ({:f}) not one of ".format(frame_size)+ + "the acceptable values" + ) + + self._frame_size_ms = frame_size + + self._calc_frame_size() + + + def set_sampling_frequency(self, samples_per_second: int) -> None: + super().set_sampling_frequency(samples_per_second) + self._calc_frame_size() + + + def buffered_encode(self, + pcm_bytes: memoryview, + flush: bool = False, + callback: Callable[[memoryview,int,bool],None] = None + ) -> List[Tuple[memoryview, int, bool]]: + """Gets encoded packets and their number of samples. + + This method returns a list, where each item in the list is + a tuple. The first item in the tuple is an Opus-encoded + frame stored as a bytes-object. The second item in the + tuple is the number of samples encoded (excluding + silence). + + If `callback` is supplied then this method will instead + return an empty list but call the callback for every + Opus-encoded frame that would have been returned as a + list. This option has the desireable property of + eliminating the copying of the encoded packets, which is + required in order to form a list. The callback should + take two arguments, the encoded frame (a Python bytes + object) and the number of samples encoded per channel (an + int). The user must either process or copy the data as + the data may be overwritten once the callback terminates. + + """ + # If there's no work to do return immediately + if len(pcm_bytes) == 0 and flush == False: + return [] # no work to do + + # Sanity checks + if self._frame_size_ms is None: + raise PyOggError("Frame size must be set before encoding") + assert self._frame_size_bytes is not None + assert self._channels is not None + assert self._buffer is not None + assert self._buffer_index is not None + + # Local variable initialisation + results = [] + pcm_index = 0 + pcm_len = len(pcm_bytes) + + # 'Cast' memoryview of PCM to ctypes Array + Buffer = ctypes.c_ubyte * len(pcm_bytes) + try: + pcm_ctypes = Buffer.from_buffer(pcm_bytes) + except TypeError: + warnings.warn( + "Because PCM was read-only, an extra memory "+ + "copy was required; consider storing PCM in "+ + "writable memory (for example, bytearray "+ + "rather than bytes)." + ) + pcm_ctypes = Buffer.from_buffer(pcm_bytes) + + # Either store the encoded packet to return at the end of the + # method or immediately call the callback with the encoded + # packet. + def store_or_callback(encoded_packet: memoryview, + samples: int, + end_of_stream: bool = False) -> None: + if callback is None: + # Store the result + results.append(( + encoded_packet, + samples, + end_of_stream + )) + else: + # Call the callback + callback( + encoded_packet, + samples, + end_of_stream + ) + + # Fill the remainder of the buffer with silence and encode it. + # The associated number of samples are only that of actual + # data, not the added silence. + def flush_buffer() -> None: + # Sanity checks to satisfy mypy + assert self._buffer_index is not None + assert self._channels is not None + assert self._buffer is not None + + # If the buffer is already empty, we have no work to do + if self._buffer_index == 0: + return + + # Store the number of samples currently in the buffer + samples = ( + self._buffer_index + // self._channels + // ctypes.sizeof(opus.opus_int16) + ) + + # Fill the buffer with silence + ctypes.memset( + # destination + ctypes.byref(self._buffer, self._buffer_index), + # value + 0, + # count + len(self._buffer) - self._buffer_index + ) + + # Encode the PCM + # As at 2020-11-05, mypy is unaware that ctype Arrays + # support the buffer protocol. + encoded_packet = self.encode(memoryview(self._buffer)) # type: ignore + + # Either store the encoded packet or call the + # callback + store_or_callback(encoded_packet, samples, True) + + + # Copy the data remaining from the provided PCM into the + # buffer. Flush if required. + def copy_insufficient_data() -> None: + # Sanity checks to satisfy mypy + assert self._buffer is not None + + # Calculate remaining data + remaining_data = len(pcm_bytes) - pcm_index + + # Copy the data into the buffer. + ctypes.memmove( + # destination + ctypes.byref(self._buffer, self._buffer_index), + # source + ctypes.byref(pcm_ctypes, pcm_index), + # count + remaining_data + ) + + self._buffer_index += remaining_data + + # If we've been asked to flush the buffer then do so + if flush: + flush_buffer() + + # Loop through the provided PCM and the current buffer, + # encoding as we have full packets. + while True: + # There are two possibilities at this point: either we + # have previously unencoded data still in the buffer or we + # do not + if self._buffer_index == 0: + # We do not have unencoded data + + # We are free to progress through the PCM that has + # been provided encoding frames without copying any + # bytes. Once there is insufficient data remaining + # for a complete frame, that data should be copied + # into the buffer and we have finished. + if pcm_len - pcm_index > self._frame_size_bytes: + # We have enough data remaining in the provided + # PCM to encode more than an entire frame without + # copying any data. Unfortunately, splicing a + # ctypes array copies the array. To avoid the + # copy we use memoryview see + # https://mattgwwalker.wordpress.com/2020/12/12/python-ctypes-slicing/ + frame_data = memoryview(pcm_bytes)[ + pcm_index:pcm_index+self._frame_size_bytes + ] + + # Update the PCM index + pcm_index += self._frame_size_bytes + + # Store number of samples (per channel) of actual + # data + samples = ( + len(frame_data) + // self._channels + // ctypes.sizeof(opus.opus_int16) + ) + + # Encode the PCM + encoded_packet = super().encode(frame_data) + + # Either store the encoded packet or call the + # callback + store_or_callback(encoded_packet, samples) + + else: + # We do not have enough data to fill a frame while + # still having data left over. Copy the data into + # the buffer. + copy_insufficient_data() + return results + + else: + # We have unencoded data. + + # Copy the provided PCM into the buffer (up until the + # buffer is full). If we can fill it, then we can + # encode the filled buffer and continue. If we can't + # fill it then we've finished. + data_required = len(self._buffer) - self._buffer_index + if pcm_len > data_required: + # We have sufficient data to fill the buffer and + # have data left over. Copy data into the buffer. + assert pcm_index == 0 + remaining = len(self._buffer) - self._buffer_index + ctypes.memmove( + # destination + ctypes.byref(self._buffer, self._buffer_index), + # source + pcm_ctypes, + # count + remaining + ) + pcm_index += remaining + self._buffer_index += remaining + assert self._buffer_index == len(self._buffer) + + # Encode the PCM + encoded_packet = super().encode( + # Memoryviews of ctypes do work, even though + # mypy complains. + memoryview(self._buffer) # type: ignore + ) + + # Store number of samples (per channel) of actual + # data + samples = ( + self._buffer_index + // self._channels + // ctypes.sizeof(opus.opus_int16) + ) + + # We've now processed the buffer + self._buffer_index = 0 + + # Either store the encoded packet or call the + # callback + store_or_callback(encoded_packet, samples) + else: + # We have insufficient data to fill the buffer + # while still having data left over. Copy the + # data into the buffer. + copy_insufficient_data() + return results + + + def _calc_frame_size(self): + """Calculates the number of bytes in a frame. + + If the frame size (in milliseconds) and the number of + samples per seconds have already been specified, then the + frame size in bytes is set. Otherwise, this method does + nothing. + + The frame size is measured in bytes required to store the + sample. + + """ + if (self._frame_size_ms is None + or self._samples_per_second is None): + return + + self._frame_size_bytes = ( + self._frame_size_ms + * self._samples_per_second + // 1000 + * ctypes.sizeof(opus.opus_int16) + * self._channels + ) + + # Allocate space for the buffer + Buffer = ctypes.c_ubyte * self._frame_size_bytes + self._buffer = Buffer() + + + def _get_next_frame(self, add_silence=False): + """Gets the next Opus-encoded frame. + + Returns a tuple where the first item is the Opus-encoded + frame and the second item is the number of encoded samples + (per channel). + + Returns None if insufficient data is available. + + """ + next_frame = bytes() + samples = 0 + + # Ensure frame size has been specified + if self._frame_size_bytes is None: + raise PyOggError( + "Desired frame size hasn't been set. Perhaps "+ + "encode() was called before set_frame_size() "+ + "and set_sampling_frequency()?" + ) + + # Check if there's insufficient data in the buffer to fill + # a frame. + if self._frame_size_bytes > self._buffer_size: + if len(self._buffer) == 0: + # No data at all in buffer + return None + if add_silence: + # Get all remaining data + while len(self._buffer) != 0: + next_frame += self._buffer.popleft() + self._buffer_size = 0 + # Store number of samples (per channel) of actual + # data + samples = ( + len(next_frame) + // self._channels + // ctypes.sizeof(opus.opus_int16) + ) + # Fill remainder of frame with silence + bytes_remaining = self._frame_size_bytes - len(next_frame) + next_frame += b'\x00' * bytes_remaining + return (next_frame, samples) + else: + # Insufficient data to fill a frame and we're not + # adding silence + return None + + bytes_remaining = self._frame_size_bytes + while bytes_remaining > 0: + if len(self._buffer[0]) <= bytes_remaining: + # Take the whole first item + buffer_ = self._buffer.popleft() + next_frame += buffer_ + bytes_remaining -= len(buffer_) + self._buffer_size -= len(buffer_) + else: + # Take only part of the buffer + + # TODO: This could be more efficiently + # implemented. Rather than appending back the + # remaining data, we could just update an index + # saying where we were up to in regards to the + # first entry of the buffer. + buffer_ = self._buffer.popleft() + next_frame += buffer_[:bytes_remaining] + self._buffer_size -= bytes_remaining + # And put the unused part back into the buffer + self._buffer.appendleft(buffer_[bytes_remaining:]) + bytes_remaining = 0 + + # Calculate number of samples (per channel) + samples = ( + len(next_frame) + // self._channels + // ctypes.sizeof(opus.opus_int16) + ) + + return (next_frame, samples) diff --git a/sbapp/pyogg/opus_decoder.py b/sbapp/pyogg/opus_decoder.py new file mode 100644 index 0000000..8a1f4dd --- /dev/null +++ b/sbapp/pyogg/opus_decoder.py @@ -0,0 +1,273 @@ +import ctypes + +from . import opus +from .pyogg_error import PyOggError + +class OpusDecoder: + def __init__(self): + self._decoder = None + self._channels = None + self._samples_per_second = None + self._pcm_buffer = None + self._pcm_buffer_ptr = None + self._pcm_buffer_size_int = None + + # TODO: Check if there is clean up that we need to do when + # closing a decoder. + + # + # User visible methods + # + + def set_channels(self, n): + + """Set the number of channels. + + n must be either 1 or 2. + + The decoder is capable of filling in either mono or + interleaved stereo pcm buffers. + + """ + if self._decoder is None: + if n < 0 or n > 2: + raise PyOggError( + "Invalid number of channels in call to "+ + "set_channels()" + ) + self._channels = n + else: + raise PyOggError( + "Cannot change the number of channels after "+ + "the decoder was created. Perhaps "+ + "set_channels() was called after decode()?" + ) + self._create_pcm_buffer() + + def set_sampling_frequency(self, samples_per_second): + """Set the number of samples (per channel) per second. + + samples_per_second must be one of 8000, 12000, 16000, + 24000, or 48000. + + Internally Opus stores data at 48000 Hz, so that should be + the default value for Fs. However, the decoder can + efficiently decode to buffers at 8, 12, 16, and 24 kHz so + if for some reason the caller cannot use data at the full + sample rate, or knows the compressed data doesn't use the + full frequency range, it can request decoding at a reduced + rate. + + """ + if self._decoder is None: + if samples_per_second in [8000, 12000, 16000, 24000, 48000]: + self._samples_per_second = samples_per_second + else: + raise PyOggError( + "Specified sampling frequency "+ + "({:d}) ".format(samples_per_second)+ + "was not one of the accepted values" + ) + else: + raise PyOggError( + "Cannot change the sampling frequency after "+ + "the decoder was created. Perhaps "+ + "set_sampling_frequency() was called after decode()?" + ) + self._create_pcm_buffer() + + def decode(self, encoded_bytes: memoryview): + """Decodes an Opus-encoded packet into PCM. + + """ + # If we haven't already created a decoder, do so now + if self._decoder is None: + self._decoder = self._create_decoder() + + # Create a ctypes array from the memoryview (without copying + # data) + Buffer = ctypes.c_char * len(encoded_bytes) + encoded_bytes_ctypes = Buffer.from_buffer(encoded_bytes) + + # Create pointer to encoded bytes + encoded_bytes_ptr = ctypes.cast( + encoded_bytes_ctypes, + ctypes.POINTER(ctypes.c_ubyte) + ) + + # Store length of encoded bytes into int32 + len_int32 = opus.opus_int32( + len(encoded_bytes) + ) + + # Check that we have a PCM buffer + if self._pcm_buffer is None: + raise PyOggError("PCM buffer was not configured.") + + # Decode the encoded frame + result = opus.opus_decode( + self._decoder, + encoded_bytes_ptr, + len_int32, + self._pcm_buffer_ptr, + self._pcm_buffer_size_int, + 0 # TODO: What's Forward Error Correction about? + ) + + # Check for any errors + if result < 0: + raise PyOggError( + "An error occurred while decoding an Opus-encoded "+ + "packet: "+ + opus.opus_strerror(result).decode("utf") + ) + + # Extract just the valid data as bytes + end_valid_data = ( + result + * ctypes.sizeof(opus.opus_int16) + * self._channels + ) + + # Create memoryview of PCM buffer to avoid copying data during slice. + mv = memoryview(self._pcm_buffer) + + # Cast memoryview to chars + mv = mv.cast('c') + + # Slice memoryview to extract only valid data + mv = mv[:end_valid_data] + + return mv + + + def decode_missing_packet(self, frame_duration): + """ Obtain PCM data despite missing a frame. + + frame_duration is in milliseconds. + + """ + + # Consider frame duration in units of 0.1ms in order to + # avoid floating-point comparisons. + if int(frame_duration*10) not in [25, 50, 100, 200, 400, 600]: + raise PyOggError( + "Frame duration ({:f}) is not one of the accepted values".format(frame_duration) + ) + + # Calculate frame size + frame_size = int( + frame_duration + * self._samples_per_second + // 1000 + ) + + # Store frame size as int + frame_size_int = ctypes.c_int(frame_size) + + # Decode missing packet + result = opus.opus_decode( + self._decoder, + None, + 0, + self._pcm_buffer_ptr, + frame_size_int, + 0 # TODO: What is this Forward Error Correction about? + ) + + # Check for any errors + if result < 0: + raise PyOggError( + "An error occurred while decoding an Opus-encoded "+ + "packet: "+ + opus.opus_strerror(result).decode("utf") + ) + + # Extract just the valid data as bytes + end_valid_data = ( + result + * ctypes.sizeof(opus.opus_int16) + * self._channels + ) + return bytes(self._pcm_buffer)[:end_valid_data] + + # + # Internal methods + # + + def _create_pcm_buffer(self): + if (self._samples_per_second is None + or self._channels is None): + # We cannot define the buffer yet + return + + # Create buffer to hold 120ms of samples. See "opus_decode()" at + # https://opus-codec.org/docs/opus_api-1.3.1/group__opus__decoder.html + max_duration = 120 # milliseconds + max_samples = max_duration * self._samples_per_second // 1000 + PCMBuffer = opus.opus_int16 * (max_samples * self._channels) + self._pcm_buffer = PCMBuffer() + self._pcm_buffer_ptr = ( + ctypes.cast(ctypes.pointer(self._pcm_buffer), + ctypes.POINTER(opus.opus_int16)) + ) + + # Store samples per channel in an int + self._pcm_buffer_size_int = ctypes.c_int(max_samples) + + def _create_decoder(self): + # To create a decoder, we must first allocate resources for it. + # We want Python to be responsible for the memory deallocation, + # and thus Python must be responsible for the initial memory + # allocation. + + # Check that the sampling frequency has been defined + if self._samples_per_second is None: + raise PyOggError( + "The sampling frequency was not specified before "+ + "attempting to create an Opus decoder. Perhaps "+ + "decode() was called before set_sampling_frequency()?" + ) + + # The sampling frequency must be passed in as a 32-bit int + samples_per_second = opus.opus_int32(self._samples_per_second) + + # Check that the number of channels has been defined + if self._channels is None: + raise PyOggError( + "The number of channels were not specified before "+ + "attempting to create an Opus decoder. Perhaps "+ + "decode() was called before set_channels()?" + ) + + # The number of channels must also be passed in as a 32-bit int + channels = opus.opus_int32(self._channels) + + # Obtain the number of bytes of memory required for the decoder + size = opus.opus_decoder_get_size(channels); + + # Allocate the required memory for the decoder + memory = ctypes.create_string_buffer(size) + + # Cast the newly-allocated memory as a pointer to a decoder. We + # could also have used opus.od_p as the pointer type, but writing + # it out in full may be clearer. + decoder = ctypes.cast(memory, ctypes.POINTER(opus.OpusDecoder)) + + # Initialise the decoder + error = opus.opus_decoder_init( + decoder, + samples_per_second, + channels + ); + + # Check that there hasn't been an error when initialising the + # decoder + if error != opus.OPUS_OK: + raise PyOggError( + "An error occurred while creating the decoder: "+ + opus.opus_strerror(error).decode("utf") + ) + + # Return our newly-created decoder + return decoder diff --git a/sbapp/pyogg/opus_encoder.py b/sbapp/pyogg/opus_encoder.py new file mode 100644 index 0000000..1da82da --- /dev/null +++ b/sbapp/pyogg/opus_encoder.py @@ -0,0 +1,358 @@ +import ctypes +from typing import Optional, Union, ByteString + +from . import opus +from .pyogg_error import PyOggError + +class OpusEncoder: + """Encodes PCM data into Opus frames.""" + def __init__(self) -> None: + self._encoder: Optional[ctypes.pointer] = None + self._channels: Optional[int] = None + self._samples_per_second: Optional[int] = None + self._application: Optional[int] = None + self._max_bytes_per_frame: Optional[opus.opus_int32] = None + self._output_buffer: Optional[ctypes.Array] = None + self._output_buffer_ptr: Optional[ctypes.pointer] = None + + # An output buffer of 4,000 bytes is recommended in + # https://opus-codec.org/docs/opus_api-1.3.1/group__opus__encoder.html + self.set_max_bytes_per_frame(4000) + + # + # User visible methods + # + + def set_channels(self, n: int) -> None: + """Set the number of channels. + + n must be either 1 or 2. + + """ + if self._encoder is None: + if n < 0 or n > 2: + raise PyOggError( + "Invalid number of channels in call to "+ + "set_channels()" + ) + self._channels = n + else: + raise PyOggError( + "Cannot change the number of channels after "+ + "the encoder was created. Perhaps "+ + "set_channels() was called after encode()?" + ) + + def set_sampling_frequency(self, samples_per_second: int) -> None: + """Set the number of samples (per channel) per second. + + This must be one of 8000, 12000, 16000, 24000, or 48000. + + Regardless of the sampling rate and number of channels + selected, the Opus encoder can switch to a lower audio + bandwidth or number of channels if the bitrate selected is + too low. This also means that it is safe to always use 48 + kHz stereo input and let the encoder optimize the + encoding. + + """ + if self._encoder is None: + if samples_per_second in [8000, 12000, 16000, 24000, 48000]: + self._samples_per_second = samples_per_second + else: + raise PyOggError( + "Specified sampling frequency "+ + "({:d}) ".format(samples_per_second)+ + "was not one of the accepted values" + ) + else: + raise PyOggError( + "Cannot change the sampling frequency after "+ + "the encoder was created. Perhaps "+ + "set_sampling_frequency() was called after encode()?" + ) + + def set_application(self, application: str) -> None: + """Set the encoding mode. + + This must be one of 'voip', 'audio', or 'restricted_lowdelay'. + + 'voip': Gives best quality at a given bitrate for voice + signals. It enhances the input signal by high-pass + filtering and emphasizing formants and + harmonics. Optionally it includes in-band forward error + correction to protect against packet loss. Use this mode + for typical VoIP applications. Because of the enhancement, + even at high bitrates the output may sound different from + the input. + + 'audio': Gives best quality at a given bitrate for most + non-voice signals like music. Use this mode for music and + mixed (music/voice) content, broadcast, and applications + requiring less than 15 ms of coding delay. + + 'restricted_lowdelay': configures low-delay mode that + disables the speech-optimized mode in exchange for + slightly reduced delay. This mode can only be set on an + newly initialized encoder because it changes the codec + delay. + """ + if self._encoder is not None: + raise PyOggError( + "Cannot change the application after "+ + "the encoder was created. Perhaps "+ + "set_application() was called after encode()?" + ) + if application == "voip": + self._application = opus.OPUS_APPLICATION_VOIP + elif application == "audio": + self._application = opus.OPUS_APPLICATION_AUDIO + elif application == "restricted_lowdelay": + self._application = opus.OPUS_APPLICATION_RESTRICTED_LOWDELAY + else: + raise PyOggError( + "The application specification '{:s}' ".format(application)+ + "wasn't one of the accepted values." + ) + + def set_max_bytes_per_frame(self, max_bytes: int) -> None: + """Set the maximum number of bytes in an encoded frame. + + Size of the output payload. This may be used to impose an + upper limit on the instant bitrate, but should not be used + as the only bitrate control. + + TODO: Use OPUS_SET_BITRATE to control the bitrate. + + """ + self._max_bytes_per_frame = opus.opus_int32(max_bytes) + OutputBuffer = ctypes.c_ubyte * max_bytes + self._output_buffer = OutputBuffer() + self._output_buffer_ptr = ( + ctypes.cast(ctypes.pointer(self._output_buffer), + ctypes.POINTER(ctypes.c_ubyte)) + ) + + + def encode(self, pcm: Union[bytes, bytearray, memoryview]) -> memoryview: + """Encodes PCM data into an Opus frame. + + `pcm` must be formatted as bytes-like, with each sample taking + two bytes (signed 16-bit integers; interleaved left, then + right channels if in stereo). + + If `pcm` is not writeable, a copy of the array will be made. + + """ + # If we haven't already created an encoder, do so now + if self._encoder is None: + self._encoder = self._create_encoder() + + # Sanity checks also satisfy mypy type checking + assert self._channels is not None + assert self._samples_per_second is not None + assert self._output_buffer is not None + + # Calculate the effective frame duration of the given PCM + # data. Calculate it in units of 0.1ms in order to avoid + # floating point comparisons. + bytes_per_sample = 2 + frame_size = ( + len(pcm) # bytes + // bytes_per_sample + // self._channels + ) + frame_duration = ( + (10*frame_size) + // (self._samples_per_second//1000) + ) + + # Check that we have a valid frame size + if int(frame_duration) not in [25, 50, 100, 200, 400, 600]: + raise PyOggError( + "The effective frame duration ({:.1f} ms) " + .format(frame_duration/10)+ + "was not one of the acceptable values." + ) + + # Create a ctypes object sharing the memory of the PCM data + PcmCtypes = ctypes.c_ubyte * len(pcm) + try: + # Attempt to share the PCM memory + + # Unfortunately, as at 2020-09-27, the type hinting for + # read-only and writeable buffer protocols was a + # work-in-progress. The following only works for writable + # cases, but the method's parameters include a read-only + # possibility (bytes), thus we ignore mypy's error. + pcm_ctypes = PcmCtypes.from_buffer(pcm) # type: ignore[arg-type] + except TypeError: + # The data must be copied if it's not writeable + pcm_ctypes = PcmCtypes.from_buffer_copy(pcm) + + # Create a pointer to the PCM data + pcm_ptr = ctypes.cast( + pcm_ctypes, + ctypes.POINTER(opus.opus_int16) + ) + + # Create an int giving the frame size per channel + frame_size_int = ctypes.c_int(frame_size) + + # Encode PCM + result = opus.opus_encode( + self._encoder, + pcm_ptr, + frame_size_int, + self._output_buffer_ptr, + self._max_bytes_per_frame + ) + + # Check for any errors + if result < 0: + raise PyOggError( + "An error occurred while encoding to Opus format: "+ + opus.opus_strerror(result).decode("utf") + ) + + # Get memoryview of buffer so that the slice operation doesn't + # copy the data. + # + # Unfortunately, as at 2020-09-27, the type hints for + # memoryview do not include ctype arrays. This is because + # there is no currently accepted manner to label a class as + # supporting the buffer protocol. However, it's clearly a + # work in progress. For more information, see: + # * https://bugs.python.org/issue27501 + # * https://github.com/python/typing/issues/593 + # * https://github.com/python/typeshed/pull/4232 + mv = memoryview(self._output_buffer) # type: ignore + + # Cast the memoryview to char + mv = mv.cast('c') + + # Slice just the valid data from the memoryview + valid_data_as_bytes = mv[:result] + + # DEBUG + # Convert memoryview back to ctypes instance + Buffer = ctypes.c_ubyte * len(valid_data_as_bytes) + buf = Buffer.from_buffer( valid_data_as_bytes ) + + # Convert PCM back to pointer and dump 4,000-byte buffer + ptr = ctypes.cast( + buf, + ctypes.POINTER(ctypes.c_ubyte) + ) + + return valid_data_as_bytes + + + def get_algorithmic_delay(self): + """Gets the total samples of delay added by the entire codec. + + This can be queried by the encoder and then the provided + number of samples can be skipped on from the start of the + decoder's output to provide time aligned input and + output. From the perspective of a decoding application the + real data begins this many samples late. + + The decoder contribution to this delay is identical for all + decoders, but the encoder portion of the delay may vary from + implementation to implementation, version to version, or even + depend on the encoder's initial configuration. Applications + needing delay compensation should call this method rather than + hard-coding a value. + + """ + # If we haven't already created an encoder, do so now + if self._encoder is None: + self._encoder = self._create_encoder() + + # Obtain the algorithmic delay of the Opus encoder. See + # https://tools.ietf.org/html/rfc7845#page-27 + delay = opus.opus_int32() + + result = opus.opus_encoder_ctl( + self._encoder, + opus.OPUS_GET_LOOKAHEAD_REQUEST, + ctypes.pointer(delay) + ) + if result != opus.OPUS_OK: + raise PyOggError( + "Failed to obtain the algorithmic delay of "+ + "the Opus encoder: "+ + opus.opus_strerror(result).decode("utf") + ) + delay_samples = delay.value + return delay_samples + + + # + # Internal methods + # + + def _create_encoder(self) -> ctypes.pointer: + # To create an encoder, we must first allocate resources for it. + # We want Python to be responsible for the memory deallocation, + # and thus Python must be responsible for the initial memory + # allocation. + + # Check that the application has been defined + if self._application is None: + raise PyOggError( + "The application was not specified before "+ + "attempting to create an Opus encoder. Perhaps "+ + "encode() was called before set_application()?" + ) + application = self._application + + # Check that the sampling frequency has been defined + if self._samples_per_second is None: + raise PyOggError( + "The sampling frequency was not specified before "+ + "attempting to create an Opus encoder. Perhaps "+ + "encode() was called before set_sampling_frequency()?" + ) + + # The frequency must be passed in as a 32-bit int + samples_per_second = opus.opus_int32(self._samples_per_second) + + # Check that the number of channels has been defined + if self._channels is None: + raise PyOggError( + "The number of channels were not specified before "+ + "attempting to create an Opus encoder. Perhaps "+ + "encode() was called before set_channels()?" + ) + channels = self._channels + + # Obtain the number of bytes of memory required for the encoder + size = opus.opus_encoder_get_size(channels); + + # Allocate the required memory for the encoder + memory = ctypes.create_string_buffer(size) + + # Cast the newly-allocated memory as a pointer to an encoder. We + # could also have used opus.oe_p as the pointer type, but writing + # it out in full may be clearer. + encoder = ctypes.cast(memory, ctypes.POINTER(opus.OpusEncoder)) + + # Initialise the encoder + error = opus.opus_encoder_init( + encoder, + samples_per_second, + channels, + application + ) + + # Check that there hasn't been an error when initialising the + # encoder + if error != opus.OPUS_OK: + raise PyOggError( + "An error occurred while creating the encoder: "+ + opus.opus_strerror(error).decode("utf") + ) + + # Return our newly-created encoder + return encoder diff --git a/sbapp/pyogg/opus_file.py b/sbapp/pyogg/opus_file.py new file mode 100644 index 0000000..f8519f4 --- /dev/null +++ b/sbapp/pyogg/opus_file.py @@ -0,0 +1,106 @@ +import ctypes + +from . import ogg +from . import opus +from .pyogg_error import PyOggError +from .audio_file import AudioFile + +class OpusFile(AudioFile): + def __init__(self, path: str) -> None: + # Open the file + error = ctypes.c_int() + of = opus.op_open_file( + ogg.to_char_p(path), + ctypes.pointer(error) + ) + + # Check for errors + if error.value != 0: + raise PyOggError( + ("File '{}' couldn't be opened or doesn't exist. "+ + "Error code: {}").format(path, error.value) + ) + + # Extract the number of channels in the newly opened file + #: Number of channels in audio file. + self.channels = opus.op_channel_count(of, -1) + + # Allocate sufficient memory to store the entire PCM + pcm_size = opus.op_pcm_total(of, -1) + Buf = opus.opus_int16*(pcm_size*self.channels) + buf = Buf() + + # Create a pointer to the newly allocated memory. It + # seems we can only do pointer arithmetic on void + # pointers. See + # https://mattgwwalker.wordpress.com/2020/05/30/pointer-manipulation-in-python/ + buf_ptr = ctypes.cast( + ctypes.pointer(buf), + ctypes.c_void_p + ) + assert buf_ptr.value is not None # for mypy + buf_ptr_zero = buf_ptr.value + + #: Bytes per sample + self.bytes_per_sample = ctypes.sizeof(opus.opus_int16) + + # Read through the entire file, copying the PCM into the + # buffer + samples = 0 + while True: + # Calculate remaining buffer size + remaining_buffer = ( + len(buf) # int + - (buf_ptr.value + - buf_ptr_zero) // self.bytes_per_sample + ) + + # Convert buffer pointer to the desired type + ptr = ctypes.cast( + buf_ptr, + ctypes.POINTER(opus.opus_int16) + ) + + # Read the next section of PCM + ns = opus.op_read( + of, + ptr, + remaining_buffer, + ogg.c_int_p() + ) + + # Check for errors + if ns<0: + raise PyOggError( + "Error while reading OggOpus file. "+ + "Error code: {}".format(ns) + ) + + # Increment the pointer + buf_ptr.value += ( + ns + * self.bytes_per_sample + * self.channels + ) + assert buf_ptr.value is not None # for mypy + + samples += ns + + # Check if we've finished + if ns==0: + break + + # Close the open file + opus.op_free(of) + + # Opus files are always stored at 48k samples per second + #: Number of samples per second (per channel). Always 48,000. + self.frequency = 48000 + + # Cast buffer to a one-dimensional array of chars + #: Raw PCM data from audio file. + CharBuffer = ( + ctypes.c_byte + * (self.bytes_per_sample * self.channels * pcm_size) + ) + self.buffer = CharBuffer.from_buffer(buf) diff --git a/sbapp/pyogg/opus_file_stream.py b/sbapp/pyogg/opus_file_stream.py new file mode 100644 index 0000000..b3e1723 --- /dev/null +++ b/sbapp/pyogg/opus_file_stream.py @@ -0,0 +1,127 @@ +import ctypes + +from . import ogg +from . import opus +from .pyogg_error import PyOggError + +class OpusFileStream: + def __init__(self, path): + """Opens an OggOpus file as a stream. + + path should be a string giving the filename of the file to + open. Unicode file names may not work correctly. + + An exception will be raised if the file cannot be opened + correctly. + + """ + error = ctypes.c_int() + + self.of = opus.op_open_file(ogg.to_char_p(path), ctypes.pointer(error)) + + if error.value != 0: + self.of = None + raise PyOggError("file couldn't be opened or doesn't exist. Error code : {}".format(error.value)) + + #: Number of channels in audio file + self.channels = opus.op_channel_count(self.of, -1) + + #: Total PCM Length + self.pcm_size = opus.op_pcm_total(self.of, -1) + + #: Number of samples per second (per channel) + self.frequency = 48000 + + # The buffer size should be (per channel) large enough to + # hold 120ms (the largest possible Opus frame) at 48kHz. + # See https://opus-codec.org/docs/opusfile_api-0.7/group__stream__decoding.html#ga963c917749335e29bb2b698c1cb20a10 + self.buffer_size = self.frequency // 1000 * 120 * self.channels + self.Buf = opus.opus_int16 * self.buffer_size + self._buf = self.Buf() + self.buffer_ptr = ctypes.cast( + ctypes.pointer(self._buf), + opus.opus_int16_p + ) + + #: Bytes per sample + self.bytes_per_sample = ctypes.sizeof(opus.opus_int16) + + def __del__(self): + if self.of is not None: + opus.op_free(self.of) + + def get_buffer(self): + """Obtains the next frame of PCM samples. + + Returns an array of signed 16-bit integers. If the file + is in stereo, the left and right channels are interleaved. + + Returns None when all data has been read. + + The array that is returned should be either processed or + copied before the next call to :meth:`~get_buffer` or + :meth:`~get_buffer_as_array` as the array's memory is reused for + each call. + + """ + # Read the next frame + samples_read = opus.op_read( + self.of, + self.buffer_ptr, + self.buffer_size, + None + ) + + # Check for errors + if samples_read < 0: + raise PyOggError( + "Failed to read OpusFileStream. Error {:d}".format(samples_read) + ) + + # Check if we've reached the end of the stream + if samples_read == 0: + return None + + # Cast the pointer to opus_int16 to an array of the + # correct size + result_ptr = ctypes.cast( + self.buffer_ptr, + ctypes.POINTER(opus.opus_int16 * (samples_read*self.channels)) + ) + + # Convert the array to Python bytes + return bytes(result_ptr.contents) + + def get_buffer_as_array(self): + """Provides the buffer as a NumPy array. + + Note that the underlying data type is 16-bit signed + integers. + + Does not copy the underlying data, so the returned array + should either be processed or copied before the next call + to :meth:`~get_buffer` or :meth:`~get_buffer_as_array`. + + """ + import numpy # type: ignore + + # Read the next samples from the stream + buf = self.get_buffer() + + # Check if we've come to the end of the stream + if buf is None: + return None + + # Convert the bytes buffer to a NumPy array + array = numpy.frombuffer( + buf, + dtype=numpy.int16 + ) + + # Reshape the array + return array.reshape( + (len(buf) + // self.bytes_per_sample + // self.channels, + self.channels) + ) diff --git a/sbapp/pyogg/py.typed b/sbapp/pyogg/py.typed new file mode 100644 index 0000000..d4defd9 --- /dev/null +++ b/sbapp/pyogg/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. This package uses inline types. \ No newline at end of file diff --git a/sbapp/pyogg/pyogg_error.py b/sbapp/pyogg/pyogg_error.py new file mode 100644 index 0000000..35f28bf --- /dev/null +++ b/sbapp/pyogg/pyogg_error.py @@ -0,0 +1,2 @@ +class PyOggError(Exception): + pass diff --git a/sbapp/pyogg/vorbis.py b/sbapp/pyogg/vorbis.py new file mode 100644 index 0000000..a8432ba --- /dev/null +++ b/sbapp/pyogg/vorbis.py @@ -0,0 +1,855 @@ +############################################################ +# Vorbis license: # +############################################################ +""" +Copyright (c) 2002-2015 Xiph.org Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import ctypes +import ctypes.util +from traceback import print_exc as _print_exc +import os + +OV_EXCLUDE_STATIC_CALLBACKS = False + +__MINGW32__ = False + +_WIN32 = False + +from .ogg import * + +from .library_loader import ExternalLibrary, ExternalLibraryError + +__here = os.getcwd() + +libvorbis = None + +try: + names = { + "Windows": "libvorbis.dll", + "Darwin": "libvorbis.0.dylib", + "external": "vorbis" + } + libvorbis = Library.load(names, tests = [lambda lib: hasattr(lib, "vorbis_info_init")]) +except ExternalLibraryError: + pass +except: + _print_exc() + +libvorbisfile = None + +try: + names = { + "Windows": "libvorbisfile.dll", + "Darwin": "libvorbisfile.3.dylib", + "external": "vorbisfile" + } + libvorbisfile = Library.load(names, tests = [lambda lib: hasattr(lib, "ov_clear")]) +except ExternalLibraryError: + pass +except: + _print_exc() + +libvorbisenc = None + +# In some cases, libvorbis may also have the libvorbisenc functionality. +libvorbis_is_also_libvorbisenc = True + +for f in ("vorbis_encode_ctl", + "vorbis_encode_init", + "vorbis_encode_init_vbr", + "vorbis_encode_setup_init", + "vorbis_encode_setup_managed", + "vorbis_encode_setup_vbr"): + if not hasattr(libvorbis, f): + libvorbis_is_also_libvorbisenc = False + break + +if libvorbis_is_also_libvorbisenc: + libvorbisenc = libvorbis +else: + try: + names = { + "Windows": "libvorbisenc.dll", + "Darwin": "libvorbisenc.2.dylib", + "external": "vorbisenc" + } + libvorbisenc = Library.load(names, tests = [lambda lib: hasattr(lib, "vorbis_encode_init")]) + except ExternalLibraryError: + pass + except: + _print_exc() + +if libvorbis is None: + PYOGG_VORBIS_AVAIL = False +else: + PYOGG_VORBIS_AVAIL = True + +if libvorbisfile is None: + PYOGG_VORBIS_FILE_AVAIL = False +else: + PYOGG_VORBIS_FILE_AVAIL = True + +if libvorbisenc is None: + PYOGG_VORBIS_ENC_AVAIL = False +else: + PYOGG_VORBIS_ENC_AVAIL = True + +# FIXME: What's the story with the lack of checking for PYOGG_VORBIS_ENC_AVAIL? +# We just seem to assume that it's available. + +if PYOGG_OGG_AVAIL and PYOGG_VORBIS_AVAIL and PYOGG_VORBIS_FILE_AVAIL: + # Sanity check also satisfies mypy type checking + assert libogg is not None + assert libvorbis is not None + assert libvorbisfile is not None + + + # codecs + class vorbis_info(ctypes.Structure): + """ + Wrapper for: + typedef struct vorbis_info vorbis_info; + """ + _fields_ = [("version", c_int), + ("channels", c_int), + ("rate", c_long), + + ("bitrate_upper", c_long), + ("bitrate_nominal", c_long), + ("bitrate_lower", c_long), + ("bitrate_window", c_long), + ("codec_setup", c_void_p)] + + + + class vorbis_dsp_state(ctypes.Structure): + """ + Wrapper for: + typedef struct vorbis_dsp_state vorbis_dsp_state; + """ + _fields_ = [("analysisp", c_int), + ("vi", POINTER(vorbis_info)), + ("pcm", c_float_p_p), + ("pcmret", c_float_p_p), + ("pcm_storage", c_int), + ("pcm_current", c_int), + ("pcm_returned", c_int), + + ("preextrapolate", c_int), + ("eofflag", c_int), + + ("lW", c_long), + ("W", c_long), + ("nW", c_long), + ("centerW", c_long), + + ("granulepos", ogg_int64_t), + ("sequence", ogg_int64_t), + + ("glue_bits", ogg_int64_t), + ("time_bits", ogg_int64_t), + ("floor_bits", ogg_int64_t), + ("res_bits", ogg_int64_t), + + ("backend_state", c_void_p)] + + class alloc_chain(ctypes.Structure): + """ + Wrapper for: + typedef struct alloc_chain; + """ + pass + + alloc_chain._fields_ = [("ptr", c_void_p), + ("next", POINTER(alloc_chain))] + + class vorbis_block(ctypes.Structure): + """ + Wrapper for: + typedef struct vorbis_block vorbis_block; + """ + _fields_ = [("pcm", c_float_p_p), + ("opb", oggpack_buffer), + ("lW", c_long), + ("W", c_long), + ("nW", c_long), + ("pcmend", c_int), + ("mode", c_int), + + ("eofflag", c_int), + ("granulepos", ogg_int64_t), + ("sequence", ogg_int64_t), + ("vd", POINTER(vorbis_dsp_state)), + + ("localstore", c_void_p), + ("localtop", c_long), + ("localalloc", c_long), + ("totaluse", c_long), + ("reap", POINTER(alloc_chain)), + + ("glue_bits", c_long), + ("time_bits", c_long), + ("floor_bits", c_long), + ("res_bits", c_long), + + ("internal", c_void_p)] + + class vorbis_comment(ctypes.Structure): + """ + Wrapper for: + typedef struct vorbis_comment vorbis_comment; + """ + _fields_ = [("user_comments", c_char_p_p), + ("comment_lengths", c_int_p), + ("comments", c_int), + ("vendor", c_char_p)] + + + + vi_p = POINTER(vorbis_info) + vc_p = POINTER(vorbis_comment) + vd_p = POINTER(vorbis_dsp_state) + vb_p = POINTER(vorbis_block) + + libvorbis.vorbis_info_init.restype = None + libvorbis.vorbis_info_init.argtypes = [vi_p] + def vorbis_info_init(vi): + libvorbis.vorbis_info_init(vi) + + libvorbis.vorbis_info_clear.restype = None + libvorbis.vorbis_info_clear.argtypes = [vi_p] + def vorbis_info_clear(vi): + libvorbis.vorbis_info_clear(vi) + + libvorbis.vorbis_info_blocksize.restype = c_int + libvorbis.vorbis_info_blocksize.argtypes = [vi_p, c_int] + def vorbis_info_blocksize(vi, zo): + return libvorbis.vorbis_info_blocksize(vi, zo) + + libvorbis.vorbis_comment_init.restype = None + libvorbis.vorbis_comment_init.argtypes = [vc_p] + def vorbis_comment_init(vc): + libvorbis.vorbis_comment_init(vc) + + libvorbis.vorbis_comment_add.restype = None + libvorbis.vorbis_comment_add.argtypes = [vc_p, c_char_p] + def vorbis_comment_add(vc, comment): + libvorbis.vorbis_comment_add(vc, comment) + + libvorbis.vorbis_comment_add_tag.restype = None + libvorbis.vorbis_comment_add_tag.argtypes = [vc_p, c_char_p, c_char_p] + def vorbis_comment_add_tag(vc, tag, comment): + libvorbis.vorbis_comment_add_tag(vc, tag, comment) + + libvorbis.vorbis_comment_query.restype = c_char_p + libvorbis.vorbis_comment_query.argtypes = [vc_p, c_char_p, c_int] + def vorbis_comment_query(vc, tag, count): + libvorbis.vorbis_comment_query(vc, tag, count) + + libvorbis.vorbis_comment_query_count.restype = c_int + libvorbis.vorbis_comment_query_count.argtypes = [vc_p, c_char_p] + def vorbis_comment_query_count(vc, tag): + libvorbis.vorbis_comment_query_count(vc, tag) + + libvorbis.vorbis_comment_clear.restype = None + libvorbis.vorbis_comment_clear.argtypes = [vc_p] + def vorbis_comment_clear(vc): + libvorbis.vorbis_comment_clear(vc) + + + + libvorbis.vorbis_block_init.restype = c_int + libvorbis.vorbis_block_init.argtypes = [vd_p, vb_p] + def vorbis_block_init(v,vb): + return libvorbis.vorbis_block_init(v,vb) + + libvorbis.vorbis_block_clear.restype = c_int + libvorbis.vorbis_block_clear.argtypes = [vb_p] + def vorbis_block_clear(vb): + return libvorbis.vorbis_block_clear(vb) + + libvorbis.vorbis_dsp_clear.restype = None + libvorbis.vorbis_dsp_clear.argtypes = [vd_p] + def vorbis_dsp_clear(v): + return libvorbis.vorbis_dsp_clear(v) + + libvorbis.vorbis_granule_time.restype = c_double + libvorbis.vorbis_granule_time.argtypes = [vd_p, ogg_int64_t] + def vorbis_granule_time(v, granulepos): + return libvorbis.vorbis_granule_time(v, granulepos) + + + + libvorbis.vorbis_version_string.restype = c_char_p + libvorbis.vorbis_version_string.argtypes = [] + def vorbis_version_string(): + return libvorbis.vorbis_version_string() + + + + + + libvorbis.vorbis_analysis_init.restype = c_int + libvorbis.vorbis_analysis_init.argtypes = [vd_p, vi_p] + def vorbis_analysis_init(v, vi): + return libvorbis.vorbis_analysis_init(v, vi) + + libvorbis.vorbis_commentheader_out.restype = c_int + libvorbis.vorbis_commentheader_out.argtypes = [vc_p, op_p] + def vorbis_commentheader_out(vc, op): + return libvorbis.vorbis_commentheader_out(vc, op) + + libvorbis.vorbis_analysis_headerout.restype = c_int + libvorbis.vorbis_analysis_headerout.argtypes = [vd_p, vc_p, op_p, op_p, op_p] + def vorbis_analysis_headerout(v,vc, op, op_comm, op_code): + return libvorbis.vorbis_analysis_headerout(v,vc, op, op_comm, op_code) + + libvorbis.vorbis_analysis_buffer.restype = c_float_p_p + libvorbis.vorbis_analysis_buffer.argtypes = [vd_p, c_int] + def vorbis_analysis_buffer(v, vals): + return libvorbis.vorbis_analysis_buffer(v, vals) + + libvorbis.vorbis_analysis_wrote.restype = c_int + libvorbis.vorbis_analysis_wrote.argtypes = [vd_p, c_int] + def vorbis_analysis_wrote(v, vals): + return libvorbis.vorbis_analysis_wrote(v, vals) + + libvorbis.vorbis_analysis_blockout.restype = c_int + libvorbis.vorbis_analysis_blockout.argtypes = [vd_p, vb_p] + def vorbis_analysis_blockout(v, vb): + return libvorbis.vorbis_analysis_blockout(v, vb) + + libvorbis.vorbis_analysis.restype = c_int + libvorbis.vorbis_analysis.argtypes = [vb_p, op_p] + def vorbis_analysis(vb, op): + return libvorbis.vorbis_analysis(vb, op) + + + + + libvorbis.vorbis_bitrate_addblock.restype = c_int + libvorbis.vorbis_bitrate_addblock.argtypes = [vb_p] + def vorbis_bitrate_addblock(vb): + return libvorbis.vorbis_bitrate_addblock(vb) + + libvorbis.vorbis_bitrate_flushpacket.restype = c_int + libvorbis.vorbis_bitrate_flushpacket.argtypes = [vd_p, op_p] + def vorbis_bitrate_flushpacket(vd, op): + return libvorbis.vorbis_bitrate_flushpacket(vd, op) + + + + + libvorbis.vorbis_synthesis_idheader.restype = c_int + libvorbis.vorbis_synthesis_idheader.argtypes = [op_p] + def vorbis_synthesis_idheader(op): + return libvorbis.vorbis_synthesis_idheader(op) + + libvorbis.vorbis_synthesis_headerin.restype = c_int + libvorbis.vorbis_synthesis_headerin.argtypes = [vi_p, vc_p, op_p] + def vorbis_synthesis_headerin(vi, vc, op): + return libvorbis.vorbis_synthesis_headerin(vi, vc, op) + + + + + libvorbis.vorbis_synthesis_init.restype = c_int + libvorbis.vorbis_synthesis_init.argtypes = [vd_p, vi_p] + def vorbis_synthesis_init(v,vi): + return libvorbis.vorbis_synthesis_init(v,vi) + + libvorbis.vorbis_synthesis_restart.restype = c_int + libvorbis.vorbis_synthesis_restart.argtypes = [vd_p] + def vorbis_synthesis_restart(v): + return libvorbis.vorbis_synthesis_restart(v) + + libvorbis.vorbis_synthesis.restype = c_int + libvorbis.vorbis_synthesis.argtypes = [vb_p, op_p] + def vorbis_synthesis(vb, op): + return libvorbis.vorbis_synthesis(vb, op) + + libvorbis.vorbis_synthesis_trackonly.restype = c_int + libvorbis.vorbis_synthesis_trackonly.argtypes = [vb_p, op_p] + def vorbis_synthesis_trackonly(vb, op): + return libvorbis.vorbis_synthesis_trackonly(vb, op) + + libvorbis.vorbis_synthesis_blockin.restype = c_int + libvorbis.vorbis_synthesis_blockin.argtypes = [vd_p, vb_p] + def vorbis_synthesis_blockin(v, vb): + return libvorbis.vorbis_synthesis_blockin(v, vb) + + libvorbis.vorbis_synthesis_pcmout.restype = c_int + libvorbis.vorbis_synthesis_pcmout.argtypes = [vd_p, c_float_p_p_p] + def vorbis_synthesis_pcmout(v, pcm): + return libvorbis.vorbis_synthesis_pcmout(v, pcm) + + libvorbis.vorbis_synthesis_lapout.restype = c_int + libvorbis.vorbis_synthesis_lapout.argtypes = [vd_p, c_float_p_p_p] + def vorbis_synthesis_lapout(v, pcm): + return libvorbis.vorbis_synthesis_lapout(v, pcm) + + libvorbis.vorbis_synthesis_read.restype = c_int + libvorbis.vorbis_synthesis_read.argtypes = [vd_p, c_int] + def vorbis_synthesis_read(v, samples): + return libvorbis.vorbis_synthesis_read(v, samples) + + libvorbis.vorbis_packet_blocksize.restype = c_long + libvorbis.vorbis_packet_blocksize.argtypes = [vi_p, op_p] + def vorbis_packet_blocksize(vi, op): + return libvorbis.vorbis_packet_blocksize(vi, op) + + + + libvorbis.vorbis_synthesis_halfrate.restype = c_int + libvorbis.vorbis_synthesis_halfrate.argtypes = [vi_p, c_int] + def vorbis_synthesis_halfrate(v, flag): + return libvorbis.vorbis_synthesis_halfrate(v, flag) + + libvorbis.vorbis_synthesis_halfrate_p.restype = c_int + libvorbis.vorbis_synthesis_halfrate_p.argtypes = [vi_p] + def vorbis_synthesis_halfrate_p(vi): + return libvorbis.vorbis_synthesis_halfrate_p(vi) + + OV_FALSE = -1 + OV_EOF = -2 + OV_HOLE = -3 + + OV_EREAD = -128 + OV_EFAULT = -129 + OV_EIMPL =-130 + OV_EINVAL =-131 + OV_ENOTVORBIS =-132 + OV_EBADHEADER =-133 + OV_EVERSION =-134 + OV_ENOTAUDIO =-135 + OV_EBADPACKET =-136 + OV_EBADLINK =-137 + OV_ENOSEEK =-138 + # end of codecs + + # vorbisfile + read_func = ctypes.CFUNCTYPE(c_size_t, + c_void_p, + c_size_t, + c_size_t, + c_void_p) + + seek_func = ctypes.CFUNCTYPE(c_int, + c_void_p, + ogg_int64_t, + c_int) + + close_func = ctypes.CFUNCTYPE(c_int, + c_void_p) + + tell_func = ctypes.CFUNCTYPE(c_long, + c_void_p) + + class ov_callbacks(ctypes.Structure): + """ + Wrapper for: + typedef struct ov_callbacks; + """ + + _fields_ = [("read_func", read_func), + ("seek_func", seek_func), + ("close_func", close_func), + ("tell_func", tell_func)] + + NOTOPEN = 0 + PARTOPEN = 1 + OPENED = 2 + STREAMSET = 3 + INITSET = 4 + + class OggVorbis_File(ctypes.Structure): + """ + Wrapper for: + typedef struct OggVorbis_File OggVorbis_File; + """ + + _fields_ = [("datasource", c_void_p), + ("seekable", c_int), + ("offset", ogg_int64_t), + ("end", ogg_int64_t), + ("oy", ogg_sync_state), + + ("links", c_int), + ("offsets", ogg_int64_t_p), + ("dataoffsets", ogg_int64_t_p), + ("serialnos", c_long_p), + ("pcmlengths", ogg_int64_t_p), + ("vi", vi_p), + ("vc", vc_p), + + ("pcm_offset", ogg_int64_t), + ("ready_state", c_int), + ("current_serialno", c_long), + ("current_link", c_int), + + ("bittrack", c_double), + ("samptrack", c_double), + + ("os", ogg_stream_state), + + ("vd", vorbis_dsp_state), + ("vb", vorbis_block), + + ("callbacks", ov_callbacks)] + vf_p = POINTER(OggVorbis_File) + + libvorbisfile.ov_clear.restype = c_int + libvorbisfile.ov_clear.argtypes = [vf_p] + + def ov_clear(vf): + return libvorbisfile.ov_clear(vf) + + libvorbisfile.ov_fopen.restype = c_int + libvorbisfile.ov_fopen.argtypes = [c_char_p, vf_p] + + def ov_fopen(path, vf): + return libvorbisfile.ov_fopen(to_char_p(path), vf) + + libvorbisfile.ov_open_callbacks.restype = c_int + libvorbisfile.ov_open_callbacks.argtypes = [c_void_p, vf_p, c_char_p, c_long, ov_callbacks] + + def ov_open_callbacks(datasource, vf, initial, ibytes, callbacks): + return libvorbisfile.ov_open_callbacks(datasource, vf, initial, ibytes, callbacks) + + def ov_open(*args, **kw): + raise PyOggError("ov_open is not supported, please use ov_fopen instead") + + def ov_test(*args, **kw): + raise PyOggError("ov_test is not supported") + + libvorbisfile.ov_test_callbacks.restype = c_int + libvorbisfile.ov_test_callbacks.argtypes = [c_void_p, vf_p, c_char_p, c_long, ov_callbacks] + + def ov_test_callbacks(datasource, vf, initial, ibytes, callbacks): + return libvorbisfile.ov_test_callbacks(datasource, vf, initial, ibytes, callbacks) + + libvorbisfile.ov_test_open.restype = c_int + libvorbisfile.ov_test_open.argtypes = [vf_p] + + def ov_test_open(vf): + return libvorbisfile.ov_test_open(vf) + + + + + libvorbisfile.ov_bitrate.restype = c_long + libvorbisfile.ov_bitrate.argtypes = [vf_p, c_int] + + def ov_bitrate(vf, i): + return libvorbisfile.ov_bitrate(vf, i) + + libvorbisfile.ov_bitrate_instant.restype = c_long + libvorbisfile.ov_bitrate_instant.argtypes = [vf_p] + + def ov_bitrate_instant(vf): + return libvorbisfile.ov_bitrate_instant(vf) + + libvorbisfile.ov_streams.restype = c_long + libvorbisfile.ov_streams.argtypes = [vf_p] + + def ov_streams(vf): + return libvorbisfile.ov_streams(vf) + + libvorbisfile.ov_seekable.restype = c_long + libvorbisfile.ov_seekable.argtypes = [vf_p] + + def ov_seekable(vf): + return libvorbisfile.ov_seekable(vf) + + libvorbisfile.ov_serialnumber.restype = c_long + libvorbisfile.ov_serialnumber.argtypes = [vf_p, c_int] + + def ov_serialnumber(vf, i): + return libvorbisfile.ov_serialnumber(vf, i) + + + + libvorbisfile.ov_raw_total.restype = ogg_int64_t + libvorbisfile.ov_raw_total.argtypes = [vf_p, c_int] + + def ov_raw_total(vf, i): + return libvorbisfile.ov_raw_total(vf, i) + + libvorbisfile.ov_pcm_total.restype = ogg_int64_t + libvorbisfile.ov_pcm_total.argtypes = [vf_p, c_int] + + def ov_pcm_total(vf, i): + return libvorbisfile.ov_pcm_total(vf, i) + + libvorbisfile.ov_time_total.restype = c_double + libvorbisfile.ov_time_total.argtypes = [vf_p, c_int] + + def ov_time_total(vf, i): + return libvorbisfile.ov_time_total(vf, i) + + + + + libvorbisfile.ov_raw_seek.restype = c_int + libvorbisfile.ov_raw_seek.argtypes = [vf_p, ogg_int64_t] + + def ov_raw_seek(vf, pos): + return libvorbisfile.ov_raw_seek(vf, pos) + + libvorbisfile.ov_pcm_seek.restype = c_int + libvorbisfile.ov_pcm_seek.argtypes = [vf_p, ogg_int64_t] + + def ov_pcm_seek(vf, pos): + return libvorbisfile.ov_pcm_seek(vf, pos) + + libvorbisfile.ov_pcm_seek_page.restype = c_int + libvorbisfile.ov_pcm_seek_page.argtypes = [vf_p, ogg_int64_t] + + def ov_pcm_seek_page(vf, pos): + return libvorbisfile.ov_pcm_seek_page(vf, pos) + + libvorbisfile.ov_time_seek.restype = c_int + libvorbisfile.ov_time_seek.argtypes = [vf_p, c_double] + + def ov_time_seek(vf, pos): + return libvorbisfile.ov_time_seek(vf, pos) + + libvorbisfile.ov_time_seek_page.restype = c_int + libvorbisfile.ov_time_seek_page.argtypes = [vf_p, c_double] + + def ov_time_seek_page(vf, pos): + return libvorbisfile.ov_time_seek_page(vf, pos) + + + + + libvorbisfile.ov_raw_seek_lap.restype = c_int + libvorbisfile.ov_raw_seek_lap.argtypes = [vf_p, ogg_int64_t] + + def ov_raw_seek_lap(vf, pos): + return libvorbisfile.ov_raw_seek_lap(vf, pos) + + libvorbisfile.ov_pcm_seek_lap.restype = c_int + libvorbisfile.ov_pcm_seek_lap.argtypes = [vf_p, ogg_int64_t] + + def ov_pcm_seek_lap(vf, pos): + return libvorbisfile.ov_pcm_seek_lap(vf, pos) + + libvorbisfile.ov_pcm_seek_page_lap.restype = c_int + libvorbisfile.ov_pcm_seek_page_lap.argtypes = [vf_p, ogg_int64_t] + + def ov_pcm_seek_page_lap(vf, pos): + return libvorbisfile.ov_pcm_seek_page_lap(vf, pos) + + libvorbisfile.ov_time_seek_lap.restype = c_int + libvorbisfile.ov_time_seek_lap.argtypes = [vf_p, c_double] + + def ov_time_seek_lap(vf, pos): + return libvorbisfile.ov_time_seek_lap(vf, pos) + + libvorbisfile.ov_time_seek_page_lap.restype = c_int + libvorbisfile.ov_time_seek_page_lap.argtypes = [vf_p, c_double] + + def ov_time_seek_page_lap(vf, pos): + return libvorbisfile.ov_time_seek_page_lap(vf, pos) + + + + libvorbisfile.ov_raw_tell.restype = ogg_int64_t + libvorbisfile.ov_raw_tell.argtypes = [vf_p] + + def ov_raw_tell(vf): + return libvorbisfile.ov_raw_tell(vf) + + libvorbisfile.ov_pcm_tell.restype = ogg_int64_t + libvorbisfile.ov_pcm_tell.argtypes = [vf_p] + + def ov_pcm_tell(vf): + return libvorbisfile.ov_pcm_tell(vf) + + libvorbisfile.ov_time_tell.restype = c_double + libvorbisfile.ov_time_tell.argtypes = [vf_p] + + def ov_time_tell(vf): + return libvorbisfile.ov_time_tell(vf) + + + + libvorbisfile.ov_info.restype = vi_p + libvorbisfile.ov_info.argtypes = [vf_p, c_int] + + def ov_info(vf, link): + return libvorbisfile.ov_info(vf, link) + + libvorbisfile.ov_comment.restype = vc_p + libvorbisfile.ov_comment.argtypes = [vf_p, c_int] + + def ov_comment(vf, link): + return libvorbisfile.ov_comment(vf, link) + + + + libvorbisfile.ov_read_float.restype = c_long + libvorbisfile.ov_read_float.argtypes = [vf_p, c_float_p_p_p, c_int, c_int_p] + + def ov_read_float(vf, pcm_channels, samples, bitstream): + return libvorbisfile.ov_read_float(vf, pcm_channels, samples, bitstream) + + filter_ = ctypes.CFUNCTYPE(None, + c_float_p_p, + c_long, + c_long, + c_void_p) + + try: + libvorbisfile.ov_read_filter.restype = c_long + libvorbisfile.ov_read_filter.argtypes = [vf_p, c_char_p, c_int, c_int, c_int, c_int, c_int_p, filter_, c_void_p] + + def ov_read_filter(vf, buffer, length, bigendianp, word, sgned, bitstream, filter_, filter_param): + return libvorbisfile.ov_read_filter(vf, buffer, length, bigendianp, word, sgned, bitstream, filter_, filter_param) + except: + pass + + libvorbisfile.ov_read.restype = c_long + libvorbisfile.ov_read.argtypes = [vf_p, c_char_p, c_int, c_int, c_int, c_int, c_int_p] + + def ov_read(vf, buffer, length, bigendianp, word, sgned, bitstream): + return libvorbisfile.ov_read(vf, buffer, length, bigendianp, word, sgned, bitstream) + + libvorbisfile.ov_crosslap.restype = c_int + libvorbisfile.ov_crosslap.argtypes = [vf_p, vf_p] + + def ov_crosslap(vf1, cf2): + return libvorbisfile.ov_crosslap(vf1, vf2) + + + + + libvorbisfile.ov_halfrate.restype = c_int + libvorbisfile.ov_halfrate.argtypes = [vf_p, c_int] + + def ov_halfrate(vf, flag): + return libvorbisfile.ov_halfrate(vf, flag) + + libvorbisfile.ov_halfrate_p.restype = c_int + libvorbisfile.ov_halfrate_p.argtypes = [vf_p] + + def ov_halfrate_p(vf): + return libvorbisfile.ov_halfrate_p(vf) + # end of vorbisfile + + try: + # vorbisenc + + # Sanity check also satisfies mypy type checking + assert libvorbisenc is not None + + libvorbisenc.vorbis_encode_init.restype = c_int + libvorbisenc.vorbis_encode_init.argtypes = [vi_p, c_long, c_long, c_long, c_long, c_long] + + def vorbis_encode_init(vi, channels, rate, max_bitrate, nominal_bitrate, min_bitrate): + return libvorbisenc.vorbis_encode_init(vi, channels, rate, max_bitrate, nominal_bitrate, min_bitrate) + + libvorbisenc.vorbis_encode_setup_managed.restype = c_int + libvorbisenc.vorbis_encode_setup_managed.argtypes = [vi_p, c_long, c_long, c_long, c_long, c_long] + + def vorbis_encode_setup_managed(vi, channels, rate, max_bitrate, nominal_bitrate, min_bitrate): + return libvorbisenc.vorbis_encode_setup_managed(vi, channels, rate, max_bitrate, nominal_bitrate, min_bitrate) + + libvorbisenc.vorbis_encode_setup_vbr.restype = c_int + libvorbisenc.vorbis_encode_setup_vbr.argtypes = [vi_p, c_long, c_long, c_float] + + def vorbis_encode_setup_vbr(vi, channels, rate, quality): + return libvorbisenc.vorbis_encode_setup_vbr(vi, channels, rate, quality) + + libvorbisenc.vorbis_encode_init_vbr.restype = c_int + libvorbisenc.vorbis_encode_init_vbr.argtypes = [vi_p, c_long, c_long, c_float] + + def vorbis_encode_init_vbr(vi, channels, rate, quality): + return libvorbisenc.vorbis_encode_init_vbr(vi, channels, rate, quality) + + libvorbisenc.vorbis_encode_setup_init.restype = c_int + libvorbisenc.vorbis_encode_setup_init.argtypes = [vi_p] + + def vorbis_encode_setup_init(vi): + return libvorbisenc.vorbis_encode_setup_init(vi) + + libvorbisenc.vorbis_encode_ctl.restype = c_int + libvorbisenc.vorbis_encode_ctl.argtypes = [vi_p, c_int, c_void_p] + + def vorbis_encode_ctl(vi, number, arg): + return libvorbisenc.vorbis_encode_ctl(vi, number, arg) + + class ovectl_ratemanage_arg(ctypes.Structure): + _fields_ = [("management_active", c_int), + ("bitrate_hard_min", c_long), + ("bitrate_hard_max", c_long), + ("bitrate_hard_window", c_double), + ("bitrate_av_lo", c_long), + ("bitrate_av_hi", c_long), + ("bitrate_av_window", c_double), + ("bitrate_av_window_center", c_double)] + + class ovectl_ratemanage2_arg(ctypes.Structure): + _fields_ = [("management_active", c_int), + ("bitrate_limit_min_kbps", c_long), + ("bitrate_limit_max_kbps", c_long), + ("bitrate_limit_reservoir_bits", c_long), + ("bitrate_limit_reservoir_bias", c_double), + ("bitrate_average_kbps", c_long), + ("bitrate_average_damping", c_double)] + + OV_ECTL_RATEMANAGE2_GET =0x14 + + OV_ECTL_RATEMANAGE2_SET =0x15 + + OV_ECTL_LOWPASS_GET =0x20 + + OV_ECTL_LOWPASS_SET =0x21 + + OV_ECTL_IBLOCK_GET =0x30 + + OV_ECTL_IBLOCK_SET =0x31 + + OV_ECTL_COUPLING_GET =0x40 + + OV_ECTL_COUPLING_SET =0x41 + + OV_ECTL_RATEMANAGE_GET =0x10 + + OV_ECTL_RATEMANAGE_SET =0x11 + + OV_ECTL_RATEMANAGE_AVG =0x12 + + OV_ECTL_RATEMANAGE_HARD =0x13 + # end of vorbisenc + except: + pass diff --git a/sbapp/pyogg/vorbis_file.py b/sbapp/pyogg/vorbis_file.py new file mode 100644 index 0000000..918f1e8 --- /dev/null +++ b/sbapp/pyogg/vorbis_file.py @@ -0,0 +1,161 @@ +import ctypes + +from . import vorbis +from .audio_file import AudioFile +from .pyogg_error import PyOggError + +# TODO: Issue #70: Vorbis files with multiple logical bitstreams could +# be supported by chaining VorbisFile instances (with say a 'next' +# attribute that points to the next VorbisFile that would contain the +# PCM for the next logical bitstream). A considerable constraint to +# implementing this was that examples files that demonstrated multiple +# logical bitstreams couldn't be found or created. Note that even +# Audacity doesn't handle multiple logical bitstreams (see +# https://wiki.audacityteam.org/wiki/OGG#Importing_multiple_stream_files). + +# TODO: Issue #53: Unicode file names are not well supported. +# They may work in macOS and Linux, they don't work under Windows. + +class VorbisFile(AudioFile): + def __init__(self, + path: str, + bytes_per_sample: int = 2, + signed:bool = True) -> None: + """Load an OggVorbis File. + + path specifies the location of the Vorbis file. Unicode + filenames may not work correctly under Windows. + + bytes_per_sample specifies the word size of the PCM. It may + be either 1 or 2. Specifying one byte per sample will save + memory but will likely decrease the quality of the decoded + audio. + + Only Vorbis files with a single logical bitstream are + supported. + + """ + # Sanity check the number of bytes per sample + assert bytes_per_sample==1 or bytes_per_sample==2 + + # Sanity check that the vorbis library is available (for mypy) + assert vorbis.libvorbisfile is not None + + #: Bytes per sample + self.bytes_per_sample = bytes_per_sample + + #: Samples are signed (rather than unsigned) + self.signed = signed + + # Create a Vorbis File structure + vf = vorbis.OggVorbis_File() + + # Attempt to open the Vorbis file + error = vorbis.libvorbisfile.ov_fopen( + vorbis.to_char_p(path), + ctypes.byref(vf) + ) + + # Check for errors during opening + if error != 0: + raise PyOggError( + ("File '{}' couldn't be opened or doesn't exist. "+ + "Error code : {}").format(path, error) + ) + + # Extract info from the Vorbis file + info = vorbis.libvorbisfile.ov_info( + ctypes.byref(vf), + -1 # the current logical bitstream + ) + + #: Number of channels in audio file. + self.channels = info.contents.channels + + #: Number of samples per second (per channel), 44100 for + # example. + self.frequency = info.contents.rate + + # Extract the total number of PCM samples for the first + # logical bitstream + pcm_length_samples = vorbis.libvorbisfile.ov_pcm_total( + ctypes.byref(vf), + 0 # to extract the length of the first logical bitstream + ) + + # Create a memory block to store the entire PCM + Buffer = ( + ctypes.c_char + * ( + pcm_length_samples + * self.bytes_per_sample + * self.channels + ) + ) + self.buffer = Buffer() + + # Create a pointer to the newly allocated memory. It + # seems we can only do pointer arithmetic on void + # pointers. See + # https://mattgwwalker.wordpress.com/2020/05/30/pointer-manipulation-in-python/ + buf_ptr = ctypes.cast( + ctypes.pointer(self.buffer), + ctypes.c_void_p + ) + + # Storage for the index of the logical bitstream + bitstream_previous = None + bitstream = ctypes.c_int() + + # Set bytes remaining to read into PCM + read_size = len(self.buffer) + + while True: + # Convert buffer pointer to the desired type + ptr = ctypes.cast( + buf_ptr, + ctypes.POINTER(ctypes.c_char) + ) + + # Attempt to decode PCM from the Vorbis file + result = vorbis.libvorbisfile.ov_read( + ctypes.byref(vf), + ptr, + read_size, + 0, # Little endian + self.bytes_per_sample, + int(self.signed), + ctypes.byref(bitstream) + ) + + # Check for errors + if result < 0: + raise PyOggError( + "An error occurred decoding the Vorbis file: "+ + f"Error code: {result}" + ) + + # Check that the bitstream hasn't changed as we only + # support Vorbis files with a single logical bitstream. + if bitstream_previous is None: + bitstream_previous = bitstream + else: + if bitstream_previous != bitstream: + raise PyOggError( + "PyOgg currently supports Vorbis files "+ + "with only one logical stream" + ) + + # Check for end of file + if result == 0: + break + + # Calculate the number of bytes remaining to read into PCM + read_size -= result + + # Update the pointer into the buffer + buf_ptr.value += result + + + # Close the file and clean up memory + vorbis.libvorbisfile.ov_clear(ctypes.byref(vf)) diff --git a/sbapp/pyogg/vorbis_file_stream.py b/sbapp/pyogg/vorbis_file_stream.py new file mode 100644 index 0000000..57677ba --- /dev/null +++ b/sbapp/pyogg/vorbis_file_stream.py @@ -0,0 +1,110 @@ +import ctypes + +from . import vorbis +from .pyogg_error import PyOggError + +class VorbisFileStream: + def __init__(self, path, buffer_size=8192): + self.exists = False + self._buffer_size = buffer_size + + self.vf = vorbis.OggVorbis_File() + error = vorbis.ov_fopen(path, ctypes.byref(self.vf)) + if error != 0: + raise PyOggError("file couldn't be opened or doesn't exist. Error code : {}".format(error)) + + info = vorbis.ov_info(ctypes.byref(self.vf), -1) + + #: Number of channels in audio file. + self.channels = info.contents.channels + + #: Number of samples per second (per channel). Always + # 48,000. + self.frequency = info.contents.rate + + array = (ctypes.c_char*(self._buffer_size*self.channels))() + + self.buffer_ = ctypes.cast(ctypes.pointer(array), ctypes.c_char_p) + + self.bitstream = ctypes.c_int() + self.bitstream_pointer = ctypes.pointer(self.bitstream) + + self.exists = True # TODO: is this the best place for this statement? + + #: Bytes per sample + self.bytes_per_sample = 2 # TODO: Where is this defined? + + def __del__(self): + if self.exists: + vorbis.ov_clear(ctypes.byref(self.vf)) + self.exists = False + + def clean_up(self): + vorbis.ov_clear(ctypes.byref(self.vf)) + + self.exists = False + + def get_buffer(self): + """get_buffer() -> bytesBuffer, bufferLength + + Returns None when all data has been read from the file. + + """ + if not self.exists: + return None + buffer = [] + total_bytes_written = 0 + + while True: + new_bytes = vorbis.ov_read(ctypes.byref(self.vf), self.buffer_, self._buffer_size*self.channels - total_bytes_written, 0, 2, 1, self.bitstream_pointer) + + array_ = ctypes.cast(self.buffer_, ctypes.POINTER(ctypes.c_char*(self._buffer_size*self.channels))).contents + + buffer.append(array_.raw[:new_bytes]) + + total_bytes_written += new_bytes + + if new_bytes == 0 or total_bytes_written >= self._buffer_size*self.channels: + break + + out_buffer = b"".join(buffer) + + if total_bytes_written == 0: + self.clean_up() + return(None) + + return out_buffer + + def get_buffer_as_array(self): + """Provides the buffer as a NumPy array. + + Note that the underlying data type is 16-bit signed + integers. + + Does not copy the underlying data, so the returned array + should either be processed or copied before the next call + to get_buffer() or get_buffer_as_array(). + + """ + import numpy # type: ignore + + # Read the next samples from the stream + buf = self.get_buffer() + + # Check if we've come to the end of the stream + if buf is None: + return None + + # Convert the bytes buffer to a NumPy array + array = numpy.frombuffer( + buf, + dtype=numpy.int16 + ) + + # Reshape the array + return array.reshape( + (len(buf) + // self.bytes_per_sample + // self.channels, + self.channels) + )