From c31c74e15f4d86c405ce632fc11bd27786f023ab Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Sat, 24 Apr 2021 11:16:01 +0100 Subject: [PATCH 01/10] Ignore PyCharm project files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d8ac85b..f9992a0 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,9 @@ dmypy.json # VSCode .vscode/ +# PyCharm +.idea/ + # macOS .DS_Store @@ -143,4 +146,4 @@ dmypy.json *.flac # Development -.archive \ No newline at end of file +.archive From eb3405e9dddf591566b5d1b064c02af3ec2323e9 Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Sat, 24 Apr 2021 11:48:25 +0100 Subject: [PATCH 02/10] Complete implementation of seek, tell and metadata callbacks This allows the encoder to seek back to the beginning of the file in order to write the metadata headers containing things like the hash and number of samples. Only seek and tell are required for this. The metadata callback just allows the user to receive a copy of the metadata. Currently this will arrive as cdata which is not very useful but it is implemented anyway for completeness. --- pyflac/encoder.py | 55 ++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/pyflac/encoder.py b/pyflac/encoder.py index e53de0e..76c5b77 100644 --- a/pyflac/encoder.py +++ b/pyflac/encoder.py @@ -216,12 +216,16 @@ class StreamEncoder(_Encoder): raw audio data. Raw audio data is passed in via the `process` method, and chunks - of compressed data is passed back to the user via the `callback`. + of compressed data is passed back to the user via the `write_callback`. Args: sample_rate (int): The raw audio sample rate (Hz) - callback (fn): Function to call when there is compressed + write_callback (fn): Function to call when there is compressed data ready, see the example below for more information. + seek_callback (fn): Optional function to call when the encoder + wants to seek within the output file. + tell_callback (fn): Optional function to call when the encoder + wants to find the current position within the output file. compression_level (int): The compression level parameter that varies from 0 (fastest) to 8 (slowest). The default setting is 5, see https://en.wikipedia.org/wiki/FLAC for more details. @@ -236,17 +240,17 @@ class StreamEncoder(_Encoder): encoding process by the extra time required for decoding and comparison. Examples: - An example callback which adds the encoded data to a queue for + An example write callback which adds the encoded data to a queue for later processing. .. code-block:: python :linenos: - def callback(self, - buffer: bytes, - num_bytes: int, - num_samples: int, - current_frame: int): + def write_callback(self, + buffer: bytes, + num_bytes: int, + num_samples: int, + current_frame: int): if num_samples == 0: # If there are no samples in the encoded data, this is # a FLAC header. The header data will arrive in several @@ -262,13 +266,19 @@ def callback(self, """ def __init__(self, sample_rate: int, - callback: Callable[[bytes, int, int, int], None], + write_callback: Callable[[bytes, int, int, int], None], + seek_callback: Callable[[int], None] = None, + tell_callback: Callable[[], int] = None, + metadata_callback: Callable[[int], None] = None, compression_level: int = 5, blocksize: int = 0, verify: bool = False): super().__init__() - self.callback = callback + self.write_callback = write_callback + self.seek_callback = seek_callback + self.tell_callback = tell_callback + self.metadata_callback = metadata_callback self._sample_rate = sample_rate self._blocksize = blocksize @@ -279,9 +289,9 @@ def _init(self): rc = _lib.FLAC__stream_encoder_init_stream( self._encoder, _lib._write_callback, - _ffi.NULL, - _ffi.NULL, - _ffi.NULL, + _lib._seek_callback if self.seek_callback else _ffi.NULL, + _lib._tell_callback if self.tell_callback else _ffi.NULL, + _lib._metadata_callback if self.metadata_callback else _ffi.NULL, self._encoder_handle ) if rc != _lib.FLAC__STREAM_ENCODER_INIT_STATUS_OK: @@ -305,7 +315,7 @@ class FileEncoder(_Encoder): blocksize (int): The size of the block to be returned in the callback. The default is 0 which allows libFLAC to determine the best block size. - verify (bool): If `True`, the encoder will verify its own + verify (bool): If `True`, the encoder will verify it's own encoded output by feeding it through an internal decoder and comparing the original signal against the decoded signal. If a mismatch occurs, the `process` method will raise a @@ -386,7 +396,7 @@ def _write_callback(_encoder, """ encoder = _ffi.from_handle(client_data) buffer = bytes(_ffi.buffer(byte_buffer, num_bytes)) - encoder.callback( + encoder.write_callback( buffer, num_bytes, num_samples, @@ -395,18 +405,22 @@ def _write_callback(_encoder, return _lib.FLAC__STREAM_ENCODER_WRITE_STATUS_OK -@_ffi.def_extern() +@_ffi.def_extern(error=_lib.FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR) def _seek_callback(_encoder, absolute_byte_offset, client_data): - raise NotImplementedError + encoder = _ffi.from_handle(client_data) + encoder.seek_callback(absolute_byte_offset) + return _lib.FLAC__STREAM_ENCODER_SEEK_STATUS_OK -@_ffi.def_extern() +@_ffi.def_extern(error=_lib.FLAC__STREAM_ENCODER_TELL_STATUS_ERROR) def _tell_callback(_encoder, absolute_byte_offset, client_data): - raise NotImplementedError + encoder = _ffi.from_handle(client_data) + absolute_byte_offset[0] = encoder.tell_callback() + return _lib.FLAC__STREAM_ENCODER_TELL_STATUS_OK @_ffi.def_extern() @@ -420,7 +434,8 @@ def _metadata_callback(_encoder, with the correct statistics after encoding (like minimum/maximum frame size and total samples). """ - raise NotImplementedError + encoder = _ffi.from_handle(client_data) + encoder.metadata_callback(metadata) @_ffi.def_extern() From 0f7215aff9fa6b798f35c859ac8b407fbb92c48d Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Sat, 24 Apr 2021 12:04:28 +0100 Subject: [PATCH 03/10] Implement streamable_subset option for stream encoder This allows to use block sizes and sample rates outside the range defined by the streamable subset. --- pyflac/builder/encoder.py | 1 + pyflac/encoder.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pyflac/builder/encoder.py b/pyflac/builder/encoder.py index b622b3a..0c894f0 100644 --- a/pyflac/builder/encoder.py +++ b/pyflac/builder/encoder.py @@ -284,6 +284,7 @@ FLAC__bool FLAC__stream_encoder_set_max_residual_partition_order(FLAC__StreamEncoder *encoder, uint32_t value); FLAC__bool FLAC__stream_encoder_set_rice_parameter_search_dist(FLAC__StreamEncoder *encoder, uint32_t value); FLAC__bool FLAC__stream_encoder_set_total_samples_estimate(FLAC__StreamEncoder *encoder, FLAC__uint64 value); +FLAC__bool FLAC__stream_encoder_set_streamable_subset(FLAC__StreamEncoder *encoder, FLAC__bool value); // GETTERS FLAC__StreamEncoderState FLAC__stream_encoder_get_state(const FLAC__StreamEncoder *encoder); diff --git a/pyflac/encoder.py b/pyflac/encoder.py index 76c5b77..932ed37 100644 --- a/pyflac/encoder.py +++ b/pyflac/encoder.py @@ -209,6 +209,19 @@ def _compression_level(self) -> int: def _compression_level(self, value: int): _lib.FLAC__stream_encoder_set_compression_level(self._encoder, value) + @property + def _streamable_subset(self) -> bool: + """ + bool: Property to get/set the streamable subset setting. + If true, the encoder will comply with the Subset and will check the settings during + init. If false, the settings may take advantage of the full range that the format allows. + """ + return _lib.FLAC__stream_encoder_get_streamable_subset(self._encoder) + + @_streamable_subset.setter + def _streamable_subset(self, value: bool): + _lib.FLAC__stream_encoder_set_streamable_subset(self._encoder, value) + class StreamEncoder(_Encoder): """ @@ -232,6 +245,9 @@ class StreamEncoder(_Encoder): blocksize (int): The size of the block to be returned in the callback. The default is 0 which allows libFLAC to determine the best block size. + streamable_subset (bool): Whether to use the streamable subset for encoding. + If true the encoder will check settings for compatibility. If false, + the settings may take advantage of the full range that the format allows. verify (bool): If `True`, the encoder will verify its own encoded output by feeding it through an internal decoder and comparing the original signal against the decoded signal. @@ -272,6 +288,7 @@ def __init__(self, metadata_callback: Callable[[int], None] = None, compression_level: int = 5, blocksize: int = 0, + streamable_subset: bool = True, verify: bool = False): super().__init__() @@ -283,6 +300,7 @@ def __init__(self, self._sample_rate = sample_rate self._blocksize = blocksize self._compression_level = compression_level + self._streamable_subset = streamable_subset self._verify = verify def _init(self): @@ -315,6 +333,9 @@ class FileEncoder(_Encoder): blocksize (int): The size of the block to be returned in the callback. The default is 0 which allows libFLAC to determine the best block size. + streamable_subset (bool): Whether to use the streamable subset for encoding. + If true the encoder will check settings for compatibility. If false, + the settings may take advantage of the full range that the format allows. verify (bool): If `True`, the encoder will verify it's own encoded output by feeding it through an internal decoder and comparing the original signal against the decoded signal. @@ -330,6 +351,7 @@ def __init__(self, output_file: Path = None, compression_level: int = 5, blocksize: int = 0, + streamable_subset: bool = True, verify: bool = False): super().__init__() @@ -343,6 +365,7 @@ def __init__(self, self._sample_rate = sample_rate self._blocksize = blocksize self._compression_level = compression_level + self._streamable_subset = streamable_subset self._verify = verify def _init(self): From e18be6ad54d7ddb1fb6875ea35d6163ba81f47b4 Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Tue, 27 Apr 2021 16:14:31 +0100 Subject: [PATCH 04/10] Make the tests use write_callback instead of callback The encoder needs multiple callbacks for seek/tell support, so the write callback can't just be called "callback" any more. --- tests/test_encoder.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_encoder.py b/tests/test_encoder.py index 8f5dd71..7552a3e 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -91,7 +91,7 @@ def setUp(self): self.default_kwargs = { 'sample_rate': DEFAULT_SAMPLE_RATE, 'blocksize': DEFAULT_BLOCKSIZE, - 'callback': self._callback, + 'write_callback': self._callback, 'verify': True } @@ -112,7 +112,7 @@ def _callback(self, def test_invalid_sample_rate(self): """ Test than an exception is raised if given an invalid sample rate """ - self.encoder = StreamEncoder(sample_rate=1000000, callback=self._callback) + self.encoder = StreamEncoder(sample_rate=1000000, write_callback=self._callback) with self.assertRaises(EncoderInitException) as err: self.encoder._init() self.assertEqual(str(err), 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE') @@ -122,7 +122,7 @@ def test_invalid_blocksize(self): self.encoder = StreamEncoder( sample_rate=DEFAULT_SAMPLE_RATE, blocksize=1000000, - callback=self._callback + write_callback=self._callback ) with self.assertRaises(EncoderInitException) as err: self.encoder._init() From 19929a6fb368b9e0e917a3d838a8fb7f29e59e5c Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Tue, 27 Apr 2021 16:52:23 +0100 Subject: [PATCH 05/10] Fix exception checking by using assertRaisesRegex An exception inside a with block will immediately exit that block. That means any subsequent checks won't run. We can test the string by using assertRaisesRegex. Fixes #10 --- tests/test_encoder.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_encoder.py b/tests/test_encoder.py index 7552a3e..f4c23b6 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -113,9 +113,8 @@ def _callback(self, def test_invalid_sample_rate(self): """ Test than an exception is raised if given an invalid sample rate """ self.encoder = StreamEncoder(sample_rate=1000000, write_callback=self._callback) - with self.assertRaises(EncoderInitException) as err: + with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE'): self.encoder._init() - self.assertEqual(str(err), 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE') def test_invalid_blocksize(self): """ Test than an exception is raised if given an invalid block size """ @@ -124,9 +123,8 @@ def test_invalid_blocksize(self): blocksize=1000000, write_callback=self._callback ) - with self.assertRaises(EncoderInitException) as err: + with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BLOCK_SIZE'): self.encoder._init() - self.assertEqual(str(err), 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BLOCK_SIZE') def test_process_mono(self): """ Test that an array of int16 mono samples can be processed """ @@ -161,7 +159,7 @@ def test_invalid_blocksize(self): """ Test than an exception is raised if given an invalid block size """ self.default_kwargs['blocksize'] = 1000000 self.encoder = FileEncoder(**self.default_kwargs) - with self.assertRaises(EncoderInitException): + with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BLOCK_SIZE'): self.encoder._init() def test_state(self): From e79e1a7e143ae14384947f5fcfd9d079890c1875 Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Tue, 27 Apr 2021 16:57:51 +0100 Subject: [PATCH 06/10] Use default_kwargs style in all tests --- tests/test_encoder.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/test_encoder.py b/tests/test_encoder.py index f4c23b6..e4f5987 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -111,18 +111,15 @@ def _callback(self, self.callback_called = True def test_invalid_sample_rate(self): - """ Test than an exception is raised if given an invalid sample rate """ - self.encoder = StreamEncoder(sample_rate=1000000, write_callback=self._callback) + self.default_kwargs['sample_rate'] = 1000000 + self.encoder = StreamEncoder(**self.default_kwargs) with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE'): self.encoder._init() def test_invalid_blocksize(self): """ Test than an exception is raised if given an invalid block size """ - self.encoder = StreamEncoder( - sample_rate=DEFAULT_SAMPLE_RATE, - blocksize=1000000, - write_callback=self._callback - ) + self.default_kwargs['blocksize'] = 1000000 + self.encoder = StreamEncoder(**self.default_kwargs) with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BLOCK_SIZE'): self.encoder._init() From ad9dbdcc67ff77136db49c80f1b4a735083629aa Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Tue, 27 Apr 2021 17:01:10 +0100 Subject: [PATCH 07/10] Add tests for streamable subset option --- tests/test_encoder.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_encoder.py b/tests/test_encoder.py index e4f5987..f4dc662 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -65,6 +65,12 @@ def test_blocksize(self): self.encoder._blocksize = test_blocksize self.assertEqual(self.encoder._blocksize, test_blocksize) + def test_streamable_subset(self): + """ Test that the streamable_subset setter returns the same value from libFLAC """ + test_streamable_subset = False + self.encoder._streamable_subset = test_streamable_subset + self.assertEqual(self.encoder._streamable_subset, test_streamable_subset) + def test_compression_level(self): """ Test that the compression level setter returns the same value from libFLAC """ test_compression_level = 8 @@ -123,6 +129,20 @@ def test_invalid_blocksize(self): with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BLOCK_SIZE'): self.encoder._init() + def test_blocksize_streamable_subset(self): + """ Test that an exception is raised if blocksize is outside the streamable subset and streamable is unset """ + self.default_kwargs['blocksize'] = 65535 + self.encoder = StreamEncoder(**self.default_kwargs) + with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_NOT_STREAMABLE'): + self.encoder._init() + + def test_blocksize_lax(self): + """ Test that an exception is not raised on large blocks when in lax mode """ + self.default_kwargs['blocksize'] = 65535 + self.default_kwargs['streamable_subset'] = False + self.encoder = StreamEncoder(**self.default_kwargs) + self.encoder._init() + def test_process_mono(self): """ Test that an array of int16 mono samples can be processed """ self.encoder = StreamEncoder(**self.default_kwargs) @@ -159,6 +179,20 @@ def test_invalid_blocksize(self): with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BLOCK_SIZE'): self.encoder._init() + def test_blocksize_streamable_subset(self): + """ Test that an exception is raised if blocksize is outside the streamable subset """ + self.default_kwargs['blocksize'] = 65535 + self.encoder = FileEncoder(**self.default_kwargs) + with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_NOT_STREAMABLE'): + self.encoder._init() + + def test_blocksize_lax(self): + """ Test that an exception is not raised on large blocks when in lax mode """ + self.default_kwargs['blocksize'] = 65535 + self.default_kwargs['streamable_subset'] = False + self.encoder = FileEncoder(**self.default_kwargs) + self.encoder._init() + def test_state(self): """ Test that the initial state is ok """ self.encoder = FileEncoder(**self.default_kwargs) From 23fd9baff62ef53638352d468fe1f61cf4c5fd39 Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Fri, 30 Apr 2021 19:58:24 +0100 Subject: [PATCH 08/10] Add tests for all callbacks --- tests/test_encoder.py | 56 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/tests/test_encoder.py b/tests/test_encoder.py index f4dc662..33140a6 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -92,12 +92,15 @@ class TestStreamEncoder(unittest.TestCase): Test suite for the stream encoder class """ def setUp(self): - self.callback_called = False + self.write_callback_called = False + self.seek_callback_called = False + self.tell_callback_called = False + self.metadata_callback_called = False self.encoder = None self.default_kwargs = { 'sample_rate': DEFAULT_SAMPLE_RATE, 'blocksize': DEFAULT_BLOCKSIZE, - 'write_callback': self._callback, + 'write_callback': self._write_callback, 'verify': True } @@ -105,7 +108,7 @@ def tearDown(self): if self.encoder: self.encoder.finish() - def _callback(self, + def _write_callback(self, buffer: bytes, num_bytes: int, num_samples: int, @@ -114,7 +117,18 @@ def _callback(self, assert isinstance(num_bytes, int) assert isinstance(num_samples, int) assert isinstance(current_frame, int) - self.callback_called = True + self.write_callback_called = True + + def _seek_callback(self, offset: int): + assert isinstance(offset, int) + self.seek_callback_called = True + + def _tell_callback(self): + self.tell_callback_called = True + return 0 + + def _metadata_callback(self, metadata): + self.metadata_callback_called = True def test_invalid_sample_rate(self): self.default_kwargs['sample_rate'] = 1000000 @@ -148,15 +162,45 @@ def test_process_mono(self): self.encoder = StreamEncoder(**self.default_kwargs) test_samples = np.random.rand(DEFAULT_BLOCKSIZE, 1).astype('int16') self.encoder.process(test_samples) - self.assertTrue(self.callback_called) + self.encoder.finish() + self.assertTrue(self.write_callback_called) def test_process_stereo(self): """ Test that an array of int16 stereo samples can be processed """ self.encoder = StreamEncoder(**self.default_kwargs) test_samples = np.random.rand(DEFAULT_BLOCKSIZE, 2).astype('int16') self.encoder.process(test_samples) - self.assertTrue(self.callback_called) + self.encoder.finish() + self.assertTrue(self.write_callback_called) + + def test_seek_tell(self): + """ Test that seek and tell callbacks are used """ + self.default_kwargs['seek_callback'] = self._seek_callback + self.default_kwargs['tell_callback'] = self._tell_callback + self.encoder = StreamEncoder(**self.default_kwargs) + test_samples = np.random.rand(DEFAULT_BLOCKSIZE, 1).astype('int16') + self.encoder.process(test_samples) + self.encoder.finish() + self.assertTrue(self.write_callback_called) + self.assertTrue(self.seek_callback_called) + self.assertTrue(self.tell_callback_called) + + def test_seek_only(self): + """ Supplying only one of seek or tell is not allowed """ + self.default_kwargs['seek_callback'] = self._seek_callback + self.encoder = StreamEncoder(**self.default_kwargs) + with self.assertRaisesRegex(EncoderInitException, 'FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_CALLBACKS'): + self.encoder._init() + def test_metadata(self): + """ Test that the metadata callback is called """ + self.default_kwargs['metadata_callback'] = self._metadata_callback + self.encoder = StreamEncoder(**self.default_kwargs) + test_samples = np.random.rand(DEFAULT_BLOCKSIZE, 1).astype('int16') + self.encoder.process(test_samples) + self.encoder.finish() + self.assertTrue(self.write_callback_called) + self.assertTrue(self.metadata_callback_called) class TestFileEncoder(unittest.TestCase): """ From b7e6c63d5e298270a89358b843f9cf93802b6c60 Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Wed, 19 May 2021 06:04:18 +0100 Subject: [PATCH 09/10] Rename decoder callback to write_callback and update tests and examples --- examples/passthrough.py | 4 ++-- examples/stream.py | 2 +- pyflac/decoder.py | 12 ++++++------ tests/test_decoder.py | 14 +++++++------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/passthrough.py b/examples/passthrough.py index 851ef7e..341f5d4 100755 --- a/examples/passthrough.py +++ b/examples/passthrough.py @@ -32,13 +32,13 @@ def __init__(self, args): self.data, self.sr = sf.read(args.input_file, dtype='int16', always_2d=True) self.encoder = pyflac.StreamEncoder( - callback=self.encoder_callback, + write_callback=self.encoder_callback, sample_rate=self.sr, blocksize=args.block_size, ) self.decoder = pyflac.StreamDecoder( - callback=self.decoder_callback + write_callback=self.decoder_callback ) def encode(self): diff --git a/examples/stream.py b/examples/stream.py index 526a289..03c61ae 100755 --- a/examples/stream.py +++ b/examples/stream.py @@ -35,7 +35,7 @@ def __init__(self, args, stream): self.num_channels = stream.channels self.sample_size = stream.samplesize - self.encoder = pyflac.StreamEncoder(callback=self.encoder_callback, + self.encoder = pyflac.StreamEncoder(write_callback=self.encoder_callback, sample_rate=int(stream.samplerate), compression_level=args.compression_level) diff --git a/pyflac/decoder.py b/pyflac/decoder.py index cb096a7..d58dabf 100644 --- a/pyflac/decoder.py +++ b/pyflac/decoder.py @@ -122,7 +122,7 @@ class StreamDecoder(_Decoder): the `callback`. Args: - callback (fn): Function to call when there is uncompressed + write_callback (fn): Function to call when there is uncompressed audio data ready, see the example below for more information. Examples: @@ -154,12 +154,12 @@ def callback(self, DecoderInitException: If initialisation of the decoder fails """ def __init__(self, - callback: Callable[[np.ndarray, int, int, int], None]): + write_callback: Callable[[np.ndarray, int, int, int], None]): super().__init__() self._done = False self._buffer = deque() - self.callback = callback + self.write_callback = write_callback rc = _lib.FLAC__stream_decoder_init_stream( self._decoder, @@ -250,7 +250,7 @@ def __init__(self, super().__init__() self.__output = None - self.callback = self._callback + self.write_callback = self._write_callback if output_file: self.__output_file = output_file else: @@ -291,7 +291,7 @@ def process(self) -> Tuple[np.ndarray, int]: self.__output.close() return sf.read(str(self.__output_file), always_2d=True) - def _callback(self, data: np.ndarray, sample_rate: int, num_channels: int, num_samples: int): + def _write_callback(self, data: np.ndarray, sample_rate: int, num_channels: int, num_samples: int): """ Internal callback to write the decoded data to a WAV file. """ @@ -418,7 +418,7 @@ def _write_callback(_decoder, np.frombuffer(cbuffer, dtype='int32').astype(np.int16) ) output = np.column_stack(channels) - decoder.callback( + decoder.write_callback( output, int(frame.header.sample_rate), int(frame.header.channels), diff --git a/tests/test_decoder.py b/tests/test_decoder.py index 0be5cea..8c4fbe4 100644 --- a/tests/test_decoder.py +++ b/tests/test_decoder.py @@ -45,22 +45,22 @@ class TestStreamDecoder(unittest.TestCase): """ def setUp(self): self.decoder = None - self.callback_called = False + self.write_callback_called = False self.tests_path = pathlib.Path(__file__).parent.absolute() - def _callback(self, data, rate, channels, samples): + def _write_callback(self, data, rate, channels, samples): assert isinstance(data, np.ndarray) assert isinstance(rate, int) assert isinstance(channels, int) assert isinstance(samples, int) - self.callback_called = True + self.write_callback_called = True def test_process_invalid_data(self): """ Test that processing invalid data raises an exception """ test_data = bytearray(os.urandom(100000)) with self.assertRaises(DecoderProcessException): - self.decoder = StreamDecoder(callback=self._callback) + self.decoder = StreamDecoder(write_callback=self._write_callback) self.decoder.process(test_data) self.decoder.finish() @@ -70,12 +70,12 @@ def test_process(self): with open(test_path, 'rb') as flac: test_data = flac.read() - self.decoder = StreamDecoder(callback=self._callback) + self.decoder = StreamDecoder(write_callback=self._write_callback) time.sleep(0.05) self.decoder.process(test_data) self.decoder.finish() - self.assertTrue(self.callback_called) + self.assertTrue(self.write_callback_called) def test_process_blocks(self): """ Test that FLAC data can be decoded in blocks """ @@ -85,7 +85,7 @@ def test_process_blocks(self): test_data = flac.read() data_length = len(test_data) - self.decoder = StreamDecoder(callback=self._callback) + self.decoder = StreamDecoder(write_callback=self._write_callback) for i in range(0, data_length - blocksize, blocksize): self.decoder.process(test_data[i:i + blocksize]) From c5c32f71db1e6a1995a6a1176488641f07d000e9 Mon Sep 17 00:00:00 2001 From: Alistair Buxton Date: Tue, 8 Jun 2021 23:18:23 +0100 Subject: [PATCH 10/10] Bump version to 2.0.0 and update changelog --- CHANGELOG.rst | 5 +++++ pyflac/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cfbbcbb..4361989 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ pyFLAC Changelog ---------------- +**v2.0.0** + +* Added `seek` and `tell` callbacks to `StreamEncoder` +* Renamed the write callbacks from `callback` to `write_callback` for `StreamEncoder` and `StreamDecoder` + **v1.0.0** * Added a `StreamEncoder` to compress raw audio data on-the-fly into a FLAC byte stream diff --git a/pyflac/__init__.py b/pyflac/__init__.py index 509c8c5..4c42cfd 100644 --- a/pyflac/__init__.py +++ b/pyflac/__init__.py @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ __title__ = 'pyFLAC' -__version__ = '1.0.1' +__version__ = '2.0.0' __all__ = [ 'StreamEncoder', 'FileEncoder',