plugin.audio.librespot/resources/lib/deps/pyogg/__init__.py
2024-02-21 01:17:59 -05:00

410 lines
16 KiB
Python

import ctypes
from . import ogg
from .ogg import PyOggError, PYOGG_OGG_AVAIL
from . import vorbis
from.vorbis import PYOGG_VORBIS_AVAIL, PYOGG_VORBIS_FILE_AVAIL
from . import opus
from.opus import PYOGG_OPUS_AVAIL, PYOGG_OPUS_FILE_AVAIL, PYOGG_OPUS_ENC_AVAIL
from . import flac
from .flac import PYOGG_FLAC_AVAIL
from itertools import chain
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))
PYOGG_STREAM_BUFFER_SIZE = 8192
if (PYOGG_OGG_AVAIL and PYOGG_VORBIS_AVAIL and PYOGG_VORBIS_FILE_AVAIL):
class VorbisFile:
def __init__(self, path):
vf = vorbis.OggVorbis_File()
error = vorbis.libvorbisfile.ov_fopen(vorbis.to_char_p(path), ctypes.byref(vf))
if error != 0:
raise PyOggError("file couldn't be opened or doesn't exist. Error code : {}".format(error))
info = vorbis.libvorbisfile.ov_info(ctypes.byref(vf), -1)
self.channels = info.contents.channels
self.frequency = info.contents.rate
array = (ctypes.c_char*4096)()
buffer_ = ctypes.cast(ctypes.pointer(array), ctypes.c_char_p)
self.buffer_array = []
bitstream = ctypes.c_int()
bitstream_pointer = ctypes.pointer(bitstream)
while True:
new_bytes = vorbis.libvorbisfile.ov_read(ctypes.byref(vf), buffer_, 4096, 0, 2, 1, bitstream_pointer)
array_ = ctypes.cast(buffer_, ctypes.POINTER(ctypes.c_char*4096)).contents
self.buffer_array.append(array_.raw[:new_bytes])
if new_bytes == 0:
break
self.buffer = b"".join(self.buffer_array)
vorbis.libvorbisfile.ov_clear(ctypes.byref(vf))
self.buffer_length = len(self.buffer)
class VorbisFileStream:
def __init__(self, path):
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)
self.channels = info.contents.channels
self.frequency = info.contents.rate
array = (ctypes.c_char*(PYOGG_STREAM_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
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"""
if not self.exists:
return None
buffer = []
total_bytes_written = 0
while True:
new_bytes = vorbis.ov_read(ctypes.byref(self.vf), self.buffer_, PYOGG_STREAM_BUFFER_SIZE*self.channels - total_bytes_written, 0, 2, 1, self.bitstream_pointer)
array_ = ctypes.cast(self.buffer_, ctypes.POINTER(ctypes.c_char*(PYOGG_STREAM_BUFFER_SIZE*self.channels))).contents
buffer.append(array_.raw[:new_bytes])
total_bytes_written += new_bytes
if new_bytes == 0 or total_bytes_written >= PYOGG_STREAM_BUFFER_SIZE*self.channels:
break
out_buffer = b"".join(buffer)
if total_bytes_written == 0:
self.clean_up()
return(None)
return(out_buffer, total_bytes_written)
else:
class VorbisFile:
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:
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):
class OpusFile:
def __init__(self, path):
error = ctypes.c_int()
of = opus.op_open_file(ogg.to_char_p(path), ctypes.pointer(error))
if error.value != 0:
raise PyOggError("file couldn't be opened or doesn't exist. Error code : {}".format(error.value))
self.channels = opus.op_channel_count(of, -1)
pcm_size = opus.op_pcm_total(of, -1)
samples_read = ctypes.c_int(0)
bfarr_t = opus.opus_int16*(pcm_size*self.channels)
self.buffer = ctypes.cast(ctypes.pointer(bfarr_t()),opus.opus_int16_p)
ptr = ctypes.cast(ctypes.pointer(self.buffer), ctypes.POINTER(ctypes.c_void_p))
ptr_init = ptr.contents.value
while samples_read.value < pcm_size:
ptr.contents.value = ptr_init + samples_read.value*self.channels*2
ns = opus.op_read(of, self.buffer , pcm_size*self.channels,ogg.c_int_p())
samples_read.value += ns
ptr.contents.value = ptr_init
del ptr
opus.op_free(of)
self.buffer_length = samples_read.value*self.channels*2
self.frequency = 48000
class OpusFileStream:
def __init__(self, path):
error = ctypes.c_int()
self.of = opus.op_open_file(ogg.to_char_p(path), ctypes.pointer(error))
if error.value != 0:
raise PyOggError("file couldn't be opened or doesn't exist. Error code : {}".format(error.value))
self.channels = opus.op_channel_count(self.of, -1)
self.pcm_size = opus.op_pcm_total(self.of, -1)
self.frequency = 48000
self.bfarr_t = opus.opus_int16*(PYOGG_STREAM_BUFFER_SIZE*self.channels*2)
self.buffer = ctypes.cast(ctypes.pointer(self.bfarr_t()),opus.opus_int16_p)
self.ptr = ctypes.cast(ctypes.pointer(self.buffer), ctypes.POINTER(ctypes.c_void_p))
self.ptr_init = self.ptr.contents.value
def get_buffer(self):
if not hasattr(self, 'ptr'):
return None
samples_read = ctypes.c_int(0)
while True:
self.ptr.contents.value = self.ptr_init + samples_read.value*self.channels*2
ns = opus.op_read(self.of, self.buffer , PYOGG_STREAM_BUFFER_SIZE*self.channels,ogg.c_int_p())
if ns == 0:
break
samples_read.value += ns
if samples_read.value*self.channels*2 + ns >= PYOGG_STREAM_BUFFER_SIZE:
break
if samples_read.value == 0:
self.clean_up()
return None
self.ptr.contents.value = self.ptr_init
buf = ctypes.pointer(self.bfarr_t())
buf[0] = ctypes.cast(self.buffer, ctypes.POINTER(self.bfarr_t))[0]
return(buf, samples_read.value*self.channels*2)
def clean_up(self):
self.ptr.contents.value = self.ptr_init
del self.ptr
opus.op_free(self.of)
else:
class OpusFile:
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 libraries weren't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)")
class OpusFileStream:
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 libraries weren't found or couldn't be loaded (maybe you're trying to use 64bit libraries with 32bit Python?)")
if PYOGG_FLAC_AVAIL:
class FlacFile:
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
self.buffer = (flac.FLAC__int16*(self.total_samples * self.channels * 2))()
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()
self.channels = None
self.frequency = None
self.total_samples = None
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),
write_callback_,
metadata_callback_,
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))
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)
self.buffer_length = len(self.buffer)
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()
self.channels = None
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))
def get_buffer(self):
if (flac.FLAC__stream_decoder_get_state(self.decoder) == 4): # end of stream
return None
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))
buffer_ = ctypes.pointer(self.buffer)
return(buffer_, self.bytes_written)
def clean_up(self):
flac.FLAC__stream_decoder_finish(self.decoder)
else:
class FlacFile:
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:
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?)")
def pyoggSetStreamBufferSize(size):
global PYOGG_STREAM_BUFFER_SIZE
PYOGG_STREAM_BUFFER_SIZE = size