changeset 418:70abdb92149a

Add AudioEventsJoinerWorker
author Amine Sehili <amine.sehili@gmail.com>
date Fri, 18 Oct 2024 22:47:58 +0200
parents 5f32574c9788
children c2ac3fc1bfbc
files auditok/cmdline.py auditok/cmdline_util.py auditok/workers.py pyproject.toml tests/test_cmdline_util.py tests/test_workers.py
diffstat 6 files changed, 345 insertions(+), 154 deletions(-) [+]
line wrap: on
line diff
--- a/auditok/cmdline.py	Thu Oct 17 22:02:41 2024 +0200
+++ b/auditok/cmdline.py	Fri Oct 18 22:47:58 2024 +0200
@@ -22,11 +22,8 @@
 
 from auditok import AudioRegion, __version__
 
-from . import workers
 from .cmdline_util import initialize_workers, make_kwargs, make_logger
 from .exceptions import AudioEncodingWarning, EndOfProcessing
-from .io import player_for
-from .util import AudioDataSource
 
 __all__ = []
 __date__ = "2015-11-23"
@@ -129,6 +126,17 @@
             metavar="STRING",
         )
         group.add_argument(
+            "-j",
+            "--join-detections",
+            dest="join_detections",
+            type=float,
+            default=None,
+            help="Join (i.e., glue) detected audio events with a silence of "
+            "this duration. Should be used jointly with the --save-stream / "
+            "-O option.",
+            metavar="FLOAT",
+        )
+        group.add_argument(
             "-T",
             "--output-format",
             dest="output_format",
@@ -380,11 +388,8 @@
         args = parser.parse_args(argv)
         logger = make_logger(args.debug, args.debug_file)
         kwargs = make_kwargs(args)
-        reader, observers = initialize_workers(
-            logger=logger, **kwargs.io, **kwargs.miscellaneous
-        )
-        tokenizer_worker = workers.TokenizerWorker(
-            reader, observers, logger=logger, **kwargs.split
+        stream_saver, tokenizer_worker = initialize_workers(
+            logger=logger, **kwargs.split, **kwargs.io, **kwargs.miscellaneous
         )
         tokenizer_worker.start_all()
 
@@ -397,16 +402,18 @@
         if tokenizer_worker is not None:
             tokenizer_worker.stop_all()
 
-            if isinstance(reader, workers.StreamSaverWorker):
-                reader.join()
+            if stream_saver is not None:
+                stream_saver.join()
                 try:
-                    reader.save_stream()
-                except AudioEncodingWarning as ae_warn:
-                    print(str(ae_warn), file=sys.stderr)
+                    stream_saver.export_audio()
+                except Exception as aee:
+                    print(aee, file=sys.stderr)
 
             if args.plot or args.save_image is not None:
                 from .plotting import plot
 
+                reader = tokenizer_worker.reader
+
                 reader.rewind()
                 record = AudioRegion(
                     reader.data, reader.sr, reader.sw, reader.ch
--- a/auditok/cmdline_util.py	Thu Oct 17 22:02:41 2024 +0200
+++ b/auditok/cmdline_util.py	Fri Oct 18 22:47:58 2024 +0200
@@ -33,6 +33,7 @@
         "use_channel": use_channel,
         "save_stream": args_ns.save_stream,
         "save_detections_as": args_ns.save_detections_as,
+        "join_detections": args_ns.join_detections,
         "export_format": args_ns.output_format,
         "large_file": args_ns.large_file,
         "frames_per_buffer": args_ns.frame_per_buffer,
@@ -84,12 +85,30 @@
     observers = []
     reader = AudioReader(source=kwargs["input"], **kwargs)
     if kwargs["save_stream"] is not None:
-        reader = workers.StreamSaverWorker(
-            reader,
-            filename=kwargs["save_stream"],
-            export_format=kwargs["export_format"],
-        )
-        reader.start()
+
+        if kwargs["join_detections"] is not None:
+            print("Using event joiner...")
+            stream_saver = workers.AudioEventsJoinerWorker(
+                silence_duration=kwargs["join_detections"],
+                filename=kwargs["save_stream"],
+                export_format=kwargs["export_format"],
+                sampling_rate=reader.sampling_rate,
+                sample_width=reader.sample_width,
+                channels=reader.channels,
+            )
+            observers.append(stream_saver)
+
+        else:
+            print("Using full stream saver...")
+            reader = workers.StreamSaverWorker(
+                reader,
+                filename=kwargs["save_stream"],
+                export_format=kwargs["export_format"],
+            )
+            stream_saver = reader
+            stream_saver.start()
+    else:
+        stream_saver = None
 
     if kwargs["save_detections_as"] is not None:
         worker = workers.RegionSaverWorker(
@@ -124,4 +143,8 @@
         )
         observers.append(worker)
 
-    return reader, observers
+    tokenizer_worker = workers.TokenizerWorker(
+        reader, observers, logger=logger, **kwargs
+    )
+
+    return stream_saver, tokenizer_worker
--- a/auditok/workers.py	Thu Oct 17 22:02:41 2024 +0200
+++ b/auditok/workers.py	Fri Oct 18 22:47:58 2024 +0200
@@ -9,12 +9,8 @@
 from tempfile import NamedTemporaryFile
 from threading import Thread
 
-from .core import split
-from .exceptions import (
-    AudioEncodingError,
-    AudioEncodingWarning,
-    EndOfProcessing,
-)
+from .core import make_silence, split
+from .exceptions import AudioEncodingError, AudioEncodingWarning
 from .io import _guess_audio_format
 from .util import AudioReader, make_duration_formatter
 
@@ -90,7 +86,8 @@
     def __init__(self, reader, observers=None, logger=None, **kwargs):
         self._observers = observers if observers is not None else []
         self._reader = reader
-        self._audio_region_gen = split(self, **kwargs)
+        kwargs["input"] = self
+        self._audio_region_gen = split(**kwargs)
         self._detections = []
         self._log_format = "[DET]: Detection {0.id} (start: {0.start:.3f}, "
         self._log_format += "end: {0.end:.3f}, duration: {0.duration:.3f})"
@@ -103,6 +100,10 @@
     def detections(self):
         return self._detections
 
+    @property
+    def reader(self):
+        return self._reader
+
     def _notify_observers(self, message):
         for observer in self._observers:
             observer.send(message)
@@ -150,27 +151,41 @@
         return getattr(self._reader, name)
 
 
-class StreamSaverWorker(Worker):
+class AudioDataSaverWorker(Worker):
+
     def __init__(
         self,
-        audio_reader,
         filename,
-        export_format=None,
-        cache_size_sec=0.5,
+        export_format,
+        sampling_rate,
+        sample_width,
+        channels,
         timeout=0.2,
     ):
-        self._reader = audio_reader
-        sample_size_bytes = self._reader.sw * self._reader.ch
-        self._cache_size = cache_size_sec * self._reader.sr * sample_size_bytes
+
+        super().__init__(timeout=timeout)
         self._output_filename = filename
+        self._sampling_rate = sampling_rate
+        self._sample_width = sample_width
+        self._channels = channels
+
         self._export_format = _guess_audio_format(filename, export_format)
         if self._export_format is None:
             self._export_format = "wav"
         self._init_output_stream()
         self._exported = False
-        self._cache = []
-        self._total_cached = 0
-        Worker.__init__(self, timeout=timeout)
+
+    @property
+    def sr(self):
+        return self._sampling_rate
+
+    @property
+    def sw(self):
+        return self._sample_width
+
+    @property
+    def ch(self):
+        return self._channels
 
     def _get_non_existent_filename(self):
         filename = self._output_filename + ".wav"
@@ -186,21 +201,90 @@
         else:
             self._tmp_output_filename = self._output_filename
         self._wfp = wave.open(self._tmp_output_filename, "wb")
-        self._wfp.setframerate(self._reader.sr)
-        self._wfp.setsampwidth(self._reader.sw)
-        self._wfp.setnchannels(self._reader.ch)
+        self._wfp.setframerate(self.sr)
+        self._wfp.setsampwidth(self.sw)
+        self._wfp.setnchannels(self.ch)
 
     @property
-    def sr(self):
-        return self._reader.sampling_rate
+    def data(self):
+        with wave.open(self._tmp_output_filename, "rb") as wfp:
+            return wfp.readframes(-1)
 
-    @property
-    def sw(self):
-        return self._reader.sample_width
+    def export_audio(self):
+        try:
+            self._encode_export_audio()
+        except AudioEncodingError as ae_error:
+            raise AudioEncodingWarning(str(ae_error)) from ae_error
+        return self._output_filename
 
-    @property
-    def ch(self):
-        return self._reader.channels
+    def _encode_export_audio(self):
+        if self._exported:
+            return self._output_filename
+
+        if self._export_format in ("raw", "wav"):
+            if self._export_format == "raw":
+                self._export_raw()
+            self._exported = True
+            return self._output_filename
+        try:
+            self._export_with_ffmpeg_or_avconv()
+
+        except AudioEncodingError:
+            try:
+                self._export_with_sox()
+            except AudioEncodingError as exc:
+                warn_msg = "Couldn't save audio data in the desired format "
+                warn_msg += "'{}'.\nEither none of 'ffmpeg', 'avconv' or 'sox' "
+                warn_msg += "is installed or this format is not recognized.\n"
+                warn_msg += "Audio file was saved as '{}'"
+                raise AudioEncodingError(
+                    warn_msg.format(
+                        self._export_format, self._tmp_output_filename
+                    )
+                ) from exc
+            else:
+                self._exported = True
+        else:
+            self._exported = True
+        return self._output_filename
+
+    def _export_raw(self):
+        with open(self._output_filename, "wb") as fp:
+            fp.write(self.data)
+
+    def _export_with_ffmpeg_or_avconv(self):
+        command = [
+            "-y",
+            "-f",
+            "wav",
+            "-i",
+            self._tmp_output_filename,
+            "-f",
+            self._export_format,
+            self._output_filename,
+        ]
+        returncode, stdout, stderr = _run_subprocess(["ffmpeg"] + command)
+        if returncode != 0:
+            returncode, stdout, stderr = _run_subprocess(["avconv"] + command)
+            if returncode != 0:
+                raise AudioEncodingError(stderr)
+        return stdout, stderr
+
+    def _export_with_sox(self):
+        command = [
+            "sox",
+            "-t",
+            "wav",
+            self._tmp_output_filename,
+            self._output_filename,
+        ]
+        returncode, stdout, stderr = _run_subprocess(command)
+        if returncode != 0:
+            raise AudioEncodingError(stderr)
+        return stdout, stderr
+
+    def close_output(self):
+        self._wfp.close()
 
     def __del__(self):
         self._post_process()
@@ -212,6 +296,33 @@
         ):
             os.remove(self._tmp_output_filename)
 
+
+class StreamSaverWorker(AudioDataSaverWorker):
+    def __init__(
+        self,
+        audio_reader,
+        filename,
+        export_format=None,
+        cache_size_sec=0.5,
+        timeout=0.2,
+    ):
+        self._reader = audio_reader
+        super().__init__(
+            filename,
+            export_format,
+            self._reader.sr,
+            self._reader.sw,
+            self._reader.ch,
+            timeout=timeout,
+        )
+
+        sample_size_bytes = self._reader.sw * self._reader.ch
+        self._cache_size = cache_size_sec * self._reader.sr * sample_size_bytes
+
+        self._exported = False
+        self._cache = []
+        self._total_cached = 0
+
     def _process_message(self, data):
         self._cache.append(data)
         self._total_cached += len(data)
@@ -253,72 +364,6 @@
         with wave.open(self._tmp_output_filename, "rb") as wfp:
             return wfp.readframes(-1)
 
-    def save_stream(self):
-        if self._exported:
-            return self._output_filename
-
-        if self._export_format in ("raw", "wav"):
-            if self._export_format == "raw":
-                self._export_raw()
-            self._exported = True
-            return self._output_filename
-        try:
-            self._export_with_ffmpeg_or_avconv()
-        except AudioEncodingError:
-            try:
-                self._export_with_sox()
-            except AudioEncodingError as exc:
-                warn_msg = "Couldn't save audio data in the desired format "
-                warn_msg += "'{}'. Either none of 'ffmpeg', 'avconv' or 'sox' "
-                warn_msg += "is installed or this format is not recognized.\n"
-                warn_msg += "Audio file was saved as '{}'"
-                raise AudioEncodingWarning(
-                    warn_msg.format(
-                        self._export_format, self._tmp_output_filename
-                    )
-                ) from exc
-        finally:
-            self._exported = True
-        return self._output_filename
-
-    def _export_raw(self):
-        with open(self._output_filename, "wb") as wfp:
-            wfp.write(self.data)
-
-    def _export_with_ffmpeg_or_avconv(self):
-        command = [
-            "-y",
-            "-f",
-            "wav",
-            "-i",
-            self._tmp_output_filename,
-            "-f",
-            self._export_format,
-            self._output_filename,
-        ]
-        returncode, stdout, stderr = _run_subprocess(["ffmpeg"] + command)
-        if returncode != 0:
-            returncode, stdout, stderr = _run_subprocess(["avconv"] + command)
-            if returncode != 0:
-                raise AudioEncodingError(stderr)
-        return stdout, stderr
-
-    def _export_with_sox(self):
-        command = [
-            "sox",
-            "-t",
-            "wav",
-            self._tmp_output_filename,
-            self._output_filename,
-        ]
-        returncode, stdout, stderr = _run_subprocess(command)
-        if returncode != 0:
-            raise AudioEncodingError(stderr)
-        return stdout, stderr
-
-    def close_output(self):
-        self._wfp.close()
-
     def read(self):
         data = self._reader.read()
         if data is not None:
@@ -328,9 +373,60 @@
         return data
 
     def __getattr__(self, name):
-        if name == "data":
-            return self.data
-        return getattr(self._reader, name)
+        try:
+            return getattr(self._reader, name)
+        except AttributeError:
+            return getattr(self, name)
+
+
+class AudioEventsJoinerWorker(AudioDataSaverWorker):
+
+    def __init__(
+        self,
+        silence_duration,
+        filename,
+        export_format,
+        sampling_rate,
+        sample_width,
+        channels,
+        timeout=0.2,
+    ):
+
+        super().__init__(
+            filename,
+            export_format,
+            sampling_rate,
+            sample_width,
+            channels,
+            timeout,
+        )
+
+        self._silence_data = make_silence(
+            silence_duration, sampling_rate, sample_width, channels
+        ).data
+        self._first_event = True
+
+    def _process_message(self, message):
+        _, audio_event = message
+        self._write_audio_event(audio_event.data)
+
+    def _post_process(self):
+        while True:
+            try:
+                message = self._inbox.get_nowait()
+                if message != _STOP_PROCESSING:
+                    _, audio_event = message
+                    self._write_audio_event(audio_event.data)
+            except Empty:
+                break
+        self._wfp.close()
+
+    def _write_audio_event(self, data):
+        if not self._first_event:
+            self._wfp.writeframes(self._silence_data)
+        else:
+            self._first_event = False
+        self._wfp.writeframes(data)
 
 
 class PlayerWorker(Worker):
@@ -357,7 +453,7 @@
         audio_format=None,
         timeout=0.2,
         logger=None,
-        **audio_parameters
+        **audio_parameters,
     ):
         self._filename_format = filename_format
         self._audio_format = audio_format
--- a/pyproject.toml	Thu Oct 17 22:02:41 2024 +0200
+++ b/pyproject.toml	Fri Oct 18 22:47:58 2024 +0200
@@ -14,3 +14,6 @@
   | dist
 )/
 '''
+
+[tool.isort]
+profile = "black"
--- a/tests/test_cmdline_util.py	Thu Oct 17 22:02:41 2024 +0200
+++ b/tests/test_cmdline_util.py	Fri Oct 18 22:47:58 2024 +0200
@@ -13,6 +13,7 @@
     make_logger,
 )
 from auditok.workers import (
+    AudioEventsJoinerWorker,
     CommandLineWorker,
     PlayerWorker,
     PrintWorker,
@@ -37,6 +38,7 @@
         "input_device_index",
         "save_stream",
         "save_detections_as",
+        "join_detections",
         "plot",
         "save_image",
         "min_duration",
@@ -57,25 +59,25 @@
 
 
 @pytest.mark.parametrize(
-    "save_stream, save_detections_as, plot, save_image, use_channel, exp_use_channel, exp_record",
+    "save_stream, save_detections_as, join_detections, plot, save_image, use_channel, exp_use_channel, exp_record",  # noqa: B950
     [
-        # no_record
-        ("stream.ogg", None, False, None, "mix", "mix", False),
-        # no_record_plot
-        ("stream.ogg", None, True, None, None, None, False),
+        # no_record_no_join
+        ("stream.ogg", None, None, False, None, "mix", "mix", False),
+        # no_record_plot_join
+        ("stream.ogg", None, 1.0, True, None, None, None, False),
         # no_record_save_image
-        ("stream.ogg", None, True, "image.png", None, None, False),
+        ("stream.ogg", None, None, True, "image.png", None, None, False),
         # record_plot
-        (None, None, True, None, None, None, True),
+        (None, None, None, True, None, None, None, True),
         # record_save_image
-        (None, None, False, "image.png", None, None, True),
+        (None, None, None, False, "image.png", None, None, True),
         # int_use_channel
-        ("stream.ogg", None, False, None, "1", 1, False),
+        ("stream.ogg", None, None, False, None, "1", 1, False),
         # save_detections_as
-        ("stream.ogg", "{id}.wav", False, None, None, None, False),
+        ("stream.ogg", "{id}.wav", None, False, None, None, None, False),
     ],
     ids=[
-        "no_record",
+        "no_record_no_join",
         "no_record_plot",
         "no_record_save_image",
         "record_plot",
@@ -87,6 +89,7 @@
 def test_make_kwargs(
     save_stream,
     save_detections_as,
+    join_detections,
     plot,
     save_image,
     use_channel,
@@ -108,6 +111,7 @@
         1,
         save_stream,
         save_detections_as,
+        join_detections,
         plot,
         save_image,
         0.2,
@@ -138,6 +142,7 @@
         "use_channel": exp_use_channel,
         "save_stream": save_stream,
         "save_detections_as": save_detections_as,
+        "join_detections": join_detections,
         "audio_format": "raw",
         "export_format": "ogg",
         "large_file": True,
@@ -187,15 +192,16 @@
     assert logger is None
 
 
-def test_initialize_workers_all():
+def test_initialize_workers_all_plus_full_stream_saver():
     with patch("auditok.cmdline_util.player_for") as patched_player_for:
         with TemporaryDirectory() as tmpdir:
             export_filename = os.path.join(tmpdir, "output.wav")
-            reader, observers = initialize_workers(
+            reader, tokenizer_worker = initialize_workers(
                 input="tests/data/test_16KHZ_mono_400Hz.wav",
                 save_stream=export_filename,
                 export_format="wave",
                 save_detections_as="{id}.wav",
+                join_detections=None,
                 echo=True,
                 progress_bar=False,
                 command="some command",
@@ -208,13 +214,48 @@
             assert patched_player_for.called
             assert isinstance(reader, StreamSaverWorker)
             for obs, cls in zip(
-                observers,
+                tokenizer_worker._observers,
                 [
                     RegionSaverWorker,
                     PlayerWorker,
                     CommandLineWorker,
                     PrintWorker,
                 ],
+                strict=True,
+            ):
+                assert isinstance(obs, cls)
+
+
+def test_initialize_workers_all_plus_audio_event_joiner():
+    with patch("auditok.cmdline_util.player_for") as patched_player_for:
+        with TemporaryDirectory() as tmpdir:
+            export_filename = os.path.join(tmpdir, "output.wav")
+            reader, tokenizer_worker = initialize_workers(
+                input="tests/data/test_16KHZ_mono_400Hz.wav",
+                save_stream=export_filename,
+                export_format="wave",
+                save_detections_as="{id}.wav",
+                join_detections=1,
+                echo=True,
+                progress_bar=False,
+                command="some command",
+                quiet=False,
+                printf="abcd",
+                time_format="%S",
+                timestamp_format="%h:%M:%S",
+            )
+            assert patched_player_for.called
+            assert not isinstance(reader, StreamSaverWorker)
+            for obs, cls in zip(
+                tokenizer_worker._observers,
+                [
+                    AudioEventsJoinerWorker,
+                    RegionSaverWorker,
+                    PlayerWorker,
+                    CommandLineWorker,
+                    PrintWorker,
+                ],
+                strict=True,
             ):
                 assert isinstance(obs, cls)
 
@@ -223,11 +264,12 @@
     with patch("auditok.cmdline_util.player_for") as patched_player_for:
         with TemporaryDirectory() as tmpdir:
             export_filename = os.path.join(tmpdir, "output.wav")
-            reader, observers = initialize_workers(
+            reader, tokenizer_worker = initialize_workers(
                 input="tests/data/test_16KHZ_mono_400Hz.wav",
                 save_stream=export_filename,
                 export_format="wave",
                 save_detections_as=None,
+                join_detections=None,
                 echo=True,
                 progress_bar=False,
                 command="some command",
@@ -240,7 +282,9 @@
             assert patched_player_for.called
             assert isinstance(reader, StreamSaverWorker)
             for obs, cls in zip(
-                observers, [PlayerWorker, CommandLineWorker, PrintWorker]
+                tokenizer_worker._observers,
+                [PlayerWorker, CommandLineWorker, PrintWorker],
+                strict=True,
             ):
                 assert isinstance(obs, cls)
 
@@ -249,11 +293,12 @@
     with patch("auditok.cmdline_util.player_for") as patched_player_for:
         with TemporaryDirectory() as tmpdir:
             export_filename = os.path.join(tmpdir, "output.wav")
-            reader, observers = initialize_workers(
+            reader, tokenizer_worker = initialize_workers(
                 input="tests/data/test_16KHZ_mono_400Hz.wav",
                 save_stream=export_filename,
                 export_format="wave",
                 save_detections_as="{id}.wav",
+                join_detections=None,
                 echo=False,
                 progress_bar=False,
                 command="some command",
@@ -266,8 +311,9 @@
             assert not patched_player_for.called
             assert isinstance(reader, StreamSaverWorker)
             for obs, cls in zip(
-                observers,
+                tokenizer_worker._observers,
                 [RegionSaverWorker, CommandLineWorker, PrintWorker],
+                strict=True,
             ):
                 assert isinstance(obs, cls)
 
@@ -276,11 +322,12 @@
     with patch("auditok.cmdline_util.player_for") as patched_player_for:
         with TemporaryDirectory() as tmpdir:
             export_filename = os.path.join(tmpdir, "output.wav")
-            reader, observers = initialize_workers(
+            reader, tokenizer_worker = initialize_workers(
                 input="tests/data/test_16KHZ_mono_400Hz.wav",
                 save_stream=export_filename,
                 export_format="wave",
                 save_detections_as="{id}.wav",
+                join_detections=None,
                 echo=True,
                 progress_bar=False,
                 command=None,
@@ -293,7 +340,9 @@
             assert patched_player_for.called
             assert isinstance(reader, StreamSaverWorker)
             for obs, cls in zip(
-                observers, [RegionSaverWorker, PlayerWorker, PrintWorker]
+                tokenizer_worker._observers,
+                [RegionSaverWorker, PlayerWorker, PrintWorker],
+                strict=True,
             ):
                 assert isinstance(obs, cls)
 
@@ -302,11 +351,12 @@
     with patch("auditok.cmdline_util.player_for") as patched_player_for:
         with TemporaryDirectory() as tmpdir:
             export_filename = os.path.join(tmpdir, "output.wav")
-            reader, observers = initialize_workers(
+            reader, tokenizer_worker = initialize_workers(
                 input="tests/data/test_16KHZ_mono_400Hz.wav",
                 save_stream=export_filename,
                 export_format="wave",
                 save_detections_as="{id}.wav",
+                join_detections=None,
                 echo=True,
                 progress_bar=False,
                 command="some command",
@@ -319,15 +369,16 @@
             assert patched_player_for.called
             assert isinstance(reader, StreamSaverWorker)
             for obs, cls in zip(
-                observers,
+                tokenizer_worker._observers,
                 [RegionSaverWorker, PlayerWorker, CommandLineWorker],
+                strict=True,
             ):
                 assert isinstance(obs, cls)
 
 
 def test_initialize_workers_no_observers():
     with patch("auditok.cmdline_util.player_for") as patched_player_for:
-        reader, observers = initialize_workers(
+        reader, tokenizer_worker = initialize_workers(
             input="tests/data/test_16KHZ_mono_400Hz.wav",
             save_stream=None,
             export_format="wave",
@@ -342,4 +393,4 @@
         )
         assert patched_player_for.called
         assert not isinstance(reader, StreamSaverWorker)
-        assert len(observers) == 1
+        assert len(tokenizer_worker._observers) == 1
--- a/tests/test_workers.py	Thu Oct 17 22:02:41 2024 +0200
+++ b/tests/test_workers.py	Fri Oct 18 22:47:58 2024 +0200
@@ -4,6 +4,7 @@
 
 import pytest
 
+import auditok.workers
 from auditok import AudioReader, AudioRegion
 from auditok.cmdline_util import make_logger
 from auditok.exceptions import AudioEncodingWarning
@@ -65,7 +66,8 @@
     )
     assert len(tokenizer.detections) == len(expected_detections)
     for i, (det, exp, log_line) in enumerate(
-        zip(tokenizer.detections, expected_detections, log_lines), 1
+        zip(tokenizer.detections, expected_detections, log_lines, strict=True),
+        1,
     ):
         start, end = exp
         exp_log_line = log_fmt.format(i, start, end, end - start)
@@ -103,7 +105,8 @@
     assert len(tokenizer.detections) == len(expected_detections)
     log_fmt = "[PLAY]: Detection {id} played"
     for i, (det, exp, log_line) in enumerate(
-        zip(tokenizer.detections, expected_detections, log_lines), 1
+        zip(tokenizer.detections, expected_detections, log_lines, strict=False),
+        1,
     ):
         start, end = exp
         exp_log_line = log_fmt.format(id=i)
@@ -156,7 +159,8 @@
 
     log_fmt = "[SAVE]: Detection {id} saved as '{filename}'"
     for i, (det, exp, log_line) in enumerate(
-        zip(tokenizer.detections, expected_detections, log_lines), 1
+        zip(tokenizer.detections, expected_detections, log_lines, strict=False),
+        1,
     ):
         start, end = exp
         expected_filename = filename_format.format(
@@ -199,7 +203,8 @@
     assert len(tokenizer.detections) == len(expected_detections)
     log_fmt = "[COMMAND]: Detection {id} command '{command}'"
     for i, (det, exp, log_line) in enumerate(
-        zip(tokenizer.detections, expected_detections, log_lines), 1
+        zip(tokenizer.detections, expected_detections, log_lines, strict=False),
+        1,
     ):
         start, end = exp
         exp_log_line = log_fmt.format(id=i, command=command_format)
@@ -237,7 +242,7 @@
     ]
     assert patched_print.mock_calls == expected_print_calls
     assert len(tokenizer.detections) == len(expected_detections)
-    for det, exp in zip(tokenizer.detections, expected_detections):
+    for det, exp in zip(tokenizer.detections, expected_detections, strict=True):
         start, end = exp
         assert pytest.approx(det.start) == start
         assert pytest.approx(det.end) == end
@@ -254,7 +259,7 @@
         tokenizer.join()
         saver.join()
 
-        output_filename = saver.save_stream()
+        output_filename = saver.export_audio()
         region = AudioRegion.load(
             "tests/data/test_split_10HZ_mono.raw", sr=10, sw=2, ch=1
         )
@@ -276,7 +281,7 @@
         tokenizer.start_all()
         tokenizer.join()
         saver.join()
-        output_filename = saver.save_stream()
+        output_filename = saver.export_audio()
         region = AudioRegion.load(
             "tests/data/test_split_10HZ_mono.raw", sr=10, sw=2, ch=1
         )
@@ -295,18 +300,24 @@
             expected_filename = os.path.join(tmpdir, "output.ogg")
             tmp_expected_filename = expected_filename + ".wav"
             saver = StreamSaverWorker(audio_data_source, expected_filename)
+            print("########## saver._exported 1:", saver._exported)
+            # import auditok
+
+            # with pytest.raises(auditok.workers.AudioEncodingWarning) as rt_warn:
             saver.start()
             tokenizer = TokenizerWorker(saver)
             tokenizer.start_all()
             tokenizer.join()
             saver.join()
-            with pytest.raises(AudioEncodingWarning) as rt_warn:
-                saver.save_stream()
+
+            with pytest.raises(auditok.workers.AudioEncodingError) as ae_error:
+                saver._encode_export_audio()
+
         warn_msg = "Couldn't save audio data in the desired format "
-        warn_msg += "'ogg'. Either none of 'ffmpeg', 'avconv' or 'sox' "
+        warn_msg += "'ogg'.\nEither none of 'ffmpeg', 'avconv' or 'sox' "
         warn_msg += "is installed or this format is not recognized.\n"
         warn_msg += "Audio file was saved as '{}'"
-        assert warn_msg.format(tmp_expected_filename) == str(rt_warn.value)
+        assert warn_msg.format(tmp_expected_filename) == str(ae_error.value)
         ffmpef_avconv = [
             "-y",
             "-f",
@@ -334,5 +345,5 @@
         region = AudioRegion.load(
             "tests/data/test_split_10HZ_mono.raw", sr=10, sw=2, ch=1
         )
-        assert saver._exported
+        assert not saver._exported
         assert saver.data == bytes(region)