From 10e74edf89ccfdd94a1368957226cd07ed0132ce Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Thu, 6 Nov 2025 13:51:01 +0200 Subject: [PATCH 01/73] Add test timeout (#1277) --- README.md | 4 ++-- buzz/db/migrator.py | 3 ++- buzz/file_transcriber_queue_worker.py | 2 +- buzz/transcriber/recording_transcriber.py | 4 ++-- buzz/transcriber/whisper_file_transcriber.py | 6 +++--- buzz/translator.py | 3 ++- buzz/widgets/main_window.py | 2 +- buzz/widgets/recording_transcriber_widget.py | 4 ++++ .../transcription_viewer_widget.py | 2 +- pytest.ini | 2 ++ tests/cli_test.py | 2 +- tests/translator_test.py | 10 +++++----- tests/widgets/export_transcription_menu_test.py | 2 +- .../transcription_segments_editor_widget_test.py | 2 +- .../transcription_viewer_widget_additional_test.py | 2 +- tests/widgets/transcription_viewer_test.py | 2 +- 16 files changed, 30 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c53ee4a6..173d25e4 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,11 @@ Install with [brew utility](https://brew.sh/) brew install --cask buzz ``` -Or download the `.dmg` from the [releases page](https://github.com/chidiwilliams/buzz/releases/latest). +Or download the `.dmg` from the [SourceForge](https://sourceforge.net/projects/buzz-captions/files/). ### Windows -Download and run the `.exe` from the [releases page](https://github.com/chidiwilliams/buzz/releases/latest). +Get the installation files from the [SourceForge](https://sourceforge.net/projects/buzz-captions/files/). App is not signed, you will get a warning when you install it. Select `More info` -> `Run anyway`. diff --git a/buzz/db/migrator.py b/buzz/db/migrator.py index 0fa6b043..d36f9b34 100644 --- a/buzz/db/migrator.py +++ b/buzz/db/migrator.py @@ -69,7 +69,8 @@ class DBMigrator: msg_argv += (args,) else: args = [] - logging.info(msg_tmpl, *msg_argv) + # Uncomment this to get debugging information + # logging.info(msg_tmpl, *msg_argv) self.db.execute(sql, args) self.n_changes += 1 diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index 24fe8013..f6cf91fb 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -139,7 +139,7 @@ class FileTranscriberQueueWorker(QObject): self.current_transcriber.stop() if self.current_transcriber_thread is not None: - if not self.current_transcriber_thread.wait(3000): + if not self.current_transcriber_thread.wait(5000): logging.warning("Transcriber thread did not terminate gracefully") self.current_transcriber_thread.terminate() diff --git a/buzz/transcriber/recording_transcriber.py b/buzz/transcriber/recording_transcriber.py index 5c71b8ba..8e5cc3d1 100644 --- a/buzz/transcriber/recording_transcriber.py +++ b/buzz/transcriber/recording_transcriber.py @@ -326,7 +326,7 @@ class RecordingTranscriber(QObject): self.is_running = False if self.process and self.process.poll() is None: self.process.terminate() - self.process.wait() + self.process.wait(5000) def start_local_whisper_server(self): self.transcription.emit(_("Starting Whisper.cpp...")) @@ -416,4 +416,4 @@ class RecordingTranscriber(QObject): def __del__(self): if self.process and self.process.poll() is None: self.process.terminate() - self.process.wait() \ No newline at end of file + self.process.wait(5000) \ No newline at end of file diff --git a/buzz/transcriber/whisper_file_transcriber.py b/buzz/transcriber/whisper_file_transcriber.py index 1b2ea99e..c5533397 100644 --- a/buzz/transcriber/whisper_file_transcriber.py +++ b/buzz/transcriber/whisper_file_transcriber.py @@ -274,11 +274,11 @@ class WhisperFileTranscriber(FileTranscriber): if self.started_process: self.current_process.terminate() # Use timeout to avoid hanging indefinitely - self.current_process.join(timeout=5) + self.current_process.join(timeout=10) if self.current_process.is_alive(): logging.warning("Process didn't terminate gracefully, force killing") self.current_process.kill() - self.current_process.join(timeout=2) + self.current_process.join(timeout=5) # Close pipes to unblock the read_line thread try: @@ -291,7 +291,7 @@ class WhisperFileTranscriber(FileTranscriber): # Join read_line_thread with timeout to prevent hanging if self.read_line_thread and self.read_line_thread.is_alive(): - self.read_line_thread.join(timeout=3) + self.read_line_thread.join(timeout=5) if self.read_line_thread.is_alive(): logging.warning("Read line thread didn't terminate gracefully") diff --git a/buzz/translator.py b/buzz/translator.py index 0243aacf..56a816ea 100644 --- a/buzz/translator.py +++ b/buzz/translator.py @@ -68,7 +68,8 @@ class Translator(QObject): messages=[ {"role": "system", "content": self.transcription_options.llm_prompt}, {"role": "user", "content": transcript} - ] + ], + timeout=30.0 ) except Exception as e: completion = None diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index ed471ec6..0ca97cd0 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -425,7 +425,7 @@ class MainWindow(QMainWindow): self.transcriber_worker.stop() self.transcriber_thread.quit() - self.transcriber_thread.wait() + self.transcriber_thread.wait(5000) # Wait up to 5 seconds if self.transcription_viewer_widget is not None: self.transcription_viewer_widget.close() diff --git a/buzz/widgets/recording_transcriber_widget.py b/buzz/widgets/recording_transcriber_widget.py index b336121b..80ae166d 100644 --- a/buzz/widgets/recording_transcriber_widget.py +++ b/buzz/widgets/recording_transcriber_widget.py @@ -624,6 +624,10 @@ class RecordingTranscriberWidget(QWidget): if self.translator is not None: self.translator.stop() + if self.translation_thread is not None: + self.translation_thread.quit() + self.translation_thread.wait(35_000) # Wait up to 35 seconds + self.settings.set_value( Settings.Key.RECORDING_TRANSCRIBER_LANGUAGE, self.transcription_options.language, diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index 5b9abeab..ba53226a 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -1348,7 +1348,7 @@ class TranscriptionViewerWidget(QWidget): self.translator.stop() self.translation_thread.quit() - self.translation_thread.wait() + self.translation_thread.wait(35_000) # Wait up to 35 seconds, translation thread also has timeouts, wait longer super().closeEvent(event) diff --git a/pytest.ini b/pytest.ini index ad52348a..b1ef248a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,5 +5,7 @@ qt_api=pyqt6 log_format = %(asctime)s %(levelname)s %(module)s::%(funcName)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S addopts = -x +timeout = 600 +timeout_method = thread markers = timeout: set a timeout on a test function. \ No newline at end of file diff --git a/tests/cli_test.py b/tests/cli_test.py index 9bd077d1..7887acf3 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -20,7 +20,7 @@ class TestCLI: "--task", "transcribe", "--model-size", - "small", + "tiny", "--output-directory", mkdtemp(), "--txt", diff --git a/tests/translator_test.py b/tests/translator_test.py index 56db2fc3..6c0f87d6 100644 --- a/tests/translator_test.py +++ b/tests/translator_test.py @@ -13,7 +13,7 @@ from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDi class TestTranslator: @patch('buzz.translator.OpenAI', autospec=True) @patch('buzz.translator.queue.Queue', autospec=True) - def test_start(self, mock_queue, mock_openai): + def test_start(self, mock_queue, mock_openai, qtbot): def side_effect(*args, **kwargs): side_effect.call_count += 1 @@ -106,11 +106,11 @@ class TestTranslator: if self.translator is not None: self.translator.stop() - self.translator.deleteLater() if self.translation_thread is not None: self.translation_thread.quit() - self.translation_thread.deleteLater() + # Wait for the thread to actually finish before cleanup + self.translation_thread.wait() - # Wait to clean-up threads - time.sleep(3) + # Note: translator and translation_thread will be automatically deleted + # via the deleteLater() connections set up earlier diff --git a/tests/widgets/export_transcription_menu_test.py b/tests/widgets/export_transcription_menu_test.py index 7c15f1c4..30a735be 100644 --- a/tests/widgets/export_transcription_menu_test.py +++ b/tests/widgets/export_transcription_menu_test.py @@ -32,7 +32,7 @@ class TestExportTranscriptionMenu: file=test_audio_path, task=Task.TRANSCRIBE.value, model_type=ModelType.WHISPER.value, - whisper_model_size=WhisperModelSize.SMALL.value, + whisper_model_size=WhisperModelSize.TINY.value, ) ) transcription_segment_dao.insert(TranscriptionSegment(40, 299, "Bien", "", str(id))) diff --git a/tests/widgets/transcription_viewer/transcription_segments_editor_widget_test.py b/tests/widgets/transcription_viewer/transcription_segments_editor_widget_test.py index 5e4fab68..ac8036a9 100644 --- a/tests/widgets/transcription_viewer/transcription_segments_editor_widget_test.py +++ b/tests/widgets/transcription_viewer/transcription_segments_editor_widget_test.py @@ -289,7 +289,7 @@ class TestTranscriptionSegmentsEditorWidget: file=test_audio_path, task=Task.TRANSCRIBE.value, model_type=ModelType.WHISPER.value, - whisper_model_size=WhisperModelSize.SMALL.value, + whisper_model_size=WhisperModelSize.TINY.value, ) ) transcription_segment_dao.insert( diff --git a/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py b/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py index cb1ceb66..8d34460c 100644 --- a/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py +++ b/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py @@ -32,7 +32,7 @@ class TestTranscriptionViewerWidgetAdditional: file=test_audio_path, task=Task.TRANSCRIBE.value, model_type=ModelType.WHISPER.value, - whisper_model_size=WhisperModelSize.SMALL.value, + whisper_model_size=WhisperModelSize.TINY.value, ) ) transcription_segment_dao.insert( diff --git a/tests/widgets/transcription_viewer_test.py b/tests/widgets/transcription_viewer_test.py index ebc5ac01..13d87bc8 100644 --- a/tests/widgets/transcription_viewer_test.py +++ b/tests/widgets/transcription_viewer_test.py @@ -42,7 +42,7 @@ class TestTranscriptionViewerWidget: file=test_audio_path, task=Task.TRANSCRIBE.value, model_type=ModelType.WHISPER.value, - whisper_model_size=WhisperModelSize.SMALL.value, + whisper_model_size=WhisperModelSize.TINY.value, ) ) transcription_segment_dao.insert(TranscriptionSegment(40, 299, "Bien", "", str(id))) From 79d8aadf2f38dc865ba8502491521d0c2f6af004 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sat, 8 Nov 2025 21:21:19 +0200 Subject: [PATCH 02/73] Inline demucs (#1279) --- .github/workflows/ci.yml | 8 + Buzz.spec | 1 - Makefile | 3 +- buzz/__version__.py | 2 +- buzz/widgets/main_window.py | 5 +- buzz/widgets/recording_transcriber_widget.py | 5 +- .../transcription_viewer_widget.py | 6 +- demucs/Readme.md | 1 + demucs/__init__.py | 7 + demucs/__main__.py | 10 + demucs/api.py | 393 ++++++++ demucs/apply.py | 322 +++++++ demucs/audio.py | 266 ++++++ demucs/audio_legacy.py | 17 + demucs/augment.py | 111 +++ demucs/demucs.py | 447 ++++++++++ demucs/distrib.py | 100 +++ demucs/ema.py | 66 ++ demucs/evaluate.py | 174 ++++ demucs/grids/__init__.py | 0 demucs/grids/_explorers.py | 64 ++ demucs/grids/mdx.py | 33 + demucs/grids/mdx_extra.py | 36 + demucs/grids/mdx_refine.py | 34 + demucs/grids/mmi.py | 69 ++ demucs/grids/mmi_ft.py | 55 ++ demucs/grids/repro.py | 50 ++ demucs/grids/repro_ft.py | 46 + demucs/grids/sdx23.py | 19 + demucs/hdemucs.py | 796 +++++++++++++++++ demucs/htdemucs.py | 661 ++++++++++++++ demucs/pretrained.py | 98 ++ demucs/py.typed | 0 demucs/repitch.py | 87 ++ demucs/repo.py | 166 ++++ demucs/separate.py | 228 +++++ demucs/solver.py | 405 +++++++++ demucs/spec.py | 47 + demucs/states.py | 163 ++++ demucs/svd.py | 83 ++ demucs/train.py | 252 ++++++ demucs/transformer.py | 839 ++++++++++++++++++ demucs/utils.py | 149 ++++ demucs/wav.py | 255 ++++++ demucs/wdemucs.py | 9 + hatch_build.py | 54 ++ pyproject.toml | 5 +- pytest.ini | 2 +- .../io.github.chidiwilliams.Buzz.metainfo.xml | 6 +- snap/snapcraft.yaml | 2 +- ...scription_viewer_widget_additional_test.py | 35 +- uv.lock | 60 +- 52 files changed, 6662 insertions(+), 90 deletions(-) create mode 100644 demucs/Readme.md create mode 100644 demucs/__init__.py create mode 100644 demucs/__main__.py create mode 100644 demucs/api.py create mode 100644 demucs/apply.py create mode 100644 demucs/audio.py create mode 100644 demucs/audio_legacy.py create mode 100644 demucs/augment.py create mode 100644 demucs/demucs.py create mode 100644 demucs/distrib.py create mode 100644 demucs/ema.py create mode 100755 demucs/evaluate.py create mode 100644 demucs/grids/__init__.py create mode 100644 demucs/grids/_explorers.py create mode 100644 demucs/grids/mdx.py create mode 100644 demucs/grids/mdx_extra.py create mode 100644 demucs/grids/mdx_refine.py create mode 100644 demucs/grids/mmi.py create mode 100644 demucs/grids/mmi_ft.py create mode 100644 demucs/grids/repro.py create mode 100644 demucs/grids/repro_ft.py create mode 100644 demucs/grids/sdx23.py create mode 100644 demucs/hdemucs.py create mode 100644 demucs/htdemucs.py create mode 100644 demucs/pretrained.py create mode 100644 demucs/py.typed create mode 100644 demucs/repitch.py create mode 100644 demucs/repo.py create mode 100644 demucs/separate.py create mode 100644 demucs/solver.py create mode 100644 demucs/spec.py create mode 100644 demucs/states.py create mode 100644 demucs/svd.py create mode 100644 demucs/train.py create mode 100644 demucs/transformer.py create mode 100755 demucs/utils.py create mode 100644 demucs/wav.py create mode 100644 demucs/wdemucs.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56c80943..dbfa02f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -358,6 +358,7 @@ jobs: files: | Buzz*-unix.tar.gz Buzz*-windows.exe + Buzz*-windows-*.bin Buzz*-mac.dmg deploy_brew_cask: @@ -371,6 +372,13 @@ jobs: with: submodules: recursive + # Should be removed with next update to whisper.cpp + - name: Downgrade Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '16.0.0' + if: matrix.os == 'macos-latest' + - name: Install uv uses: astral-sh/setup-uv@v6 diff --git a/Buzz.spec b/Buzz.spec index 2c6fb968..0f4e8edb 100644 --- a/Buzz.spec +++ b/Buzz.spec @@ -13,7 +13,6 @@ datas += collect_data_files("torch") datas += collect_data_files("demucs") datas += copy_metadata("tqdm") datas += copy_metadata("torch") -datas += copy_metadata("demucs") datas += copy_metadata("regex") datas += copy_metadata("requests") datas += copy_metadata("packaging") diff --git a/Makefile b/Makefile index 859d4b88..9b4050ef 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ -version := 1.3.2 -version_escaped := $$(echo ${version} | sed -e 's/\./\\./g') +version := 1.3.3 mac_app_path := ./dist/Buzz.app mac_zip_path := ./dist/Buzz-${version}-mac.zip diff --git a/buzz/__version__.py b/buzz/__version__.py index 3b734b24..e371c8ac 100644 --- a/buzz/__version__.py +++ b/buzz/__version__.py @@ -1 +1 @@ -VERSION = "1.3.2" +VERSION = "1.3.3" diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 0ca97cd0..8c605f94 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -425,7 +425,10 @@ class MainWindow(QMainWindow): self.transcriber_worker.stop() self.transcriber_thread.quit() - self.transcriber_thread.wait(5000) # Wait up to 5 seconds + # Only wait if thread is actually running + if self.transcriber_thread.isRunning(): + if not self.transcriber_thread.wait(5000): # Wait up to 5 seconds + logging.warning("Transcriber thread did not finish within timeout") if self.transcription_viewer_widget is not None: self.transcription_viewer_widget.close() diff --git a/buzz/widgets/recording_transcriber_widget.py b/buzz/widgets/recording_transcriber_widget.py index 80ae166d..b036fa03 100644 --- a/buzz/widgets/recording_transcriber_widget.py +++ b/buzz/widgets/recording_transcriber_widget.py @@ -626,7 +626,10 @@ class RecordingTranscriberWidget(QWidget): if self.translation_thread is not None: self.translation_thread.quit() - self.translation_thread.wait(35_000) # Wait up to 35 seconds + # Only wait if thread is actually running + if self.translation_thread.isRunning(): + if not self.translation_thread.wait(45_000): + logging.warning("Translation thread did not finish within timeout") self.settings.set_value( Settings.Key.RECORDING_TRANSCRIBER_LANGUAGE, diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index ba53226a..bf4400b3 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -1348,7 +1348,11 @@ class TranscriptionViewerWidget(QWidget): self.translator.stop() self.translation_thread.quit() - self.translation_thread.wait(35_000) # Wait up to 35 seconds, translation thread also has timeouts, wait longer + + # Only wait if thread is actually running + if self.translation_thread.isRunning(): + if not self.translation_thread.wait(45_000): + logging.warning("Translation thread did not finish within timeout") super().closeEvent(event) diff --git a/demucs/Readme.md b/demucs/Readme.md new file mode 100644 index 00000000..402d2b4a --- /dev/null +++ b/demucs/Readme.md @@ -0,0 +1 @@ +Inlined demucs https://github.com/adefossez/demucs \ No newline at end of file diff --git a/demucs/__init__.py b/demucs/__init__.py new file mode 100644 index 00000000..3bf9f708 --- /dev/null +++ b/demucs/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +__version__ = "4.1.0a3" diff --git a/demucs/__main__.py b/demucs/__main__.py new file mode 100644 index 00000000..da0a5410 --- /dev/null +++ b/demucs/__main__.py @@ -0,0 +1,10 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from .separate import main + +if __name__ == '__main__': + main() diff --git a/demucs/api.py b/demucs/api.py new file mode 100644 index 00000000..ee8a5126 --- /dev/null +++ b/demucs/api.py @@ -0,0 +1,393 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +"""API methods for demucs + +Classes +------- +`demucs.api.Separator`: The base separator class + +Functions +--------- +`demucs.api.save_audio`: Save an audio +`demucs.api.list_models`: Get models list + +Examples +-------- +See the end of this module (if __name__ == "__main__") +""" + +import subprocess + +from . import audio_legacy +import torch as th +import torchaudio as ta + +from dora.log import fatal +from pathlib import Path +from typing import Optional, Callable, Dict, Tuple, Union + +from .apply import apply_model, _replace_dict +from .audio import AudioFile, convert_audio, save_audio +from .pretrained import get_model, _parse_remote_files, REMOTE_ROOT +from .repo import RemoteRepo, LocalRepo, ModelOnlyRepo, BagOnlyRepo + + +class LoadAudioError(Exception): + pass + + +class LoadModelError(Exception): + pass + + +class _NotProvided: + pass + + +NotProvided = _NotProvided() + + +class Separator: + def __init__( + self, + model: str = "htdemucs", + repo: Optional[Path] = None, + device: str = "cuda" if th.cuda.is_available() else "cpu", + shifts: int = 1, + overlap: float = 0.25, + split: bool = True, + segment: Optional[int] = None, + jobs: int = 0, + progress: bool = False, + callback: Optional[Callable[[dict], None]] = None, + callback_arg: Optional[dict] = None, + ): + """ + `class Separator` + ================= + + Parameters + ---------- + model: Pretrained model name or signature. Default is htdemucs. + repo: Folder containing all pre-trained models for use. + segment: Length (in seconds) of each segment (only available if `split` is `True`). If \ + not specified, will use the command line option. + shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and \ + apply the oppositve shift to the output. This is repeated `shifts` time and all \ + predictions are averaged. This effectively makes the model time equivariant and \ + improves SDR by up to 0.2 points. If not specified, will use the command line option. + split: If True, the input will be broken down into small chunks (length set by `segment`) \ + and predictions will be performed individually on each and concatenated. Useful for \ + model with large memory footprint like Tasnet. If not specified, will use the command \ + line option. + overlap: The overlap between the splits. If not specified, will use the command line \ + option. + device (torch.device, str, or None): If provided, device on which to execute the \ + computation, otherwise `wav.device` is assumed. When `device` is different from \ + `wav.device`, only local computations will be on `device`, while the entire tracks \ + will be stored on `wav.device`. If not specified, will use the command line option. + jobs: Number of jobs. This can increase memory usage but will be much faster when \ + multiple cores are available. If not specified, will use the command line option. + callback: A function will be called when the separation of a chunk starts or finished. \ + The argument passed to the function will be a dict. For more information, please see \ + the Callback section. + callback_arg: A dict containing private parameters to be passed to callback function. For \ + more information, please see the Callback section. + progress: If true, show a progress bar. + + Callback + -------- + The function will be called with only one positional parameter whose type is `dict`. The + `callback_arg` will be combined with information of current separation progress. The + progress information will override the values in `callback_arg` if same key has been used. + To abort the separation, raise `KeyboardInterrupt`. + + Progress information contains several keys (These keys will always exist): + - `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. + - `shift_idx`: The index of shifts. Starts from 0. + - `segment_offset`: The offset of current segment. If the number is 441000, it doesn't + mean that it is at the 441000 second of the audio, but the "frame" of the tensor. + - `state`: Could be `"start"` or `"end"`. + - `audio_length`: Length of the audio (in "frame" of the tensor). + - `models`: Count of submodels in the model. + """ + self._name = model + self._repo = repo + self._load_model() + self.update_parameter(device=device, shifts=shifts, overlap=overlap, split=split, + segment=segment, jobs=jobs, progress=progress, callback=callback, + callback_arg=callback_arg) + + def update_parameter( + self, + device: Union[str, _NotProvided] = NotProvided, + shifts: Union[int, _NotProvided] = NotProvided, + overlap: Union[float, _NotProvided] = NotProvided, + split: Union[bool, _NotProvided] = NotProvided, + segment: Optional[Union[int, _NotProvided]] = NotProvided, + jobs: Union[int, _NotProvided] = NotProvided, + progress: Union[bool, _NotProvided] = NotProvided, + callback: Optional[ + Union[Callable[[dict], None], _NotProvided] + ] = NotProvided, + callback_arg: Optional[Union[dict, _NotProvided]] = NotProvided, + ): + """ + Update the parameters of separation. + + Parameters + ---------- + segment: Length (in seconds) of each segment (only available if `split` is `True`). If \ + not specified, will use the command line option. + shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and \ + apply the oppositve shift to the output. This is repeated `shifts` time and all \ + predictions are averaged. This effectively makes the model time equivariant and \ + improves SDR by up to 0.2 points. If not specified, will use the command line option. + split: If True, the input will be broken down into small chunks (length set by `segment`) \ + and predictions will be performed individually on each and concatenated. Useful for \ + model with large memory footprint like Tasnet. If not specified, will use the command \ + line option. + overlap: The overlap between the splits. If not specified, will use the command line \ + option. + device (torch.device, str, or None): If provided, device on which to execute the \ + computation, otherwise `wav.device` is assumed. When `device` is different from \ + `wav.device`, only local computations will be on `device`, while the entire tracks \ + will be stored on `wav.device`. If not specified, will use the command line option. + jobs: Number of jobs. This can increase memory usage but will be much faster when \ + multiple cores are available. If not specified, will use the command line option. + callback: A function will be called when the separation of a chunk starts or finished. \ + The argument passed to the function will be a dict. For more information, please see \ + the Callback section. + callback_arg: A dict containing private parameters to be passed to callback function. For \ + more information, please see the Callback section. + progress: If true, show a progress bar. + + Callback + -------- + The function will be called with only one positional parameter whose type is `dict`. The + `callback_arg` will be combined with information of current separation progress. The + progress information will override the values in `callback_arg` if same key has been used. + To abort the separation, raise `KeyboardInterrupt`. + + Progress information contains several keys (These keys will always exist): + - `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. + - `shift_idx`: The index of shifts. Starts from 0. + - `segment_offset`: The offset of current segment. If the number is 441000, it doesn't + mean that it is at the 441000 second of the audio, but the "frame" of the tensor. + - `state`: Could be `"start"` or `"end"`. + - `audio_length`: Length of the audio (in "frame" of the tensor). + - `models`: Count of submodels in the model. + """ + if not isinstance(device, _NotProvided): + self._device = device + if not isinstance(shifts, _NotProvided): + self._shifts = shifts + if not isinstance(overlap, _NotProvided): + self._overlap = overlap + if not isinstance(split, _NotProvided): + self._split = split + if not isinstance(segment, _NotProvided): + self._segment = segment + if not isinstance(jobs, _NotProvided): + self._jobs = jobs + if not isinstance(progress, _NotProvided): + self._progress = progress + if not isinstance(callback, _NotProvided): + self._callback = callback + if not isinstance(callback_arg, _NotProvided): + self._callback_arg = callback_arg + + def _load_model(self): + self._model = get_model(name=self._name, repo=self._repo) + if self._model is None: + raise LoadModelError("Failed to load model") + self._audio_channels = self._model.audio_channels + self._samplerate = self._model.samplerate + + def _load_audio(self, track: Path): + errors = {} + wav = None + + try: + wav = AudioFile(track).read(streams=0, samplerate=self._samplerate, + channels=self._audio_channels) + except FileNotFoundError: + errors["ffmpeg"] = "FFmpeg is not installed." + except subprocess.CalledProcessError: + errors["ffmpeg"] = "FFmpeg could not read the file." + + if wav is None: + try: + wav, sr = ta.load(str(track)) + except RuntimeError as err: + errors["torchaudio"] = err.args[0] + else: + wav = convert_audio(wav, sr, self._samplerate, self._audio_channels) + + if wav is None: + raise LoadAudioError( + "\n".join( + "When trying to load using {}, got the following error: {}".format( + backend, error + ) + for backend, error in errors.items() + ) + ) + return wav + + def separate_tensor( + self, wav: th.Tensor, sr: Optional[int] = None + ) -> Tuple[th.Tensor, Dict[str, th.Tensor]]: + """ + Separate a loaded tensor. + + Parameters + ---------- + wav: Waveform of the audio. Should have 2 dimensions, the first is each audio channel, \ + while the second is the waveform of each channel. Type should be float32. \ + e.g. `tuple(wav.shape) == (2, 884000)` means the audio has 2 channels. + sr: Sample rate of the original audio, the wave will be resampled if it doesn't match the \ + model. + + Returns + ------- + A tuple, whose first element is the original wave and second element is a dict, whose keys + are the name of stems and values are separated waves. The original wave will have already + been resampled. + + Notes + ----- + Use this function with cautiousness. This function does not provide data verifying. + """ + if sr is not None and sr != self.samplerate: + wav = convert_audio(wav, sr, self._samplerate, self._audio_channels) + ref = wav.mean(0) + wav -= ref.mean() + wav /= ref.std() + 1e-8 + out = apply_model( + self._model, + wav[None], + segment=self._segment, + shifts=self._shifts, + split=self._split, + overlap=self._overlap, + device=self._device, + num_workers=self._jobs, + callback=self._callback, + callback_arg=_replace_dict( + self._callback_arg, ("audio_length", wav.shape[1]) + ), + progress=self._progress, + ) + if out is None: + raise KeyboardInterrupt + out *= ref.std() + 1e-8 + out += ref.mean() + wav *= ref.std() + 1e-8 + wav += ref.mean() + return (wav, dict(zip(self._model.sources, out[0]))) + + def separate_audio_file(self, file: Path): + """ + Separate an audio file. The method will automatically read the file. + + Parameters + ---------- + wav: Path of the file to be separated. + + Returns + ------- + A tuple, whose first element is the original wave and second element is a dict, whose keys + are the name of stems and values are separated waves. The original wave will have already + been resampled. + """ + return self.separate_tensor(self._load_audio(file), self.samplerate) + + @property + def samplerate(self): + return self._samplerate + + @property + def audio_channels(self): + return self._audio_channels + + @property + def model(self): + return self._model + + +def list_models(repo: Optional[Path] = None) -> Dict[str, Dict[str, Union[str, Path]]]: + """ + List the available models. Please remember that not all the returned models can be + successfully loaded. + + Parameters + ---------- + repo: The repo whose models are to be listed. + + Returns + ------- + A dict with two keys ("single" for single models and "bag" for bag of models). The values are + lists whose components are strs. + """ + model_repo: ModelOnlyRepo + if repo is None: + models = _parse_remote_files(REMOTE_ROOT / 'files.txt') + model_repo = RemoteRepo(models) + bag_repo = BagOnlyRepo(REMOTE_ROOT, model_repo) + else: + if not repo.is_dir(): + fatal(f"{repo} must exist and be a directory.") + model_repo = LocalRepo(repo) + bag_repo = BagOnlyRepo(repo, model_repo) + return {"single": model_repo.list_model(), "bag": bag_repo.list_model()} + + +if __name__ == "__main__": + # Test API functions + # two-stem not supported + + from .separate import get_parser + + args = get_parser().parse_args() + separator = Separator( + model=args.name, + repo=args.repo, + device=args.device, + shifts=args.shifts, + overlap=args.overlap, + split=args.split, + segment=args.segment, + jobs=args.jobs, + callback=print + ) + out = args.out / args.name + out.mkdir(parents=True, exist_ok=True) + for file in args.tracks: + separated = separator.separate_audio_file(file)[1] + if args.mp3: + ext = "mp3" + elif args.flac: + ext = "flac" + else: + ext = "wav" + kwargs = { + "samplerate": separator.samplerate, + "bitrate": args.mp3_bitrate, + "clip": args.clip_mode, + "as_float": args.float32, + "bits_per_sample": 24 if args.int24 else 16, + } + for stem, source in separated.items(): + stem = out / args.filename.format( + track=Path(file).name.rsplit(".", 1)[0], + trackext=Path(file).name.rsplit(".", 1)[-1], + stem=stem, + ext=ext, + ) + stem.parent.mkdir(parents=True, exist_ok=True) + save_audio(source, str(stem), **kwargs) diff --git a/demucs/apply.py b/demucs/apply.py new file mode 100644 index 00000000..c84993de --- /dev/null +++ b/demucs/apply.py @@ -0,0 +1,322 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +Code to apply a model to a mix. It will handle chunking with overlaps and +inteprolation between chunks, as well as the "shift trick". +""" +from concurrent.futures import ThreadPoolExecutor +import copy +import random +from threading import Lock +import typing as tp + +import torch as th +from torch import nn +from torch.nn import functional as F +import tqdm + +from .demucs import Demucs +from .hdemucs import HDemucs +from .htdemucs import HTDemucs +from .utils import center_trim, DummyPoolExecutor + +Model = tp.Union[Demucs, HDemucs, HTDemucs] + + +class BagOfModels(nn.Module): + def __init__(self, models: tp.List[Model], + weights: tp.Optional[tp.List[tp.List[float]]] = None, + segment: tp.Optional[float] = None): + """ + Represents a bag of models with specific weights. + You should call `apply_model` rather than calling directly the forward here for + optimal performance. + + Args: + models (list[nn.Module]): list of Demucs/HDemucs models. + weights (list[list[float]]): list of weights. If None, assumed to + be all ones, otherwise it should be a list of N list (N number of models), + each containing S floats (S number of sources). + segment (None or float): overrides the `segment` attribute of each model + (this is performed inplace, be careful is you reuse the models passed). + """ + super().__init__() + assert len(models) > 0 + first = models[0] + for other in models: + assert other.sources == first.sources + assert other.samplerate == first.samplerate + assert other.audio_channels == first.audio_channels + if segment is not None: + if not isinstance(other, HTDemucs) or segment <= other.segment: + other.segment = segment + + self.audio_channels = first.audio_channels + self.samplerate = first.samplerate + self.sources = first.sources + self.models = nn.ModuleList(models) + + if weights is None: + weights = [[1. for _ in first.sources] for _ in models] + else: + assert len(weights) == len(models) + for weight in weights: + assert len(weight) == len(first.sources) + self.weights = weights + + @property + def max_allowed_segment(self) -> float: + max_allowed_segment = float('inf') + for model in self.models: + if isinstance(model, HTDemucs): + max_allowed_segment = min(max_allowed_segment, float(model.segment)) + return max_allowed_segment + + def forward(self, x): + raise NotImplementedError("Call `apply_model` on this.") + + +class TensorChunk: + def __init__(self, tensor, offset=0, length=None): + total_length = tensor.shape[-1] + assert offset >= 0 + assert offset < total_length + + if length is None: + length = total_length - offset + else: + length = min(total_length - offset, length) + + if isinstance(tensor, TensorChunk): + self.tensor = tensor.tensor + self.offset = offset + tensor.offset + else: + self.tensor = tensor + self.offset = offset + self.length = length + self.device = tensor.device + + @property + def shape(self): + shape = list(self.tensor.shape) + shape[-1] = self.length + return shape + + def padded(self, target_length): + delta = target_length - self.length + total_length = self.tensor.shape[-1] + assert delta >= 0 + + start = self.offset - delta // 2 + end = start + target_length + + correct_start = max(0, start) + correct_end = min(total_length, end) + + pad_left = correct_start - start + pad_right = end - correct_end + + out = F.pad(self.tensor[..., correct_start:correct_end], (pad_left, pad_right)) + assert out.shape[-1] == target_length + return out + + +def tensor_chunk(tensor_or_chunk): + if isinstance(tensor_or_chunk, TensorChunk): + return tensor_or_chunk + else: + assert isinstance(tensor_or_chunk, th.Tensor) + return TensorChunk(tensor_or_chunk) + + +def _replace_dict(_dict: tp.Optional[dict], *subs: tp.Tuple[tp.Hashable, tp.Any]) -> dict: + if _dict is None: + _dict = {} + else: + _dict = copy.copy(_dict) + for key, value in subs: + _dict[key] = value + return _dict + + +def apply_model(model: tp.Union[BagOfModels, Model], + mix: tp.Union[th.Tensor, TensorChunk], + shifts: int = 1, split: bool = True, + overlap: float = 0.25, transition_power: float = 1., + progress: bool = False, device=None, + num_workers: int = 0, segment: tp.Optional[float] = None, + pool=None, lock=None, + callback: tp.Optional[tp.Callable[[dict], None]] = None, + callback_arg: tp.Optional[dict] = None) -> th.Tensor: + """ + Apply model to a given mixture. + + Args: + shifts (int): if > 0, will shift in time `mix` by a random amount between 0 and 0.5 sec + and apply the oppositve shift to the output. This is repeated `shifts` time and + all predictions are averaged. This effectively makes the model time equivariant + and improves SDR by up to 0.2 points. + split (bool): if True, the input will be broken down in 8 seconds extracts + and predictions will be performed individually on each and concatenated. + Useful for model with large memory footprint like Tasnet. + progress (bool): if True, show a progress bar (requires split=True) + device (torch.device, str, or None): if provided, device on which to + execute the computation, otherwise `mix.device` is assumed. + When `device` is different from `mix.device`, only local computations will + be on `device`, while the entire tracks will be stored on `mix.device`. + num_workers (int): if non zero, device is 'cpu', how many threads to + use in parallel. + segment (float or None): override the model segment parameter. + """ + if device is None: + device = mix.device + else: + device = th.device(device) + if pool is None: + if num_workers > 0 and device.type == 'cpu': + pool = ThreadPoolExecutor(num_workers) + else: + pool = DummyPoolExecutor() + if lock is None: + lock = Lock() + callback_arg = _replace_dict( + callback_arg, *{"model_idx_in_bag": 0, "shift_idx": 0, "segment_offset": 0}.items() + ) + kwargs: tp.Dict[str, tp.Any] = { + 'shifts': shifts, + 'split': split, + 'overlap': overlap, + 'transition_power': transition_power, + 'progress': progress, + 'device': device, + 'pool': pool, + 'segment': segment, + 'lock': lock, + } + out: tp.Union[float, th.Tensor] + res: tp.Union[float, th.Tensor] + if isinstance(model, BagOfModels): + # Special treatment for bag of model. + # We explicitely apply multiple times `apply_model` so that the random shifts + # are different for each model. + estimates: tp.Union[float, th.Tensor] = 0. + totals = [0.] * len(model.sources) + callback_arg["models"] = len(model.models) + for sub_model, model_weights in zip(model.models, model.weights): + kwargs["callback"] = (( + lambda d, i=callback_arg["model_idx_in_bag"]: callback( + _replace_dict(d, ("model_idx_in_bag", i))) if callback else None) + ) + original_model_device = next(iter(sub_model.parameters())).device + sub_model.to(device) + + res = apply_model(sub_model, mix, **kwargs, callback_arg=callback_arg) + out = res + sub_model.to(original_model_device) + for k, inst_weight in enumerate(model_weights): + out[:, k, :, :] *= inst_weight + totals[k] += inst_weight + estimates += out + del out + callback_arg["model_idx_in_bag"] += 1 + + assert isinstance(estimates, th.Tensor) + for k in range(estimates.shape[1]): + estimates[:, k, :, :] /= totals[k] + return estimates + + if "models" not in callback_arg: + callback_arg["models"] = 1 + model.to(device) + model.eval() + assert transition_power >= 1, "transition_power < 1 leads to weird behavior." + batch, channels, length = mix.shape + if shifts: + kwargs['shifts'] = 0 + max_shift = int(0.5 * model.samplerate) + mix = tensor_chunk(mix) + assert isinstance(mix, TensorChunk) + padded_mix = mix.padded(length + 2 * max_shift) + out = 0. + for shift_idx in range(shifts): + offset = random.randint(0, max_shift) + shifted = TensorChunk(padded_mix, offset, length + max_shift - offset) + kwargs["callback"] = ( + (lambda d, i=shift_idx: callback(_replace_dict(d, ("shift_idx", i))) + if callback else None) + ) + res = apply_model(model, shifted, **kwargs, callback_arg=callback_arg) + shifted_out = res + out += shifted_out[..., max_shift - offset:] + out /= shifts + assert isinstance(out, th.Tensor) + return out + elif split: + kwargs['split'] = False + out = th.zeros(batch, len(model.sources), channels, length, device=mix.device) + sum_weight = th.zeros(length, device=mix.device) + if segment is None: + segment = model.segment + assert segment is not None and segment > 0. + segment_length: int = int(model.samplerate * segment) + stride = int((1 - overlap) * segment_length) + offsets = range(0, length, stride) + scale = float(format(stride / model.samplerate, ".2f")) + # We start from a triangle shaped weight, with maximal weight in the middle + # of the segment. Then we normalize and take to the power `transition_power`. + # Large values of transition power will lead to sharper transitions. + weight = th.cat([th.arange(1, segment_length // 2 + 1, device=device), + th.arange(segment_length - segment_length // 2, 0, -1, device=device)]) + assert len(weight) == segment_length + # If the overlap < 50%, this will translate to linear transition when + # transition_power is 1. + weight = (weight / weight.max())**transition_power + futures = [] + for offset in offsets: + chunk = TensorChunk(mix, offset, segment_length) + future = pool.submit(apply_model, model, chunk, **kwargs, callback_arg=callback_arg, + callback=(lambda d, i=offset: + callback(_replace_dict(d, ("segment_offset", i))) + if callback else None)) + futures.append((future, offset)) + offset += segment_length + if progress: + futures = tqdm.tqdm(futures, unit_scale=scale, ncols=120, unit='seconds') + for future, offset in futures: + try: + chunk_out = future.result() # type: th.Tensor + except Exception: + pool.shutdown(wait=True, cancel_futures=True) + raise + chunk_length = chunk_out.shape[-1] + out[..., offset:offset + segment_length] += ( + weight[:chunk_length] * chunk_out).to(mix.device) + sum_weight[offset:offset + segment_length] += weight[:chunk_length].to(mix.device) + assert sum_weight.min() > 0 + out /= sum_weight + assert isinstance(out, th.Tensor) + return out + else: + valid_length: int + if isinstance(model, HTDemucs) and segment is not None: + valid_length = int(segment * model.samplerate) + elif hasattr(model, 'valid_length'): + valid_length = model.valid_length(length) # type: ignore + else: + valid_length = length + mix = tensor_chunk(mix) + assert isinstance(mix, TensorChunk) + padded_mix = mix.padded(valid_length).to(device) + with lock: + if callback is not None: + callback(_replace_dict(callback_arg, ("state", "start"))) # type: ignore + with th.no_grad(): + out = model(padded_mix) + with lock: + if callback is not None: + callback(_replace_dict(callback_arg, ("state", "end"))) # type: ignore + assert isinstance(out, th.Tensor) + return center_trim(out, length) diff --git a/demucs/audio.py b/demucs/audio.py new file mode 100644 index 00000000..600bd55b --- /dev/null +++ b/demucs/audio.py @@ -0,0 +1,266 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +import json +import subprocess as sp +from pathlib import Path + +import lameenc +import julius +import numpy as np +from . import audio_legacy +import torch +import torchaudio as ta +import typing as tp + +from .utils import temp_filenames + + +def _read_info(path): + stdout_data = sp.check_output([ + 'ffprobe', "-loglevel", "panic", + str(path), '-print_format', 'json', '-show_format', '-show_streams' + ]) + return json.loads(stdout_data.decode('utf-8')) + + +class AudioFile: + """ + Allows to read audio from any format supported by ffmpeg, as well as resampling or + converting to mono on the fly. See :method:`read` for more details. + """ + def __init__(self, path: Path): + self.path = Path(path) + self._info = None + + def __repr__(self): + features = [("path", self.path)] + features.append(("samplerate", self.samplerate())) + features.append(("channels", self.channels())) + features.append(("streams", len(self))) + features_str = ", ".join(f"{name}={value}" for name, value in features) + return f"AudioFile({features_str})" + + @property + def info(self): + if self._info is None: + self._info = _read_info(self.path) + return self._info + + @property + def duration(self): + return float(self.info['format']['duration']) + + @property + def _audio_streams(self): + return [ + index for index, stream in enumerate(self.info["streams"]) + if stream["codec_type"] == "audio" + ] + + def __len__(self): + return len(self._audio_streams) + + def channels(self, stream=0): + return int(self.info['streams'][self._audio_streams[stream]]['channels']) + + def samplerate(self, stream=0): + return int(self.info['streams'][self._audio_streams[stream]]['sample_rate']) + + def read(self, + seek_time=None, + duration=None, + streams=slice(None), + samplerate=None, + channels=None): + """ + Slightly more efficient implementation than stempeg, + in particular, this will extract all stems at once + rather than having to loop over one file multiple times + for each stream. + + Args: + seek_time (float): seek time in seconds or None if no seeking is needed. + duration (float): duration in seconds to extract or None to extract until the end. + streams (slice, int or list): streams to extract, can be a single int, a list or + a slice. If it is a slice or list, the output will be of size [S, C, T] + with S the number of streams, C the number of channels and T the number of samples. + If it is an int, the output will be [C, T]. + samplerate (int): if provided, will resample on the fly. If None, no resampling will + be done. Original sampling rate can be obtained with :method:`samplerate`. + channels (int): if 1, will convert to mono. We do not rely on ffmpeg for that + as ffmpeg automatically scale by +3dB to conserve volume when playing on speakers. + See https://sound.stackexchange.com/a/42710. + Our definition of mono is simply the average of the two channels. Any other + value will be ignored. + """ + streams = np.array(range(len(self)))[streams] + single = not isinstance(streams, np.ndarray) + if single: + streams = [streams] + + if duration is None: + target_size = None + query_duration = None + else: + target_size = int((samplerate or self.samplerate()) * duration) + query_duration = float((target_size + 1) / (samplerate or self.samplerate())) + + with temp_filenames(len(streams)) as filenames: + command = ['ffmpeg', '-y'] + command += ['-loglevel', 'panic'] + if seek_time: + command += ['-ss', str(seek_time)] + command += ['-i', str(self.path)] + for stream, filename in zip(streams, filenames): + command += ['-map', f'0:{self._audio_streams[stream]}'] + if query_duration is not None: + command += ['-t', str(query_duration)] + command += ['-threads', '1'] + command += ['-f', 'f32le'] + if samplerate is not None: + command += ['-ar', str(samplerate)] + command += [filename] + + sp.run(command, check=True) + wavs = [] + for filename in filenames: + wav = np.fromfile(filename, dtype=np.float32) + wav = torch.from_numpy(wav) + wav = wav.view(-1, self.channels()).t() + if channels is not None: + wav = convert_audio_channels(wav, channels) + if target_size is not None: + wav = wav[..., :target_size] + wavs.append(wav) + wav = torch.stack(wavs, dim=0) + if single: + wav = wav[0] + return wav + + +def convert_audio_channels(wav, channels=2): + """Convert audio to the given number of channels.""" + *shape, src_channels, length = wav.shape + if src_channels == channels: + pass + elif channels == 1: + # Case 1: + # The caller asked 1-channel audio, but the stream have multiple + # channels, downmix all channels. + wav = wav.mean(dim=-2, keepdim=True) + elif src_channels == 1: + # Case 2: + # The caller asked for multiple channels, but the input file have + # one single channel, replicate the audio over all channels. + wav = wav.expand(*shape, channels, length) + elif src_channels >= channels: + # Case 3: + # The caller asked for multiple channels, and the input file have + # more channels than requested. In that case return the first channels. + wav = wav[..., :channels, :] + else: + # Case 4: What is a reasonable choice here? + raise ValueError('The audio file has less channels than requested but is not mono.') + return wav + + +def convert_audio(wav, from_samplerate, to_samplerate, channels) -> torch.Tensor: + """Convert audio from a given samplerate to a target one and target number of channels.""" + wav = convert_audio_channels(wav, channels) + return julius.resample_frac(wav, from_samplerate, to_samplerate) + + +def i16_pcm(wav): + """Convert audio to 16 bits integer PCM format.""" + if wav.dtype.is_floating_point: + return (wav.clamp_(-1, 1) * (2**15 - 1)).short() + else: + return wav + + +def f32_pcm(wav): + """Convert audio to float 32 bits PCM format.""" + if wav.dtype.is_floating_point: + return wav + else: + return wav.float() / (2**15 - 1) + + +def as_dtype_pcm(wav, dtype): + """Convert audio to either f32 pcm or i16 pcm depending on the given dtype.""" + if wav.dtype.is_floating_point: + return f32_pcm(wav) + else: + return i16_pcm(wav) + + +def encode_mp3(wav, path, samplerate=44100, bitrate=320, quality=2, verbose=False): + """Save given audio as mp3. This should work on all OSes.""" + C, T = wav.shape + wav = i16_pcm(wav) + encoder = lameenc.Encoder() + encoder.set_bit_rate(bitrate) + encoder.set_in_sample_rate(samplerate) + encoder.set_channels(C) + encoder.set_quality(quality) # 2-highest, 7-fastest + if not verbose: + encoder.silence() + wav = wav.data.cpu() + wav = wav.transpose(0, 1).numpy() + mp3_data = encoder.encode(wav.tobytes()) + mp3_data += encoder.flush() + with open(path, "wb") as f: + f.write(mp3_data) + + +def prevent_clip(wav, mode='rescale'): + """ + different strategies for avoiding raw clipping. + """ + if mode is None or mode == 'none': + return wav + assert wav.dtype.is_floating_point, "too late for clipping" + if mode == 'rescale': + wav = wav / max(1.01 * wav.abs().max(), 1) + elif mode == 'clamp': + wav = wav.clamp(-0.99, 0.99) + elif mode == 'tanh': + wav = torch.tanh(wav) + else: + raise ValueError(f"Invalid mode {mode}") + return wav + + +def save_audio(wav: torch.Tensor, + path: tp.Union[str, Path], + samplerate: int, + bitrate: int = 320, + clip: tp.Literal["rescale", "clamp", "tanh", "none"] = 'rescale', + bits_per_sample: tp.Literal[16, 24, 32] = 16, + as_float: bool = False, + preset: tp.Literal[2, 3, 4, 5, 6, 7] = 2): + """Save audio file, automatically preventing clipping if necessary + based on the given `clip` strategy. If the path ends in `.mp3`, this + will save as mp3 with the given `bitrate`. Use `preset` to set mp3 quality: + 2 for highest quality, 7 for fastest speed + """ + wav = prevent_clip(wav, mode=clip) + path = Path(path) + suffix = path.suffix.lower() + if suffix == ".mp3": + encode_mp3(wav, path, samplerate, bitrate, preset, verbose=True) + elif suffix == ".wav": + if as_float: + bits_per_sample = 32 + encoding = 'PCM_F' + else: + encoding = 'PCM_S' + ta.save(str(path), wav, sample_rate=samplerate, + encoding=encoding, bits_per_sample=bits_per_sample) + elif suffix == ".flac": + ta.save(str(path), wav, sample_rate=samplerate, bits_per_sample=bits_per_sample) + else: + raise ValueError(f"Invalid suffix for path: {suffix}") diff --git a/demucs/audio_legacy.py b/demucs/audio_legacy.py new file mode 100644 index 00000000..ab6bdce4 --- /dev/null +++ b/demucs/audio_legacy.py @@ -0,0 +1,17 @@ +# This file is to extend support for torchaudio 2.1 + +import importlib +import os +import sys +import warnings + +if not "torchaudio" in sys.modules: + os.environ["TORCHAUDIO_USE_BACKEND_DISPATCHER"] = "0" +elif os.getenv("TORCHAUDIO_USE_BACKEND_DISPATCHER", default="1") == "1": + if sys.modules["torchaudio"].__version__ >= "2.1": + os.environ["TORCHAUDIO_USE_BACKEND_DISPATCHER"] = "0" + importlib.reload(sys.modules["torchaudio"]) + warnings.warn( + "TORCHAUDIO_USE_BACKEND_DISPATCHER is set to 0 and torchaudio is reloaded.", + ImportWarning, + ) diff --git a/demucs/augment.py b/demucs/augment.py new file mode 100644 index 00000000..6dab7f12 --- /dev/null +++ b/demucs/augment.py @@ -0,0 +1,111 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Data augmentations. +""" + +import random +import torch as th +from torch import nn + + +class Shift(nn.Module): + """ + Randomly shift audio in time by up to `shift` samples. + """ + def __init__(self, shift=8192, same=False): + super().__init__() + self.shift = shift + self.same = same + + def forward(self, wav): + batch, sources, channels, time = wav.size() + length = time - self.shift + if self.shift > 0: + if not self.training: + wav = wav[..., :length] + else: + srcs = 1 if self.same else sources + offsets = th.randint(self.shift, [batch, srcs, 1, 1], device=wav.device) + offsets = offsets.expand(-1, sources, channels, -1) + indexes = th.arange(length, device=wav.device) + wav = wav.gather(3, indexes + offsets) + return wav + + +class FlipChannels(nn.Module): + """ + Flip left-right channels. + """ + def forward(self, wav): + batch, sources, channels, time = wav.size() + if self.training and wav.size(2) == 2: + left = th.randint(2, (batch, sources, 1, 1), device=wav.device) + left = left.expand(-1, -1, -1, time) + right = 1 - left + wav = th.cat([wav.gather(2, left), wav.gather(2, right)], dim=2) + return wav + + +class FlipSign(nn.Module): + """ + Random sign flip. + """ + def forward(self, wav): + batch, sources, channels, time = wav.size() + if self.training: + signs = th.randint(2, (batch, sources, 1, 1), device=wav.device, dtype=th.float32) + wav = wav * (2 * signs - 1) + return wav + + +class Remix(nn.Module): + """ + Shuffle sources to make new mixes. + """ + def __init__(self, proba=1, group_size=4): + """ + Shuffle sources within one batch. + Each batch is divided into groups of size `group_size` and shuffling is done within + each group separatly. This allow to keep the same probability distribution no matter + the number of GPUs. Without this grouping, using more GPUs would lead to a higher + probability of keeping two sources from the same track together which can impact + performance. + """ + super().__init__() + self.proba = proba + self.group_size = group_size + + def forward(self, wav): + batch, streams, channels, time = wav.size() + device = wav.device + + if self.training and random.random() < self.proba: + group_size = self.group_size or batch + if batch % group_size != 0: + raise ValueError(f"Batch size {batch} must be divisible by group size {group_size}") + groups = batch // group_size + wav = wav.view(groups, group_size, streams, channels, time) + permutations = th.argsort(th.rand(groups, group_size, streams, 1, 1, device=device), + dim=1) + wav = wav.gather(1, permutations.expand(-1, -1, -1, channels, time)) + wav = wav.view(batch, streams, channels, time) + return wav + + +class Scale(nn.Module): + def __init__(self, proba=1., min=0.25, max=1.25): + super().__init__() + self.proba = proba + self.min = min + self.max = max + + def forward(self, wav): + batch, streams, channels, time = wav.size() + device = wav.device + if self.training and random.random() < self.proba: + scales = th.empty(batch, streams, 1, 1, device=device).uniform_(self.min, self.max) + wav *= scales + return wav diff --git a/demucs/demucs.py b/demucs/demucs.py new file mode 100644 index 00000000..f6a4305c --- /dev/null +++ b/demucs/demucs.py @@ -0,0 +1,447 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import math +import typing as tp + +import julius +import torch +from torch import nn +from torch.nn import functional as F + +from .states import capture_init +from .utils import center_trim, unfold +from .transformer import LayerScale + + +class BLSTM(nn.Module): + """ + BiLSTM with same hidden units as input dim. + If `max_steps` is not None, input will be splitting in overlapping + chunks and the LSTM applied separately on each chunk. + """ + def __init__(self, dim, layers=1, max_steps=None, skip=False): + super().__init__() + assert max_steps is None or max_steps % 4 == 0 + self.max_steps = max_steps + self.lstm = nn.LSTM(bidirectional=True, num_layers=layers, hidden_size=dim, input_size=dim) + self.linear = nn.Linear(2 * dim, dim) + self.skip = skip + + def forward(self, x): + B, C, T = x.shape + y = x + framed = False + if self.max_steps is not None and T > self.max_steps: + width = self.max_steps + stride = width // 2 + frames = unfold(x, width, stride) + nframes = frames.shape[2] + framed = True + x = frames.permute(0, 2, 1, 3).reshape(-1, C, width) + + x = x.permute(2, 0, 1) + + x = self.lstm(x)[0] + x = self.linear(x) + x = x.permute(1, 2, 0) + if framed: + out = [] + frames = x.reshape(B, -1, C, width) + limit = stride // 2 + for k in range(nframes): + if k == 0: + out.append(frames[:, k, :, :-limit]) + elif k == nframes - 1: + out.append(frames[:, k, :, limit:]) + else: + out.append(frames[:, k, :, limit:-limit]) + out = torch.cat(out, -1) + out = out[..., :T] + x = out + if self.skip: + x = x + y + return x + + +def rescale_conv(conv, reference): + """Rescale initial weight scale. It is unclear why it helps but it certainly does. + """ + std = conv.weight.std().detach() + scale = (std / reference)**0.5 + conv.weight.data /= scale + if conv.bias is not None: + conv.bias.data /= scale + + +def rescale_module(module, reference): + for sub in module.modules(): + if isinstance(sub, (nn.Conv1d, nn.ConvTranspose1d, nn.Conv2d, nn.ConvTranspose2d)): + rescale_conv(sub, reference) + + +class DConv(nn.Module): + """ + New residual branches in each encoder layer. + This alternates dilated convolutions, potentially with LSTMs and attention. + Also before entering each residual branch, dimension is projected on a smaller subspace, + e.g. of dim `channels // compress`. + """ + def __init__(self, channels: int, compress: float = 4, depth: int = 2, init: float = 1e-4, + norm=True, attn=False, heads=4, ndecay=4, lstm=False, gelu=True, + kernel=3, dilate=True): + """ + Args: + channels: input/output channels for residual branch. + compress: amount of channel compression inside the branch. + depth: number of layers in the residual branch. Each layer has its own + projection, and potentially LSTM and attention. + init: initial scale for LayerNorm. + norm: use GroupNorm. + attn: use LocalAttention. + heads: number of heads for the LocalAttention. + ndecay: number of decay controls in the LocalAttention. + lstm: use LSTM. + gelu: Use GELU activation. + kernel: kernel size for the (dilated) convolutions. + dilate: if true, use dilation, increasing with the depth. + """ + + super().__init__() + assert kernel % 2 == 1 + self.channels = channels + self.compress = compress + self.depth = abs(depth) + dilate = depth > 0 + + norm_fn: tp.Callable[[int], nn.Module] + norm_fn = lambda d: nn.Identity() # noqa + if norm: + norm_fn = lambda d: nn.GroupNorm(1, d) # noqa + + hidden = int(channels / compress) + + act: tp.Type[nn.Module] + if gelu: + act = nn.GELU + else: + act = nn.ReLU + + self.layers = nn.ModuleList([]) + for d in range(self.depth): + dilation = 2 ** d if dilate else 1 + padding = dilation * (kernel // 2) + mods = [ + nn.Conv1d(channels, hidden, kernel, dilation=dilation, padding=padding), + norm_fn(hidden), act(), + nn.Conv1d(hidden, 2 * channels, 1), + norm_fn(2 * channels), nn.GLU(1), + LayerScale(channels, init), + ] + if attn: + mods.insert(3, LocalState(hidden, heads=heads, ndecay=ndecay)) + if lstm: + mods.insert(3, BLSTM(hidden, layers=2, max_steps=200, skip=True)) + layer = nn.Sequential(*mods) + self.layers.append(layer) + + def forward(self, x): + for layer in self.layers: + x = x + layer(x) + return x + + +class LocalState(nn.Module): + """Local state allows to have attention based only on data (no positional embedding), + but while setting a constraint on the time window (e.g. decaying penalty term). + + Also a failed experiments with trying to provide some frequency based attention. + """ + def __init__(self, channels: int, heads: int = 4, nfreqs: int = 0, ndecay: int = 4): + super().__init__() + assert channels % heads == 0, (channels, heads) + self.heads = heads + self.nfreqs = nfreqs + self.ndecay = ndecay + self.content = nn.Conv1d(channels, channels, 1) + self.query = nn.Conv1d(channels, channels, 1) + self.key = nn.Conv1d(channels, channels, 1) + if nfreqs: + self.query_freqs = nn.Conv1d(channels, heads * nfreqs, 1) + if ndecay: + self.query_decay = nn.Conv1d(channels, heads * ndecay, 1) + # Initialize decay close to zero (there is a sigmoid), for maximum initial window. + self.query_decay.weight.data *= 0.01 + assert self.query_decay.bias is not None # stupid type checker + self.query_decay.bias.data[:] = -2 + self.proj = nn.Conv1d(channels + heads * nfreqs, channels, 1) + + def forward(self, x): + B, C, T = x.shape + heads = self.heads + indexes = torch.arange(T, device=x.device, dtype=x.dtype) + # left index are keys, right index are queries + delta = indexes[:, None] - indexes[None, :] + + queries = self.query(x).view(B, heads, -1, T) + keys = self.key(x).view(B, heads, -1, T) + # t are keys, s are queries + dots = torch.einsum("bhct,bhcs->bhts", keys, queries) + dots /= keys.shape[2]**0.5 + if self.nfreqs: + periods = torch.arange(1, self.nfreqs + 1, device=x.device, dtype=x.dtype) + freq_kernel = torch.cos(2 * math.pi * delta / periods.view(-1, 1, 1)) + freq_q = self.query_freqs(x).view(B, heads, -1, T) / self.nfreqs ** 0.5 + dots += torch.einsum("fts,bhfs->bhts", freq_kernel, freq_q) + if self.ndecay: + decays = torch.arange(1, self.ndecay + 1, device=x.device, dtype=x.dtype) + decay_q = self.query_decay(x).view(B, heads, -1, T) + decay_q = torch.sigmoid(decay_q) / 2 + decay_kernel = - decays.view(-1, 1, 1) * delta.abs() / self.ndecay**0.5 + dots += torch.einsum("fts,bhfs->bhts", decay_kernel, decay_q) + + # Kill self reference. + dots.masked_fill_(torch.eye(T, device=dots.device, dtype=torch.bool), -100) + weights = torch.softmax(dots, dim=2) + + content = self.content(x).view(B, heads, -1, T) + result = torch.einsum("bhts,bhct->bhcs", weights, content) + if self.nfreqs: + time_sig = torch.einsum("bhts,fts->bhfs", weights, freq_kernel) + result = torch.cat([result, time_sig], 2) + result = result.reshape(B, -1, T) + return x + self.proj(result) + + +class Demucs(nn.Module): + @capture_init + def __init__(self, + sources, + # Channels + audio_channels=2, + channels=64, + growth=2., + # Main structure + depth=6, + rewrite=True, + lstm_layers=0, + # Convolutions + kernel_size=8, + stride=4, + context=1, + # Activations + gelu=True, + glu=True, + # Normalization + norm_starts=4, + norm_groups=4, + # DConv residual branch + dconv_mode=1, + dconv_depth=2, + dconv_comp=4, + dconv_attn=4, + dconv_lstm=4, + dconv_init=1e-4, + # Pre/post processing + normalize=True, + resample=True, + # Weight init + rescale=0.1, + # Metadata + samplerate=44100, + segment=4 * 10): + """ + Args: + sources (list[str]): list of source names + audio_channels (int): stereo or mono + channels (int): first convolution channels + depth (int): number of encoder/decoder layers + growth (float): multiply (resp divide) number of channels by that + for each layer of the encoder (resp decoder) + depth (int): number of layers in the encoder and in the decoder. + rewrite (bool): add 1x1 convolution to each layer. + lstm_layers (int): number of lstm layers, 0 = no lstm. Deactivated + by default, as this is now replaced by the smaller and faster small LSTMs + in the DConv branches. + kernel_size (int): kernel size for convolutions + stride (int): stride for convolutions + context (int): kernel size of the convolution in the + decoder before the transposed convolution. If > 1, + will provide some context from neighboring time steps. + gelu: use GELU activation function. + glu (bool): use glu instead of ReLU for the 1x1 rewrite conv. + norm_starts: layer at which group norm starts being used. + decoder layers are numbered in reverse order. + norm_groups: number of groups for group norm. + dconv_mode: if 1: dconv in encoder only, 2: decoder only, 3: both. + dconv_depth: depth of residual DConv branch. + dconv_comp: compression of DConv branch. + dconv_attn: adds attention layers in DConv branch starting at this layer. + dconv_lstm: adds a LSTM layer in DConv branch starting at this layer. + dconv_init: initial scale for the DConv branch LayerScale. + normalize (bool): normalizes the input audio on the fly, and scales back + the output by the same amount. + resample (bool): upsample x2 the input and downsample /2 the output. + rescale (float): rescale initial weights of convolutions + to get their standard deviation closer to `rescale`. + samplerate (int): stored as meta information for easing + future evaluations of the model. + segment (float): duration of the chunks of audio to ideally evaluate the model on. + This is used by `demucs.apply.apply_model`. + """ + + super().__init__() + self.audio_channels = audio_channels + self.sources = sources + self.kernel_size = kernel_size + self.context = context + self.stride = stride + self.depth = depth + self.resample = resample + self.channels = channels + self.normalize = normalize + self.samplerate = samplerate + self.segment = segment + self.encoder = nn.ModuleList() + self.decoder = nn.ModuleList() + self.skip_scales = nn.ModuleList() + + if glu: + activation = nn.GLU(dim=1) + ch_scale = 2 + else: + activation = nn.ReLU() + ch_scale = 1 + if gelu: + act2 = nn.GELU + else: + act2 = nn.ReLU + + in_channels = audio_channels + padding = 0 + for index in range(depth): + norm_fn = lambda d: nn.Identity() # noqa + if index >= norm_starts: + norm_fn = lambda d: nn.GroupNorm(norm_groups, d) # noqa + + encode = [] + encode += [ + nn.Conv1d(in_channels, channels, kernel_size, stride), + norm_fn(channels), + act2(), + ] + attn = index >= dconv_attn + lstm = index >= dconv_lstm + if dconv_mode & 1: + encode += [DConv(channels, depth=dconv_depth, init=dconv_init, + compress=dconv_comp, attn=attn, lstm=lstm)] + if rewrite: + encode += [ + nn.Conv1d(channels, ch_scale * channels, 1), + norm_fn(ch_scale * channels), activation] + self.encoder.append(nn.Sequential(*encode)) + + decode = [] + if index > 0: + out_channels = in_channels + else: + out_channels = len(self.sources) * audio_channels + if rewrite: + decode += [ + nn.Conv1d(channels, ch_scale * channels, 2 * context + 1, padding=context), + norm_fn(ch_scale * channels), activation] + if dconv_mode & 2: + decode += [DConv(channels, depth=dconv_depth, init=dconv_init, + compress=dconv_comp, attn=attn, lstm=lstm)] + decode += [nn.ConvTranspose1d(channels, out_channels, + kernel_size, stride, padding=padding)] + if index > 0: + decode += [norm_fn(out_channels), act2()] + self.decoder.insert(0, nn.Sequential(*decode)) + in_channels = channels + channels = int(growth * channels) + + channels = in_channels + if lstm_layers: + self.lstm = BLSTM(channels, lstm_layers) + else: + self.lstm = None + + if rescale: + rescale_module(self, reference=rescale) + + def valid_length(self, length): + """ + Return the nearest valid length to use with the model so that + there is no time steps left over in a convolution, e.g. for all + layers, size of the input - kernel_size % stride = 0. + + Note that input are automatically padded if necessary to ensure that the output + has the same length as the input. + """ + if self.resample: + length *= 2 + + for _ in range(self.depth): + length = math.ceil((length - self.kernel_size) / self.stride) + 1 + length = max(1, length) + + for idx in range(self.depth): + length = (length - 1) * self.stride + self.kernel_size + + if self.resample: + length = math.ceil(length / 2) + return int(length) + + def forward(self, mix): + x = mix + length = x.shape[-1] + + if self.normalize: + mono = mix.mean(dim=1, keepdim=True) + mean = mono.mean(dim=-1, keepdim=True) + std = mono.std(dim=-1, keepdim=True) + x = (x - mean) / (1e-5 + std) + else: + mean = 0 + std = 1 + + delta = self.valid_length(length) - length + x = F.pad(x, (delta // 2, delta - delta // 2)) + + if self.resample: + x = julius.resample_frac(x, 1, 2) + + saved = [] + for encode in self.encoder: + x = encode(x) + saved.append(x) + + if self.lstm: + x = self.lstm(x) + + for decode in self.decoder: + skip = saved.pop(-1) + skip = center_trim(skip, x) + x = decode(x + skip) + + if self.resample: + x = julius.resample_frac(x, 2, 1) + x = x * std + mean + x = center_trim(x, length) + x = x.view(x.size(0), len(self.sources), self.audio_channels, x.size(-1)) + return x + + def load_state_dict(self, state, strict=True): + # fix a mismatch with previous generation Demucs models. + for idx in range(self.depth): + for a in ['encoder', 'decoder']: + for b in ['bias', 'weight']: + new = f'{a}.{idx}.3.{b}' + old = f'{a}.{idx}.2.{b}' + if old in state and new not in state: + state[new] = state.pop(old) + super().load_state_dict(state, strict=strict) diff --git a/demucs/distrib.py b/demucs/distrib.py new file mode 100644 index 00000000..dc1576cb --- /dev/null +++ b/demucs/distrib.py @@ -0,0 +1,100 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Distributed training utilities. +""" +import logging +import pickle + +import numpy as np +import torch +from torch.utils.data.distributed import DistributedSampler +from torch.utils.data import DataLoader, Subset +from torch.nn.parallel.distributed import DistributedDataParallel + +from dora import distrib as dora_distrib + +logger = logging.getLogger(__name__) +rank = 0 +world_size = 1 + + +def init(): + global rank, world_size + if not torch.distributed.is_initialized(): + dora_distrib.init() + rank = dora_distrib.rank() + world_size = dora_distrib.world_size() + + +def average(metrics, count=1.): + if isinstance(metrics, dict): + keys, values = zip(*sorted(metrics.items())) + values = average(values, count) + return dict(zip(keys, values)) + if world_size == 1: + return metrics + tensor = torch.tensor(list(metrics) + [1], device='cuda', dtype=torch.float32) + tensor *= count + torch.distributed.all_reduce(tensor, op=torch.distributed.ReduceOp.SUM) + return (tensor[:-1] / tensor[-1]).cpu().numpy().tolist() + + +def wrap(model): + if world_size == 1: + return model + else: + return DistributedDataParallel( + model, + # find_unused_parameters=True, + device_ids=[torch.cuda.current_device()], + output_device=torch.cuda.current_device()) + + +def barrier(): + if world_size > 1: + torch.distributed.barrier() + + +def share(obj=None, src=0): + if world_size == 1: + return obj + size = torch.empty(1, device='cuda', dtype=torch.long) + if rank == src: + dump = pickle.dumps(obj) + size[0] = len(dump) + torch.distributed.broadcast(size, src=src) + # size variable is now set to the length of pickled obj in all processes + + if rank == src: + buffer = torch.from_numpy(np.frombuffer(dump, dtype=np.uint8).copy()).cuda() + else: + buffer = torch.empty(size[0].item(), device='cuda', dtype=torch.uint8) + torch.distributed.broadcast(buffer, src=src) + # buffer variable is now set to pickled obj in all processes + + if rank != src: + obj = pickle.loads(buffer.cpu().numpy().tobytes()) + logger.debug(f"Shared object of size {len(buffer)}") + return obj + + +def loader(dataset, *args, shuffle=False, klass=DataLoader, **kwargs): + """ + Create a dataloader properly in case of distributed training. + If a gradient is going to be computed you must set `shuffle=True`. + """ + if world_size == 1: + return klass(dataset, *args, shuffle=shuffle, **kwargs) + + if shuffle: + # train means we will compute backward, we use DistributedSampler + sampler = DistributedSampler(dataset) + # We ignore shuffle, DistributedSampler already shuffles + return klass(dataset, *args, **kwargs, sampler=sampler) + else: + # We make a manual shard, as DistributedSampler otherwise replicate some examples + dataset = Subset(dataset, list(range(rank, len(dataset), world_size))) + return klass(dataset, *args, shuffle=shuffle, **kwargs) diff --git a/demucs/ema.py b/demucs/ema.py new file mode 100644 index 00000000..101bee02 --- /dev/null +++ b/demucs/ema.py @@ -0,0 +1,66 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +# Inspired from https://github.com/rwightman/pytorch-image-models +from contextlib import contextmanager + +import torch + +from .states import swap_state + + +class ModelEMA: + """ + Perform EMA on a model. You can switch to the EMA weights temporarily + with the `swap` method. + + ema = ModelEMA(model) + with ema.swap(): + # compute valid metrics with averaged model. + """ + def __init__(self, model, decay=0.9999, unbias=True, device='cpu'): + self.decay = decay + self.model = model + self.state = {} + self.count = 0 + self.device = device + self.unbias = unbias + + self._init() + + def _init(self): + for key, val in self.model.state_dict().items(): + if val.dtype != torch.float32: + continue + device = self.device or val.device + if key not in self.state: + self.state[key] = val.detach().to(device, copy=True) + + def update(self): + if self.unbias: + self.count = self.count * self.decay + 1 + w = 1 / self.count + else: + w = 1 - self.decay + for key, val in self.model.state_dict().items(): + if val.dtype != torch.float32: + continue + device = self.device or val.device + self.state[key].mul_(1 - w) + self.state[key].add_(val.detach().to(device), alpha=w) + + @contextmanager + def swap(self): + with swap_state(self.model, self.state): + yield + + def state_dict(self): + return {'state': self.state, 'count': self.count} + + def load_state_dict(self, state): + self.count = state['count'] + for k, v in state['state'].items(): + self.state[k].copy_(v) diff --git a/demucs/evaluate.py b/demucs/evaluate.py new file mode 100755 index 00000000..fa2ff453 --- /dev/null +++ b/demucs/evaluate.py @@ -0,0 +1,174 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +"""Test time evaluation, either using the original SDR from [Vincent et al. 2006] +or the newest SDR definition from the MDX 2021 competition (this one will +be reported as `nsdr` for `new sdr`). +""" + +from concurrent import futures +import logging + +from dora.log import LogProgress +import numpy as np +import musdb +import museval +import torch as th + +from .apply import apply_model +from .audio import convert_audio, save_audio +from . import distrib +from .utils import DummyPoolExecutor + + +logger = logging.getLogger(__name__) + + +def new_sdr(references, estimates): + """ + Compute the SDR according to the MDX challenge definition. + Adapted from AIcrowd/music-demixing-challenge-starter-kit (MIT license) + """ + assert references.dim() == 4 + assert estimates.dim() == 4 + delta = 1e-7 # avoid numerical errors + num = th.sum(th.square(references), dim=(2, 3)) + den = th.sum(th.square(references - estimates), dim=(2, 3)) + num += delta + den += delta + scores = 10 * th.log10(num / den) + return scores + + +def eval_track(references, estimates, win, hop, compute_sdr=True): + references = references.transpose(1, 2).double() + estimates = estimates.transpose(1, 2).double() + + new_scores = new_sdr(references.cpu()[None], estimates.cpu()[None])[0] + + if not compute_sdr: + return None, new_scores + else: + references = references.numpy() + estimates = estimates.numpy() + scores = museval.metrics.bss_eval( + references, estimates, + compute_permutation=False, + window=win, + hop=hop, + framewise_filters=False, + bsseval_sources_version=False)[:-1] + return scores, new_scores + + +def evaluate(solver, compute_sdr=False): + """ + Evaluate model using museval. + compute_sdr=False means using only the MDX definition of the SDR, which + is much faster to evaluate. + """ + + args = solver.args + + output_dir = solver.folder / "results" + output_dir.mkdir(exist_ok=True, parents=True) + json_folder = solver.folder / "results/test" + json_folder.mkdir(exist_ok=True, parents=True) + + # we load tracks from the original musdb set + if args.test.nonhq is None: + test_set = musdb.DB(args.dset.musdb, subsets=["test"], is_wav=True) + else: + test_set = musdb.DB(args.test.nonhq, subsets=["test"], is_wav=False) + src_rate = args.dset.musdb_samplerate + + eval_device = 'cpu' + + model = solver.model + win = int(1. * model.samplerate) + hop = int(1. * model.samplerate) + + indexes = range(distrib.rank, len(test_set), distrib.world_size) + indexes = LogProgress(logger, indexes, updates=args.misc.num_prints, + name='Eval') + pendings = [] + + pool = futures.ProcessPoolExecutor if args.test.workers else DummyPoolExecutor + with pool(args.test.workers) as pool: + for index in indexes: + track = test_set.tracks[index] + + mix = th.from_numpy(track.audio).t().float() + if mix.dim() == 1: + mix = mix[None] + mix = mix.to(solver.device) + ref = mix.mean(dim=0) # mono mixture + mix = (mix - ref.mean()) / ref.std() + mix = convert_audio(mix, src_rate, model.samplerate, model.audio_channels) + estimates = apply_model(model, mix[None], + shifts=args.test.shifts, split=args.test.split, + overlap=args.test.overlap)[0] + estimates = estimates * ref.std() + ref.mean() + estimates = estimates.to(eval_device) + + references = th.stack( + [th.from_numpy(track.targets[name].audio).t() for name in model.sources]) + if references.dim() == 2: + references = references[:, None] + references = references.to(eval_device) + references = convert_audio(references, src_rate, + model.samplerate, model.audio_channels) + if args.test.save: + folder = solver.folder / "wav" / track.name + folder.mkdir(exist_ok=True, parents=True) + for name, estimate in zip(model.sources, estimates): + save_audio(estimate.cpu(), folder / (name + ".mp3"), model.samplerate) + + pendings.append((track.name, pool.submit( + eval_track, references, estimates, win=win, hop=hop, compute_sdr=compute_sdr))) + + pendings = LogProgress(logger, pendings, updates=args.misc.num_prints, + name='Eval (BSS)') + tracks = {} + for track_name, pending in pendings: + pending = pending.result() + scores, nsdrs = pending + tracks[track_name] = {} + for idx, target in enumerate(model.sources): + tracks[track_name][target] = {'nsdr': [float(nsdrs[idx])]} + if scores is not None: + (sdr, isr, sir, sar) = scores + for idx, target in enumerate(model.sources): + values = { + "SDR": sdr[idx].tolist(), + "SIR": sir[idx].tolist(), + "ISR": isr[idx].tolist(), + "SAR": sar[idx].tolist() + } + tracks[track_name][target].update(values) + + all_tracks = {} + for src in range(distrib.world_size): + all_tracks.update(distrib.share(tracks, src)) + + result = {} + metric_names = next(iter(all_tracks.values()))[model.sources[0]] + for metric_name in metric_names: + avg = 0 + avg_of_medians = 0 + for source in model.sources: + medians = [ + np.nanmedian(all_tracks[track][source][metric_name]) + for track in all_tracks.keys()] + mean = np.mean(medians) + median = np.median(medians) + result[metric_name.lower() + "_" + source] = mean + result[metric_name.lower() + "_med" + "_" + source] = median + avg += mean / len(model.sources) + avg_of_medians += median / len(model.sources) + result[metric_name.lower()] = avg + result[metric_name.lower() + "_med"] = avg_of_medians + return result diff --git a/demucs/grids/__init__.py b/demucs/grids/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/demucs/grids/_explorers.py b/demucs/grids/_explorers.py new file mode 100644 index 00000000..ec3a858d --- /dev/null +++ b/demucs/grids/_explorers.py @@ -0,0 +1,64 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +from dora import Explorer +import treetable as tt + + +class MyExplorer(Explorer): + test_metrics = ['nsdr', 'sdr_med'] + + def get_grid_metrics(self): + """Return the metrics that should be displayed in the tracking table. + """ + return [ + tt.group("train", [ + tt.leaf("epoch"), + tt.leaf("reco", ".3f"), + ], align=">"), + tt.group("valid", [ + tt.leaf("penalty", ".1f"), + tt.leaf("ms", ".1f"), + tt.leaf("reco", ".2%"), + tt.leaf("breco", ".2%"), + tt.leaf("b_nsdr", ".2f"), + # tt.leaf("b_nsdr_drums", ".2f"), + # tt.leaf("b_nsdr_bass", ".2f"), + # tt.leaf("b_nsdr_other", ".2f"), + # tt.leaf("b_nsdr_vocals", ".2f"), + ], align=">"), + tt.group("test", [ + tt.leaf(name, ".2f") + for name in self.test_metrics + ], align=">") + ] + + def process_history(self, history): + train = { + 'epoch': len(history), + } + valid = {} + test = {} + best_v_main = float('inf') + breco = float('inf') + for metrics in history: + train.update(metrics['train']) + valid.update(metrics['valid']) + if 'main' in metrics['valid']: + best_v_main = min(best_v_main, metrics['valid']['main']['loss']) + valid['bmain'] = best_v_main + valid['breco'] = min(breco, metrics['valid']['reco']) + breco = valid['breco'] + if (metrics['valid']['loss'] == metrics['valid']['best'] or + metrics['valid'].get('nsdr') == metrics['valid']['best']): + for k, v in metrics['valid'].items(): + if k.startswith('reco_'): + valid['b_' + k[len('reco_'):]] = v + if k.startswith('nsdr'): + valid[f'b_{k}'] = v + if 'test' in metrics: + test.update(metrics['test']) + metrics = history[-1] + return {"train": train, "valid": valid, "test": test} diff --git a/demucs/grids/mdx.py b/demucs/grids/mdx.py new file mode 100644 index 00000000..62d447f1 --- /dev/null +++ b/demucs/grids/mdx.py @@ -0,0 +1,33 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +Main training for the Track A MDX models. +""" + +from ._explorers import MyExplorer +from ..train import main + + +TRACK_A = ['0d19c1c6', '7ecf8ec1', 'c511e2ab', '7d865c68'] + + +@MyExplorer +def explorer(launcher): + launcher.slurm_( + gpus=8, + time=3 * 24 * 60, + partition='learnlab') + + # Reproduce results from MDX competition Track A + # This trains the first round of models. Once this is trained, + # you will need to schedule `mdx_refine`. + for sig in TRACK_A: + xp = main.get_xp_from_sig(sig) + parent = xp.cfg.continue_from + xp = main.get_xp_from_sig(parent) + launcher(xp.argv) + launcher(xp.argv, {'quant.diffq': 1e-4}) + launcher(xp.argv, {'quant.diffq': 3e-4}) diff --git a/demucs/grids/mdx_extra.py b/demucs/grids/mdx_extra.py new file mode 100644 index 00000000..b99a37b0 --- /dev/null +++ b/demucs/grids/mdx_extra.py @@ -0,0 +1,36 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +Main training for the Track A MDX models. +""" + +from ._explorers import MyExplorer +from ..train import main + +TRACK_B = ['e51eebcc', 'a1d90b5c', '5d2d6c55', 'cfa93e08'] + + +@MyExplorer +def explorer(launcher): + launcher.slurm_( + gpus=8, + time=3 * 24 * 60, + partition='learnlab') + + # Reproduce results from MDX competition Track A + # This trains the first round of models. Once this is trained, + # you will need to schedule `mdx_refine`. + for sig in TRACK_B: + while sig is not None: + xp = main.get_xp_from_sig(sig) + sig = xp.cfg.continue_from + + for dset in ['extra44', 'extra_test']: + sub = launcher.bind(xp.argv, dset=dset) + sub() + if dset == 'extra_test': + sub({'quant.diffq': 1e-4}) + sub({'quant.diffq': 3e-4}) diff --git a/demucs/grids/mdx_refine.py b/demucs/grids/mdx_refine.py new file mode 100644 index 00000000..f62da1de --- /dev/null +++ b/demucs/grids/mdx_refine.py @@ -0,0 +1,34 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +Main training for the Track A MDX models. +""" + +from ._explorers import MyExplorer +from .mdx import TRACK_A +from ..train import main + + +@MyExplorer +def explorer(launcher): + launcher.slurm_( + gpus=8, + time=3 * 24 * 60, + partition='learnlab') + + # Reproduce results from MDX competition Track A + # WARNING: all the experiments in the `mdx` grid must have completed. + for sig in TRACK_A: + xp = main.get_xp_from_sig(sig) + launcher(xp.argv) + for diffq in [1e-4, 3e-4]: + xp_src = main.get_xp_from_sig(xp.cfg.continue_from) + q_argv = [f'quant.diffq={diffq}'] + actual_src = main.get_xp(xp_src.argv + q_argv) + actual_src.link.load() + assert len(actual_src.link.history) == actual_src.cfg.epochs + argv = xp.argv + q_argv + [f'continue_from="{actual_src.sig}"'] + launcher(argv) diff --git a/demucs/grids/mmi.py b/demucs/grids/mmi.py new file mode 100644 index 00000000..d75aa2b6 --- /dev/null +++ b/demucs/grids/mmi.py @@ -0,0 +1,69 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from ._explorers import MyExplorer +from dora import Launcher + + +@MyExplorer +def explorer(launcher: Launcher): + launcher.slurm_(gpus=8, time=3 * 24 * 60, partition="devlab,learnlab,learnfair") # 3 days + + sub = launcher.bind_( + { + "dset": "extra_mmi_goodclean", + "test.shifts": 0, + "model": "htdemucs", + "htdemucs.dconv_mode": 3, + "htdemucs.depth": 4, + "htdemucs.t_dropout": 0.02, + "htdemucs.t_layers": 5, + "max_batches": 800, + "ema.epoch": [0.9, 0.95], + "ema.batch": [0.9995, 0.9999], + "dset.segment": 10, + "batch_size": 32, + } + ) + sub({"model": "hdemucs"}) + sub({"model": "hdemucs", "dset": "extra44"}) + sub({"model": "hdemucs", "dset": "musdb44"}) + + sparse = { + 'batch_size': 3 * 8, + 'augment.remix.group_size': 3, + 'htdemucs.t_auto_sparsity': True, + 'htdemucs.t_sparse_self_attn': True, + 'htdemucs.t_sparse_cross_attn': True, + 'htdemucs.t_sparsity': 0.9, + "htdemucs.t_layers": 7 + } + + with launcher.job_array(): + for transf_layers in [5, 7]: + for bottom_channels in [0, 512]: + sub = launcher.bind({ + "htdemucs.t_layers": transf_layers, + "htdemucs.bottom_channels": bottom_channels, + }) + if bottom_channels == 0 and transf_layers == 5: + sub({"augment.remix.proba": 0.0}) + sub({ + "augment.repitch.proba": 0.0, + # when doing repitching, we trim the outut to align on the + # highest change of BPM. When removing repitching, + # we simulate it here to ensure the training context is the same. + # Another second is lost for all experiments due to the random + # shift augmentation. + "dset.segment": 10 * 0.88}) + elif bottom_channels == 512 and transf_layers == 5: + sub(dset="musdb44") + sub(dset="extra44") + # Sparse kernel XP, currently not released as kernels are still experimental. + sub(sparse, {'dset.segment': 15, "htdemucs.t_layers": 7}) + + for duration in [5, 10, 15]: + sub({"dset.segment": duration}) diff --git a/demucs/grids/mmi_ft.py b/demucs/grids/mmi_ft.py new file mode 100644 index 00000000..73e488b5 --- /dev/null +++ b/demucs/grids/mmi_ft.py @@ -0,0 +1,55 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from ._explorers import MyExplorer +from dora import Launcher +from demucs import train + + +def get_sub(launcher, sig): + xp = train.main.get_xp_from_sig(sig) + sub = launcher.bind(xp.argv) + sub() + sub.bind_({ + 'continue_from': sig, + 'continue_best': True}) + return sub + + +@MyExplorer +def explorer(launcher: Launcher): + launcher.slurm_(gpus=4, time=3 * 24 * 60, partition="devlab,learnlab,learnfair") # 3 days + ft = { + 'optim.lr': 1e-4, + 'augment.remix.proba': 0, + 'augment.scale.proba': 0, + 'augment.shift_same': True, + 'htdemucs.t_weight_decay': 0.05, + 'batch_size': 8, + 'optim.clip_grad': 5, + 'optim.optim': 'adamw', + 'epochs': 50, + 'dset.wav2_valid': True, + 'ema.epoch': [], # let's make valid a bit faster + } + with launcher.job_array(): + for sig in ['2899e11a']: + sub = get_sub(launcher, sig) + sub.bind_(ft) + for segment in [15, 18]: + for source in range(4): + w = [0] * 4 + w[source] = 1 + sub({'weights': w, 'dset.segment': segment}) + + for sig in ['955717e8']: + sub = get_sub(launcher, sig) + sub.bind_(ft) + for segment in [10, 15]: + for source in range(4): + w = [0] * 4 + w[source] = 1 + sub({'weights': w, 'dset.segment': segment}) diff --git a/demucs/grids/repro.py b/demucs/grids/repro.py new file mode 100644 index 00000000..21d33fce --- /dev/null +++ b/demucs/grids/repro.py @@ -0,0 +1,50 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +Easier training for reproducibility +""" + +from ._explorers import MyExplorer + + +@MyExplorer +def explorer(launcher): + launcher.slurm_( + gpus=8, + time=3 * 24 * 60, + partition='devlab,learnlab') + + launcher.bind_({'ema.epoch': [0.9, 0.95]}) + launcher.bind_({'ema.batch': [0.9995, 0.9999]}) + launcher.bind_({'epochs': 600}) + + base = {'model': 'demucs', 'demucs.dconv_mode': 0, 'demucs.gelu': False, + 'demucs.lstm_layers': 2} + newt = {'model': 'demucs', 'demucs.normalize': True} + hdem = {'model': 'hdemucs'} + svd = {'svd.penalty': 1e-5, 'svd': 'base2'} + + with launcher.job_array(): + for model in [base, newt, hdem]: + sub = launcher.bind(model) + if model is base: + # Training the v2 Demucs on MusDB HQ + sub(epochs=360) + continue + + # those two will be used in the repro_mdx_a bag of models. + sub(svd) + sub(svd, seed=43) + if model == newt: + # Ablation study + sub() + abl = sub.bind(svd) + abl({'ema.epoch': [], 'ema.batch': []}) + abl({'demucs.dconv_lstm': 10}) + abl({'demucs.dconv_attn': 10}) + abl({'demucs.dconv_attn': 10, 'demucs.dconv_lstm': 10, 'demucs.lstm_layers': 2}) + abl({'demucs.dconv_mode': 0}) + abl({'demucs.gelu': False}) diff --git a/demucs/grids/repro_ft.py b/demucs/grids/repro_ft.py new file mode 100644 index 00000000..7bb4ee89 --- /dev/null +++ b/demucs/grids/repro_ft.py @@ -0,0 +1,46 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +Fine tuning experiments +""" + +from ._explorers import MyExplorer +from ..train import main + + +@MyExplorer +def explorer(launcher): + launcher.slurm_( + gpus=8, + time=300, + partition='devlab,learnlab') + + # Mus + launcher.slurm_(constraint='volta32gb') + + grid = "repro" + folder = main.dora.dir / "grids" / grid + + for sig in folder.iterdir(): + if not sig.is_symlink(): + continue + xp = main.get_xp_from_sig(sig) + xp.link.load() + if len(xp.link.history) != xp.cfg.epochs: + continue + sub = launcher.bind(xp.argv, [f'continue_from="{xp.sig}"']) + sub.bind_({'ema.epoch': [0.9, 0.95], 'ema.batch': [0.9995, 0.9999]}) + sub.bind_({'test.every': 1, 'test.sdr': True, 'epochs': 4}) + sub.bind_({'dset.segment': 28, 'dset.shift': 2}) + sub.bind_({'batch_size': 32}) + auto = {'dset': 'auto_mus'} + auto.update({'augment.remix.proba': 0, 'augment.scale.proba': 0, + 'augment.shift_same': True}) + sub.bind_(auto) + sub.bind_({'batch_size': 16}) + sub.bind_({'optim.lr': 1e-4}) + sub.bind_({'model_segment': 44}) + sub() diff --git a/demucs/grids/sdx23.py b/demucs/grids/sdx23.py new file mode 100644 index 00000000..3bdb4191 --- /dev/null +++ b/demucs/grids/sdx23.py @@ -0,0 +1,19 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from ._explorers import MyExplorer +from dora import Launcher + + +@MyExplorer +def explorer(launcher: Launcher): + launcher.slurm_(gpus=8, time=3 * 24 * 60, partition="speechgpt,learnfair", + mem_per_gpu=None, constraint='') + launcher.bind_({"dset.use_musdb": False}) + + with launcher.job_array(): + launcher(dset='sdx23_bleeding') + launcher(dset='sdx23_labelnoise') diff --git a/demucs/hdemucs.py b/demucs/hdemucs.py new file mode 100644 index 00000000..9992b60a --- /dev/null +++ b/demucs/hdemucs.py @@ -0,0 +1,796 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +This code contains the spectrogram and Hybrid version of Demucs. +""" +from copy import deepcopy +import math +import typing as tp + +from openunmix.filtering import wiener +import torch +from torch import nn +from torch.nn import functional as F + +from .demucs import DConv, rescale_module +from .states import capture_init +from .spec import spectro, ispectro + + +def pad1d(x: torch.Tensor, paddings: tp.Tuple[int, int], mode: str = 'constant', value: float = 0.): + """Tiny wrapper around F.pad, just to allow for reflect padding on small input. + If this is the case, we insert extra 0 padding to the right before the reflection happen.""" + x0 = x + length = x.shape[-1] + padding_left, padding_right = paddings + if mode == 'reflect': + max_pad = max(padding_left, padding_right) + if length <= max_pad: + extra_pad = max_pad - length + 1 + extra_pad_right = min(padding_right, extra_pad) + extra_pad_left = extra_pad - extra_pad_right + paddings = (padding_left - extra_pad_left, padding_right - extra_pad_right) + x = F.pad(x, (extra_pad_left, extra_pad_right)) + out = F.pad(x, paddings, mode, value) + assert out.shape[-1] == length + padding_left + padding_right + assert (out[..., padding_left: padding_left + length] == x0).all() + return out + + +class ScaledEmbedding(nn.Module): + """ + Boost learning rate for embeddings (with `scale`). + Also, can make embeddings continuous with `smooth`. + """ + def __init__(self, num_embeddings: int, embedding_dim: int, + scale: float = 10., smooth=False): + super().__init__() + self.embedding = nn.Embedding(num_embeddings, embedding_dim) + if smooth: + weight = torch.cumsum(self.embedding.weight.data, dim=0) + # when summing gaussian, overscale raises as sqrt(n), so we nornalize by that. + weight = weight / torch.arange(1, num_embeddings + 1).to(weight).sqrt()[:, None] + self.embedding.weight.data[:] = weight + self.embedding.weight.data /= scale + self.scale = scale + + @property + def weight(self): + return self.embedding.weight * self.scale + + def forward(self, x): + out = self.embedding(x) * self.scale + return out + + +class HEncLayer(nn.Module): + def __init__(self, chin, chout, kernel_size=8, stride=4, norm_groups=1, empty=False, + freq=True, dconv=True, norm=True, context=0, dconv_kw={}, pad=True, + rewrite=True): + """Encoder layer. This used both by the time and the frequency branch. + + Args: + chin: number of input channels. + chout: number of output channels. + norm_groups: number of groups for group norm. + empty: used to make a layer with just the first conv. this is used + before merging the time and freq. branches. + freq: this is acting on frequencies. + dconv: insert DConv residual branches. + norm: use GroupNorm. + context: context size for the 1x1 conv. + dconv_kw: list of kwargs for the DConv class. + pad: pad the input. Padding is done so that the output size is + always the input size / stride. + rewrite: add 1x1 conv at the end of the layer. + """ + super().__init__() + norm_fn = lambda d: nn.Identity() # noqa + if norm: + norm_fn = lambda d: nn.GroupNorm(norm_groups, d) # noqa + if pad: + pad = kernel_size // 4 + else: + pad = 0 + klass = nn.Conv1d + self.freq = freq + self.kernel_size = kernel_size + self.stride = stride + self.empty = empty + self.norm = norm + self.pad = pad + if freq: + kernel_size = [kernel_size, 1] + stride = [stride, 1] + pad = [pad, 0] + klass = nn.Conv2d + self.conv = klass(chin, chout, kernel_size, stride, pad) + if self.empty: + return + self.norm1 = norm_fn(chout) + self.rewrite = None + if rewrite: + self.rewrite = klass(chout, 2 * chout, 1 + 2 * context, 1, context) + self.norm2 = norm_fn(2 * chout) + + self.dconv = None + if dconv: + self.dconv = DConv(chout, **dconv_kw) + + def forward(self, x, inject=None): + """ + `inject` is used to inject the result from the time branch into the frequency branch, + when both have the same stride. + """ + if not self.freq and x.dim() == 4: + B, C, Fr, T = x.shape + x = x.view(B, -1, T) + + if not self.freq: + le = x.shape[-1] + if not le % self.stride == 0: + x = F.pad(x, (0, self.stride - (le % self.stride))) + y = self.conv(x) + if self.empty: + return y + if inject is not None: + assert inject.shape[-1] == y.shape[-1], (inject.shape, y.shape) + if inject.dim() == 3 and y.dim() == 4: + inject = inject[:, :, None] + y = y + inject + y = F.gelu(self.norm1(y)) + if self.dconv: + if self.freq: + B, C, Fr, T = y.shape + y = y.permute(0, 2, 1, 3).reshape(-1, C, T) + y = self.dconv(y) + if self.freq: + y = y.view(B, Fr, C, T).permute(0, 2, 1, 3) + if self.rewrite: + z = self.norm2(self.rewrite(y)) + z = F.glu(z, dim=1) + else: + z = y + return z + + +class MultiWrap(nn.Module): + """ + Takes one layer and replicate it N times. each replica will act + on a frequency band. All is done so that if the N replica have the same weights, + then this is exactly equivalent to applying the original module on all frequencies. + + This is a bit over-engineered to avoid edge artifacts when splitting + the frequency bands, but it is possible the naive implementation would work as well... + """ + def __init__(self, layer, split_ratios): + """ + Args: + layer: module to clone, must be either HEncLayer or HDecLayer. + split_ratios: list of float indicating which ratio to keep for each band. + """ + super().__init__() + self.split_ratios = split_ratios + self.layers = nn.ModuleList() + self.conv = isinstance(layer, HEncLayer) + assert not layer.norm + assert layer.freq + assert layer.pad + if not self.conv: + assert not layer.context_freq + for k in range(len(split_ratios) + 1): + lay = deepcopy(layer) + if self.conv: + lay.conv.padding = (0, 0) + else: + lay.pad = False + for m in lay.modules(): + if hasattr(m, 'reset_parameters'): + m.reset_parameters() + self.layers.append(lay) + + def forward(self, x, skip=None, length=None): + B, C, Fr, T = x.shape + + ratios = list(self.split_ratios) + [1] + start = 0 + outs = [] + for ratio, layer in zip(ratios, self.layers): + if self.conv: + pad = layer.kernel_size // 4 + if ratio == 1: + limit = Fr + frames = -1 + else: + limit = int(round(Fr * ratio)) + le = limit - start + if start == 0: + le += pad + frames = round((le - layer.kernel_size) / layer.stride + 1) + limit = start + (frames - 1) * layer.stride + layer.kernel_size + if start == 0: + limit -= pad + assert limit - start > 0, (limit, start) + assert limit <= Fr, (limit, Fr) + y = x[:, :, start:limit, :] + if start == 0: + y = F.pad(y, (0, 0, pad, 0)) + if ratio == 1: + y = F.pad(y, (0, 0, 0, pad)) + outs.append(layer(y)) + start = limit - layer.kernel_size + layer.stride + else: + if ratio == 1: + limit = Fr + else: + limit = int(round(Fr * ratio)) + last = layer.last + layer.last = True + + y = x[:, :, start:limit] + s = skip[:, :, start:limit] + out, _ = layer(y, s, None) + if outs: + outs[-1][:, :, -layer.stride:] += ( + out[:, :, :layer.stride] - layer.conv_tr.bias.view(1, -1, 1, 1)) + out = out[:, :, layer.stride:] + if ratio == 1: + out = out[:, :, :-layer.stride // 2, :] + if start == 0: + out = out[:, :, layer.stride // 2:, :] + outs.append(out) + layer.last = last + start = limit + out = torch.cat(outs, dim=2) + if not self.conv and not last: + out = F.gelu(out) + if self.conv: + return out + else: + return out, None + + +class HDecLayer(nn.Module): + def __init__(self, chin, chout, last=False, kernel_size=8, stride=4, norm_groups=1, empty=False, + freq=True, dconv=True, norm=True, context=1, dconv_kw={}, pad=True, + context_freq=True, rewrite=True): + """ + Same as HEncLayer but for decoder. See `HEncLayer` for documentation. + """ + super().__init__() + norm_fn = lambda d: nn.Identity() # noqa + if norm: + norm_fn = lambda d: nn.GroupNorm(norm_groups, d) # noqa + if pad: + pad = kernel_size // 4 + else: + pad = 0 + self.pad = pad + self.last = last + self.freq = freq + self.chin = chin + self.empty = empty + self.stride = stride + self.kernel_size = kernel_size + self.norm = norm + self.context_freq = context_freq + klass = nn.Conv1d + klass_tr = nn.ConvTranspose1d + if freq: + kernel_size = [kernel_size, 1] + stride = [stride, 1] + klass = nn.Conv2d + klass_tr = nn.ConvTranspose2d + self.conv_tr = klass_tr(chin, chout, kernel_size, stride) + self.norm2 = norm_fn(chout) + if self.empty: + return + self.rewrite = None + if rewrite: + if context_freq: + self.rewrite = klass(chin, 2 * chin, 1 + 2 * context, 1, context) + else: + self.rewrite = klass(chin, 2 * chin, [1, 1 + 2 * context], 1, + [0, context]) + self.norm1 = norm_fn(2 * chin) + + self.dconv = None + if dconv: + self.dconv = DConv(chin, **dconv_kw) + + def forward(self, x, skip, length): + if self.freq and x.dim() == 3: + B, C, T = x.shape + x = x.view(B, self.chin, -1, T) + + if not self.empty: + x = x + skip + + if self.rewrite: + y = F.glu(self.norm1(self.rewrite(x)), dim=1) + else: + y = x + if self.dconv: + if self.freq: + B, C, Fr, T = y.shape + y = y.permute(0, 2, 1, 3).reshape(-1, C, T) + y = self.dconv(y) + if self.freq: + y = y.view(B, Fr, C, T).permute(0, 2, 1, 3) + else: + y = x + assert skip is None + z = self.norm2(self.conv_tr(y)) + if self.freq: + if self.pad: + z = z[..., self.pad:-self.pad, :] + else: + z = z[..., self.pad:self.pad + length] + assert z.shape[-1] == length, (z.shape[-1], length) + if not self.last: + z = F.gelu(z) + return z, y + + +class HDemucs(nn.Module): + """ + Spectrogram and hybrid Demucs model. + The spectrogram model has the same structure as Demucs, except the first few layers are over the + frequency axis, until there is only 1 frequency, and then it moves to time convolutions. + Frequency layers can still access information across time steps thanks to the DConv residual. + + Hybrid model have a parallel time branch. At some layer, the time branch has the same stride + as the frequency branch and then the two are combined. The opposite happens in the decoder. + + Models can either use naive iSTFT from masking, Wiener filtering ([Ulhih et al. 2017]), + or complex as channels (CaC) [Choi et al. 2020]. Wiener filtering is based on + Open Unmix implementation [Stoter et al. 2019]. + + The loss is always on the temporal domain, by backpropagating through the above + output methods and iSTFT. This allows to define hybrid models nicely. However, this breaks + a bit Wiener filtering, as doing more iteration at test time will change the spectrogram + contribution, without changing the one from the waveform, which will lead to worse performance. + I tried using the residual option in OpenUnmix Wiener implementation, but it didn't improve. + CaC on the other hand provides similar performance for hybrid, and works naturally with + hybrid models. + + This model also uses frequency embeddings are used to improve efficiency on convolutions + over the freq. axis, following [Isik et al. 2020] (https://arxiv.org/pdf/2008.04470.pdf). + + Unlike classic Demucs, there is no resampling here, and normalization is always applied. + """ + @capture_init + def __init__(self, + sources, + # Channels + audio_channels=2, + channels=48, + channels_time=None, + growth=2, + # STFT + nfft=4096, + wiener_iters=0, + end_iters=0, + wiener_residual=False, + cac=True, + # Main structure + depth=6, + rewrite=True, + hybrid=True, + hybrid_old=False, + # Frequency branch + multi_freqs=None, + multi_freqs_depth=2, + freq_emb=0.2, + emb_scale=10, + emb_smooth=True, + # Convolutions + kernel_size=8, + time_stride=2, + stride=4, + context=1, + context_enc=0, + # Normalization + norm_starts=4, + norm_groups=4, + # DConv residual branch + dconv_mode=1, + dconv_depth=2, + dconv_comp=4, + dconv_attn=4, + dconv_lstm=4, + dconv_init=1e-4, + # Weight init + rescale=0.1, + # Metadata + samplerate=44100, + segment=4 * 10): + """ + Args: + sources (list[str]): list of source names. + audio_channels (int): input/output audio channels. + channels (int): initial number of hidden channels. + channels_time: if not None, use a different `channels` value for the time branch. + growth: increase the number of hidden channels by this factor at each layer. + nfft: number of fft bins. Note that changing this require careful computation of + various shape parameters and will not work out of the box for hybrid models. + wiener_iters: when using Wiener filtering, number of iterations at test time. + end_iters: same but at train time. For a hybrid model, must be equal to `wiener_iters`. + wiener_residual: add residual source before wiener filtering. + cac: uses complex as channels, i.e. complex numbers are 2 channels each + in input and output. no further processing is done before ISTFT. + depth (int): number of layers in the encoder and in the decoder. + rewrite (bool): add 1x1 convolution to each layer. + hybrid (bool): make a hybrid time/frequency domain, otherwise frequency only. + hybrid_old: some models trained for MDX had a padding bug. This replicates + this bug to avoid retraining them. + multi_freqs: list of frequency ratios for splitting frequency bands with `MultiWrap`. + multi_freqs_depth: how many layers to wrap with `MultiWrap`. Only the outermost + layers will be wrapped. + freq_emb: add frequency embedding after the first frequency layer if > 0, + the actual value controls the weight of the embedding. + emb_scale: equivalent to scaling the embedding learning rate + emb_smooth: initialize the embedding with a smooth one (with respect to frequencies). + kernel_size: kernel_size for encoder and decoder layers. + stride: stride for encoder and decoder layers. + time_stride: stride for the final time layer, after the merge. + context: context for 1x1 conv in the decoder. + context_enc: context for 1x1 conv in the encoder. + norm_starts: layer at which group norm starts being used. + decoder layers are numbered in reverse order. + norm_groups: number of groups for group norm. + dconv_mode: if 1: dconv in encoder only, 2: decoder only, 3: both. + dconv_depth: depth of residual DConv branch. + dconv_comp: compression of DConv branch. + dconv_attn: adds attention layers in DConv branch starting at this layer. + dconv_lstm: adds a LSTM layer in DConv branch starting at this layer. + dconv_init: initial scale for the DConv branch LayerScale. + rescale: weight recaling trick + + """ + super().__init__() + self.cac = cac + self.wiener_residual = wiener_residual + self.audio_channels = audio_channels + self.sources = sources + self.kernel_size = kernel_size + self.context = context + self.stride = stride + self.depth = depth + self.channels = channels + self.samplerate = samplerate + self.segment = segment + + self.nfft = nfft + self.hop_length = nfft // 4 + self.wiener_iters = wiener_iters + self.end_iters = end_iters + self.freq_emb = None + self.hybrid = hybrid + self.hybrid_old = hybrid_old + if hybrid_old: + assert hybrid, "hybrid_old must come with hybrid=True" + if hybrid: + assert wiener_iters == end_iters + + self.encoder = nn.ModuleList() + self.decoder = nn.ModuleList() + + if hybrid: + self.tencoder = nn.ModuleList() + self.tdecoder = nn.ModuleList() + + chin = audio_channels + chin_z = chin # number of channels for the freq branch + if self.cac: + chin_z *= 2 + chout = channels_time or channels + chout_z = channels + freqs = nfft // 2 + + for index in range(depth): + lstm = index >= dconv_lstm + attn = index >= dconv_attn + norm = index >= norm_starts + freq = freqs > 1 + stri = stride + ker = kernel_size + if not freq: + assert freqs == 1 + ker = time_stride * 2 + stri = time_stride + + pad = True + last_freq = False + if freq and freqs <= kernel_size: + ker = freqs + pad = False + last_freq = True + + kw = { + 'kernel_size': ker, + 'stride': stri, + 'freq': freq, + 'pad': pad, + 'norm': norm, + 'rewrite': rewrite, + 'norm_groups': norm_groups, + 'dconv_kw': { + 'lstm': lstm, + 'attn': attn, + 'depth': dconv_depth, + 'compress': dconv_comp, + 'init': dconv_init, + 'gelu': True, + } + } + kwt = dict(kw) + kwt['freq'] = 0 + kwt['kernel_size'] = kernel_size + kwt['stride'] = stride + kwt['pad'] = True + kw_dec = dict(kw) + multi = False + if multi_freqs and index < multi_freqs_depth: + multi = True + kw_dec['context_freq'] = False + + if last_freq: + chout_z = max(chout, chout_z) + chout = chout_z + + enc = HEncLayer(chin_z, chout_z, + dconv=dconv_mode & 1, context=context_enc, **kw) + if hybrid and freq: + tenc = HEncLayer(chin, chout, dconv=dconv_mode & 1, context=context_enc, + empty=last_freq, **kwt) + self.tencoder.append(tenc) + + if multi: + enc = MultiWrap(enc, multi_freqs) + self.encoder.append(enc) + if index == 0: + chin = self.audio_channels * len(self.sources) + chin_z = chin + if self.cac: + chin_z *= 2 + dec = HDecLayer(chout_z, chin_z, dconv=dconv_mode & 2, + last=index == 0, context=context, **kw_dec) + if multi: + dec = MultiWrap(dec, multi_freqs) + if hybrid and freq: + tdec = HDecLayer(chout, chin, dconv=dconv_mode & 2, empty=last_freq, + last=index == 0, context=context, **kwt) + self.tdecoder.insert(0, tdec) + self.decoder.insert(0, dec) + + chin = chout + chin_z = chout_z + chout = int(growth * chout) + chout_z = int(growth * chout_z) + if freq: + if freqs <= kernel_size: + freqs = 1 + else: + freqs //= stride + if index == 0 and freq_emb: + self.freq_emb = ScaledEmbedding( + freqs, chin_z, smooth=emb_smooth, scale=emb_scale) + self.freq_emb_scale = freq_emb + + if rescale: + rescale_module(self, reference=rescale) + + def _spec(self, x): + hl = self.hop_length + nfft = self.nfft + x0 = x # noqa + + if self.hybrid: + # We re-pad the signal in order to keep the property + # that the size of the output is exactly the size of the input + # divided by the stride (here hop_length), when divisible. + # This is achieved by padding by 1/4th of the kernel size (here nfft). + # which is not supported by torch.stft. + # Having all convolution operations follow this convention allow to easily + # align the time and frequency branches later on. + assert hl == nfft // 4 + le = int(math.ceil(x.shape[-1] / hl)) + pad = hl // 2 * 3 + if not self.hybrid_old: + x = pad1d(x, (pad, pad + le * hl - x.shape[-1]), mode='reflect') + else: + x = pad1d(x, (pad, pad + le * hl - x.shape[-1])) + + z = spectro(x, nfft, hl)[..., :-1, :] + if self.hybrid: + assert z.shape[-1] == le + 4, (z.shape, x.shape, le) + z = z[..., 2:2+le] + return z + + def _ispec(self, z, length=None, scale=0): + hl = self.hop_length // (4 ** scale) + z = F.pad(z, (0, 0, 0, 1)) + if self.hybrid: + z = F.pad(z, (2, 2)) + pad = hl // 2 * 3 + if not self.hybrid_old: + le = hl * int(math.ceil(length / hl)) + 2 * pad + else: + le = hl * int(math.ceil(length / hl)) + x = ispectro(z, hl, length=le) + if not self.hybrid_old: + x = x[..., pad:pad + length] + else: + x = x[..., :length] + else: + x = ispectro(z, hl, length) + return x + + def _magnitude(self, z): + # return the magnitude of the spectrogram, except when cac is True, + # in which case we just move the complex dimension to the channel one. + if self.cac: + B, C, Fr, T = z.shape + m = torch.view_as_real(z).permute(0, 1, 4, 2, 3) + m = m.reshape(B, C * 2, Fr, T) + else: + m = z.abs() + return m + + def _mask(self, z, m): + # Apply masking given the mixture spectrogram `z` and the estimated mask `m`. + # If `cac` is True, `m` is actually a full spectrogram and `z` is ignored. + niters = self.wiener_iters + if self.cac: + B, S, C, Fr, T = m.shape + out = m.view(B, S, -1, 2, Fr, T).permute(0, 1, 2, 4, 5, 3) + out = torch.view_as_complex(out.contiguous()) + return out + if self.training: + niters = self.end_iters + if niters < 0: + z = z[:, None] + return z / (1e-8 + z.abs()) * m + else: + return self._wiener(m, z, niters) + + def _wiener(self, mag_out, mix_stft, niters): + # apply wiener filtering from OpenUnmix. + init = mix_stft.dtype + wiener_win_len = 300 + residual = self.wiener_residual + + B, S, C, Fq, T = mag_out.shape + mag_out = mag_out.permute(0, 4, 3, 2, 1) + mix_stft = torch.view_as_real(mix_stft.permute(0, 3, 2, 1)) + + outs = [] + for sample in range(B): + pos = 0 + out = [] + for pos in range(0, T, wiener_win_len): + frame = slice(pos, pos + wiener_win_len) + z_out = wiener( + mag_out[sample, frame], mix_stft[sample, frame], niters, + residual=residual) + out.append(z_out.transpose(-1, -2)) + outs.append(torch.cat(out, dim=0)) + out = torch.view_as_complex(torch.stack(outs, 0)) + out = out.permute(0, 4, 3, 2, 1).contiguous() + if residual: + out = out[:, :-1] + assert list(out.shape) == [B, S, C, Fq, T] + return out.to(init) + + def forward(self, mix): + x = mix + length = x.shape[-1] + + z = self._spec(mix) + mag = self._magnitude(z).to(mix.device) + x = mag + + B, C, Fq, T = x.shape + + # unlike previous Demucs, we always normalize because it is easier. + mean = x.mean(dim=(1, 2, 3), keepdim=True) + std = x.std(dim=(1, 2, 3), keepdim=True) + x = (x - mean) / (1e-5 + std) + # x will be the freq. branch input. + + if self.hybrid: + # Prepare the time branch input. + xt = mix + meant = xt.mean(dim=(1, 2), keepdim=True) + stdt = xt.std(dim=(1, 2), keepdim=True) + xt = (xt - meant) / (1e-5 + stdt) + + # okay, this is a giant mess I know... + saved = [] # skip connections, freq. + saved_t = [] # skip connections, time. + lengths = [] # saved lengths to properly remove padding, freq branch. + lengths_t = [] # saved lengths for time branch. + for idx, encode in enumerate(self.encoder): + lengths.append(x.shape[-1]) + inject = None + if self.hybrid and idx < len(self.tencoder): + # we have not yet merged branches. + lengths_t.append(xt.shape[-1]) + tenc = self.tencoder[idx] + xt = tenc(xt) + if not tenc.empty: + # save for skip connection + saved_t.append(xt) + else: + # tenc contains just the first conv., so that now time and freq. + # branches have the same shape and can be merged. + inject = xt + x = encode(x, inject) + if idx == 0 and self.freq_emb is not None: + # add frequency embedding to allow for non equivariant convolutions + # over the frequency axis. + frs = torch.arange(x.shape[-2], device=x.device) + emb = self.freq_emb(frs).t()[None, :, :, None].expand_as(x) + x = x + self.freq_emb_scale * emb + + saved.append(x) + + x = torch.zeros_like(x) + if self.hybrid: + xt = torch.zeros_like(x) + # initialize everything to zero (signal will go through u-net skips). + + for idx, decode in enumerate(self.decoder): + skip = saved.pop(-1) + x, pre = decode(x, skip, lengths.pop(-1)) + # `pre` contains the output just before final transposed convolution, + # which is used when the freq. and time branch separate. + + if self.hybrid: + offset = self.depth - len(self.tdecoder) + if self.hybrid and idx >= offset: + tdec = self.tdecoder[idx - offset] + length_t = lengths_t.pop(-1) + if tdec.empty: + assert pre.shape[2] == 1, pre.shape + pre = pre[:, :, 0] + xt, _ = tdec(pre, None, length_t) + else: + skip = saved_t.pop(-1) + xt, _ = tdec(xt, skip, length_t) + + # Let's make sure we used all stored skip connections. + assert len(saved) == 0 + assert len(lengths_t) == 0 + assert len(saved_t) == 0 + + S = len(self.sources) + x = x.view(B, S, -1, Fq, T) + x = x * std[:, None] + mean[:, None] + + # to cpu as mps doesnt support complex numbers + # demucs issue #435 ##432 + # NOTE: in this case z already is on cpu + # TODO: remove this when mps supports complex numbers + x_is_mps_xpu = x.device.type in ["mps", "xpu"] + x_device = x.device + if x_is_mps_xpu: + x = x.cpu() + + zout = self._mask(z, x) + x = self._ispec(zout, length) + + # back to mps device + if x_is_mps_xpu: + x = x.to(x_device) + + + if self.hybrid: + xt = xt.view(B, S, -1, length) + xt = xt * stdt[:, None] + meant[:, None] + x = xt + x + return x diff --git a/demucs/htdemucs.py b/demucs/htdemucs.py new file mode 100644 index 00000000..56568608 --- /dev/null +++ b/demucs/htdemucs.py @@ -0,0 +1,661 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +# First author is Simon Rouard. +""" +This code contains the spectrogram and Hybrid version of Demucs. +""" +import math + +from openunmix.filtering import wiener +import torch +from torch import nn +from torch.nn import functional as F +from fractions import Fraction +from einops import rearrange + +from .transformer import CrossTransformerEncoder + +from .demucs import rescale_module +from .states import capture_init +from .spec import spectro, ispectro +from .hdemucs import pad1d, ScaledEmbedding, HEncLayer, MultiWrap, HDecLayer + + +class HTDemucs(nn.Module): + """ + Spectrogram and hybrid Demucs model. + The spectrogram model has the same structure as Demucs, except the first few layers are over the + frequency axis, until there is only 1 frequency, and then it moves to time convolutions. + Frequency layers can still access information across time steps thanks to the DConv residual. + + Hybrid model have a parallel time branch. At some layer, the time branch has the same stride + as the frequency branch and then the two are combined. The opposite happens in the decoder. + + Models can either use naive iSTFT from masking, Wiener filtering ([Ulhih et al. 2017]), + or complex as channels (CaC) [Choi et al. 2020]. Wiener filtering is based on + Open Unmix implementation [Stoter et al. 2019]. + + The loss is always on the temporal domain, by backpropagating through the above + output methods and iSTFT. This allows to define hybrid models nicely. However, this breaks + a bit Wiener filtering, as doing more iteration at test time will change the spectrogram + contribution, without changing the one from the waveform, which will lead to worse performance. + I tried using the residual option in OpenUnmix Wiener implementation, but it didn't improve. + CaC on the other hand provides similar performance for hybrid, and works naturally with + hybrid models. + + This model also uses frequency embeddings are used to improve efficiency on convolutions + over the freq. axis, following [Isik et al. 2020] (https://arxiv.org/pdf/2008.04470.pdf). + + Unlike classic Demucs, there is no resampling here, and normalization is always applied. + """ + + @capture_init + def __init__( + self, + sources, + # Channels + audio_channels=2, + channels=48, + channels_time=None, + growth=2, + # STFT + nfft=4096, + wiener_iters=0, + end_iters=0, + wiener_residual=False, + cac=True, + # Main structure + depth=4, + rewrite=True, + # Frequency branch + multi_freqs=None, + multi_freqs_depth=3, + freq_emb=0.2, + emb_scale=10, + emb_smooth=True, + # Convolutions + kernel_size=8, + time_stride=2, + stride=4, + context=1, + context_enc=0, + # Normalization + norm_starts=4, + norm_groups=4, + # DConv residual branch + dconv_mode=1, + dconv_depth=2, + dconv_comp=8, + dconv_init=1e-3, + # Before the Transformer + bottom_channels=0, + # Transformer + t_layers=5, + t_emb="sin", + t_hidden_scale=4.0, + t_heads=8, + t_dropout=0.0, + t_max_positions=10000, + t_norm_in=True, + t_norm_in_group=False, + t_group_norm=False, + t_norm_first=True, + t_norm_out=True, + t_max_period=10000.0, + t_weight_decay=0.0, + t_lr=None, + t_layer_scale=True, + t_gelu=True, + t_weight_pos_embed=1.0, + t_sin_random_shift=0, + t_cape_mean_normalize=True, + t_cape_augment=True, + t_cape_glob_loc_scale=[5000.0, 1.0, 1.4], + t_sparse_self_attn=False, + t_sparse_cross_attn=False, + t_mask_type="diag", + t_mask_random_seed=42, + t_sparse_attn_window=500, + t_global_window=100, + t_sparsity=0.95, + t_auto_sparsity=False, + # ------ Particuliar parameters + t_cross_first=False, + # Weight init + rescale=0.1, + # Metadata + samplerate=44100, + segment=10, + use_train_segment=True, + ): + """ + Args: + sources (list[str]): list of source names. + audio_channels (int): input/output audio channels. + channels (int): initial number of hidden channels. + channels_time: if not None, use a different `channels` value for the time branch. + growth: increase the number of hidden channels by this factor at each layer. + nfft: number of fft bins. Note that changing this require careful computation of + various shape parameters and will not work out of the box for hybrid models. + wiener_iters: when using Wiener filtering, number of iterations at test time. + end_iters: same but at train time. For a hybrid model, must be equal to `wiener_iters`. + wiener_residual: add residual source before wiener filtering. + cac: uses complex as channels, i.e. complex numbers are 2 channels each + in input and output. no further processing is done before ISTFT. + depth (int): number of layers in the encoder and in the decoder. + rewrite (bool): add 1x1 convolution to each layer. + multi_freqs: list of frequency ratios for splitting frequency bands with `MultiWrap`. + multi_freqs_depth: how many layers to wrap with `MultiWrap`. Only the outermost + layers will be wrapped. + freq_emb: add frequency embedding after the first frequency layer if > 0, + the actual value controls the weight of the embedding. + emb_scale: equivalent to scaling the embedding learning rate + emb_smooth: initialize the embedding with a smooth one (with respect to frequencies). + kernel_size: kernel_size for encoder and decoder layers. + stride: stride for encoder and decoder layers. + time_stride: stride for the final time layer, after the merge. + context: context for 1x1 conv in the decoder. + context_enc: context for 1x1 conv in the encoder. + norm_starts: layer at which group norm starts being used. + decoder layers are numbered in reverse order. + norm_groups: number of groups for group norm. + dconv_mode: if 1: dconv in encoder only, 2: decoder only, 3: both. + dconv_depth: depth of residual DConv branch. + dconv_comp: compression of DConv branch. + dconv_attn: adds attention layers in DConv branch starting at this layer. + dconv_lstm: adds a LSTM layer in DConv branch starting at this layer. + dconv_init: initial scale for the DConv branch LayerScale. + bottom_channels: if >0 it adds a linear layer (1x1 Conv) before and after the + transformer in order to change the number of channels + t_layers: number of layers in each branch (waveform and spec) of the transformer + t_emb: "sin", "cape" or "scaled" + t_hidden_scale: the hidden scale of the Feedforward parts of the transformer + for instance if C = 384 (the number of channels in the transformer) and + t_hidden_scale = 4.0 then the intermediate layer of the FFN has dimension + 384 * 4 = 1536 + t_heads: number of heads for the transformer + t_dropout: dropout in the transformer + t_max_positions: max_positions for the "scaled" positional embedding, only + useful if t_emb="scaled" + t_norm_in: (bool) norm before addinf positional embedding and getting into the + transformer layers + t_norm_in_group: (bool) if True while t_norm_in=True, the norm is on all the + timesteps (GroupNorm with group=1) + t_group_norm: (bool) if True, the norms of the Encoder Layers are on all the + timesteps (GroupNorm with group=1) + t_norm_first: (bool) if True the norm is before the attention and before the FFN + t_norm_out: (bool) if True, there is a GroupNorm (group=1) at the end of each layer + t_max_period: (float) denominator in the sinusoidal embedding expression + t_weight_decay: (float) weight decay for the transformer + t_lr: (float) specific learning rate for the transformer + t_layer_scale: (bool) Layer Scale for the transformer + t_gelu: (bool) activations of the transformer are GeLU if True, ReLU else + t_weight_pos_embed: (float) weighting of the positional embedding + t_cape_mean_normalize: (bool) if t_emb="cape", normalisation of positional embeddings + see: https://arxiv.org/abs/2106.03143 + t_cape_augment: (bool) if t_emb="cape", must be True during training and False + during the inference, see: https://arxiv.org/abs/2106.03143 + t_cape_glob_loc_scale: (list of 3 floats) if t_emb="cape", CAPE parameters + see: https://arxiv.org/abs/2106.03143 + t_sparse_self_attn: (bool) if True, the self attentions are sparse + t_sparse_cross_attn: (bool) if True, the cross-attentions are sparse (don't use it + unless you designed really specific masks) + t_mask_type: (str) can be "diag", "jmask", "random", "global" or any combination + with '_' between: i.e. "diag_jmask_random" (note that this is permutation + invariant i.e. "diag_jmask_random" is equivalent to "jmask_random_diag") + t_mask_random_seed: (int) if "random" is in t_mask_type, controls the seed + that generated the random part of the mask + t_sparse_attn_window: (int) if "diag" is in t_mask_type, for a query (i), and + a key (j), the mask is True id |i-j|<=t_sparse_attn_window + t_global_window: (int) if "global" is in t_mask_type, mask[:t_global_window, :] + and mask[:, :t_global_window] will be True + t_sparsity: (float) if "random" is in t_mask_type, t_sparsity is the sparsity + level of the random part of the mask. + t_cross_first: (bool) if True cross attention is the first layer of the + transformer (False seems to be better) + rescale: weight rescaling trick + use_train_segment: (bool) if True, the actual size that is used during the + training is used during inference. + """ + super().__init__() + self.cac = cac + self.wiener_residual = wiener_residual + self.audio_channels = audio_channels + self.sources = sources + self.kernel_size = kernel_size + self.context = context + self.stride = stride + self.depth = depth + self.bottom_channels = bottom_channels + self.channels = channels + self.samplerate = samplerate + self.segment = segment + self.use_train_segment = use_train_segment + self.nfft = nfft + self.hop_length = nfft // 4 + self.wiener_iters = wiener_iters + self.end_iters = end_iters + self.freq_emb = None + assert wiener_iters == end_iters + + self.encoder = nn.ModuleList() + self.decoder = nn.ModuleList() + + self.tencoder = nn.ModuleList() + self.tdecoder = nn.ModuleList() + + chin = audio_channels + chin_z = chin # number of channels for the freq branch + if self.cac: + chin_z *= 2 + chout = channels_time or channels + chout_z = channels + freqs = nfft // 2 + + for index in range(depth): + norm = index >= norm_starts + freq = freqs > 1 + stri = stride + ker = kernel_size + if not freq: + assert freqs == 1 + ker = time_stride * 2 + stri = time_stride + + pad = True + last_freq = False + if freq and freqs <= kernel_size: + ker = freqs + pad = False + last_freq = True + + kw = { + "kernel_size": ker, + "stride": stri, + "freq": freq, + "pad": pad, + "norm": norm, + "rewrite": rewrite, + "norm_groups": norm_groups, + "dconv_kw": { + "depth": dconv_depth, + "compress": dconv_comp, + "init": dconv_init, + "gelu": True, + }, + } + kwt = dict(kw) + kwt["freq"] = 0 + kwt["kernel_size"] = kernel_size + kwt["stride"] = stride + kwt["pad"] = True + kw_dec = dict(kw) + multi = False + if multi_freqs and index < multi_freqs_depth: + multi = True + kw_dec["context_freq"] = False + + if last_freq: + chout_z = max(chout, chout_z) + chout = chout_z + + enc = HEncLayer( + chin_z, chout_z, dconv=dconv_mode & 1, context=context_enc, **kw + ) + if freq: + tenc = HEncLayer( + chin, + chout, + dconv=dconv_mode & 1, + context=context_enc, + empty=last_freq, + **kwt + ) + self.tencoder.append(tenc) + + if multi: + enc = MultiWrap(enc, multi_freqs) + self.encoder.append(enc) + if index == 0: + chin = self.audio_channels * len(self.sources) + chin_z = chin + if self.cac: + chin_z *= 2 + dec = HDecLayer( + chout_z, + chin_z, + dconv=dconv_mode & 2, + last=index == 0, + context=context, + **kw_dec + ) + if multi: + dec = MultiWrap(dec, multi_freqs) + if freq: + tdec = HDecLayer( + chout, + chin, + dconv=dconv_mode & 2, + empty=last_freq, + last=index == 0, + context=context, + **kwt + ) + self.tdecoder.insert(0, tdec) + self.decoder.insert(0, dec) + + chin = chout + chin_z = chout_z + chout = int(growth * chout) + chout_z = int(growth * chout_z) + if freq: + if freqs <= kernel_size: + freqs = 1 + else: + freqs //= stride + if index == 0 and freq_emb: + self.freq_emb = ScaledEmbedding( + freqs, chin_z, smooth=emb_smooth, scale=emb_scale + ) + self.freq_emb_scale = freq_emb + + if rescale: + rescale_module(self, reference=rescale) + + transformer_channels = channels * growth ** (depth - 1) + if bottom_channels: + self.channel_upsampler = nn.Conv1d(transformer_channels, bottom_channels, 1) + self.channel_downsampler = nn.Conv1d( + bottom_channels, transformer_channels, 1 + ) + self.channel_upsampler_t = nn.Conv1d( + transformer_channels, bottom_channels, 1 + ) + self.channel_downsampler_t = nn.Conv1d( + bottom_channels, transformer_channels, 1 + ) + + transformer_channels = bottom_channels + + if t_layers > 0: + self.crosstransformer = CrossTransformerEncoder( + dim=transformer_channels, + emb=t_emb, + hidden_scale=t_hidden_scale, + num_heads=t_heads, + num_layers=t_layers, + cross_first=t_cross_first, + dropout=t_dropout, + max_positions=t_max_positions, + norm_in=t_norm_in, + norm_in_group=t_norm_in_group, + group_norm=t_group_norm, + norm_first=t_norm_first, + norm_out=t_norm_out, + max_period=t_max_period, + weight_decay=t_weight_decay, + lr=t_lr, + layer_scale=t_layer_scale, + gelu=t_gelu, + sin_random_shift=t_sin_random_shift, + weight_pos_embed=t_weight_pos_embed, + cape_mean_normalize=t_cape_mean_normalize, + cape_augment=t_cape_augment, + cape_glob_loc_scale=t_cape_glob_loc_scale, + sparse_self_attn=t_sparse_self_attn, + sparse_cross_attn=t_sparse_cross_attn, + mask_type=t_mask_type, + mask_random_seed=t_mask_random_seed, + sparse_attn_window=t_sparse_attn_window, + global_window=t_global_window, + sparsity=t_sparsity, + auto_sparsity=t_auto_sparsity, + ) + else: + self.crosstransformer = None + + def _spec(self, x): + hl = self.hop_length + nfft = self.nfft + x0 = x # noqa + + # We re-pad the signal in order to keep the property + # that the size of the output is exactly the size of the input + # divided by the stride (here hop_length), when divisible. + # This is achieved by padding by 1/4th of the kernel size (here nfft). + # which is not supported by torch.stft. + # Having all convolution operations follow this convention allow to easily + # align the time and frequency branches later on. + assert hl == nfft // 4 + le = int(math.ceil(x.shape[-1] / hl)) + pad = hl // 2 * 3 + x = pad1d(x, (pad, pad + le * hl - x.shape[-1]), mode="reflect") + + z = spectro(x, nfft, hl)[..., :-1, :] + assert z.shape[-1] == le + 4, (z.shape, x.shape, le) + z = z[..., 2: 2 + le] + return z + + def _ispec(self, z, length=None, scale=0): + hl = self.hop_length // (4**scale) + z = F.pad(z, (0, 0, 0, 1)) + z = F.pad(z, (2, 2)) + pad = hl // 2 * 3 + le = hl * int(math.ceil(length / hl)) + 2 * pad + x = ispectro(z, hl, length=le) + x = x[..., pad: pad + length] + return x + + def _magnitude(self, z): + # return the magnitude of the spectrogram, except when cac is True, + # in which case we just move the complex dimension to the channel one. + if self.cac: + B, C, Fr, T = z.shape + m = torch.view_as_real(z).permute(0, 1, 4, 2, 3) + m = m.reshape(B, C * 2, Fr, T) + else: + m = z.abs() + return m + + def _mask(self, z, m): + # Apply masking given the mixture spectrogram `z` and the estimated mask `m`. + # If `cac` is True, `m` is actually a full spectrogram and `z` is ignored. + niters = self.wiener_iters + if self.cac: + B, S, C, Fr, T = m.shape + out = m.view(B, S, -1, 2, Fr, T).permute(0, 1, 2, 4, 5, 3) + out = torch.view_as_complex(out.contiguous()) + return out + if self.training: + niters = self.end_iters + if niters < 0: + z = z[:, None] + return z / (1e-8 + z.abs()) * m + else: + return self._wiener(m, z, niters) + + def _wiener(self, mag_out, mix_stft, niters): + # apply wiener filtering from OpenUnmix. + init = mix_stft.dtype + wiener_win_len = 300 + residual = self.wiener_residual + + B, S, C, Fq, T = mag_out.shape + mag_out = mag_out.permute(0, 4, 3, 2, 1) + mix_stft = torch.view_as_real(mix_stft.permute(0, 3, 2, 1)) + + outs = [] + for sample in range(B): + pos = 0 + out = [] + for pos in range(0, T, wiener_win_len): + frame = slice(pos, pos + wiener_win_len) + z_out = wiener( + mag_out[sample, frame], + mix_stft[sample, frame], + niters, + residual=residual, + ) + out.append(z_out.transpose(-1, -2)) + outs.append(torch.cat(out, dim=0)) + out = torch.view_as_complex(torch.stack(outs, 0)) + out = out.permute(0, 4, 3, 2, 1).contiguous() + if residual: + out = out[:, :-1] + assert list(out.shape) == [B, S, C, Fq, T] + return out.to(init) + + def valid_length(self, length: int): + """ + Return a length that is appropriate for evaluation. + In our case, always return the training length, unless + it is smaller than the given length, in which case this + raises an error. + """ + if not self.use_train_segment: + return length + training_length = int(self.segment * self.samplerate) + if training_length < length: + raise ValueError( + f"Given length {length} is longer than " + f"training length {training_length}") + return training_length + + def forward(self, mix): + length = mix.shape[-1] + length_pre_pad = None + if self.use_train_segment: + if self.training: + self.segment = Fraction(mix.shape[-1], self.samplerate) + else: + training_length = int(self.segment * self.samplerate) + if mix.shape[-1] < training_length: + length_pre_pad = mix.shape[-1] + mix = F.pad(mix, (0, training_length - length_pre_pad)) + z = self._spec(mix) + mag = self._magnitude(z).to(mix.device) + x = mag + + B, C, Fq, T = x.shape + + # unlike previous Demucs, we always normalize because it is easier. + mean = x.mean(dim=(1, 2, 3), keepdim=True) + std = x.std(dim=(1, 2, 3), keepdim=True) + x = (x - mean) / (1e-5 + std) + # x will be the freq. branch input. + + # Prepare the time branch input. + xt = mix + meant = xt.mean(dim=(1, 2), keepdim=True) + stdt = xt.std(dim=(1, 2), keepdim=True) + xt = (xt - meant) / (1e-5 + stdt) + + # okay, this is a giant mess I know... + saved = [] # skip connections, freq. + saved_t = [] # skip connections, time. + lengths = [] # saved lengths to properly remove padding, freq branch. + lengths_t = [] # saved lengths for time branch. + for idx, encode in enumerate(self.encoder): + lengths.append(x.shape[-1]) + inject = None + if idx < len(self.tencoder): + # we have not yet merged branches. + lengths_t.append(xt.shape[-1]) + tenc = self.tencoder[idx] + xt = tenc(xt) + if not tenc.empty: + # save for skip connection + saved_t.append(xt) + else: + # tenc contains just the first conv., so that now time and freq. + # branches have the same shape and can be merged. + inject = xt + x = encode(x, inject) + if idx == 0 and self.freq_emb is not None: + # add frequency embedding to allow for non equivariant convolutions + # over the frequency axis. + frs = torch.arange(x.shape[-2], device=x.device) + emb = self.freq_emb(frs).t()[None, :, :, None].expand_as(x) + x = x + self.freq_emb_scale * emb + + saved.append(x) + if self.crosstransformer: + if self.bottom_channels: + b, c, f, t = x.shape + x = rearrange(x, "b c f t-> b c (f t)") + x = self.channel_upsampler(x) + x = rearrange(x, "b c (f t)-> b c f t", f=f) + xt = self.channel_upsampler_t(xt) + + x, xt = self.crosstransformer(x, xt) + + if self.bottom_channels: + x = rearrange(x, "b c f t-> b c (f t)") + x = self.channel_downsampler(x) + x = rearrange(x, "b c (f t)-> b c f t", f=f) + xt = self.channel_downsampler_t(xt) + + for idx, decode in enumerate(self.decoder): + skip = saved.pop(-1) + x, pre = decode(x, skip, lengths.pop(-1)) + # `pre` contains the output just before final transposed convolution, + # which is used when the freq. and time branch separate. + + offset = self.depth - len(self.tdecoder) + if idx >= offset: + tdec = self.tdecoder[idx - offset] + length_t = lengths_t.pop(-1) + if tdec.empty: + assert pre.shape[2] == 1, pre.shape + pre = pre[:, :, 0] + xt, _ = tdec(pre, None, length_t) + else: + skip = saved_t.pop(-1) + xt, _ = tdec(xt, skip, length_t) + + # Let's make sure we used all stored skip connections. + assert len(saved) == 0 + assert len(lengths_t) == 0 + assert len(saved_t) == 0 + + S = len(self.sources) + x = x.view(B, S, -1, Fq, T) + x = x * std[:, None] + mean[:, None] + + # to cpu as mps doesnt support complex numbers + # demucs issue #435 ##432 + # NOTE: in this case z already is on cpu + # TODO: remove this when mps supports complex numbers + x_is_mps_xpu = x.device.type in ["mps", "xpu"] + x_device = x.device + if x_is_mps_xpu: + x = x.cpu() + + zout = self._mask(z, x) + if self.use_train_segment: + if self.training: + x = self._ispec(zout, length) + else: + x = self._ispec(zout, training_length) + else: + x = self._ispec(zout, length) + + # back to mps device + if x_is_mps_xpu: + x = x.to(x_device) + + if self.use_train_segment: + if self.training: + xt = xt.view(B, S, -1, length) + else: + xt = xt.view(B, S, -1, training_length) + else: + xt = xt.view(B, S, -1, length) + xt = xt * stdt[:, None] + meant[:, None] + x = xt + x + if length_pre_pad: + x = x[..., :length_pre_pad] + return x diff --git a/demucs/pretrained.py b/demucs/pretrained.py new file mode 100644 index 00000000..80ae49cb --- /dev/null +++ b/demucs/pretrained.py @@ -0,0 +1,98 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Loading pretrained models. +""" + +import logging +from pathlib import Path +import typing as tp + +from dora.log import fatal, bold + +from .hdemucs import HDemucs +from .repo import RemoteRepo, LocalRepo, ModelOnlyRepo, BagOnlyRepo, AnyModelRepo, ModelLoadingError # noqa +from .states import _check_diffq + +logger = logging.getLogger(__name__) +ROOT_URL = "https://dl.fbaipublicfiles.com/demucs/" +REMOTE_ROOT = Path(__file__).parent / 'remote' + +SOURCES = ["drums", "bass", "other", "vocals"] +DEFAULT_MODEL = 'htdemucs' + + +def demucs_unittest(): + model = HDemucs(channels=4, sources=SOURCES) + return model + + +def add_model_flags(parser): + group = parser.add_mutually_exclusive_group(required=False) + group.add_argument("-s", "--sig", help="Locally trained XP signature.") + group.add_argument("-n", "--name", default="htdemucs", + help="Pretrained model name or signature. Default is htdemucs.") + parser.add_argument("--repo", type=Path, + help="Folder containing all pre-trained models for use with -n.") + + +def _parse_remote_files(remote_file_list) -> tp.Dict[str, str]: + root: str = '' + models: tp.Dict[str, str] = {} + for line in remote_file_list.read_text().split('\n'): + line = line.strip() + if line.startswith('#'): + continue + elif len(line) == 0: + continue + elif line.startswith('root:'): + root = line.split(':', 1)[1].strip() + else: + sig = line.split('-', 1)[0] + assert sig not in models + models[sig] = ROOT_URL + root + line + return models + + +def get_model(name: str, + repo: tp.Optional[Path] = None): + """`name` must be a bag of models name or a pretrained signature + from the remote AWS model repo or the specified local repo if `repo` is not None. + """ + if name == 'demucs_unittest': + return demucs_unittest() + model_repo: ModelOnlyRepo + if repo is None: + models = _parse_remote_files(REMOTE_ROOT / 'files.txt') + model_repo = RemoteRepo(models) + bag_repo = BagOnlyRepo(REMOTE_ROOT, model_repo) + else: + if not repo.is_dir(): + fatal(f"{repo} must exist and be a directory.") + model_repo = LocalRepo(repo) + bag_repo = BagOnlyRepo(repo, model_repo) + any_repo = AnyModelRepo(model_repo, bag_repo) + try: + model = any_repo.get_model(name) + except ImportError as exc: + if 'diffq' in exc.args[0]: + _check_diffq() + raise + + model.eval() + return model + + +def get_model_from_args(args): + """ + Load local model package or pre-trained model. + """ + if args.name is None: + args.name = DEFAULT_MODEL + print(bold("Important: the default model was recently changed to `htdemucs`"), + "the latest Hybrid Transformer Demucs model. In some cases, this model can " + "actually perform worse than previous models. To get back the old default model " + "use `-n mdx_extra_q`.") + return get_model(name=args.name, repo=args.repo) diff --git a/demucs/py.typed b/demucs/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/demucs/repitch.py b/demucs/repitch.py new file mode 100644 index 00000000..b69c0d25 --- /dev/null +++ b/demucs/repitch.py @@ -0,0 +1,87 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Utility for on the fly pitch/tempo change for data augmentation.""" + +import random +import subprocess as sp +import tempfile + +from . import audio_legacy +import torch +import torchaudio as ta + +from .audio import save_audio + + +class RepitchedWrapper: + """ + Wrap a dataset to apply online change of pitch / tempo. + """ + def __init__(self, dataset, proba=0.2, max_pitch=2, max_tempo=12, + tempo_std=5, vocals=[3], same=True): + self.dataset = dataset + self.proba = proba + self.max_pitch = max_pitch + self.max_tempo = max_tempo + self.tempo_std = tempo_std + self.same = same + self.vocals = vocals + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, index): + streams = self.dataset[index] + in_length = streams.shape[-1] + out_length = int((1 - 0.01 * self.max_tempo) * in_length) + + if random.random() < self.proba: + outs = [] + for idx, stream in enumerate(streams): + if idx == 0 or not self.same: + delta_pitch = random.randint(-self.max_pitch, self.max_pitch) + delta_tempo = random.gauss(0, self.tempo_std) + delta_tempo = min(max(-self.max_tempo, delta_tempo), self.max_tempo) + stream = repitch( + stream, + delta_pitch, + delta_tempo, + voice=idx in self.vocals) + outs.append(stream[:, :out_length]) + streams = torch.stack(outs) + else: + streams = streams[..., :out_length] + return streams + + +def repitch(wav, pitch, tempo, voice=False, quick=False, samplerate=44100): + """ + tempo is a relative delta in percentage, so tempo=10 means tempo at 110%! + pitch is in semi tones. + Requires `soundstretch` to be installed, see + https://www.surina.net/soundtouch/soundstretch.html + """ + infile = tempfile.NamedTemporaryFile(suffix=".wav") + outfile = tempfile.NamedTemporaryFile(suffix=".wav") + save_audio(wav, infile.name, samplerate, clip='clamp') + command = [ + "soundstretch", + infile.name, + outfile.name, + f"-pitch={pitch}", + f"-tempo={tempo:.6f}", + ] + if quick: + command += ["-quick"] + if voice: + command += ["-speech"] + try: + sp.run(command, capture_output=True, check=True) + except sp.CalledProcessError as error: + raise RuntimeError(f"Could not change bpm because {error.stderr.decode('utf-8')}") + wav, sr = ta.load(outfile.name) + assert sr == samplerate + return wav diff --git a/demucs/repo.py b/demucs/repo.py new file mode 100644 index 00000000..5e20ff51 --- /dev/null +++ b/demucs/repo.py @@ -0,0 +1,166 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Represents a model repository, including pre-trained models and bags of models. +A repo can either be the main remote repository stored in AWS, or a local repository +with your own models. +""" + +from hashlib import sha256 +from pathlib import Path +import typing as tp + +import torch +import yaml + +from .apply import BagOfModels, Model +from .states import load_model + + +AnyModel = tp.Union[Model, BagOfModels] + + +class ModelLoadingError(RuntimeError): + pass + + +def check_checksum(path: Path, checksum: str): + sha = sha256() + with open(path, 'rb') as file: + while True: + buf = file.read(2**20) + if not buf: + break + sha.update(buf) + actual_checksum = sha.hexdigest()[:len(checksum)] + if actual_checksum != checksum: + raise ModelLoadingError(f'Invalid checksum for file {path}, ' + f'expected {checksum} but got {actual_checksum}') + + +class ModelOnlyRepo: + """Base class for all model only repos. + """ + def has_model(self, sig: str) -> bool: + raise NotImplementedError() + + def get_model(self, sig: str) -> Model: + raise NotImplementedError() + + def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: + raise NotImplementedError() + + +class RemoteRepo(ModelOnlyRepo): + def __init__(self, models: tp.Dict[str, str]): + self._models = models + + def has_model(self, sig: str) -> bool: + return sig in self._models + + def get_model(self, sig: str) -> Model: + try: + url = self._models[sig] + except KeyError: + raise ModelLoadingError(f'Could not find a pre-trained model with signature {sig}.') + pkg = torch.hub.load_state_dict_from_url( + url, map_location='cpu', check_hash=True) # type: ignore + return load_model(pkg) + + def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: + return self._models # type: ignore + + +class LocalRepo(ModelOnlyRepo): + def __init__(self, root: Path): + self.root = root + self.scan() + + def scan(self): + self._models = {} + self._checksums = {} + for file in self.root.iterdir(): + if file.suffix == '.th': + if '-' in file.stem: + xp_sig, checksum = file.stem.split('-') + self._checksums[xp_sig] = checksum + else: + xp_sig = file.stem + if xp_sig in self._models: + raise ModelLoadingError( + f'Duplicate pre-trained model exist for signature {xp_sig}. ' + 'Please delete all but one.') + self._models[xp_sig] = file + + def has_model(self, sig: str) -> bool: + return sig in self._models + + def get_model(self, sig: str) -> Model: + try: + file = self._models[sig] + except KeyError: + raise ModelLoadingError(f'Could not find pre-trained model with signature {sig}.') + if sig in self._checksums: + check_checksum(file, self._checksums[sig]) + return load_model(file) + + def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: + return self._models + + +class BagOnlyRepo: + """Handles only YAML files containing bag of models, leaving the actual + model loading to some Repo. + """ + def __init__(self, root: Path, model_repo: ModelOnlyRepo): + self.root = root + self.model_repo = model_repo + self.scan() + + def scan(self): + self._bags = {} + for file in self.root.iterdir(): + if file.suffix == '.yaml': + self._bags[file.stem] = file + + def has_model(self, name: str) -> bool: + return name in self._bags + + def get_model(self, name: str) -> BagOfModels: + try: + yaml_file = self._bags[name] + except KeyError: + raise ModelLoadingError(f'{name} is neither a single pre-trained model or ' + 'a bag of models.') + bag = yaml.safe_load(open(yaml_file)) + signatures = bag['models'] + models = [self.model_repo.get_model(sig) for sig in signatures] + weights = bag.get('weights') + segment = bag.get('segment') + return BagOfModels(models, weights, segment) + + def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: + return self._bags + + +class AnyModelRepo: + def __init__(self, model_repo: ModelOnlyRepo, bag_repo: BagOnlyRepo): + self.model_repo = model_repo + self.bag_repo = bag_repo + + def has_model(self, name_or_sig: str) -> bool: + return self.model_repo.has_model(name_or_sig) or self.bag_repo.has_model(name_or_sig) + + def get_model(self, name_or_sig: str) -> AnyModel: + if self.model_repo.has_model(name_or_sig): + return self.model_repo.get_model(name_or_sig) + else: + return self.bag_repo.get_model(name_or_sig) + + def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: + models = self.model_repo.list_model() + for key, value in self.bag_repo.list_model().items(): + models[key] = value + return models diff --git a/demucs/separate.py b/demucs/separate.py new file mode 100644 index 00000000..7de5f114 --- /dev/null +++ b/demucs/separate.py @@ -0,0 +1,228 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import argparse +import sys +from pathlib import Path + +from dora.log import fatal +import torch as th + +from .api import Separator, save_audio, list_models + +from .apply import BagOfModels +from .htdemucs import HTDemucs +from .pretrained import add_model_flags, ModelLoadingError + + +def get_parser(): + parser = argparse.ArgumentParser("demucs.separate", + description="Separate the sources for the given tracks") + parser.add_argument("tracks", nargs='*', type=Path, default=[], help='Path to tracks') + add_model_flags(parser) + parser.add_argument("--list-models", action="store_true", help="List available models " + "from current repo and exit") + parser.add_argument("-v", "--verbose", action="store_true") + parser.add_argument("-o", + "--out", + type=Path, + default=Path("separated"), + help="Folder where to put extracted tracks. A subfolder " + "with the model name will be created.") + parser.add_argument("--filename", + default="{track}/{stem}.{ext}", + help="Set the name of output file. \n" + 'Use "{track}", "{trackext}", "{stem}", "{ext}" to use ' + "variables of track name without extension, track extension, " + "stem name and default output file extension. \n" + 'Default is "{track}/{stem}.{ext}".') + parser.add_argument("-d", + "--device", + default=( + "cuda" + if th.cuda.is_available() + else "mps" + if th.backends.mps.is_available() + else "cpu" + ), + help="Device to use, default is cuda if available else cpu") + parser.add_argument("--shifts", + default=1, + type=int, + help="Number of random shifts for equivariant stabilization." + "Increase separation time but improves quality for Demucs. 10 was used " + "in the original paper.") + parser.add_argument("--overlap", + default=0.25, + type=float, + help="Overlap between the splits.") + split_group = parser.add_mutually_exclusive_group() + split_group.add_argument("--no-split", + action="store_false", + dest="split", + default=True, + help="Doesn't split audio in chunks. " + "This can use large amounts of memory.") + split_group.add_argument("--segment", type=int, + help="Set split size of each chunk. " + "This can help save memory of graphic card. ") + parser.add_argument("--two-stems", + dest="stem", metavar="STEM", + help="Only separate audio into {STEM} and no_{STEM}. ") + parser.add_argument("--other-method", dest="other_method", choices=["none", "add", "minus"], + default="add", help='Decide how to get "no_{STEM}". "none" will not save ' + '"no_{STEM}". "add" will add all the other stems. "minus" will use the ' + "original track minus the selected stem.") + depth_group = parser.add_mutually_exclusive_group() + depth_group.add_argument("--int24", action="store_true", + help="Save wav output as 24 bits wav.") + depth_group.add_argument("--float32", action="store_true", + help="Save wav output as float32 (2x bigger).") + parser.add_argument("--clip-mode", default="rescale", choices=["rescale", "clamp", "none"], + help="Strategy for avoiding clipping: rescaling entire signal " + "if necessary (rescale) or hard clipping (clamp).") + format_group = parser.add_mutually_exclusive_group() + format_group.add_argument("--flac", action="store_true", + help="Convert the output wavs to flac.") + format_group.add_argument("--mp3", action="store_true", + help="Convert the output wavs to mp3.") + parser.add_argument("--mp3-bitrate", + default=320, + type=int, + help="Bitrate of converted mp3.") + parser.add_argument("--mp3-preset", choices=range(2, 8), type=int, default=2, + help="Encoder preset of MP3, 2 for highest quality, 7 for " + "fastest speed. Default is 2") + parser.add_argument("-j", "--jobs", + default=0, + type=int, + help="Number of jobs. This can increase memory usage but will " + "be much faster when multiple cores are available.") + + return parser + + +def main(opts=None): + parser = get_parser() + args = parser.parse_args(opts) + if args.list_models: + models = list_models(args.repo) + print("Bag of models:", end="\n ") + print("\n ".join(models["bag"])) + print("Single models:", end="\n ") + print("\n ".join(models["single"])) + sys.exit(0) + if len(args.tracks) == 0: + print("error: the following arguments are required: tracks", file=sys.stderr) + sys.exit(1) + + try: + separator = Separator(model=args.name, + repo=args.repo, + device=args.device, + shifts=args.shifts, + split=args.split, + overlap=args.overlap, + progress=True, + jobs=args.jobs, + segment=args.segment) + except ModelLoadingError as error: + fatal(error.args[0]) + + max_allowed_segment = float('inf') + if isinstance(separator.model, HTDemucs): + max_allowed_segment = float(separator.model.segment) + elif isinstance(separator.model, BagOfModels): + max_allowed_segment = separator.model.max_allowed_segment + if args.segment is not None and args.segment > max_allowed_segment: + fatal("Cannot use a Transformer model with a longer segment " + f"than it was trained for. Maximum segment is: {max_allowed_segment}") + + if isinstance(separator.model, BagOfModels): + print( + f"Selected model is a bag of {len(separator.model.models)} models. " + "You will see that many progress bars per track." + ) + + if args.stem is not None and args.stem not in separator.model.sources: + fatal( + 'error: stem "{stem}" is not in selected model. ' + "STEM must be one of {sources}.".format( + stem=args.stem, sources=", ".join(separator.model.sources) + ) + ) + out = args.out / args.name + out.mkdir(parents=True, exist_ok=True) + print(f"Separated tracks will be stored in {out.resolve()}") + for track in args.tracks: + if not track.exists(): + print(f"File {track} does not exist. If the path contains spaces, " + 'please try again after surrounding the entire path with quotes "".', + file=sys.stderr) + continue + print(f"Separating track {track}") + + origin, res = separator.separate_audio_file(track) + + if args.mp3: + ext = "mp3" + elif args.flac: + ext = "flac" + else: + ext = "wav" + kwargs = { + "samplerate": separator.samplerate, + "bitrate": args.mp3_bitrate, + "preset": args.mp3_preset, + "clip": args.clip_mode, + "as_float": args.float32, + "bits_per_sample": 24 if args.int24 else 16, + } + if args.stem is None: + for name, source in res.items(): + stem = out / args.filename.format( + track=track.name.rsplit(".", 1)[0], + trackext=track.name.rsplit(".", 1)[-1], + stem=name, + ext=ext, + ) + stem.parent.mkdir(parents=True, exist_ok=True) + save_audio(source, str(stem), **kwargs) + else: + stem = out / args.filename.format( + track=track.name.rsplit(".", 1)[0], + trackext=track.name.rsplit(".", 1)[-1], + stem="minus_" + args.stem, + ext=ext, + ) + if args.other_method == "minus": + stem.parent.mkdir(parents=True, exist_ok=True) + save_audio(origin - res[args.stem], str(stem), **kwargs) + stem = out / args.filename.format( + track=track.name.rsplit(".", 1)[0], + trackext=track.name.rsplit(".", 1)[-1], + stem=args.stem, + ext=ext, + ) + stem.parent.mkdir(parents=True, exist_ok=True) + save_audio(res.pop(args.stem), str(stem), **kwargs) + # Warning : after poping the stem, selected stem is no longer in the dict 'res' + if args.other_method == "add": + other_stem = th.zeros_like(next(iter(res.values()))) + for i in res.values(): + other_stem += i + stem = out / args.filename.format( + track=track.name.rsplit(".", 1)[0], + trackext=track.name.rsplit(".", 1)[-1], + stem="no_" + args.stem, + ext=ext, + ) + stem.parent.mkdir(parents=True, exist_ok=True) + save_audio(other_stem, str(stem), **kwargs) + + +if __name__ == "__main__": + main() diff --git a/demucs/solver.py b/demucs/solver.py new file mode 100644 index 00000000..7c80b148 --- /dev/null +++ b/demucs/solver.py @@ -0,0 +1,405 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Main training loop.""" + +import logging + +from dora import get_xp +from dora.utils import write_and_rename +from dora.log import LogProgress, bold +import torch +import torch.nn.functional as F + +from . import augment, distrib, states, pretrained +from .apply import apply_model +from .ema import ModelEMA +from .evaluate import evaluate, new_sdr +from .svd import svd_penalty +from .utils import pull_metric, EMA + +logger = logging.getLogger(__name__) + + +def _summary(metrics): + return " | ".join(f"{key.capitalize()}={val}" for key, val in metrics.items()) + + +class Solver(object): + def __init__(self, loaders, model, optimizer, args): + self.args = args + self.loaders = loaders + + self.model = model + self.optimizer = optimizer + self.quantizer = states.get_quantizer(self.model, args.quant, self.optimizer) + self.dmodel = distrib.wrap(model) + self.device = next(iter(self.model.parameters())).device + + # Exponential moving average of the model, either updated every batch or epoch. + # The best model from all the EMAs and the original one is kept based on the valid + # loss for the final best model. + self.emas = {'batch': [], 'epoch': []} + for kind in self.emas.keys(): + decays = getattr(args.ema, kind) + device = self.device if kind == 'batch' else 'cpu' + if decays: + for decay in decays: + self.emas[kind].append(ModelEMA(self.model, decay, device=device)) + + # data augment + augments = [augment.Shift(shift=int(args.dset.samplerate * args.dset.shift), + same=args.augment.shift_same)] + if args.augment.flip: + augments += [augment.FlipChannels(), augment.FlipSign()] + for aug in ['scale', 'remix']: + kw = getattr(args.augment, aug) + if kw.proba: + augments.append(getattr(augment, aug.capitalize())(**kw)) + self.augment = torch.nn.Sequential(*augments) + + xp = get_xp() + self.folder = xp.folder + # Checkpoints + self.checkpoint_file = xp.folder / 'checkpoint.th' + self.best_file = xp.folder / 'best.th' + logger.debug("Checkpoint will be saved to %s", self.checkpoint_file.resolve()) + self.best_state = None + self.best_changed = False + + self.link = xp.link + self.history = self.link.history + + self._reset() + + def _serialize(self, epoch): + package = {} + package['state'] = self.model.state_dict() + package['optimizer'] = self.optimizer.state_dict() + package['history'] = self.history + package['best_state'] = self.best_state + package['args'] = self.args + for kind, emas in self.emas.items(): + for k, ema in enumerate(emas): + package[f'ema_{kind}_{k}'] = ema.state_dict() + with write_and_rename(self.checkpoint_file) as tmp: + torch.save(package, tmp) + + save_every = self.args.save_every + if save_every and (epoch + 1) % save_every == 0 and epoch + 1 != self.args.epochs: + with write_and_rename(self.folder / f'checkpoint_{epoch + 1}.th') as tmp: + torch.save(package, tmp) + + if self.best_changed: + # Saving only the latest best model. + with write_and_rename(self.best_file) as tmp: + package = states.serialize_model(self.model, self.args) + package['state'] = self.best_state + torch.save(package, tmp) + self.best_changed = False + + def _reset(self): + """Reset state of the solver, potentially using checkpoint.""" + if self.checkpoint_file.exists(): + logger.info(f'Loading checkpoint model: {self.checkpoint_file}') + package = torch.load(self.checkpoint_file, 'cpu') + self.model.load_state_dict(package['state']) + self.optimizer.load_state_dict(package['optimizer']) + self.history[:] = package['history'] + self.best_state = package['best_state'] + for kind, emas in self.emas.items(): + for k, ema in enumerate(emas): + ema.load_state_dict(package[f'ema_{kind}_{k}']) + elif self.args.continue_pretrained: + model = pretrained.get_model( + name=self.args.continue_pretrained, + repo=self.args.pretrained_repo) + self.model.load_state_dict(model.state_dict()) + elif self.args.continue_from: + name = 'checkpoint.th' + root = self.folder.parent + cf = root / str(self.args.continue_from) / name + logger.info("Loading from %s", cf) + package = torch.load(cf, 'cpu') + self.best_state = package['best_state'] + if self.args.continue_best: + self.model.load_state_dict(package['best_state'], strict=False) + else: + self.model.load_state_dict(package['state'], strict=False) + if self.args.continue_opt: + self.optimizer.load_state_dict(package['optimizer']) + + def _format_train(self, metrics: dict) -> dict: + """Formatting for train/valid metrics.""" + losses = { + 'loss': format(metrics['loss'], ".4f"), + 'reco': format(metrics['reco'], ".4f"), + } + if 'nsdr' in metrics: + losses['nsdr'] = format(metrics['nsdr'], ".3f") + if self.quantizer is not None: + losses['ms'] = format(metrics['ms'], ".2f") + if 'grad' in metrics: + losses['grad'] = format(metrics['grad'], ".4f") + if 'best' in metrics: + losses['best'] = format(metrics['best'], '.4f') + if 'bname' in metrics: + losses['bname'] = metrics['bname'] + if 'penalty' in metrics: + losses['penalty'] = format(metrics['penalty'], ".4f") + if 'hloss' in metrics: + losses['hloss'] = format(metrics['hloss'], ".4f") + return losses + + def _format_test(self, metrics: dict) -> dict: + """Formatting for test metrics.""" + losses = {} + if 'sdr' in metrics: + losses['sdr'] = format(metrics['sdr'], '.3f') + if 'nsdr' in metrics: + losses['nsdr'] = format(metrics['nsdr'], '.3f') + for source in self.model.sources: + key = f'sdr_{source}' + if key in metrics: + losses[key] = format(metrics[key], '.3f') + key = f'nsdr_{source}' + if key in metrics: + losses[key] = format(metrics[key], '.3f') + return losses + + def train(self): + # Optimizing the model + if self.history: + logger.info("Replaying metrics from previous run") + for epoch, metrics in enumerate(self.history): + formatted = self._format_train(metrics['train']) + logger.info( + bold(f'Train Summary | Epoch {epoch + 1} | {_summary(formatted)}')) + formatted = self._format_train(metrics['valid']) + logger.info( + bold(f'Valid Summary | Epoch {epoch + 1} | {_summary(formatted)}')) + if 'test' in metrics: + formatted = self._format_test(metrics['test']) + if formatted: + logger.info(bold(f"Test Summary | Epoch {epoch + 1} | {_summary(formatted)}")) + + epoch = 0 + for epoch in range(len(self.history), self.args.epochs): + # Train one epoch + self.model.train() # Turn on BatchNorm & Dropout + metrics = {} + logger.info('-' * 70) + logger.info("Training...") + metrics['train'] = self._run_one_epoch(epoch) + formatted = self._format_train(metrics['train']) + logger.info( + bold(f'Train Summary | Epoch {epoch + 1} | {_summary(formatted)}')) + + # Cross validation + logger.info('-' * 70) + logger.info('Cross validation...') + self.model.eval() # Turn off Batchnorm & Dropout + with torch.no_grad(): + valid = self._run_one_epoch(epoch, train=False) + bvalid = valid + bname = 'main' + state = states.copy_state(self.model.state_dict()) + metrics['valid'] = {} + metrics['valid']['main'] = valid + key = self.args.test.metric + for kind, emas in self.emas.items(): + for k, ema in enumerate(emas): + with ema.swap(): + valid = self._run_one_epoch(epoch, train=False) + name = f'ema_{kind}_{k}' + metrics['valid'][name] = valid + a = valid[key] + b = bvalid[key] + if key.startswith('nsdr'): + a = -a + b = -b + if a < b: + bvalid = valid + state = ema.state + bname = name + metrics['valid'].update(bvalid) + metrics['valid']['bname'] = bname + + valid_loss = metrics['valid'][key] + mets = pull_metric(self.link.history, f'valid.{key}') + [valid_loss] + if key.startswith('nsdr'): + best_loss = max(mets) + else: + best_loss = min(mets) + metrics['valid']['best'] = best_loss + if self.args.svd.penalty > 0: + kw = dict(self.args.svd) + kw.pop('penalty') + with torch.no_grad(): + penalty = svd_penalty(self.model, exact=True, **kw) + metrics['valid']['penalty'] = penalty + + formatted = self._format_train(metrics['valid']) + logger.info( + bold(f'Valid Summary | Epoch {epoch + 1} | {_summary(formatted)}')) + + # Save the best model + if valid_loss == best_loss or self.args.dset.train_valid: + logger.info(bold('New best valid loss %.4f'), valid_loss) + self.best_state = states.copy_state(state) + self.best_changed = True + + # Eval model every `test.every` epoch or on last epoch + should_eval = (epoch + 1) % self.args.test.every == 0 + is_last = epoch == self.args.epochs - 1 + # # Tries to detect divergence in a reliable way and finish job + # # not to waste compute. + # # Commented out as this was super specific to the MDX competition. + # reco = metrics['valid']['main']['reco'] + # div = epoch >= 180 and reco > 0.18 + # div = div or epoch >= 100 and reco > 0.25 + # div = div and self.args.optim.loss == 'l1' + # if div: + # logger.warning("Finishing training early because valid loss is too high.") + # is_last = True + if should_eval or is_last: + # Evaluate on the testset + logger.info('-' * 70) + logger.info('Evaluating on the test set...') + # We switch to the best known model for testing + if self.args.test.best: + state = self.best_state + else: + state = states.copy_state(self.model.state_dict()) + compute_sdr = self.args.test.sdr and is_last + with states.swap_state(self.model, state): + with torch.no_grad(): + metrics['test'] = evaluate(self, compute_sdr=compute_sdr) + formatted = self._format_test(metrics['test']) + logger.info(bold(f"Test Summary | Epoch {epoch + 1} | {_summary(formatted)}")) + self.link.push_metrics(metrics) + + if distrib.rank == 0: + # Save model each epoch + self._serialize(epoch) + logger.debug("Checkpoint saved to %s", self.checkpoint_file.resolve()) + if is_last: + break + + def _run_one_epoch(self, epoch, train=True): + args = self.args + data_loader = self.loaders['train'] if train else self.loaders['valid'] + if distrib.world_size > 1 and train: + data_loader.sampler.set_epoch(epoch) + + label = ["Valid", "Train"][train] + name = label + f" | Epoch {epoch + 1}" + total = len(data_loader) + if args.max_batches: + total = min(total, args.max_batches) + logprog = LogProgress(logger, data_loader, total=total, + updates=self.args.misc.num_prints, name=name) + averager = EMA() + + for idx, sources in enumerate(logprog): + sources = sources.to(self.device) + if train: + sources = self.augment(sources) + mix = sources.sum(dim=1) + else: + mix = sources[:, 0] + sources = sources[:, 1:] + + if not train and self.args.valid_apply: + estimate = apply_model(self.model, mix, split=self.args.test.split, overlap=0) + else: + estimate = self.dmodel(mix) + if train and hasattr(self.model, 'transform_target'): + sources = self.model.transform_target(mix, sources) + assert estimate.shape == sources.shape, (estimate.shape, sources.shape) + dims = tuple(range(2, sources.dim())) + + if args.optim.loss == 'l1': + loss = F.l1_loss(estimate, sources, reduction='none') + loss = loss.mean(dims).mean(0) + reco = loss + elif args.optim.loss == 'mse': + loss = F.mse_loss(estimate, sources, reduction='none') + loss = loss.mean(dims) + reco = loss**0.5 + reco = reco.mean(0) + else: + raise ValueError(f"Invalid loss {self.args.loss}") + weights = torch.tensor(args.weights).to(sources) + loss = (loss * weights).sum() / weights.sum() + + ms = 0 + if self.quantizer is not None: + ms = self.quantizer.model_size() + if args.quant.diffq: + loss += args.quant.diffq * ms + + losses = {} + losses['reco'] = (reco * weights).sum() / weights.sum() + losses['ms'] = ms + + if not train: + nsdrs = new_sdr(sources, estimate.detach()).mean(0) + total = 0 + for source, nsdr, w in zip(self.model.sources, nsdrs, weights): + losses[f'nsdr_{source}'] = nsdr + total += w * nsdr + losses['nsdr'] = total / weights.sum() + + if train and args.svd.penalty > 0: + kw = dict(args.svd) + kw.pop('penalty') + penalty = svd_penalty(self.model, **kw) + losses['penalty'] = penalty + loss += args.svd.penalty * penalty + + losses['loss'] = loss + + for k, source in enumerate(self.model.sources): + losses[f'reco_{source}'] = reco[k] + + # optimize model in training mode + if train: + loss.backward() + grad_norm = 0 + grads = [] + for p in self.model.parameters(): + if p.grad is not None: + grad_norm += p.grad.data.norm()**2 + grads.append(p.grad.data) + losses['grad'] = grad_norm ** 0.5 + if args.optim.clip_grad: + torch.nn.utils.clip_grad_norm_( + self.model.parameters(), + args.optim.clip_grad) + + if self.args.flag == 'uns': + for n, p in self.model.named_parameters(): + if p.grad is None: + print('no grad', n) + self.optimizer.step() + self.optimizer.zero_grad() + for ema in self.emas['batch']: + ema.update() + losses = averager(losses) + logs = self._format_train(losses) + logprog.update(**logs) + # Just in case, clear some memory + del loss, estimate, reco, ms + if args.max_batches == idx: + break + if self.args.debug and train: + break + if self.args.flag == 'debug': + break + if train: + for ema in self.emas['epoch']: + ema.update() + return distrib.average(losses, idx + 1) diff --git a/demucs/spec.py b/demucs/spec.py new file mode 100644 index 00000000..d8f6ee5e --- /dev/null +++ b/demucs/spec.py @@ -0,0 +1,47 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Conveniance wrapper to perform STFT and iSTFT""" + +import torch as th + + +def spectro(x, n_fft=512, hop_length=None, pad=0): + *other, length = x.shape + x = x.reshape(-1, length) + is_mps_xpu = x.device.type in ['mps', 'xpu'] + if is_mps_xpu: + x = x.cpu() + z = th.stft(x, + n_fft * (1 + pad), + hop_length or n_fft // 4, + window=th.hann_window(n_fft).to(x), + win_length=n_fft, + normalized=True, + center=True, + return_complex=True, + pad_mode='reflect') + _, freqs, frame = z.shape + return z.view(*other, freqs, frame) + + +def ispectro(z, hop_length=None, length=None, pad=0): + *other, freqs, frames = z.shape + n_fft = 2 * freqs - 2 + z = z.view(-1, freqs, frames) + win_length = n_fft // (1 + pad) + is_mps_xpu = z.device.type in ['mps', 'xpu'] + if is_mps_xpu: + z = z.cpu() + x = th.istft(z, + n_fft, + hop_length, + window=th.hann_window(win_length).to(z.real), + win_length=win_length, + normalized=True, + length=length, + center=True) + _, length = x.shape + return x.view(*other, length) diff --git a/demucs/states.py b/demucs/states.py new file mode 100644 index 00000000..361bb419 --- /dev/null +++ b/demucs/states.py @@ -0,0 +1,163 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +""" +Utilities to save and load models. +""" +from contextlib import contextmanager + +import functools +import hashlib +import inspect +import io +from pathlib import Path +import warnings + +from omegaconf import OmegaConf +from dora.log import fatal +import torch + + +def _check_diffq(): + try: + import diffq # noqa + except ImportError: + fatal('Trying to use DiffQ, but diffq is not installed.\n' + 'On Windows run: python.exe -m pip install diffq \n' + 'On Linux/Mac, run: python3 -m pip install diffq') + + +def get_quantizer(model, args, optimizer=None): + """Return the quantizer given the XP quantization args.""" + quantizer = None + if args.diffq: + _check_diffq() + from diffq import DiffQuantizer + quantizer = DiffQuantizer( + model, min_size=args.min_size, group_size=args.group_size) + if optimizer is not None: + quantizer.setup_optimizer(optimizer) + elif args.qat: + _check_diffq() + from diffq import UniformQuantizer + quantizer = UniformQuantizer( + model, bits=args.qat, min_size=args.min_size) + return quantizer + + +def load_model(path_or_package, strict=False): + """Load a model from the given serialized model, either given as a dict (already loaded) + or a path to a file on disk.""" + if isinstance(path_or_package, dict): + package = path_or_package + elif isinstance(path_or_package, (str, Path)): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + path = path_or_package + package = torch.load(path, 'cpu') + else: + raise ValueError(f"Invalid type for {path_or_package}.") + + klass = package["klass"] + args = package["args"] + kwargs = package["kwargs"] + + if strict: + model = klass(*args, **kwargs) + else: + sig = inspect.signature(klass) + for key in list(kwargs): + if key not in sig.parameters: + warnings.warn("Dropping inexistant parameter " + key) + del kwargs[key] + model = klass(*args, **kwargs) + + state = package["state"] + + set_state(model, state) + return model + + +def get_state(model, quantizer, half=False): + """Get the state from a model, potentially with quantization applied. + If `half` is True, model are stored as half precision, which shouldn't impact performance + but half the state size.""" + if quantizer is None: + dtype = torch.half if half else None + state = {k: p.data.to(device='cpu', dtype=dtype) for k, p in model.state_dict().items()} + else: + state = quantizer.get_quantized_state() + state['__quantized'] = True + return state + + +def set_state(model, state, quantizer=None): + """Set the state on a given model.""" + if state.get('__quantized'): + if quantizer is not None: + quantizer.restore_quantized_state(model, state['quantized']) + else: + _check_diffq() + from diffq import restore_quantized_state + restore_quantized_state(model, state) + else: + model.load_state_dict(state) + return state + + +def save_with_checksum(content, path): + """Save the given value on disk, along with a sha256 hash. + Should be used with the output of either `serialize_model` or `get_state`.""" + buf = io.BytesIO() + torch.save(content, buf) + sig = hashlib.sha256(buf.getvalue()).hexdigest()[:8] + + path = path.parent / (path.stem + "-" + sig + path.suffix) + path.write_bytes(buf.getvalue()) + + +def serialize_model(model, training_args, quantizer=None, half=True): + args, kwargs = model._init_args_kwargs + klass = model.__class__ + + state = get_state(model, quantizer, half) + return { + 'klass': klass, + 'args': args, + 'kwargs': kwargs, + 'state': state, + 'training_args': OmegaConf.to_container(training_args, resolve=True), + } + + +def copy_state(state): + return {k: v.cpu().clone() for k, v in state.items()} + + +@contextmanager +def swap_state(model, state): + """ + Context manager that swaps the state of a model, e.g: + + # model is in old state + with swap_state(model, new_state): + # model in new state + # model back to old state + """ + old_state = copy_state(model.state_dict()) + model.load_state_dict(state, strict=False) + try: + yield + finally: + model.load_state_dict(old_state) + + +def capture_init(init): + @functools.wraps(init) + def __init__(self, *args, **kwargs): + self._init_args_kwargs = (args, kwargs) + init(self, *args, **kwargs) + + return __init__ diff --git a/demucs/svd.py b/demucs/svd.py new file mode 100644 index 00000000..1cbaa82c --- /dev/null +++ b/demucs/svd.py @@ -0,0 +1,83 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Ways to make the model stronger.""" +import random +import torch + + +def power_iteration(m, niters=1, bs=1): + """This is the power method. batch size is used to try multiple starting point in parallel.""" + assert m.dim() == 2 + assert m.shape[0] == m.shape[1] + dim = m.shape[0] + b = torch.randn(dim, bs, device=m.device, dtype=m.dtype) + + for _ in range(niters): + n = m.mm(b) + norm = n.norm(dim=0, keepdim=True) + b = n / (1e-10 + norm) + + return norm.mean() + + +# We need a shared RNG to make sure all the distributed worker will skip the penalty together, +# as otherwise we wouldn't get any speed up. +penalty_rng = random.Random(1234) + + +def svd_penalty(model, min_size=0.1, dim=1, niters=2, powm=False, convtr=True, + proba=1, conv_only=False, exact=False, bs=1): + """ + Penalty on the largest singular value for a layer. + Args: + - model: model to penalize + - min_size: minimum size in MB of a layer to penalize. + - dim: projection dimension for the svd_lowrank. Higher is better but slower. + - niters: number of iterations in the algorithm used by svd_lowrank. + - powm: use power method instead of lowrank SVD, my own experience + is that it is both slower and less stable. + - convtr: when True, differentiate between Conv and Transposed Conv. + this is kept for compatibility with older experiments. + - proba: probability to apply the penalty. + - conv_only: only apply to conv and conv transposed, not LSTM + (might not be reliable for other models than Demucs). + - exact: use exact SVD (slow but useful at validation). + - bs: batch_size for power method. + """ + total = 0 + if penalty_rng.random() > proba: + return 0. + + for m in model.modules(): + for name, p in m.named_parameters(recurse=False): + if p.numel() / 2**18 < min_size: + continue + if convtr: + if isinstance(m, (torch.nn.ConvTranspose1d, torch.nn.ConvTranspose2d)): + if p.dim() in [3, 4]: + p = p.transpose(0, 1).contiguous() + if p.dim() == 3: + p = p.view(len(p), -1) + elif p.dim() == 4: + p = p.view(len(p), -1) + elif p.dim() == 1: + continue + elif conv_only: + continue + assert p.dim() == 2, (name, p.shape) + if exact: + estimate = torch.svd(p, compute_uv=False)[1].pow(2).max() + elif powm: + a, b = p.shape + if a < b: + n = p.mm(p.t()) + else: + n = p.t().mm(p) + estimate = power_iteration(n, niters, bs) + else: + estimate = torch.svd_lowrank(p, dim, niters)[1][0].pow(2) + total += estimate + return total / proba diff --git a/demucs/train.py b/demucs/train.py new file mode 100644 index 00000000..e045b83f --- /dev/null +++ b/demucs/train.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Main training script entry point""" + +import logging +import os +from pathlib import Path +import sys + +from dora import hydra_main +import hydra +from hydra.core.global_hydra import GlobalHydra +from omegaconf import OmegaConf +from . import audio_legacy +import torch +from torch import nn +import torchaudio +from torch.utils.data import ConcatDataset + +from . import distrib +from .wav import get_wav_datasets, get_musdb_wav_datasets +from .demucs import Demucs +from .hdemucs import HDemucs +from .htdemucs import HTDemucs +from .repitch import RepitchedWrapper +from .solver import Solver +from .states import capture_init +from .utils import random_subset + +logger = logging.getLogger(__name__) + + +class TorchHDemucsWrapper(nn.Module): + """Wrapper around torchaudio HDemucs implementation to provide the proper metadata + for model evaluation. + See https://pytorch.org/audio/stable/tutorials/hybrid_demucs_tutorial.html""" + + @capture_init + def __init__(self, **kwargs): + super().__init__() + try: + from torchaudio.models import HDemucs as TorchHDemucs + except ImportError: + raise ImportError("Please upgrade torchaudio for using its implementation of HDemucs") + self.samplerate = kwargs.pop('samplerate') + self.segment = kwargs.pop('segment') + self.sources = kwargs['sources'] + self.torch_hdemucs = TorchHDemucs(**kwargs) + + def forward(self, mix): + return self.torch_hdemucs.forward(mix) + + +def get_model(args): + extra = { + 'sources': list(args.dset.sources), + 'audio_channels': args.dset.channels, + 'samplerate': args.dset.samplerate, + 'segment': args.model_segment or 4 * args.dset.segment, + } + klass = { + 'demucs': Demucs, + 'hdemucs': HDemucs, + 'htdemucs': HTDemucs, + 'torch_hdemucs': TorchHDemucsWrapper, + }[args.model] + kw = OmegaConf.to_container(getattr(args, args.model), resolve=True) + model = klass(**extra, **kw) + return model + + +def get_optimizer(model, args): + seen_params = set() + other_params = [] + groups = [] + for n, module in model.named_modules(): + if hasattr(module, "make_optim_group"): + group = module.make_optim_group() + params = set(group["params"]) + assert params.isdisjoint(seen_params) + seen_params |= set(params) + groups.append(group) + for param in model.parameters(): + if param not in seen_params: + other_params.append(param) + groups.insert(0, {"params": other_params}) + parameters = groups + if args.optim.optim == "adam": + return torch.optim.Adam( + parameters, + lr=args.optim.lr, + betas=(args.optim.momentum, args.optim.beta2), + weight_decay=args.optim.weight_decay, + ) + elif args.optim.optim == "adamw": + return torch.optim.AdamW( + parameters, + lr=args.optim.lr, + betas=(args.optim.momentum, args.optim.beta2), + weight_decay=args.optim.weight_decay, + ) + else: + raise ValueError("Invalid optimizer %s", args.optim.optimizer) + + +def get_datasets(args): + if args.dset.backend: + torchaudio.set_audio_backend(args.dset.backend) + if args.dset.use_musdb: + train_set, valid_set = get_musdb_wav_datasets(args.dset) + else: + train_set, valid_set = [], [] + if args.dset.wav: + extra_train_set, extra_valid_set = get_wav_datasets(args.dset) + if len(args.dset.sources) <= 4: + train_set = ConcatDataset([train_set, extra_train_set]) + valid_set = ConcatDataset([valid_set, extra_valid_set]) + else: + train_set = extra_train_set + valid_set = extra_valid_set + + if args.dset.wav2: + extra_train_set, extra_valid_set = get_wav_datasets(args.dset, "wav2") + weight = args.dset.wav2_weight + if weight is not None: + b = len(train_set) + e = len(extra_train_set) + reps = max(1, round(e / b * (1 / weight - 1))) + else: + reps = 1 + train_set = ConcatDataset([train_set] * reps + [extra_train_set]) + if args.dset.wav2_valid: + if weight is not None: + b = len(valid_set) + n_kept = int(round(weight * b / (1 - weight))) + valid_set = ConcatDataset( + [valid_set, random_subset(extra_valid_set, n_kept)] + ) + else: + valid_set = ConcatDataset([valid_set, extra_valid_set]) + if args.dset.valid_samples is not None: + valid_set = random_subset(valid_set, args.dset.valid_samples) + assert len(train_set) + assert len(valid_set) + return train_set, valid_set + + +def get_solver(args, model_only=False): + distrib.init() + + torch.manual_seed(args.seed) + model = get_model(args) + if args.misc.show: + logger.info(model) + mb = sum(p.numel() for p in model.parameters()) * 4 / 2**20 + logger.info('Size: %.1f MB', mb) + if hasattr(model, 'valid_length'): + field = model.valid_length(1) + logger.info('Field: %.1f ms', field / args.dset.samplerate * 1000) + sys.exit(0) + + # torch also initialize cuda seed if available + if torch.cuda.is_available(): + model.cuda() + + # optimizer + optimizer = get_optimizer(model, args) + + assert args.batch_size % distrib.world_size == 0 + args.batch_size //= distrib.world_size + + if model_only: + return Solver(None, model, optimizer, args) + + train_set, valid_set = get_datasets(args) + + if args.augment.repitch.proba: + vocals = [] + if 'vocals' in args.dset.sources: + vocals.append(args.dset.sources.index('vocals')) + else: + logger.warning('No vocal source found') + if args.augment.repitch.proba: + train_set = RepitchedWrapper(train_set, vocals=vocals, **args.augment.repitch) + + logger.info("train/valid set size: %d %d", len(train_set), len(valid_set)) + train_loader = distrib.loader( + train_set, batch_size=args.batch_size, shuffle=True, + num_workers=args.misc.num_workers, drop_last=True) + if args.dset.full_cv: + valid_loader = distrib.loader( + valid_set, batch_size=1, shuffle=False, + num_workers=args.misc.num_workers) + else: + valid_loader = distrib.loader( + valid_set, batch_size=args.batch_size, shuffle=False, + num_workers=args.misc.num_workers, drop_last=True) + loaders = {"train": train_loader, "valid": valid_loader} + + # Construct Solver + return Solver(loaders, model, optimizer, args) + + +def get_solver_from_sig(sig, model_only=False): + inst = GlobalHydra.instance() + hyd = None + if inst.is_initialized(): + hyd = inst.hydra + inst.clear() + xp = main.get_xp_from_sig(sig) + if hyd is not None: + inst.clear() + inst.initialize(hyd) + + with xp.enter(stack=True): + return get_solver(xp.cfg, model_only) + + +@hydra_main(config_path="../conf", config_name="config", version_base="1.1") +def main(args): + global __file__ + __file__ = hydra.utils.to_absolute_path(__file__) + for attr in ["musdb", "wav", "metadata"]: + val = getattr(args.dset, attr) + if val is not None: + setattr(args.dset, attr, hydra.utils.to_absolute_path(val)) + + os.environ["OMP_NUM_THREADS"] = "1" + os.environ["MKL_NUM_THREADS"] = "1" + + if args.misc.verbose: + logger.setLevel(logging.DEBUG) + + logger.info("For logs, checkpoints and samples check %s", os.getcwd()) + logger.debug(args) + from dora import get_xp + logger.debug(get_xp().cfg) + + solver = get_solver(args) + solver.train() + + +if '_DORA_TEST_PATH' in os.environ: + main.dora.dir = Path(os.environ['_DORA_TEST_PATH']) + + +if __name__ == "__main__": + main() diff --git a/demucs/transformer.py b/demucs/transformer.py new file mode 100644 index 00000000..56a465b8 --- /dev/null +++ b/demucs/transformer.py @@ -0,0 +1,839 @@ +# Copyright (c) 2019-present, Meta, Inc. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +# First author is Simon Rouard. + +import random +import typing as tp + +import torch +import torch.nn as nn +import torch.nn.functional as F +import numpy as np +import math +from einops import rearrange + + +def create_sin_embedding( + length: int, dim: int, shift: int = 0, device="cpu", max_period=10000 +): + # We aim for TBC format + assert dim % 2 == 0 + pos = shift + torch.arange(length, device=device).view(-1, 1, 1) + half_dim = dim // 2 + adim = torch.arange(dim // 2, device=device).view(1, 1, -1) + phase = pos / (max_period ** (adim / (half_dim - 1))) + return torch.cat( + [ + torch.cos(phase), + torch.sin(phase), + ], + dim=-1, + ) + + +def create_2d_sin_embedding(d_model, height, width, device="cpu", max_period=10000): + """ + :param d_model: dimension of the model + :param height: height of the positions + :param width: width of the positions + :return: d_model*height*width position matrix + """ + if d_model % 4 != 0: + raise ValueError( + "Cannot use sin/cos positional encoding with " + "odd dimension (got dim={:d})".format(d_model) + ) + pe = torch.zeros(d_model, height, width) + # Each dimension use half of d_model + d_model = int(d_model / 2) + div_term = torch.exp( + torch.arange(0.0, d_model, 2) * -(math.log(max_period) / d_model) + ) + pos_w = torch.arange(0.0, width).unsqueeze(1) + pos_h = torch.arange(0.0, height).unsqueeze(1) + pe[0:d_model:2, :, :] = ( + torch.sin(pos_w * div_term).transpose(0, 1).unsqueeze(1).repeat(1, height, 1) + ) + pe[1:d_model:2, :, :] = ( + torch.cos(pos_w * div_term).transpose(0, 1).unsqueeze(1).repeat(1, height, 1) + ) + pe[d_model::2, :, :] = ( + torch.sin(pos_h * div_term).transpose(0, 1).unsqueeze(2).repeat(1, 1, width) + ) + pe[d_model + 1:: 2, :, :] = ( + torch.cos(pos_h * div_term).transpose(0, 1).unsqueeze(2).repeat(1, 1, width) + ) + + return pe[None, :].to(device) + + +def create_sin_embedding_cape( + length: int, + dim: int, + batch_size: int, + mean_normalize: bool, + augment: bool, # True during training + max_global_shift: float = 0.0, # delta max + max_local_shift: float = 0.0, # epsilon max + max_scale: float = 1.0, + device: str = "cpu", + max_period: float = 10000.0, +): + # We aim for TBC format + assert dim % 2 == 0 + pos = 1.0 * torch.arange(length).view(-1, 1, 1) # (length, 1, 1) + pos = pos.repeat(1, batch_size, 1) # (length, batch_size, 1) + if mean_normalize: + pos -= torch.nanmean(pos, dim=0, keepdim=True) + + if augment: + delta = np.random.uniform( + -max_global_shift, +max_global_shift, size=[1, batch_size, 1] + ) + delta_local = np.random.uniform( + -max_local_shift, +max_local_shift, size=[length, batch_size, 1] + ) + log_lambdas = np.random.uniform( + -np.log(max_scale), +np.log(max_scale), size=[1, batch_size, 1] + ) + pos = (pos + delta + delta_local) * np.exp(log_lambdas) + + pos = pos.to(device) + + half_dim = dim // 2 + adim = torch.arange(dim // 2, device=device).view(1, 1, -1) + phase = pos / (max_period ** (adim / (half_dim - 1))) + return torch.cat( + [ + torch.cos(phase), + torch.sin(phase), + ], + dim=-1, + ).float() + + +def get_causal_mask(length): + pos = torch.arange(length) + return pos > pos[:, None] + + +def get_elementary_mask( + T1, + T2, + mask_type, + sparse_attn_window, + global_window, + mask_random_seed, + sparsity, + device, +): + """ + When the input of the Decoder has length T1 and the output T2 + The mask matrix has shape (T2, T1) + """ + assert mask_type in ["diag", "jmask", "random", "global"] + + if mask_type == "global": + mask = torch.zeros(T2, T1, dtype=torch.bool) + mask[:, :global_window] = True + line_window = int(global_window * T2 / T1) + mask[:line_window, :] = True + + if mask_type == "diag": + + mask = torch.zeros(T2, T1, dtype=torch.bool) + rows = torch.arange(T2)[:, None] + cols = ( + (T1 / T2 * rows + torch.arange(-sparse_attn_window, sparse_attn_window + 1)) + .long() + .clamp(0, T1 - 1) + ) + mask.scatter_(1, cols, torch.ones(1, dtype=torch.bool).expand_as(cols)) + + elif mask_type == "jmask": + mask = torch.zeros(T2 + 2, T1 + 2, dtype=torch.bool) + rows = torch.arange(T2 + 2)[:, None] + t = torch.arange(0, int((2 * T1) ** 0.5 + 1)) + t = (t * (t + 1) / 2).int() + t = torch.cat([-t.flip(0)[:-1], t]) + cols = (T1 / T2 * rows + t).long().clamp(0, T1 + 1) + mask.scatter_(1, cols, torch.ones(1, dtype=torch.bool).expand_as(cols)) + mask = mask[1:-1, 1:-1] + + elif mask_type == "random": + gene = torch.Generator(device=device) + gene.manual_seed(mask_random_seed) + mask = ( + torch.rand(T1 * T2, generator=gene, device=device).reshape(T2, T1) + > sparsity + ) + + mask = mask.to(device) + return mask + + +def get_mask( + T1, + T2, + mask_type, + sparse_attn_window, + global_window, + mask_random_seed, + sparsity, + device, +): + """ + Return a SparseCSRTensor mask that is a combination of elementary masks + mask_type can be a combination of multiple masks: for instance "diag_jmask_random" + """ + from xformers.sparse import SparseCSRTensor + # create a list + mask_types = mask_type.split("_") + + all_masks = [ + get_elementary_mask( + T1, + T2, + mask, + sparse_attn_window, + global_window, + mask_random_seed, + sparsity, + device, + ) + for mask in mask_types + ] + + final_mask = torch.stack(all_masks).sum(axis=0) > 0 + + return SparseCSRTensor.from_dense(final_mask[None]) + + +class ScaledEmbedding(nn.Module): + def __init__( + self, + num_embeddings: int, + embedding_dim: int, + scale: float = 1.0, + boost: float = 3.0, + ): + super().__init__() + self.embedding = nn.Embedding(num_embeddings, embedding_dim) + self.embedding.weight.data *= scale / boost + self.boost = boost + + @property + def weight(self): + return self.embedding.weight * self.boost + + def forward(self, x): + return self.embedding(x) * self.boost + + +class LayerScale(nn.Module): + """Layer scale from [Touvron et al 2021] (https://arxiv.org/pdf/2103.17239.pdf). + This rescales diagonaly residual outputs close to 0 initially, then learnt. + """ + + def __init__(self, channels: int, init: float = 0, channel_last=False): + """ + channel_last = False corresponds to (B, C, T) tensors + channel_last = True corresponds to (T, B, C) tensors + """ + super().__init__() + self.channel_last = channel_last + self.scale = nn.Parameter(torch.zeros(channels, requires_grad=True)) + self.scale.data[:] = init + + def forward(self, x): + if self.channel_last: + return self.scale * x + else: + return self.scale[:, None] * x + + +class MyGroupNorm(nn.GroupNorm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def forward(self, x): + """ + x: (B, T, C) + if num_groups=1: Normalisation on all T and C together for each B + """ + x = x.transpose(1, 2) + return super().forward(x).transpose(1, 2) + + +class MyTransformerEncoderLayer(nn.TransformerEncoderLayer): + def __init__( + self, + d_model, + nhead, + dim_feedforward=2048, + dropout=0.1, + activation=F.relu, + group_norm=0, + norm_first=False, + norm_out=False, + layer_norm_eps=1e-5, + layer_scale=False, + init_values=1e-4, + device=None, + dtype=None, + sparse=False, + mask_type="diag", + mask_random_seed=42, + sparse_attn_window=500, + global_window=50, + auto_sparsity=False, + sparsity=0.95, + batch_first=False, + ): + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__( + d_model=d_model, + nhead=nhead, + dim_feedforward=dim_feedforward, + dropout=dropout, + activation=activation, + layer_norm_eps=layer_norm_eps, + batch_first=batch_first, + norm_first=norm_first, + device=device, + dtype=dtype, + ) + self.sparse = sparse + self.auto_sparsity = auto_sparsity + if sparse: + if not auto_sparsity: + self.mask_type = mask_type + self.sparse_attn_window = sparse_attn_window + self.global_window = global_window + self.sparsity = sparsity + if group_norm: + self.norm1 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) + self.norm2 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) + + self.norm_out = None + if self.norm_first & norm_out: + self.norm_out = MyGroupNorm(num_groups=int(norm_out), num_channels=d_model) + self.gamma_1 = ( + LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() + ) + self.gamma_2 = ( + LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() + ) + + if sparse: + self.self_attn = MultiheadAttention( + d_model, nhead, dropout=dropout, batch_first=batch_first, + auto_sparsity=sparsity if auto_sparsity else 0, + ) + self.__setattr__("src_mask", torch.zeros(1, 1)) + self.mask_random_seed = mask_random_seed + + def forward(self, src, src_mask=None, src_key_padding_mask=None): + """ + if batch_first = False, src shape is (T, B, C) + the case where batch_first=True is not covered + """ + device = src.device + x = src + T, B, C = x.shape + if self.sparse and not self.auto_sparsity: + assert src_mask is None + src_mask = self.src_mask + if src_mask.shape[-1] != T: + src_mask = get_mask( + T, + T, + self.mask_type, + self.sparse_attn_window, + self.global_window, + self.mask_random_seed, + self.sparsity, + device, + ) + self.__setattr__("src_mask", src_mask) + + if self.norm_first: + x = x + self.gamma_1( + self._sa_block(self.norm1(x), src_mask, src_key_padding_mask) + ) + x = x + self.gamma_2(self._ff_block(self.norm2(x))) + + if self.norm_out: + x = self.norm_out(x) + else: + x = self.norm1( + x + self.gamma_1(self._sa_block(x, src_mask, src_key_padding_mask)) + ) + x = self.norm2(x + self.gamma_2(self._ff_block(x))) + + return x + + +class CrossTransformerEncoderLayer(nn.Module): + def __init__( + self, + d_model: int, + nhead: int, + dim_feedforward: int = 2048, + dropout: float = 0.1, + activation=F.relu, + layer_norm_eps: float = 1e-5, + layer_scale: bool = False, + init_values: float = 1e-4, + norm_first: bool = False, + group_norm: bool = False, + norm_out: bool = False, + sparse=False, + mask_type="diag", + mask_random_seed=42, + sparse_attn_window=500, + global_window=50, + sparsity=0.95, + auto_sparsity=None, + device=None, + dtype=None, + batch_first=False, + ): + factory_kwargs = {"device": device, "dtype": dtype} + super().__init__() + + self.sparse = sparse + self.auto_sparsity = auto_sparsity + if sparse: + if not auto_sparsity: + self.mask_type = mask_type + self.sparse_attn_window = sparse_attn_window + self.global_window = global_window + self.sparsity = sparsity + + self.cross_attn: nn.Module + self.cross_attn = nn.MultiheadAttention( + d_model, nhead, dropout=dropout, batch_first=batch_first) + # Implementation of Feedforward model + self.linear1 = nn.Linear(d_model, dim_feedforward, **factory_kwargs) + self.dropout = nn.Dropout(dropout) + self.linear2 = nn.Linear(dim_feedforward, d_model, **factory_kwargs) + + self.norm_first = norm_first + self.norm1: nn.Module + self.norm2: nn.Module + self.norm3: nn.Module + if group_norm: + self.norm1 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) + self.norm2 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) + self.norm3 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) + else: + self.norm1 = nn.LayerNorm(d_model, eps=layer_norm_eps, **factory_kwargs) + self.norm2 = nn.LayerNorm(d_model, eps=layer_norm_eps, **factory_kwargs) + self.norm3 = nn.LayerNorm(d_model, eps=layer_norm_eps, **factory_kwargs) + + self.norm_out = None + if self.norm_first & norm_out: + self.norm_out = MyGroupNorm(num_groups=int(norm_out), num_channels=d_model) + + self.gamma_1 = ( + LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() + ) + self.gamma_2 = ( + LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() + ) + + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + + # Legacy string support for activation function. + if isinstance(activation, str): + self.activation = self._get_activation_fn(activation) + else: + self.activation = activation + + if sparse: + self.cross_attn = MultiheadAttention( + d_model, nhead, dropout=dropout, batch_first=batch_first, + auto_sparsity=sparsity if auto_sparsity else 0) + if not auto_sparsity: + self.__setattr__("mask", torch.zeros(1, 1)) + self.mask_random_seed = mask_random_seed + + def forward(self, q, k, mask=None): + """ + Args: + q: tensor of shape (T, B, C) + k: tensor of shape (S, B, C) + mask: tensor of shape (T, S) + + """ + device = q.device + T, B, C = q.shape + S, B, C = k.shape + if self.sparse and not self.auto_sparsity: + assert mask is None + mask = self.mask + if mask.shape[-1] != S or mask.shape[-2] != T: + mask = get_mask( + S, + T, + self.mask_type, + self.sparse_attn_window, + self.global_window, + self.mask_random_seed, + self.sparsity, + device, + ) + self.__setattr__("mask", mask) + + if self.norm_first: + x = q + self.gamma_1(self._ca_block(self.norm1(q), self.norm2(k), mask)) + x = x + self.gamma_2(self._ff_block(self.norm3(x))) + if self.norm_out: + x = self.norm_out(x) + else: + x = self.norm1(q + self.gamma_1(self._ca_block(q, k, mask))) + x = self.norm2(x + self.gamma_2(self._ff_block(x))) + + return x + + # self-attention block + def _ca_block(self, q, k, attn_mask=None): + x = self.cross_attn(q, k, k, attn_mask=attn_mask, need_weights=False)[0] + return self.dropout1(x) + + # feed forward block + def _ff_block(self, x): + x = self.linear2(self.dropout(self.activation(self.linear1(x)))) + return self.dropout2(x) + + def _get_activation_fn(self, activation): + if activation == "relu": + return F.relu + elif activation == "gelu": + return F.gelu + + raise RuntimeError("activation should be relu/gelu, not {}".format(activation)) + + +# ----------------- MULTI-BLOCKS MODELS: ----------------------- + + +class CrossTransformerEncoder(nn.Module): + def __init__( + self, + dim: int, + emb: str = "sin", + hidden_scale: float = 4.0, + num_heads: int = 8, + num_layers: int = 6, + cross_first: bool = False, + dropout: float = 0.0, + max_positions: int = 1000, + norm_in: bool = True, + norm_in_group: bool = False, + group_norm: int = False, + norm_first: bool = False, + norm_out: bool = False, + max_period: float = 10000.0, + weight_decay: float = 0.0, + lr: tp.Optional[float] = None, + layer_scale: bool = False, + gelu: bool = True, + sin_random_shift: int = 0, + weight_pos_embed: float = 1.0, + cape_mean_normalize: bool = True, + cape_augment: bool = True, + cape_glob_loc_scale: list = [5000.0, 1.0, 1.4], + sparse_self_attn: bool = False, + sparse_cross_attn: bool = False, + mask_type: str = "diag", + mask_random_seed: int = 42, + sparse_attn_window: int = 500, + global_window: int = 50, + auto_sparsity: bool = False, + sparsity: float = 0.95, + ): + super().__init__() + """ + """ + assert dim % num_heads == 0 + + hidden_dim = int(dim * hidden_scale) + + self.num_layers = num_layers + # classic parity = 1 means that if idx%2 == 1 there is a + # classical encoder else there is a cross encoder + self.classic_parity = 1 if cross_first else 0 + self.emb = emb + self.max_period = max_period + self.weight_decay = weight_decay + self.weight_pos_embed = weight_pos_embed + self.sin_random_shift = sin_random_shift + if emb == "cape": + self.cape_mean_normalize = cape_mean_normalize + self.cape_augment = cape_augment + self.cape_glob_loc_scale = cape_glob_loc_scale + if emb == "scaled": + self.position_embeddings = ScaledEmbedding(max_positions, dim, scale=0.2) + + self.lr = lr + + activation: tp.Any = F.gelu if gelu else F.relu + + self.norm_in: nn.Module + self.norm_in_t: nn.Module + if norm_in: + self.norm_in = nn.LayerNorm(dim) + self.norm_in_t = nn.LayerNorm(dim) + elif norm_in_group: + self.norm_in = MyGroupNorm(int(norm_in_group), dim) + self.norm_in_t = MyGroupNorm(int(norm_in_group), dim) + else: + self.norm_in = nn.Identity() + self.norm_in_t = nn.Identity() + + # spectrogram layers + self.layers = nn.ModuleList() + # temporal layers + self.layers_t = nn.ModuleList() + + kwargs_common = { + "d_model": dim, + "nhead": num_heads, + "dim_feedforward": hidden_dim, + "dropout": dropout, + "activation": activation, + "group_norm": group_norm, + "norm_first": norm_first, + "norm_out": norm_out, + "layer_scale": layer_scale, + "mask_type": mask_type, + "mask_random_seed": mask_random_seed, + "sparse_attn_window": sparse_attn_window, + "global_window": global_window, + "sparsity": sparsity, + "auto_sparsity": auto_sparsity, + "batch_first": True, + } + + kwargs_classic_encoder = dict(kwargs_common) + kwargs_classic_encoder.update({ + "sparse": sparse_self_attn, + }) + kwargs_cross_encoder = dict(kwargs_common) + kwargs_cross_encoder.update({ + "sparse": sparse_cross_attn, + }) + + for idx in range(num_layers): + if idx % 2 == self.classic_parity: + + self.layers.append(MyTransformerEncoderLayer(**kwargs_classic_encoder)) + self.layers_t.append( + MyTransformerEncoderLayer(**kwargs_classic_encoder) + ) + + else: + self.layers.append(CrossTransformerEncoderLayer(**kwargs_cross_encoder)) + + self.layers_t.append( + CrossTransformerEncoderLayer(**kwargs_cross_encoder) + ) + + def forward(self, x, xt): + B, C, Fr, T1 = x.shape + pos_emb_2d = create_2d_sin_embedding( + C, Fr, T1, x.device, self.max_period + ) # (1, C, Fr, T1) + pos_emb_2d = rearrange(pos_emb_2d, "b c fr t1 -> b (t1 fr) c") + x = rearrange(x, "b c fr t1 -> b (t1 fr) c") + x = self.norm_in(x) + x = x + self.weight_pos_embed * pos_emb_2d + + B, C, T2 = xt.shape + xt = rearrange(xt, "b c t2 -> b t2 c") # now T2, B, C + pos_emb = self._get_pos_embedding(T2, B, C, x.device) + pos_emb = rearrange(pos_emb, "t2 b c -> b t2 c") + xt = self.norm_in_t(xt) + xt = xt + self.weight_pos_embed * pos_emb + + for idx in range(self.num_layers): + if idx % 2 == self.classic_parity: + x = self.layers[idx](x) + xt = self.layers_t[idx](xt) + else: + old_x = x + x = self.layers[idx](x, xt) + xt = self.layers_t[idx](xt, old_x) + + x = rearrange(x, "b (t1 fr) c -> b c fr t1", t1=T1) + xt = rearrange(xt, "b t2 c -> b c t2") + return x, xt + + def _get_pos_embedding(self, T, B, C, device): + if self.emb == "sin": + shift = random.randrange(self.sin_random_shift + 1) + pos_emb = create_sin_embedding( + T, C, shift=shift, device=device, max_period=self.max_period + ) + elif self.emb == "cape": + if self.training: + pos_emb = create_sin_embedding_cape( + T, + C, + B, + device=device, + max_period=self.max_period, + mean_normalize=self.cape_mean_normalize, + augment=self.cape_augment, + max_global_shift=self.cape_glob_loc_scale[0], + max_local_shift=self.cape_glob_loc_scale[1], + max_scale=self.cape_glob_loc_scale[2], + ) + else: + pos_emb = create_sin_embedding_cape( + T, + C, + B, + device=device, + max_period=self.max_period, + mean_normalize=self.cape_mean_normalize, + augment=False, + ) + + elif self.emb == "scaled": + pos = torch.arange(T, device=device) + pos_emb = self.position_embeddings(pos)[:, None] + + return pos_emb + + def make_optim_group(self): + group = {"params": list(self.parameters()), "weight_decay": self.weight_decay} + if self.lr is not None: + group["lr"] = self.lr + return group + + +# Attention Modules + + +class MultiheadAttention(nn.Module): + def __init__( + self, + embed_dim, + num_heads, + dropout=0.0, + bias=True, + add_bias_kv=False, + add_zero_attn=False, + kdim=None, + vdim=None, + batch_first=False, + auto_sparsity=None, + ): + super().__init__() + assert auto_sparsity is not None, "sanity check" + self.num_heads = num_heads + self.q = torch.nn.Linear(embed_dim, embed_dim, bias=bias) + self.k = torch.nn.Linear(embed_dim, embed_dim, bias=bias) + self.v = torch.nn.Linear(embed_dim, embed_dim, bias=bias) + self.attn_drop = torch.nn.Dropout(dropout) + self.proj = torch.nn.Linear(embed_dim, embed_dim, bias) + self.proj_drop = torch.nn.Dropout(dropout) + self.batch_first = batch_first + self.auto_sparsity = auto_sparsity + + def forward( + self, + query, + key, + value, + key_padding_mask=None, + need_weights=True, + attn_mask=None, + average_attn_weights=True, + ): + + if not self.batch_first: # N, B, C + query = query.permute(1, 0, 2) # B, N_q, C + key = key.permute(1, 0, 2) # B, N_k, C + value = value.permute(1, 0, 2) # B, N_k, C + B, N_q, C = query.shape + B, N_k, C = key.shape + + q = ( + self.q(query) + .reshape(B, N_q, self.num_heads, C // self.num_heads) + .permute(0, 2, 1, 3) + ) + q = q.flatten(0, 1) + k = ( + self.k(key) + .reshape(B, N_k, self.num_heads, C // self.num_heads) + .permute(0, 2, 1, 3) + ) + k = k.flatten(0, 1) + v = ( + self.v(value) + .reshape(B, N_k, self.num_heads, C // self.num_heads) + .permute(0, 2, 1, 3) + ) + v = v.flatten(0, 1) + + if self.auto_sparsity: + assert attn_mask is None + x = dynamic_sparse_attention(q, k, v, sparsity=self.auto_sparsity) + else: + x = scaled_dot_product_attention(q, k, v, attn_mask, dropout=self.attn_drop) + x = x.reshape(B, self.num_heads, N_q, C // self.num_heads) + + x = x.transpose(1, 2).reshape(B, N_q, C) + x = self.proj(x) + x = self.proj_drop(x) + if not self.batch_first: + x = x.permute(1, 0, 2) + return x, None + + +def scaled_query_key_softmax(q, k, att_mask): + from xformers.ops import masked_matmul + q = q / (k.size(-1)) ** 0.5 + att = masked_matmul(q, k.transpose(-2, -1), att_mask) + att = torch.nn.functional.softmax(att, -1) + return att + + +def scaled_dot_product_attention(q, k, v, att_mask, dropout): + att = scaled_query_key_softmax(q, k, att_mask=att_mask) + att = dropout(att) + y = att @ v + return y + + +def _compute_buckets(x, R): + qq = torch.einsum('btf,bfhi->bhti', x, R) + qq = torch.cat([qq, -qq], dim=-1) + buckets = qq.argmax(dim=-1) + + return buckets.permute(0, 2, 1).byte().contiguous() + + +def dynamic_sparse_attention(query, key, value, sparsity, infer_sparsity=True, attn_bias=None): + # assert False, "The code for the custom sparse kernel is not ready for release yet." + from xformers.ops import find_locations, sparse_memory_efficient_attention + n_hashes = 32 + proj_size = 4 + query, key, value = [x.contiguous() for x in [query, key, value]] + with torch.no_grad(): + R = torch.randn(1, query.shape[-1], n_hashes, proj_size // 2, device=query.device) + bucket_query = _compute_buckets(query, R) + bucket_key = _compute_buckets(key, R) + row_offsets, column_indices = find_locations( + bucket_query, bucket_key, sparsity, infer_sparsity) + return sparse_memory_efficient_attention( + query, key, value, row_offsets, column_indices, attn_bias) diff --git a/demucs/utils.py b/demucs/utils.py new file mode 100755 index 00000000..a3f5993e --- /dev/null +++ b/demucs/utils.py @@ -0,0 +1,149 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from collections import defaultdict +from concurrent.futures import CancelledError +from contextlib import contextmanager +import math +import os +import tempfile +import typing as tp + +import torch +from torch.nn import functional as F +from torch.utils.data import Subset + + +def unfold(a, kernel_size, stride): + """Given input of size [*OT, T], output Tensor of size [*OT, F, K] + with K the kernel size, by extracting frames with the given stride. + + This will pad the input so that `F = ceil(T / K)`. + + see https://github.com/pytorch/pytorch/issues/60466 + """ + *shape, length = a.shape + n_frames = math.ceil(length / stride) + tgt_length = (n_frames - 1) * stride + kernel_size + a = F.pad(a, (0, tgt_length - length)) + strides = list(a.stride()) + assert strides[-1] == 1, 'data should be contiguous' + strides = strides[:-1] + [stride, 1] + return a.as_strided([*shape, n_frames, kernel_size], strides) + + +def center_trim(tensor: torch.Tensor, reference: tp.Union[torch.Tensor, int]): + """ + Center trim `tensor` with respect to `reference`, along the last dimension. + `reference` can also be a number, representing the length to trim to. + If the size difference != 0 mod 2, the extra sample is removed on the right side. + """ + ref_size: int + if isinstance(reference, torch.Tensor): + ref_size = reference.size(-1) + else: + ref_size = reference + delta = tensor.size(-1) - ref_size + if delta < 0: + raise ValueError("tensor must be larger than reference. " f"Delta is {delta}.") + if delta: + tensor = tensor[..., delta // 2:-(delta - delta // 2)] + return tensor + + +def pull_metric(history: tp.List[dict], name: str): + out = [] + for metrics in history: + metric = metrics + for part in name.split("."): + metric = metric[part] + out.append(metric) + return out + + +def EMA(beta: float = 1): + """ + Exponential Moving Average callback. + Returns a single function that can be called to repeatidly update the EMA + with a dict of metrics. The callback will return + the new averaged dict of metrics. + + Note that for `beta=1`, this is just plain averaging. + """ + fix: tp.Dict[str, float] = defaultdict(float) + total: tp.Dict[str, float] = defaultdict(float) + + def _update(metrics: dict, weight: float = 1) -> dict: + nonlocal total, fix + for key, value in metrics.items(): + total[key] = total[key] * beta + weight * float(value) + fix[key] = fix[key] * beta + weight + return {key: tot / fix[key] for key, tot in total.items()} + return _update + + +def sizeof_fmt(num: float, suffix: str = 'B'): + """ + Given `num` bytes, return human readable size. + Taken from https://stackoverflow.com/a/1094933 + """ + for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, 'Yi', suffix) + + +@contextmanager +def temp_filenames(count: int, delete=True): + names = [] + try: + for _ in range(count): + names.append(tempfile.NamedTemporaryFile(delete=False).name) + yield names + finally: + if delete: + for name in names: + os.unlink(name) + + +def random_subset(dataset, max_samples: int, seed: int = 42): + if max_samples >= len(dataset): + return dataset + + generator = torch.Generator().manual_seed(seed) + perm = torch.randperm(len(dataset), generator=generator) + return Subset(dataset, perm[:max_samples].tolist()) + + +class DummyPoolExecutor: + class DummyResult: + def __init__(self, func, _dict, *args, **kwargs): + self.func = func + self._dict = _dict + self.args = args + self.kwargs = kwargs + + def result(self): + if self._dict["run"]: + return self.func(*self.args, **self.kwargs) + else: + raise CancelledError() + + def __init__(self, workers=0): + self._dict = {"run": True} + + def submit(self, func, *args, **kwargs): + return DummyPoolExecutor.DummyResult(func, self._dict, *args, **kwargs) + + def shutdown(self, *_, **__): + self._dict["run"] = False + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + return diff --git a/demucs/wav.py b/demucs/wav.py new file mode 100644 index 00000000..ca1e23a3 --- /dev/null +++ b/demucs/wav.py @@ -0,0 +1,255 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +"""Loading wav based datasets, including MusdbHQ.""" + +from collections import OrderedDict +import hashlib +import math +import json +import os +from pathlib import Path +import tqdm + +import musdb +import julius +from . import audio_legacy +import torch as th +from torch import distributed +import torchaudio as ta +from torch.nn import functional as F + +from .audio import convert_audio_channels +from . import distrib + +MIXTURE = "mixture" +EXT = ".wav" + + +def _track_metadata(track, sources, normalize=True, ext=EXT): + track_length = None + track_samplerate = None + mean = 0 + std = 1 + for source in sources + [MIXTURE]: + file = track / f"{source}{ext}" + if source == MIXTURE and not file.exists(): + audio = 0 + for sub_source in sources: + sub_file = track / f"{sub_source}{ext}" + sub_audio, sr = ta.load(sub_file) + audio += sub_audio + would_clip = audio.abs().max() >= 1 + if would_clip: + assert ta.get_audio_backend() == 'soundfile', 'use dset.backend=soundfile' + ta.save(file, audio, sr, encoding='PCM_F') + + try: + info = ta.info(str(file)) + except RuntimeError: + print(file) + raise + length = info.num_frames + if track_length is None: + track_length = length + track_samplerate = info.sample_rate + elif track_length != length: + raise ValueError( + f"Invalid length for file {file}: " + f"expecting {track_length} but got {length}.") + elif info.sample_rate != track_samplerate: + raise ValueError( + f"Invalid sample rate for file {file}: " + f"expecting {track_samplerate} but got {info.sample_rate}.") + if source == MIXTURE and normalize: + try: + wav, _ = ta.load(str(file)) + except RuntimeError: + print(file) + raise + wav = wav.mean(0) + mean = wav.mean().item() + std = wav.std().item() + + return {"length": length, "mean": mean, "std": std, "samplerate": track_samplerate} + + +def build_metadata(path, sources, normalize=True, ext=EXT): + """ + Build the metadata for `Wavset`. + + Args: + path (str or Path): path to dataset. + sources (list[str]): list of sources to look for. + normalize (bool): if True, loads full track and store normalization + values based on the mixture file. + ext (str): extension of audio files (default is .wav). + """ + + meta = {} + path = Path(path) + pendings = [] + from concurrent.futures import ThreadPoolExecutor + with ThreadPoolExecutor(8) as pool: + for root, folders, files in os.walk(path, followlinks=True): + root = Path(root) + if root.name.startswith('.') or folders or root == path: + continue + name = str(root.relative_to(path)) + pendings.append((name, pool.submit(_track_metadata, root, sources, normalize, ext))) + # meta[name] = _track_metadata(root, sources, normalize, ext) + for name, pending in tqdm.tqdm(pendings, ncols=120): + meta[name] = pending.result() + return meta + + +class Wavset: + def __init__( + self, + root, metadata, sources, + segment=None, shift=None, normalize=True, + samplerate=44100, channels=2, ext=EXT): + """ + Waveset (or mp3 set for that matter). Can be used to train + with arbitrary sources. Each track should be one folder inside of `path`. + The folder should contain files named `{source}.{ext}`. + + Args: + root (Path or str): root folder for the dataset. + metadata (dict): output from `build_metadata`. + sources (list[str]): list of source names. + segment (None or float): segment length in seconds. If `None`, returns entire tracks. + shift (None or float): stride in seconds bewteen samples. + normalize (bool): normalizes input audio, **based on the metadata content**, + i.e. the entire track is normalized, not individual extracts. + samplerate (int): target sample rate. if the file sample rate + is different, it will be resampled on the fly. + channels (int): target nb of channels. if different, will be + changed onthe fly. + ext (str): extension for audio files (default is .wav). + + samplerate and channels are converted on the fly. + """ + self.root = Path(root) + self.metadata = OrderedDict(metadata) + self.segment = segment + self.shift = shift or segment + self.normalize = normalize + self.sources = sources + self.channels = channels + self.samplerate = samplerate + self.ext = ext + self.num_examples = [] + for name, meta in self.metadata.items(): + track_duration = meta['length'] / meta['samplerate'] + if segment is None or track_duration < segment: + examples = 1 + else: + examples = int(math.ceil((track_duration - self.segment) / self.shift) + 1) + self.num_examples.append(examples) + + def __len__(self): + return sum(self.num_examples) + + def get_file(self, name, source): + return self.root / name / f"{source}{self.ext}" + + def __getitem__(self, index): + for name, examples in zip(self.metadata, self.num_examples): + if index >= examples: + index -= examples + continue + meta = self.metadata[name] + num_frames = -1 + offset = 0 + if self.segment is not None: + offset = int(meta['samplerate'] * self.shift * index) + num_frames = int(math.ceil(meta['samplerate'] * self.segment)) + wavs = [] + for source in self.sources: + file = self.get_file(name, source) + wav, _ = ta.load(str(file), frame_offset=offset, num_frames=num_frames) + wav = convert_audio_channels(wav, self.channels) + wavs.append(wav) + + example = th.stack(wavs) + example = julius.resample_frac(example, meta['samplerate'], self.samplerate) + if self.normalize: + example = (example - meta['mean']) / meta['std'] + if self.segment: + length = int(self.segment * self.samplerate) + example = example[..., :length] + example = F.pad(example, (0, length - example.shape[-1])) + return example + + +def get_wav_datasets(args, name='wav'): + """Extract the wav datasets from the XP arguments.""" + path = getattr(args, name) + sig = hashlib.sha1(str(path).encode()).hexdigest()[:8] + metadata_file = Path(args.metadata) / ('wav_' + sig + ".json") + train_path = Path(path) / "train" + valid_path = Path(path) / "valid" + if not metadata_file.is_file() and distrib.rank == 0: + metadata_file.parent.mkdir(exist_ok=True, parents=True) + train = build_metadata(train_path, args.sources) + valid = build_metadata(valid_path, args.sources) + json.dump([train, valid], open(metadata_file, "w")) + if distrib.world_size > 1: + distributed.barrier() + train, valid = json.load(open(metadata_file)) + if args.full_cv: + kw_cv = {} + else: + kw_cv = {'segment': args.segment, 'shift': args.shift} + train_set = Wavset(train_path, train, args.sources, + segment=args.segment, shift=args.shift, + samplerate=args.samplerate, channels=args.channels, + normalize=args.normalize) + valid_set = Wavset(valid_path, valid, [MIXTURE] + list(args.sources), + samplerate=args.samplerate, channels=args.channels, + normalize=args.normalize, **kw_cv) + return train_set, valid_set + + +def _get_musdb_valid(): + # Return musdb valid set. + import yaml + setup_path = Path(musdb.__path__[0]) / 'configs' / 'mus.yaml' + setup = yaml.safe_load(open(setup_path, 'r')) + return setup['validation_tracks'] + + +def get_musdb_wav_datasets(args): + """Extract the musdb dataset from the XP arguments.""" + sig = hashlib.sha1(str(args.musdb).encode()).hexdigest()[:8] + metadata_file = Path(args.metadata) / ('musdb_' + sig + ".json") + root = Path(args.musdb) / "train" + if not metadata_file.is_file() and distrib.rank == 0: + metadata_file.parent.mkdir(exist_ok=True, parents=True) + metadata = build_metadata(root, args.sources) + json.dump(metadata, open(metadata_file, "w")) + if distrib.world_size > 1: + distributed.barrier() + metadata = json.load(open(metadata_file)) + + valid_tracks = _get_musdb_valid() + if args.train_valid: + metadata_train = metadata + else: + metadata_train = {name: meta for name, meta in metadata.items() if name not in valid_tracks} + metadata_valid = {name: meta for name, meta in metadata.items() if name in valid_tracks} + if args.full_cv: + kw_cv = {} + else: + kw_cv = {'segment': args.segment, 'shift': args.shift} + train_set = Wavset(root, metadata_train, args.sources, + segment=args.segment, shift=args.shift, + samplerate=args.samplerate, channels=args.channels, + normalize=args.normalize) + valid_set = Wavset(root, metadata_valid, [MIXTURE] + list(args.sources), + samplerate=args.samplerate, channels=args.channels, + normalize=args.normalize, **kw_cv) + return train_set, valid_set diff --git a/demucs/wdemucs.py b/demucs/wdemucs.py new file mode 100644 index 00000000..03d6dd3b --- /dev/null +++ b/demucs/wdemucs.py @@ -0,0 +1,9 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +# For compat +from .hdemucs import HDemucs + +WDemucs = HDemucs diff --git a/hatch_build.py b/hatch_build.py index b7267b85..b3469d36 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -66,6 +66,19 @@ class CustomBuildHook(BuildHookInterface): print(result.stderr, file=sys.stderr) print("Successfully built whisper.cpp binaries") + # Run the make command for translation files + result = subprocess.run( + ["make", "translation_mo"], + cwd=project_root, + check=True, + capture_output=True, + text=True + ) + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + print("Successfully compiled translation files") + # Force include all files in buzz/whisper_cpp directory whisper_cpp_dir = project_root / "buzz" / "whisper_cpp" if whisper_cpp_dir.exists(): @@ -88,6 +101,47 @@ class CustomBuildHook(BuildHookInterface): else: print(f"Warning: {whisper_cpp_dir} does not exist after build", file=sys.stderr) + # Force include all files in demucs directory + demucs_dir = project_root / "demucs" + if demucs_dir.exists(): + # Get all files in the demucs directory + demucs_files = glob.glob(str(demucs_dir / "**" / "*"), recursive=True) + + # Filter only files (not directories) + demucs_files = [f for f in demucs_files if Path(f).is_file()] + + # Add them to force_include + if 'force_include' not in build_data: + build_data['force_include'] = {} + + for file_path in demucs_files: + # Convert to relative path from project root + rel_path = Path(file_path).relative_to(project_root) + build_data['force_include'][str(rel_path)] = str(rel_path) + + print(f"Force including {len(demucs_files)} files from demucs/") + else: + print(f"Warning: {demucs_dir} does not exist", file=sys.stderr) + + # Force include all .mo files from buzz/locale directory + locale_dir = project_root / "buzz" / "locale" + if locale_dir.exists(): + # Get all .mo files in the locale directory + locale_files = glob.glob(str(locale_dir / "**" / "*.mo"), recursive=True) + + # Add them to force_include + if 'force_include' not in build_data: + build_data['force_include'] = {} + + for file_path in locale_files: + # Convert to relative path from project root + rel_path = Path(file_path).relative_to(project_root) + build_data['force_include'][str(rel_path)] = str(rel_path) + + print(f"Force including {len(locale_files)} .mo files from buzz/locale/") + else: + print(f"Warning: {locale_dir} does not exist", file=sys.stderr) + except subprocess.CalledProcessError as e: print(f"Error building whisper.cpp: {e}", file=sys.stderr) print(f"stdout: {e.stdout}", file=sys.stderr) diff --git a/pyproject.toml b/pyproject.toml index 144be849..01894149 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "buzz-captions" -version = "1.3.2" +version = "1.3.3" description = "" authors = [{ name = "Chidi Williams", email = "williamschidi1@gmail.com" }] requires-python = ">=3.12,<3.13" @@ -56,7 +56,6 @@ dependencies = [ "treetable>=0.2.5,<0.3", "soundfile>=0.13.1,<0.14", "urllib3>=2.3.0,<3", - "demucs @ https://github.com/raivisdejus/demucs/releases/download/4.1.0a3/demucs-4.1.0a3-py3-none-any.whl", "posthog>=3.23.0,<4", "onnxruntime==1.18.1", "vulkan>=1.3.275.1,<2", @@ -131,6 +130,7 @@ include = [ "buzz", "buzz/whisper_cpp/*", "buzz/locale/*/LC_MESSAGES/buzz.mo", + "demucs", ] [tool.hatch.build.targets.wheel] @@ -138,6 +138,7 @@ include = [ "buzz", "buzz/whisper_cpp/*", "buzz/locale/*/LC_MESSAGES/buzz.mo", + "demucs", ] [tool.hatch.build.hooks.custom] diff --git a/pytest.ini b/pytest.ini index b1ef248a..abd57212 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,7 +4,7 @@ log_cli_level = DEBUG qt_api=pyqt6 log_format = %(asctime)s %(levelname)s %(module)s::%(funcName)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S -addopts = -x +addopts = -x -p no:xdist -p no:pytest_parallel timeout = 600 timeout_method = thread markers = diff --git a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml index f03483ac..5faf4bcc 100644 --- a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml +++ b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml @@ -64,8 +64,8 @@ - - https://github.com/chidiwilliams/buzz/releases/tag/v1.3.2 + + https://github.com/chidiwilliams/buzz/releases/tag/v1.3.3

This release introduces Vulkan GPU support for whisper.cpp making it significantly faster even on laptops. Real-time transcription is possible even with large models on computers with ~5GB RAM video cards. There @@ -77,7 +77,7 @@

  • Option to switch the UI language from preferences
  • Library updates for better Linux compatibility, especially in Flatpak installations
  • Option to upload live transcripts to a server
  • -
  • Search and additional controls in Transcription viewer by [@shlomi-dr](https://github.com/shlomi-dr)
  • +
  • Search and additional controls in Transcription viewer
  • Added UI translation for German, Dutch, Danish and Portuguese (Brazilian)
  • Minor bug fixes
  • diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index bb7ae97b..8017d213 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -119,8 +119,8 @@ parts: uv cache clean # Copy source files - mkdir -p $CRAFT_PART_INSTALL/buzz cp -r $CRAFT_PART_BUILD/buzz $CRAFT_PART_INSTALL/ + cp -r $CRAFT_PART_BUILD/demucs $CRAFT_PART_INSTALL/ # Create desktop file mkdir -p $CRAFT_PART_INSTALL/usr/share/applications diff --git a/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py b/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py index 8d34460c..c007caf4 100644 --- a/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py +++ b/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py @@ -778,23 +778,24 @@ class TestTranscriptionViewerWidgetAdditional: widget.close() - def test_run_translation(self, qtbot: QtBot, transcription, transcription_service, shortcuts): - """Test run_translation method""" - widget = TranscriptionViewerWidget( - transcription, transcription_service, shortcuts - ) - qtbot.add_widget(widget) - - # Set required options - widget.transcription_options.llm_model = "gpt-4" - widget.transcription_options.llm_prompt = "Translate" - - widget.run_translation() - - # Should enqueue translation tasks - assert hasattr(widget, 'run_translation') - - widget.close() + # Skipped as it seems it is sending actual requests and maybe failing on CI + # def test_run_translation(self, qtbot: QtBot, transcription, transcription_service, shortcuts): + # """Test run_translation method""" + # widget = TranscriptionViewerWidget( + # transcription, transcription_service, shortcuts + # ) + # qtbot.add_widget(widget) + # + # # Set required options + # widget.transcription_options.llm_model = "gpt-4" + # widget.transcription_options.llm_prompt = "Translate" + # + # widget.run_translation() + # + # # Should enqueue translation tasks + # assert hasattr(widget, 'run_translation') + # + # widget.close() def test_restore_ui_state(self, qtbot: QtBot, transcription, transcription_service, shortcuts): """Test restore_ui_state method""" diff --git a/uv.lock b/uv.lock index f09bc661..0c326aa8 100644 --- a/uv.lock +++ b/uv.lock @@ -128,7 +128,7 @@ wheels = [ [[package]] name = "buzz-captions" -version = "1.3.2" +version = "1.3.3" source = { editable = "." } dependencies = [ { name = "accelerate" }, @@ -137,7 +137,6 @@ dependencies = [ { name = "ctranslate2", version = "4.6.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' or sys_platform != 'darwin'" }, { name = "darkdetect" }, { name = "dataclasses-json" }, - { name = "demucs" }, { name = "diffq" }, { name = "dora-search" }, { name = "einops" }, @@ -218,7 +217,6 @@ requires-dist = [ { name = "ctranslate2", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==4.3.1" }, { name = "darkdetect", specifier = ">=0.8.0,<0.9" }, { name = "dataclasses-json", specifier = ">=0.6.4,<0.7" }, - { name = "demucs", url = "https://github.com/raivisdejus/demucs/releases/download/4.1.0a3/demucs-4.1.0a3-py3-none-any.whl" }, { name = "diffq", specifier = ">=0.2.4,<0.3" }, { name = "dora-search", specifier = ">=0.1.12,<0.2" }, { name = "einops", specifier = ">=0.8.1,<0.9" }, @@ -566,62 +564,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, ] -[[package]] -name = "demucs" -version = "4.1.0a3" -source = { url = "https://github.com/raivisdejus/demucs/releases/download/4.1.0a3/demucs-4.1.0a3-py3-none-any.whl" } -dependencies = [ - { name = "dora-search" }, - { name = "einops" }, - { name = "julius" }, - { name = "lameenc" }, - { name = "openunmix" }, - { name = "pyyaml" }, - { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, - { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "tqdm" }, -] -wheels = [ - { url = "https://github.com/raivisdejus/demucs/releases/download/4.1.0a3/demucs-4.1.0a3-py3-none-any.whl", hash = "sha256:3c52712c0b6022f7e26a00b0cfb4e4ed04ed9994f78f06cfa485dc7006cbef60" }, -] - -[package.metadata] -requires-dist = [ - { name = "diffq", marker = "extra == 'dev'", specifier = ">=0.2.1" }, - { name = "dora-search" }, - { name = "dora-search", marker = "extra == 'dev'", specifier = ">=0.1.12" }, - { name = "einops" }, - { name = "einops", marker = "extra == 'dev'" }, - { name = "flake8", marker = "extra == 'dev'" }, - { name = "hydra-colorlog", marker = "extra == 'dev'", specifier = ">=1.1" }, - { name = "hydra-core", marker = "extra == 'dev'", specifier = ">=1.1" }, - { name = "julius", specifier = ">=0.2.3" }, - { name = "julius", marker = "extra == 'dev'", specifier = ">=0.2.3" }, - { name = "lameenc", specifier = ">=1.2" }, - { name = "lameenc", marker = "extra == 'dev'", specifier = ">=1.2" }, - { name = "museval", marker = "extra == 'dev'" }, - { name = "mypy", marker = "extra == 'dev'" }, - { name = "openunmix" }, - { name = "openunmix", marker = "extra == 'dev'" }, - { name = "pyyaml" }, - { name = "pyyaml", marker = "extra == 'dev'" }, - { name = "soundfile", marker = "extra == 'dev'", specifier = ">=0.10.3" }, - { name = "submitit", marker = "extra == 'dev'" }, - { name = "torch", specifier = ">=1.8.1" }, - { name = "torch", marker = "extra == 'dev'", specifier = ">=1.8.1" }, - { name = "torchaudio", specifier = ">=0.8" }, - { name = "torchaudio", marker = "extra == 'dev'", specifier = ">=0.8" }, - { name = "tqdm" }, - { name = "tqdm", marker = "extra == 'dev'" }, - { name = "treetable", marker = "extra == 'dev'" }, -] -provides-extras = ["dev"] - [[package]] name = "diffq" version = "0.2.4" From ccdeb09ac9030d74a768dece17af049ae0b32f37 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sun, 9 Nov 2025 11:52:20 +0200 Subject: [PATCH 03/73] Fix for translator test (#1280) --- .../local_whisper_cpp_server_transcriber.py | 3 +- .../openai_whisper_api_file_transcriber.py | 3 +- buzz/transcriber/recording_transcriber.py | 3 +- buzz/translator.py | 27 ++++++++------ .../general_preferences_widget.py | 2 +- .../transcription_viewer_widget.py | 11 ++++-- tests/translator_test.py | 17 +++++---- ...scription_viewer_widget_additional_test.py | 36 +++++++++---------- 8 files changed, 58 insertions(+), 44 deletions(-) diff --git a/buzz/transcriber/local_whisper_cpp_server_transcriber.py b/buzz/transcriber/local_whisper_cpp_server_transcriber.py index c58553d9..d57252fe 100644 --- a/buzz/transcriber/local_whisper_cpp_server_transcriber.py +++ b/buzz/transcriber/local_whisper_cpp_server_transcriber.py @@ -64,7 +64,8 @@ class LocalWhisperCppServerTranscriber(OpenAIWhisperAPIFileTranscriber): self.openai_client = OpenAI( api_key="not-used", - base_url="http://127.0.0.1:3000" + base_url="http://127.0.0.1:3000", + max_retries=0 ) def transcribe(self) -> List[Segment]: diff --git a/buzz/transcriber/openai_whisper_api_file_transcriber.py b/buzz/transcriber/openai_whisper_api_file_transcriber.py index 21a6652f..b2f02898 100644 --- a/buzz/transcriber/openai_whisper_api_file_transcriber.py +++ b/buzz/transcriber/openai_whisper_api_file_transcriber.py @@ -46,7 +46,8 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber): self.task = task.transcription_options.task self.openai_client = OpenAI( api_key=self.transcription_task.transcription_options.openai_access_token, - base_url=custom_openai_base_url if custom_openai_base_url else None + base_url=custom_openai_base_url if custom_openai_base_url else None, + max_retries=0 ) self.whisper_api_model = get_custom_api_whisper_model(custom_openai_base_url) self.word_level_timings = self.transcription_task.transcription_options.word_level_timings diff --git a/buzz/transcriber/recording_transcriber.py b/buzz/transcriber/recording_transcriber.py index 8e5cc3d1..7867e50e 100644 --- a/buzz/transcriber/recording_transcriber.py +++ b/buzz/transcriber/recording_transcriber.py @@ -126,7 +126,8 @@ class RecordingTranscriber(QObject): self.whisper_api_model = get_custom_api_whisper_model(custom_openai_base_url) self.openai_client = OpenAI( api_key=self.transcription_options.openai_access_token, - base_url=custom_openai_base_url if custom_openai_base_url else None + base_url=custom_openai_base_url if custom_openai_base_url else None, + max_retries=0 ) logging.debug("Will use whisper API on %s, %s", custom_openai_base_url, self.whisper_api_model) diff --git a/buzz/translator.py b/buzz/translator.py index 56a816ea..ffeecf7b 100644 --- a/buzz/translator.py +++ b/buzz/translator.py @@ -3,7 +3,7 @@ import logging import queue from typing import Optional -from openai import OpenAI +from openai import OpenAI, max_retries from PyQt6.QtCore import QObject, pyqtSignal from buzz.settings.settings import Settings @@ -15,7 +15,6 @@ from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDi class Translator(QObject): translation = pyqtSignal(str, int) finished = pyqtSignal() - is_running = False def __init__( self, @@ -48,19 +47,22 @@ class Translator(QObject): ) self.openai_client = OpenAI( api_key=openai_api_key, - base_url=custom_openai_base_url if custom_openai_base_url else None + base_url=custom_openai_base_url if custom_openai_base_url else None, + max_retries=0 ) def start(self): logging.debug("Starting translation queue") - self.is_running = True + while True: + item = self.queue.get() # Block until item available - while self.is_running: - try: - transcript, transcript_id = self.queue.get(timeout=1) - except queue.Empty: - continue + # Check for sentinel value (None means stop) + if item is None: + logging.debug("Translation queue received stop signal") + break + + transcript, transcript_id = item try: completion = self.openai_client.chat.completions.create( @@ -69,7 +71,8 @@ class Translator(QObject): {"role": "system", "content": self.transcription_options.llm_prompt}, {"role": "user", "content": transcript} ], - timeout=30.0 + timeout=30.0, + ) except Exception as e: completion = None @@ -84,6 +87,7 @@ class Translator(QObject): self.translation.emit(next_translation, transcript_id) + logging.debug("Translation queue stopped") self.finished.emit() def on_transcription_options_changed( @@ -95,4 +99,5 @@ class Translator(QObject): self.queue.put((transcript, transcript_id)) def stop(self): - self.is_running = False + # Send sentinel value to unblock and stop the worker thread + self.queue.put(None) diff --git a/buzz/widgets/preferences_dialog/general_preferences_widget.py b/buzz/widgets/preferences_dialog/general_preferences_widget.py index 5cefcdaa..b7bdfc74 100644 --- a/buzz/widgets/preferences_dialog/general_preferences_widget.py +++ b/buzz/widgets/preferences_dialog/general_preferences_widget.py @@ -328,7 +328,7 @@ class ValidateOpenAIApiKeyJob(QRunnable): client = OpenAI( api_key=self.api_key, base_url=custom_openai_base_url if custom_openai_base_url else None, - timeout=5, + timeout=15, ) client.models.list() self.signals.success.emit() diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index bf4400b3..e77c2179 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -1351,8 +1351,15 @@ class TranscriptionViewerWidget(QWidget): # Only wait if thread is actually running if self.translation_thread.isRunning(): - if not self.translation_thread.wait(45_000): - logging.warning("Translation thread did not finish within timeout") + # Wait up to 35 seconds for graceful shutdown + # (30s max API call timeout + 5s buffer) + if not self.translation_thread.wait(35_000): + logging.warning("Translation thread did not finish gracefully, terminating") + # Force terminate the thread if it doesn't stop + self.translation_thread.terminate() + # Give it a brief moment to terminate + if not self.translation_thread.wait(1_000): + logging.error("Translation thread could not be terminated") super().closeEvent(event) diff --git a/tests/translator_test.py b/tests/translator_test.py index 6c0f87d6..c9b4d8e3 100644 --- a/tests/translator_test.py +++ b/tests/translator_test.py @@ -15,14 +15,12 @@ class TestTranslator: @patch('buzz.translator.queue.Queue', autospec=True) def test_start(self, mock_queue, mock_openai, qtbot): def side_effect(*args, **kwargs): - side_effect.call_count += 1 + if side_effect.call_count <= 1: + side_effect.call_count += 1 + return ("Hello, how are you?", 1) - if side_effect.call_count >= 5: - translator.is_running = False - - if side_effect.call_count < 3: - raise Empty - return "Hello, how are you?", None + # Finally return sentinel to stop + return None side_effect.call_count = 0 @@ -51,6 +49,8 @@ class TestTranslator: mock_queue.get.assert_called() mock_chat.completions.create.assert_called() + translator.stop() + @patch('buzz.translator.OpenAI', autospec=True) def test_translator(self, mock_openai, qtbot): @@ -94,8 +94,7 @@ class TestTranslator: self.translation_thread.start() - time.sleep(3) - assert self.translator.is_running + time.sleep(1) # Give thread time to start self.translator.enqueue("Hello, how are you?") diff --git a/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py b/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py index c007caf4..9e716e7a 100644 --- a/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py +++ b/tests/widgets/transcription_viewer/transcription_viewer_widget_additional_test.py @@ -778,24 +778,24 @@ class TestTranscriptionViewerWidgetAdditional: widget.close() - # Skipped as it seems it is sending actual requests and maybe failing on CI - # def test_run_translation(self, qtbot: QtBot, transcription, transcription_service, shortcuts): - # """Test run_translation method""" - # widget = TranscriptionViewerWidget( - # transcription, transcription_service, shortcuts - # ) - # qtbot.add_widget(widget) - # - # # Set required options - # widget.transcription_options.llm_model = "gpt-4" - # widget.transcription_options.llm_prompt = "Translate" - # - # widget.run_translation() - # - # # Should enqueue translation tasks - # assert hasattr(widget, 'run_translation') - # - # widget.close() + # TODO - it is sending actual requests, should mock + def test_run_translation(self, qtbot: QtBot, transcription, transcription_service, shortcuts): + """Test run_translation method""" + widget = TranscriptionViewerWidget( + transcription, transcription_service, shortcuts + ) + qtbot.add_widget(widget) + + # Set required options + widget.transcription_options.llm_model = "gpt-4" + widget.transcription_options.llm_prompt = "Translate" + + widget.run_translation() + + # Should enqueue translation tasks + assert hasattr(widget, 'run_translation') + + widget.close() def test_restore_ui_state(self, qtbot: QtBot, transcription, transcription_service, shortcuts): """Test restore_ui_state method""" From 070d9f17d576716d98e1945425bb7414011d14a4 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sun, 9 Nov 2025 21:57:39 +0200 Subject: [PATCH 04/73] Documentation adjustments (#1281) --- .github/workflows/ci.yml | 73 ++++++++++--------- README.md | 48 ++++++------ docs/docs/faq.md | 2 +- .../io.github.chidiwilliams.Buzz.metainfo.xml | 2 +- 4 files changed, 60 insertions(+), 65 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbfa02f0..010e183a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -357,40 +357,41 @@ jobs: with: files: | Buzz*-unix.tar.gz - Buzz*-windows.exe - Buzz*-windows-*.bin - Buzz*-mac.dmg + Buzz*.exe + Buzz*.bin + Buzz*.dmg - deploy_brew_cask: - runs-on: macos-latest - env: - BUZZ_DISABLE_TELEMETRY: true - needs: [release] - if: startsWith(github.ref, 'refs/tags/') - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - # Should be removed with next update to whisper.cpp - - name: Downgrade Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '16.0.0' - if: matrix.os == 'macos-latest' - - - name: Install uv - uses: astral-sh/setup-uv@v6 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install dependencies - run: uv sync - - - name: Upload to Brew - run: uv run make upload_brew - env: - HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} +# Brew Cask deployment fails and the app is deprecated on Brew. +# deploy_brew_cask: +# runs-on: macos-latest +# env: +# BUZZ_DISABLE_TELEMETRY: true +# needs: [release] +# if: startsWith(github.ref, 'refs/tags/') +# steps: +# - uses: actions/checkout@v4 +# with: +# submodules: recursive +# +# # Should be removed with next update to whisper.cpp +# - name: Downgrade Xcode +# uses: maxim-lobanov/setup-xcode@v1 +# with: +# xcode-version: '16.0.0' +# if: matrix.os == 'macos-latest' +# +# - name: Install uv +# uses: astral-sh/setup-uv@v6 +# +# - name: Set up Python +# uses: actions/setup-python@v5 +# with: +# python-version: "3.12" +# +# - name: Install dependencies +# run: uv sync +# +# - name: Upload to Brew +# run: uv run make upload_brew +# env: +# HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} diff --git a/README.md b/README.md index 173d25e4..55c62f9d 100644 --- a/README.md +++ b/README.md @@ -22,26 +22,9 @@ OpenAI's [Whisper](https://github.com/openai/whisper). ## Installation -### PyPI - -Install [ffmpeg](https://www.ffmpeg.org/download.html) - -Install Buzz - -```shell -pip install buzz-captions -python -m buzz -``` - ### macOS -Install with [brew utility](https://brew.sh/) - -```shell -brew install --cask buzz -``` - -Or download the `.dmg` from the [SourceForge](https://sourceforge.net/projects/buzz-captions/files/). +Download the `.dmg` from the [SourceForge](https://sourceforge.net/projects/buzz-captions/files/). ### Windows @@ -55,15 +38,6 @@ App is not signed, you will get a warning when you install it. Select `More info winget install ChidiWilliams.Buzz ``` -**GPU support for PyPI** - -To have GPU support for Nvidia GPUS on Windows, for PyPI installed version ensure, CUDA support for [torch](https://pytorch.org/get-started/locally/) - -``` -pip3 install -U torch==2.7.1+cu128 torchaudio==2.7.1+cu128 --index-url https://download.pytorch.org/whl/cu128 -pip3 install nvidia-cublas-cu12==12.8.3.14 nvidia-cuda-cupti-cu12==12.8.57 nvidia-cuda-nvrtc-cu12==12.8.61 nvidia-cuda-runtime-cu12==12.8.57 nvidia-cudnn-cu12==9.7.1.26 nvidia-cufft-cu12==11.3.3.41 nvidia-curand-cu12==10.3.9.55 nvidia-cusolver-cu12==11.7.2.55 nvidia-cusparse-cu12==12.5.4.2 nvidia-cusparselt-cu12==0.6.3 nvidia-nvjitlink-cu12==12.8.61 nvidia-nvtx-cu12==12.8.55 --extra-index-url https://pypi.ngc.nvidia.com -``` - ### Linux Buzz is available as a [Flatpak](https://flathub.org/apps/io.github.chidiwilliams.Buzz) or a [Snap](https://snapcraft.io/buzz). @@ -80,6 +54,26 @@ sudo snap install buzz sudo snap connect buzz:password-manager-service ``` +### PyPI + +Install [ffmpeg](https://www.ffmpeg.org/download.html) + +Install Buzz + +```shell +pip install buzz-captions +python -m buzz +``` + +**GPU support for PyPI** + +To have GPU support for Nvidia GPUS on Windows, for PyPI installed version ensure, CUDA support for [torch](https://pytorch.org/get-started/locally/) + +``` +pip3 install -U torch==2.7.1+cu128 torchaudio==2.7.1+cu128 --index-url https://download.pytorch.org/whl/cu128 +pip3 install nvidia-cublas-cu12==12.8.3.14 nvidia-cuda-cupti-cu12==12.8.57 nvidia-cuda-nvrtc-cu12==12.8.61 nvidia-cuda-runtime-cu12==12.8.57 nvidia-cudnn-cu12==9.7.1.26 nvidia-cufft-cu12==11.3.3.41 nvidia-curand-cu12==10.3.9.55 nvidia-cusolver-cu12==11.7.2.55 nvidia-cusparse-cu12==12.5.4.2 nvidia-cusparselt-cu12==0.6.3 nvidia-nvjitlink-cu12==12.8.61 nvidia-nvtx-cu12==12.8.55 --extra-index-url https://pypi.ngc.nvidia.com +``` + ### Latest development version For info on how to get latest development version with latest features and bug fixes see [FAQ](https://chidiwilliams.github.io/buzz/docs/faq#9-where-can-i-get-latest-development-version). diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 10d74409..4de7f377 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -84,7 +84,7 @@ gsettings set org.gnome.desktop.interface color-scheme prefer-dark If your system theme is not applied to Buzz installed from Flatpak Linux app store, ensure the desired theme is in `~/.themes` folder. -You may need to copy the system themes to this folder `cp -r /usr/share/themes/ ~/.themes/`. +You may need to copy the system themes to this folder `cp -r /usr/share/themes/ ~/.themes/` and give Flatpaks access to this folder `flatpak override --user --filesystem=~/.themes`. On Fedora run the following to install the necessary packages `sudo dnf install gnome-themes-extra qadwaitadecorations-qt{5,6} qt{5,6}-qtwayland` \ No newline at end of file diff --git a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml index 5faf4bcc..d65251fd 100644 --- a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml +++ b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml @@ -16,7 +16,7 @@ Required permissions in Buzz will let you select audio and video files for transcription, from most common file location on your computer. Network permission is used to download transcription model files. Microphone permission lets you transcribe real time speech.

    - Note: If your system theme is not applied to Buzz, ensure it is in ~/.themes folder. You may need to copy the system themes to this folder cp -r /usr/share/themes/ ~/.themes/. + Note: If your system theme is not applied to Buzz, ensure it is in ~/.themes folder. You may need to copy the system themes to this folder cp -r /usr/share/themes/ ~/.themes/ and give Flatpaks access to this folder flatpak override --user --filesystem=~/.themes.

    From 629fa9f1f7c10bef09f608aef6c911dfb8b930fb Mon Sep 17 00:00:00 2001 From: albanobattistella <34811668+albanobattistella@users.noreply.github.com> Date: Sun, 9 Nov 2025 21:36:33 +0100 Subject: [PATCH 05/73] Update buzz.po (#1282) --- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 87 ++++++++++++++------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index 2159b4b4..b1206756 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-10-12 19:10+0300\n" -"PO-Revision-Date: 2025-05-30 15:22+0100\n" +"PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" "MIME-Version: 1.0\n" @@ -46,7 +46,7 @@ msgstr "URL:" #: buzz/widgets/import_url_dialog.py:44 msgid "Invalid URL" -msgstr "URL non valido" +msgstr "URL non valido" #: buzz/widgets/import_url_dialog.py:44 msgid "The URL you entered is invalid." @@ -107,8 +107,7 @@ msgid "Polish" msgstr "Polacco" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:45 -#, fuzzy -msgid "Portuguese (Brazil)" +msgid "Portuguese" msgstr "Portoghese" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:46 @@ -172,15 +171,15 @@ msgstr "Modalità di registrazione in diretta" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 msgid "Use only CPU and disable GPU acceleration" -msgstr "" +msgstr "Utilizza solo la CPU e disattiva l'accelerazione GPU" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" -msgstr "" +msgstr "Imposta questa opzione se i modelli più grandi non si adattano alla memoria della tua GPU e Buzz si blocca" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 msgid "Disable GPU" -msgstr "" +msgstr "Disabilita GPU" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 #: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 @@ -392,6 +391,8 @@ msgid "" "Enter instructions for AI on how to translate, for example 'Please translate " "each text sent to you from English to Spanish.'" msgstr "" +Inserisci le istruzioni per l'IA su come tradurre, ad esempio 'Per favore, traduci " +"ogni testo che ti viene inviato dall'inglese allo spagnolo.'" #: buzz/widgets/transcriber/advanced_settings_dialog.py:92 msgid "Instructions for AI:" @@ -562,86 +563,88 @@ msgstr "Ridimensionare" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 msgid "Find" -msgstr "" +msgstr "Trova" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 msgid "Show/Hide Search Bar (Ctrl+F)" -msgstr "" +msgstr "Mostra/Nascondi barra di ricerca (Ctrl+F)" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find:" -msgstr "" +msgstr "Trova:" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Enter text to find..." -msgstr "" +msgstr "Inserisci il testo per trovare..." #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 msgid "Previous match (Shift+Enter)" -msgstr "" +msgstr "Corrispondenza precedente (Maiusc+Invio)" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 msgid "Next match (Enter)" -msgstr "" +msgstr "Prossima corrispondenza (Invio)" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 msgid "Clear" -msgstr "" +msgstr "Elimina" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 msgid "Playback Controls:" -msgstr "" +msgstr "Controlli di riproduzione:" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 msgid "Loop Segment" -msgstr "" +msgstr "Ciclo di segmento" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 msgid "Enable/disable looping when clicking on transcript segments" -msgstr "" +msgstr "Abilita/disabilita il loop quando si fa clic sui segmenti della trascrizione" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 msgid "Follow Audio" -msgstr "" +msgstr "Segui Audio" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" +"Abilita/disabilita la lettura della posizione audio corrente nella trascrizione. Quando " +"abilitato, scorre automaticamente fino al testo corrente." #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Scroll to Current" -msgstr "" +msgstr "Scorri fino al Corrente" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 msgid "Scroll to the currently spoken text" -msgstr "" +msgstr "Scorrere fino al testo attualmente pronunciato" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 msgid "1 of 100+ matches" -msgstr "" +msgstr "1 di 100+ corrispondenze" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 msgid "1 of " -msgstr "" +msgstr "1 di" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 msgid " matches" -msgstr "" +msgstr "corrispondenze" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 msgid "No matches found" -msgstr "" +msgstr "Nessuna corrispondenza trovata" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 msgid " of 100+ matches" -msgstr "" +msgstr " di oltre 100 corrispondenze" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 msgid " of " -msgstr "" +msgstr " di " #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 msgid "API Key Required" @@ -761,7 +764,7 @@ msgstr "Impossibile salvare la chiave API OpenAI nel portachiavi" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 #: buzz/transcriber/recording_transcriber.py:394 msgid "Whisper server failed to start. Check logs for details." -msgstr "" +msgstr "Impossibile avviare il server Whisper. Controllare i log per i dettagli." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 #: buzz/transcriber/recording_transcriber.py:398 @@ -770,11 +773,13 @@ msgid "" "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " "variable." msgstr "" +"Impossibile avviare il server Whisper a causa di memoria insufficiente. Riprovare " +"con un modello più piccolo. Per forzare la modalità CPU, utilizzare la variabile d'ambiente " +"BUZZ_FORCE_CPU=TRUE" #: buzz/transcriber/transcriber.py:24 -#, fuzzy msgid "Translate to English" -msgstr "Impostazioni di traduzione" +msgstr "Traduci in inglese" #: buzz/transcriber/transcriber.py:25 msgid "Transcribe" @@ -1142,12 +1147,11 @@ msgstr "Si è verificato un errore di connessione" #: buzz/transcriber/recording_transcriber.py:332 msgid "Starting Whisper.cpp..." -msgstr "" +msgstr "Avvio di Whisper.cpp..." #: buzz/transcriber/recording_transcriber.py:385 -#, fuzzy msgid "Starting transcription..." -msgstr "Annulla trascrizione" +msgstr "Inizio trascrizione..." #: buzz/settings/shortcut.py:17 msgid "Open Record Window" @@ -1174,41 +1178,40 @@ msgid "View Transcript Timestamps" msgstr "Visualizza i timestamp della trascrizione" #: buzz/settings/shortcut.py:25 -#, fuzzy msgid "Search Transcript" -msgstr "Apri trascrizione" +msgstr "Cerca trascrizione" #: buzz/settings/shortcut.py:26 msgid "Scroll to Current Text" -msgstr "" +msgstr "Scorri fino al testo corrente" #: buzz/settings/shortcut.py:27 msgid "Play/Pause Audio" -msgstr "" +msgstr "Riproduci/Pausa audio" #: buzz/settings/shortcut.py:28 msgid "Replay Current Segment" -msgstr "" +msgstr "Riproduci il segmento corrente" #: buzz/settings/shortcut.py:29 msgid "Toggle Playback Controls" -msgstr "" +msgstr "Attiva/disattiva i controlli di riproduzione" #: buzz/settings/shortcut.py:31 msgid "Decrease Segment Start Time" -msgstr "" +msgstr "Riduci l'ora di inizio del segmento" #: buzz/settings/shortcut.py:32 msgid "Increase Segment Start Time" -msgstr "" +msgstr "Aumenta l'ora di inizio del segmento" #: buzz/settings/shortcut.py:33 msgid "Decrease Segment End Time" -msgstr "" +msgstr "Diminuisci l'ora di fine del segmento" #: buzz/settings/shortcut.py:34 msgid "Increase Segment End Time" -msgstr "" +msgstr "Aumenta l'ora di fine del segmento" #: buzz/settings/recording_transcriber_mode.py:5 msgid "Append below" From 93559530abcd95393cbe52b478598c93dfc5d1b0 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Mon, 17 Nov 2025 22:53:06 +0200 Subject: [PATCH 06/73] Adjusting flatpak meta (#1285) --- CONTRIBUTING.md | 3 +- io.github.chidiwilliams.Buzz.yml | 90 ------------------- share/icons/io.github.chidiwilliams.Buzz.svg | 37 ++++---- .../io.github.chidiwilliams.Buzz.metainfo.xml | 2 +- 4 files changed, 25 insertions(+), 107 deletions(-) delete mode 100644 io.github.chidiwilliams.Buzz.yml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea9fb22e..43df6166 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,8 @@ What version of the Buzz are you using? On what OS? What are steps to reproduce **Logs** Log files contain valuable information about what the Buzz was doing before the issue occurred. You can get the logs like this: -* Mac and Linux run the app from the terminal and check the output. +* Linux run the app from the terminal and check the output. +* Mac get logs from `~/Library/Logs/Buzz`. * Windows paste this into the Windows Explorer address bar `%USERPROFILE%\AppData\Local\Buzz\Buzz\Logs` and check the logs file. **Test on latest version** diff --git a/io.github.chidiwilliams.Buzz.yml b/io.github.chidiwilliams.Buzz.yml deleted file mode 100644 index 10536b23..00000000 --- a/io.github.chidiwilliams.Buzz.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Building notes: -# See https://docs.flathub.org/docs/for-app-authors/submission/ -# This flatpak is build from the snap package. -# - Get relevant snap package infor - curl -H 'Snap-Device-Series: 16' http://api.snapcraft.io/v2/snaps/info/buzz # | jq -# - Download snap and generate sha256sum, update yaml entry. - -app-id: io.github.chidiwilliams.Buzz -runtime: org.freedesktop.Platform -# TODO - Update to 24.08 when snap is updated to core24 -runtime-version: '22.08' # To match `core22` of the snap -sdk: org.freedesktop.Sdk -command: run-buzz.sh -finish-args: - - --socket=wayland - - --socket=fallback-x11 - - --socket=pulseaudio - - --talk-name=org.freedesktop.secrets - - --device=dri - # TODO switch 'all' to input when it is widely available - #- --device=input - - --device=all - - --share=network - - --share=ipc - - --filesystem=xdg-documents - # Environment variables - - --env=LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/lib/python3.10/site-packages/nvidia/cudnn/lib:/app/lib/python3.10/site-packages/PyQt6:/app/lib/python3.10/site-packages/PyQt6/Qt6/lib:/app/usr/lib/x86_64-linux-gnu/lapack:/app/usr/lib/x86_64-linux-gnu/blas:/app/usr/lib/x86_64-linux-gnu/pulseaudio:/app/usr/lib/x86_64-linux-gnu:/app/lib/x86_64-linux-gnu/ - - --env=PYTHONPATH=$PYTHONPATH:/app/lib/python3.10/site-packages:/app/lib/python3.10/site-packages/PyQt6:/app/lib/python3.10/site-packages/PyQt6/Qt6/lib - -modules: - - name: unsquashfs - buildsystem: simple - build-commands: - - XZ_SUPPORT=1 make -C squashfs-tools -j ${FLATPAK_BUILDER_N_JOBS} unsquashfs - - install -Dpm755 -t "${FLATPAK_DEST}/bin" squashfs-tools/unsquashfs - sources: - - type: git - url: https://github.com/plougher/squashfs-tools.git - tag: 4.6.1 - commit: d8cb82d9840330f9344ec37b992595b5d7b44184 - - - name: snap - buildsystem: simple - build-commands: - - unsquashfs -dest buzz -quiet -no-progress buzz.snap - - cp -rT buzz ${FLATPAK_DEST} && rm -rf buzz - sources: - - type: file - dest-filename: buzz.snap - # Stable 1.2.0 - url: https://api.snapcraft.io/api/v1/snaps/download/RSpCVxCNDwoTXHPXhlYQnziD0jQhVnKA_362.snap - sha256: fbc045426c867b1d7ee01178d4f53d785c161709e2a9db6854cefec29aa510d7 - # Edge - #url: https://api.snapcraft.io/api/v1/snaps/download/RSpCVxCNDwoTXHPXhlYQnziD0jQhVnKA_402.snap - #sha256: 0acecacf8fa476bf6d7afcd98b7b557829b70cfa8b1d57e6ff5248737b63ab60 - - # Borrowed from https://github.com/flathub/org.audacityteam.Audacity/blob/master/org.audacityteam.Audacity.yaml - - name: portaudio - buildsystem: cmake-ninja - config-opts: - - -DCMAKE_BUILD_TYPE=RelWithDebInfo - sources: - - type: archive - url: https://github.com/PortAudio/portaudio/archive/refs/tags/v19.7.0.tar.gz - sha256: 5af29ba58bbdbb7bbcefaaecc77ec8fc413f0db6f4c4e286c40c3e1b83174fa0 - - # Borrowed from https://github.com/flathub/org.freedownloadmanager.Manager/pull/20/files - - name: kerberos - subdir: src - sources: - - type: archive - url: https://kerberos.org/dist/krb5/1.21/krb5-1.21.tar.gz - sha256: 69f8aaff85484832df67a4bbacd99b9259bd95aab8c651fbbe65cdc9620ea93b - - - name: Buzz - buildsystem: simple - build-commands: - - install -Dm755 flatpak/run-buzz.sh ${FLATPAK_DEST}/bin/run-buzz.sh - - - install -Dm644 share/icons/${FLATPAK_ID}.svg ${FLATPAK_DEST}/share/icons/hicolor/scalable/apps/${FLATPAK_ID}.svg - - install -Dm644 share/applications/${FLATPAK_ID}.desktop ${FLATPAK_DEST}/share/applications/${FLATPAK_ID}.desktop - - install -Dm644 share/metainfo/${FLATPAK_ID}.metainfo.xml ${FLATPAK_DEST}/share/metainfo/${FLATPAK_ID}.metainfo.xml - - - install -Dm644 flatpak/libbsd.so.0 ${FLATPAK_DEST}/lib/x86_64-linux-gnu/libbsd.so.0 - - install -Dm644 flatpak/libmd.so.0 ${FLATPAK_DEST}/lib/x86_64-linux-gnu/libmd.so.0 - - install -Dm644 flatpak/libdb-5.3.so ${FLATPAK_DEST}/lib/x86_64-linux-gnu/libdb-5.3.so - - install -Dm644 flatpak/libapparmor.so.1 ${FLATPAK_DEST}/lib/x86_64-linux-gnu/libapparmor.so.1 - - install -Dm644 flatpak/libavutil.so.58 ${FLATPAK_DEST}/lib/x86_64-linux-gnu/libavutil.so.58 - sources: - - type: dir - path: . diff --git a/share/icons/io.github.chidiwilliams.Buzz.svg b/share/icons/io.github.chidiwilliams.Buzz.svg index 79604329..d5b67bc0 100644 --- a/share/icons/io.github.chidiwilliams.Buzz.svg +++ b/share/icons/io.github.chidiwilliams.Buzz.svg @@ -1,16 +1,23 @@ - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml index d65251fd..b94e23bd 100644 --- a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml +++ b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml @@ -64,7 +64,7 @@ - + https://github.com/chidiwilliams/buzz/releases/tag/v1.3.3

    This release introduces Vulkan GPU support for whisper.cpp making it significantly faster even on laptops. From de1ed90f50eedef512c274b1a0468d76567e712f Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Tue, 18 Nov 2025 18:22:10 +0200 Subject: [PATCH 07/73] Fix for snap (#1286) --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 8017d213..346ff7e4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -89,6 +89,7 @@ parts: - libgstreamer1.0-0 - libgstreamer-plugins-base1.0-0 - libgstreamer-plugins-good1.0-0 + - liboss4-salsa2 # Display - libxkbcommon-x11-0 - libxcb-icccm4 From 5a81c715d1a6dc4ee768fe29b06fa1613a58056b Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Thu, 20 Nov 2025 07:50:56 +0200 Subject: [PATCH 08/73] Adjusting Windows build notes (#1288) --- CONTRIBUTING.md | 34 ++++++++++----------------- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 6 +---- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43df6166..d8a540cf 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,16 +94,18 @@ Assumes you have [Git](https://git-scm.com/downloads) and [python](https://www.p ``` Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) ``` -2. Install the GNU make. `choco install make` +2. Install the build tools. `choco install make cmake` 3. Install the ffmpeg. `choco install ffmpeg` -4. Install [MSYS2](https://www.msys2.org/), follow [this guide](https://sajidifti.medium.com/how-to-install-gcc-and-gdb-on-windows-using-msys2-tutorial-0fceb7e66454). -5. Clone the repository `git clone --recursive https://github.com/chidiwilliams/buzz.git` -6. Enter repo folder `cd buzz` -7. Install uv `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` -8. Install the dependencies `uv sync` -9. `cp -r .\dll_backup\ .\buzz\` -10. Build Buzz `uv build` -11. Run Buzz `uv run buzz` +4. Download [Build Tools for Visual Studio 2022](https://visualstudio.microsoft.com/vs/older-downloads/) and install "Desktop development with C++" workload. +5. Add location of `namke` to your PATH environment variable. Usually it is `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x86` +6. Install Vulkan SDK from https://vulkan.lunarg.com/sdk/home +7. Clone the repository `git clone --recursive https://github.com/chidiwilliams/buzz.git` +8. Enter repo folder `cd buzz` +9. Install uv `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` +10. Install the dependencies `uv sync` +11. Build Whisper.cpp `uv run make buzz/whisper_cpp` +12. `cp -r .\dll_backup\ .\buzz\` +13. Run Buzz `uv run buzz` Note: It should be safe to ignore any "syntax errors" you see during the build. Buzz will work. Also you can ignore any errors for FFmpeg. Buzz tries to load FFmpeg by several different means and some of them throw errors, but FFmpeg should eventually be found and work. @@ -119,16 +121,4 @@ uv add --index https://pypi.ngc.nvidia.com nvidia-cublas-cu12==12.8.3.14 nvidia- To use Faster Whisper on GPU, install the following libraries: * [cuBLAS](https://developer.nvidia.com/cublas) -* [cuDNN](https://developer.nvidia.com/cudnn) - -If you run into issues with FFmpeg, ensure ffmpeg dependencies are installed -``` -pip3 uninstall ffmpeg ffmpeg-python -pip3 install ffmpeg -pip3 install ffmpeg-python -``` - -For Whisper.cpp you will need to install Vulkan SDK. -Follow the instructions here https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html - -Run Buzz `python -m buzz` \ No newline at end of file +* [cuDNN](https://developer.nvidia.com/cudnn) \ No newline at end of file diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index b1206756..f215fb66 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -106,10 +106,6 @@ msgstr "Lettone" msgid "Polish" msgstr "Polacco" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:45 -msgid "Portuguese" -msgstr "Portoghese" - #: buzz/widgets/preferences_dialog/general_preferences_widget.py:46 #: buzz/transcriber/transcriber.py:59 msgid "Ukrainian" @@ -391,7 +387,7 @@ msgid "" "Enter instructions for AI on how to translate, for example 'Please translate " "each text sent to you from English to Spanish.'" msgstr "" -Inserisci le istruzioni per l'IA su come tradurre, ad esempio 'Per favore, traduci " +"Inserisci le istruzioni per l'IA su come tradurre, ad esempio 'Per favore, traduci " "ogni testo che ti viene inviato dall'inglese allo spagnolo.'" #: buzz/widgets/transcriber/advanced_settings_dialog.py:92 From f3765a586fe004c7c6bd906ca16f3e39e73984c8 Mon Sep 17 00:00:00 2001 From: David Olowomeye <100958002+greatdaveo@users.noreply.github.com> Date: Mon, 24 Nov 2025 07:20:12 +0000 Subject: [PATCH 09/73] Implemented resume functionality for downloading models #1287 (#1289) --- buzz/model_loader.py | 215 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 175 insertions(+), 40 deletions(-) diff --git a/buzz/model_loader.py b/buzz/model_loader.py index 790dbbdf..ce12ba42 100644 --- a/buzz/model_loader.py +++ b/buzz/model_loader.py @@ -30,7 +30,6 @@ os.makedirs(model_root_dir, exist_ok=True) logging.debug("Model root directory: %s", model_root_dir) - class WhisperModelSize(str, enum.Enum): TINY = "tiny" TINYEN = "tiny.en" @@ -60,6 +59,25 @@ class WhisperModelSize(str, enum.Enum): def __str__(self): return self.value.capitalize() +# Approximate expected file sizes for Whisper models +WHISPER_MODEL_SIZES = { + WhisperModelSize.TINY: 75 * 1024 * 1024, + WhisperModelSize.TINYEN: 75 * 1024 * 1024, + WhisperModelSize.BASE: 150 * 1024 * 1024, + WhisperModelSize.BASEEN: 150 * 1024 * 1024, + WhisperModelSize.SMALL: 500 * 1024 * 1024, + WhisperModelSize.SMALLEN: 500 * 1024 * 1024, + WhisperModelSize.MEDIUM: 1500 * 1024 * 1024, + WhisperModelSize.MEDIUMEN: 1500 * 1024 * 1024, + WhisperModelSize.LARGE: 3100 * 1024 * 1024, + WhisperModelSize.LARGEV2: 3100 * 1024 * 1024, + WhisperModelSize.LARGEV3: 3100 * 1024 * 1024, + WhisperModelSize.LARGEV3TURBO: 3100 * 1024 * 1024, +} + +def get_expected_whisper_model_size(size: WhisperModelSize) -> Optional[int]: + """Get expected file size for a Whisper model without network request.""" + return WHISPER_MODEL_SIZES.get(size, None) class ModelType(enum.Enum): WHISPER = "Whisper" @@ -200,7 +218,21 @@ class TranscriptionModel: file_path = get_whisper_file_path(size=self.whisper_model_size) if not os.path.exists(file_path) or not os.path.isfile(file_path): return None - return file_path + + file_size = os.path.getsize(file_path) + + expected_size = get_expected_whisper_model_size(self.whisper_model_size) + + if expected_size is not None: + if file_size < expected_size * 0.95: # Allow 5% tolerance for file system differences + return None + return file_path + else: + # For unknown model size + if file_size < 50 * 1024 * 1024: + return None + + return file_path if self.model_type == ModelType.FASTER_WHISPER: try: @@ -244,7 +276,7 @@ def get_whisper_cpp_file_path(size: WhisperModelSize) -> str: model_filename = f"ggml-{size.to_whisper_cpp_model_size()}.bin" try: - model_path = huggingface_hub.snapshot_download( + model_path = huggingface_hub.snapshot_download( repo_id=repo_id, allow_patterns=[model_filename], local_files_only=True, @@ -271,7 +303,8 @@ class HuggingfaceDownloadMonitor: def __init__(self, model_root: str, progress: pyqtSignal(tuple), total_file_size: int): self.model_root = model_root self.progress = progress - self.total_file_size = round(total_file_size * 1.1) # To keep dialog open even if it reports 100% + # To keep dialog open even if it reports 100% + self.total_file_size = round(total_file_size * 1.1) self.incomplete_download_root = None self.stop_event = threading.Event() self.monitor_thread = None @@ -279,8 +312,10 @@ class HuggingfaceDownloadMonitor: def set_download_roots(self): normalized_model_root = os.path.normpath(self.model_root) - two_dirs_up = os.path.normpath(os.path.join(normalized_model_root, "..", "..")) - self.incomplete_download_root = os.path.normpath(os.path.join(two_dirs_up, "blobs")) + two_dirs_up = os.path.normpath( + os.path.join(normalized_model_root, "..", "..")) + self.incomplete_download_root = os.path.normpath( + os.path.join(two_dirs_up, "blobs")) def clean_tmp_files(self): for filename in os.listdir(model_root_dir): @@ -292,12 +327,14 @@ class HuggingfaceDownloadMonitor: if model_root_dir is not None: for filename in os.listdir(model_root_dir): if filename.startswith("tmp"): - file_size = os.path.getsize(os.path.join(model_root_dir, filename)) + file_size = os.path.getsize( + os.path.join(model_root_dir, filename)) self.progress.emit((file_size, self.total_file_size)) for filename in os.listdir(self.incomplete_download_root): if filename.endswith(".incomplete"): - file_size = os.path.getsize(os.path.join(self.incomplete_download_root, filename)) + file_size = os.path.getsize(os.path.join( + self.incomplete_download_root, filename)) self.progress.emit((file_size, self.total_file_size)) time.sleep(2) @@ -332,7 +369,8 @@ def download_from_huggingface( try: model_root = huggingface_hub.snapshot_download( repo_id, - allow_patterns=allow_patterns[num_large_files:], # all, but largest + # all, but largest + allow_patterns=allow_patterns[num_large_files:], cache_dir=model_root_dir, etag_timeout=60 ) @@ -354,7 +392,8 @@ def download_from_huggingface( except requests.exceptions.RequestException as e: continue - model_download_monitor = HuggingfaceDownloadMonitor(model_root, progress, largest_file_size) + model_download_monitor = HuggingfaceDownloadMonitor( + model_root, progress, largest_file_size) model_download_monitor.start_monitoring() try: @@ -367,9 +406,7 @@ def download_from_huggingface( except Exception as exc: logging.exception(exc) model_download_monitor.stop_monitoring() - # Cleanup to prevent incomplete downloads errors - if os.path.exists(model_root): - shutil.rmtree(model_root) + return "" model_download_monitor.stop_monitoring() @@ -429,19 +466,22 @@ class ModelDownloader(QRunnable): def __init__(self, model: TranscriptionModel, custom_model_url: Optional[str] = None): super().__init__() - self.is_coreml_supported = platform.system() == "Darwin" and platform.machine() == "arm64" + self.is_coreml_supported = platform.system( + ) == "Darwin" and platform.machine() == "arm64" self.signals = self.Signals() self.model = model self.stopped = False self.custom_model_url = custom_model_url def run(self) -> None: - logging.debug("Downloading model: %s, %s", self.model, self.model.hugging_face_model_id) + logging.debug("Downloading model: %s, %s", self.model, + self.model.hugging_face_model_id) if self.model.model_type == ModelType.WHISPER_CPP: if self.custom_model_url: url = self.custom_model_url - file_path = get_whisper_cpp_file_path(size=self.model.whisper_model_size) + file_path = get_whisper_cpp_file_path( + size=self.model.whisper_model_size) return self.download_model_to_path(url=url, file_path=file_path) repo_id = WHISPER_CPP_REPO_ID @@ -458,9 +498,9 @@ class ModelDownloader(QRunnable): num_large_files = 1 if self.is_coreml_supported: whisper_cpp_model_files = [ - f"ggml-{model_name}.bin", - f"ggml-{model_name}-encoder.mlmodelc.zip", - "README.md" + f"ggml-{model_name}.bin", + f"ggml-{model_name}-encoder.mlmodelc.zip", + "README.md" ] num_large_files = 2 @@ -476,12 +516,14 @@ class ModelDownloader(QRunnable): os.path.join(model_path, f"ggml-{model_name}-encoder.mlmodelc.zip"), 'r') as zip_ref: zip_ref.extractall(model_path) - self.signals.finished.emit(os.path.join(model_path, f"ggml-{model_name}.bin")) + self.signals.finished.emit(os.path.join( + model_path, f"ggml-{model_name}.bin")) return if self.model.model_type == ModelType.WHISPER: url = whisper._MODELS[self.model.whisper_model_size.value] - file_path = get_whisper_file_path(size=self.model.whisper_model_size) + file_path = get_whisper_file_path( + size=self.model.whisper_model_size) expected_sha256 = url.split("/")[-2] return self.download_model_to_path( url=url, file_path=file_path, expected_sha256=expected_sha256 @@ -526,16 +568,18 @@ class ModelDownloader(QRunnable): downloaded = self.download_model(url, file_path, expected_sha256) if downloaded: self.signals.finished.emit(file_path) - except requests.RequestException: + except requests.RequestException as e: self.signals.error.emit(_("A connection error occurred")) - if os.path.exists(file_path): - os.remove(file_path) + if not self.stopped and "timeout" not in str(e).lower(): + if os.path.exists(file_path): + os.remove(file_path) logging.exception("") except Exception as exc: self.signals.error.emit(str(exc)) - if os.path.exists(file_path): - os.remove(file_path) - logging.exception(exc) + if not self.stopped: + if os.path.exists(file_path): + os.remove(file_path) + logging.exception(exc) def download_model( self, url: str, file_path: str, expected_sha256: Optional[str] @@ -547,27 +591,118 @@ class ModelDownloader(QRunnable): if os.path.exists(file_path) and not os.path.isfile(file_path): raise RuntimeError(f"{file_path} exists and is not a regular file") + resume_from = 0 + file_mode = "wb" + if os.path.isfile(file_path): - if expected_sha256 is None: - return True + file_size = os.path.getsize(file_path) - model_bytes = open(file_path, "rb").read() - model_sha256 = hashlib.sha256(model_bytes).hexdigest() - if model_sha256 == expected_sha256: - return True + if expected_sha256 is not None: + # Get the expected file size from URL + try: + head_response = requests.head(url, timeout=5, allow_redirects=True) + expected_size = int(head_response.headers.get("Content-Length", 0)) + + if expected_size > 0: + if file_size < expected_size: + resume_from = file_size + file_mode = "ab" + logging.debug( + f"File incomplete ({file_size}/{expected_size} bytes), resuming from byte {resume_from}" + ) + elif file_size == expected_size: + # This means file size matches - verify SHA256 to confirm it is complete + try: + with open(file_path, "rb") as f: + model_bytes = f.read() + model_sha256 = hashlib.sha256(model_bytes).hexdigest() + if model_sha256 == expected_sha256: + logging.debug("Model already downloaded and verified") + return True + else: + warnings.warn( + f"{file_path} exists, but the SHA256 checksum does not match; re-downloading the file" + ) + # File exists but it is wrong, delete it + os.remove(file_path) + except Exception as e: + logging.warning(f"Error checking existing file: {e}") + os.remove(file_path) + else: + # File is larger than expected - corrupted, delete it + warnings.warn(f"File size ({file_size}) exceeds expected size ({expected_size}), re-downloading") + os.remove(file_path) + else: + # Can't get expected size - use threshold approach + if file_size < 10 * 1024 * 1024: + resume_from = file_size + file_mode = "ab" # Append mode to resume + logging.debug(f"Resuming download from byte {resume_from}") + else: + # Large file - verify SHA256 + try: + with open(file_path, "rb") as f: + model_bytes = f.read() + model_sha256 = hashlib.sha256(model_bytes).hexdigest() + if model_sha256 == expected_sha256: + logging.debug("Model already downloaded and verified") + return True + else: + warnings.warn("SHA256 mismatch, re-downloading") + os.remove(file_path) + except Exception as e: + logging.warning(f"Error verifying file: {e}") + os.remove(file_path) + + except Exception as e: + # Can't get expected size - use threshold + logging.debug(f"Could not get expected file size: {e}, using threshold") + if file_size < 10 * 1024 * 1024: + resume_from = file_size + file_mode = "ab" + logging.debug(f"Resuming from byte {resume_from}") else: - warnings.warn( - f"{file_path} exists, but the SHA256 checksum does not match; re-downloading the file" - ) - + # No SHA256 to verify - just check file size + if file_size > 0: + resume_from = file_size + file_mode = "ab" + logging.debug(f"Resuming download from byte {resume_from}") + # Downloads the model using the requests module instead of urllib to # use the certs from certifi when the app is running in frozen mode + headers = {} + if resume_from > 0: + headers["Range"] = f"bytes={resume_from}-" + with requests.get(url, stream=True, timeout=15) as source, open( - file_path, "wb" + file_path, file_mode ) as output: source.raise_for_status() - total_size = float(source.headers.get("Content-Length", 0)) - current = 0.0 + + if resume_from > 0: + if source.status_code == 206: + logging.debug( + f"Server supports resume, continuing from byte {resume_from}") + total_size = int(source.headers.get( + "Content-Range", "").split("/")[-1]) + current = resume_from + self.signals.progress.emit((current, total_size)) + elif source.status_code == 200: + logging.debug( + "Server doesn't support Range requests, starting from beginning") + # Truncate file and start over + output.close() + output = open(file_path, "wb") + total_size = float(source.headers.get("Content-Length", 0)) + current = 0.0 + resume_from = 0 + else: + source.raise_for_status() + + else: + total_size = float(source.headers.get("Content-Length", 0)) + current = 0.0 + self.signals.progress.emit((current, total_size)) for chunk in source.iter_content(chunk_size=8192): if self.stopped: From 252db3c3edaddbd487b0de9d77efa0ad30111356 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Mon, 24 Nov 2025 21:59:21 +0200 Subject: [PATCH 10/73] Adding option to delete saved models and files on uninstall (#1291) --- installer.iss | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/installer.iss b/installer.iss index 69fa9b39..85b690d0 100644 --- a/installer.iss +++ b/installer.iss @@ -51,16 +51,6 @@ Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange( Root: HKCU; Subkey: "{#AppRegKey}" [Code] -procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); -begin - if CurUninstallStep = usPostUninstall then - begin - if RegKeyExists(HKEY_CURRENT_USER, '{#AppRegKey}') then - if MsgBox('Do you want to delete Buzz settings?', mbConfirmation, MB_YESNO) = IDYES - then - RegDeleteKeyIncludingSubkeys(HKEY_CURRENT_USER, '{#AppRegKey}'); - end; -end; procedure DeleteFileOrFolder(FilePath: string); begin if FileExists(FilePath) then @@ -73,6 +63,21 @@ begin end; end; +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + if CurUninstallStep = usPostUninstall then + begin + if RegKeyExists(HKEY_CURRENT_USER, '{#AppRegKey}') then + if MsgBox('Do you want to delete Buzz settings and saved files?', mbConfirmation, MB_YESNO) = IDYES + then + begin + RegDeleteKeyIncludingSubkeys(HKEY_CURRENT_USER, '{#AppRegKey}'); + // Remove model and cache directories + DeleteFileOrFolder(ExpandConstant('{localappdata}\Buzz')); + end; + end; +end; + procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssInstall then From cabbd487f94b6f2516df58d7d03ad9e0afaa218f Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Fri, 28 Nov 2025 21:30:36 +0200 Subject: [PATCH 11/73] Improvements (#1296) --- Makefile | 2 +- buzz/__version__.py | 2 +- buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/en_US/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 54 +++++++++++++++++---------- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 26 +++++++------ buzz/locale/nl/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 32 +++++++++------- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 24 +++++++----- buzz/transcriber/whisper_cpp.py | 9 +++-- buzz/widgets/about_dialog.py | 10 +++++ docs/docs/faq.md | 2 + pyproject.toml | 2 +- snap/snapcraft.yaml | 2 +- whisper.cpp | 2 +- 22 files changed, 244 insertions(+), 163 deletions(-) diff --git a/Makefile b/Makefile index 9b4050ef..af2aa9a1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -version := 1.3.3 +version := 1.3.4 mac_app_path := ./dist/Buzz.app mac_zip_path := ./dist/Buzz-${version}-mac.zip diff --git a/buzz/__version__.py b/buzz/__version__.py index e371c8ac..4a16f216 100644 --- a/buzz/__version__.py +++ b/buzz/__version__.py @@ -1 +1 @@ -VERSION = "1.3.3" +VERSION = "1.3.4" diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index 1450d9c0..e88359f6 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -308,8 +308,8 @@ msgid "Download failed" msgstr "Descàrrega fallida" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Error" @@ -513,11 +513,15 @@ msgstr "" "Comproveu els vostres dispositius d'àudio o els registres de l'aplicació per " "a més informació." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Comprova si hi ha actualitzacions" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "Estàs al dia!" @@ -764,14 +768,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "No s'ha pogut desar la clau OpenAI API a l'anell de claus" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" "El servidor Whisper no s'ha pogut iniciar. Consulteu els registres per " "obtenir més informació." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1145,15 +1149,15 @@ msgstr "Sundanès" msgid "Cantonese" msgstr "Cantonès" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "S'ha produït un error de connexió" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "Començant Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Cancel·la la transcripció" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index 7328ba15..7d356c67 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -307,8 +307,8 @@ msgid "Download failed" msgstr "Download mislykkedes" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Fejl" @@ -511,11 +511,15 @@ msgstr "" "Tjek venligst dine audioenheder eller tjek applikationens logs for " "mereinformation." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Tjek for opdateringer" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "Du er opdateret!" @@ -760,12 +764,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Kan ikke gemme OpenAI API-nøgle i nøgleringen" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1137,15 +1141,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Der er opstået en forbindelsesfejl" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Afbryd transkription" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index de802203..14ebc504 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -307,8 +307,8 @@ msgid "Download failed" msgstr "Der Download ist fehlgeschlagen" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Fehler" @@ -511,11 +511,15 @@ msgstr "" "Bitte überprüfen Sie Ihre Audiogeräte oder prüfen Sie die " "Anwendungsprotokolle für weitere Informationen." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Nach Updates suchen" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "Sie sind auf dem Laufenden!" @@ -761,12 +765,12 @@ msgstr "" "Der OpenAI-API-Schlüssel kann nicht im Schlüsselbund gespeichert werden" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1138,15 +1142,15 @@ msgstr "Sundanesisch" msgid "Cantonese" msgstr "Kantonesisch" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Ein Verbindungsfehler ist aufgetreten" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Transkription abbrechen" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index d7fb3dc7..87f47cea 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -299,8 +299,8 @@ msgid "Download failed" msgstr "" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "" @@ -499,11 +499,15 @@ msgid "" "information." msgstr "" -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "" @@ -742,12 +746,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1118,15 +1122,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 msgid "Starting transcription..." msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index f7e2d9e3..133209e1 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -314,8 +314,8 @@ msgid "Download failed" msgstr "Descarga fallida" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Error" @@ -546,12 +546,16 @@ msgstr "" "aplicación para obtener más información." # automatic translation -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Buscar actualizaciones" +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + # automatic translation -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "¡Estás al día!" @@ -810,14 +814,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "No se puede guardar la clave de la API de OpenAI en el llavero" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" "El servidor Whisper no se pudo iniciar. Consulta los registros para obtener " "más detalles." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1192,16 +1196,16 @@ msgstr "Sundanés" msgid "Cantonese" msgstr "Cantonés" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Se ha producido un error de conexión" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "Iniciando Whisper.cpp..." # automatic translation -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Cancelar transcripción" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index f215fb66..127a3b0c 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -106,6 +106,11 @@ msgstr "Lettone" msgid "Polish" msgstr "Polacco" +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:45 +#, fuzzy +msgid "Portuguese (Brazil)" +msgstr "Portoghese" + #: buzz/widgets/preferences_dialog/general_preferences_widget.py:46 #: buzz/transcriber/transcriber.py:59 msgid "Ukrainian" @@ -171,7 +176,9 @@ msgstr "Utilizza solo la CPU e disattiva l'accelerazione GPU" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" -msgstr "Imposta questa opzione se i modelli più grandi non si adattano alla memoria della tua GPU e Buzz si blocca" +msgstr "" +"Imposta questa opzione se i modelli più grandi non si adattano alla memoria " +"della tua GPU e Buzz si blocca" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 msgid "Disable GPU" @@ -301,8 +308,8 @@ msgid "Download failed" msgstr "Download non riuscito" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Errore" @@ -387,8 +394,8 @@ msgid "" "Enter instructions for AI on how to translate, for example 'Please translate " "each text sent to you from English to Spanish.'" msgstr "" -"Inserisci le istruzioni per l'IA su come tradurre, ad esempio 'Per favore, traduci " -"ogni testo che ti viene inviato dall'inglese allo spagnolo.'" +"Inserisci le istruzioni per l'IA su come tradurre, ad esempio 'Per favore, " +"traduci ogni testo che ti viene inviato dall'inglese allo spagnolo.'" #: buzz/widgets/transcriber/advanced_settings_dialog.py:92 msgid "Instructions for AI:" @@ -507,11 +514,15 @@ msgstr "" "Controlla i tuoi dispositivi audio o i registri dell'applicazione per " "maggiori informazioni." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Controlla gli aggiornamenti" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "Il programma è aggiornato!" @@ -595,7 +606,8 @@ msgstr "Ciclo di segmento" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 msgid "Enable/disable looping when clicking on transcript segments" -msgstr "Abilita/disabilita il loop quando si fa clic sui segmenti della trascrizione" +msgstr "" +"Abilita/disabilita il loop quando si fa clic sui segmenti della trascrizione" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 msgid "Follow Audio" @@ -606,8 +618,9 @@ msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -"Abilita/disabilita la lettura della posizione audio corrente nella trascrizione. Quando " -"abilitato, scorre automaticamente fino al testo corrente." +"Abilita/disabilita la lettura della posizione audio corrente nella " +"trascrizione. Quando abilitato, scorre automaticamente fino al testo " +"corrente." #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Scroll to Current" @@ -758,20 +771,21 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Impossibile salvare la chiave API OpenAI nel portachiavi" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." -msgstr "Impossibile avviare il server Whisper. Controllare i log per i dettagli." +msgstr "" +"Impossibile avviare il server Whisper. Controllare i log per i dettagli." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " "variable." msgstr "" -"Impossibile avviare il server Whisper a causa di memoria insufficiente. Riprovare " -"con un modello più piccolo. Per forzare la modalità CPU, utilizzare la variabile d'ambiente " -"BUZZ_FORCE_CPU=TRUE" +"Impossibile avviare il server Whisper a causa di memoria insufficiente. " +"Riprovare con un modello più piccolo. Per forzare la modalità CPU, " +"utilizzare la variabile d'ambiente BUZZ_FORCE_CPU=TRUE" #: buzz/transcriber/transcriber.py:24 msgid "Translate to English" @@ -1137,15 +1151,15 @@ msgstr "Sundanese" msgid "Cantonese" msgstr "Cantonese" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Si è verificato un errore di connessione" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "Avvio di Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 msgid "Starting transcription..." msgstr "Inizio trascrizione..." diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index efbab4d7..b5ec2b11 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -303,8 +303,8 @@ msgid "Download failed" msgstr "ダウンロード失敗" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "エラー" @@ -507,11 +507,15 @@ msgstr "" "オーディオデバイスを確認するか、詳細をアプリケーションのログで確認してくださ" "い。" -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "アップデートを確認する" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "最新の状態です!" @@ -755,12 +759,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "OpenAI API キーをkeyringに保存できません" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1132,15 +1136,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "接続エラーが発生しました" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "文字起こしをキャンセルする" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index ae700ba1..18f799f5 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" -"PO-Revision-Date: 2025-10-12 19:11+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"PO-Revision-Date: 2025-11-28 16:50+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -311,8 +311,8 @@ msgid "Download failed" msgstr "Lejupielāde neizdevās" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Kļūda" @@ -517,11 +517,15 @@ msgstr "" "Lūdzu pārbaudiet savas audio ierīces vai pārbaudiet lietotnes ziņojumu " "žurnālus, lai iegūtu papildu informāciju." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Pārbaudīt atjauninājumus" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "Parādīt sistēmas žurnālu" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "Jums ir jaunākā versija!" @@ -766,14 +770,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" "Whisper serverim neizdevās ieslēgties. Lūdzu pārbaudiet lietotnes žurnāla " "ierakstus." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1147,15 +1151,15 @@ msgstr "Sundāņu" msgid "Cantonese" msgstr "Kantonas" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Notika savienojuma kļūda" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "Palaiž Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 msgid "Starting transcription..." msgstr "Sāk atpazīšanu..." diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index b311c175..2e21acc2 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -309,8 +309,8 @@ msgid "Download failed" msgstr "Het downloaden is mislukt" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Foutmelding" @@ -511,11 +511,15 @@ msgid "" "information." msgstr "Controleer uw geluidsapparatuur of het programmalogboek." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Controleren op updates" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "De software is actueel!" @@ -759,12 +763,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "De OpenAI-api-sleutel kan niet worden bewaard in de sleutelbos" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1136,15 +1140,15 @@ msgstr "Soedanees" msgid "Cantonese" msgstr "Kantonees" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Er is een verbindingsfout opgetreden" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Transcriptie wissen" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index 09c61f94..2d452294 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -310,8 +310,8 @@ msgid "Download failed" msgstr "Pobrany" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Błąd" @@ -519,11 +519,15 @@ msgstr "" "Sprawdź urządzenia audio lub przejrzyj logi aplikacji, by uzyskać więcej " "informacji." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Sprawdź aktualizacje" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "Posiadasz najnowszą wersję!" @@ -769,12 +773,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1147,15 +1151,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Anuluj transkrypcję" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 6e002ac8..25165acd 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -176,7 +176,8 @@ msgstr "Usar somente a CPU e desabilitar aceleração por GPU" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" -msgstr "Marque isso se modelos maiores não couberem na memória da GPU e o Buzz travar" +msgstr "" +"Marque isso se modelos maiores não couberem na memória da GPU e o Buzz travar" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 msgid "Disable GPU" @@ -306,8 +307,8 @@ msgid "Download failed" msgstr "Falha ao baixar" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Erro" @@ -390,8 +391,8 @@ msgid "" "Enter instructions for AI on how to translate, for example 'Please translate " "each text sent to you from English to Spanish.'" msgstr "" -"Instrua a IA sobre como traduzir, por exemplo: \"Por favor, " -"traduza cada texto enviado a você do Inglês para o Português\"." +"Instrua a IA sobre como traduzir, por exemplo: \"Por favor, traduza cada " +"texto enviado a você do Inglês para o Português\"." #: buzz/widgets/transcriber/advanced_settings_dialog.py:92 msgid "Instructions for AI:" @@ -510,11 +511,15 @@ msgstr "" "Verifique seus dispositivos de áudio ou os logs do aplicativo para mais " "informações." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Verificar atualizações" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "Você está atualizado!" @@ -761,12 +766,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Não foi possível salvar a chave da API OpenAI no cofre de chaves" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "Falha ao iniciar o servidor Whisper. Verifique os logs." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1140,15 +1145,15 @@ msgstr "Sundanês" msgid "Cantonese" msgstr "Cantonês" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Ocorreu um erro de conexão" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "Iniciando Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Iniciando transcrição..." @@ -1233,5 +1238,6 @@ msgstr "Acrescentar e corrigir" #~ msgid "Undo" #~ msgstr "Desfazer" + #~ msgid "Redo" #~ msgstr "Refazer" diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index f0c8d508..f45a5184 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -305,8 +305,8 @@ msgid "Download failed" msgstr "Невдале завантаження" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "Помилка" @@ -509,11 +509,15 @@ msgstr "" "Будь ласка, перевірте свої аудіопристрої або пошукайте додаткову інформацію " "в звітах програми." -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "Перевірити оновлення" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "У вас актуальна версія!" @@ -756,12 +760,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Не вдається додати до звʼязки ключів API-ключ OpenAI" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1133,15 +1137,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "Виникла помилка зʼєднання" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "Скасувати транскрипцію" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index 4ea086de..0e9154a2 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -313,8 +313,8 @@ msgid "Download failed" msgstr "下载模型失败" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "错误" @@ -520,11 +520,15 @@ msgid "" "information." msgstr "请检查您的音频设备或检查应用程序日志以获取更多信息。" -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "检查更新" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "已经是最新版本" @@ -769,12 +773,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "无法将OpenAI API密钥保存到密钥串" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1147,15 +1151,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "连接发生错误" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "取消识别" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index bc0f7679..ed67c2c8 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-12 19:10+0300\n" +"POT-Creation-Date: 2025-11-28 16:49+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -308,8 +308,8 @@ msgid "Download failed" msgstr "下載模型" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:497 -#: buzz/model_loader.py:511 +#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/model_loader.py:553 msgid "Error" msgstr "" @@ -515,11 +515,15 @@ msgid "" "information." msgstr "請檢查您的音頻設備或檢查應用程序日誌以獲取更多信息。" -#: buzz/widgets/about_dialog.py:80 +#: buzz/widgets/about_dialog.py:81 msgid "Check for updates" msgstr "檢查更新" -#: buzz/widgets/about_dialog.py:109 +#: buzz/widgets/about_dialog.py:84 +msgid "Show logs" +msgstr "" + +#: buzz/widgets/about_dialog.py:118 msgid "You're up to date!" msgstr "你是最新的!" @@ -763,12 +767,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:394 +#: buzz/transcriber/recording_transcriber.py:397 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:398 +#: buzz/transcriber/recording_transcriber.py:401 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1141,15 +1145,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:223 buzz/model_loader.py:530 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:332 +#: buzz/transcriber/recording_transcriber.py:333 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:385 +#: buzz/transcriber/recording_transcriber.py:388 #, fuzzy msgid "Starting transcription..." msgstr "取消錄製" diff --git a/buzz/transcriber/whisper_cpp.py b/buzz/transcriber/whisper_cpp.py index 201ac450..8f12aec8 100644 --- a/buzz/transcriber/whisper_cpp.py +++ b/buzz/transcriber/whisper_cpp.py @@ -109,12 +109,13 @@ class WhisperCpp: # Add translate flag if needed if task.transcription_options.task == Task.TRANSLATE: - cmd.append("--translate") + cmd.extend(["--translate"]) # Force CPU if specified force_cpu = os.getenv("BUZZ_FORCE_CPU", "false") if force_cpu != "false" or not IS_VULKAN_SUPPORTED: - cmd.append("--no-gpu") + cmd.extend(["--no-gpu"]) + cmd.extend(["-t", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8) // 2))]) print(f"Running Whisper CLI: {' '.join(cmd)}") @@ -125,7 +126,7 @@ class WhisperCpp: si.wShowWindow = subprocess.SW_HIDE process = subprocess.Popen( cmd, - stdout=subprocess.PIPE, + stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, startupinfo=si, @@ -135,7 +136,7 @@ class WhisperCpp: else: process = subprocess.Popen( cmd, - stdout=subprocess.PIPE, + stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, ) diff --git a/buzz/widgets/about_dialog.py b/buzz/widgets/about_dialog.py index d4b3abbb..5c6b6757 100644 --- a/buzz/widgets/about_dialog.py +++ b/buzz/widgets/about_dialog.py @@ -1,5 +1,6 @@ import json from typing import Optional +from platformdirs import user_log_dir from PyQt6 import QtGui from PyQt6.QtCore import Qt, QUrl @@ -80,6 +81,9 @@ class AboutDialog(QDialog): self.check_updates_button = QPushButton(_("Check for updates"), self) self.check_updates_button.clicked.connect(self.on_click_check_for_updates) + self.show_logs_button = QPushButton(_("Show logs"), self) + self.show_logs_button.clicked.connect(self.on_click_show_logs) + button_box = QDialogButtonBox( QDialogButtonBox.StandardButton(QDialogButtonBox.StandardButton.Close), self ) @@ -90,15 +94,21 @@ class AboutDialog(QDialog): layout.addWidget(buzz_label) layout.addWidget(version_label) layout.addWidget(self.check_updates_button) + layout.addWidget(self.show_logs_button) layout.addWidget(button_box) self.setLayout(layout) + self.setMinimumWidth(350) def on_click_check_for_updates(self): url = QUrl(self.GITHUB_API_LATEST_RELEASE_URL) self.network_access_manager.get(QNetworkRequest(url)) self.check_updates_button.setDisabled(True) + def on_click_show_logs(self): + log_dir = user_log_dir(appname="Buzz") + QDesktopServices.openUrl(QUrl.fromLocalFile(log_dir)) + def on_latest_release_reply(self, reply: QNetworkReply): if reply.error() == QNetworkReply.NetworkError.NoError: response = json.loads(reply.readAll().data()) diff --git a/docs/docs/faq.md b/docs/docs/faq.md index 4de7f377..ab47a824 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -13,6 +13,8 @@ The models are stored: Paste the location in your file manager to access the models. +Since Version `1.3.4`, to get to the logs folder go to `Help -> About Buzz` and click on `Show logs` button. + ### 2. What can I try if the transcription runs too slowly? Speech recognition requires large amount of computation, so one option is to try using a lower Whisper model size or using a Whisper.cpp model to run speech recognition of your computer. If you have access to a computer with GPU that has at least 6GB of VRAM you can try using the Faster Whisper model. diff --git a/pyproject.toml b/pyproject.toml index 01894149..094ffccd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "buzz-captions" -version = "1.3.3" +version = "1.3.4" description = "" authors = [{ name = "Chidi Williams", email = "williamschidi1@gmail.com" }] requires-python = ">=3.12,<3.13" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 346ff7e4..53cc91db 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -153,7 +153,7 @@ apps: desktop: usr/share/applications/buzz.desktop environment: PATH: $SNAP/usr/bin:$SNAP/bin:$PATH - LD_LIBRARY_PATH: $SNAP/lib/python3.12/site-packages/nvidia/cudnn/lib:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/lapack:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/blas:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$SNAP:$LD_LIBRARY_PATH + LD_LIBRARY_PATH: $SNAP/lib/python3.12/site-packages/nvidia/cudnn/lib:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/lapack:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/blas:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/oss4-libsalsa:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$SNAP:$LD_LIBRARY_PATH PYTHONPATH: $SNAP:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/python3/dist-packages:$SNAP/usr/lib/python3.12/site-packages:$SNAP/usr/local/lib/python3.12/dist-packages:$SNAP/usr/lib/python3.12/dist-packages:$PYTHONPATH QT_MEDIA_BACKEND: gstreamer PULSE_LATENCY_MSEC: "30" diff --git a/whisper.cpp b/whisper.cpp index a8d002cf..4979e04f 160000 --- a/whisper.cpp +++ b/whisper.cpp @@ -1 +1 @@ -Subproject commit a8d002cfd879315632a579e73f0148d06959de36 +Subproject commit 4979e04f5dcaccb36057e059bbaed8a2f5288315 From 73376a63ac660b94f9f94175910f224ff0ed0d2b Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Tue, 2 Dec 2025 21:39:24 +0200 Subject: [PATCH 12/73] Add speaker identification2 (#1290) Co-authored-by: David Olowomeye <100958002+greatdaveo@users.noreply.github.com> --- .coveragerc | 5 +- .github/workflows/ci.yml | 4 +- .github/workflows/snapcraft.yml | 13 + .gitignore | 3 +- .gitmodules | 12 + Buzz.spec | 8 +- CONTRIBUTING.md | 6 +- Makefile | 7 +- README.md | 2 +- buzz/__version__.py | 2 +- buzz/assets/speaker-identification.svg | 14 + buzz/buzz.py | 11 + buzz/file_transcriber_queue_worker.py | 2 +- buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 145 +- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 145 +- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 145 +- buzz/locale/en_US/LC_MESSAGES/buzz.po | 143 +- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 147 +- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 145 +- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 145 +- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 146 +- buzz/locale/nl/LC_MESSAGES/buzz.po | 145 +- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 145 +- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 145 +- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 145 +- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 145 +- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 145 +- buzz/transcriber/whisper_cpp.py | 5 +- buzz/widgets/icon.py | 4 + .../speaker_identification_widget.py | 504 ++++ .../transcription_resizer_widget.py | 10 +- .../transcription_viewer_widget.py | 238 +- ctc_forced_aligner | 1 + deepmultilingualpunctuation | 1 + demucs/.github/ISSUE_TEMPLATE/bug.md | 33 + demucs/.github/ISSUE_TEMPLATE/question.md | 10 + demucs/.github/workflows/linter.yml | 36 + demucs/.github/workflows/tests.yml | 36 + demucs/.gitignore | 17 + demucs/CODE_OF_CONDUCT.md | 76 + demucs/CONTRIBUTING.md | 23 + demucs/Demucs.ipynb | 153 ++ demucs/LICENSE | 21 + demucs/MANIFEST.in | 13 + demucs/Makefile | 36 + demucs/README.md | 319 +++ demucs/Readme.md | 1 - demucs/conf/config.yaml | 304 +++ demucs/conf/dset/aetl.yaml | 19 + demucs/conf/dset/auto_extra_test.yaml | 18 + demucs/conf/dset/auto_mus.yaml | 20 + demucs/conf/dset/extra44.yaml | 8 + demucs/conf/dset/extra_mmi_goodclean.yaml | 12 + demucs/conf/dset/extra_test.yaml | 12 + demucs/conf/dset/musdb44.yaml | 5 + demucs/conf/dset/sdx23_bleeding.yaml | 10 + demucs/conf/dset/sdx23_labelnoise.yaml | 10 + demucs/conf/svd/base.yaml | 14 + demucs/conf/svd/base2.yaml | 14 + demucs/conf/svd/default.yaml | 1 + demucs/conf/variant/default.yaml | 1 + demucs/conf/variant/example.yaml | 5 + demucs/conf/variant/finetune.yaml | 19 + demucs/demucs.png | Bin 0 -> 339294 bytes demucs/{ => demucs}/__init__.py | 0 demucs/{ => demucs}/__main__.py | 0 demucs/{ => demucs}/api.py | 0 demucs/{ => demucs}/apply.py | 0 demucs/{ => demucs}/audio.py | 0 demucs/{ => demucs}/audio_legacy.py | 0 demucs/{ => demucs}/augment.py | 0 demucs/{ => demucs}/demucs.py | 0 demucs/{ => demucs}/distrib.py | 0 demucs/{ => demucs}/ema.py | 0 demucs/{ => demucs}/evaluate.py | 0 demucs/{ => demucs}/grids/__init__.py | 0 demucs/{ => demucs}/grids/_explorers.py | 0 demucs/{ => demucs}/grids/mdx.py | 0 demucs/{ => demucs}/grids/mdx_extra.py | 0 demucs/{ => demucs}/grids/mdx_refine.py | 0 demucs/{ => demucs}/grids/mmi.py | 0 demucs/{ => demucs}/grids/mmi_ft.py | 0 demucs/{ => demucs}/grids/repro.py | 0 demucs/{ => demucs}/grids/repro_ft.py | 0 demucs/{ => demucs}/grids/sdx23.py | 0 demucs/{ => demucs}/hdemucs.py | 0 demucs/{ => demucs}/htdemucs.py | 0 demucs/{ => demucs}/pretrained.py | 0 demucs/{ => demucs}/py.typed | 0 demucs/{ => demucs}/remote/files.txt | 0 demucs/{ => demucs}/remote/hdemucs_mmi.yaml | 0 demucs/{ => demucs}/remote/htdemucs.yaml | 0 demucs/{ => demucs}/remote/htdemucs_6s.yaml | 0 demucs/{ => demucs}/remote/htdemucs_ft.yaml | 0 demucs/{ => demucs}/remote/mdx.yaml | 0 demucs/{ => demucs}/remote/mdx_extra.yaml | 0 demucs/{ => demucs}/remote/mdx_extra_q.yaml | 0 demucs/{ => demucs}/remote/mdx_q.yaml | 0 demucs/{ => demucs}/remote/repro_mdx_a.yaml | 0 .../remote/repro_mdx_a_hybrid_only.yaml | 0 .../remote/repro_mdx_a_time_only.yaml | 0 demucs/{ => demucs}/repitch.py | 0 demucs/{ => demucs}/repo.py | 0 demucs/{ => demucs}/separate.py | 0 demucs/{ => demucs}/solver.py | 0 demucs/{ => demucs}/spec.py | 0 demucs/{ => demucs}/states.py | 0 demucs/{ => demucs}/svd.py | 0 demucs/{ => demucs}/train.py | 0 demucs/{ => demucs}/transformer.py | 0 demucs/{ => demucs}/utils.py | 0 demucs/{ => demucs}/wav.py | 0 demucs/{ => demucs}/wdemucs.py | 0 demucs/docs/api.md | 204 ++ demucs/docs/linux.md | 28 + demucs/docs/mac.md | 28 + demucs/docs/mdx.md | 73 + demucs/docs/release.md | 114 + demucs/docs/sdx23.md | 61 + demucs/docs/training.md | 290 +++ demucs/docs/windows.md | 67 + demucs/environment-cpu.yml | 28 + demucs/environment-cuda.yml | 28 + demucs/hubconf.py | 11 + demucs/mypy.ini | 5 + demucs/outputs.tar.gz | Bin 0 -> 1885 bytes demucs/requirements.txt | 19 + demucs/requirements_minimal.txt | 10 + demucs/setup.cfg | 8 + demucs/setup.py | 75 + demucs/test.mp3 | Bin 0 -> 802480 bytes demucs/tools/__init__.py | 5 + demucs/tools/automix.py | 343 +++ demucs/tools/bench.py | 78 + demucs/tools/convert.py | 152 ++ demucs/tools/export.py | 71 + demucs/tools/test_pretrained.py | 43 + docs/docs/preferences.md | 2 +- docs/docs/usage/1_file_import.md | 2 + docs/docs/usage/5_speaker_identification.md | 9 + flatpak/run-buzz.sh | 2 + hatch_build.py | 37 + pyproject.toml | 16 +- pytest.ini | 1 + share/applications/buzz.desktop | 17 + snap/snapcraft.yaml | 12 +- tests/gui_test.py | 5 +- .../file_transcriber_queue_worker_test.py | 6 +- tests/transcriber/whisper_cpp_test.py | 2 +- .../speaker_identification_widget_test.py | 90 + tests/widgets/transcription_viewer_test.py | 8 +- uv.lock | 2024 ++++++++++++++++- whisper_diarization | 1 + 153 files changed, 7397 insertions(+), 707 deletions(-) create mode 100644 buzz/assets/speaker-identification.svg create mode 100644 buzz/widgets/transcription_viewer/speaker_identification_widget.py create mode 160000 ctc_forced_aligner create mode 160000 deepmultilingualpunctuation create mode 100644 demucs/.github/ISSUE_TEMPLATE/bug.md create mode 100644 demucs/.github/ISSUE_TEMPLATE/question.md create mode 100644 demucs/.github/workflows/linter.yml create mode 100644 demucs/.github/workflows/tests.yml create mode 100644 demucs/.gitignore create mode 100644 demucs/CODE_OF_CONDUCT.md create mode 100644 demucs/CONTRIBUTING.md create mode 100644 demucs/Demucs.ipynb create mode 100644 demucs/LICENSE create mode 100644 demucs/MANIFEST.in create mode 100644 demucs/Makefile create mode 100644 demucs/README.md delete mode 100644 demucs/Readme.md create mode 100644 demucs/conf/config.yaml create mode 100644 demucs/conf/dset/aetl.yaml create mode 100644 demucs/conf/dset/auto_extra_test.yaml create mode 100644 demucs/conf/dset/auto_mus.yaml create mode 100644 demucs/conf/dset/extra44.yaml create mode 100644 demucs/conf/dset/extra_mmi_goodclean.yaml create mode 100644 demucs/conf/dset/extra_test.yaml create mode 100644 demucs/conf/dset/musdb44.yaml create mode 100644 demucs/conf/dset/sdx23_bleeding.yaml create mode 100644 demucs/conf/dset/sdx23_labelnoise.yaml create mode 100644 demucs/conf/svd/base.yaml create mode 100644 demucs/conf/svd/base2.yaml create mode 100644 demucs/conf/svd/default.yaml create mode 100644 demucs/conf/variant/default.yaml create mode 100644 demucs/conf/variant/example.yaml create mode 100644 demucs/conf/variant/finetune.yaml create mode 100644 demucs/demucs.png rename demucs/{ => demucs}/__init__.py (100%) rename demucs/{ => demucs}/__main__.py (100%) rename demucs/{ => demucs}/api.py (100%) rename demucs/{ => demucs}/apply.py (100%) rename demucs/{ => demucs}/audio.py (100%) rename demucs/{ => demucs}/audio_legacy.py (100%) rename demucs/{ => demucs}/augment.py (100%) rename demucs/{ => demucs}/demucs.py (100%) rename demucs/{ => demucs}/distrib.py (100%) rename demucs/{ => demucs}/ema.py (100%) rename demucs/{ => demucs}/evaluate.py (100%) rename demucs/{ => demucs}/grids/__init__.py (100%) rename demucs/{ => demucs}/grids/_explorers.py (100%) rename demucs/{ => demucs}/grids/mdx.py (100%) rename demucs/{ => demucs}/grids/mdx_extra.py (100%) rename demucs/{ => demucs}/grids/mdx_refine.py (100%) rename demucs/{ => demucs}/grids/mmi.py (100%) rename demucs/{ => demucs}/grids/mmi_ft.py (100%) rename demucs/{ => demucs}/grids/repro.py (100%) rename demucs/{ => demucs}/grids/repro_ft.py (100%) rename demucs/{ => demucs}/grids/sdx23.py (100%) rename demucs/{ => demucs}/hdemucs.py (100%) rename demucs/{ => demucs}/htdemucs.py (100%) rename demucs/{ => demucs}/pretrained.py (100%) rename demucs/{ => demucs}/py.typed (100%) rename demucs/{ => demucs}/remote/files.txt (100%) rename demucs/{ => demucs}/remote/hdemucs_mmi.yaml (100%) rename demucs/{ => demucs}/remote/htdemucs.yaml (100%) rename demucs/{ => demucs}/remote/htdemucs_6s.yaml (100%) rename demucs/{ => demucs}/remote/htdemucs_ft.yaml (100%) rename demucs/{ => demucs}/remote/mdx.yaml (100%) rename demucs/{ => demucs}/remote/mdx_extra.yaml (100%) rename demucs/{ => demucs}/remote/mdx_extra_q.yaml (100%) rename demucs/{ => demucs}/remote/mdx_q.yaml (100%) rename demucs/{ => demucs}/remote/repro_mdx_a.yaml (100%) rename demucs/{ => demucs}/remote/repro_mdx_a_hybrid_only.yaml (100%) rename demucs/{ => demucs}/remote/repro_mdx_a_time_only.yaml (100%) rename demucs/{ => demucs}/repitch.py (100%) rename demucs/{ => demucs}/repo.py (100%) rename demucs/{ => demucs}/separate.py (100%) rename demucs/{ => demucs}/solver.py (100%) rename demucs/{ => demucs}/spec.py (100%) rename demucs/{ => demucs}/states.py (100%) rename demucs/{ => demucs}/svd.py (100%) rename demucs/{ => demucs}/train.py (100%) rename demucs/{ => demucs}/transformer.py (100%) rename demucs/{ => demucs}/utils.py (100%) rename demucs/{ => demucs}/wav.py (100%) rename demucs/{ => demucs}/wdemucs.py (100%) create mode 100644 demucs/docs/api.md create mode 100644 demucs/docs/linux.md create mode 100644 demucs/docs/mac.md create mode 100644 demucs/docs/mdx.md create mode 100644 demucs/docs/release.md create mode 100644 demucs/docs/sdx23.md create mode 100644 demucs/docs/training.md create mode 100644 demucs/docs/windows.md create mode 100644 demucs/environment-cpu.yml create mode 100644 demucs/environment-cuda.yml create mode 100644 demucs/hubconf.py create mode 100644 demucs/mypy.ini create mode 100644 demucs/outputs.tar.gz create mode 100644 demucs/requirements.txt create mode 100644 demucs/requirements_minimal.txt create mode 100644 demucs/setup.cfg create mode 100644 demucs/setup.py create mode 100644 demucs/test.mp3 create mode 100644 demucs/tools/__init__.py create mode 100644 demucs/tools/automix.py create mode 100644 demucs/tools/bench.py create mode 100644 demucs/tools/convert.py create mode 100644 demucs/tools/export.py create mode 100644 demucs/tools/test_pretrained.py create mode 100644 docs/docs/usage/5_speaker_identification.md create mode 100644 share/applications/buzz.desktop create mode 100644 tests/widgets/speaker_identification_widget_test.py create mode 160000 whisper_diarization diff --git a/.coveragerc b/.coveragerc index c8f35eab..566ba584 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,9 +1,12 @@ [run] omit = buzz/whisper_cpp/* + buzz/transcriber/local_whisper_cpp_server_transcriber.py *_test.py demucs/* - buzz/transcriber/local_whisper_cpp_server_transcriber.py + whisper_diarization/* + deepmultilingualpunctuation/* + ctc_forced_aligner/* [html] directory = coverage/html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 010e183a..54e7158d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,10 +70,10 @@ jobs: ~/AppData/Local/Buzz/Buzz/Cache key: whisper-models - - uses: AnimMouse/setup-ffmpeg@v1.2.1 + - uses: AnimMouse/setup-ffmpeg@v1 id: setup-ffmpeg with: - version: ${{ matrix.os == 'macos-15-intel' && '7.1.1' || matrix.os == 'macos-latest' && '71' || '7.1' }} + version: ${{ matrix.os == 'macos-15-intel' && '7.1.1' || matrix.os == 'macos-latest' && '80' || '8.0' }} - name: Test ffmpeg run: ffmpeg -i ./testdata/audio-long.mp3 ./testdata/audio-long.wav diff --git a/.github/workflows/snapcraft.yml b/.github/workflows/snapcraft.yml index 0b1ecec3..286fe59c 100644 --- a/.github/workflows/snapcraft.yml +++ b/.github/workflows/snapcraft.yml @@ -15,9 +15,22 @@ concurrency: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 90 + env: + BUZZ_DISABLE_TELEMETRY: true outputs: snap: ${{ steps.snapcraft.outputs.snap }} steps: + # Ideas from https://github.com/orgs/community/discussions/25678 + - name: Remove unused build tools + run: | + sudo apt-get remove -y '^llvm-.*' + sudo apt-get remove -y 'php.*' + sudo apt-get remove -y azure-cli google-cloud-sdk hhvm google-chrome-stable firefox powershell mono-devel || true + sudo apt-get autoremove -y + sudo apt-get clean + python -m pip cache purge + rm -rf /opt/hostedtoolcache || true - name: Maximize build space uses: easimon/maximize-build-space@master with: diff --git a/.gitignore b/.gitignore index f0c01776..66f3b3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ benchmarks.json /coverage/ /wheelhouse/ /.flatpak-builder -/repo \ No newline at end of file +/repo +/nemo_msdd_configs diff --git a/.gitmodules b/.gitmodules index fa83e220..1c0c8b24 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,15 @@ [submodule "whisper.cpp"] path = whisper.cpp url = https://github.com/ggerganov/whisper.cpp +[submodule "whisper_diarization"] + path = whisper_diarization + url = https://github.com/MahmoudAshraf97/whisper-diarization +[submodule "demucs"] + path = demucs + url = https://github.com/MahmoudAshraf97/demucs.git +[submodule "deepmultilingualpunctuation"] + path = deepmultilingualpunctuation + url = https://github.com/oliverguhr/deepmultilingualpunctuation.git +[submodule "ctc_forced_aligner"] + path = ctc_forced_aligner + url = https://github.com/MahmoudAshraf97/ctc-forced-aligner.git diff --git a/Buzz.spec b/Buzz.spec index 0f4e8edb..c2d93bb1 100644 --- a/Buzz.spec +++ b/Buzz.spec @@ -30,7 +30,13 @@ datas += collect_data_files("transformers", include_py_files=True) datas += collect_data_files("faster_whisper", include_py_files=True) datas += collect_data_files("stable_whisper", include_py_files=True) datas += collect_data_files("whisper") -datas += [("demucs", "demucs")] +datas += collect_data_files("demucs", include_py_files=True) +datas += collect_data_files("whisper_diarization", include_py_files=True) +datas += collect_data_files("deepmultilingualpunctuation", include_py_files=True) +datas += collect_data_files("ctc_forced_aligner", include_py_files=True) +datas += collect_data_files("nemo", include_py_files=True) +datas += collect_data_files("lightning_fabric", include_py_files=True) +datas += collect_data_files("pytorch_lightning", include_py_files=True) datas += [("buzz/assets/*", "assets")] datas += [("buzz/locale", "locale")] datas += [("buzz/schema.sql", ".")] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8a540cf..3b2fddf4 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,8 +53,7 @@ sudo apt-get install --no-install-recommends libyaml-dev libtbb-dev libxkbcommon ``` On versions prior to Ubuntu 24.04 install `sudo apt-get install --no-install-recommends libegl1-mesa` 5. Install the dependencies `uv sync` -6. Build Buzz `uv build` -7. Run Buzz `uv run buzz` +6. Run Buzz `uv run buzz` #### Necessary dependencies for Faster Whisper on GPU @@ -81,8 +80,7 @@ On versions prior to Ubuntu 24.04 install `sudo apt-get install --no-install-rec 3. Install uv `curl -LsSf https://astral.sh/uv/install.sh | sh` (or `brew install uv`) 4. Install system dependencies you may be missing `brew install ffmpeg` 5. Install the dependencies `uv sync` -6. Build Buzz `uv build` -7. Run Buzz `uv run buzz` +6. Run Buzz `uv run buzz` diff --git a/Makefile b/Makefile index af2aa9a1..92315dbd 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -version := 1.3.4 +# Change also in pyproject.toml and buzz/__version__.py +version := 1.4.0 mac_app_path := ./dist/Buzz.app mac_zip_path := ./dist/Buzz-${version}-mac.zip @@ -28,7 +29,7 @@ else rm -rf dist/* || true endif -COVERAGE_THRESHOLD := 75 +COVERAGE_THRESHOLD := 70 test: buzz/whisper_cpp pytest -s -vv --cov=buzz --cov-report=xml --cov-report=html --benchmark-skip --cov-fail-under=${COVERAGE_THRESHOLD} --cov-config=.coveragerc @@ -67,7 +68,7 @@ ifeq ($(shell uname -s), Linux) cp whisper.cpp/build/bin/whisper-server buzz/whisper_cpp/ || true cp whisper.cpp/build/src/libwhisper.so buzz/whisper_cpp/ || true cp whisper.cpp/build/src/libwhisper.so.1 buzz/whisper_cpp/ || true - cp whisper.cpp/build/src/libwhisper.so.1.7.6 buzz/whisper_cpp/ || true + cp whisper.cpp/build/src/libwhisper.so.1.8.2 buzz/whisper_cpp/ || true cp whisper.cpp/build/ggml/src/libggml.so buzz/whisper_cpp/ || true cp whisper.cpp/build/ggml/src/libggml-base.so buzz/whisper_cpp/ || true cp whisper.cpp/build/ggml/src/libggml-cpu.so buzz/whisper_cpp/ || true diff --git a/README.md b/README.md index 55c62f9d..7b5db725 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ OpenAI's [Whisper](https://github.com/openai/whisper). [![Github all releases](https://img.shields.io/github/downloads/chidiwilliams/buzz/total.svg)](https://GitHub.com/chidiwilliams/buzz/releases/)

    -

    Buzz is better on the App Store. Get a Mac-native version of Buzz with a cleaner look, audio playback, drag-and-drop import, transcript editing, search, and much more.

    +

    An older version of Buzz available on the App Store. Get a Mac-native version of Buzz with a cleaner look, audio playback, drag-and-drop import, transcript editing, search, and much more.

    Download on the Mac App Store
    diff --git a/buzz/__version__.py b/buzz/__version__.py index 4a16f216..af63e4ae 100644 --- a/buzz/__version__.py +++ b/buzz/__version__.py @@ -1 +1 @@ -VERSION = "1.3.4" +VERSION = "1.4.0" diff --git a/buzz/assets/speaker-identification.svg b/buzz/assets/speaker-identification.svg new file mode 100644 index 00000000..cfea8b41 --- /dev/null +++ b/buzz/assets/speaker-identification.svg @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/buzz/buzz.py b/buzz/buzz.py index e6f755f7..6c4750d6 100644 --- a/buzz/buzz.py +++ b/buzz/buzz.py @@ -56,6 +56,17 @@ def main(): format=log_format, ) + # Silence noisy third-party library loggers + logging.getLogger("matplotlib").setLevel(logging.WARNING) + logging.getLogger("graphviz").setLevel(logging.WARNING) + logging.getLogger("nemo_logger").setLevel(logging.ERROR) + logging.getLogger("numba").setLevel(logging.WARNING) + logging.getLogger("torio._extension.utils").setLevel(logging.WARNING) + logging.getLogger("export_config_manager").setLevel(logging.WARNING) + logging.getLogger("training_telemetry_provider").setLevel(logging.ERROR) + logging.getLogger("default_recorder").setLevel(logging.WARNING) + logging.getLogger("config").setLevel(logging.WARNING) + if getattr(sys, "frozen", False) is False: stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setLevel(logging.DEBUG) diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index f6cf91fb..b056981f 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -7,7 +7,7 @@ from uuid import UUID from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot -from demucs import api as demucsApi +from demucs.demucs import api as demucsApi from buzz.model_loader import ModelType from buzz.transcriber.file_transcriber import FileTranscriber diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index e88359f6..aaf56614 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -554,64 +554,68 @@ msgstr "Veure" msgid "Timestamps" msgstr "Marqua de temps" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Exporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Traduir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "Cerca" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/amaga la barra de cerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "Cerca:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "Introduïu el text a cercar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "Coincidència anterior (Maj+Retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "Coincidència següent (retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "Neteja" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "Controls de reproducció:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "Segment de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Activa/desactiva el bucle en fer clic als segments de transcripció" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "Segueix l'àudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -619,75 +623,146 @@ msgstr "" "Activa/desactiva seguint la posició d'àudio actual a la transcripció. Quan " "està activada, es desplaça automàticament al text actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "Desplaça't fins a l'actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "Desplaçar-se fins al text que es parla actualment" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "1 de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr " coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "No s'ha trobat cap coincidència" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr " de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "Clau API necessària" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Introduïu la clau API d'OpenAI a les preferències" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Opcions de redimensionament" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Longitud desitjada dels subtítols" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Opcions de fusió" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Fusiona per buit" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Divideix per puntuació" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Divideix per la longitud màxima" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Fusiona" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Cancel·la la transcripció" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Desa el fitxer" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Desa el fitxer" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index 7d356c67..fe698374 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -552,138 +552,213 @@ msgstr "Vis" msgid "Timestamps" msgstr "Tidsstempler" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Eksporter" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Oversæt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Behandel størrelse" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "API-nøgle påkrævet" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Indtast venligst OpenAI API-nøgle i indstillinger" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Størrelsesindstillinger" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Ønskede undertekst længde" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Sammenfletningsindstillinger" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Sammenflet ved hul" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Split ved punktum" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Split ved max længde" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Sammenflet" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Afbryd transkription" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Gem fil" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Gem fil" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index 14ebc504..1b547455 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -552,138 +552,213 @@ msgstr "Anzeigen" msgid "Timestamps" msgstr "Zeitstempel" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Export" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Übersetzen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Größe ändern" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "API-Schlüssel erforderlich" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Bitte geben Sie den OpenAI-API-Schlüssel in den Einstellungen ein" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Größenänderungsoptionen" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Gewünschte Untertitellänge" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Zusammenführungsoptionen" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Nach Abstand zusammenführen" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Durch Satzzeichen getrennt" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Aufgeteilt nach maximaler Länge" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Vereinigen" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Transkription abbrechen" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Datei speichern" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Datei speichern" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index 87f47cea..02bac9f4 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -540,138 +540,211 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +msgid "5/8 Preparing transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +msgid "Save" +msgstr "" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index 133209e1..1c7d3e0c 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -589,66 +589,70 @@ msgstr "Ver" msgid "Timestamps" msgstr "Marcas de tiempo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Traducir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Cambiar el tamaño" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "Buscar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar barra de búsqueda (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "Encontrar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "Introducir texto para encontrar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "Coincidencia anterior (Mayús+Intro)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "Siguiente coincidencia (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "Limpiar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "Controles de reproducción:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "Segmento de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Activar/desactivar la reproducción en bucle al hacer clic en segmentos de la " "transcripción" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "Seguir audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -657,75 +661,148 @@ msgstr "" "transcripción. Cuando está activado, se desplaza automáticamente al texto " "actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "Desplácese hasta Actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "Desplazarse hasta el texto hablado actualmente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "1 de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr " coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "No se encontraron coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr " de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "Clave de API requerida" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Ingrese la clave API de OpenAI en las preferencias" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Opciones de cambio de tamaño" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Longitud deseada de los subtítulos" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Opciones de fusión" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Fusión por hueco" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Dividido por puntuación" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Dividido por la longitud máxima" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Fusión" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +# automatic translation +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Cancelar transcripción" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +# automatic translation +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Guardar archivo" + # automatic translation #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index 127a3b0c..fd231e6a 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -555,65 +555,69 @@ msgstr "Visualizza" msgid "Timestamps" msgstr "Timestamp" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Esporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Tradurre" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Ridimensionare" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "Trova" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/Nascondi barra di ricerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "Trova:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "Inserisci il testo per trovare..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "Corrispondenza precedente (Maiusc+Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "Prossima corrispondenza (Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "Elimina" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "Controlli di riproduzione:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "Ciclo di segmento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Abilita/disabilita il loop quando si fa clic sui segmenti della trascrizione" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "Segui Audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -622,75 +626,146 @@ msgstr "" "trascrizione. Quando abilitato, scorre automaticamente fino al testo " "corrente." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "Scorri fino al Corrente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "Scorrere fino al testo attualmente pronunciato" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "1 di 100+ corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "1 di" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "Nessuna corrispondenza trovata" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr " di oltre 100 corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr " di " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "Chiave API richiesta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Inserisci la chiave API OpenAI nelle preferenze" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Opzioni di ridimensionamento" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Lunghezza desiderata dei sottotitoli" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Opzioni di unione" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Unito per spazio" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Diviso per punteggiatura" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Diviso per lunghezza massima" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Unione" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Inizio trascrizione..." + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Salva file" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Salva file" diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index b5ec2b11..2bdda8b2 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -548,139 +548,214 @@ msgstr "表示" msgid "Timestamps" msgstr "タイムスタンプ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "出力" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "翻訳" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "リサイズ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "APIキーが必要" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "設定画面でOpenAI APIキーを入力してください" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 #, fuzzy msgid "Resize Options" msgstr "リサイズ" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "希望する字幕の長さ" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "文字起こしをキャンセルする" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "ファイルを保存" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "ファイルを保存" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index 18f799f5..df528784 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -3,13 +3,12 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" -"PO-Revision-Date: 2025-11-28 16:50+0200\n" +"POT-Creation-Date: 2025-11-23 13:02+0200\n" +"PO-Revision-Date: 2025-11-23 12:58+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -558,64 +557,68 @@ msgstr "Skats" msgid "Timestamps" msgstr "Laiks" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Eksportēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Tulkot" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Mainīt garumu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "Noteikt runātājus" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "Meklēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Rādīt/Slēpt meklēšanas joslu (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "Meklēt:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "Ievadiet meklējamo..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "Iepriekšējais rezultāts (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "Nākamais rezultāts (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "Notīrīt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "Atskaņošanas iespējas:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "Atkārtot segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Nosaka vai atkārtot izvēlēto segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "Sekot audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -623,75 +626,144 @@ msgstr "" "Nosaka vai atskaņojot audio iezīmētajam segmentam vajadzētu automātiski " "sekot tam kas tiek atskaņots." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "Pāriet uz tekošo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "Pāriet uz šobrīd atskaņojamo tesktu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "1 no 100+ " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "1 no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr " " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "Nekas nav atrasts" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr " no 100+" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr " no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "API atslēgas kļūda" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Lūdzu ievadiet OpenAI API atslēgu iestatījumos" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Garuma maiņas iestatījumi" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Vēlamais teksta garums" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Apvienošanas iestatījumi" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Apvienot pēc attāluma" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Dalīt pie pieturzīmēm" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Dalīt pie maksimālā garuma" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Apvienot" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "1/8 Apkopo transkripcijas" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "2/8 Ielādē audio" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "3/8 Ielādē identifikācijas modeli" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "4/8 Apstrādā audio" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +msgid "5/8 Preparing transcripts" +msgstr "5/8 Sagatavo transkripcijas" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "6/8 Nosaka runātājus" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "0/0 Kļūda nosakot runātājus" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "7/8 Marķē runātāju teikumus" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "8/8 Runātāju noteikšana pabeigta" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "1. solis: Runātāju noteikšana" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "Noteikt" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "Gatavs noteikt runātājus" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "Audio datne nav atrasta" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "2. solis: Runātāju identifikācija" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "Atskaņot paraugu" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "Apvienot secīgus runātāja teikumus" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +msgid "Save" +msgstr "Saglabāt" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Saglabāt failu" diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index 2e21acc2..75b59ea1 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -552,138 +552,213 @@ msgstr "Bekijken" msgid "Timestamps" msgstr "Tijdstippen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Exporteren" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Vertalen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Grootte" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "Api-sleutel vereist" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Voer de OpenAI-api-sleutel in in de instellingen" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Grootteopties" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Voorkeurslengte van ondertiteling" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Samenvoegopties" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Samenvoegen op basis van tussenruimte" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Splitsen op basis van leestekens" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Splitsen op basis van max. lengte" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Samenvoegen" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Transcriptie wissen" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Bestand opslaan" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Bestand opslaan" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index 2d452294..261fcd5b 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -561,138 +561,213 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Anuluj transkrypcję" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Zapisz plik" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 #, fuzzy msgid "Save File" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 25165acd..39ae4c38 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -552,64 +552,68 @@ msgstr "Visualizar" msgid "Timestamps" msgstr "Marcações de tempo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Traduzir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "Procurar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar a Barra de Pesquisa" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "Procurar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "Digite o texto a procurar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "Encontro prévio (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "Póximo encontro (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "Limpar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "Controles de Reprodução:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "Segmento de Loop" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Habilitar/desabilitar loop ao clicar em segmentos de transcrição" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "Siga o Áudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -617,75 +621,146 @@ msgstr "" "Ativar/desativar a opção de seguir a posição atual do áudio na transcrição. " "Quando ativado, rola automaticamente para o texto atual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "Rolar para o Atual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "Role até o texto falado no momento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "1 de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr " encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "Nada encontrado" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr " de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "Chave API Necessária" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Insira a chave API OpenAI nas preferências" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "Opções de Redimensionamento" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "Duração desejada da legenda" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "Opções de Mesclagem" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "Mesclar por intervalo" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "Dividir por pontuação" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "Dividir por tamanho máximo" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "Mesclar" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Iniciando transcrição..." + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Salvar Arquivo" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Salvar Arquivo" diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index f45a5184..2ef57f95 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -550,138 +550,213 @@ msgstr "Вигляд" msgid "Timestamps" msgstr "Позначки часу" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "Експорт" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "Перекласти" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "Потрібен API-ключ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "Будь ласка, введіть API-ключ OpenAI в налаштуваннях" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "Скасувати транскрипцію" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "Зберегти файл" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 msgid "Save File" msgstr "Зберегти файл" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index 0e9154a2..5cdb3a7b 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -562,139 +562,214 @@ msgstr "查看" msgid "Timestamps" msgstr "时间戳" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "导出" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "翻译" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "调整大小" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "需要API Key" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "请在偏好设置中输入OpenAI API Key" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 #, fuzzy msgid "Resize Options" msgstr "调整大小" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "所需字幕长度" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "合并选项" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "按间隔合并" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "按标点符号拆分" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "按最大长度拆分" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "合并" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "取消识别" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "保存文件" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 #, fuzzy msgid "Save File" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index ed67c2c8..fd0fe400 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-28 16:49+0200\n" +"POT-Creation-Date: 2025-11-23 12:55+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -557,138 +557,213 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:230 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:240 -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:177 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:252 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +msgid "Identify Speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:255 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:339 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:347 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:355 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:382 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:387 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:389 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:395 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:397 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:446 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:768 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:770 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:775 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:834 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:836 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1191 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1192 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 msgid "Please enter OpenAI API Key in preferences" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:159 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:157 msgid "Resize Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:170 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:168 msgid "Desired subtitle length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:195 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:193 msgid "Merge Options" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:206 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:204 msgid "Merge by gap" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:214 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:212 msgid "Split by punctuation" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:222 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:220 msgid "Split by max length" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:232 msgid "Merge" msgstr "" +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +msgid "1/8 Collecting transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +msgid "2/8 Loading audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +msgid "3/8 Loading alignment model" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +msgid "4/8 Processing audio" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#, fuzzy +msgid "5/8 Preparing transcripts" +msgstr "取消錄製" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +msgid "6/8 Identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +msgid "7/8 Mapping speakers to transcripts" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +msgid "8/8 Identification done" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +msgid "Step 1: Identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +msgid "Identify" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +msgid "Ready to identify speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +msgid "Audio file not found" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +msgid "Step 2: Name speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +msgid "Play sample" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +msgid "Merge speaker sentences" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#, fuzzy +msgid "Save" +msgstr "檔案" + #: buzz/widgets/transcription_viewer/export_transcription_menu.py:82 #, fuzzy msgid "Save File" diff --git a/buzz/transcriber/whisper_cpp.py b/buzz/transcriber/whisper_cpp.py index 8f12aec8..7bea69fb 100644 --- a/buzz/transcriber/whisper_cpp.py +++ b/buzz/transcriber/whisper_cpp.py @@ -4,7 +4,6 @@ import sys import logging import subprocess import json -import tempfile from typing import List from buzz.assets import APP_BASE_DIR from buzz.transcriber.transcriber import Segment, Task, FileTranscriptionTask @@ -58,9 +57,7 @@ class WhisperCpp: file_to_process = task.file_path if file_ext not in supported_formats: - # Create temporary WAV file - temp_dir = tempfile.gettempdir() - temp_file = os.path.join(temp_dir, f"buzz_temp_{os.path.basename(task.file_path)}.wav") + temp_file = task.file_path + ".wav" logging.info(f"Converting {task.file_path} to WAV format") diff --git a/buzz/widgets/icon.py b/buzz/widgets/icon.py index cac92525..1efca875 100644 --- a/buzz/widgets/icon.py +++ b/buzz/widgets/icon.py @@ -82,6 +82,10 @@ class ResizeIcon(Icon): def __init__(self, parent: QWidget): super().__init__(get_path("assets/resize_black.svg"), parent) +class SpeakerIdentificationIcon(Icon): + def __init__(self, parent: QWidget): + super().__init__(get_path("assets/speaker-identification.svg"), parent) + class VisibilityIcon(Icon): def __init__(self, parent: QWidget): super().__init__( diff --git a/buzz/widgets/transcription_viewer/speaker_identification_widget.py b/buzz/widgets/transcription_viewer/speaker_identification_widget.py new file mode 100644 index 00000000..cbbe6216 --- /dev/null +++ b/buzz/widgets/transcription_viewer/speaker_identification_widget.py @@ -0,0 +1,504 @@ +import re +import os +import logging +import faster_whisper +import torch +import random +from typing import Optional +from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput +from PyQt6.QtCore import Qt, QThread, QObject, pyqtSignal, QUrl, QTimer +from PyQt6.QtGui import QFont +from PyQt6.QtWidgets import ( + QWidget, + QFormLayout, + QVBoxLayout, + QHBoxLayout, + QLabel, + QProgressBar, + QPushButton, + QCheckBox, + QGroupBox, + QSpacerItem, + QSizePolicy, + QLayout, +) +from buzz.locale import _ +from buzz.db.entity.transcription import Transcription +from buzz.db.service.transcription_service import TranscriptionService +from buzz.paths import file_path_as_title +from buzz.settings.settings import Settings +from buzz.widgets.line_edit import LineEdit +from buzz.transcriber.transcriber import Segment + +from ctc_forced_aligner.ctc_forced_aligner import ( + generate_emissions, + get_alignments, + get_spans, + load_alignment_model, + postprocess_results, + preprocess_text, +) +from whisper_diarization.helpers import ( + get_realigned_ws_mapping_with_punctuation, + get_sentences_speaker_mapping, + get_words_speaker_mapping, + langs_to_iso, + punct_model_langs, +) +from deepmultilingualpunctuation.deepmultilingualpunctuation import PunctuationModel +from whisper_diarization.diarization import MSDDDiarizer + +SENTENCE_END = re.compile(r'.*[.!?。!?]') + +class IdentificationWorker(QObject): + finished = pyqtSignal(list) + progress_update = pyqtSignal(str) + + def __init__(self, transcription, transcription_service): + super().__init__() + self.transcription = transcription + self.transcription_service = transcription_service + + def get_transcript(self, audio, **kwargs) -> dict: + buzz_segments = self.transcription_service.get_transcription_segments( + transcription_id=self.transcription.id_as_uuid + ) + + segments = [] + words = [] + text = "" + for buzz_segment in buzz_segments: + words.append({ + 'word': buzz_segment.text + " ", + 'start': buzz_segment.start_time / 100, + 'end': buzz_segment.end_time / 100, + }) + text += buzz_segment.text + " " + + if SENTENCE_END.match(buzz_segment.text): + segments.append({ + 'text': text, + 'words': words + }) + words = [] + text = "" + + return { + 'language': self.transcription.language, + 'segments': segments + } + + def run(self): + self.progress_update.emit(_("1/8 Collecting transcripts")) + + # Step 1 - Get transcript + # TODO - Add detected language to the transcript, detect and store separately in metadata + # Will also be relevant for template parsing of transcript file names + # - See diarize.py for example on how to get this info from whisper transcript, maybe other whisper models also have it + language = self.transcription.language if self.transcription.language else "en" + + segments = self.transcription_service.get_transcription_segments( + transcription_id=self.transcription.id_as_uuid + ) + + full_transcript = "".join(segment.text for segment in segments) + + self.progress_update.emit(_("2/8 Loading audio")) + audio_waveform = faster_whisper.decode_audio(self.transcription.file) + + # Step 2 - Forced alignment + force_cpu = os.getenv("BUZZ_FORCE_CPU", "false") + use_cuda = torch.cuda.is_available() and force_cpu == "false" + device = "cuda" if use_cuda else "cpu" + torch_dtype = torch.float16 if use_cuda else torch.float32 + + self.progress_update.emit(_("3/8 Loading alignment model")) + alignment_model, alignment_tokenizer = load_alignment_model( + device, + dtype=torch_dtype, + ) + + self.progress_update.emit(_("4/8 Processing audio")) + emissions, stride = generate_emissions( + alignment_model, + torch.from_numpy(audio_waveform) + .to(alignment_model.dtype) + .to(alignment_model.device), + batch_size=8, + ) + + del alignment_model + torch.cuda.empty_cache() + + self.progress_update.emit(_("5/8 Preparing transcripts")) + tokens_starred, text_starred = preprocess_text( + full_transcript, + romanize=True, + language=langs_to_iso[language], + ) + + segments, scores, blank_token = get_alignments( + emissions, + tokens_starred, + alignment_tokenizer, + ) + + spans = get_spans(tokens_starred, segments, blank_token) + + word_timestamps = postprocess_results(text_starred, spans, stride, scores) + + # Step 3 - Diarization + self.progress_update.emit(_("6/8 Identifying speakers")) + + try: + diarizer_model = MSDDDiarizer(device) + speaker_ts = diarizer_model.diarize(torch.from_numpy(audio_waveform).unsqueeze(0)) + + except Exception as e: + self.progress_update.emit(_("0/0 Error identifying speakers")) + logging.error(f"Error during diarization: {e}") + return + finally: + del diarizer_model + torch.cuda.empty_cache() + + # Step 4 - Reading timestamps <> Speaker Labels mapping + self.progress_update.emit(_("7/8 Mapping speakers to transcripts")) + + wsm = get_words_speaker_mapping(word_timestamps, speaker_ts, "start") + + if language in punct_model_langs: + # restoring punctuation in the transcript to help realign the sentences + punct_model = PunctuationModel(model="kredor/punctuate-all") + + words_list = list(map(lambda x: x["word"], wsm)) + + labled_words = punct_model.predict(words_list, chunk_size=230) + + ending_puncts = ".?!。!?" + model_puncts = ".,;:!?。!?" + + # We don't want to punctuate U.S.A. with a period. Right? + is_acronym = lambda x: re.fullmatch(r"\b(?:[a-zA-Z]\.){2,}", x) + + for word_dict, labeled_tuple in zip(wsm, labled_words): + word = word_dict["word"] + if ( + word + and labeled_tuple[1] in ending_puncts + and (word[-1] not in model_puncts or is_acronym(word)) + ): + word += labeled_tuple[1] + if word.endswith(".."): + word = word.rstrip(".") + word_dict["word"] = word + + else: + logging.warning( + f"Punctuation restoration is not available for {language} language." + " Using the original punctuation." + ) + + wsm = get_realigned_ws_mapping_with_punctuation(wsm) + ssm = get_sentences_speaker_mapping(wsm, speaker_ts) + + self.progress_update.emit(_("8/8 Identification done")) + self.finished.emit(ssm) + + +class SpeakerIdentificationWidget(QWidget): + resize_button_clicked = pyqtSignal() + transcription: Transcription + settings = Settings() + + def __init__( + self, + transcription: Transcription, + transcription_service: TranscriptionService, + parent: Optional["QWidget"] = None, + flags: Qt.WindowType = Qt.WindowType.Widget, + transcriptions_updated_signal: Optional[pyqtSignal] = None, + ) -> None: + super().__init__(parent, flags) + self.transcription = transcription + self.transcription_service = transcription_service + self.transcriptions_updated_signal = transcriptions_updated_signal + + self.identification_result = None + + self.thread = None + self.worker = None + + self.setMinimumWidth(650) + self.setMinimumHeight(400) + + self.setWindowTitle(file_path_as_title(transcription.file)) + + layout = QFormLayout(self) + layout.setSizeConstraint(QLayout.SizeConstraint.SetMinAndMaxSize) + + # Step 1: Identify speakers + step_1_label = QLabel(_("Step 1: Identify speakers"), self) + font = step_1_label.font() + font.setWeight(QFont.Weight.Bold) + step_1_label.setFont(font) + layout.addRow(step_1_label) + + step_1_group_box = QGroupBox(self) + step_1_group_box.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + step_1_layout = QVBoxLayout(step_1_group_box) + + self.step_1_row = QHBoxLayout() + + self.step_1_button = QPushButton(_("Identify")) + self.step_1_button.setMinimumWidth(200) + self.step_1_button.clicked.connect(self.on_identify_button_clicked) + + # Progress container with label and bar + progress_container = QVBoxLayout() + + self.progress_label = QLabel(self) + if os.path.isfile(self.transcription.file): + self.progress_label.setText(_("Ready to identify speakers")) + else: + self.progress_label.setText(_("Audio file not found")) + self.step_1_button.setEnabled(False) + + self.progress_bar = QProgressBar(self) + self.progress_bar.setMinimumWidth(400) + self.progress_bar.setRange(0, 8) + self.progress_bar.setValue(0) + + progress_container.addWidget(self.progress_label) + progress_container.addWidget(self.progress_bar) + + self.step_1_row.addLayout(progress_container) + + self.step_1_row.addWidget(self.step_1_button, alignment=Qt.AlignmentFlag.AlignTop) + + step_1_layout.addLayout(self.step_1_row) + + layout.addRow(step_1_group_box) + + # Spacer + spacer = QSpacerItem(0, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + layout.addItem(spacer) + + # Step 2: Name speakers + step_2_label = QLabel(_("Step 2: Name speakers"), self) + font = step_2_label.font() + font.setWeight(QFont.Weight.Bold) + step_2_label.setFont(font) + layout.addRow(step_2_label) + + self.step_2_group_box = QGroupBox(self) + self.step_2_group_box.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.step_2_group_box.setEnabled(False) + step_2_layout = QVBoxLayout(self.step_2_group_box) + + self.speaker_preview_row = QVBoxLayout() + + self.speaker_0_input = LineEdit("Speaker 0", self) + + self.speaker_0_preview_button = QPushButton(_("Play sample")) + self.speaker_0_preview_button.setMinimumWidth(200) + self.speaker_0_preview_button.clicked.connect(lambda: self.on_speaker_preview("Speaker 0")) + + speaker_0_layout = QHBoxLayout() + speaker_0_layout.addWidget(self.speaker_0_input) + speaker_0_layout.addWidget(self.speaker_0_preview_button) + + self.speaker_preview_row.addLayout(speaker_0_layout) + + step_2_layout.addLayout(self.speaker_preview_row) + + layout.addRow(self.step_2_group_box) + + # Save button + self.merge_speaker_sentences = QCheckBox(_("Merge speaker sentences")) + self.merge_speaker_sentences.setChecked(True) + self.merge_speaker_sentences.setEnabled(False) + self.merge_speaker_sentences.setMinimumWidth(250) + + self.save_button = QPushButton(_("Save")) + self.save_button.setEnabled(False) + self.save_button.clicked.connect(self.on_save_button_clicked) + + layout.addRow(self.merge_speaker_sentences) + layout.addRow(self.save_button) + + self.setLayout(layout) + + # Invisible preview player + url = QUrl.fromLocalFile(self.transcription.file) + self.player = QMediaPlayer() + self.audio_output = QAudioOutput() + self.player.setAudioOutput(self.audio_output) + self.player.setSource(url) + self.player_timer = None + + def on_identify_button_clicked(self): + self.step_1_button.setEnabled(False) + + self.thread = QThread() + self.worker = IdentificationWorker( + self.transcription, + self.transcription_service + ) + self.worker.moveToThread(self.thread) + self.thread.started.connect(self.worker.run) + self.worker.finished.connect(self.thread.quit) + self.worker.finished.connect(self.worker.deleteLater) + self.thread.finished.connect(self.thread.deleteLater) + self.worker.finished.connect(self.on_identification_finished) + self.worker.progress_update.connect(self.on_progress_update) + + self.thread.start() + + def on_progress_update(self, progress): + self.progress_label.setText(progress) + + progress_value = 0 + if progress and progress[0].isdigit(): + progress_value = int(progress[0]) + self.progress_bar.setValue(progress_value) + else: + logging.error(f"Invalid progress format: {progress}") + + if progress_value == 8: + self.step_2_group_box.setEnabled(True) + self.merge_speaker_sentences.setEnabled(True) + self.save_button.setEnabled(True) + + def on_identification_finished(self, result): + self.identification_result = result + + unique_speakers = {entry['speaker'] for entry in result} + + while self.speaker_preview_row.count(): + item = self.speaker_preview_row.takeAt(0) + widget = item.widget() + if widget: + widget.deleteLater() + else: + layout = item.layout() + if layout: + while layout.count(): + sub_item = layout.takeAt(0) + sub_widget = sub_item.widget() + if sub_widget: + sub_widget.deleteLater() + + for speaker in sorted(unique_speakers): + speaker_input = LineEdit(speaker, self) + speaker_input.setMinimumWidth(200) + + speaker_preview_button = QPushButton(_("Play sample")) + speaker_preview_button.setMinimumWidth(200) + speaker_preview_button.clicked.connect(lambda checked, s=speaker: self.on_speaker_preview(s)) + + speaker_layout = QHBoxLayout() + speaker_layout.addWidget(speaker_input) + speaker_layout.addWidget(speaker_preview_button) + + self.speaker_preview_row.addLayout(speaker_layout) + + def on_speaker_preview(self, speaker_id): + if self.player_timer: + self.player_timer.stop() + + speaker_records = [record for record in self.identification_result if record['speaker'] == speaker_id] + + if speaker_records: + random_record = random.choice(speaker_records) + + start_time = random_record['start_time'] + end_time = random_record['end_time'] + + self.player.setPosition(int(start_time)) + self.player.play() + + self.player_timer = QTimer(self) + self.player_timer.setSingleShot(True) + self.player_timer.timeout.connect(self.player.stop) + self.player_timer.start(min(end_time, 10 * 1000)) # 10 seconds + + def on_save_button_clicked(self): + speaker_names = [] + for i in range(self.speaker_preview_row.count()): + item = self.speaker_preview_row.itemAt(i) + if item.layout(): + for j in range(item.layout().count()): + sub_item = item.layout().itemAt(j) + widget = sub_item.widget() + if isinstance(widget, LineEdit): + speaker_names.append(widget.text()) + + unique_speakers = {entry['speaker'] for entry in self.identification_result} + original_speakers = sorted(unique_speakers) + speaker_mapping = dict(zip(original_speakers, speaker_names)) + + segments = [] + if self.merge_speaker_sentences.isChecked(): + previous_segment = None + + for entry in self.identification_result: + speaker_name = speaker_mapping.get(entry['speaker'], entry['speaker']) + + if previous_segment and previous_segment['speaker'] == speaker_name: + previous_segment['end_time'] = entry['end_time'] + previous_segment['text'] += " " + entry['text'] + else: + if previous_segment: + segment = Segment( + start=previous_segment['start_time'], + end=previous_segment['end_time'], + text=f"{previous_segment['speaker']}: {previous_segment['text']}" + ) + segments.append(segment) + previous_segment = { + 'start_time': entry['start_time'], + 'end_time': entry['end_time'], + 'speaker': speaker_name, + 'text': entry['text'] + } + + if previous_segment: + segment = Segment( + start=previous_segment['start_time'], + end=previous_segment['end_time'], + text=f"{previous_segment['speaker']}: {previous_segment['text']}" + ) + segments.append(segment) + else: + for entry in self.identification_result: + speaker_name = speaker_mapping.get(entry['speaker'], entry['speaker']) + segment = Segment( + start=entry['start_time'], + end=entry['end_time'], + text=f"{speaker_name}: {entry['text']}" + ) + segments.append(segment) + + new_transcript_id = self.transcription_service.copy_transcription( + self.transcription.id_as_uuid + ) + + self.transcription_service.update_transcription_as_completed(new_transcript_id, segments) + + # TODO - See if we can get rows in the transcription viewer to be of variable height + # If text is longer they should expand + if self.transcriptions_updated_signal: + self.transcriptions_updated_signal.emit(new_transcript_id) + + self.player.stop() + + if self.player_timer: + self.player_timer.stop() + + self.close() + + def closeEvent(self, event): + self.hide() + + super().closeEvent(event) diff --git a/buzz/widgets/transcription_viewer/transcription_resizer_widget.py b/buzz/widgets/transcription_viewer/transcription_resizer_widget.py index a873eb0c..cb8dfcfc 100644 --- a/buzz/widgets/transcription_viewer/transcription_resizer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_resizer_widget.py @@ -37,8 +37,7 @@ from buzz.widgets.preferences_dialog.models.file_transcription_preferences impor SENTENCE_END = re.compile(r'.*[.!?。!?]') class TranscriptionWorker(QObject): - finished = pyqtSignal() - result_ready = pyqtSignal(list) + finished = pyqtSignal(list) def __init__(self, transcription, transcription_options, transcription_service, regroup_string: str): super().__init__() @@ -85,7 +84,7 @@ class TranscriptionWorker(QObject): if self.transcription_options.extract_speech and os.path.exists(speech_path): transcription_file = str(speech_path) transcription_file_exists = True - # TODO - Fix VAD and Silence suppression that fails to work/download VAd model in compilded form on Mac and Windows + # TODO - Fix VAD and Silence suppression that fails to work/download Vad model in compilded form on Mac and Windows try: result = stable_whisper.transcribe_any( @@ -113,8 +112,7 @@ class TranscriptionWorker(QObject): ) ) - self.result_ready.emit(segments) - self.finished.emit() + self.finished.emit(segments) class TranscriptionResizerWidget(QWidget): @@ -336,7 +334,7 @@ class TranscriptionResizerWidget(QWidget): self.worker.finished.connect(self.thread.quit) self.worker.finished.connect(self.worker.deleteLater) self.thread.finished.connect(self.thread.deleteLater) - self.worker.result_ready.connect(self.on_transcription_completed) + self.worker.finished.connect(self.on_transcription_completed) self.thread.start() diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index e77c2179..51b4e67c 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -1,5 +1,6 @@ import os import logging +import platform from typing import Optional from uuid import UUID @@ -38,6 +39,7 @@ from buzz.widgets.icon import ( ResizeIcon, ScrollToCurrentIcon, VisibilityIcon, + SpeakerIdentificationIcon, ) from buzz.translator import Translator from buzz.widgets.text_display_box import TextDisplayBox @@ -59,6 +61,10 @@ from buzz.widgets.transcription_viewer.transcription_view_mode_tool_button impor ) from buzz.widgets.transcription_viewer.transcription_resizer_widget import TranscriptionResizerWidget +# Underlying libs do not support intel Macs +if not (platform.system() == "Darwin" and platform.machine() == "x86_64"): + from buzz.widgets.transcription_viewer.speaker_identification_widget import SpeakerIdentificationWidget + class TranscriptionViewerWidget(QWidget): resize_button_clicked = pyqtSignal() @@ -85,6 +91,7 @@ class TranscriptionViewerWidget(QWidget): self.setWindowTitle(file_path_as_title(transcription.file)) self.transcription_resizer_dialog = None + self.speaker_identification_dialog = None self.transcriptions_updated_signal = transcriptions_updated_signal self.translation_thread = None @@ -98,7 +105,7 @@ class TranscriptionViewerWidget(QWidget): # Loop functionality self.segment_looping_enabled = self.settings.settings.value("transcription_viewer/segment_looping_enabled", False, type=bool) - + # UI visibility preferences self.playback_controls_visible = self.settings.settings.value("transcription_viewer/playback_controls_visible", False, type=bool) self.find_widget_visible = self.settings.settings.value("transcription_viewer/find_widget_visible", False, type=bool) @@ -165,18 +172,18 @@ class TranscriptionViewerWidget(QWidget): # Create a better current segment display that handles long text self.current_segment_frame = QFrame() self.current_segment_frame.setFrameStyle(QFrame.Shape.NoFrame) - + segment_layout = QVBoxLayout(self.current_segment_frame) segment_layout.setContentsMargins(4, 4, 4, 4) # Minimal margins for clean appearance segment_layout.setSpacing(0) # No spacing between elements - + # Text display - centered with scroll capability (no header label) self.current_segment_text = QLabel("") self.current_segment_text.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) self.current_segment_text.setWordWrap(True) self.current_segment_text.setStyleSheet("color: #666; line-height: 1.2; margin: 0; padding: 4px;") self.current_segment_text.setMinimumHeight(60) # Ensure minimum height for text - + # Make it scrollable for long text self.current_segment_scroll_area = QScrollArea() self.current_segment_scroll_area.setWidget(self.current_segment_text) @@ -185,13 +192,13 @@ class TranscriptionViewerWidget(QWidget): self.current_segment_scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.current_segment_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.current_segment_scroll_area.setStyleSheet("QScrollBar:vertical { width: 12px; } QScrollBar::handle:vertical { background: #ccc; border-radius: 6px; }") - + # Ensure the text label can expand to show all content self.current_segment_text.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) - + # Add scroll area to layout (simplified single-widget layout) segment_layout.addWidget(self.current_segment_scroll_area) - + # Initially hide the frame until there's content self.current_segment_frame.hide() @@ -247,6 +254,19 @@ class TranscriptionViewerWidget(QWidget): toolbar.addWidget(resize_button) + # Underlying libs do not support intel Macs + if not (platform.system() == "Darwin" and platform.machine() == "x86_64"): + speaker_identification_button = QToolButton() + speaker_identification_button.setText(_("Identify Speakers")) + speaker_identification_button.setObjectName("speaker_identification_button") + speaker_identification_button.setIcon(SpeakerIdentificationIcon(self)) + speaker_identification_button.setToolButtonStyle( + Qt.ToolButtonStyle.ToolButtonTextBesideIcon + ) + speaker_identification_button.clicked.connect(self.on_speaker_identification_button_clicked) + + toolbar.addWidget(speaker_identification_button) + # Add Find button self.find_button = QToolButton() self.find_button.setText(_("Find")) @@ -267,14 +287,14 @@ class TranscriptionViewerWidget(QWidget): # Table widget should take the majority of the space layout.addWidget(self.table_widget, 1) # Stretch factor 1 (majority) - + # Loop controls section (minimal space) self.create_loop_controls() layout.addWidget(self.loop_controls_frame, 0) # Stretch factor 0 (minimal) - + # Audio player (minimal space) layout.addWidget(self.audio_player, 0) # Stretch factor 0 (minimal) - + # Text display box (minimal space) layout.addWidget(self.text_display_box, 0) # Stretch factor 0 (minimal) @@ -291,7 +311,7 @@ class TranscriptionViewerWidget(QWidget): # Restore UI state from settings self.restore_ui_state() - + # Restore geometry from settings self.load_geometry() @@ -302,7 +322,7 @@ class TranscriptionViewerWidget(QWidget): # Restore playback controls visibility if self.playback_controls_visible: self.show_loop_controls() - + # Restore find widget visibility if self.find_widget_visible: self.show_search_bar() @@ -312,28 +332,28 @@ class TranscriptionViewerWidget(QWidget): self.search_frame = QFrame() self.search_frame.setFrameStyle(QFrame.Shape.StyledPanel) self.search_frame.setMaximumHeight(60) - + search_layout = QHBoxLayout(self.search_frame) search_layout.setContentsMargins(10, 5, 10, 5) - + # Find label search_label = QLabel(_("Find:")) search_label.setStyleSheet("font-weight: bold;") search_layout.addWidget(search_label) - + # Find input - make it wider for better usability self.search_input = QLineEdit() self.search_input.setPlaceholderText(_("Enter text to find...")) self.search_input.textChanged.connect(self.on_search_text_changed) self.search_input.returnPressed.connect(self.search_next) self.search_input.setMinimumWidth(300) # Increased from 200 to 300 - + # Add keyboard shortcuts for search navigation from PyQt6.QtGui import QKeySequence self.search_input.installEventFilter(self) - + search_layout.addWidget(self.search_input) - + # Search buttons - make them consistent height and remove hardcoded font sizes self.search_prev_button = QPushButton("↑") self.search_prev_button.setToolTip(_("Previous match (Shift+Enter)")) @@ -342,7 +362,7 @@ class TranscriptionViewerWidget(QWidget): self.search_prev_button.setMaximumWidth(40) self.search_prev_button.setMinimumHeight(30) # Ensure consistent height search_layout.addWidget(self.search_prev_button) - + self.search_next_button = QPushButton("↓") self.search_next_button.setToolTip(_("Next match (Enter)")) self.search_next_button.clicked.connect(self.search_next) @@ -350,21 +370,21 @@ class TranscriptionViewerWidget(QWidget): self.search_next_button.setMaximumWidth(40) self.search_next_button.setMinimumHeight(30) # Ensure consistent height search_layout.addWidget(self.search_next_button) - + # Clear button - make it bigger to accommodate different language translations self.clear_search_button = QPushButton(_("Clear")) self.clear_search_button.clicked.connect(self.clear_search) self.clear_search_button.setMaximumWidth(80) # Increased from 60 to 80 self.clear_search_button.setMinimumHeight(30) # Ensure consistent height search_layout.addWidget(self.clear_search_button) - + # Results label self.search_results_label = QLabel("") self.search_results_label.setStyleSheet("color: #666;") search_layout.addWidget(self.search_results_label) - + search_layout.addStretch() - + # Initially hide the search bar self.search_frame.hide() @@ -373,23 +393,23 @@ class TranscriptionViewerWidget(QWidget): self.loop_controls_frame = QFrame() self.loop_controls_frame.setFrameStyle(QFrame.Shape.StyledPanel) self.loop_controls_frame.setMaximumHeight(50) - + loop_layout = QHBoxLayout(self.loop_controls_frame) loop_layout.setContentsMargins(10, 5, 10, 5) loop_layout.setSpacing(8) # Add some spacing between elements for better visual separation - + # Loop controls label loop_label = QLabel(_("Playback Controls:")) loop_label.setStyleSheet("font-weight: bold;") loop_layout.addWidget(loop_label) - + # Loop toggle button self.loop_toggle = QCheckBox(_("Loop Segment")) self.loop_toggle.setChecked(self.segment_looping_enabled) self.loop_toggle.setToolTip(_("Enable/disable looping when clicking on transcript segments")) self.loop_toggle.toggled.connect(self.on_loop_toggle_changed) loop_layout.addWidget(self.loop_toggle) - + # Follow audio toggle button self.follow_audio_enabled = self.settings.settings.value("transcription_viewer/follow_audio_enabled", False, type=bool) self.follow_audio_toggle = QCheckBox(_("Follow Audio")) @@ -397,19 +417,19 @@ class TranscriptionViewerWidget(QWidget): self.follow_audio_toggle.setToolTip(_("Enable/disable following the current audio position in the transcript. When enabled, automatically scrolls to current text.")) self.follow_audio_toggle.toggled.connect(self.on_follow_audio_toggle_changed) loop_layout.addWidget(self.follow_audio_toggle) - + # Visual separator separator1 = QFrame() separator1.setFrameShape(QFrame.Shape.VLine) separator1.setFrameShadow(QFrame.Shadow.Sunken) separator1.setMaximumHeight(20) loop_layout.addWidget(separator1) - + # Speed controls speed_label = QLabel("Speed:") speed_label.setStyleSheet("font-weight: bold;") loop_layout.addWidget(speed_label) - + self.speed_combo = QComboBox() self.speed_combo.setEditable(True) self.speed_combo.addItems(["0.5x", "0.75x", "1x", "1.25x", "1.5x", "2x"]) @@ -417,29 +437,29 @@ class TranscriptionViewerWidget(QWidget): self.speed_combo.currentTextChanged.connect(self.on_speed_changed) self.speed_combo.setMaximumWidth(80) loop_layout.addWidget(self.speed_combo) - + self.speed_down_btn = QPushButton("-") self.speed_down_btn.setMaximumWidth(40) # Match search button width self.speed_down_btn.setMinimumHeight(30) # Match search button height self.speed_down_btn.clicked.connect(self.decrease_speed) loop_layout.addWidget(self.speed_down_btn) - + self.speed_up_btn = QPushButton("+") self.speed_up_btn.setMaximumWidth(40) # Match speed down button width self.speed_up_btn.setMinimumHeight(30) # Match search button height self.speed_up_btn.clicked.connect(self.increase_speed) loop_layout.addWidget(self.speed_up_btn) - + # Initialize speed control with current value from audio player self.initialize_speed_control() - + # Visual separator separator2 = QFrame() separator2.setFrameShape(QFrame.Shape.VLine) separator2.setFrameShadow(QFrame.Shadow.Sunken) separator2.setMaximumHeight(20) loop_layout.addWidget(separator2) - + # Scroll to current button self.scroll_to_current_button = QPushButton(_("Scroll to Current")) self.scroll_to_current_button.setIcon(ScrollToCurrentIcon(self)) @@ -448,16 +468,16 @@ class TranscriptionViewerWidget(QWidget): self.scroll_to_current_button.setMinimumHeight(30) self.scroll_to_current_button.setStyleSheet("QPushButton { padding: 4px 8px; }") # Better padding loop_layout.addWidget(self.scroll_to_current_button) - + loop_layout.addStretch() - + # Initially hide the loop controls frame self.loop_controls_frame.hide() def show_loop_controls(self): """Show the loop controls when audio is playing""" self.loop_controls_frame.show() - + # Save the visibility state to settings self.playback_controls_visible = True self.settings.settings.setValue("transcription_viewer/playback_controls_visible", self.playback_controls_visible) @@ -465,7 +485,7 @@ class TranscriptionViewerWidget(QWidget): def hide_loop_controls(self): """Hide the loop controls when audio is not playing""" self.loop_controls_frame.hide() - + # Save the visibility state to settings self.playback_controls_visible = False self.settings.settings.setValue("transcription_viewer/playback_controls_visible", self.playback_controls_visible) @@ -600,7 +620,7 @@ class TranscriptionViewerWidget(QWidget): def on_audio_playback_state_changed(self, state): """Handle audio playback state changes to automatically show/hide playback controls""" from PyQt6.QtMultimedia import QMediaPlayer - + if state == QMediaPlayer.PlaybackState.PlayingState: # Show playback controls when audio starts playing if self.view_mode == ViewMode.TIMESTAMPS: @@ -630,25 +650,25 @@ class TranscriptionViewerWidget(QWidget): # Extract the numeric value from speed text (e.g., "1.5x" -> 1.5) clean_text = speed_text.replace('x', '').strip() speed_value = float(clean_text) - + # Clamp the speed value to valid range speed_value = max(0.1, min(5.0, speed_value)) - + # Update the combo box text to show the clamped value if not speed_text.endswith('x'): speed_text = f"{speed_value:.2f}x" - + # Block signals to prevent recursion self.speed_combo.blockSignals(True) self.speed_combo.setCurrentText(speed_text) self.speed_combo.blockSignals(False) - + # Set the playback rate on the audio player self.audio_player.media_player.setPlaybackRate(speed_value) - + # Save the new rate to settings self.settings.set_value(self.settings.Key.AUDIO_PLAYBACK_RATE, speed_value) - + except ValueError: logging.warning(f"Invalid speed value: {speed_text}") # Reset to current valid value @@ -680,14 +700,14 @@ class TranscriptionViewerWidget(QWidget): """Set the playback speed programmatically""" # Clamp the speed value to valid range speed = max(0.1, min(5.0, speed)) - + # Update the combo box speed_text = f"{speed:.2f}x" self.speed_combo.setCurrentText(speed_text) - + # Set the playback rate on the audio player self.audio_player.media_player.setPlaybackRate(speed) - + # Save the new rate to settings self.settings.set_value(self.settings.Key.AUDIO_PLAYBACK_RATE, speed) @@ -707,49 +727,49 @@ class TranscriptionViewerWidget(QWidget): """Perform the actual search based on current view mode""" self.search_results = [] self.current_search_index = 0 - + if self.view_mode == ViewMode.TIMESTAMPS: self.search_in_table() else: # TEXT or TRANSLATION mode self.search_in_text() - + self.update_search_ui() def search_in_table(self): """Search in the table view (segments)""" segments = self.table_widget.segments() search_text_lower = self.search_text.lower() - + # Limit search results to avoid performance issues with very long segments max_results = 100 - + for i, segment in enumerate(segments): if len(self.search_results) >= max_results: break - + text = segment.value("text").lower() if search_text_lower in text: self.search_results.append(("table", i, segment)) - + # Also search in translations if available if self.has_translations: for i, segment in enumerate(segments): if len(self.search_results) >= max_results: break - + translation = segment.value("translation").lower() if search_text_lower in translation: - self.search_results.append(("table", i, segment)) + self.search_results.append(("table", i, segment)) def search_in_text(self): """Search in the text display box""" text = self.text_display_box.toPlainText() search_text_lower = self.search_text.lower() text_lower = text.lower() - + # Limit search results to avoid performance issues with very long text max_results = 100 - + start = 0 result_count = 0 while True: @@ -780,9 +800,9 @@ class TranscriptionViewerWidget(QWidget): """Highlight the current search match""" if not self.search_results: return - + match_type, match_data, _ = self.search_results[self.current_search_index] - + if match_type == "table": # Highlight in table self.highlight_table_match(match_data) @@ -802,10 +822,10 @@ class TranscriptionViewerWidget(QWidget): cursor = QTextCursor(self.text_display_box.document()) cursor.setPosition(start_pos) cursor.setPosition(start_pos + len(self.search_text), QTextCursor.MoveMode.KeepAnchor) - + # Set the cursor to highlight the text self.text_display_box.setTextCursor(cursor) - + # Ensure the highlighted text is visible self.text_display_box.ensureCursorVisible() @@ -813,7 +833,7 @@ class TranscriptionViewerWidget(QWidget): """Go to next search result""" if not self.search_results: return - + self.current_search_index = (self.current_search_index + 1) % len(self.search_results) self.highlight_current_match() self.update_search_results_label() @@ -822,7 +842,7 @@ class TranscriptionViewerWidget(QWidget): """Go to previous search result""" if not self.search_results: return - + self.current_search_index = (self.current_search_index - 1) % len(self.search_results) self.highlight_current_match() self.update_search_results_label() @@ -845,13 +865,13 @@ class TranscriptionViewerWidget(QWidget): self.search_prev_button.setEnabled(False) self.search_next_button.setEnabled(False) - + # Clear text highlighting if self.view_mode in (ViewMode.TEXT, ViewMode.TRANSLATION): cursor = QTextCursor(self.text_display_box.document()) cursor.clearSelection() self.text_display_box.setTextCursor(cursor) - + # Keep search bar visible but clear the input self.search_input.setFocus() @@ -861,7 +881,7 @@ class TranscriptionViewerWidget(QWidget): self.find_button.setChecked(False) # Sync button state self.clear_search() self.search_input.clearFocus() - + # Save the visibility state to settings self.find_widget_visible = False self.settings.settings.setValue("transcription_viewer/find_widget_visible", False) @@ -869,11 +889,11 @@ class TranscriptionViewerWidget(QWidget): def setup_shortcuts(self): """Set up keyboard shortcuts""" from PyQt6.QtGui import QShortcut, QKeySequence - + # Search shortcut (Ctrl+F) search_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.SEARCH_TRANSCRIPT)), self) search_shortcut.activated.connect(self.focus_search_input) - + # Scroll to current text shortcut (Ctrl+G) scroll_to_current_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.SCROLL_TO_CURRENT_TEXT)), self) scroll_to_current_shortcut.activated.connect(self.on_scroll_to_current_button_clicked) @@ -912,7 +932,7 @@ class TranscriptionViewerWidget(QWidget): self.find_button.setChecked(True) # Sync button state self.search_input.setFocus() self.search_input.selectAll() - + # Save the visibility state to settings self.find_widget_visible = True self.settings.settings.setValue("transcription_viewer/find_widget_visible", True) @@ -923,7 +943,7 @@ class TranscriptionViewerWidget(QWidget): self.hide_search_bar() else: self.show_search_bar() - + # Save the visibility state to settings self.find_widget_visible = self.search_frame.isVisible() self.settings.settings.setValue("transcription_viewer/find_widget_visible", self.find_widget_visible) @@ -934,7 +954,7 @@ class TranscriptionViewerWidget(QWidget): self.find_button.setChecked(True) self.search_input.setFocus() self.search_input.selectAll() - + # Save the visibility state to settings self.find_widget_visible = True self.settings.settings.setValue("transcription_viewer/find_widget_visible", True) @@ -942,7 +962,7 @@ class TranscriptionViewerWidget(QWidget): def eventFilter(self, obj, event): """Event filter to handle keyboard shortcuts in search input""" from PyQt6.QtCore import QEvent, Qt - + if obj == self.search_input and event.type() == QEvent.Type.KeyPress: # The event is already a QKeyEvent, no need to create a new one if event.key() == Qt.Key.Key_Return and event.modifiers() == Qt.KeyboardModifier.ShiftModifier: @@ -999,7 +1019,7 @@ class TranscriptionViewerWidget(QWidget): self.loop_controls_frame.hide() # Hide current segment display in translation mode self.current_segment_frame.hide() - + # Refresh search if there's active search text if self.search_text: self.perform_search() @@ -1007,7 +1027,7 @@ class TranscriptionViewerWidget(QWidget): def on_view_mode_changed(self, view_mode: ViewMode) -> None: self.view_mode = view_mode self.reset_view() - + # Refresh search if there's active search text if self.search_text: self.perform_search() @@ -1091,17 +1111,17 @@ class TranscriptionViewerWidget(QWidget): if current_segment is not None: self.current_segment_text.setText(current_segment.value("text")) self.current_segment_frame.show() # Show the frame when there's a current segment - + # Force the text label to recalculate its size self.current_segment_text.adjustSize() - + # Resize the frame to fit the text content self.resize_current_segment_frame() - + # Ensure the scroll area updates properly and shows scrollbars when needed self.current_segment_scroll_area.updateGeometry() self.current_segment_scroll_area.verticalScrollBar().setVisible(True) # Ensure scrollbar is visible - + # Update highlighting based on follow audio and loop settings if self.follow_audio_enabled: # Follow audio mode: highlight the current segment based on audio position @@ -1143,30 +1163,30 @@ class TranscriptionViewerWidget(QWidget): # Calculate the height needed for the text area line_height = self.current_segment_text.fontMetrics().lineSpacing() max_visible_lines = 3 # Fixed at 3 lines for consistency and clean UI - + # Calculate the height needed for the maximum visible lines (25% larger) text_height = line_height * max_visible_lines * 1.25 - + # Add some vertical margins/padding margins = 8 # Increased from 2 to 8 for better spacing - + # Calculate total height needed (no header height anymore) total_height = text_height + margins - + # Convert to integer since Qt methods expect int values total_height = int(total_height) - + # Set maximum height to ensure consistent sizing, but allow minimum to be flexible self.current_segment_frame.setMaximumHeight(total_height) self.current_segment_frame.setMinimumHeight(total_height) - + # Convert text_height to integer since Qt methods expect int values text_height = int(text_height) - + # Allow the scroll area to be flexible in height for proper scrolling self.current_segment_scroll_area.setMinimumHeight(text_height) self.current_segment_scroll_area.setMaximumHeight(text_height) - + # Allow the text label to size naturally for proper scrolling self.current_segment_text.setMinimumHeight(text_height) @@ -1220,12 +1240,27 @@ class TranscriptionViewerWidget(QWidget): self.transcription_resizer_dialog.show() + def on_speaker_identification_button_clicked(self): + # Underlying libs do not support intel Macs + if not (platform.system() == "Darwin" and platform.machine() == "x86_64"): + self.speaker_identification_dialog = SpeakerIdentificationWidget( + transcription=self.transcription, + transcription_service=self.transcription_service, + transcriptions_updated_signal=self.transcriptions_updated_signal, + ) + + self.transcriptions_updated_signal.connect(self.close) + + self.speaker_identification_dialog.show() + + pass + def on_loop_toggle_changed(self, enabled: bool): """Handle loop toggle state change""" self.segment_looping_enabled = enabled # Save preference to settings self.settings.settings.setValue("transcription_viewer/segment_looping_enabled", enabled) - + if enabled: # If looping is re-enabled and we have a selected segment, return to it if self.currently_selected_segment is not None: @@ -1235,21 +1270,21 @@ class TranscriptionViewerWidget(QWidget): if segment.value("id") == self.currently_selected_segment.value("id"): # Highlight and scroll to the selected segment self.table_widget.highlight_and_scroll_to_row(i) - + # Get the segment timing start_time = self.currently_selected_segment.value("start_time") end_time = self.currently_selected_segment.value("end_time") - + # Set the loop range for the selected segment self.audio_player.set_range((start_time, end_time)) - + # If audio is currently playing and outside the range, jump to the start current_pos = self.audio_player.position_ms playback_state = self.audio_player.media_player.playbackState() - if (playback_state == QMediaPlayer.PlaybackState.PlayingState and + if (playback_state == QMediaPlayer.PlaybackState.PlayingState and (current_pos < start_time or current_pos > end_time)): self.audio_player.set_position(start_time) - + break else: # Clear any existing range if looping is disabled @@ -1260,7 +1295,7 @@ class TranscriptionViewerWidget(QWidget): self.follow_audio_enabled = enabled # Save preference to settings self.settings.settings.setValue("transcription_viewer/follow_audio_enabled", enabled) - + if enabled: # When follow audio is first enabled, automatically scroll to current position # This gives immediate feedback that the feature is working @@ -1310,17 +1345,17 @@ class TranscriptionViewerWidget(QWidget): # Only scroll if we're in timestamps view mode (table is visible) if self.view_mode != ViewMode.TIMESTAMPS: return - + current_pos = self.audio_player.position_ms segments = self.table_widget.segments() - + # Find the current segment based on audio position current_segment = next( - (segment for segment in segments + (segment for segment in segments if segment.value("start_time") <= current_pos < segment.value("end_time")), None ) - + if current_segment is not None: # Find the row index and scroll to it for i, segment in enumerate(segments): @@ -1329,7 +1364,7 @@ class TranscriptionViewerWidget(QWidget): # Method 1: Use the table widget's built-in scrolling method self.table_widget.highlight_and_scroll_to_row(i) break - + except Exception as e: pass # Silently handle any errors @@ -1346,6 +1381,9 @@ class TranscriptionViewerWidget(QWidget): if self.transcription_resizer_dialog: self.transcription_resizer_dialog.close() + if self.speaker_identification_dialog: + self.speaker_identification_dialog.close() + self.translator.stop() self.translation_thread.quit() diff --git a/ctc_forced_aligner b/ctc_forced_aligner new file mode 160000 index 00000000..1f0a5f86 --- /dev/null +++ b/ctc_forced_aligner @@ -0,0 +1 @@ +Subproject commit 1f0a5f860d3d9daf3d94edb1c7d18f90d1702e5b diff --git a/deepmultilingualpunctuation b/deepmultilingualpunctuation new file mode 160000 index 00000000..5a0dd7f4 --- /dev/null +++ b/deepmultilingualpunctuation @@ -0,0 +1 @@ +Subproject commit 5a0dd7f4fd56687f59405aa8eba1144393d8b74b diff --git a/demucs/.github/ISSUE_TEMPLATE/bug.md b/demucs/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 00000000..217654a9 --- /dev/null +++ b/demucs/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,33 @@ +--- +name: 🐛 Bug Report +about: Submit a bug report to help us improve +labels: 'bug' +--- + +## 🐛 Bug Report + +(A clear and concise description of what the bug is) + +## To Reproduce + +(Write your steps here:) + +1. Step 1... +1. Step 2... +1. Step 3... + +## Expected behavior + +(Write what you thought would happen.) + +## Actual Behavior + +(Write what happened. Add screenshots, if applicable.) + +## Your Environment + + + +- Python and PyTorch version: +- Operating system and version (desktop or mobile): +- Hardware (gpu or cpu, amount of RAM etc.): diff --git a/demucs/.github/ISSUE_TEMPLATE/question.md b/demucs/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000..85a007e4 --- /dev/null +++ b/demucs/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,10 @@ +--- +name: "❓Questions/Help/Support" +about: If you have a question about the paper, code or algorithm, please ask here! +labels: question + +--- + +## ❓ Questions + +(Please ask your question here.) diff --git a/demucs/.github/workflows/linter.yml b/demucs/.github/workflows/linter.yml new file mode 100644 index 00000000..64f235fb --- /dev/null +++ b/demucs/.github/workflows/linter.yml @@ -0,0 +1,36 @@ +name: linter +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + if: ${{ github.repository == 'facebookresearch/demucs' || github.event_name == 'workflow_dispatch' }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - uses: actions/cache@v2 + with: + path: env + key: env-${{ hashFiles('**/requirements.txt', '.github/workflows/*') }} + + - name: Install dependencies + run: | + python3 -m venv env + . env/bin/activate + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install '.[dev]' + + + - name: Run linter + run: | + . env/bin/activate + make linter diff --git a/demucs/.github/workflows/tests.yml b/demucs/.github/workflows/tests.yml new file mode 100644 index 00000000..b31e3dd6 --- /dev/null +++ b/demucs/.github/workflows/tests.yml @@ -0,0 +1,36 @@ +name: tests +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + if: ${{ github.repository == 'facebookresearch/demucs' || github.event_name == 'workflow_dispatch' }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - uses: actions/cache@v2 + with: + path: env + key: env-${{ hashFiles('**/requirements.txt', '.github/workflows/*') }} + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y ffmpeg + python3 -m venv env + . env/bin/activate + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run separation test + run: | + . env/bin/activate + make test_eval diff --git a/demucs/.gitignore b/demucs/.gitignore new file mode 100644 index 00000000..179cf0dd --- /dev/null +++ b/demucs/.gitignore @@ -0,0 +1,17 @@ +*.egg-info +__pycache__ +Session.vim +/build +/dist +/lab +/metadata +/notebooks +/outputs +/release +/release_models +/separated +/tests +/trash +/misc +/mdx +.mypy_cache diff --git a/demucs/CODE_OF_CONDUCT.md b/demucs/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f049d4c5 --- /dev/null +++ b/demucs/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/demucs/CONTRIBUTING.md b/demucs/CONTRIBUTING.md new file mode 100644 index 00000000..f14f4af3 --- /dev/null +++ b/demucs/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing to Demucs + +## Pull Requests + +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +Demucs is the implementation of a research paper. +Therefore, we do not plan on accepting many pull requests for new features. +We certainly welcome them for bug fixes. + + +## Issues + +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + + +## License +By contributing to this repository, you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. diff --git a/demucs/Demucs.ipynb b/demucs/Demucs.ipynb new file mode 100644 index 00000000..9ebcfd5a --- /dev/null +++ b/demucs/Demucs.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "Be9yoh-ILfRr" + }, + "source": [ + "# Hybrid Demucs\n", + "\n", + "Feel free to use the Colab version:\n", + "https://colab.research.google.com/drive/1dC9nVxk3V_VPjUADsnFu8EiT-xnU1tGH?usp=sharing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 139 + }, + "colab_type": "code", + "executionInfo": { + "elapsed": 12277, + "status": "ok", + "timestamp": 1583778134659, + "user": { + "displayName": "Marllus Lustosa", + "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgLl2RbW64ZyWz3Y8IBku0zhHCMnt7fz7fEl0LTdA=s64", + "userId": "14811735256675200480" + }, + "user_tz": 180 + }, + "id": "kOjIPLlzhPfn", + "outputId": "c75f17ec-b576-4105-bc5b-c2ac9c1018a3" + }, + "outputs": [], + "source": [ + "!pip install -U demucs\n", + "# or for local development, if you have a clone of Demucs\n", + "# pip install -e ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "5lYOzKKCKAbJ" + }, + "outputs": [], + "source": [ + "# You can use the `demucs` command line to separate tracks\n", + "!demucs test.mp3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# You can also load directly the pretrained models,\n", + "# for instance for the MDX 2021 winning model of Track A:\n", + "from demucs import pretrained\n", + "model = pretrained.get_model('mdx')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Because `model` is a bag of 4 models, you cannot directly call it on your data,\n", + "# but the `apply_model` will know what to do of it.\n", + "import torch\n", + "from demucs.apply import apply_model\n", + "x = torch.randn(1, 2, 44100 * 10) # ten seconds of white noise for the demo\n", + "out = apply_model(model, x)[0] # shape is [S, C, T] with S the number of sources\n", + "\n", + "# So let see, where is all the white noise content is going ?\n", + "for name, source in zip(model.sources, out):\n", + " print(name, source.std() / x.std())\n", + "# The outputs are quite weird to be fair, not what I would have expected." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# now let's take a single model from the bag, and let's test it on a pure cosine\n", + "freq = 440 # in Hz\n", + "sr = model.samplerate\n", + "t = torch.arange(10 * sr).float() / sr\n", + "x = torch.cos(2 * 3.1416 * freq * t).expand(1, 2, -1)\n", + "sub_model = model.models[3]\n", + "out = sub_model(x)[0]\n", + "\n", + "# Same question where does it go?\n", + "for name, source in zip(model.sources, out):\n", + " print(name, source.std() / x.std())\n", + " \n", + "# Well now it makes much more sense, all the energy is going\n", + "# in the `other` source.\n", + "# Feel free to try lower pitch (try 80 Hz) to see what happens !" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# For training or more fun, refer to the Demucs README on our repo\n", + "# https://github.com/facebookresearch/demucs/tree/main/demucs" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "authorship_tag": "ABX9TyM9xpVr1M86NRcjtQ7g9tCx", + "collapsed_sections": [], + "name": "Demucs.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/demucs/LICENSE b/demucs/LICENSE new file mode 100644 index 00000000..a45a376f --- /dev/null +++ b/demucs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/demucs/MANIFEST.in b/demucs/MANIFEST.in new file mode 100644 index 00000000..96e5f54f --- /dev/null +++ b/demucs/MANIFEST.in @@ -0,0 +1,13 @@ +recursive-exclude env * +recursive-include conf *.yaml +include Makefile +include LICENSE +include demucs.png +include outputs.tar.gz +include test.mp3 +include requirements.txt +include requirements_minimal.txt +include mypy.ini +include demucs/py.typed +include demucs/remote/*.txt +include demucs/remote/*.yaml diff --git a/demucs/Makefile b/demucs/Makefile new file mode 100644 index 00000000..0474d587 --- /dev/null +++ b/demucs/Makefile @@ -0,0 +1,36 @@ +all: linter tests + +linter: + flake8 demucs + mypy demucs + +tests: test_train test_eval + +test_train: tests/musdb + _DORA_TEST_PATH=/tmp/demucs python3 -m dora run --clear \ + dset.musdb=./tests/musdb dset.segment=4 dset.shift=2 epochs=2 model=demucs \ + demucs.depth=2 demucs.channels=4 test.sdr=false misc.num_workers=0 test.workers=0 \ + test.shifts=0 + +test_eval: + python3 -m demucs -n demucs_unittest test.mp3 + python3 -m demucs -n demucs_unittest --two-stems=vocals test.mp3 + python3 -m demucs -n demucs_unittest --mp3 test.mp3 + python3 -m demucs -n demucs_unittest --flac --int24 test.mp3 + python3 -m demucs -n demucs_unittest --int24 --clip-mode clamp test.mp3 + python3 -m demucs -n demucs_unittest --segment 8 test.mp3 + python3 -m demucs.api -n demucs_unittest --segment 8 test.mp3 + python3 -m demucs --list-models + +tests/musdb: + test -e tests || mkdir tests + python3 -c 'import musdb; musdb.DB("tests/tmp", download=True)' + musdbconvert tests/tmp tests/musdb + +dist: + python3 setup.py sdist + +clean: + rm -r dist build *.egg-info + +.PHONY: linter dist test_train test_eval diff --git a/demucs/README.md b/demucs/README.md new file mode 100644 index 00000000..1bc16ee6 --- /dev/null +++ b/demucs/README.md @@ -0,0 +1,319 @@ +# Demucs Music Source Separation + +![tests badge](https://github.com/facebookresearch/demucs/workflows/tests/badge.svg) +![linter badge](https://github.com/facebookresearch/demucs/workflows/linter/badge.svg) + + +**This is the officially maintained Demucs** now that I (Alexandre Défossez) have left Meta to join [Kyutai](https://twitter.com/kyutai_labs). +Note that I'm not actively working on Demucs anymore, so expect slow replies and no new feature for now. + + + +This is the 4th release of Demucs (v4), featuring Hybrid Transformer based source separation. +**For the classic Hybrid Demucs (v3):** [Go this commit][demucs_v3]. +If you are experiencing issues and want the old Demucs back, please file an issue, and then you can get back to Demucs v3 with +`git checkout v3`. You can also go [Demucs v2][demucs_v2]. + + +Demucs is a state-of-the-art music source separation model, currently capable of separating +drums, bass, and vocals from the rest of the accompaniment. +Demucs is based on a U-Net convolutional architecture inspired by [Wave-U-Net][waveunet]. +The v4 version features [Hybrid Transformer Demucs][htdemucs], a hybrid spectrogram/waveform separation model using Transformers. +It is based on [Hybrid Demucs][hybrid_paper] (also provided in this repo), with the innermost layers +replaced by a cross-domain Transformer Encoder. This Transformer uses self-attention within each domain, +and cross-attention across domains. +The model achieves a SDR of 9.00 dB on the MUSDB HQ test set. Moreover, when using sparse attention +kernels to extend its receptive field and per source fine-tuning, we achieve state-of-the-art 9.20 dB of SDR. + +Samples are available [on our sample page](https://ai.honu.io/papers/htdemucs/index.html). +Checkout [our paper][htdemucs] for more information. +It has been trained on the [MUSDB HQ][musdb] dataset + an extra training dataset of 800 songs. +This model separates drums, bass and vocals and other stems for any song. + + +As Hybrid Transformer Demucs is brand new, it is not activated by default, you can activate it in the usual +commands described hereafter with `-n htdemucs_ft`. +The single, non fine-tuned model is provided as `-n htdemucs`, and the retrained baseline +as `-n hdemucs_mmi`. The Sparse Hybrid Transformer model decribed in our paper is not provided as its +requires custom CUDA code that is not ready for release yet. +We are also releasing an experimental 6 sources model, that adds a `guitar` and `piano` source. +Quick testing seems to show okay quality for `guitar`, but a lot of bleeding and artifacts for the `piano` source. + + +

    +Schema representing the structure of Hybrid Transformer Demucs,
+    with a dual U-Net structure, one branch for the temporal domain,
+    and one branch for the spectral domain. There is a cross-domain Transformer between the Encoders and Decoders.

    + + + +## Important news if you are already using Demucs + +See the [release notes](./docs/release.md) for more details. + +- 22/02/2023: added support for the [SDX 2023 Challenge](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023), + see the dedicated [doc page](./docs/sdx23.md) +- 07/12/2022: Demucs v4 now on PyPI. **htdemucs** model now used by default. Also releasing + a 6 sources models (adding `guitar` and `piano`, although the latter doesn't work so well at the moment). +- 16/11/2022: Added the new **Hybrid Transformer Demucs v4** models. + Adding support for the [torchaudio implementation of HDemucs](https://pytorch.org/audio/stable/tutorials/hybrid_demucs_tutorial.html). +- 30/08/2022: added reproducibility and ablation grids, along with an updated version of the paper. +- 17/08/2022: Releasing v3.0.5: Set split segment length to reduce memory. Compatible with pyTorch 1.12. +- 24/02/2022: Releasing v3.0.4: split into two stems (i.e. karaoke mode). + Export as float32 or int24. +- 17/12/2021: Releasing v3.0.3: bug fixes (thanks @keunwoochoi), memory drastically + reduced on GPU (thanks @famzah) and new multi-core evaluation on CPU (`-j` flag). +- 12/11/2021: Releasing **Demucs v3** with hybrid domain separation. Strong improvements + on all sources. This is the model that won Sony MDX challenge. +- 11/05/2021: Adding support for MusDB-HQ and arbitrary wav set, for the MDX challenge. For more information +on joining the challenge with Demucs see [the Demucs MDX instructions](docs/mdx.md) + + +## Comparison with other models + +We provide hereafter a summary of the different metrics presented in the paper. +You can also compare Hybrid Demucs (v3), [KUIELAB-MDX-Net][kuielab], [Spleeter][spleeter], Open-Unmix, Demucs (v1), and Conv-Tasnet on one of my favorite +songs on my [soundcloud playlist][soundcloud]. + +### Comparison of accuracy + +`Overall SDR` is the mean of the SDR for each of the 4 sources, `MOS Quality` is a rating from 1 to 5 +of the naturalness and absence of artifacts given by human listeners (5 = no artifacts), `MOS Contamination` +is a rating from 1 to 5 with 5 being zero contamination by other sources. We refer the reader to our [paper][hybrid_paper], +for more details. + +| Model | Domain | Extra data? | Overall SDR | MOS Quality | MOS Contamination | +|------------------------------|-------------|-------------------|-------------|-------------|-------------------| +| [Wave-U-Net][waveunet] | waveform | no | 3.2 | - | - | +| [Open-Unmix][openunmix] | spectrogram | no | 5.3 | - | - | +| [D3Net][d3net] | spectrogram | no | 6.0 | - | - | +| [Conv-Tasnet][demucs_v2] | waveform | no | 5.7 | - | | +| [Demucs (v2)][demucs_v2] | waveform | no | 6.3 | 2.37 | 2.36 | +| [ResUNetDecouple+][decouple] | spectrogram | no | 6.7 | - | - | +| [KUIELAB-MDX-Net][kuielab] | hybrid | no | 7.5 | **2.86** | 2.55 | +| [Band-Spit RNN][bandsplit] | spectrogram | no | **8.2** | - | - | +| **Hybrid Demucs (v3)** | hybrid | no | 7.7 | **2.83** | **3.04** | +| [MMDenseLSTM][mmdenselstm] | spectrogram | 804 songs | 6.0 | - | - | +| [D3Net][d3net] | spectrogram | 1.5k songs | 6.7 | - | - | +| [Spleeter][spleeter] | spectrogram | 25k songs | 5.9 | - | - | +| [Band-Spit RNN][bandsplit] | spectrogram | 1.7k (mixes only) | **9.0** | - | - | +| **HT Demucs f.t. (v4)** | hybrid | 800 songs | **9.0** | - | - | + + + +## Requirements + +You will need at least Python 3.8. See `requirements_minimal.txt` for requirements for separation only, +and `environment-[cpu|cuda].yml` (or `requirements.txt`) if you want to train a new model. + +### For Windows users + +Everytime you see `python3`, replace it with `python.exe`. You should always run commands from the +Anaconda console. + +### For musicians + +If you just want to use Demucs to separate tracks, you can install it with + +```bash +python3 -m pip install -U demucs +``` + +For bleeding edge versions, you can install directly from this repo using +```bash +python3 -m pip install -U git+https://github.com/facebookresearch/demucs#egg=demucs +``` + +Advanced OS support are provided on the following page, **you must read the page for your OS before posting an issues**: +- **If you are using Windows:** [Windows support](docs/windows.md). +- **If you are using macOS:** [macOS support](docs/mac.md). +- **If you are using Linux:** [Linux support](docs/linux.md). + +### For machine learning scientists + +If you have anaconda installed, you can run from the root of this repository: + +```bash +conda env update -f environment-cpu.yml # if you don't have GPUs +conda env update -f environment-cuda.yml # if you have GPUs +conda activate demucs +pip install -e . +``` + +This will create a `demucs` environment with all the dependencies installed. + +You will also need to install [soundstretch/soundtouch](https://www.surina.net/soundtouch/soundstretch.html): on macOS you can do `brew install sound-touch`, +and on Ubuntu `sudo apt-get install soundstretch`. This is used for the +pitch/tempo augmentation. + + +### Running in Docker + +Thanks to @xserrat, there is now a Docker image definition ready for using Demucs. This can ensure all libraries are correctly installed without interfering with the host OS. See his repo [Docker Facebook Demucs](https://github.com/xserrat/docker-facebook-demucs) for more information. + + +### Running from Colab + +I made a Colab to easily separate track with Demucs. Note that +transfer speeds with Colab are a bit slow for large media files, +but it will allow you to use Demucs without installing anything. + +[Demucs on Google Colab](https://colab.research.google.com/drive/1dC9nVxk3V_VPjUADsnFu8EiT-xnU1tGH?usp=sharing) + +### Web Demo + +Integrated to [Hugging Face Spaces](https://huggingface.co/spaces) with [Gradio](https://github.com/gradio-app/gradio). See demo: [![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/akhaliq/demucs) + +### Graphical Interface + +@CarlGao4 has released a GUI for Demucs: [CarlGao4/Demucs-Gui](https://github.com/CarlGao4/Demucs-Gui). Downloads for Windows and macOS is available [here](https://github.com/CarlGao4/Demucs-Gui/releases). Use [FossHub mirror](https://fosshub.com/Demucs-GUI.html) to speed up your download. + +@Anjok07 is providing a self contained GUI in [UVR (Ultimate Vocal Remover)](https://github.com/facebookresearch/demucs/issues/334) that supports Demucs. + +### Other providers + +Audiostrip is providing free online separation with Demucs on their website [https://audiostrip.co.uk/](https://audiostrip.co.uk/). + +[MVSep](https://mvsep.com/) also provides free online separation, select `Demucs3 model B` for the best quality. + +[Neutone](https://neutone.space/) provides a realtime Demucs model in their free VST/AU plugin that can be used in your favorite DAW. + + +## Separating tracks + +In order to try Demucs, you can just run from any folder (as long as you properly installed it) + +```bash +demucs PATH_TO_AUDIO_FILE_1 [PATH_TO_AUDIO_FILE_2 ...] # for Demucs +# If you used `pip install --user` you might need to replace demucs with python3 -m demucs +python3 -m demucs --mp3 --mp3-bitrate BITRATE PATH_TO_AUDIO_FILE_1 # output files saved as MP3 + # use --mp3-preset to change encoder preset, 2 for best quality, 7 for fastest +# If your filename contain spaces don't forget to quote it !!! +demucs "my music/my favorite track.mp3" +# You can select different models with `-n` mdx_q is the quantized model, smaller but maybe a bit less accurate. +demucs -n mdx_q myfile.mp3 +# If you only want to separate vocals out of an audio, use `--two-stems=vocals` (You can also set to drums or bass) +demucs --two-stems=vocals myfile.mp3 +``` + + +If you have a GPU, but you run out of memory, please use `--segment SEGMENT` to reduce length of each split. `SEGMENT` should be changed to a integer describing the length of each segment in seconds. +A segment length of at least 10 is recommended (the bigger the number is, the more memory is required, but quality may increase). Note that the Hybrid Transformer models only support a maximum segment length of 7.8 seconds. +Creating an environment variable `PYTORCH_NO_CUDA_MEMORY_CACHING=1` is also helpful. If this still does not help, please add `-d cpu` to the command line. See the section hereafter for more details on the memory requirements for GPU acceleration. + +Separated tracks are stored in the `separated/MODEL_NAME/TRACK_NAME` folder. There you will find four stereo wav files sampled at 44.1 kHz: `drums.wav`, `bass.wav`, +`other.wav`, `vocals.wav` (or `.mp3` if you used the `--mp3` option). + +All audio formats supported by `torchaudio` can be processed (i.e. wav, mp3, flac, ogg/vorbis on Linux/macOS, etc.). On Windows, `torchaudio` has limited support, so we rely on `ffmpeg`, which should support pretty much anything. +Audio is resampled on the fly if necessary. +The output will be a wav file encoded as int16. +You can save as float32 wav files with `--float32`, or 24 bits integer wav with `--int24`. +You can pass `--mp3` to save as mp3 instead, and set the bitrate (in kbps) with `--mp3-bitrate` (default is 320). + +It can happen that the output would need clipping, in particular due to some separation artifacts. +Demucs will automatically rescale each output stem so as to avoid clipping. This can however break +the relative volume between stems. If instead you prefer hard clipping, pass `--clip-mode clamp`. +You can also try to reduce the volume of the input mixture before feeding it to Demucs. + + +Other pre-trained models can be selected with the `-n` flag. +The list of pre-trained models is: +- `htdemucs`: first version of Hybrid Transformer Demucs. Trained on MusDB + 800 songs. Default model. +- `htdemucs_ft`: fine-tuned version of `htdemucs`, separation will take 4 times more time + but might be a bit better. Same training set as `htdemucs`. +- `htdemucs_6s`: 6 sources version of `htdemucs`, with `piano` and `guitar` being added as sources. + Note that the `piano` source is not working great at the moment. +- `hdemucs_mmi`: Hybrid Demucs v3, retrained on MusDB + 800 songs. +- `mdx`: trained only on MusDB HQ, winning model on track A at the [MDX][mdx] challenge. +- `mdx_extra`: trained with extra training data (**including MusDB test set**), ranked 2nd on the track B + of the [MDX][mdx] challenge. +- `mdx_q`, `mdx_extra_q`: quantized version of the previous models. Smaller download and storage + but quality can be slightly worse. +- `SIG`: where `SIG` is a single model from the [model zoo](docs/training.md#model-zoo). + +The `--two-stems=vocals` option allows separating vocals from the rest of the accompaniment (i.e., karaoke mode). +`vocals` can be changed to any source in the selected model. +This will mix the files after separating the mix fully, so this won't be faster or use less memory. + +The `--shifts=SHIFTS` performs multiple predictions with random shifts (a.k.a the *shift trick*) of the input and average them. This makes prediction `SHIFTS` times +slower. Don't use it unless you have a GPU. + +The `--overlap` option controls the amount of overlap between prediction windows. Default is 0.25 (i.e. 25%) which is probably fine. +It can probably be reduced to 0.1 to improve a bit speed. + + +The `-j` flag allow to specify a number of parallel jobs (e.g. `demucs -j 2 myfile.mp3`). +This will multiply by the same amount the RAM used so be careful! + +### Memory requirements for GPU acceleration + +If you want to use GPU acceleration, you will need at least 3GB of RAM on your GPU for `demucs`. However, about 7GB of RAM will be required if you use the default arguments. Add `--segment SEGMENT` to change size of each split. If you only have 3GB memory, set SEGMENT to 8 (though quality may be worse if this argument is too small). Creating an environment variable `PYTORCH_NO_CUDA_MEMORY_CACHING=1` can help users with even smaller RAM such as 2GB (I separated a track that is 4 minutes but only 1.5GB is used), but this would make the separation slower. + +If you do not have enough memory on your GPU, simply add `-d cpu` to the command line to use the CPU. With Demucs, processing time should be roughly equal to 1.5 times the duration of the track. + +## Calling from another Python program + +The main function provides an `opt` parameter as a simple API. You can just pass the parsed command line as this parameter: +```python +# Assume that your command is `demucs --mp3 --two-stems vocals -n mdx_extra "track with space.mp3"` +# The following codes are same as the command above: +import demucs.separate +demucs.separate.main(["--mp3", "--two-stems", "vocals", "-n", "mdx_extra", "track with space.mp3"]) + +# Or like this +import demucs.separate +import shlex +demucs.separate.main(shlex.split('--mp3 --two-stems vocals -n mdx_extra "track with space.mp3"')) +``` + +To use more complicated APIs, see [API docs](docs/api.md) + +## Training Demucs + +If you want to train (Hybrid) Demucs, please follow the [training doc](docs/training.md). + +## MDX Challenge reproduction + +In order to reproduce the results from the Track A and Track B submissions, checkout the [MDX Hybrid Demucs submission repo][mdx_submission]. + + + +## How to cite + +``` +@inproceedings{rouard2022hybrid, + title={Hybrid Transformers for Music Source Separation}, + author={Rouard, Simon and Massa, Francisco and D{\'e}fossez, Alexandre}, + booktitle={ICASSP 23}, + year={2023} +} + +@inproceedings{defossez2021hybrid, + title={Hybrid Spectrogram and Waveform Source Separation}, + author={D{\'e}fossez, Alexandre}, + booktitle={Proceedings of the ISMIR 2021 Workshop on Music Source Separation}, + year={2021} +} +``` + +## License + +Demucs is released under the MIT license as found in the [LICENSE](LICENSE) file. + +[hybrid_paper]: https://arxiv.org/abs/2111.03600 +[waveunet]: https://github.com/f90/Wave-U-Net +[musdb]: https://sigsep.github.io/datasets/musdb.html +[openunmix]: https://github.com/sigsep/open-unmix-pytorch +[mmdenselstm]: https://arxiv.org/abs/1805.02410 +[demucs_v2]: https://github.com/facebookresearch/demucs/tree/v2 +[demucs_v3]: https://github.com/facebookresearch/demucs/tree/v3 +[spleeter]: https://github.com/deezer/spleeter +[soundcloud]: https://soundcloud.com/honualx/sets/source-separation-in-the-waveform-domain +[d3net]: https://arxiv.org/abs/2010.01733 +[mdx]: https://www.aicrowd.com/challenges/music-demixing-challenge-ismir-2021 +[kuielab]: https://github.com/kuielab/mdx-net-submission +[decouple]: https://arxiv.org/abs/2109.05418 +[mdx_submission]: https://github.com/adefossez/mdx21_demucs +[bandsplit]: https://arxiv.org/abs/2209.15174 +[htdemucs]: https://arxiv.org/abs/2211.08553 diff --git a/demucs/Readme.md b/demucs/Readme.md deleted file mode 100644 index 402d2b4a..00000000 --- a/demucs/Readme.md +++ /dev/null @@ -1 +0,0 @@ -Inlined demucs https://github.com/adefossez/demucs \ No newline at end of file diff --git a/demucs/conf/config.yaml b/demucs/conf/config.yaml new file mode 100644 index 00000000..d2597cb5 --- /dev/null +++ b/demucs/conf/config.yaml @@ -0,0 +1,304 @@ +defaults: + - _self_ + - dset: musdb44 + - svd: default + - variant: default + - override hydra/hydra_logging: colorlog + - override hydra/job_logging: colorlog + +dummy: +dset: + musdb: /checkpoint/defossez/datasets/musdbhq + musdb_samplerate: 44100 + use_musdb: true # set to false to not use musdb as training data. + wav: # path to custom wav dataset + wav2: # second custom wav dataset + segment: 11 + shift: 1 + train_valid: false + full_cv: true + samplerate: 44100 + channels: 2 + normalize: true + metadata: ./metadata + sources: ['drums', 'bass', 'other', 'vocals'] + valid_samples: # valid dataset size + backend: null # if provided select torchaudio backend. + +test: + save: False + best: True + workers: 2 + every: 20 + split: true + shifts: 1 + overlap: 0.25 + sdr: true + metric: 'loss' # metric used for best model selection on the valid set, can also be nsdr + nonhq: # path to non hq MusDB for evaluation + +epochs: 360 +batch_size: 64 +max_batches: # limit the number of batches per epoch, useful for debugging + # or if your dataset is gigantic. +optim: + lr: 3e-4 + momentum: 0.9 + beta2: 0.999 + loss: l1 # l1 or mse + optim: adam + weight_decay: 0 + clip_grad: 0 + +seed: 42 +debug: false +valid_apply: true +flag: +save_every: +weights: [1., 1., 1., 1.] # weights over each source for the training/valid loss. + +augment: + shift_same: false + repitch: + proba: 0.2 + max_tempo: 12 + remix: + proba: 1 + group_size: 4 + scale: + proba: 1 + min: 0.25 + max: 1.25 + flip: true + +continue_from: # continue from other XP, give the XP Dora signature. +continue_pretrained: # signature of a pretrained XP, this cannot be a bag of models. +pretrained_repo: # repo for pretrained model (default is official AWS) +continue_best: true +continue_opt: false + +misc: + num_workers: 10 + num_prints: 4 + show: false + verbose: false + +# List of decay for EMA at batch or epoch level, e.g. 0.999. +# Batch level EMA are kept on GPU for speed. +ema: + epoch: [] + batch: [] + +use_train_segment: true # to remove +model_segment: # override the segment parameter for the model, usually 4 times the training segment. +model: demucs # see demucs/train.py for the possibilities, and config for each model hereafter. +demucs: # see demucs/demucs.py for a detailed description + # Channels + channels: 64 + growth: 2 + # Main structure + depth: 6 + rewrite: true + lstm_layers: 0 + # Convolutions + kernel_size: 8 + stride: 4 + context: 1 + # Activations + gelu: true + glu: true + # Normalization + norm_groups: 4 + norm_starts: 4 + # DConv residual branch + dconv_depth: 2 + dconv_mode: 1 # 1 = branch in encoder, 2 = in decoder, 3 = in both. + dconv_comp: 4 + dconv_attn: 4 + dconv_lstm: 4 + dconv_init: 1e-4 + # Pre/post treatment + resample: true + normalize: false + # Weight init + rescale: 0.1 + +hdemucs: # see demucs/hdemucs.py for a detailed description + # Channels + channels: 48 + channels_time: + growth: 2 + # STFT + nfft: 4096 + wiener_iters: 0 + end_iters: 0 + wiener_residual: false + cac: true + # Main structure + depth: 6 + rewrite: true + hybrid: true + hybrid_old: false + # Frequency Branch + multi_freqs: [] + multi_freqs_depth: 3 + freq_emb: 0.2 + emb_scale: 10 + emb_smooth: true + # Convolutions + kernel_size: 8 + stride: 4 + time_stride: 2 + context: 1 + context_enc: 0 + # normalization + norm_starts: 4 + norm_groups: 4 + # DConv residual branch + dconv_mode: 1 + dconv_depth: 2 + dconv_comp: 4 + dconv_attn: 4 + dconv_lstm: 4 + dconv_init: 1e-3 + # Weight init + rescale: 0.1 + +# Torchaudio implementation of HDemucs +torch_hdemucs: +# Channels + channels: 48 + growth: 2 + # STFT + nfft: 4096 + # Main structure + depth: 6 + freq_emb: 0.2 + emb_scale: 10 + emb_smooth: true + # Convolutions + kernel_size: 8 + stride: 4 + time_stride: 2 + context: 1 + context_enc: 0 + # normalization + norm_starts: 4 + norm_groups: 4 + # DConv residual branch + dconv_depth: 2 + dconv_comp: 4 + dconv_attn: 4 + dconv_lstm: 4 + dconv_init: 1e-3 + +htdemucs: # see demucs/htdemucs.py for a detailed description + # Channels + channels: 48 + channels_time: + growth: 2 + # STFT + nfft: 4096 + wiener_iters: 0 + end_iters: 0 + wiener_residual: false + cac: true + # Main structure + depth: 4 + rewrite: true + # Frequency Branch + multi_freqs: [] + multi_freqs_depth: 3 + freq_emb: 0.2 + emb_scale: 10 + emb_smooth: true + # Convolutions + kernel_size: 8 + stride: 4 + time_stride: 2 + context: 1 + context_enc: 0 + # normalization + norm_starts: 4 + norm_groups: 4 + # DConv residual branch + dconv_mode: 1 + dconv_depth: 2 + dconv_comp: 8 + dconv_init: 1e-3 + # Before the Transformer + bottom_channels: 0 + # CrossTransformer + # ------ Common to all + # Regular parameters + t_layers: 5 + t_hidden_scale: 4.0 + t_heads: 8 + t_dropout: 0.0 + t_layer_scale: True + t_gelu: True + # ------------- Positional Embedding + t_emb: sin + t_max_positions: 10000 # for the scaled embedding + t_max_period: 10000.0 + t_weight_pos_embed: 1.0 + t_cape_mean_normalize: True + t_cape_augment: True + t_cape_glob_loc_scale: [5000.0, 1.0, 1.4] + t_sin_random_shift: 0 + # ------------- norm before a transformer encoder + t_norm_in: True + t_norm_in_group: False + # ------------- norm inside the encoder + t_group_norm: False + t_norm_first: True + t_norm_out: True + # ------------- optim + t_weight_decay: 0.0 + t_lr: + # ------------- sparsity + t_sparse_self_attn: False + t_sparse_cross_attn: False + t_mask_type: diag + t_mask_random_seed: 42 + t_sparse_attn_window: 400 + t_global_window: 100 + t_sparsity: 0.95 + t_auto_sparsity: False + # Cross Encoder First (False) + t_cross_first: False + # Weight init + rescale: 0.1 + +svd: # see svd.py for documentation + penalty: 0 + min_size: 0.1 + dim: 1 + niters: 2 + powm: false + proba: 1 + conv_only: false + convtr: false + bs: 1 + +quant: # quantization hyper params + diffq: # diffq penalty, typically 1e-4 or 3e-4 + qat: # use QAT with a fixed number of bits (not as good as diffq) + min_size: 0.2 + group_size: 8 + +dora: + dir: outputs + exclude: ["misc.*", "slurm.*", 'test.reval', 'flag', 'dset.backend'] + +slurm: + time: 4320 + constraint: volta32gb + setup: ['module load cudnn/v8.4.1.50-cuda.11.6 NCCL/2.11.4-6-cuda.11.6 cuda/11.6'] + +# Hydra config +hydra: + job_logging: + formatters: + colorlog: + datefmt: "%m-%d %H:%M:%S" diff --git a/demucs/conf/dset/aetl.yaml b/demucs/conf/dset/aetl.yaml new file mode 100644 index 00000000..7c983160 --- /dev/null +++ b/demucs/conf/dset/aetl.yaml @@ -0,0 +1,19 @@ +# @package _global_ + +# automix dataset with Musdb, extra training data and the test set of Musdb. +# This used even more remixes than auto_extra_test. +dset: + wav: /checkpoint/defossez/datasets/aetl + samplerate: 44100 + channels: 2 +epochs: 320 +max_batches: 500 + +augment: + shift_same: true + scale: + proba: 0. + remix: + proba: 0 + repitch: + proba: 0 diff --git a/demucs/conf/dset/auto_extra_test.yaml b/demucs/conf/dset/auto_extra_test.yaml new file mode 100644 index 00000000..056183a5 --- /dev/null +++ b/demucs/conf/dset/auto_extra_test.yaml @@ -0,0 +1,18 @@ +# @package _global_ + +# automix dataset with Musdb, extra training data and the test set of Musdb. +dset: + wav: /checkpoint/defossez/datasets/automix_extra_test2 + samplerate: 44100 + channels: 2 +epochs: 320 +max_batches: 500 + +augment: + shift_same: true + scale: + proba: 0. + remix: + proba: 0 + repitch: + proba: 0 diff --git a/demucs/conf/dset/auto_mus.yaml b/demucs/conf/dset/auto_mus.yaml new file mode 100644 index 00000000..9a2d9df5 --- /dev/null +++ b/demucs/conf/dset/auto_mus.yaml @@ -0,0 +1,20 @@ +# @package _global_ + +# Automix dataset based on musdb train set. +dset: + wav: /checkpoint/defossez/datasets/automix_musdb + samplerate: 44100 + channels: 2 +epochs: 360 +max_batches: 300 +test: + every: 4 + +augment: + shift_same: true + scale: + proba: 0.5 + remix: + proba: 0 + repitch: + proba: 0 diff --git a/demucs/conf/dset/extra44.yaml b/demucs/conf/dset/extra44.yaml new file mode 100644 index 00000000..f0adc467 --- /dev/null +++ b/demucs/conf/dset/extra44.yaml @@ -0,0 +1,8 @@ +# @package _global_ + +# Musdb + extra tracks +dset: + wav: /checkpoint/defossez/datasets/allstems_44/ + samplerate: 44100 + channels: 2 +epochs: 320 diff --git a/demucs/conf/dset/extra_mmi_goodclean.yaml b/demucs/conf/dset/extra_mmi_goodclean.yaml new file mode 100644 index 00000000..fe47bcf2 --- /dev/null +++ b/demucs/conf/dset/extra_mmi_goodclean.yaml @@ -0,0 +1,12 @@ +# @package _global_ + +# Musdb + extra tracks +dset: + wav: /checkpoint/defossez/datasets/allstems_44/ + wav2: /checkpoint/defossez/datasets/mmi44_goodclean + samplerate: 44100 + channels: 2 + wav2_weight: null + wav2_valid: false + valid_samples: 100 +epochs: 1200 diff --git a/demucs/conf/dset/extra_test.yaml b/demucs/conf/dset/extra_test.yaml new file mode 100644 index 00000000..1e7d05ad --- /dev/null +++ b/demucs/conf/dset/extra_test.yaml @@ -0,0 +1,12 @@ +# @package _global_ + +# Musdb + extra tracks + test set from musdb. +dset: + wav: /checkpoint/defossez/datasets/allstems_test_44/ + samplerate: 44100 + channels: 2 +epochs: 320 +max_batches: 700 +test: + sdr: false + every: 500 diff --git a/demucs/conf/dset/musdb44.yaml b/demucs/conf/dset/musdb44.yaml new file mode 100644 index 00000000..c5623468 --- /dev/null +++ b/demucs/conf/dset/musdb44.yaml @@ -0,0 +1,5 @@ +# @package _global_ + +dset: + samplerate: 44100 + channels: 2 \ No newline at end of file diff --git a/demucs/conf/dset/sdx23_bleeding.yaml b/demucs/conf/dset/sdx23_bleeding.yaml new file mode 100644 index 00000000..5f7fd1e4 --- /dev/null +++ b/demucs/conf/dset/sdx23_bleeding.yaml @@ -0,0 +1,10 @@ +# @package _global_ + +# Musdb + extra tracks +dset: + wav: /shared/home/defossez/data/datasets/moisesdb23_bleeding_v1.0/ + use_musdb: false + samplerate: 44100 + channels: 2 + backend: soundfile # must use soundfile as some mixture would clip with sox. +epochs: 320 diff --git a/demucs/conf/dset/sdx23_labelnoise.yaml b/demucs/conf/dset/sdx23_labelnoise.yaml new file mode 100644 index 00000000..367769e6 --- /dev/null +++ b/demucs/conf/dset/sdx23_labelnoise.yaml @@ -0,0 +1,10 @@ +# @package _global_ + +# Musdb + extra tracks +dset: + wav: /shared/home/defossez/data/datasets/moisesdb23_labelnoise_v1.0 + use_musdb: false + samplerate: 44100 + channels: 2 + backend: soundfile # must use soundfile as some mixture would clip with sox. +epochs: 320 diff --git a/demucs/conf/svd/base.yaml b/demucs/conf/svd/base.yaml new file mode 100644 index 00000000..e4de8685 --- /dev/null +++ b/demucs/conf/svd/base.yaml @@ -0,0 +1,14 @@ +# @package _global_ + +svd: + penalty: 0 + min_size: 1 + dim: 50 + niters: 4 + powm: false + proba: 1 + conv_only: false + convtr: false # ideally this should be true, but some models were trained with this to false. + +optim: + beta2: 0.9998 \ No newline at end of file diff --git a/demucs/conf/svd/base2.yaml b/demucs/conf/svd/base2.yaml new file mode 100644 index 00000000..b88a7519 --- /dev/null +++ b/demucs/conf/svd/base2.yaml @@ -0,0 +1,14 @@ +# @package _global_ + +svd: + penalty: 0 + min_size: 1 + dim: 100 + niters: 4 + powm: false + proba: 1 + conv_only: false + convtr: true + +optim: + beta2: 0.9998 \ No newline at end of file diff --git a/demucs/conf/svd/default.yaml b/demucs/conf/svd/default.yaml new file mode 100644 index 00000000..03bfe3db --- /dev/null +++ b/demucs/conf/svd/default.yaml @@ -0,0 +1 @@ +# @package _global_ diff --git a/demucs/conf/variant/default.yaml b/demucs/conf/variant/default.yaml new file mode 100644 index 00000000..03bfe3db --- /dev/null +++ b/demucs/conf/variant/default.yaml @@ -0,0 +1 @@ +# @package _global_ diff --git a/demucs/conf/variant/example.yaml b/demucs/conf/variant/example.yaml new file mode 100644 index 00000000..9b38aeca --- /dev/null +++ b/demucs/conf/variant/example.yaml @@ -0,0 +1,5 @@ +# @package _global_ + +model: hdemucs +hdemucs: + channels: 32 \ No newline at end of file diff --git a/demucs/conf/variant/finetune.yaml b/demucs/conf/variant/finetune.yaml new file mode 100644 index 00000000..c3ea21ed --- /dev/null +++ b/demucs/conf/variant/finetune.yaml @@ -0,0 +1,19 @@ +# @package _global_ + +epochs: 4 +batch_size: 16 +optim: + lr: 0.0006 +test: + every: 1 + sdr: false +dset: + segment: 28 + shift: 2 + +augment: + scale: + proba: 0 + shift_same: true + remix: + proba: 0 diff --git a/demucs/demucs.png b/demucs/demucs.png new file mode 100644 index 0000000000000000000000000000000000000000..d043f64442f24d1825dfabb3eed57ff0f843f64a GIT binary patch literal 339294 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%0Ar4F8@pxSe!)fMsWlY#9z&6JJ`J#>tyWQ* zDZb>QYg1WxPS$(xWq!#w+QA4Ia*l-xm@F1#q3&fpCT!PSRnjAU7{=Sc9d?M@?A!hO zugdLR8iUw@b;9KVu93Xu{D{sDj6WPP1bJE=X+nwb80ym{B+rwmrgo0CEA+KUO@djV zEtE@qeIL>vII`_~qu?5t_y$|3-cS_zye8tv_~yk15YYbtJZ7Spn2;Nw%m*$48 z1H>%ga-fVa@U02I2$yKZZKBkjqUSe8JPGO+?@!CwxuY?Mn}li3B1AB&OeY>5LIFu` zGHc*9^BeO|=}M;^Zgd4A;fL)hu>k6@szOAtA}t~qZZbo|x@m0PgB>Fsz|==$S&R2s zLQy4x3l;d2=XE1+G099Ja6!S7so|!WGR+tYg4+p$HVAlPB3CDnRgbv`l#yv8F((FOuNoly6$m+l7itkB|vF=2i}x@_Rb(u0|}-dR+8HN=<9CmDR543Ek;c|`xRwr z0Epf(t4+h6>j~fg!2)fX5<+IXL1j~#WChL}gwAFY7n65lyKg{^Pm_Mx$J1#EM=YHa z1eD>KvpUOz5fR>kSqK91AM0W6d?&GoY5Zp@!(PuQOHL(-X@8;*ZVaH`WHEEI;6?BQ zRM&87RI;ke&xQPnV(@;n%I*2e%J2pSgDh1(i!~}#6bd*^35Y>j7Vl zSrxx{qJx_>&}iY^kVDeHEMB6qOGciKvc7i6 zoV9JEl81mDkS-MH`m{_Ln1pE-LIIO+!vgHa$SrgkCYAp=H!8zrR?O#>^Z(%PdY6VF z3cv9m|MFQ&c;_zsqh$aJ_ul2&Q5FqWtiQ@+A^ZM9VJbw1(vDl^=inCN)UlZ?Cb?x< z9C>uwy8e|Y%q`m>M^qAtuHryuhX*u4GR&Wjbv2iG&($rKx1Im9FANo`cf-=mhxm1i z>^6zUjVR$NR*)hSNf`}y^8xw0fzo(e5h{2qWH=}`0237w++fafA%Y-fSfelu(ZlV3 zL2Ya%VxYP*D7;Tp%eu8D#f*7LZfq9OBnO@W$~Ajvf=Kua31|cp5(kl=Q3t5P;pf_Z zVSzG;Q%th_^%hJHvC#vSU^~MWfj8Jc7|mND4K{NX8YAk`9-VL$BeBiW^=S4o=X+BO4a}ck1IGZGYnsO&Q#2saJWUT0 z9Rv$a3%CrV3yCPVWR(Q z5c*Nuj(6M_P@S+RBrijOuo#9_i7BPB8zBvNII&`Y7X)S$_nMdPO^U4QO*cZNRX{S` zD8Y4b{XwcMvm7*ae+kHJTga*^JWAOT{ZDDpvutW|BbquS4L{6cgQdu7r|>&xFO z?v39F1FV(26D%a!o$O@g{Jx+sKl#(s?w6UR;Kksipk-&*#8UUy-tf^n8-_jnob&$S zkEU~>_*=z)#FP4?@6cX>GlEVAJVTv-C&DKqD6GjkrsD(p(*8f=fQ?$s5 zN|Bqy(~Tf>HGc-h-L(V_ z)Ly)Tw~|+}L!#9HD{X_QC6edZ@~bjy^z|S5ra^vfCs1$mH-DBQF72nEj+&(dXhe;_ zVJv9BHN-p!UQ!M zEv{Lk7?0@!VYjdTX82`kAA=H_iEPbs$du9s=fH`OyOb z)F#6sQ2_dBHF@=m@2+?9W?)S2W!x3IZP@Z7dWa_(GO0g5f4?#WkykA}EW|yiCxpU_ z@nFD9;ep8GeAC5Nio4I+3@%!)8>sGDTBtEPvC>A7N{~ve#rcI*E=FsJje*n zuHbRJ&-i(_GXXz4N4kaA3d9a*$UPN>4#;mBpChm@simkuG{V3+@+QV-Zx^ap|l>ydL}wJ}IC59YY}kgh2{qX5OiJojo#r zENMw#^AQX^0UBuIVjHa(z2l0rFigolcoR1-dVc$ya3I(!nr)Jw%u;woA0Rm4tV>dEh4LYb2*?6ykc)^Ug5VNH_z)3AB)t3yqMf>n@ZhID7D{uQ zcwoqwFo0+($+&TpgA-yY%uhfhLuM(-#A!bJUc#7mI)4IGGl3okK8@WUL8k8QC1_|i z--#k&L?4Ks@ksvg50bhssD_`@Cwcr)zyi)+&N@#PPdz;L_52Y>({MZY{|1^rpdAzt z7kXcy87z*U$mErv)KJQ1(~|nb{((UsIN0FrRpKA#Zr`A^+e$F|8jZCEfzARCDtQsPq%$Pg#0V*YqHkXLNUoyhZotT+P6+0KE@NsI0y^e#rs z0y_d}jteY@>v zUBET)5Xt1QF`qw_qVSt8SMeMjB#EFzW~D|Bn#kcBqsjBeoJB`2fbWd@X4Xj=v+sxeHiJPY9| zhyk1`c+vl8MO;vB!_zToUXGHX%F;0}NED1Btl)1%*S&ebL}e#QMZ2>%h^{!Es~y1- zH5ycG1@uP4)luVprGLV2p3ye&2!Eu;_}gN)lIiO7lA|Y@ko3(MY3;-Pi=|L5Ej_A8 zq;MT9H5Grf8%f*cKN#zOw30BWswMlyx-Xvzh9lzC*y_+#KJ&^+6scqgj(W?uK0j>7 zsj=J!E18YQGKyqhB%EmhP0&pcox6wne|7AIcjirK;UKiKB~`9?!k77IS1zl%bKgFw`V?91$zE-ixzN&fI%?~#Gg zodlI)GII-dQ>PU#mgNWMe-VfO6%GD(Oq_uDA>=SUDZQ3pn=^P7Ce4%&ZqARVMJO>@ zlA|XQhYnRQDG&<_E%IC_{r|`s|Kt*30%v~$ip@w&R*rdk)!u1MJf-c!&=W!H1u!cm zXBrW+k$?HY{=E*N>eV9BafU@M3k(WHyN>cm<+1PoP@Ulb5R%)m|3m`c5y~0r-+};^ zgdC4>!l<$?DG;G0+u?eQ(~>a>s0tNYwIow1QW@UrL85n`J>MSp$EmYVY__>HDm4|{ z&Q`5tfBgV3jm``IM;dbY>ST1XSl?+ck z0yvyMze*F&)9(`eYoGl4Fb}2xp=(&p+4&y$V4D=Um{6u!=4KM_XZMY zfeUW{?CW$%)1)W{3r1D2U;{^q8%f@v@n1gw73|OtiOPWCQ=B*=S{kL?=t{^@P7Kf> z0868=9WZQjD4;XOLRkru_+B~Ze8`Lp5CK2#K!*jv(>Rl>>=>}_nz5NC1EQW+kK2E$ z)_*Ix|0hIY(C%n*lD$+Msv6p+ed_O2(U2EIDAKotA0*R>P=NfE5(I}v%?+K}u)*yp zrz6_<>r(pnYrUb}^19!<0DI9yzhQ%EyZnOVe$sdr*YNmPKr`uAt?NDbvI2R%_TYRFLa@?5!A`ACN>BqDfrewGLK6voM>R)bA%`mt=0Lu-KmykD? z06D0t2BU+s@ABz(dm?AW?4}H3`ei7!G*;B;hg9s#M|!PKX+A=yDM^6VOU0N`Y}AKG zXIRhx=qI9Ad%x)0Z?Wrd{3!U`PnnN9m{+^ROV)tVWgAnPq@9t38ScZb9-3RW5OK{~ zO_I~+U#k}QY3K_d*c}1vf?ci9aLd}^HmXZ9vn@%+AbY; zB)P;IR)X*3OS7B^d}K9yYO3S?Ns*8TbG4s_jB6h6@ax54((`!UE|JMF;6K*}3f9o? z6&qjZO?NwlCTag4bBr>MpX_*{cFyb0aH-4dd*`*H-{VG5>#kvpgV|WBJ`U5{8d0P7 zBP$F@OL~H+{R4mqn!EU0Yx7_@v;F)_1_r@O0tjsm;HCH!4?umz0LZPcWGDpFy2rLg zxoS!1GEvC(t<0>=udR^j=3{+U=RLrZqW+nZ#1%;Kys8kJmpNZ!I%@aXS-dp4oQP@? zx_f^~x;c4lf@pEG73bfd$e;s&v!OU}+GN0eFzr>5Mz{DSPUh%2k!pQxJ5Go;s@srE zBFccW7$cQYON7%_kEynMS?$~Hw|W24;uc(lVE|67k?UQU6^+lP8jAL=YT$86r@sdl zO7xbd9P|0sLq;j(Thggdo#*#fFPVjg_oMl9J?7COEzSG$g8+~3i^m7~!|3P&my9dw zks$g07M=2zp8(YXr==T#Rs}Bxq{*&48`f^bPAIAf^qd942M79{mq}So!&!tOBH%0M zDmCSPWa)&K^hjGLkIb_P7$yT#y0V(Pw}WYV6ab`t_5~^-5ADU4!tkHnchoXvKJBTS zn$PW&ecfy$)*m|q5 zxVms#vvBty0fGeg;1Jy1J-AzN4^~)!;O-XO9SSG76N0>$w3OwktagE)<6m*6fzjT$M%IRo@pO#;-+mN*N#8Uu&F#J`AN8=1kdA zwS9Gtmn&$g(5crHBq)(pz+G?bubNG~^p8Y1v+pSW=_ep+-_{tFcz=_%A~ZJAB_d8tS7wFFVAYO7Lw!4SAf{jtHG zG0jaY4JEBnrLI7soiyeF`UKttjW-)Q z%)eGZJn=}AkV<*3W&IR@0>!nIBzdDY8lB11_T1%P)^EJN@`M$%@DN?I(6%(%dOYcW_i2-gHcevyEFK+e8pH_@q6!|f)$vAodx-g4`6 zZ;sznp3h@{@s)5&XhMVQJ}Ku5jZ&JCu;F}FjO}Lkos-?4z)^L>C}LjuAZMG@1I_$g6Xva{kNOYtjYc?|a}(uf9D0s6-~q{Nn;Z z@hCJOy;!_r#5ckZvdX*uZJwJq5FVi1V5I`UF~60FslE#2#`?T{;&V@J0&wt@hm%5v z&el%{i;A@Ob~X@A`uF#fEI2T(2L2!hS*7d#ztz=Ma@8gu7Z<0KgI++ap?kmT#?~8Yal3$T-Zl*F8s_9a^F%GK92Jb)R72zN|hQ8iK zWU7pt0mlE`mqHXatBZMO;Z-`4pC!fnG_O0?4xPzGQG}xfQ`=_UgT7`)T}lPIgV9&R z-IHcn)EOg$(IDvLbtpsBcmUKPJ8&cD{CI@>STi|j^SZJ>nC?HrX$8=j|Gh^9?8%s8 z6r!)T4*4kLj0adviYFi=M-M1w<|3yIxG(27=^)b94UIOvZCYsOk~s??+qVqUPq&MLEaxb zu}&Cnch)K2{_{DXI;`xDXeLhkmqt|3!v}uu-s=guE&y(0kDA6jVin+r~ z-Ye-pff79&G5mwAo|iuQF(k=Hvo;xOD!_=*@#E9>lF!u+QhqQguQMbLD>Ca#F#)To zbP_#hpS9gytFt-7@IP~uqDTV+V#e9Szl zm|iZhx`vbOmUnLVm?(p;-~lB=k=Gwu9NmPAFPphg)Z-3Kj5>uH*L&FdG{x_4zHCNg zt|OyDPNsuN+GR4{syBm)!||f*^VA+@)rF^WDa>+{e!jDCjsZ7sxuNPRUGA2VWidrx zCaREQ#y+ykE^!!ZMR;l8TZ`eoY|dE+a783f;S9)(m<<>HNTQGDvt;(_1hCu(2_k-T zmNbw_P+(c}H+K&P(8J!fh-#^imp}hV$(d?DGXB()%aDt)=jOK0+{XA2Yrc6;qRhV< z0|C0<$SH&x<+3Xfrw=c8xO!1}cFWBG-;K)t^~m-|XS!g0ty#u5+7#(_)@KupztuUZ2B&nWy4xd{LfVouS+YyzviDum09F$zd5 z3xF=4thSAUVHhL&l11=Y3^-6s{jQ0DzvbD_!P$He0TygYj)N@6BZ0Ax_|rw=A)am7b%>TDu#(z(_J_auvs;J1fd@EgiLMQZKg`daoExONdAz?gHP0I;d;7X0HC!=grS#jUERhZuQR9OC zFaM}eR9A~73O-AmbK&UFKU{{FRxsOUYO6UhzpFMdFZsTbb28jkau{nRg6ZaedCFa< zw=L{P_)@8&=-CEaDDJEvjG6hhL;t-XM!&;d0gutZ>;sL=c?;G+B$gZ#we{=~w@PJY zC~VE>f?8?Dx2Ky!d3-rs7INDZ#_3w>TO5;ij6&JO#9Dv(*ri!p+L{};S?_#Ha5E2dOH&`FA`2jl`fyjp}BhWLpB6CNx=)R?e+P72GR#R zAP~L4us7Shc4Q2&`L!RzT9&w5X3tAi@WOaZI)jO%T0`+w9ceRBOn+d{Y(rlr3suTyETu0!1*;~7jd{Z83e)xRqY8b!ahKX(}o z_e$6(EVsC1+?>6(AbDR_aAt*ohy$1~+LtP)>8= zY%C-wH{^fwsIv4#N2({&OtJh4%R#H`*z)>(ZVOL4SGC$rDeYV(QIl9}@wH@RAna_; z{xMa+TM@{dXr)hPmahDkho9WbKC#3h0{uRm@Li~9%Oe;Rk{WcqJ4$mrBf-KUOwL^e zX!r$Lnc-z~6EpvYg=hcl{7?>eH9#uUBAXHIm>;G&(JKBn~4 zT6I^GPWpvAZWey$UN%Q62FD|cSgxVaSXLmDwRC>B(HV|Hos3t--LnQmY|!Z=A^Fa# z$doq487_f(I!R|evjL|s-BH<-SpqIKOD>=fjBGv#pO#ykpV+;Ll?6k&gnigR) z=B!A#%Z}#djBux%)--4Tpe>>#J57bP4f1t4v__7-@lQ2Mf|Hu^TY9}n#!MRmqL@^3`3?FPc&-CLn%w=aj zB2U?*;ZM|$J^isVR?4*|vyM~q#f!uKvk7J?-$a$ANv0zc%Q@E$cT)_nkFi~(>MfXX zjcDd}D9o@Pp>MxAB% zuWC)Dq`UlrP&e|S7&Wu_nz?nDBR#al;3L2Sez%(WL8cD5oxYQU*UwfSa@y2_e=Y$#? zNg53%VBPvqtIew~0M%x;?FtQp_YSzdRC)0fR>jH@zNS#S1VccM+~2j!7v&PtD>D30 zeZxp4vz0Omwt2C4G@P~t8PE)^fYqE=%(H+aNb8Zd)q;Uax5@rTZs!L&Rcn4@4;&(H zyCw1ZFo$v@LBE&WB;iQ6ryH58?<<~!+kQe^w*itjppU#_fe;olPKS@ecCVlwED9hI zRMC5EnS0Xnx42BfuB`)oZifR}gEgc?3yF6h*PrC|Rcm@X zK3|`NlHRC{YP0DyeGebdyo+5U#fatix?V1e?18%Qe7Jyevt!xw;Q#zSl*S(8^K>{^ zw$VieER@k2^MEyh7Sxl=S1q{Lv7g^)Nb0r_p^!hinCs`}^P(_)wr7=zr=;GLXLY+r zX3-2E+(kf9w!DhKN*8gN`bYI|zc+ge$@RM~qmdI$Ca*)4lbdPY^6hH0q?PGopp8a3 zvn>ElV^xDBV>=*V+P*6C$rPlQQ>SyleuJ3RhL=BYmjC(cRh%s3>R;FNiEpS+#gtNR zK>lig?-VyvL#2CX$I$vT6cNGn*-dK|9`<3-?W+92eT|I!b2H!(v+=nPW_;|X+$<%B;AV_kX zM0-_R!s!u})(fRQv)M5K+V$POx=7JnPCx@hJliMc%KIl!242$o*2`|CCeNwVJvrhNU$Ph1 zWNF0Yr%yD&d`wQGk~r1BqEn?}h6_hiZUj$Lel#9SFa9euPo>9l?05xerBntl&y#9( zW-_?6k_o1*HIYEll7yOM&W)rDAav!W@$TJmSW8+>S9$2i*Zn#C7E-8~$4IA97dca= zKM4|y5Aa-ehgkR9_T>;={5dn@wpIj5JLGeP0bX^8B_2%2*JN< zWN@L(tleRXa*e)dz+gO`cb2j>tyoihYo1=SKp0+}sA)Xbfj zrzU4$p7hJD(V<)tTU71KLMn6i=4P+|Uk|QgC?x@JH%>P+;)lw(q#2jPD~ahrT9va= z#B8dHB;f92PnI5m3k{p3c(y>=t(2tX$7VglWBP_b$5)~kx<809IY7``rZE~R!BvJY zN3dHft(eK9DEJI)w|7ie>LMK84-b|Fx08S3d_-M?hQgXl^L@UPwLxnmlbKA&1z`Ul z9iQKxJD|TayB%?qxjua9zMjD0C(T9{_U<$r{)fErYxKhKXNk#Nejua6@$q|O%P&V)y`?tt!YAbI_JhdtT$s*sTwM#uh{PK5yfqN8T zQJ4H#c5Noa1Ny@^4m;{`4%RU4avS+5p+2{0^_{*9);KD49B-f5vhV6BSP^Cw9k3YH2ntX(Aq<9M>V7G2D z#~Rd{-R;H;eynG9DKPn8<1;0?+pVb^lTK=5GbAxM98qI_6c&x;Lcs(b1Y>ofkR_$c zR0w*{_FJAl-4dQR_BPl#6rd{vZ7F5I@ozW6b|qKoRec+?q;mO*uqtWC+v#=oAPo9C z;4NZmO_FrLei`0Jzc+;UK-2bZ1Do<9Tk@mA36T2}<<1ur6ElCdF|w7gCS~B?7*cCd zsa-Go1O1?!aG1hiln-NzUY?j7DFE1IJ{*Lx{P?^)OyPZ^(&;tu2rJ2tqT?DrNdkMm zQK{5=v!`=a5o39w;mfTp4E#=M1@=KQt-)Mv!^z0lJawp0xA`oXf%|vyFrTT!d_TR*SEpdeln5mi zR1LWYncfNi_Nb58+=X_%7vFKd-V?M_FmWH=zbrW(t5T_Y`M?dv#Q@z7lF{T zoF{ooj=9_cLZYENY;77QW6lol*JZIiA_I~Ys#qO|Yl$h9BPkm2{Cn)K) z>W!&{R72i#%6J+t5|`}t-`-?P`!ilEk3?^9{oLLpf@*6-n6#NMl z?xB5!)l${sY0GUL7|g}Czu1$!WC@JjcdwTM`^)gg0&(Hl{+)T>_T)m3GTV**Kqnxw z^Iy(BFJos*I#yRxW*FD{%2q1m?REj{L6+e?g$zoGU(ce{#C1n;t&w|C^6`+4j^(B- z!+(j}gos@p=Yox%8aL(m;|VdX^YJD^2?LU8{YM@3l*KK15vyAgzYgMP2LgXaQ1`)x zNv2j5VOIS_!1$C{bR;U3g-B3At%19d$J^f#EzzomA|{WEw~E#pcaDTMki+KETA-9L zk$Ry}_=GVp=_cRwtdZuZf^SFk;f6%OZ{>q&y9_qX?dVY9U->H=J<3;5i7;YxIHBc; zS-Pw#vsCma(-j52uInnnp!8MsnEF#;)F@wP#WN54b=GHe=)gx<@%>4|FM{V*=@61N zf1w*1>q}>LQ(x4B*4$_(JF*?r~`%?U#(p{`t!-*(7<+A5c?VauXVd+L7>yj+Hy>qx>$=zcHFpAmn+Y!;F1L=a zaSd~xlaLz>IVXA11aV$JdP!Dat53kTv2QkzE#`dWX8uZvf;b9G8;Q$awKn}cl2@~` zUNh7DTSD%^Efj6S3D;wy*pEiQuf`ER6_fi0boiA(4(gZKzG%)mIH90b=(7vRZDp<{ z@{zXbZC{45LG2*<3*$z(Xd;m3zg=Pe&inxo^C9ZQ;6LSG6`A=8UQm>H5Sqx2~1set$dF{^1-gtd$$ATcwkBW zF;2$h%EsZlUcdUmRV5+c@{QA2fe#zpb!nAs&buSe!f8BZL$XESkR9Q_f^Z?)Ag4nr zRw)}eu0H1```4!R4#3_S1)cZSx^CpHua=oG7JnP?<5Y1~nfQNM00BHlHaTL=OkeY^ zGQIbslv_{N>c#%33v^BQZ7XQqFYLQ!!!^tUFlETnM5>_9{O9L!l348nY1!aqme-(W z6#Oxblr67#8V|qiPQ5}|g|4qzLU-ru48W03i`PKrpn(5%g;CtuzukP$d_AS6_imD= zUy5Qn#{~bhj=IiIHmDQ{&m+ogiHhwB%eZ#*3)(#Di*R{$k;?b;uJ>q_PWYR4A#@?1 zRgdP9jHBWhwQ-y-t{$)`p4#w?1L3G*G%>GeNSvUcplXBBCW0Q6SYhoK%|qv?!puA* zwY49wXbSYIOVu9x0P$ut2|v%f8rDhKp<`|7r9ZeqMZ_(cG3~WVs^lvTts+?sSvNWB zPe{rFTfwH~<=C4Ch*@bg%%O!=GS-DeBowo8{(0`?-B9%%N9w_(13q3v{~x05C4n2VeqJ+4_XFioUBa?tY==OS?xtksB+Iym>k{5Bk1G#rQ733 z;hMDats_6Rb<~%hMYwwH8nVwfr=I3Tbo_A9rKM8;NA@DClOyBd@wZyp_KY!)rb_xG z+Y#XSof2?Ks%uSs-t#TaBK0R`q)Zvk7D&mFXUAQ6MgVEHy7FG*?=D*8wo{jpBmFGo zpjVr#(m#CmD^Ef&5iH5AEv-+&FP~?`$G%Mqyhu2%Rg~pP`sV9&7rn5?tBz>9-42g7X^CCBd*2#p$o_FhiR^erk&^9f{OBF7w(+7= z+*g;KzYu@8xY1;m_`7r0s|Y3Fj@p*m8`E(!oXCr&h`GbDy~;=j7e7CDj3raj8i(fr zp`Y!SYa$|HDA!P@`AOc*=ar$q@aPU>@SA5V9|oe1Ff*No^mI=}QG z)qj~xU~m3DFJM$MZy)BT&!pD(xdzIIFpB8U?0sx&Iw<&i4<2mIv<9??aGlqw)~2xt zInE)un~QuV?FPtri(w(X@2fs2S37lGgIftPAWpV%D5Keq^OZ`CvX3V$@8o$AVITaM&(dR!#p#R4V2Z`&lVyYNkgLO^=WhA^-uS~N#U877^ zAqv04B-rZ%Op_hVMZQ_>?|9XKyy3Mn8fJs`0?_*}8B|#1x<0<28CU${|6~job5~^| zVLbNy6X!!Xk_WLrDme|S>(;#BON_mxGj_TkyR;ha|Ga4(ME_G`XI>S#|LQ_~KeAdGS@pUXOP z$pA1XX64qI?{lKZEQjXAAgy@ak&Bnesw|Yx#m|0{NhdB`qstkz3GAvSV(xQrG%uS< zBXUxKZBF(-K+{42?mb#aVKL0pEO;Jis9BnfJ;T=w!(~WG4<-QO1vMqs6wHKTP4sHqz`IYKtB*9R)99STep}DGZKT>17 zo^g^tRti=GQI@`>RgWBWdMade(s#YxC2;%`XPdNgPKhF#Ct|=({tf*YfvCeK^mKp& zm%$$8Mx#+SV{wdJ;qx7)#%hzl9v6OO42U*NW0Hwqa|sPuzwy{2HI0HRZnR)@Q=O|= zbX}?6iDte^8)Qsk)LGIl?R-9y<1HrRGJFe^-0UWCLqI>#ShG;R*dmG{(HB6nDd&t_ z7<3(Rc?w{#e!B4p-ubNmHs4D*2X*nKS(n#*8F}1c=LuLZn)hGzOG+}&JG6_{peH~q zi&~Kr#yL%Yj#-Qb-yY8&bOxrQ>3fa&glU{VlN+XSN=Im|Uo_+W`5j6XwJ?QG$dR1c z;?um>P3Wo3&cCmI4v4ORBTA^>12)i~g-srjHO5#ITlYDPl~v&qTW*$O1dmMEMt*+? zcHkG|r@%z=!R}KsJL}@b9CX};z6n31*CK(vWi{vWqCrJiavcxt%835+TnB_vEI7?|pSKX3+4$5dwzNsz2IbC2 zr!ZH~W4raU^2=a>IUkV}f4LDMkGD8O$*7qM=Qg)^1(OCXs}k)CRgxb1hnQ~yfBQrM zw{4uq+II$uftRYqMa6@@JyX0cN*}LMKbWY|A3AuiQrN7 zdGS`V->`(k*LeIW4)P)i3&`a6O?N6EH)aAJqHO7ck06ycP7P?+oo2E?}0-m6l64LQdgv>B`(FQy61Ca z6|Q}K|2kZfCq%R%Oezo_g2Cz+BgX$8KGPo;L$$@Vfy1mKcsQL=KnH3WW-pS-NUlUJ zG5!uYh)+uNKNNa(Ed9duuH-r^gM=S`@bNc1li5gRAuQ}UbLrFT98(iqzof`n}XgQLpl9Mr22FL#ycT`K6J5Qj_a634;@>^+b;-dNM z&nPzcf26JV7WQ!3mpkdqV_W464sfSLPK8h^b7N_{`SzbK+1O#T#zBy}r9KzPm4W{JJ{?o`%ns&k6_zb_li-rg;EEdN z_mNMc{>WyASl_!2ajO58V}Mcric%RZG4%+K0$ZyIr~?x2*N$z#2oaifHf*FK?Y-K! z+9xpZCMBv7A=NCeme~O7fHPSS*LDVh;QLHTX)Pgiat*Ap#FH>PlV~E0e60FzMr4}2 z_OL`$i#fT12_YEkMswLiqluV(QFQTNAY-vL=V)VAd97y0tGkpb9VHe53Eb|ehGtA? z;y_(8trIr8)@+H&AZ0pv+s>oQK4sgt7&fyDx97>+(*(v4FP!EJ9;b5#SLHqVG&YfX zG3q3ILL1LtfvX>e)ntmsbmZTq!(gl|40{+SS3TmW2e9VHoOkI3Yk0YLqg(0X3hfSK}`wGhz zzWF04q&bm4SX|Oi31fQ&k(-?MCE`*kyzRGtXYi_VMWJPx3gfa?t%!}P^)4ts(B#vj zdfyz_Rt$yC5T} z>u=dLpiQ8TlO5Ti1F}QcUD;3LsCmcC^)s`l9s>8(yP>YoKk-&1S1T!eP86rl?EDwZ z!MK-Ja!xx$aTfteAHk(3MEhcd?FfH+FfY~GHNW`X&B*|Npzq!|?c_pJ*Xu9@U;eR* z)VCzV2cIkA{#WZKaeFxDzp2JuL;LGKOajk$>~jg*G)f}}IsVhADV^U~NZk-lm-CBX zDtv_smPEQg;tAmWUAM+g@0zvyt#OT)u5e4CaQvR-^S3Q!QE)r4m7Kf6&~R1K{nNP3c`Zm? z<74y$WpU%xbg3plP#w6%%+#Ncd9sW zQciSb%&qT3gRjrW^{Lvc1+L5R`qBbKG*yfSwR%wg<_Vcr3t@(ZB3b)Ux6a{f3}*=8 zgpMlxsw#Rogic+K)2MbPhEjF)*|Q!8mgDrlob!*pvs|&_*P5+Qq4zNk4D53JZws%g z!^X1Ti*Ftb@#}?3^e%Q&249%{YE>n(6n4 zQyH*~U^Xnzjikr8P~M#f+G5U^6+>J?HLXhB4Ply*0TS)US;*b_7) z+1JRd%f-?c>Z>}>PQZ3F+eTVVxD^$w8ls}Ud}T;G``TA7P^p<98AW(F(3I~wmCY=^ zOq1oQr62psYLeI$&MQvj#DEeDV`TtJ216^MQOA4{#@3>$&{F(k%}{`f;&?|cE+?;V z_N9@idCA!-aQF;~4C#!rMNc2Yqa>G%kMfkPf)UggxBv)}sL>l0$~7f%Y?il(guTQo zi6KMKB#V85NK1kkdV$MZ-m9(B)n}xsr>G0mI}OnD9=8i1I75hp$)R4eQ$F;xl56B; zq7szZn2S4EqQB`M6ZX5VnGW3kz!>|!2_B|D)#jsGsgT}wFX!dnx6YP(uno0NFh5u* zk2yKB@3lg;Ir(Z|%sO|8SC78!NR!gcED15Cbwj zLo$oQ%CE(yf?xEEW#!Kdc#AY~124ZU=2r{9d1nU@r_=URe{qgRdHbZQHGOSUXoTbK zIdpA~B-i`g@k18NEy(V-OU`vB6V6oE@JpLaC5c1lW6`|FyLC*?tKD|LoW<5mw!7iV zT`xLj*tGicfeeb@6@wol|&qxapcA`6Oatny><~*f`S)DhWq2;dC1BcSf?Rb@bEJvg|B~G_j(>(*fwKW3Dp)BShy}!mOl@V18VB=){Ul# zwu?XXB#s~TzxY?lzy?FZ$Z_H=Z1u~2L|LvgkKhR2W-;xj-oq5eBeCkgqGGeXb6ut0 z_BFLe$M8iT7BtYJx}|yb2DF@hXAb&T5WAWjuk*Ez*(bqgr}7_L^Pw>`+^OG1bkDZ8 zDH1p$HUM`A(O7w))^~tbqAJ0}z6O%IuyK?VKu1V5%fjQa)MpHqS-~byp-!`Rx1-B# zb=;QsolKfeMmu?)e7rV;d=r)dtYfo&O(%;X6ehX z^l@f2Udc%Og!+|c_9or!xFFdc$yvNISsMosszA4z14QjAVawg4S=9=wsV`8-P~h*M zx`7A(L0UbLU8+0%$C@-8qDOIdxSzIuOD7`C?aTH*5lYir zZQv?rxUAhCjKtKgCTrD>`z%=+E4Lb~S=yM?nhi^9RxPrG9=SovTwca}|LyyIcE^D8 zdhXeE95!Z8A|UV?;%{u&LJs{$#u#_`MTI4h1zy0MgNr{)=xUtB^JSevMnxzc4WU^!+4 zk`Nd$PY&wG@jL98dJJ}CSro)Wgf4-1bfw&%tVUDvIKoBh6GF&Pu^m|L(3qkYx(xab zU+MTS(LvriNi)Efc)WV48eb^Zq2E3*v1VV7#+Y(4rDH5QZn%)f8u3je5+>B_T*yt%|iMCo7MMs3-CbCNfqmui$JQ+GTGH+#^1Jo7v;oW(k8eK!iN{ZMzX zTyXd#_6yybUu#Z&ZM9jn46QjkY#d52gs|&jivmUz(kmE_hk3&)g1^sy}p29jcM+*fTz z4Zt2T4egLj-)e=7WU_61ci=L-+r90$p;8ePGo9^i*S9CEo^`BuwF}bm3PmNQ)^Bs? z%oSq12%!{yzB|>D5I5oVIFr{th+jrXf%%^=zSt0sryLIUg^RFZ{yA6}jzeR!AS-+A zwbGhBaEpchp{MVuHK`l02XNTcv@TnJJsc!&=#X;5xrUz2mjAUkcv$XTISTdPr+ubC z0Nn9nm3_~^6b>ytsH;)L>I%8s4Y1rqj$S1(Fw>$c7__;y0heUMc|(O)x!qG_HNNk8 zjuk_9AK>A+$;AU2&mbDnKvBkEp|^*8nQcYq=s|Mf&52LTg%EoiKdF0B;0J^M=j(b% z5dIa)6lX}YAhPGiYA@ou=4b%V@PAi)!JIr#bRn|b#FvIb9>M?98IZ|<9c$H))TVpC$}^V#?IK5BOb!IDBRnn2 z7ZfD!nOt3zENm?74_i&Wd$&NhCIIj)0lfW^azJw)qY>&|`#nL_>s3!Tvd$i3_m+RCZCE*8ArcNLlZUTpm9 zq|N95|8G-42O-M<#CMtaFH5}}ZVWwvJwT0=1=LJtfQs{yH*wCvgAW zW^X7E)XHF7W@vBuUl}z5oA~z}Oyw_&ZC>$-Qm1b!iboM@09s*c0EL|ZNace^Gq&BA zcDwT(Hvlg%HH>D3b}h-S^F87Jr_VnV^EmHF*YG!G``+mcr?D4{N8p_XxWB(WJ1jQY zWdK9ZUVsZR#Pu+~_8+Bu>fhtXTy*HKlUaP0S1SBApF|orvtt&Bwd>5ncSchtkFSZc zB}t5#LRNyyG|JN zBMY`cY6Hn=2Yi%Ah$Z&-o)nyhbP;v-hHH{5+#N0n_y*cw@rtx&Qb(xRi z@p@k5QYvNZ0m=3@jC3x0W2~D3FA8r6j9`E!nFyf2z0D^>Q1B@M@kKnDNFc%l1g}@H;n$>2V6*@?(FujF@FMpkg_zUxZmR8R|Jjx zuW}R>g_v`A^y<)sV6BVOq6ZmuxsSb~t%Oj0ja04ebF@@#_i zJmj!2&>dTV1zQf~)}J!{QZ{LOO@R{aL|!fyEyG*#{A<+bNcPGg)1ro!q)38^|PcVS>|! z4&P3TSGDjot!9UN#zcQfOwX2UjSIM)Esywa>F~Orqz=!Gy;UXiSe`!ucuM(ChttK} z_v|IC*ThWvtuy>Nh$DbGQ+Bf_#BTPi`M>8XE(``TotLYW&Qbc2^D3f+-Jsnwo>t}a zb;h|w1RjzB^N^s@SKp_G0>!MQ>zcT@F*lHCPe`B`B}|^3r7x@LKq9c;m;T7l*!q6D z-nD*l=+vx@g6_Yt!|@?Lvo3<#?embUxq|iiE1DAGu;B9#JA?ac<%kba03})Xvir6EUE=` zfi)>WVM)UJ`?5#4Mh-G)`o4(z7kDU($V%F=gp-e9j`FVwK`5u*HX@H1Txs8XKTlZ- zt7%gL9k*Y(m*EX3RGIuKc9NvZYM%T10vU+A_-FR@ipu~3*?zW|TCpPMJ$((U3)Af- zpffk11G=Q<15Ci|VYbVErO?M2w2KX9DDhoZmZnn7Tv+EflZu3nrX;~rU)uJ02f#D% z1OOj%-ixPueN2w_Psy|l?cW7MbfW`x#jm6MmFqW2kExw)AGmn`13O;7z-zH$aY=11 zmm}KInS~|MYp_Mg889070A(y#0{zP-MI_*srRdWX=C9U1eaPL|;9uAN4pIZoqC)0o zZFI4cfl`69qEv(Ih24z?lFFh}E%;LW*x_|;@du3)karB=;-cW(no#vT_4)Ci09}J8 zUmSv@Ji_}#OiVt&bC6QpU5xSRtLw#yedGc$-o5GY^AQvhW^Ww-0 zE}uQhcBS^DUfD4IKdt+eAByG}etOZ-ie5IF4x(>M1k@cbHQGvMLw}2l>7!Ep>2i~C zO`NYkVzjx#Gs3y^r4FZ%>Y8{k^H}aEkN@LE+@EZh8cV~tphU}xi$JfGC(!he@zZ#w zYz~k8zDw0M!D%k4xRad!Ax&NC+mxWM8RL+>;KZ>tz9Smc*e2&`+Xo+X0K>fON&Umy zFUU}Cf%an_Q%DWLJXOOMq@!iWnXJMYJp2i&JNVNF)5GvVeuSC|3a3;X7R4lgtx`@ ziR&ifd5WD{;%R%wXWQz1qPK05e$i28Q06|&vqH{+(46V0;gM;ca1I0mLoBiRK7Re8 zTL}-)Iu!nMJ95;^zsG9qPP7NXT2LF^(R;i>yPt7 zYS!X+I?0qIn(5~p(pVxx=}gL*xV+5hWR*PniQw}P#b{n-HW7k&nX5DyL(1(>A$CAC zqT>m=*yc81j0~vjlg92EZUwWdoCCw5{HGiIdEFiU+}Xt{>+V+?ZQ>0QtfN3E`p?S2 z+*z#FHDsFuUWz9e6t>hrOgA`xWCp%^LPK^4I$!1gB7Q|Q4*UDFxliUZhtptVvI?L5GnxZdB3Pth z-~tNDu+ztDTJX{t(WVoS+nO={gpfe>VYSZ05HT|gt)S?|*R*dAjPi?Du)Fn|;?Zkw zS|LEAsv>Ag2Mf*vX60q=+wbpz*8mgd0c7-%+o?dL#c}Zmogv^EsUNDnPw`gFsWm|; zaNI}(N&lmM5wR9t*NN>;0Ic;HAt?T)UO#hYP)MyqTL{ZJUyK5v|FBH-qMUs)-O%3@ zY`f&Vi*8v5CXv(#Al0!~z+8LFEnLoF%508~^eeSKRV~&9GPjL=onsASkVTQ|rp~Y8*b?l;mmWVRqz#QflMTQF5fPa8uK7@howq2;ngUYfJ#)|GMCl7;C1c|Je$S+I^&c#9dLN-VQWZHF^k%LjF*0onRaOO2jRr_z^o(z00C4}&0VELSWfQ4as zU;ZTFiA0rCpJgKccGMu`x65g-?@Er(ZM{|tioB7 zz_MWs`WGC5&p@3uj$(H?F#ZyBcDwwWsvp|&TiWlb$2bG7xgNSWD!O(*p776>e(ZBW zS+2c^4O*2`HxXa&jQLo#AwdRG>lh5OkrA;s$>`6v6`bfF`yBSZ>=}bUcEuHE_uUSC zY*NbjVjPIHn%mL6=XbQS;|$U(44pYaz+X&`vT ziSB_|cmU?$^c(Gi*3a>HtTol4&w+4+d)^Hk%q_%WVZ_3iWWn0%La5}P9Z+z=QO(;8 z0kdddU_c?5%2t$6itP?i0hOVLDcl>QtWR(yHJbj3f;Tu!U-oHxp`pR3L%&(ToZZO# z3NgVoyY$-g8~*JAf|TdRF4zB$(2ZG zG%pWr5v<^NG+UBiBHaXdoSi{pWA*OLcbaPx`V%h1ViTU&zWFR5Hqhs~!DI?+;;ZgJ z_Wj00<4$-UHm-K6l48*i&WSRtSJX?J#wp`I5lb0iYI_o91uyJ3jCf59LM6Y!(z98)G_ zBw#vb){92DAfi?{BeGOD&o##O^PNIxi)I|eDD;Wxwy)nz^V*M)QGN`HLNeQew-S!T zjWv6AK>zyoM>O0V8J`IB1ts$8h%hk1N;udEz8eI+i6#E*qm6oD7cQv#+a4Sp1yib1 zUljR?Zw^d_hsj6{YQPZFvlL;U|AR3QOr6@FsQ&@BSn-VaOnEF%`#N({%(2QNdGj%N z-~Gj^Oy^}o(dHb@ALeNje~+opSV%LCg!6rm^lp}5Rnx~-R?Ug|<{NT|WAX88U%=libsh5WvI z#Mi8+%6N~)Vtn7Td=Oxf<1{d@Y1yDYnlA4;EV+r~8LPy;gT{1pZ%!83hw3&9g@X}7 zRuCBpt>O@8%tf7scZJ2bK!Jf9F1AeGskP+NP*{o{IIw7ezxpBiV3>j$$fj)2#TH?F z6X>rajRY7+30?)i*1t~~l<0WvgnS8pLLkfKw+sFNMeq^EK0lykn`hbdM0NeWXnDvITBAyX`1F4%pp%P9o+g#v%Z|#pR9^1>$Z_W00d-UC0nvxt4@V^c0 zvlKnKig38mo6sjaqCce(5^yN6j5!w=JvKuwSSy#j%K<`)Tb;@n$)FYIEb-;U=8ZM4 ziT|0WJ?w(cIX=wj1*?Yf&g1MrfiuYvb$ayzwisw9k$0d-_wbjb_7+;kmR}Bd-xt^> zI{EzB*MIYyt`!*GAc^u0KP5Dl?0Z5;J_fy5knHe-u?s)K))Y*FFrUQMtr4ox_+24$ za}obyn&PICc`T}H$>7N%D|*b69zPwI*`;*0e&9z3)ofNr4Wq7U7`LexfI`|q5T;Ew z;JCgz_>^z!Zeq7v%o!Yx1iqtP62N;v)MkuI#!N)S0AWHS<-zfYT-1U0iO7S5drsj3 z`Z$wIP(b0wRH75mINocIryqu9WM1^86>De%YPg^*GJ|!ogURkFJ&_RmY(kHDw?P-* zNTw_W-T#1_(t;5hLdXU?23uL6l2xR6SDCXS5YIkEkXA`tB*=&M2_jm1h9(m$P{L}% zy{OI2oiBR46*(|QLzFO2r##MF9J_hv9ffrClu5pf2L@OMYLg5QV)y+He%efnnV7oM zA-ltj650-KUg7i3Il!L`PY5d%!P;z1{@Swj=FW@h&eKiY$3Fld+eh0b+Qy$FO|USx z6LH2W$J_#jITEcYAIUzVqPQf+jy3nCvHN>c>=Bvl#afPa%w2~!Kuz9m$BEIlvXm&zzWH#tvJzFN|St&hcO79oNvRi!lgC6^Sc)q z2IQN~S@f`lE>YX)x-DX;^T-D{~GHwoPGtVFv1k_Ka%Jq3^*ci5k$igG!Q`S^#** z`?vk|hf#)`YQfCUj}n+4?!6OmJRKN6vhG@;5cUJK9ZQyiEd`{C*7x5&AJK~tfwX(W znBbTCF#C(}btd`8;aaO4p%3l+;0m*){Wb)wrLKqZ7x#U^!f9~@vYrkj^`dDrPy$uO z-7wyiNJuzL-rwR->HIkZpJ+~7wfceCGy!_?!Lk! zx+4-kuHj6o%-_#}bcn2NF`EN}chFPPjmfrqW`? zfdM}K9XtjZ2t+4Nw*|#6z2t8DU1&hfU{gkqTSMFihaG2U##o!6DZvdRD6S#Yp?z`# z+J3_G2ayU5&eczJs89sl-w`pr%hY?OlRe1c$o7O~;T3W~khtths>%!GiSNtXX7uUVkQ$AY$X_Z}&n&+cH- z$FUkQ{A(;^!tMSqpdUn5@`<~Q$c*4OJl1{Ja!xV4rEPxu@xt-jw0&5qFYbBtIFjf7 z_1gCT#9`0&ASTf%hP|XfnN*zAAFQTK9ZchwJhcP$e#eNVpT`y-qC81jT1QVtGS(hP zZa{oSaSpG-5m_jC{Ci4!+64q?)-73m8A0+k8T=k$23T-zB3V>4MrWpiK~y=HEND1% zYAC$DUSwp$Jnh7g4Tp{$;1eF=~G(jU2J?7$BX24-)1Yn~osw$B9P|8TPi9Z&OzU*8c&x40PBwHEP(eT!JS#&f$u8uP>E+jOGmuEe zXeJQM3b@Iz3IVY&@bSX`Vf^u@`bXQ%fFRVq!hju!oE%5(Rw+U}lgW0yJZgJgu_Czd zgT`}DF<(Clu-fuDAA`b~QiD|Txu|l%uiQ9&Odlvx%ZM?R$ zsRMOz%mGyI?yEi-*gS&{py*&~S~Uo#0t*}ktcVEr@O5DOEFkE%wY)}~SQC6Mf9hAO z|5-Tue@5HThl=Xz-kr*^^LE9Y_$NV?qty%30~Zhp8TKy!(h8j?YO9?MQ%qULs#e58coPL1qndd z)prluG&?ZGZ)U(@Pji_5Sjq!UGHrI7m-%^K8XR3#^Z#Ujy%)F)?t|VO=qVz*yC|Je zt+UkX>FhcPaYiynwy@Oq8R-0}zR`6B22n(vEUei4n;k|-ArR!!Y;$w`JCW4`6CBuW z66v>IB5q7=B!Khvmff22-qK#GnE(MyKuopj7=Q-8>(h(`d7~pk^BoB}5GK{_3pM?5 zyBvO5{(th>{$Jr2-of2zRU3T&8lqIRWIO*w3M&cqf5yT8E4Tii`T~$NvgHC%k^O?| za~H`GheHY$)yMk#4|6Pg^}93g|2@5q36HSZS*ZjhZspB4|Ek~@36hjDXf;+!Oot*D z{qH)^|B=aWBtQs(>8%HxjutKLaFrI%UHBFHuK&eKwdS*jK4IQKS7kJ++i3);hdU=>@v`H8enb&T zw4dcFgVC&k062dyIP498nO6h7O91n!*}6S#E4cPA`_{ij4U%D>V66TD0Q%VK+YOMC zB-`B|c7V76by2KG+$wrZ0bxCb&4NNEg`JIJ1DpC8`K-=jeyB1_IPX{Vmi`V-)9OD( zp?`iwpTOPD$?bllqx!NbV6F;myN(_LD*c)Z<2a+p9oPZFSj9@K>$Ihz zY04tt`~=i+_`6LAjZ>o;o(kKV090TsK>1n{7`+1&x4W&@))}LrL{%NWr$qlgZy5Pp zQ7od8O?lrw@9O@X22d}6RWqMLj>+nT^-$URt~U|)NQAm{E8NUdiuXd zh0po0&%tCy^t0>LU};j#HxG<`rKF$;wjbF+Eoumk6wRoxDKIWK(3uvUV8o+vFDMpjXch0G5&~{l1qY^T0jzCs=UaU`*$;Q>>$#X#*c^B zFa{HCHy5{u#qFUaHlz096N@cUNm)zK2ha_&tPck=zQm>CkwFLa` z;_mM5Ut@J8Nq&qE#`e!DQU?Ayxs4v$C#iNik*w=Hoih?LBNzTHz2~}@_WE>o1Xxq) z0}|O1_s7MJ0*Eu@YCCd$s5jU!&*{YNax>5)eL$+ck)bW@0(F5rDvhVN%HZ@L}V z8;YwNVs0Hsr(Pp4lk=7_=kVNX2RF)!N`y%%n|;Jb|l!th21>ypjqzIPcIX^F2k+6hz0rhRF-J7sQlp;%%?lFd$8M@RyIl;UZh>d359 z8(Hj@;smu-g62zAimG>pWA}>?kIDej&%wp^&eX2dRQYJiKRBT`bU~_@I@co=^%l>x zuQrd>^Ek3;yyRZeX>?>=;ur#1P)vwey@|Mo{EUDZXG}G@l*AmJdaKUUSM*DF%hmSL zpBh&?Lsk@n{fCjh>(k9tJFZgM-*{S@9l#9MzM5|jSGYz)BY&$s_GRm4$YKsAvn2q6 z;!eQ+(O}4IN!_c6LY3CTh6--}P!_LRn{^pBy_%XqfAGnC1K{Z=7jXprcpn@a!>_F& z5zi98-t>prZ~)U0+>{OCs_qv9FrUiST5rpA5sML2wLxt*)478f8nb~ z_2m|<&}hqi0|~bBo&m@!N>^*rSlnL|fb_b#%K2<@T87mQh|DIpL02Ky)E>wMU<4&}ZJAZZQ##?=^h4?nN$q^Xv_!D{$$4RMCa; z2xGB-xf^OcnjEi?U)NO*4t4+vrc+n8Q~Y(r&U>=YMOh$dySM^ZxeOw~#NJ_H)h*xc@9? z=;NIkUSll0f`rxOfIP`JjDSn)Ze!R`&{+*nQSBI>S1NJr`+k{6jDVM?vRax_dY>X( zDB}QHs0Ugwzo-VA{ZJe=91|x%KNR3ES8o%mHXb{^b2fVPVK;i-kzkQwrH!Zlp|JL* zi~CS1rE0ePh`Ly5mlO7hW9~l3bTmd?gWZCmP@tA3<%InCHZuHRkBu~|A7*2>uS7&4N|s@i^k zi%+eTA2*WBb62&6MM1@MI(I~>p0nK1I(_4%aPrV>e{dch*et`VyXWsS_Pb2&7aibYkUhR`G9_ zZS2n{g!tB_9#KB(`EF5T33$2}DYqq~+}mfSa(gW4;81tu6!~VVZFW~H$FpTg0MsAW z(Ao)0Qr>wF;e9bW6`%oT0%&p?c@~$vqxzA3c(xzb01GNnDzblvkbSN9{>gI1bkzC! zfSJs^wy;!YAGZ0m= z({|S^S*CIG43)7^wJkoGO|A?fQ;=7hSg(_NiUcNrfkwNW+)RxNq@pQex%B6EGp|qU z+J9$DQdU~+Sk<(7%MvCTwAYzK0L$GqHNBcC z*lWAwQkyC z5(E%~mEgiQK6eIjp}3cwCifsnT}NB(*!>1xy#=G)X>4 zUM`>I%s^u`8JIpZOT86pwP#B}V`)gz7|5Uz@*3e}rdzdmJ=uHTu{AVTLruYuu1G58 zNp2TxoU1q`HsfwVOB@Rf62Gc2z6)}_t#k@M~2YkLL|B zyglX8>$Z!GzkK;TlUMu4SHQ-CH6A?GUum!uR^kdYY&sUF-eyNQM$Xnbl)izO9zU8gZ}C$ETDkK6 zOn{I)5bZOQic{P)#`uCZT(oQe+z|;j<8*)}uwQxsF7;)I}O~#n%%7r_3_i?OODj0m#@ar__ z{5Ba$vbtZ$vEV4m8c!|wjVVKc$edlgH)YNy96%H)|5wK`?GKMvy3yqzu}!U|S-Ou~ z-QmaKi!Ea*a#OUorNhi*!Ad9{lkq3T!(tx$$u9wpJ~0Y33G|ffuh%)2%T-PHE++H`aHTYF>ij_cDj9YzrY4zxbk*A(RK;nw`uj$K z>CHn%z?VOxy`}$y{MFb6%0<|0g`z6A$HXY;B+R@kvNTSjpWpNX8Veuu6F!>FVzuZN zAMU!E-WS?PzfTutog$Yd?%-V_s=HV*HSX+IFWb;jxZhzUhPs%Xjs6E zQWDKTd%`IwoKCkqMoC5LZ^Js;)D6VI@cnQ!DZqkTZ|&g&-`+KNH#l;2RAB%Wr=iAW zZeut>Zx`mR^G|GUi)u<~^NZ`}rOLw%WF{Y_@)J4j6#iB)d%it8eBYK*{BsI@S#g-` z>oTBrr=@qo$=lIn!-{=Ss@0_RSlnWU6v)$Nx`>o4NES{r+KsO-v)X2ws8pe&sZy@7 zSMfCHn8H??3D9Mj7^+W}ofDcIOvUSp*`Cuqz)<6SRh9h_MA2FoS4%yo4Y+7bls1Y4 zIRCN$Fw&t9C|Z62tIgK95VgQBY*Zt0)UnOaxymF}vN#MHWHt>yKUAHb3WxpfC5Zoe zI)s$GY_5H9_h3pvakea^`%(9>vsjqweXKZvsylS=2l4C={NUSE0u`AeZX59kOLZ9( zY>f^Se*QzTxS0yRLkLT?RX^G+HX(-uOLZ}Ag?29q5LfY-mMZ?JEl=gDk^17HpN4EQ zv|$oBtW{e$sRRRl@nOgjYPa3YhE$)ZAdeWsKtSYguXh!}Jlt8#UTVi`T3A;JeUpLk zG!6ur>&3Uq#4)pWO24-FNQiD<)oe}66tWDwL$k35W2KVi!(1=tivsUSt2CDGdqX}< zSDex5HNyXFBuW6$E`BJb54lUtnDsXN$$GZ-LMcDmYIQU>s)>XEpKQAAsI1}w{cUKn zL*&pvrO6$x!*ltl=dnS((Rw8ldDKEdFdj}S;Yk-1#qhNK`a ztr-K^k~)t8+qQ$p_8?QlL|$B4S;`y-Cz!Bg$!yfO#_ z|CRKwzpGC$aGI+|y*Ar23h!fVum4_U{c^8JQ7Tjubvjuhsc5^6p=b{#q5nV!r~z&2 zHnbB7iEd`OsLO}4bEfL8q-0j?dK$N1N`{gvIv$m}_8tGu+eAg{^2`Rj0IL2NC8RGT zYu~jrlk>W(I}83XbAVAw|A0rlC~o*uOAX7z}=j zCepDSnso^PU=w_T53;C(K4e9jll_Ttnoy>h>q8+8CAc#dKiw)4nm`|I<4N#^`Ga_^ zXI&oD$2dxg!|64TmzZM9rB?NKZ%n;p5UOHkK`#b1;*R1Y&9_K_IqV^4-D2nuTZ7ic;0Z}>Dj00~N!_i_xP|zgC5QHZ zsD6C=`>gL=q}cX5j!x$rgAC^oAE5tZFL&uBlrh(h5ZN(EZEyCct1C!xO4zg>1{pUSWCsCMtS1}=* z`Pac*J4F!pp6(WQBEt{bD&IyIB;MaB4;p1>cF!OLd-P$Wa;=A%?&>k`7Ml({lX6qN zqS^SmLg-f_&wH^DY=&}%fI*1%Pr~)}kN@3dH-a&K{_=}8G&aUe%~3e7s%6U3dz?MitqpY)f|JNcgF6= zhh9=Kv{>$;6bK{SF_QUmi~B>i-88ureXwyltX;+cj%=!hAgoQ$6{|4}&95^~JQtX) zQ9wkzJF+;fP&MV3E>^#UMHyKHHl(Ijsw|YA%w_HIdJviyXvphnd7Cz+O{b@@`&$3i z>A2DUNBs8nIyLJvQj-7*yo;RTk;XYb>f}HfONxlo`?U0t5NZ^MXuj|F8<=-Yv1T6$ zrwpA>Dk0HO-BX45y2Gl;_)s|Vd7v*WiiF(mYGXx7Xig`w=nJ!eXOz>aw1M7bVgv#7{@)!n4hZgVNm~fa{E2v--owvzN@_ zp-|JgNWQ+5%k#Ax?(>m6wQhcCEqGfdsjNKaczZfqot0WAw=h?J!z@@7vST(qbC}p) zs~_0|gLC;;>6`}+R?oCAdG=DP)s)<;{n;}V0i1TVY%SHr41%0TcS!W?A6b|Te#f$P zK7A%F`HH-ZS2y#e1v*D z_hK$m06Mqhj`m*$REK;8%>V(zjWTP*Y&N$~EF#QGh*Wl;o$lFY%RLdsmXDY@IfOnYQ6H8b}|g3Kz{@G^H*3dL^A)O3GT-TN0%eL?JcBLHC&ssuc+|{tJWR z>6X9yti_0k)bk|3NI0!$P8JUjoa;vUQgKXG8oH#!n#|h4{H` z^FFiay0#aFM~J-C(Al$xZed`Wx#fNm7Y_G(bTNsGdJPts7*vuje68OARf7&-SL`v` zMb*3kGznIf3qrofPe-~vxG4jfLN78D8bo#0ZO@k$p5t&Ti$9C$+RD5y1YYFUu}zgv zZP3x8{vga>L&CxL7ZW++{*SCvfawoyuXdi%WcK?*2>+p;+H=I5bWWqYr@#TNfc!V7 zDTn=W3ut6rT)ab5durv@M)gjW5-m}&qWJJeP6R!_U1TX#Up4@J=*xqDg$^BV5j^T$ zUzoX+Z_P_Tdx)!vg({!Fnqo6(fh4YeZeS(iZGczne?)!r#_bjtr35=6UY$@|FahZ&I zWc<1%I`#B|{^Ik^<+l{NgiNY`Jle!ydO#5#803FMZ>6SMsJVC6l8+9hg}I|&yLr~W z%0FO_t_T{v>Fm;#7_(f=8*Vu(f=oK=@2dA}t)AC~n!FG^KL}E*Hizw-d3n7#?$w69 z82bL2X=k6#rIwXHsn}=}5GN2i*7~+8hdpc{2rE!UclCTu$BPy|v+wrS1#{W;*8J}0 z=;+2a=_-$-XR66aop+ejr){M&d*d@l>L8uWgsHqoI>sbE)r4|2eJwL^5fAm5Qi-Yu z{r>IN!l#rzCs1^n{Q)%Cx4EIguD=L-?KADKOVd=mfVI<77wnWw&euLGypSZW_+k#}w|o49j3a3DY0D=XJo_ zufgDsKPkj3ntV^mYm|Q1hirnL&-#^69d`^DPhZ}nONzIe(*1y+t1`vbdRI^KB9zu6 zd$kkzh>`PZ164?qJqBL%kth zFOO`iI7ZDg081=VckJ=_ENWx))dReHo+ZrUa=u@$vDj#r26gN4#L_purujdEdTvb;ghskwd3g94`) zlz=>*He9BIuc+t8u3~|);(+%Ya)ygd4W6dVc&kDp3xH5t4NJ zVzZCj%eD#o9J?4FO~=xkDAzuC6$`!;S~%Cx!y4N*U9CJJb}4CRyw_57K3$YyelxRAl-IokfkrKv_E$dMWCnO z0ytD5md^&3F%HEQ5%biSXtwbqQ=yVnds{EHb~Tz~dLh}e9LO2Jzx%j>cp#ZF*AHp~ zNMMCWt8iXz#-8)Pc4W$9Yic z4m(pzHQNR6CR?IxkjY7Mw8imdsn!t)t`vHVUgO+ubiN8ABOd`ZF#oWO(d0IYnHJMK z2K6?Rrsm;1Ht%@$fw7_d{YH;>BBsB1{7;4yORIdMLnPp6@lY7kt}5EzEkb@@HFC3C zudIwTP~`v-P}FJN{BZAwQ$=T0gB{TLm}@vE*9wK{Sh9HxN}gh9R&3=L0Cc2Fqf}@B zF(0pT-lKq1V|JX3EHU04Vhd~4X|hjR9Lh?j_&5aM%!2v&oQ?}A*ozISvtGHJ^B*XX z8So+SMrJ^ALEmM2JD%f#4Jtd*O{9w`$Z9zR@@6zpnX1vJ4#H&uxJT)`(8cP>&e zzIos)w8(p+!jqx{|H>)+4t1umLEs(PXjh@rCYF(L>4O^qG9~4s>OI-0f4qgS9|#BC zaj6M+7SBHTSzo5M!}d<9;|ehFXFa`DnpHY{aZ*A0Sfk0zw3#|Nh+&}CJ{^r z1S!gM4&T2$&xhIZ z{TalUbpG)!m5}7QVg>D?I0j=#m5N`Jx_`XVk%OSWV`C--8HjFE<2c-LnTv;%77~EFn z-ghjW{rptnd&8H+&vTIxUf>~MA!txM(;oVDEgprxT}ASi)d*dhQoF;mr?HAFQWUc8 z0*a&3Qi|mtNd&OP{s_n}zc(3^u>PsZW`=c@UG9x~Z>QTFrS=&7p^tTsUV3rb8%rt* z))FgHw({o+f`bjn4JX}i>&S6{>aRqD7Fj9=G-y`I)Pl(sojFO-)QSZ%Uv)mQS@1KK zX?B#UT4J0vf*@}%t>|FTlh$cr-&dCZ7D zUcYM5{Ds`vw9S4qc98xoXW?bPSYiJX{eAWRffubS7N{9pU}h4E6^R~G_GxEk6dv?50&h}9idbc?z9}@%WhYnyc{klJie+JE3eNbA?)!(wcRN zL?VI8+*LFC#%GeX$-E7_OtJ9h!|s+9xPUH0)q9438w6Txjk^J9x?Hc%Yzj_=^_5=Z zYlrR8la+pm2bs+bl~r!SFd%mlEDa$F`%Gd|Y$4RN@XE z*ErHXQ%&As!(@xeDCYZ4=+>BK{~UaqXX8E!`T|C@jIvGd>U0Y3JfiB8cHdVwU1s!p z!Y{xH(f))SENbs5oGv)KcP@+`zq;SMiHf{dgrF7Nyf7|3yQ8i`TS_K<$1y7=kA7thRXYzNdURKd`E;tgO#0 z>bPMY%3uW26ou}h1-p^?1%Z~+`INx5C4Jv}0p07GuVtg{tfA`DTZh}p&27s1TJ{9S zd=n}|Cv+(P*KZ7OJNw6XUia}ZIDhEq5>*fnQ@<0shJi)GtP(vgkqWk4w!_!w*9|1E zL?_4Gm$^Qbm@c7mxDWGR1j~imajTD+S;vg7eP%a=TtOGO_Ti*bP;#~`jDNU$@gEZ zg`mrlir27iHfcv?A9Zw@KgKYOf(i|*o=>sO9K+E?kHD8FGakywtKz52r4 z(k?o6E%1?_#U$!J6l3+%xblGan)Z8@=&O*8u5N}#lANaL1#nw#GHVcfYYsS16wFcB zoXiucbciBYWI1ouI0jy?_XTi6FXc49e26`BfAd=DMwv6)J6@b4>2X$q8t2(hb6Ty_ zt+VmmcYCVtTn9${;UPYKC5K}oHM`kX#29!5Rp8O*Gue18Q0O&T#cnC;^c1qceoJ{!iNhZ^ba-4PTg?8(w_5Xk z2c&fWg!vl(~mR)HKsp zz&V0Pjf2AzB9jIbB4=>RUqVv!PVb+zZIo2)X=uIf`zqckzns0@d)+D*NjL==H=VYu zI~t#-Iwe)tq`j3pte%B z9Bz&>jt7>fu<*WNywihUYZLzbag&xM^S(QT7`!AR-Ko7p zUny`VWQ@;o{iiXJ^V(GqAJsdu!u{%~JfoFpzwJtU?EU)`B>!ZW%{)A7*8R3K-UhI9 z>Z7{a_J@xT_eMy;H6-P2pe^!)W_qPqYfn#V8Q81()7BPj#awUT6 z(Pgw}>UeTpARmEg@w$d3>r2RYGYZSO*xN&hZZ;MRJQ?g0x!9W;kz1vdvB_y z<1_ENq1bg~9SCF0=`#@%&e^Q=N)P5Z!cJR zt!`g>d0_DbAMr2Anq7a|?Sq`B8@_9*-`Ts74J>s2nF{Fgw#QINfxzFGP)2tC_+Tb@ z-KN9CA;mGD(Hnc|ZaqFjh?D_Pp);Ce+k{$RREoO1OMSBTsh_XYLth5>X)p`DD^l?j zAVJc51!+y!KDj3F4s9vp{5CD6K&EIGM&lmv&Y;VL$A_~SNPkTKJwH>jaF@13#r0mqHN-N81##t8Ra#@ngG z2kR{4=Q4+^t^WSEBZY#o$7UxUu%!1%ir2kizZU=XK##@@S*kh%ikID3h^7YIeP2w4^8U`b{XPaENf^g18wE^zrjHB+1%}0vP*Dpo#@MD2) z=F<>f4oN%OipFO}CsFi(bTNc@KQtUow?soJ^aFf4-lgi5~ZO@pr+AwDv{<6_Ph z86r~yv*4z{>qQjKYg<3z4a>S4c_(xW;|Ve3(xufV<)@$|=-Q1o^n$2!dcD_xzr7W~CK2 z+$QLFy8b@up)N?B)Wq>>$3>J*FXdd;V6{HVWU`M}U}Ra?lVgmIjmcPbuUTpFI-qay zS$5xVm_n;2Wz})g<@<3h9d_)F8OOV@Sa$lZI0Zy*De_~zml>^p7AOPv-;#chIPkRj$$)fp^f{6z@V6nS#FaGzMbj$9a_@FgZfrnH z(+aU^G>)`tlj)deFodR60kFrh6_0Ji;~I-@4AcJUF|1I$i`7w21Mm!X#DroSP>V=m z_#ATq%C6$$=;*F47d>LPW8kyuLFj|RFIZROc>8xVe@5LS_>Z$1iz~CSc7>WS5vH+c zJUa>+Gr%WR1;dI>g)1X6tHs5`#`f&Gp(|~v78`SS{qilxq>1_8ZAOxA4IFq^PWH_VqIH&0!5( z6?ko?hpC6k?tE(bLVwIri65wT8imuUtfDVm3XearvOYA}dnGr-f5e)x=&kTvw%iPR zkLA;)MZN6Lpq%2*n={9Ku3n@8+j+m|+(F7S(32XzrML4fwMD(glq&(7;yq&tAQzt_ zz>aZ99V{PES7;j@kmzxNN-_txbj>uet@&}Yf=;weJ96+{(CgC*-;F1>QDpH~vVEg( z%Fma&NiYo^rIYntupIbc&rtaF_!9|7P)C#GJOtUlhYk(VHt+q*`y$J2(feV8oi?m8 zj`h^Vigt6a#uxUYEbV%s&=tzQEx_IY75cZp8nNwz$?$V@qTn03xA_xb8ocuj3uugX zl~v4sb9UYpIr3NH3bZS~zbK9VT7zR9GyPR?I)vdZ<}` zXO|Ycyns#so0^M3+(p((vI+w_rP&weBTuIf%-lb)w4Gq0YQ?>xF+$by3XegMH3c(TVe9>2Z72M-8P2lhjwHQ5ds%{QI$mxa8BsRo z35u}i=Q+x;D6K#ZPo+B0|Jj7)p%>;1oa*TdPH?ctRHF`8vBf$m8KM4X^Evqr{Y0ZR znC854VX`s<<>XOs>2|l|u(NrXwYgUqgYp@KW~Fu_>_Mkm|U+lp7!sd{XVzP%h~MB7oxn3wAO zpG_}F3MAM02c5vKSF@)J<)XZ4&!!>Mb~hyc_CSEBPGF#|L+;;8SdN5+gb^A7a*!o& zAreCPD$x!gG&>&mdRVo@txn+1l>#?@9ch-yTw(e%u zYpd85|6BM6+*p&QhlTBf{3C9Jq|Pni~=T-ZFk{7-#Z%ThIhk` z9|_s3hN9v$_dKVZWf}w`*0<<-l;L4&S}>Z%Gp|q-C-aA?>s!_Y_uald7x?97ezobd z^U7q*;12gZt=G<#tLhxk1_LfFVX?94x3aRTHyXrD;dQMJ=`oCCfFTaO`^9aQBn{DXR&%M@FnHewnxB|BPw0CP zJi2-ajDST5@i=R{Tax8DF#?~|C7!fijX{j=da>jdHUo*QpH5pUkn7t_Mn73tSvk&^ zfEz3qSu|T+s#4UTyr3GPE&0zoVd{q&S~xdwSH3fy;_A9j&N5rCa3;Q8^bJYG@tWQq<@YQYceYknT40v1Nf6 z8lgIg&0>y@qrGx-QSoi0r`RVSIc&D5YnS*r`eF zSJu`i+~`49ZryUi-hi;?_4cwl=D|zoOdRbKAvzCSv1dch?UC-=QhDp8WCMn2V0&4a zb||Y2WqlE$*Vj+)Tjm;zLe5m1Dw({yfYjcy4?R1g3!~ZyYxKKVB)t;K8+nR;;K*Y@ z$B1F8Dw!benYM>V+f%tU0&xuDW9_tT4FIoe8J4ni?=>-q+6?R-|$n{HKGDU&nBvs5CR-GJ>12Sd!0E_ssx`qZGuDjks>oN9=R;YDS zu`s3`QD618-2_bnCV`t7#nrtjVRXJ7c9?vbls!S>&U2uj$GJ1wnN}S12n<7R40Xg? zJ+wv64G)BmVy7@#m(NcI;jVH}ldWE@jJ2#(q=oZd|GW$=TQC7$bM!Jc5=07wxec8n z9H#vfweq6>w{hKXsXC#C92s1 z%S*KqCKuF2iy6$xG~gTF6&1h)L(=kc++G6%;ZjQx>S@4cT3rZD%)B zt#2xJT4T0xUcacJpJT)slTLU#u94&5^sVy|-PSH#!!wT7PSXqB8_%$TCv?%{fa%>z zL+`AN2}V8*Xrh1`^6eEF1%M<0i%`UuYlPtAs&W+2p&vT`fszQV5Q$!}M9N^s*(=!+ z@Gfg11@4o~x7{r(({w9F zMh&xvDb21<3hP^^AMS=%)0(vz(tiMfC_|-s7^~~0CoC3CxKUzljPf=C7DX(2F$W)% z*yVNHpG?;?n&Ev8OA=xqL>FLEcgQ3i=mAT6%+h$zpo@!m*m)q{;>VjQAs4}XP^OSw zA}TDM@>=r{L~0pzk3vUA0Wo#H&WNK-L$TXJug86L40?~gPS?F8$GB(iqYjtWs*vo)d zhV#|zBlzLT^w>xd#R><4EKT>3=VL};;Drwhp5Hyw8cRCUZ>D#ww@eYrERkUaVcWIw zEO-qcas8MJt49f@*$=-6ylfHHntf~mRsGRnTAwhf{puU81W{t&`w3(Cwb~$J6aR~M zRZ3HU#fZgq=&0t$?@QetwXrsyiX5)dq{jC<WF8-|&U2ic)3M9+ApshjU2g;cg}@O?2iRtrradM+Q2lcH#_V)e;gc0OUDV8Ie+ zH4K(ihi&``jv35lVa{UeV&-B&yco+)_((>fNAVDx-09oA2oO?9=?^P`{y?c!`Ysc;#1(viDr8s5(vJ zL}9JSiRRT0Yy}*l2h?go_Eh-teKjZaRf6B@I6xlWbW5MSbwCs+CGLQj&VZKAiIh4s_$YF0ys^pmFP_^$V8YnnKQZ}T4*!d%z4h7XP$ZI znz`VQogMb#x8M9kKh)g3{3FADY%djMHajm7_^PGa*FINt zi%Re-Z6vm&)xI`+UYY&Qv&L>!LaGyFn$m zW|9l-9b0zsHxE>7E2ChNS*BO0*@!xx393v+->&N1_^b&=symlfhgI=3-3HQ~JiqZT zh)Wm=rDdZy*EXrH#uOs~q#PARY!XQBr`m8{Z$ED5X{0>ltmF&;Ju+}X9Ls=ThED-+ z3moC(q+I>3j0tEQ+XIJb8Z`4jbw|ZQR)E&}EXv#U%-ZG5S#j)r0}VRT3_Gx;CM!_* z8^|Wxe>J#Lmx8bjl8TQ@2wQ4c zoO2lI6xB`su#R0op|{ z#eez;k3S^X+ALJdApPYc6A=cX7-2P`WFZEOxXbJyGUPf|$Cp9o9GSrP9YcwePxUOYe{IBIICi!vKhc5OS zeZ^Hd96>+#o`W)XC@MOax!5i|xGrzDly*IOYGhwcl5goX`d3?FJ7KJ9bOePw_TifX zeE=2snqrLrUhgpbswL#1(&nB7&|7$gK^l6{UN~z&5C_hTqr1+7*MNcs#E_(x1z*KU z?;kdA<(!Y+G_XpyHpfA>Fy_*fpPF?I8kX(G%`i(}G&k}E zrV=JH+urF#+#`Boj`|}x?yb1Bj#V;y{U!v1`o9v6%V4d|u+$6V{|s;HU1t1G>1P8L zQoVffY%&+W1EV?l1NorYMjK_ACls+>`6LefczoB**904(&uAJVqTZ% zGZN8Q7xu}LmE#ga3VGDuxey_*W+2Hqq1g!F>|)%@dCaxR-mxzsY1+xztl6)@ zHl?pSOtW@S6=%I3h=Ht8PtkJbI+gtsm^W~0Qwz5`h{X*Vq@B>!a?oNersf!3uf9Ci z9)#l>Y20R~P-{zb^5mu~w7YORi;o7Tz|nVIjj5Q`>0Y8fk)YUUJLzwQ#*eFPv}z4t zrdv9D2{MWMVO>bf?EI_2z|_-@QzvG%yRpMRBl4c+)W{5-oFYlZs;4{l1~Tz+EQp1L zf}_-rCVerhMOgW#blQk?-CC;sN9!f|st03dWckC-JSnZ?XOJd(nXB4ojr8daH}77K zFJd0d9r(|_o6;9yM{Cb{qyP{e6XtZ5k_BNnD%KV*)aFelb9gMEkTp{!VF1E}E4VSJ zSf~cwjbGbV+aHb%=R{DS;MtO3;*V=+L)kc%;oUoH@@r%#g&;XGRmJDco`Yvz?AQ*b znPQ)_rqG2jeh-BW#ascsVJIKq*(javp9sPhB?18T0Ama8mA(Mth2AxK0I0Bg_75Kq z7uvv7tXK=Fc20&T;n{(-3)%pFtTLZakWIDm&wui$vQyC+Y4yU3P#tQ3zLJ%MJb>ZLsQ5um zEc*p8CNXR9mj*Q|hT|!t4iJH|g{6>}GW@vMzH0T$9mG;9BKovFwBS^#Z<3-Ze(ifx zz$s6I`6A3qy2J!cW5i^_{-wJX<&U?C`yNMN1IrAZ)h`vq?$x-K_Bw-sntbh|B_Wp0 zVxBoxD{InPVz=#L(r2mFnB+D3Z5*CErS;^{&54+|I`p4i-CJBk!_60BmTNv#Qdn9N z<*@;}OZhz$vM4>P?U2Gto0+ZjY8f6=<<Ug2;M8ldfB#%ivJ2Nh3y|ZW;YipdA;rP{ zdaUw9Iv;=ypa#T`?7!mC1PBQ&IfFO^-J;mwTnGmF7GJ|k*?fCL4DXvo4h|K0Tm;oA zu9@sq72WJ5*ziI+o9neqm}z0%Z~U&;0cccqUK$137ZNWN$b|tz3iM&ETm#9c#jgo` z?{eV4PJsFu*3ZjCz_YfTEr8R>t6++SNk6n=z*4F^U;-ezpx57>_5FkZtyyAh7QJ|^ zoS<1DCj2xCP4m3xT&{Bhn~p(W;IzCCE`2}q+!t^HK&HzYxaozNg17>!yR-acOsBTx z&an2ewCNKl@3AgUc#WTmT=@zF*$npe3dSf2@wmkIhjsAr&DOs`MV<}lcVFqpAv4oH>gA#}wBoT}V^l-pN9u{d#XTH9 z>x1s??A|b{Bc5}6fq^9=XOl$U#67k{r6-&`O0wO!_p!prANHu+QLa6v1>JZpCya%e z7m=8gSh9aGoIOJ|6Ev4S^&1j?|FbyARNN1LnHno+9030y?y-rCi{*cMjrjxm?1##W z!N&ouVoI=bqv7#E?$bVU4cnVKKR!}~eeMYMG{tbo?a%bno`eBQ&w}XBI+?XP+*T?J zx=){Y*{iw0scf8Q%E*O6tO|6f=-XZG*Xlb;##$hw5$>6!Se}hKj2%PAF_Q?#G8sv+ zih6@1X-!w@fCy@9@safYB^ossAKC_F4e4;lQFgn@GT*krXNR8C?ADr@hFVV7GFmN) zrYy?IcOllx0=7z@2=Z*2k;CoPdZNc+Ws9Ob5~oqAr;-qR4gTy_gYI3%Jd;PMJUKag zqp|ojZE-Vq-)o&|30V-REQL@R=ov9Cvb;L#Gtwc^P0R7R{e?oEq1mG8S`2|xjIh?6V*h|5>Ru0;!Fommu1HugE`a(K{HO@u?6MCgA(bQL31}{x z>`TXQ{>BeXHk&|$qS{uVeBWuudj1CbDX_SCUajD5nD)v-LI}?vrRy|8CinxOej-JK zx_ie3zF`IDM~H5<^;K;g+rF)mSsQdnKGDy^urlBXKr08PXiouHIXsVda&{Dw!TDln z9Ij{wAl;npoZqgjcV-LuVONu9tH@u)+VaG~E+U@!To)94LOcf(KU6mNl{f}lcc}DL z@?0qs$O;909Sp7p&;z$S$d(1RMm-==ZC0Gh8gw;FlA$$sx^v8I{NT(cMH&@{+j|q-h z)8%~>e@~ra!0jkg=GBI{$|ij!CL-`Ta7|0v6%7?x%#_DOr(nAF0%iOkB0gZi>4Fl z=X7|`Ce`Yy9b5_=^B8qQ`X@-8qH#7NTQEx(S83cQxi+m7&T?3(^S*RVWjf?}F8MA@ zf2d5da_(o2Vv*`bjJE}ZuCHtW;xW-{lPIx+5(j&X?6dq_v`n)B} z^hw-C!ph_D;W}HMvz9x@%C0tI#yZEh-gbN~u>d6p^01#`e;DtS*U!>8+PqplLL(jY z!Oqf(=DA2FLF>JG!l_KXPBG+pjr!(chuG>#g+xp0)ZXX*X0K=Am#Lu%W)fv@6<6LA z!j&SV(SN}}4xj9HcS8Nx;bcsalg(9eFYMdV;DfRBi|;8|+OXsY*$B36eVs{~wBZaK z4iqGd+i2UPpvY+TfgxnjFVwj9cuM*x-_L55&->N)^ch9Cv-&C@N!P5iJ=>HM@t`=9 zmk=LKxSbzdtR=fFB98^a{xPDEH~=c>9VFM=B%?<0y7w=omG_{fn2MOndRWfCm~LqT z@=$M~9v^eMUTH>Jj8To`kBHkldin9@g(seV04Am+ZhWv`ymJ-&0ru0lrd$3jI@Y=M z=j=tYsDn|W+~?$}Q0nQNtoz(5u}qmTNz^8(_x>wU@RIzCoY~2jdVgabM9i?wE2&zS z@Wc!p>oj0LIM;hqcYj7{E>WG6aUo{XlW{=~J1u6?8~M8F{Klp&Fz~5`k3wKT!D#h> z`H0(Fl{&{LO+^s0M0J`UHbVWj)wH5;~D@mj&yuy)DZNI>cb$n6XTls@Hyy80y z;K*7nW8Th>FwSp{@;_IirY9m`2pxa^c0r((?PK(DM7+Z#hjXk1JI;?)P9aU)S{ zlzCmqwI~|BXUFvlvcL(n)SsAP0KUi>NAG7BGb0=8qF3I1oR-soAm2Vic5$v3O7FR8 zoxGGtaB1Z7q#*CFjS1&CgH&JY`58UMfA&6WV2M34soQ+Xa*paXDC6(um3LvVI90+u zP3y|tFhHcgTdI@Gc1rQ} zCS!Q{^vRcW_y$PKX}5~Ew?qq;R)|b&B%9OdVO3_mukzbmTzt2J3+8b6rlb=``R&uR zt41BFu!Is4$hG5Yv^dbswg4|Bf$vJ`R$Y{|bZw#W$9#rW&(*;rVk3T;$+#}@-P0K) zZlUU@h&VeIC+3pj{40ublDx@@=iVi~uMRBvSKq60BnCSRO2DF^W}K5uy}HFF;j>wG zcDPz%?&&n~d*Vlm=)O9nB#1aI&{WQ4o#&5CNs`#(W#H$UdNV5eO zti#q&K%5adH8;g-fkA|%_jO}A1 zYkYw8DEh@vMQQZt8dxOvjRICZOGyj6J*d!4$xI!$sVGQd)ZJB)52k&(7bYB!1x3OS zBqXC}-k_D=e7908ZZYY28N@jHT8Gc1?W2y$ce0Dur+8V)SvdYq{$75@R74$G%svJY$AYRUJ&0D3TfYQ15HD8$b7xOw zX>#b#k7_+Hy>u79c$&|j9AeGF#nQRAG7t724pF;BA%C`k7)q3_cU8gChRf{Tf1cL1 z!p>o;uL`}8^vJE$)dXvS5tfV<@ibm2YksILn>G{l=eVBi^+JG05zZR6o;rSOYpYf? zDY?2>s_hRukbS#hnX>g0mhE&h(2#55@89MH0o_I>&w)dUXj*a2pz^~O!*3p>`8;N- z+uZDj^#W-mU53;=ob2*TOcZHASF>Jg8Q5Sh;&h3Lh}cvV2Cs)tpJPB=X{#q*26>A1DSqU9x~=dY>@ph^6Hmh2Q(P@Nb*CeRfiP@Q z=JMLheFxQDbbus*5)iU)?BagtJT;0|0hV4{bwRAmLZADmw zXeWRBUj=h>=m{W+7wQ?d@%rl*Sl{Vp%3d@X8>m8 zmf5dVeR{6=oYxu&*plociNuH|t^qXFT4GBIiiuh+X$j6iAx>L1+nbnhSO?cQkKrxf zu@$lz6`VjnYtq1zi!PB)#fGxRiMbcvK_5<|o_*E0mrm8HB}D$EM0y|)HkANRcfvYV{~eTEEBmd%t2 zhD6)gM~M6({^iz-{=csdsecFwYDVzM_P-sAU1$k|d>DPNT&ZyUxB^sDGUb>H=?oeN zk5I1%tUV`y50ZiKfO2q}1@_MdU)~N)fMw$7(W1y5)v@PEi@3JAmhk7f)Og{71J6oV zV3)IE2}e@-@tnmUgXuH;sO6yw^5FA)d`M07n8*^F=;w0B29tgtv)A~xZN@;0gyy%P z@Toc@P>Ao>(t8$AIKc(*;%BBof9sCXnS=J=K}9Z3kD0IdAPLV{F~HiQ-mc)){PJWB zghLb6QHWa6P44v*$2&V^oq|PXhw1v0)S#w(cIbH=n z8xvh74lm%XT(KREao*$}0=47(-`Btm7qCh09d(gZF_EZ&JR{P>78gw(h03d+L<n&K`*0tfl=%(!V}`Wh@H! zdcdn)saS}4afYKN^KgWWBMJc9Y<|kN1I3J9h+2D7!15JOXx*{3TIPvWQGWiIt-BdC zvzqSH{)o*dj*tkX-q$Av^iHF(u_5dvVO)nU2IEU9ul0WX&gdi#YoS^eaHT%;wA2 zTBLrNyqtWDBz&3+$NBOsh05gbfSf|RR2bR9zInwea7zlTK06`7a5NvbA~Ct$w@mT9t8Rx56#KLK9MN~&ZdjkG+N8*v+X zqB#SF3zx0LPc#(^$5*EenW57Ef1%F)=iW^JFCsJloF}z=&vNxUhp3Gu3RKLCp2sOq ziU?;eh8Vk0Zfk^Qmdq$8P0yc44|IWECkpvYh#hs+`wWS6v=g#cE`BrOBfy(1<^ z<64&is$=1yv}WHpI>&N~>H-@S^F!dd-fygd-yN&D@)RM_pk#w~u%((1%p@>Ia*dm4 zy`q+To~wA7dra7B%peI+f3IppR;ka^2pRx&MWq9@rK&_h4UWQJdz1=Q*RM2|o3f$C zd+f-#cTv843Y?`pmOEq>stNJ@#nAEEy6kFV?9tWasbx;)>yK}*RN2%}u(|*H>QDp2 zF~ev;B-nAMq$VUWyyhI7ON10ep%O?xjn#|pW-^f22uZU8n7^NE2<9Nz2K-4D}I#=@UI~xK1iBq6y)g;XbRInM!#E! zB*67{^~x4=$%IrYL%-Jy_Bp&{Gp>`@-SCdI0U} zOF^h+`l(JY3|HOgc%LaI2z5Glw>hlcvfkA$C{h#-Lm z?oLbd$fIw{2E_y&!a3)uc>9jV$@X@>9Eh|A_CI4k+)BSNKN1!i@ie_7*!yax3e`SH ztrCHH(WlWIkgJLJMSn+Okp2sQ_&;~s&~()2SNdARSq3t`T8q24xA3{wU-$P0 zU#ruPW2Cl#6x%DrYLsIz&PM>p{atYS<5I_krx-&3(=Q~%b(lEceP!*J8bd|c^FL2; z5HA{5=oV+1Neo#4aKIXo%kx2v8>OCg{A=lcrxWqaBZS<56e5z@S-s=3s6wu3AOZg9 znSp?H{%flI2?NCx-Q+IV`dZIiDd`~@O(NQ8iPG*m+cqgJ41_E5y6YM#0P z6eAR^u}Dn3mzA>P)2^J=+rig$BW(_ohRx&Ggn z&;LSx{<}w>8vL8pr&Z}{KXrMRxFA99zJVhN@dD`&3^GM2Yj0O|tgv7T@djv`&qQ{h z6ZjdH)z37MGeN7tH99dY8kY_bNmvar8A%zJGw;gIVCEpjSV93PNouLPG`|xM(O{>l zu(`A4R!Xm1(8d=l;J8T4?@%EZA5xqD?(3FCO&bXj3SdN#x-&_(o!MD_Rk~?TMpZ^C z6o>JL6u-T;@15x%SIX~{h(5bMs(GkFeUUuh2Rnsj1Lq{{d{NQ+2hmwbUa51X36m0>Q~#Ym)f=nVEW-qv8e zPkx_rffN@W+X>s?KKHXOL$OHfr4a92iE-{Y=BS+ys-KdP=+zuPBxQ1givg0+2G61+ z?d>E)e4ikQt62{)oM89~zR5-u*H~y~WVqnwBuc4K5>@M$0A6i&X3=eC7%;9Vn|!-s zPPD=Vt67Vuime&n{oBHGYyL;Z|3=IHpP!MZTA)I1I>k9@9uq$1JjFCe6Vfc%Vj>&O z9oABiU2{~!C=J9%g>s*M;LRWUz%YZ+1J*B5Xc>lBqiNczw%Aw@c&S6T(Q(Gr6CS`< zMHzX`TDl=MWvBpyIS9h7ZFGroIY z68p3uUMm>WSC3ZWHnvl+Py4)h4x;rB>;xg2ik8IJ~L*X zO#CH%o*V)6Xx!hr5}4f4I46TBW?t_^%8R+u1yT1Aq4=oQ+0rj9>EyMFe+R!-?8{WL zi3zZ5XH&w5UA3!Dhz&MAOmYwteWUAF=SS#MOQZsAcd0WZUJLnG_fe45&0niA5$L}$ zqG}{!VBr`*Ihhoqum*tnUby8Bevi5b#-&w!88wxL*@w4l+JhquQIQ|G1rLU$#*8_N zush;v8>-)RZ@Z<3NnHKE!(EkH%!E$JdCvR!^CPp-itr>OBD57t>Js4C!Y(WLI)VkpV_v z5^+}zwKuT`VXTJQ4N z^FUdRE`9t6L*Wryg6O=83G5}C(e_Me)gUB2gSu5Tx#SNqn$A5`eThR_M8$Hn88{X4 z3?Fh`l*gxbxHwp^HOJQmkA3)x(OFH&EY%0@C?K!sCo!F4vvexU3%SqB?7q?{x*z<) zu+I5PcmYalf}6#J8H<#OBlG1;$$UK&Jy9B|cj?wwtwt8^gDPEz#@ zK9#8Bt#3C08hP*nQ&t#vt5ws$^g>bKnQ`(*#cPMr>Z=U_XX&um`O)kD$0?Hjmo)S4 zN;=!yCFgHZ@Rd)Ia_}9L?PEiC_v-WBOVyrVy9uZN3?ghVQ5fpBjTc*eJ<%$Xni@Py zHkC}{T#)DtN>(-Pr}a1b{G%NNW2s%Q(PPF-a3LV@8UwL9&xDO2qmFv?6V?tr`3wAx zG)tcj5GgeR-v-=Skq@2XT8R23=58F;Uu`z^iI$l@9%LX9IOvK$AR;hEL=F{|ztto+ za{Tr-y3`HfUXm>pDfs36{Bt2QMLbw??|m9)cCVpLZIK3~ph8i-*m%}|;pw+@3yV!f zt(8CDY@jw^(=?)NYCb`wOdMkFcq@xY#&wv8q+IUZZGk9TTW zC2CUiKPB~l{nl))Iq;b^?+%$lEiyKiOz)0Tmd-r-P+EWj!v+4sndeLT)z{QaB4b;f z=Q>cXrC&aM6KKk#`@4H2zWV0G1IQ3el40TUgw>fFWeF)6Zkye^n-P(7h7CU_W$l4pgJTz_hN zHON`v7T?EWBWrX=;oKjqHn*gVFk)`t?vQHNr|N8miX%XwLaEJR5`zj!2zN;Py5vAw zIMa@RGC;~3i#8`UoT(VXmiori&dP&Pkh2zvqKhl=RJpwy+YvAY^D?QfIwuS1? z0d-Dz@)Yan<#l*W?8AMM>~Ygf)o;?#UyA$8JDc0Y^#&Ddd}X}6`&Sssy3e@u^3z^ZGzG=++x9x6AzV8 zKKqt3RtA`ruGucgry4#qpo0G75;I=yV^pIGM!Fq4uqRoR;Manqi3QtMjbYD_#7L`Q z1KE811gop?GLKggP8i{MWy@M=br%UwX>}3KCDWqLEaylYJBBc9H-Fh6ysRVFU;-N| zYe33BS>~c0d;!OSvprEY^I%7!18tjMR0m4wSG`=A*i3C{Rc09{UhL}*3+)+30tk zSzk^4tpD?*JVZ%s_ZI%UoQs@c{nzCO?|-q@3JvW^Vtl%JWq14!bq?Q(rnLF$f%jtF zQ+ypY(>$rjvO&=ztMrOC!x@!LVm(d_L7Fa7AP~@^+D8=w$KLKhfAu7tl^SkIX@+JN zcR3Aya(Lif!FG{O{JC21d7H;EnS{_<$rdQuarGd8<2!vQ1^Ik@oF^Z+J|LJAAJ=;) zNcH+ng{7s)+Nc~`n~DZg0~hAUVC;b7a&%;tQEudFxYUi zwQ_+CEFM{4t=2UrSr(~+76$878+dV4@q}BxI_wLUy@@)Pd|0W8E&pOg3)tPvBTdse zwpC%|M)ylGJpY(%`&9w@c7^6?=7$b*^`7p#E~xeBVF@VLkSpQMilp=Oge(q*f=k?5 z>Mr)7pPi6nELX?ty$8>zZoBu*JHc4bch;qA+8<4a)nuiKf6NA$$Z=e2Mb?+XiTTVeZXye6 zLcXhO@f!zgpto`^F&|K%kLfpzG^R?xCDL4V@UxV%(icgKZS)Dq=N)XAIUUy3ek<6q z9Ue@Q#gJ@c`w#CGBA6N6I;gq-4=1Zdsb8jrVK(+#cRWNcT?6-w4eNtzk#1l z8!Gdfqf%t9-n8}!P87=U=ui}OB%)^uK6`HZEWCY(u%xy1^UI%QjH~U-Ha(6sdnj%g z3K3mSauEZ~gZ9!kd1tqkFFYl+!}(sXv#kL^tLTUmH}C)k3r&PraR&+@0k5 zf^n0XS}QrK64j76?5$~XXEr1`e3?Bjn|T(|vRaSN23R%#ZaDuh2Nc3 zFTMD~*(*CIvJ@{xVb;Y-TBTg>(z)gmEipgfjXr?a#enp&#!2)!zj^ntD$Jwji zi&~j|m95pN)-LjmO81(v*IWSS>v5Q7vR`vmeU>Hd&NHp4os4A6&UUyHJhIcX!6ubBmNK*|cPvU7OTn%_|@V8GD&xiNFRTYWJt!$7iec%*~ zOkxH$A&`*r6(wqkuLCxz8@eZkByq}w-S-hV+lIVzC6i&AwdwIUret)}2Yh4+mXHdj zt5;${SQzChEjVCiOb89HS=Ea0p!kc%@`tzd1xw5nM3}^tf(sn60`ylBJjEGJULDl~ z76!K5JVY7kM${hIWp>9bidF`abzpG4Bw-<3`+#}W*NzCOnjYe!96&zu0VZfx)lA&& zGS$v&0Qed(D*EBO)XtORmgxq*Op%R_Db1f{PTBa=d@af8bi>HoZvK@3@lX~Ikt2>3airI?Wk0P9_mOveZ+;%)T& zm{H#$MLRX>%n!EAWuSgkn)awhH{p8*<9A!Kf@R%-SLJ*bbJ7v6+T#%L~W^~-8( z$Iw1+{`+|TH}J>Nv|zu7bH2}*>iHweI{Qm0O}qNZU0?jMrAioz!Lj;=nAHPiJ#%Jf zZ(m02zFIXS2weHm|M$=N&HC2k5~TA3uZztkgIZh6U}Oo3>_y81B*YPizUupu0=a?3 z(mSE#%Z^oZJj|dS92mLiAuMpgQ>sO5mv$9ej92>;Dkro|_@WcqItZh@D9L)kdgHL6 zXqKU&r9zseQS`=auWzDS%8aUf`DA9+eN#g;U$rEhYYc)hATN1v+OF)H6Mc`U4gMhICGVAmzq(j(-iQ(TK zkXTd;|H*@OBm#+UOvShVJQYk~jvSeq&NBD8FRV2!*1zqXzSGSO#h`)78E7Tdo>niG zRvsg=25i}zgLYURrZSc#LKhErhCLqhy94i6y;^ewXHZc8<9^S@c;lP?t@neOd{(`i zJi1ShPQ`+!*dC5Eo^{lVy$sRfLl@}#UDk0P`LHhDbQd#|^T*B0yl2)OPx3gb?DTiS z=K2e{h(#@1TulIN2WL^(r(}s;>|x8>hR)4*A1(bh0+v_#JHPDiqDLHx6hX2l0lz$c zPQ`dfqP+EPPgxo7)H_$zl$2h74r760@1^MF>4CH|`Fu79kVjuo|HyYmlb)R$hi#w4 zo@s^YthfB*^r7qBqQ0$lC|oA{1kS33>2VgS-3s4HD$IFn*N4r~i%c4ek9 z{Dm1QbxO0Nju|6J!|qh%{}&^M>Ydh=O#JP~>ckt^O-sPcEP-H$8`21upMaL#Nf*xo zDi&%;s^dQ;;><8+z|8*Xxt*p!&5Rt8p+z2KB(R@t#y(>#XW zp=rnS-LJ0VoO@ceJyQ7%wmFjpg_;=1NjHED!wDJHK7D9eqaYUfP4!v{?^)%{RLl3l zRUP*YmkqwL<~BoH<6;sgqqU^&G*WbBByl?3h(o6E+r$O4>C?4IXkWB-%-*nfKM`Nmf(W zm`w;Q#@gZ$B_cRA_aV!9wMRf=`DWA0%eg6VhWuGu?D^l=Kl`2H>t7*do*#cl-?TDr zIL!V2t|9&HC+R6x|2@(=;LxYxBL&a6sTt4d>c%M%`&j|6`x*x_ktn95-XU@AHv(i1?{`QMl>#wBPPNvWZWo+Bq+Z)49 zD8w59aKjO%9Bv1-=lqE~mTK*O?YL4~@Ja3s6*f{E3LNn9JS81^BS@IOJC5*el zJPN{+`+GNMsiTvH=+XHYOSfjVmZ;?JphPm8>GHj2^I8DqtoKFgoa$T%_5lSVS)uwj z9kEwO*5Y9r3~BONbZ(?xj=olSRzM98%2i&QY5;gk z2Sqs6l}qHJ!%~Mat^ztN8!)RpbozR#_?Os7p(W(AeHb)9Ds*)2`#zKH&Z_#Y zPn*N@qzftL`;&`SBoHf4$W`rOQK!=ksqeoW6BnS|(C}X2c+Rd<1K%Q@KJ*iENv7U_ z=^R4&*}tA8QdM$k*YgeQsR4M?W&!-^Z|kB4 zVE*Q7S<2eD+zn*81BJp^YjK?Run%T()Kz7_s;z7Eg{Nv(+Yi3Nc%6w6_@e(|CUf3>a2MXX|67M}Q{^E;IY$&6y+}-}!3ns4e z#>QEbMl0&yKT*hTTDE^FO+8F@D^YetWKL!;u>29l8C>gpf6Xt>be-Q5;amoC3!=m4 z{-UI5BY_AGo?B?N?hS8u+>5{Cyf7xan5rCIA`Vm-bt)&o*)vU?&WUeTIBAQ?(KAQt zUOqlaQ`cBVHtDbZ{7LJ`B6!hl^#LrDZ1P)~U*a+l z$3Aj{xt?i!A?XLT2b$j%2PrwOses0T2;`F#83g&BYh4AXH{_g-v9>mL!je)Bs&k{& zw)34aoK8q{=w74NT=R~ueA8LqGSa);VAVtwKU2n^RgY-cwiwSnj7`e?OdZT&Exf#@ zrsFU6W(6F$kOEmZ9}wo~Q3kH6@(Z(ke6wO8xtB)crt`94ptQWKH^@+DV%^PPfCeCy z9Yr%k`Thdq7R+%nn7(&T{f@-tqrlHyl?0CrlzzPQ`NBrpM-U1Yj$)9EB!m!-pG%ZH zSa@L#?_ahX;8IZx_L(!IQ4QPud9e|mEBj-iwIlG_BCk9I(b9l#sORNp^EmiIBvY1VV%Pvjv2s6|9|o}GwpTKX z>rr;A-S}U&Z{^>^isgO{5=sfhSTGLk@jknYFNk=&3;e7jzKW7X*lc-o-VoM?o2~Wo zZu;5USh5Gj2YHfb9 zDnujrDduF?yQj3Zg8se9jpypuiStu;LfN0Gf5szz$0EdiM>e0YwYkpDHg*Qz9p-rj zYP(!Cdb&io;Mx`(UF2>39!r+JF}p29?ldzOr}kq_{T?KIIbXI!eg5SbrBsKStJ|`w zqc-PBOe4oE0{y>z#o<^-uM6I9-`c(h_YfIj$GLESg9;jfq9vfrbjoyU{C52$QRb;4 z$Wclj<7MuMl@D$z`d)Y=@jf$kmeM2h}NaE$ntfW@IKx(RZswpGmKT zIlVJ`AWdgUN-eSV)0@jo3!(L)ruTLwZFJ*!jIPv?s%8T;P+^Ql%@!qp23_L_zRx`C z^3h$X;kM~wbsip%-ce;~TUVm^c26?Yw0CG$HJ11-xndfnRZ?`8c0M zi|$ITfA1uCUI`eX6L4xJwJyyDjZ?Gq?}_<*QpVa}T9zJ*XHAj*gp86saD@H(!MNqY zS>V%>M1dCcMPpo2DCpBS&W0D4VBX+Ts0P{?vg+zjcL4jqF4g(VW+Kjf@1b{;f0~j6 zfZ4rnvDB;;-+=mdm$RZ25Ae}QHi{FzT29@3mnxJLhg2u1-Wywde&pBRQNfoVA^sWL z-?X0HzeTB&{keJe_xjna0)YqZKu6QamZ;-LQ>X}5t@Y2x+y1SWzlIs59;OK+ehr5k zFE+UgSyx+tiIA0Ur|$_%Ku_MEh`Nh+w)V`%_;7amO{jHV&; zV!>Yg$>oW@N*^k{Z-_JaQ46jR4RC*m332a330LHQRultR!O*l;Uh|2r55CzKW?YuEAm28B-pTTMhhIDEN zh4E)*RL$nON0OL`NRxO9uDx>`=~wLr<$woW4*0X(VUp5ty9U0G7O z07KIsVqvyS`=AJ4l6nffosw7vxAV}ghox?ETQn{7Aoh7_rqiv*)2)+i`{ZD) zjlm@G&7d-S;J0PlBK*x+AV(r?8%&Ah_)WFNjf0msYzRG1#923G)V}U^inmE3xP^Kw zw^A_X7A4%$g8D1>l9@N$oB9%i$**?=BkrbS&@$eW*b-et^IXUSnGaBcI*ZXSzSbpQ zTeGsE+oR`E%j}^B@bfW*)Z4{koU4_JM?olpJGu1FI3fTHoINZ!B)5x@)C^Wc=v!OkEUe8d1Bp$ot< zFYat>v+y)(a7+j=WcBPEsjUF%8TKg$JV6A&C@<#Y9t`N(qT;N%IC)@DSqu{38_4R( zB45~yuVG((T!Vpke>;aPt~;%`3K4&$@P}iIEYyzjc}dqUUh<{}FTwkIoeDK~jC#Ola@(~+#;YYeIXe-&V zeYVqdM}Olv!;{cRpM5_%O!)MKf~vYkh}g?LZgc=PPWQecz#Z{3_QgrP-&}{Q;Zg5h z?9=uthEPKrHRn~z#$Ua}rMoS2){hiD`H@e`>u;}fI15`Uv@+MetTwqGnMZWWdHRBh z9=@M<{@tkggydSEkG|pX@_x!YAR@+-oF|3UcibUBL!bW07?`3GD2gzh9Qx=arVlPp zj%TO!g{QwKNo3%lhfA1c97p|mYFmU66hZe&NW6?0^nlW|yenE94fyLcd>hZ(=$&w{ z7k+Hi*nr|q+uUAn1DX@=kDj8skC&5NT}}6SqtdnI&Fi%Hjdm}IbB6FnO+eO>m9e7p)Z~daSk8J-FBaC z_0eB60=_vt?=}M08=e(+N2@uo|Y(nAm9 zBHx-cJr4dXjEXdDa@Bko7I|eh03n)-QMPp5Vtl;73Povod7TDBQD#brH7pdF)(gyH|$6cp|3Pa+E9fzpT*=nhVv0)C$#CTxM| z|10jT!s6_fZDAm|1b1)To#5^+jk`M`xH|+17HHhv-3boCX@Xm@Ai;w>{GGk`+2^cp zt^2=k`l|bx@0?Zhtr}HhRJ!{z+8*(Xuji&UIM)jMc3qlGSUoRAI3LKdHhW3HNGwV? zr6;9g8uO__l^!T5|3$+1)+-v$jag73w-OdL`eDRNu8%$@vow1yWtTm2Il%k;%a{u0 zRw|}jDx)WVd4Gno_q}d1YgUvfLw<}h6L0+2mt3Jtds(DX%$u2d5pU?Jv12nSQ#MYG zo8P`1WKGkV*8Dz)E=Rd=Eo=Y`LTv-9kl!SeIB?7s=ExS&*7qt+<5@UjgeT3km^zUJ2@r8^5cB{NK!d=0`&Z-8iC`@*V_+%_tCPgN4(A~ zalg)+%fXOhag#&umB)mY+wJ9-w3F||ftGErw+ZGb;3j!NWiod+^*jHOhlAy$5wcS} z{11kAMi-AFPWDJjnS5g+{$rm{ZA70k4CWilFDrtupqT5&#sEb7$g<>6#XaWGuIQD? z$5$}p09^DEZ%pYuEbaHWQ}#3R-Q~2#K`%EdxEG0%?++U;m=n!1)7_Z#a>=-ksVAoZ zD;Sz*k63Q_sFYdV!DUBM#L;%BYtlk}X!5}lP^ebUa^^zsF#PYjHcuM}R-^5yp+aH* zK<5vp6l@qE$t5uai3-tuvl$S9k4~iE+pB;LH>5%jfKs|C^+Xwc98(S4;YDCPMj1Z? z-$4|}c60eXZnWW*XagzhaY=ZUDEqGq-u%uw%FJkir%2768&Ks`b0MJ;Vpouwo;{>0 zaO0by#!I0qcmxq~Hm^aI z3PX>Lb3R|RQvOY^uH3?NZ7|HYEIubjqjq~b5>4|TkhGx;lW%Ht)Y}|?IPWD>0v14=z$}kD-kPvN+(~d5FY+@o- zc#(^o`UR5JbJ-Kzvc~0Og+cO=IFKxRduxRYo*;~4ev80122GS)xhR#G{#} zXC1*}zdOcbUazV{-~HfP8bFMUtUiWHvT_X+ZG@C%Zu&H!DSY1vu{ZJEJ#5_d*bIg3 zX2I%?SkO0Ct33bgw{8%DalB1gRyy#3nTF3=MuajPaE7NSZ)EPnwC{#uScN8DG7x1t zuM*lP-OhkQM!4)rBJrem4?@eL zXqevZpitBWkNcVWce8jb#3X}8s~Xo(?|GKF z4|ZXewr(HCMC67v`bSm(`S)ODrY$9ipxj`rrDVU1#j};N6L~9yw^6N28&Br=p6QP3 zidUN|QK)!En<YA(yCQ%;3`fHYeU; zrUN7*FsS?9D|5Rgi;V26m>{BRf}yYJGv6Fh z5i-34Ar*qiu9~`^-$PQ5uh3bLEK7P`2AaTY>La8yV(Gt~{hf?zFf|H$E@~JZerCXb zVmI4~4{M*;GQt3v5RlPqsK*}~w!xRw_QTqv13Wi*+#g|=86U(R3n(t`ItbtyvSD*2HjMd@UtUE z9V@d|>(qa;4BIave<{D3jnT$9-F^VV47|=CUpe~yHxXbCf^DtdC;rjErABQG3rB_$ zoCifi-7tMVccg*t(}#-~nX?sG13*YKAI*nNp`ev0IU=kYY|KLVbomdhVk)praj>no7|$e_E6TZ}t%UVN7mfyj_Iw^^u4%p4LD8jM!!f{5Q2n#zyL1E%P2Trb8ztM_5A zT|jYiEK{o+`ZOVijpEnLyKFivj@+w$aqMKauKCcMChh$(x_8jzc?aJ)42owggmx81 zx5~N%stMo7{fB|rrPu_WC=~UKROL5vWoli`bhSPNC``erTwPv&5-1>~05kQey0N;S zOp{Fv_Q`Mc-Et;mLRZ}|bYa8cDMLT!?9vgBhN{n#z=)rO33P*^Dzn4ue#CStlRFkz<0+0; zC^@gV#!e{rxKG07@0I`=3k94S4(FJg26bZ@a3tl8WiIRyw-gwq-W8A!oHlMw$iV^M zS$$h}W~eCkl$mSw9HOpb8rRi_Voz~w{@%eh$`E&1rL4~4Xo0Mg^kurKkU+MtOhzxH za{&)z&xDIZ1WRirn=U^lqrQbIri0hD#GuUy$Qo^=tx6v+Y2UE&8>$I83Wo4n0YVf;N@%`lP0~oNl%pKyX?T)II^y=?x(`5 z9D90Y?~4cglOt6lt2zes*htl&Yu7A3AK9nnG7;y`BtE(cHHK6+h$fFG_)fP>O^(wJ zZo4(yRysz3C62^Fwojz4`2k0js&@TC_frXEOgD7hu#D7wg?Q+B?K?Km`Wf|QHk9ZX zq0yB}<(q?ss8YkVmx1veUiB)nYAweoI6cpo6Vs`Q^?1-#nnpn;z0ef?jN+7H1i{Ue z94Cy&mg?yDu-9-%?4kA)t>))}cU?d2PIeJjUl-~E7ghw}eL4~j#OUKfppLd)Zvw9T zq0V3AL(U_Pz6Bef?72*T7WzQlj|1S!MM9#*t)IMtuu0j$9i75Z!j?!BQYF|=Lb~o^ z9}}S>A#&rc(~hQ}K4rt2p0HV{@V}T1hCM>%mjmAck|N*51-klg=UqLb1%Z%NotF9{;`7_U8a3}YoN%&gBTsF zk4TNdEy2x=h3}lnfB`Q~o7^9L%9rclI`u2#?rd{aB}9{>Ro>83>*z^#k7!!N@9jAE9wr;D~DMB`Qt;pd6>uGNfFUqqD2+Nrw>*F)tZqw`R;b zYbY8_5?-bWj29QsTAK$p?LV_jr%fs)#%DGW-~yoqI$h$9E7RCN@YoXqHMNZBIkCLdGoX!rMitlwHfoTK3E_wA>3m@+n2|O6TTrSm3E*PHc%bxXu zd+w6o+`1(Ry$hvr=G8lMpTT?g5pU^mt73zyJXG z!JHN0$75Fp{JptFc{Yy`CT7A8ZW)l_=K2ExrvjCPYNN4VMR}eo7*R0sr>yK#Rj(4f zd>I_&+`^929OD6MOA4Qif==Q@mK*@Hw-Ef4V_2qG7evuWx>;G`5M{`i13b{$-1mhk zJC9vEm73xCxEHX&`0ZK9iwv4B!t0f%y@GVq*T?4%nFA)fy7RQY4V)q!ER$;c7tD2y z-=+H@dOgj^09TihULD84UONj%MaP$c`K2r9x z>y4JA3?c7b=EUkMzGKd!cV4(mR0iE?+^IGA8}gJSdTLv%Z-1YAjMaa`u*3-+Co#!k zjEYG5$!jCLxS3r)HZYw+LDpKy0;-5JJS)iPMn_IsnT+>u zAxeE(_8o~gh^UMn|6_J6@`8Fq9z!4A2;X6JXg*!9MfEeKkL0z`XyKqZX+IUe`h7+0 zeS17l3s2w49%?^dalI$AVYBBn`b3Mp-v=qY0Yr5z#zXhJTKCk(jetbLk!2#Apa9Lf zF!Ei*YT10?(GJ23sE&-!edc<90(03J@pqd2$}ix{pSIL}E?izJ9tBFK##npjqd^E{ zVL)pbbgUl)Ug9|}@8F!G8-J}l6W23b0MXqj*~g5|85jYKG{(8>N=rIeF^3WYPAop& zq%8U8I@W7YNIzS`f$Xc)(r+xaixs7GcnFYFlZeHCF=wV3HVGMD!L%L5uI5=EFR~@+ zEpR>81i9-5J>Q7no6Q|pe?h?H!8F%W`guen!5RUqi>^I{taTERk70eXZd%JPc3?;# z6J>D&Ko4hNHGrPp(bEl-Hf0iiSqF(o444y@8MkG=j=|tP$wsTr)Qd7E>$20q=80eZoL^&0%JO`!Ko1&jj=sU! z>6pCA5|&()zgOOzZ@&?5F8&Tuw5fMKQ^FmG5e`;hL$mTY-9*ysN-Pa#tv_dGw9E~d z)b#n$t>n|~U+Qfbi+xbj`4*^1(RiWvRTejOP&k#=q$%;elykb-5Eg0NHC`dNw!DTD zvEXNc7^@cifv6oirc>k)UZdhHYc+0c=2GOjXmvyb`ZNuur+skpc&7XlE;dk}p|*zv zTDA|<1=g7?q$NYABD|5IGFD|lVaWh8Sg_f0L6fmUuJ|O_%T-=F*+~tWN+AbmUb>Gh ztzAM92PZ7hm8WniZM&DSKe1b9>>p)d7_v(Xe7=yvK|5q%VHxQWtn zd!OT9cTN@XvErA8rO~Fz(b^nyq1;IgKu6(p{kN1^lDJ8HpSF0P{5GCXNwKr|={@#k z>A+oh!nQ0<@pkQy{nDn4RCtY!Z$+c{K~ZB$DW8j9T!|L`JK;vWWLyYBVeX?7X<><1A41P@Za9&I+Q z;v>WGmAc1xCoLF@GYlLQ%s=L-Cs(m!+RB&$=YGdp_1>mt^u)}}45kIs5Ru;YVvq_2 zo@0c#DU3$K5yz0Kuu2#QuZ*BFoFEMUkbl{9jp941FaEumk%`KuWxytKn^jg2SZL5` zCDCLEazMPKFp!~0?)HSZE11z=X+BMrdUsDT_DO1YPPL~%)wUUQNSV8X2jtC|0Q`_* z54h%=lrL+N1gKx}g=D5X&IG_yrkY92S9IXgW7&#zWid&a0;3?EQ2|2JrU~tC($|O$Di`*ZU z>l*jLnsI0MnD}l2nA2_Q58)H&-k&M+XL27p9+b^W)+T3SmTlveZ{-*?K7OeA#Ir^Geh1HMl8zR8vu-RXT35#gVJ6>jmWN<>fsetDd5~JzTZA zfzP;yw(1fdW#jt<@u8=5gx^l`PDKMAMLQWkg{A3*t9|To{t|qW2>>^ijgxg?DmH!6 zSu$`M;&EQ$$A!9rbbyz1$H*p-bz7zWSZfLDji;)|#mujb+q@2N@`Vk&xXN2IO3Q8O z=XCo-sz5*!;O055{aFo+=rRVc?BISYmM*tEf3n$NGj&T|?m_k`BPMTN0tS`nh;N{A)i9D<=-CEBZ3?tL%p5 zqi7fns+yEA6zNE#UWV)>>)}1~-`9#M6yC~RkhgwzQ)9y*MyzhkJ zx+j7IPPisy6o>=W z8(-7#rmPz}IJwfNBu?sM78SxbE!AMESR5vPj78aJiXj^;+ZxcPC~%^I)R0xG-1!(m zKy8h3+tr2gfC=+8^Mo4O)|E;y&PMA;|K3E%_VNAqE<(QgN+lps0;xYgkc3I&9_m)c z1Nn5dg(+@W8=Sb4CVEq$TJn>BwNR-?8lo@ZfWbCkRA~jHK@rm6d#Bd18hSN3jV9(o zUl4Swn)>aWocZbZQ_W+Ss8|n0*fQN|pK+C?I?@Q(fE6%?JzR^_5Q|u#XLsDP&`LJo zU{0LTV+(pEZC&Td_C$&-j$WiRg}$AG7mi-Zxu$ZB7po*$Ql&_fXR9ci(@b6KUE}Fi0smu#|GSNA7=Lq~KhiqLE6J)N zc0`36y+OS-h3)b@{<}%Cv#ewRwKSLaId3ndPv}FIX?_*zeKIS&qSl0tpa|Fq)E%y_ ztEgdW-`hJ)Yz~E*A-25w_1-tH#f!72YTpyMs+>++QUotOyZlY-Jf9zhcO(s{;HHL& zMUrmI;ae4l-n%HPw0d*bjrHedj~{FVx&9I3QCijMx*~p^ESpF^6A_cYL2_kSy z{-pJGgl!@P+Wl7){XfApE8i}bcm2QSVSAYkP-gKZ#ZYo4cMIwnwxlz-3HRS)SA$pv zEHbf^za{PZY-8c+Ovmn&!fI$)%qZDHYcJF0RqYznTshTPIcB2GOAOf7cSNGD9B9`k zORT0m5>itK+1Fy+uuoKz2dY}**~}dig>wLAXql7BW27=qSq#iXk&N_y zXQhicsXWMGM7!b5)D@rsBlya%u6vM*qw)T?dVQ- ze-{hiFMn>m`%t{u{jV8JQ(S~oKguR;LFCl7;AInWZ^L!NAlu&~ z61+A4K-zo;k~SEQWLVT%?i#6tZI{EH#1fh#+6YNs+I9F@v7_Vrz`%vm&n@BpMoG45 z(J?nk$BJ>p$>oH$|JszRb>t>eFOLs5l|isJ3nu;e<**Vh-59{tpx996VAdD z8ufjWsP(bF=aPiM->I>0KjX9imJwc#?-gkSxr564sA3%PZVV_*V~2{afBMYsjlTa16EIirHC{Z9ZGf3!@&Ks z&v80Yi#b!n{s~D%@oVn@>7z+M+wuQ%Sq4+Ne8&8<8Gb&4lD~X)_w7LVaP5)Bkm-U$ zjMP!$GSdeU6PgiO*-pJb5$6zBO3*RIk0s#|&w`ZaJ3=z%Nopb`Hld}qs%7Qr7TE-j zYij=L!a!(5_!xb4seu1X>Xq=Kd5;T?PUCE}4YPGnqZTislO9R?#3OwzTj8_E7n4Rj zSaz|DD$r+UvA8+If_QME8WEfLZwhOfPMg^Am_}#`&CeTn+(#x|L^xsxsrM$gyAS2x}WUej~SXGR(OvoBe;K47T|;~gk!+#a!f2# zYN_wL!p=5(CYvj0!8UWze5}kzgom$bcr!+%=?-TZP1R7z;89W|wy0#BM{%&WE?4H=Y_0{kMZa9s!<>r>}wC%>zYk3U&DA>2oGCWH&S1xu4A%3APZbLtgyQe~mZx!zp=hXK1v^}cYt;GTx5N?TKI z(haOv?}l-y1)yoM@9;x6P+C^f5SJG6Sv+b<^|&cq6jypT-?wUYJ8W?it%oBAR7$sl z-_c|C!jzHBWI#3^P_nG*hNYB#pyDnYt1i?v$Kd?fJ{pMFYbUl27&UpyHJeD4+Fz#O z3Y(hX(zYV!X#MdxAL?;((#DKj1mnh79+RPD+R{N&HOZ>?G1zsidjFDSiB!xkqMg=e zT9;cug&Y8zdR0pO$oTC=@*l=z+C>2Y9VWibmg_l%SJRi%e2)0K(gD@N6sa4LSiOT> zz*z2w6k{ksWRtwo6ZP;7V_=w_yppI@4bz%NYj&mA(z)MWldJWM zK-=T>1h3eOB;G>J-Nw&s{4P4AV&oV*t>aeL9aKh$8xOfW%#bWPK%c6@k*qTo25c%g{-vP%Lf79d5>Btcb+bb-Y z^$_Se=i`Iw<_ZIzCK2cDa=T2a$h~!l-eYr>*_wWOZKHF1Jt;l~V-ZObs5rG)2b}kO zsQH~3FrSJyx4QtcQPsViQOEgFT0BWRQB6~t@882TbV!(Hu*dUxot;-s zmRt+{oAfdlw)HO@oFa@2=ApU}bKTFVk+F&uEgGIPYTEMFXlw6Jc?NR{z3#A21{}%p zrtY!ag673`DU3K;(36fBS5b<;)EYPFzDFBeM=Xw(W6sH}mnvXO#$I%LgN|)*T9ju( z>zd%bv@Jt(dpTk64yKPl+ecLFV$-K!qVs#Nl?P2Z=|1a0Uv)f06I+pED~{X*yV*Be zt*1^$WyWJDx+FOjej84dDZZ_!CJ;A6WU-ONryc6DOl;!42N z(B_!BV#`Bx1ceRXL_k8OSi}VLf6f3+GT%Lm~ZK zz+A%4t)*_6GA|!$(@NS!JC;`&%?@mXl91&shwHk{pXVrAQpP~Dtc&YJh{007m%Ee7 zn7B3bAxQ%-s6}D{}9V(6HL=G+%N?L z!j79rRYUn9L5a$*eOQ3x7R69RO+c$mwYN+W3Y4G-;|HosxMco(7e39t*8Y*rd>Bv(=IGz$wPFhiG-N7KAU|U}vb%&)I7|DSe zo|$7dOJXOnU71VnJQTJheV;gU=wl{PXuDtV3b(ZUWXrwjbzcYUaJoC1B><(gfc*q?}ZP5w)4o4U?tw`vEm09E#c_Pb$PkDm}@Ke@Mmn8du zrO7i$U&7r|h+O2;i9<)nlIVfA$5jX!vU*og-4Y^USNNFvpzNIeP*;wx=f zQ)ogund=>HqnEm3(w&ax13QOy^L(??NIIAmDD`@b{hBT4(2I%;PS8h2Gf0?)QX=An zvEIfGftY}1g&RprP=!w{ZCMT9?8l6sUG;@{HT~QtR6tI|x<3Y6+)t^zL1+f@P$NSyT0B!;jI|lD`A(NCP_9^@YdMak{zkZ-Y zbV(u_S<(X3`9>*u;SXPeQcxo)Nuw|t#8Ss6u&F(#4Gw-kq(t%XiwqPJ+2^7QmRFZf zSs@a9jOg~0d;I1u9iXU2u{Ub@u>&iHc9*p(2FadbRsfkM?=roTN+CAD_~iW0!R|0ANl(Fu^}4S4mDTd89wFEGLcVm0-$IV-~a*b3+(v+1{q3qB}a7 zk7zUW0%u6KMWoz`N=zTmU-G3;L!0$S?EMk{#9U6gID?~#xyfGY|AAiqZ_W?B z22X%-j&Z%v$N_w34R3S+ww0=UE=1<8Pce%n0ZT+u-9L914BvuIxc?kFUpSVWM(LjU z5y&#c#}%JA#|*;EB*!7F#?UA_Sdixeah6@RQiR7LbA|gG0>2(I=R_tBido`h{XVI} zP9@F~hbtK+D@0w>{Es;X*W7yKZ8 z=H}zNC^0`1t`HYRk%j6OHXL|{dzmeH`i1}Qd3ZuwSB#(9P&R&!Pc|x^?Vqnm_qB-*^)x4#hX({ z4%FvrFXvu0$ zUNyl3!7g3vV|g308R_YA^^~G*(6s7ga>8$`UlOjf1{!?$}E`clV7G_b2k(kf&8 zg}YfP`9Kjw8Js6(6!kQiRwS)RO;PG177?}Z)E%Z(X$LH8n^|WCJwrl!8X!FMSNyG- z73qPB|GYoR-M`UJQhFbzKAtq?y;Q$I$bPL5df}4xZ&Lq#a1bqXseJ@!6;~^MLmaMR zH>TVPNebnrmYv_`YI43s`!`04z~|1=6~TyF%VP{t9Nwwvk|RP(mM~m5%n~*P!jw#e z7D)YdWoNx6+Q&iE<3}ofeNg3H;(>jjI-^y?Y;ZoS0fHHuVW@?6&5ZaAs(g_;0toAX z+aYS8%w6>l2o5M!NBh;=K3llJ*tM26fi7j?KoF@AlVR^0A)y$Wng8oi@qM?F5Hya~Z^~){=hN}oS!DH|5_Lme9r*pG-_U#i zlg(AA?g0G?d(XY1Yt@E2ZuiK8w==KaCWzDnoA3w-;il|2p%R0Uz_O#g{kTewkR3#2 ziER>-4$zZIHY=tfSkj``9R` zJ3C&U4gAA5RKz-9kLk@Z?`i#D!<(&6v~;8em(>X}x47=KAA!7C1;(PbW$~6O_LME? zCS3Ry{d`105$^l3VuC}7U*O9xenxgHjORI;1C&WD${C6Y*Ln2;==kA9V0yb}>@hOhR=UdkscPHC{o@!Nc zAuk@!P28RypmD$o8|xL9&oBTo*m!)L%$IH;$uaSj_95Bxh(jHh=-`d z(bmF2$D>6>!;SAt>DkNC!NbTuPhSu%tQ{)Z)LCx0opXg`?RWteZWA>_B!*FBTh;WLQ5 z_?!oK@BPp7zz;O}$DrZAoW0VSEC^ePi6-nX5#tm=^j`e2W4^D2M?}QgPJvZ>&c{Ttv2UrLC2&8BA>uHtp4-@ zVG27I6%`%!ES;O3^1p8U<7MkjN3|6OG}u}IX?WhA=&f~m)kUY*m{g@K^+Gfe78e$* z0H4#?Oj;qW5ub0968wkY;o&!2+UI5Um`ci{b-VAn&s$UjFOwOz{Rcz*3rD1~r4(>I zsbI={`XT+U`)HrXcODN_gEEaD2}2$5l%JAbZXiNXb{@mT?*m`3lUn{#sc0qm`S~A8 zCAquqeUiHV7u z;H>wgf-S)~6`iY;%nL5cA@lrJSI<1+tO;1{je+tthMHja_rj{MYD-;@*^iGWCyteB z3gSXRul@>%CGgW=aGj{rV~1mTV|s@?T4Am`bE&ACl-X>5LSm zPL4{hAQy~@yi-1)P=juvfnK6bhOth>nB0L}1NY|s@#^qXl1W8Ir&j>;jcRmtXI>Rj zMbs&THI=>5hFFWETu}lRee+i+^r&s2DY0lo*^^R!N@3*w@|>4TC#~uU zJiWtUC_a|_WGTL2xSa%1Le$o*NJUQk;p3GQ+S%&v{Il!Ki>+Aoj&ZiVH0tPO6)Vv9 zY*nZvMG8~1dr4#w;toFl(H#zp&UbZ|&Zqw2DSyj`#1wzNkB$(NoS=U+fwd$B{REy{ zJ)9gK$}bEPQv$L|aXK9;B7AFs!WEGpG^sunIXwLO#|;!r_2=$yA?e8jM*iwaB9^H#42abZA^~Ac!kie6*ZMW?3HyBJ(YwKD}11G#^XxI z;>uawyp&FsklQTJe~F$U%`se%T=OH>mcfmy0ks`(r2;rKmRZ_%kp`oFb+|ke8$C^9? zYJOlmXo3s(v83PMbO^j_0rFGg&i=_tmyxr2Mu{cS8N4L U!mh~1LP0+A(kfE55~iX5A2>YpwEzGB literal 0 HcmV?d00001 diff --git a/demucs/__init__.py b/demucs/demucs/__init__.py similarity index 100% rename from demucs/__init__.py rename to demucs/demucs/__init__.py diff --git a/demucs/__main__.py b/demucs/demucs/__main__.py similarity index 100% rename from demucs/__main__.py rename to demucs/demucs/__main__.py diff --git a/demucs/api.py b/demucs/demucs/api.py similarity index 100% rename from demucs/api.py rename to demucs/demucs/api.py diff --git a/demucs/apply.py b/demucs/demucs/apply.py similarity index 100% rename from demucs/apply.py rename to demucs/demucs/apply.py diff --git a/demucs/audio.py b/demucs/demucs/audio.py similarity index 100% rename from demucs/audio.py rename to demucs/demucs/audio.py diff --git a/demucs/audio_legacy.py b/demucs/demucs/audio_legacy.py similarity index 100% rename from demucs/audio_legacy.py rename to demucs/demucs/audio_legacy.py diff --git a/demucs/augment.py b/demucs/demucs/augment.py similarity index 100% rename from demucs/augment.py rename to demucs/demucs/augment.py diff --git a/demucs/demucs.py b/demucs/demucs/demucs.py similarity index 100% rename from demucs/demucs.py rename to demucs/demucs/demucs.py diff --git a/demucs/distrib.py b/demucs/demucs/distrib.py similarity index 100% rename from demucs/distrib.py rename to demucs/demucs/distrib.py diff --git a/demucs/ema.py b/demucs/demucs/ema.py similarity index 100% rename from demucs/ema.py rename to demucs/demucs/ema.py diff --git a/demucs/evaluate.py b/demucs/demucs/evaluate.py similarity index 100% rename from demucs/evaluate.py rename to demucs/demucs/evaluate.py diff --git a/demucs/grids/__init__.py b/demucs/demucs/grids/__init__.py similarity index 100% rename from demucs/grids/__init__.py rename to demucs/demucs/grids/__init__.py diff --git a/demucs/grids/_explorers.py b/demucs/demucs/grids/_explorers.py similarity index 100% rename from demucs/grids/_explorers.py rename to demucs/demucs/grids/_explorers.py diff --git a/demucs/grids/mdx.py b/demucs/demucs/grids/mdx.py similarity index 100% rename from demucs/grids/mdx.py rename to demucs/demucs/grids/mdx.py diff --git a/demucs/grids/mdx_extra.py b/demucs/demucs/grids/mdx_extra.py similarity index 100% rename from demucs/grids/mdx_extra.py rename to demucs/demucs/grids/mdx_extra.py diff --git a/demucs/grids/mdx_refine.py b/demucs/demucs/grids/mdx_refine.py similarity index 100% rename from demucs/grids/mdx_refine.py rename to demucs/demucs/grids/mdx_refine.py diff --git a/demucs/grids/mmi.py b/demucs/demucs/grids/mmi.py similarity index 100% rename from demucs/grids/mmi.py rename to demucs/demucs/grids/mmi.py diff --git a/demucs/grids/mmi_ft.py b/demucs/demucs/grids/mmi_ft.py similarity index 100% rename from demucs/grids/mmi_ft.py rename to demucs/demucs/grids/mmi_ft.py diff --git a/demucs/grids/repro.py b/demucs/demucs/grids/repro.py similarity index 100% rename from demucs/grids/repro.py rename to demucs/demucs/grids/repro.py diff --git a/demucs/grids/repro_ft.py b/demucs/demucs/grids/repro_ft.py similarity index 100% rename from demucs/grids/repro_ft.py rename to demucs/demucs/grids/repro_ft.py diff --git a/demucs/grids/sdx23.py b/demucs/demucs/grids/sdx23.py similarity index 100% rename from demucs/grids/sdx23.py rename to demucs/demucs/grids/sdx23.py diff --git a/demucs/hdemucs.py b/demucs/demucs/hdemucs.py similarity index 100% rename from demucs/hdemucs.py rename to demucs/demucs/hdemucs.py diff --git a/demucs/htdemucs.py b/demucs/demucs/htdemucs.py similarity index 100% rename from demucs/htdemucs.py rename to demucs/demucs/htdemucs.py diff --git a/demucs/pretrained.py b/demucs/demucs/pretrained.py similarity index 100% rename from demucs/pretrained.py rename to demucs/demucs/pretrained.py diff --git a/demucs/py.typed b/demucs/demucs/py.typed similarity index 100% rename from demucs/py.typed rename to demucs/demucs/py.typed diff --git a/demucs/remote/files.txt b/demucs/demucs/remote/files.txt similarity index 100% rename from demucs/remote/files.txt rename to demucs/demucs/remote/files.txt diff --git a/demucs/remote/hdemucs_mmi.yaml b/demucs/demucs/remote/hdemucs_mmi.yaml similarity index 100% rename from demucs/remote/hdemucs_mmi.yaml rename to demucs/demucs/remote/hdemucs_mmi.yaml diff --git a/demucs/remote/htdemucs.yaml b/demucs/demucs/remote/htdemucs.yaml similarity index 100% rename from demucs/remote/htdemucs.yaml rename to demucs/demucs/remote/htdemucs.yaml diff --git a/demucs/remote/htdemucs_6s.yaml b/demucs/demucs/remote/htdemucs_6s.yaml similarity index 100% rename from demucs/remote/htdemucs_6s.yaml rename to demucs/demucs/remote/htdemucs_6s.yaml diff --git a/demucs/remote/htdemucs_ft.yaml b/demucs/demucs/remote/htdemucs_ft.yaml similarity index 100% rename from demucs/remote/htdemucs_ft.yaml rename to demucs/demucs/remote/htdemucs_ft.yaml diff --git a/demucs/remote/mdx.yaml b/demucs/demucs/remote/mdx.yaml similarity index 100% rename from demucs/remote/mdx.yaml rename to demucs/demucs/remote/mdx.yaml diff --git a/demucs/remote/mdx_extra.yaml b/demucs/demucs/remote/mdx_extra.yaml similarity index 100% rename from demucs/remote/mdx_extra.yaml rename to demucs/demucs/remote/mdx_extra.yaml diff --git a/demucs/remote/mdx_extra_q.yaml b/demucs/demucs/remote/mdx_extra_q.yaml similarity index 100% rename from demucs/remote/mdx_extra_q.yaml rename to demucs/demucs/remote/mdx_extra_q.yaml diff --git a/demucs/remote/mdx_q.yaml b/demucs/demucs/remote/mdx_q.yaml similarity index 100% rename from demucs/remote/mdx_q.yaml rename to demucs/demucs/remote/mdx_q.yaml diff --git a/demucs/remote/repro_mdx_a.yaml b/demucs/demucs/remote/repro_mdx_a.yaml similarity index 100% rename from demucs/remote/repro_mdx_a.yaml rename to demucs/demucs/remote/repro_mdx_a.yaml diff --git a/demucs/remote/repro_mdx_a_hybrid_only.yaml b/demucs/demucs/remote/repro_mdx_a_hybrid_only.yaml similarity index 100% rename from demucs/remote/repro_mdx_a_hybrid_only.yaml rename to demucs/demucs/remote/repro_mdx_a_hybrid_only.yaml diff --git a/demucs/remote/repro_mdx_a_time_only.yaml b/demucs/demucs/remote/repro_mdx_a_time_only.yaml similarity index 100% rename from demucs/remote/repro_mdx_a_time_only.yaml rename to demucs/demucs/remote/repro_mdx_a_time_only.yaml diff --git a/demucs/repitch.py b/demucs/demucs/repitch.py similarity index 100% rename from demucs/repitch.py rename to demucs/demucs/repitch.py diff --git a/demucs/repo.py b/demucs/demucs/repo.py similarity index 100% rename from demucs/repo.py rename to demucs/demucs/repo.py diff --git a/demucs/separate.py b/demucs/demucs/separate.py similarity index 100% rename from demucs/separate.py rename to demucs/demucs/separate.py diff --git a/demucs/solver.py b/demucs/demucs/solver.py similarity index 100% rename from demucs/solver.py rename to demucs/demucs/solver.py diff --git a/demucs/spec.py b/demucs/demucs/spec.py similarity index 100% rename from demucs/spec.py rename to demucs/demucs/spec.py diff --git a/demucs/states.py b/demucs/demucs/states.py similarity index 100% rename from demucs/states.py rename to demucs/demucs/states.py diff --git a/demucs/svd.py b/demucs/demucs/svd.py similarity index 100% rename from demucs/svd.py rename to demucs/demucs/svd.py diff --git a/demucs/train.py b/demucs/demucs/train.py similarity index 100% rename from demucs/train.py rename to demucs/demucs/train.py diff --git a/demucs/transformer.py b/demucs/demucs/transformer.py similarity index 100% rename from demucs/transformer.py rename to demucs/demucs/transformer.py diff --git a/demucs/utils.py b/demucs/demucs/utils.py similarity index 100% rename from demucs/utils.py rename to demucs/demucs/utils.py diff --git a/demucs/wav.py b/demucs/demucs/wav.py similarity index 100% rename from demucs/wav.py rename to demucs/demucs/wav.py diff --git a/demucs/wdemucs.py b/demucs/demucs/wdemucs.py similarity index 100% rename from demucs/wdemucs.py rename to demucs/demucs/wdemucs.py diff --git a/demucs/docs/api.md b/demucs/docs/api.md new file mode 100644 index 00000000..dbd858a7 --- /dev/null +++ b/demucs/docs/api.md @@ -0,0 +1,204 @@ +# Demucs APIs + +## Quick start + +Notes: Type hints have been added to all API functions. It is recommended to check them before passing parameters to a function as some arguments only support limited types (e.g. parameter `repo` of method `load_model` only support type `pathlib.Path`). + +1. The first step is to import api module: + +```python +import demucs.api +``` + +2. Then initialize the `Separator`. Parameters which will be served as default values for methods can be passed. Model should be specified. + +```python +# Initialize with default parameters: +separator = demucs.api.Separator() + +# Use another model and segment: +separator = demucs.api.Separator(model="mdx_extra", segment=12) + +# You can also use other parameters defined +``` + +3. Separate it! + +```python +# Separating an audio file +origin, separated = separator.separate_audio_file("file.mp3") + +# Separating a loaded audio +origin, separated = separator.separate_tensor(audio) + +# If you encounter an error like CUDA out of memory, you can use this to change parameters like `segment`: +separator.update_parameter(segment=smaller_segment) +``` + +4. Save audio + +```python +# Remember to create the destination folder before calling `save_audio` +# Or you are likely to recieve `FileNotFoundError` +for file, sources in separated: + for stem, source in sources.items(): + demucs.api.save_audio(source, f"{stem}_{file}", samplerate=separator.samplerate) +``` + +## API References + +The types of each parameter and return value is not listed in this document. To know the exact type of them, please read the type hints in api.py (most modern code editors support inferring types based on type hints). + +### `class Separator` + +The base separator class + +##### Parameters + +model: Pretrained model name or signature. Default is htdemucs. + +repo: Folder containing all pre-trained models for use. + +segment: Length (in seconds) of each segment (only available if `split` is `True`). If not specified, will use the command line option. + +shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and apply the oppositve shift to the output. This is repeated `shifts` time and all predictions are averaged. This effectively makes the model time equivariant and improves SDR by up to 0.2 points. If not specified, will use the command line option. + +split: If True, the input will be broken down into small chunks (length set by `segment`) and predictions will be performed individually on each and concatenated. Useful for model with large memory footprint like Tasnet. If not specified, will use the command line option. + +overlap: The overlap between the splits. If not specified, will use the command line option. + +device (torch.device, str, or None): If provided, device on which to execute the computation, otherwise `wav.device` is assumed. When `device` is different from `wav.device`, only local computations will be on `device`, while the entire tracks will be stored on `wav.device`. If not specified, will use the command line option. + +jobs: Number of jobs. This can increase memory usage but will be much faster when multiple cores are available. If not specified, will use the command line option. + +callback: A function will be called when the separation of a chunk starts or finished. The argument passed to the function will be a dict. For more information, please see the Callback section. + +callback_arg: A dict containing private parameters to be passed to callback function. For more information, please see the Callback section. + +progress: If true, show a progress bar. + +##### Notes for callback + +The function will be called with only one positional parameter whose type is `dict`. The `callback_arg` will be combined with information of current separation progress. The progress information will override the values in `callback_arg` if same key has been used. To abort the separation, raise an exception in `callback` which should be handled by yourself if you want your codes continue to function. + +Progress information contains several keys (These keys will always exist): +- `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. +- `shift_idx`: The index of shifts. Starts from 0. +- `segment_offset`: The offset of current segment. If the number is 441000, it doesn't mean that it is at the 441000 second of the audio, but the "frame" of the tensor. +- `state`: Could be `"start"` or `"end"`. +- `audio_length`: Length of the audio (in "frame" of the tensor). +- `models`: Count of submodels in the model. + +#### `property samplerate` + +A read-only property saving sample rate of the model requires. Will raise a warning if the model is not loaded and return the default value. + +#### `property audio_channels` + +A read-only property saving audio channels of the model requires. Will raise a warning if the model is not loaded and return the default value. + +#### `property model` + +A read-only property saving the model. + +#### `method update_parameter()` + +Update the parameters of separation. + +##### Parameters + +segment: Length (in seconds) of each segment (only available if `split` is `True`). If not specified, will use the command line option. + +shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and apply the oppositve shift to the output. This is repeated `shifts` time and all predictions are averaged. This effectively makes the model time equivariant and improves SDR by up to 0.2 points. If not specified, will use the command line option. + +split: If True, the input will be broken down into small chunks (length set by `segment`) and predictions will be performed individually on each and concatenated. Useful for model with large memory footprint like Tasnet. If not specified, will use the command line option. + +overlap: The overlap between the splits. If not specified, will use the command line option. + +device (torch.device, str, or None): If provided, device on which to execute the computation, otherwise `wav.device` is assumed. When `device` is different from `wav.device`, only local computations will be on `device`, while the entire tracks will be stored on `wav.device`. If not specified, will use the command line option. + +jobs: Number of jobs. This can increase memory usage but will be much faster when multiple cores are available. If not specified, will use the command line option. + +callback: A function will be called when the separation of a chunk starts or finished. The argument passed to the function will be a dict. For more information, please see the Callback section. + +callback_arg: A dict containing private parameters to be passed to callback function. For more information, please see the Callback section. + +progress: If true, show a progress bar. + +##### Notes for callback + +The function will be called with only one positional parameter whose type is `dict`. The `callback_arg` will be combined with information of current separation progress. The progress information will override the values in `callback_arg` if same key has been used. To abort the separation, raise an exception in `callback` which should be handled by yourself if you want your codes continue to function. + +Progress information contains several keys (These keys will always exist): +- `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. +- `shift_idx`: The index of shifts. Starts from 0. +- `segment_offset`: The offset of current segment. If the number is 441000, it doesn't mean that it is at the 441000 second of the audio, but the "frame" of the tensor. +- `state`: Could be `"start"` or `"end"`. +- `audio_length`: Length of the audio (in "frame" of the tensor). +- `models`: Count of submodels in the model. + +#### `method separate_tensor()` + +Separate an audio. + +##### Parameters + +wav: Waveform of the audio. Should have 2 dimensions, the first is each audio channel, while the second is the waveform of each channel. e.g. `tuple(wav.shape) == (2, 884000)` means the audio has 2 channels. + +sr: Sample rate of the original audio, the wave will be resampled if it doesn't match the model. + +##### Returns + +A tuple, whose first element is the original wave and second element is a dict, whose keys are the name of stems and values are separated waves. The original wave will have already been resampled. + +##### Notes + +Use this function with cautiousness. This function does not provide data verifying. + +#### `method separate_audio_file()` + +Separate an audio file. The method will automatically read the file. + +##### Parameters + +wav: Path of the file to be separated. + +##### Returns + +A tuple, whose first element is the original wave and second element is a dict, whose keys are the name of stems and values are separated waves. The original wave will have already been resampled. + +### `function save_audio()` + +Save audio file. + +##### Parameters + +wav: Audio to be saved + +path: The file path to be saved. Ending must be one of `.mp3` and `.wav`. + +samplerate: File sample rate. + +bitrate: If the suffix of `path` is `.mp3`, it will be used to specify the bitrate of mp3. + +clip: Clipping preventing strategy. + +bits_per_sample: If the suffix of `path` is `.wav`, it will be used to specify the bit depth of wav. + +as_float: If it is True and the suffix of `path` is `.wav`, then `bits_per_sample` will be set to 32 and will write the wave file with float format. + +##### Returns + +None + +### `function list_models()` + +List the available models. Please remember that not all the returned models can be successfully loaded. + +##### Parameters + +repo: The repo whose models are to be listed. + +##### Returns + +A dict with two keys ("single" for single models and "bag" for bag of models). The values are lists whose components are strs. \ No newline at end of file diff --git a/demucs/docs/linux.md b/demucs/docs/linux.md new file mode 100644 index 00000000..31d9a695 --- /dev/null +++ b/demucs/docs/linux.md @@ -0,0 +1,28 @@ +# Linux support for Demucs + +If your distribution has at least Python 3.8, and you just wish to separate +tracks with Demucs, not train it, you can just run + +```bash +pip3 install --user -U demucs +# Then anytime you want to use demucs, just do +python3 -m demucs -d cpu PATH_TO_AUDIO_FILE_1 +# If you have added the user specific pip bin/ folder to your path, you can also do +demucs -d cpu PATH_TO_AUDIO_FILE_1 +``` + +If Python is too old, or you want to be able to train, I recommend [installing Miniconda][miniconda], with Python 3.8 or more. + +```bash +conda activate +pip3 install -U demucs +# Then anytime you want to use demucs, first do conda activate, then +demucs -d cpu PATH_TO_AUDIO_FILE_1 +``` + +Of course, you can also use a specific env for Demucs. + +**Important, torchaudio 0.12 update:** Torchaudio no longer supports decoding mp3s without ffmpeg installed. You must have ffmpeg installed, either through Anaconda (`conda install ffmpeg -c conda-forge`) or as a distribution package (e.g. `sudo apt-get install ffmpeg`). + + +[miniconda]: https://docs.conda.io/en/latest/miniconda.html#linux-installers diff --git a/demucs/docs/mac.md b/demucs/docs/mac.md new file mode 100644 index 00000000..62dd235e --- /dev/null +++ b/demucs/docs/mac.md @@ -0,0 +1,28 @@ +# macOS support for Demucs + +If you have a sufficiently recent version of macOS, you can just run + +```bash +python3 -m pip install --user -U demucs +# Then anytime you want to use demucs, just do +python3 -m demucs -d cpu PATH_TO_AUDIO_FILE_1 +# If you have added the user specific pip bin/ folder to your path, you can also do +demucs -d cpu PATH_TO_AUDIO_FILE_1 +``` + +If you do not already have Anaconda installed or much experience with the terminal on macOS, here are some detailed instructions: + +1. Download [Anaconda 3.8 (or more recent) 64-bit for macOS][anaconda]: +2. Open [Anaconda Prompt in macOS][prompt] +3. Follow these commands: +```bash +conda activate +pip3 install -U demucs +# Then anytime you want to use demucs, first do conda activate, then +demucs -d cpu PATH_TO_AUDIO_FILE_1 +``` + +**Important, torchaudio 0.12 update:** Torchaudio no longer supports decoding mp3s without ffmpeg installed. You must have ffmpeg installed, either through Anaconda (`conda install ffmpeg -c conda-forge`) or with Homebrew for instance (`brew install ffmpeg`). + +[anaconda]: https://www.anaconda.com/download +[prompt]: https://docs.anaconda.com/anaconda/user-guide/getting-started/#open-nav-mac diff --git a/demucs/docs/mdx.md b/demucs/docs/mdx.md new file mode 100644 index 00000000..2a20f9cb --- /dev/null +++ b/demucs/docs/mdx.md @@ -0,0 +1,73 @@ +# Music DemiXing challenge (MDX) + +If you want to use Demucs for the [MDX challenge](https://www.aicrowd.com/challenges/music-demixing-challenge-ismir-2021), +please follow the instructions hereafter + +## Installing Demucs + +Follow the instructions from the [main README](https://github.com/facebookresearch/demucs#requirements) +in order to setup Demucs using Anaconda. You will need the full setup up for training, including soundstretch. + +## Getting MusDB-HQ + +Download [MusDB-HQ](https://zenodo.org/record/3338373) to some folder and unzip it. + +## Training Demucs + +Train Demucs (you might need to change the batch size depending on the number of GPUs available). +It seems 48 channels is enough to get the best performance on MusDB-HQ, and training will faster +and less memory demanding. In any case, the 64 channels versions is timing out on the challenge. +```bash +./run.py --channels=48 --batch_size 64 --musdb=PATH_TO_MUSDB --is_wav [EXTRA_FLAGS] +``` + +### Post training + +Once the training is completed, a new model file will be exported in `models/`. + +You can look at the SDR on the MusDB dataset using `python result_table.py`. + + +### Evaluate and export a model before training is over + +If you want to export a model before training is complete, use the following command: +```bash +python -m demucs [ALL EXACT TRAINING FLAGS] --save_model +``` +You can also pass the `--half` flag, in order to save weights in half precision. This will divide the model size by 2 and won't impact SDR. + +Once this is done, you can partially evaluate a model with +```bash +./run.py --test NAME_OF_MODEL.th --musdb=PATH_TO_MUSDB --is_wav +``` + +**Note:** `NAME_OF_MODEL.th` is given relative to the models folder (given by `--models`, defaults to `models/`), so don't include it in the name. + + +### Training smaller models + +If you want to quickly test idea, I would recommend training a 16 kHz model, and testing if things work there or not, before training the full 44kHz model. You can train one of those with +```bash +./run.py --channels=32 --samplerate 16000 --samples 160000 --data_stride 16000 --depth=5 --batch_size 64 --repitch=0 --musdb=PATH_TO_MUSDB --is_wav [EXTRA_FLAGS] +``` +(repitch must be turned off, because things will break at 16kHz). + +## Submitting your model + +1. Git clone [the Music Demixing Challenge - Starter Kit - Demucs Edition](https://github.com/adefossez/music-demixing-challenge-starter-kit). +2. Inside the starter kit, create a `models/` folder and copy over the trained model from the Demucs repo (renaming +it for instance `my_model.th`) +3. Inside the `test_demuc.py` file, change the function `prediction_setup`: comment the loading +of the pre-trained model, and uncomment the code to load your own model. +4. Edit the file `aicrowd.json` with your username. +5. Install [git-lfs](https://git-lfs.github.com/). Then run + +```bash +git lfs install +git add models/ +git add -u . +git commit -m "My Demucs submission" +``` +6. Follow the [submission instructions](https://github.com/AIcrowd/music-demixing-challenge-starter-kit/blob/master/docs/SUBMISSION.md). + +Best of luck 🤞 diff --git a/demucs/docs/release.md b/demucs/docs/release.md new file mode 100644 index 00000000..df8f122f --- /dev/null +++ b/demucs/docs/release.md @@ -0,0 +1,114 @@ +# Release notes for Demucs + +## V4.1.0a, TBD + +Get models list + +Check segment of HTDemucs inside BagOfModels + +Added api.py to be called from another program + +Use api in separate.py + +Added `--other-method`: method to get `no_{STEM}`, add up all the other stems (add), original track substract the specific stem (minus), and discard (none) + +Added type `HTDemucs` to type alias `AnyModel`. + +Improving recent torchaudio versions support (Thanks @CarlGao4) + +## V4.0.1, 8th of September 2023 + +**From this version, Python 3.7 is no longer supported. This is not a problem since the latest PyTorch 2.0.0 no longer support it either.** + +Various improvements by @CarlGao4. Support for `segment` param inside of HTDemucs +model. + +Made diffq an optional dependency, with an error message if not installed. + +Added output format flac (Free Lossless Audio Codec) + +Will use CPU for complex numbers, when using MPS device (all other computations are performed by mps). + +Optimize codes to save memory + +Allow changing preset of MP3 + +## V4.0.0, 7th of December 2022 + +Adding hybrid transformer Demucs model. + +Added support for [Torchaudio implementation of HDemucs](https://pytorch.org/audio/main/tutorials/hybrid_demucs_tutorial.html), thanks @skim0514. + +Added experimental 6 sources model `htdemucs_6s` (`drums`, `bass`, `other`, `vocals`, `piano`, `guitar`). + +## V3.0.6, 16th of November 2022 + +Option to customize output path of stems (@CarlGao4) + +Fixed bug in pad1d leading to failure sometimes. + +## V3.0.5, 17th of August 2022 + +Added `--segment` flag to customize the segment length and use less memory (thanks @CarlGao4). + +Fix reflect padding bug on small inputs. + +Compatible with pyTorch 1.12 + +## V3.0.4, 24th of February 2022 + +Added option to split into two stems (i.e. vocals, vs. non vocals), thanks to @CarlGao4. + +Added `--float32`, `--int24` and `--clip-mode` options to customize how output stems are saved. + +## V3.0.3, 2nd of December 2021 + +Fix bug in weights used for different sources. Thanks @keunwoochoi for the report and fix. + +Improving drastically memory usage on GPU for long files. Thanks a lot @famzah for providing this. + +Adding multithread evaluation on CPU (`-j` option). + +(v3.0.2 had a bug with the CPU pool and is skipped.) + +## V3.0.1, 12th of November 2021 + +Release of Demucs v3, featuring hybrid domain separation and much more. +This drops support for Conv-Tasnet and training on the non HQ MusDB dataset. +There is no version 3.0.0 because I messed up. + +## V2.0.2, 26th of May 2021 + +- Fix in Tasnet (PR #178) +- Use ffmpeg in priority when available instead of torchaudio to avoid small shift in MP3 data. +- other minor fixes + +## v2.0.1, 11th of May 2021 + +MusDB HQ support added. Custom wav dataset support added. +Minor changes: issue with padding of mp3 and torchaudio reading, in order to limit that, +Demucs now uses ffmpeg in priority and fallback to torchaudio. +Replaced pre-trained demucs model with one trained on more recent codebase. + +## v2.0.0, 28th of April 2021 + +This is a big release, with at lof of breaking changes. You will likely +need to install Demucs from scratch. + + + +- Demucs now supports on the fly resampling by a factor of 2. +This improves SDR almost 0.3 points. +- Random scaling of each source added (From Uhlich et al. 2017). +- Random pitch and tempo augmentation addded, from [Cohen-Hadria et al. 2019]. +- With extra augmentation, the best performing Demucs model now has only 64 channels +instead of 100, so model size goes from 2.4GB to 1GB. Also SDR is up from 5.6 SDR to 6.3 when trained only on MusDB. +- Quantized model using [DiffQ](https://github.com/facebookresearch/diffq) has been added. Model size is 150MB, no loss in quality as far as I, or the metrics, +can say. +- Pretrained models are now using the TorchHub interface. +- Overlap mode for separation, to limit inconsitencies at + frame boundaries, with linear transition over the overlap. Overlap is currently + at 25%. Not that this is only done for separation, not training, because + I added that quite late to the code. For Conv-TasNet this can improve + SDR quite a bit (+0.3 points, to 6.0). +- PyPI hosting, for separation, not training! diff --git a/demucs/docs/sdx23.md b/demucs/docs/sdx23.md new file mode 100644 index 00000000..65c5df9a --- /dev/null +++ b/demucs/docs/sdx23.md @@ -0,0 +1,61 @@ +# SDX 23 challenge + +Checkout [the challenge page](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023) +for more information. This page is specifically on training models for the [MDX'23 sub-challenge](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023/problems/music-demixing-track-mdx-23). +There are two tracks: one trained on a dataset with bleeding, and the other with label mixups. + +This gives instructions on training an Hybrid Demucs model on those datasets. +I haven't tried the HT Demucs model, as it typically requires quite a bit of training data but the same could be done with it. + +You will need to work from an up to date clone of this repo. See the [generic training instructions](./training.md) for more information. + +## Getting the data + +Register on the challenge, then checkout the [Resources page](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023/problems/music-demixing-track-mdx-23/dataset_files) and download the dataset you are +interested in. + +Update the `conf/dset/sdx23_bleeding.yaml` and `conf/dset/sdx23_labelnoise.yaml` files to point to the right path. + +**Make sure soundfile** is installed (`conda install -c conda-forge libsndfile; pip install soundfile`). + +### Create proper train / valid structure + +Demucs requires a valid set to work properly. Go to the folder where you extracted the tracks then do: + +```shell +mkdir train +mv * train # there will be a warning saying cannot move train to itself but that's fine the other tracks should have. +mkdir valid +cd train +mv 5640831d-7853-4d06-8166-988e2844b652 bc964128-da16-4e4c-af95-4d1211e78c70 \ + cc7f7675-d3c8-4a49-a2d7-a8959b694004 f40ffd10-4e8b-41e6-bd8a-971929ca9138 \ + bc1f2967-f834-43bd-aadc-95afc897cfe7 cc3e4991-6cce-40fe-a917-81a4fbb92ea6 \ + ed90a89a-bf22-444d-af3d-d9ac3896ebd2 f4b735de-14b1-4091-a9ba-c8b30c0740a7 ../valid +``` + +## Training + +See `dora grid sdx23` for a starting point. You can do `dora grid sdx23 --init --dry_run` then `dora run -f SIG -d` with `SIG` one of the signature +to train on a machine with GPUs if you do not have a SLURM cluster. + +Keep in mind that the valid tracks and train tracks are corrupted in different ways for those tasks, so do not expect +the valid loss to go down as smoothly as with normal training on the clean MusDB. + +I only trained Hybrid Demucs baselines as Hybrid Transformer typically requires more data. + + +## Exporting models + +Run +``` +python -m tools.export SIG +``` + +This will export the trained model into the `release_models` folder. + +## Submitting a model + +Clone the [Demucs Starter Kit for SDX23](https://github.com/adefossez/sdx23). Follow the instructions there. + +You will to copy the models under `release_models` in the `sdx23/models/` folder before you can use them. +Make sure you have git-lfs properly installed and setup before adding those files to your fork of `sdx23`. diff --git a/demucs/docs/training.md b/demucs/docs/training.md new file mode 100644 index 00000000..fa046070 --- /dev/null +++ b/demucs/docs/training.md @@ -0,0 +1,290 @@ +# Training (Hybrid) Demucs + +## Install all the dependencies + +You should install all the dependencies either with either Anaconda (using the env file `environment-cuda.yml` ) +or `pip`, with `requirements.txt`. + +## Datasets + +### MusDB HQ + +Note that we do not support MusDB non HQ training anymore. +Get the [Musdb HQ](https://zenodo.org/record/3338373) dataset, and update the path to it in two places: +- The `dset.musdb` key inside `conf/config.yaml`. +- The variable `MUSDB_PATH` inside `tools/automix.py`. + +### Create the fine tuning datasets + +**This is only for the MDX 2021 competition models** + +I use a fine tuning on a dataset crafted by remixing songs in a musically plausible way. +The automix script will make sure that BPM, first beat and pitches are aligned. +In the file `tools/automix.py`, edit `OUTPATH` to suit your setup, as well as the `MUSDB_PATH` +to point to your copy of MusDB HQ. Then run + +```bash +export NUMBA_NUM_THREADS=1; python3 -m tools.automix +``` + +**Important:** the script will show many errors, those are normals. They just indicate when two stems + do not batch due to BPM or music scale difference. + +Finally, edit the file `conf/dset/auto_mus.yaml` and replace `dset.wav` to the value of `OUTPATH`. + +If you have a custom dataset, you can also uncomment the lines `dset2 = ...` and +`dset3 = ...` to add your custom wav data and the test set of MusDB for Track B models. +You can then replace the paths in `conf/dset/auto_extra.yaml`, `conf/dset/auto_extra_test.yaml` +and `conf/dset/aetl.yaml` (this last one was using 10 mixes instead of 6 for each song). + +### Dataset metadata cache + +Datasets are scanned the first time they are used to determine the files and their durations. +If you change a dataset and need a rescan, just delete the `metadata` folder. + +## A short intro to Dora + +I use [Dora][dora] for all the of experiments (XPs) management. You should have a look at the Dora README +to learn about the tool. Here is a quick summary of what to know: + +- An XP is a unique set of hyper-parameters with a given signature. The signature is a hash of + those hyper-parameters. I will always refer to an XP with its signature, e.g. `9357e12e`. + We will see after that you can retrieve the hyper-params and re-rerun it in a single command. +- In fact, the hash is defined as a delta between the base config and the one obtained with + the config overrides you passed from the command line. + **This means you must never change the `conf/**.yaml` files directly.**, + except for editing things like paths. Changing the default values in the config files means + the XP signature won't reflect that change, and wrong checkpoints might be reused. + I know, this is annoying, but the reason is that otherwise, any change to the config file would + mean that all XPs ran so far would see their signature change. + +### Dora commands + +Run `tar xvf outputs.tar.gz`. This will initialize the Dora XP repository, so that Dora knows +which hyper-params match the signature like `9357e12e`. Once you have done that, you should be able +to run the following: + +```bash +dora info -f 81de367c # this will show the hyper-parameter used by a specific XP. + # Be careful some overrides might present twice, and the right most one + # will give you the right value for it. +dora run -d -f 81de367c # run an XP with the hyper-parameters from XP 81de367c. + # `-d` is for distributed, it will use all available GPUs. +dora run -d -f 81de367c hdemucs.channels=32 # start from the config of XP 81de367c but change some hyper-params. + # This will give you a new XP with a new signature (here 3fe9c332). +``` + +An XP runs from a specific folder based on its signature, by default under the `outputs/` folder. +You can safely interrupt a training and resume it, it will reuse any existing checkpoint, as it will +reuse the same folder. +If you made some change to the code and need to ignore a previous checkpoint you can use `dora run --clear [RUN ARGS]`. + +If you have a Slurm cluster, you can also use the `dora grid` command, e.g. `dora grid mdx`. +Please refer to the [Dora documentation][dora] for more information. + +## Hyper parameters + +Have a look at [conf/config.yaml](../conf/config.yaml) for a list of all the hyper-parameters you can override. +If you are not familiar with [Hydra](https://github.com/facebookresearch/hydra), go checkout their page +to be familiar with how to provide overrides for your trainings. + + +## Model architecture + +A number of architectures are supported. You can select one with `model=NAME`, and have a look +in [conf/config.yaml'(../conf/config.yaml) for each architecture specific hyperparams. +Those specific params will be always prefixed with the architecture name when passing the override +from the command line or in grid files. Here is the list of models: + +- demucs: original time-only Demucs. +- hdemucs: Hybrid Demucs (v3). +- torch_hdemucs: Same as Hybrid Demucs, but using [torchaudio official implementation](https://pytorch.org/audio/stable/tutorials/hybrid_demucs_tutorial.html). +- htdemucs: Hybrid Transformer Demucs (v4). + +### Storing config in files + +As mentioned earlier, you should never change the base config files. However, you can use Hydra config groups +in order to store variants you often use. If you want to create a new variant combining multiple hyper-params, +copy the file `conf/variant/example.yaml` to `conf/variant/my_variant.yaml`, and then you can use it with + +```bash +dora run -d variant=my_variant +``` + +Once you have created this file, you should not edit it once you have started training models with it. + + +## Fine tuning + +If a first model is trained, you can fine tune it with other settings (e.g. automix dataset) with + +```bash +dora run -d -f 81de367c continue_from=81de367c dset=auto_mus variant=finetune +```` + +Note that you need both `-f 81de367c` and `continue_from=81de367c`. The first one indicates +that the hyper-params of `81de367c` should be used as a starting point for the config. +The second indicates that the weights from `81de367c` should be used as a starting point for the solver. + + +## Model evaluation + +Your model will be evaluated automatically with the new SDR definition from MDX every 20 epochs. +Old style SDR (which is quite slow) will only happen at the end of training. + +## Model Export + + +In order to use your models with other commands (such as the `demucs` command for separation) you must +export it. For that run + +```bash +python3 -m tools.export 9357e12e [OTHER SIGS ...] # replace with the appropriate signatures. +``` + +The models will be stored under `release_models/`. You can use them with the `demucs` separation command with the following flags: +```bash +demucs --repo ./release_models -n 9357e12e my_track.mp3 +``` + +### Bag of models + +If you want to combine multiple models, potentially with different weights for each source, you can copy +`demucs/remote/mdx.yaml` to `./release_models/my_bag.yaml`. You can then edit the list of models (all models used should have been exported first) and the weights per source and model (list of list, outer list is over models, inner list is over sources). You can then use your bag of model as + +```bash +demucs --repo ./release_models -n my_bag my_track.mp3 +``` + +## Model evaluation + +You can evaluate any pre-trained model or bag of models using the following command: +```bash +python3 -m tools.test_pretrained -n NAME_OF_MODEL [EXTRA ARGS] +``` +where `NAME_OF_MODEL` is either the name of the bag (e.g. `mdx`, `repro_mdx_a`), +or a single Dora signature of one of the model of the bags. You can pass `EXTRA ARGS` to customize +the test options, like the number of random shifts (e.g. `test.shifts=2`). This will compute the old-style +SDR and can take quite bit of time. + +For custom models that were trained locally, you will need to indicate that you wish +to use the local model repositories, with the `--repo ./release_models` flag, e.g., +```bash +python3 -m tools.test_pretrained --repo ./release_models -n my_bag +``` + + +## API to retrieve the model + +You can retrieve officially released models in Python using the following API: +```python +from demucs import pretrained +from demucs.apply import apply_model +bag = pretrained.get_model('htdemucs') # for a bag of models or a named model + # (which is just a bag with 1 model). +model = pretrained.get_model('955717e8') # using the signature for single models. + +bag.models # list of individual models +stems = apply_model(model, mix) # apply the model to the given mix. +``` + +## Model Zoo + +### Hybrid Transformer Demucs + +The configuration for the Hybrid Transformer models are available in: + +```shell +dora grid mmi --dry_run --init +dora grid mmi_ft --dry_run --init # fined tuned on each sources. +``` + +We release in particular `955717e8`, Hybrid Transformer Demucs using 5 layers, 512 channels, 10 seconds training segment length. We also release its fine tuned version, with one model +for each source `f7e0c4bc`, `d12395a8`, `92cfc3b6`, `04573f0d` (drums, bass, other, vocals). +The model `955717e8` is also named `htdemucs`, while the bag of models is provided +as `htdemucs_ft`. + +We also release `75fc33f5`, a regular Hybrid Demucs trained on the same dataset, +available as `hdemucs_mmi`. + + + +### Models from the MDX Competition 2021 + + +Here is a short descriptions of the models used for the MDX submission, either Track A (MusDB HQ only) +or Track B (extra training data allowed). Training happen in two stage, with the second stage +being the fine tunining on the automix generated dataset. +All the fine tuned models are available on our AWS repository +(you can retrieve it with `demucs.pretrained.get_model(SIG)`). The bag of models are available +by doing `demucs.pretrained.get_model(NAME)` with `NAME` begin either `mdx` (for Track A) or `mdx_extra` +(for Track B). + +#### Track A + +The 4 models are: + +- `0d19c1c6`: fine-tuned on automix dataset from `9357e12e` +- `7ecf8ec1`: fine-tuned on automix dataset from `e312f349` +- `c511e2ab`: fine-tuned on automix dataset from `81de367c` +- `7d865c68`: fine-tuned on automix dataset from `80a68df8` + +The 4 initial models (before fine tuning are): + +- `9357e12e`: 64ch time domain only improved Demucs, with new residual branches, group norm, + and singular value penalty. +- `e312f349`: 64ch time domain only improved, with new residual branches, group norm, + and singular value penalty, trained with a loss that focus only on drums and bass. +- `81de367c`: 48ch hybrid model , with residual branches, group norm, + singular value penalty penalty and amplitude spectrogram. +- `80a68df8`: same as b5559babb but using CaC and different + random seed, as well different weigths per frequency bands in outermost layers. + +The hybrid models are combined with equal weights for all sources except for the bass. +`0d19c1c6` (time domain) is used for both drums and bass. `7ecf8ec1` is used only for the bass. + +You can see all the hyper parameters at once with (one common line for all common hyper params, and then only shows +the hyper parameters that differs), along with the DiffQ variants that are used for the `mdx_q` models: +``` +dora grid mdx --dry_run --init +dora grid mdx --dry_run --init +``` + +#### Track B + +- `e51eebcc` +- `a1d90b5c` +- `5d2d6c55` +- `cfa93e08` + +All the models are 48ch hybrid demucs with different random seeds. Two of them +are using CaC, and two are using amplitude spectrograms with masking. +All the models are combined with equal weights for all sources. + +Things are a bit messy for Track B, there was a lot of fine tuning +over different datasets. I won't describe the entire genealogy of models here, +but all the information can be accessed with the `dora info -f SIG` command. + +Similarly you can do (those will contain a few extra lines, for training without the MusDB test set as training, and extra DiffQ XPs): +``` +dora grid mdx_extra --dry_run --init +``` + +### Reproducibility and Ablation + +I updated the paper to report numbers with a more homogeneous setup than the one used for the competition. +On MusDB HQ, I still need to use a combination of time only and hybrid models to achieve the best performance. +The experiments are provided in the grids [repro.py](../demucs/grids/repro.py) and +[repro_ft._py](../demucs/grids/repro_ft.py) for the fine tuning on the realistic mix datasets. + +The new bag of models reaches an SDR of 7.64 (vs. 7.68 for the original track A model). It uses +2 time only models trained with residual branches, local attention and the SVD penalty, +along with 2 hybrid models, with the same features, and using CaC representation. +We average the performance of all the models with the same weight over all sources, unlike +what was done for the original track A model. We trained for 600 epochs, against 360 before. + +The new bag of model is available as part of the pretrained model as `repro_mdx_a`. +The time only bag is named `repro_mdx_a_time_only`, and the hybrid only `repro_mdx_a_hybrid_only`. +Checkout the paper for more information on the training. + +[dora]: https://github.com/facebookresearch/dora diff --git a/demucs/docs/windows.md b/demucs/docs/windows.md new file mode 100644 index 00000000..b259b765 --- /dev/null +++ b/demucs/docs/windows.md @@ -0,0 +1,67 @@ +# Windows support for Demucs + +## Installation and usage + +If you don't have much experience with Anaconda, python or the shell, here are more detailed instructions. Note that **Demucs is not supported on 32bits systems** (as Pytorch is not available there). + +- First install Anaconda with **Python 3.8** or more recent, which you can find [here][install]. +- Start the [Anaconda prompt][prompt]. + +Then, all commands that follow must be run from this prompt. + +

    + I have no coding experience and these are too difficult for me + +> Then a GUI is suitable for you. See [Demucs GUI](https://github.com/CarlGao4/Demucs-Gui) + +
    + +### If you want to use your GPU + +If you have graphic cards produced by NVIDIA with more than 2GiB of memory, you can separate tracks with GPU acceleration. To achieve this, you must install Pytorch with CUDA. If Pytorch was already installed (you already installed Demucs for instance), first run `python.exe -m pip uninstall torch torchaudio`. +Then visit [Pytorch Home Page](https://pytorch.org/get-started/locally/) and follow the guide on it to install with CUDA support. Please make sure that the version of torchaudio should no greater than 2.1 (which is the latest version when this document is written, but 2.2.0 is sure unsupported) + +### Installation + +Start the Anaconda prompt, and run the following + +```cmd +conda install -c conda-forge ffmpeg +python.exe -m pip install -U demucs SoundFile +``` + +### Upgrade + +To upgrade Demucs, simply run `python.exe -m pip install -U demucs`, from the Anaconda prompt. + +### Usage + +Then to use Demucs, just start the **Anaconda prompt** and run: +``` +demucs -d cpu "PATH_TO_AUDIO_FILE_1" ["PATH_TO_AUDIO_FILE_2" ...] +``` +The `"` around the filename are required if the path contains spaces. A simple way to input these paths is draging a file from a folder into the terminal. + +To find out the separated files, you can run this command and open the folders: +``` +explorer separated +``` + +### Separating an entire folder + +You can use the following command to separate an entire folder of mp3s for instance (replace the extension `.mp3` if needs be for other file types) +``` +cd FOLDER +for %i in (*.mp3) do (demucs -d cpu "%i") +``` + +## Potential errors + +If you have an error saying that `mkl_intel_thread.dll` cannot be found, you can try to first run +`conda install -c defaults intel-openmp -f`. Then try again to run the `demucs` command. If it still doesn't work, you can try to run first `set CONDA_DLL_SEARCH_MODIFICATION_ENABLE=1`, then again the `demucs` command and hopefully it will work 🙏. + +**If you get a permission error**, please try starting the Anaconda Prompt as administrator. + + +[install]: https://www.anaconda.com/download +[prompt]: https://docs.anaconda.com/anaconda/user-guide/getting-started/#open-prompt-win diff --git a/demucs/environment-cpu.yml b/demucs/environment-cpu.yml new file mode 100644 index 00000000..2419bf35 --- /dev/null +++ b/demucs/environment-cpu.yml @@ -0,0 +1,28 @@ +name: demucs + +channels: + - pytorch + - conda-forge + +dependencies: + - python>=3.8,<3.10 + - ffmpeg>=4.2 + - pytorch>=1.8.1 + - torchaudio>=0.8 + - tqdm>=4.36 + - pip + - pip: + - diffq>=0.2 + - dora-search + - einops + - hydra-colorlog>=1.1 + - hydra-core>=1.1 + - julius>=0.2.3 + - lameenc>=1.2 + - openunmix + - musdb>=0.4.0 + - museval>=0.4.0 + - soundfile + - submitit + - treetable>=0.2.3 + diff --git a/demucs/environment-cuda.yml b/demucs/environment-cuda.yml new file mode 100644 index 00000000..0d61d33d --- /dev/null +++ b/demucs/environment-cuda.yml @@ -0,0 +1,28 @@ +name: demucs + +channels: + - pytorch + - conda-forge + +dependencies: + - python>=3.8,<3.10 + - ffmpeg>=4.2 + - pytorch>=1.8.1 + - torchaudio>=0.8 + - cudatoolkit>=10 + - tqdm>=4.36 + - pip + - pip: + - diffq>=0.2 + - dora-search + - einops + - hydra-colorlog>=1.1 + - hydra-core>=1.1 + - julius>=0.2.3 + - lameenc>=1.2 + - openunmix + - musdb>=0.4.0 + - museval>=0.4.0 + - soundfile + - submitit + - treetable>=0.2.3 diff --git a/demucs/hubconf.py b/demucs/hubconf.py new file mode 100644 index 00000000..0cdb553e --- /dev/null +++ b/demucs/hubconf.py @@ -0,0 +1,11 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +dependencies = ['dora-search', 'julius', 'lameenc', 'openunmix', 'pyyaml', + 'torch', 'torchaudio', 'tqdm'] + +from demucs.pretrained import get_model + diff --git a/demucs/mypy.ini b/demucs/mypy.ini new file mode 100644 index 00000000..c4e17f16 --- /dev/null +++ b/demucs/mypy.ini @@ -0,0 +1,5 @@ +[mypy] + +[mypy-treetable,torchaudio.*,diffq,yaml,tqdm,lameenc,musdb,museval,openunmix.*,einops,xformers.*] +ignore_missing_imports = True + diff --git a/demucs/outputs.tar.gz b/demucs/outputs.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..51933ac9ad898690980dd85c53ff4160567dbc8c GIT binary patch literal 1885 zcmXBOdo+}ZAIEW*sYbge%2qPJ8Y0wUT5Y1}sdkl=r3({l4BG`I6kU`&>#~#)>ryh) zxeHXGzKj$9vs$XA!6A^8K0p`!|RO z8?oBiPloDb@*-X zZR5<&T}Xc@Tzrtv*xO+0=pN_N1T*nxBfe zEg6K6{z%BYQU<9wncxTxM=Lx^L!jDJz3VHk+S7rv$v{Q|k*d0G2X5XaL7J%!`wFy3 zrHmduNY-nXBD*Vs_l;je`{dva+q)!%R}QubsMBAkW^F-Df7$1 zRp0P{xF!oF&VEFewx!2T!X_n}phQgm(exbR+Li@MfDPO)4+(tq=bqo&1d*D7>1vxH z*cOC&?Kidjao=(y*ps38YAR1ZzDCmlJ2uJahd@`)8ggtdUMSGyL<&QkclECFxGd4^ zX`in}_&#AUXd8kqpmxHu373tPv}`XSD|};>X$kL{88KoiLe9yyF@Ut|4Hb^dtSDB> zvkf*+TWV>|pPl{f1z~mX?CA=zFYAr%GdWTIL~x`wzQ7*)ZE4zZj-Q)x?K?|sI$>`j zhc%NKH-5HlU-u+Y&u{8`X(%}YHw|ILp#JJ?&dIQdw63P_{47T|40V(SW5P37>Xw50 z{0Q+Rudru*2D8j&!0X|$m39o#pG@TPxMLZ^$KBvFUNu?IV~)kn^3Rih0mBoJ8|FF< z{_Yua!-Q0EfuFB0Nnre{Nh{Csn@ka>q`VjZZ)tnuqXvh%nK^_%z5cCk*~^i)M`GRQ z(?{~hDGPU7n_!{MjxSvfUf&rtrk}6OvRjB*B~Ps-Y+1LMb+YD==XF1Kt83~?$7@&j z+X}aA`0FUkXHNT&#DI7;>5*@AXo08vcr^1^q{5qA*F5QAW!(_#pMPfYef1+xto>fU zx0YUboJqAk^)OEuN)0coq*cvyv+?*||H}54M_4C6-U+S}XIXa(oX#CA&mZa@omd|9 zSH|6EDJ6MUWfsY`V0aI1_Zh0d|MVNwDBTfPZ}S!~MdB5{vMXFDons`rV!n zufC=ur~d@e&0Ba+2 zTW>(cA8jhcC|w9!63=tbgLV4*%!+fc^(F?cLU0^Ii?u%m*U3RFMHI$BzY*soLUkWO zl8RDn(}xgto&bwZpl1`DM4;CR`z!I5tFXo=6pT!&v=`D|HjLg5!9%eNfln$1<|t#U(V4Qn@?g5i@K})ZLw04<+b-ft=-VoJFDDL z&atf?hzFQ_2mn$eKJWrIj-e;q)`OwmE1+$|r#f8^kEHs!@LzH+78##DcL2g@OE1MJ zZ%ry`BaE))m%i&9Mq5V_3*lTOrZEfv-Gw3}IIJDcQh8C)vMOEjRl^k!lc1fLyW!RE ziYWuID~lzGm@;zJSPN?yInMq8^b^NqP%w&gPIYnaR}HY?Jp#s!_yK?e3N7dV?|PkLvrybzEm(ESxSS?1G9G;z@|>`9Z6 z&FTZSWbWKl$VTy=~q5n+u zQ(8lAU;s=sE@^zXx}e-LW(%um^;~w+&C4Ytd|=0.1.12 +diffq>=0.2.1 +einops +flake8 +hydra-colorlog>=1.1 +hydra-core>=1.1 +julius>=0.2.3 +lameenc>=1.2 +museval +mypy +openunmix +pyyaml +submitit +torch>=1.8.1 +torchaudio>=0.8 +tqdm +treetable +soundfile>=0.10.3 diff --git a/demucs/requirements_minimal.txt b/demucs/requirements_minimal.txt new file mode 100644 index 00000000..8c6f1e57 --- /dev/null +++ b/demucs/requirements_minimal.txt @@ -0,0 +1,10 @@ +# please make sure you have already a pytorch install that is cuda enabled! +dora-search +einops +julius>=0.2.3 +lameenc>=1.2 +openunmix +pyyaml +torch>=1.8.1 +torchaudio>=0.8 +tqdm diff --git a/demucs/setup.cfg b/demucs/setup.cfg new file mode 100644 index 00000000..d54d56a0 --- /dev/null +++ b/demucs/setup.cfg @@ -0,0 +1,8 @@ +[pep8] +max-line-length = 100 + +[flake8] +max-line-length = 100 + +[yapf] +column_limit = 100 diff --git a/demucs/setup.py b/demucs/setup.py new file mode 100644 index 00000000..47163d79 --- /dev/null +++ b/demucs/setup.py @@ -0,0 +1,75 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. +# author: adefossez +# Inspired from https://github.com/kennethreitz/setup.py + +from pathlib import Path + +from setuptools import setup + + +NAME = 'demucs' +DESCRIPTION = 'Music source separation in the waveform domain.' + +URL = 'https://github.com/facebookresearch/demucs' +EMAIL = 'defossez@fb.com' +AUTHOR = 'Alexandre Défossez' +REQUIRES_PYTHON = '>=3.8.0' + +HERE = Path(__file__).parent + +# Get version without explicitely loading the module. +for line in open('demucs/__init__.py'): + line = line.strip() + if '__version__' in line: + context = {} + exec(line, context) + VERSION = context['__version__'] + + +def load_requirements(name): + required = [i.strip() for i in open(HERE / name)] + required = [i for i in required if not i.startswith('#')] + return required + + +REQUIRED = load_requirements('requirements_minimal.txt') +ALL_REQUIRED = load_requirements('requirements.txt') + +try: + with open(HERE / "README.md", encoding='utf-8') as f: + long_description = '\n' + f.read() +except FileNotFoundError: + long_description = DESCRIPTION + +setup( + name=NAME, + version=VERSION, + description=DESCRIPTION, + long_description=long_description, + long_description_content_type='text/markdown', + author=AUTHOR, + author_email=EMAIL, + python_requires=REQUIRES_PYTHON, + url=URL, + packages=['demucs'], + extras_require={ + 'dev': ALL_REQUIRED, + }, + install_requires=REQUIRED, + include_package_data=True, + entry_points={ + 'console_scripts': ['demucs=demucs.separate:main'], + }, + license='MIT License', + classifiers=[ + # Trove classifiers + # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers + 'License :: OSI Approved :: MIT License', + 'Topic :: Multimedia :: Sound/Audio', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + ], +) diff --git a/demucs/test.mp3 b/demucs/test.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..668604d876880d39913ef73bcf9df4b4308a9d70 GIT binary patch literal 802480 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

    +RjCwR>N)iXJXs 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%0Aqy_xmoqZfCK1|6r_B#CSPwnR~(-p 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 literal 0 HcmV?d00001 diff --git a/demucs/tools/__init__.py b/demucs/tools/__init__.py new file mode 100644 index 00000000..0952fcc3 --- /dev/null +++ b/demucs/tools/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. diff --git a/demucs/tools/automix.py b/demucs/tools/automix.py new file mode 100644 index 00000000..a839345e --- /dev/null +++ b/demucs/tools/automix.py @@ -0,0 +1,343 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +""" +This script creates realistic mixes with stems from different songs. +In particular, it will align BPM, sync up the first beat and perform pitch +shift to maximize pitches overlap. +In order to limit artifacts, only parts that can be mixed with less than 15% +tempo shift, and 3 semitones of pitch shift are mixed together. +""" +from collections import namedtuple +from concurrent.futures import ProcessPoolExecutor +import hashlib +from pathlib import Path +import random +import shutil +import tqdm +import pickle + +from librosa.beat import beat_track +from librosa.feature import chroma_cqt +import numpy as np +import torch +from torch.nn import functional as F + +from dora.utils import try_load +from demucs.audio import save_audio +from demucs.repitch import repitch +from demucs.pretrained import SOURCES +from demucs.wav import build_metadata, Wavset, _get_musdb_valid + + +MUSDB_PATH = '/checkpoint/defossez/datasets/musdbhq' +EXTRA_WAV_PATH = "/checkpoint/defossez/datasets/allstems_44" +# WARNING: OUTPATH will be completely erased. +OUTPATH = Path.home() / 'tmp/demucs_mdx/automix_musdb/' +CACHE = Path.home() / 'tmp/automix_cache' # cache BPM and pitch information. +CHANNELS = 2 +SR = 44100 +MAX_PITCH = 3 # maximum allowable pitch shift in semi tones +MAX_TEMPO = 0.15 # maximum allowable tempo shift + + +Spec = namedtuple("Spec", "tempo onsets kr track index") + + +def rms(wav, window=10000): + """efficient rms computed for each time step over a given window.""" + half = window // 2 + window = 2 * half + 1 + wav = F.pad(wav, (half, half)) + tot = wav.pow(2).cumsum(dim=-1) + return ((tot[..., window - 1:] - tot[..., :-window + 1]) / window).sqrt() + + +def analyse_track(dset, index): + """analyse track, extract bpm and distribution of notes from the bass line.""" + track = dset[index] + mix = track.sum(0).mean(0) + ref = mix.std() + + starts = (abs(mix) >= 1e-2 * ref).float().argmax().item() + track = track[..., starts:] + + cache = CACHE / dset.sig + cache.mkdir(exist_ok=True, parents=True) + + cache_file = cache / f"{index}.pkl" + cached = None + if cache_file.exists(): + cached = try_load(cache_file) + if cached is not None: + tempo, events, hist_kr = cached + + if cached is None: + drums = track[0].mean(0) + if drums.std() > 1e-2 * ref: + tempo, events = beat_track(y=drums.numpy(), units='time', sr=SR) + else: + print("failed drums", drums.std(), ref) + return None, track + + bass = track[1].mean(0) + r = rms(bass) + peak = r.max() + mask = r >= 0.05 * peak + bass = bass[mask] + if bass.std() > 1e-2 * ref: + kr = torch.from_numpy(chroma_cqt(y=bass.numpy(), sr=SR)) + hist_kr = (kr.max(dim=0, keepdim=True)[0] == kr).float().mean(1) + else: + print("failed bass", bass.std(), ref) + return None, track + + pickle.dump([tempo, events, hist_kr], open(cache_file, 'wb')) + spec = Spec(tempo, events, hist_kr, track, index) + return spec, None + + +def best_pitch_shift(kr_a, kr_b): + """find the best pitch shift between two chroma distributions.""" + deltas = [] + for p in range(12): + deltas.append((kr_a - kr_b).abs().mean()) + kr_b = kr_b.roll(1, 0) + + ps = np.argmin(deltas) + if ps > 6: + ps = ps - 12 + return ps + + +def align_stems(stems): + """Align the first beats of the stems. + This is a naive implementation. A grid with a time definition 10ms is defined and + each beat onset is represented as a gaussian over this grid. + Then, we try each possible time shift to make two grids align the best. + We repeat for all sources. + """ + sources = len(stems) + width = 5e-3 # grid of 10ms + limit = 5 + std = 2 + x = torch.arange(-limit, limit + 1, 1).float() + gauss = torch.exp(-x**2 / (2 * std**2)) + + grids = [] + for wav, onsets in stems: + le = wav.shape[-1] + dur = le / SR + grid = torch.zeros(int(le / width / SR)) + for onset in onsets: + pos = int(onset / width) + if onset >= dur - 1: + continue + if onset < 1: + continue + grid[pos - limit:pos + limit + 1] += gauss + grids.append(grid) + + shifts = [0] + for s in range(1, sources): + max_shift = int(4 / width) + dots = [] + for shift in range(-max_shift, max_shift): + other = grids[s] + ref = grids[0] + if shift >= 0: + other = other[shift:] + else: + ref = ref[shift:] + le = min(len(other), len(ref)) + dots.append((ref[:le].dot(other[:le]), int(shift * width * SR))) + + _, shift = max(dots) + shifts.append(-shift) + + outs = [] + new_zero = min(shifts) + for (wav, _), shift in zip(stems, shifts): + offset = shift - new_zero + wav = F.pad(wav, (offset, 0)) + outs.append(wav) + + le = min(x.shape[-1] for x in outs) + + outs = [w[..., :le] for w in outs] + return torch.stack(outs) + + +def find_candidate(spec_ref, catalog, pitch_match=True): + """Given reference track, this finds a track in the catalog that + is a potential match (pitch and tempo delta must be within the allowable limits). + """ + candidates = list(catalog) + random.shuffle(candidates) + + for spec in candidates: + ok = False + for scale in [1/4, 1/2, 1, 2, 4]: + tempo = spec.tempo * scale + delta_tempo = spec_ref.tempo / tempo - 1 + if abs(delta_tempo) < MAX_TEMPO: + ok = True + break + if not ok: + print(delta_tempo, spec_ref.tempo, spec.tempo, "FAILED TEMPO") + # too much of a tempo difference + continue + spec = spec._replace(tempo=tempo) + + ps = 0 + if pitch_match: + ps = best_pitch_shift(spec_ref.kr, spec.kr) + if abs(ps) > MAX_PITCH: + print("Failed pitch", ps) + # too much pitch difference + continue + return spec, delta_tempo, ps + + +def get_part(spec, source, dt, dp): + """Apply given delta of tempo and delta of pitch to a stem.""" + wav = spec.track[source] + if dt or dp: + wav = repitch(wav, dp, dt * 100, samplerate=SR, voice=source == 3) + spec = spec._replace(onsets=spec.onsets / (1 + dt)) + return wav, spec + + +def build_track(ref_index, catalog): + """Given the reference track index and a catalog of track, builds + a completely new track. One of the source at random from the ref track will + be kept and other sources will be drawn from the catalog. + """ + order = list(range(len(SOURCES))) + random.shuffle(order) + + stems = [None] * len(order) + indexes = [None] * len(order) + origs = [None] * len(order) + dps = [None] * len(order) + dts = [None] * len(order) + + first = order[0] + spec_ref = catalog[ref_index] + stems[first] = (spec_ref.track[first], spec_ref.onsets) + indexes[first] = ref_index + origs[first] = spec_ref.track[first] + dps[first] = 0 + dts[first] = 0 + + pitch_match = order != 0 + + for src in order[1:]: + spec, dt, dp = find_candidate(spec_ref, catalog, pitch_match=pitch_match) + if not pitch_match: + spec_ref = spec_ref._replace(kr=spec.kr) + pitch_match = True + dps[src] = dp + dts[src] = dt + wav, spec = get_part(spec, src, dt, dp) + stems[src] = (wav, spec.onsets) + indexes[src] = spec.index + origs.append(spec.track[src]) + print("FINAL CHOICES", ref_index, indexes, dps, dts) + stems = align_stems(stems) + return stems, origs + + +def get_musdb_dataset(part='train'): + root = Path(MUSDB_PATH) / part + ext = '.wav' + metadata = build_metadata(root, SOURCES, ext=ext, normalize=False) + valid_tracks = _get_musdb_valid() + metadata_train = {name: meta for name, meta in metadata.items() if name not in valid_tracks} + train_set = Wavset( + root, metadata_train, SOURCES, samplerate=SR, channels=CHANNELS, + normalize=False, ext=ext) + sig = hashlib.sha1(str(root).encode()).hexdigest()[:8] + train_set.sig = sig + return train_set + + +def get_wav_dataset(): + root = Path(EXTRA_WAV_PATH) + ext = '.wav' + metadata = _build_metadata(root, SOURCES, ext=ext, normalize=False) + train_set = Wavset( + root, metadata, SOURCES, samplerate=SR, channels=CHANNELS, + normalize=False, ext=ext) + sig = hashlib.sha1(str(root).encode()).hexdigest()[:8] + train_set.sig = sig + return train_set + + +def main(): + random.seed(4321) + if OUTPATH.exists(): + shutil.rmtree(OUTPATH) + OUTPATH.mkdir(exist_ok=True, parents=True) + (OUTPATH / 'train').mkdir(exist_ok=True, parents=True) + (OUTPATH / 'valid').mkdir(exist_ok=True, parents=True) + out = OUTPATH / 'train' + + dset = get_musdb_dataset() + # dset2 = get_wav_dataset() + # dset3 = get_musdb_dataset('test') + dset2 = None + dset3 = None + pendings = [] + copies = 6 + copies_rej = 2 + + with ProcessPoolExecutor(20) as pool: + for index in range(len(dset)): + pendings.append(pool.submit(analyse_track, dset, index)) + + if dset2: + for index in range(len(dset2)): + pendings.append(pool.submit(analyse_track, dset2, index)) + if dset3: + for index in range(len(dset3)): + pendings.append(pool.submit(analyse_track, dset3, index)) + + catalog = [] + rej = 0 + for pending in tqdm.tqdm(pendings, ncols=120): + spec, track = pending.result() + if spec is not None: + catalog.append(spec) + else: + mix = track.sum(0) + for copy in range(copies_rej): + folder = out / f'rej_{rej}_{copy}' + folder.mkdir() + save_audio(mix, folder / "mixture.wav", SR) + for stem, source in zip(track, SOURCES): + save_audio(stem, folder / f"{source}.wav", SR, clip='clamp') + rej += 1 + + for copy in range(copies): + for index in range(len(catalog)): + track, origs = build_track(index, catalog) + mix = track.sum(0) + mx = mix.abs().max() + scale = max(1, 1.01 * mx) + mix = mix / scale + track = track / scale + folder = out / f'{copy}_{index}' + folder.mkdir() + save_audio(mix, folder / "mixture.wav", SR) + for stem, source, orig in zip(track, SOURCES, origs): + save_audio(stem, folder / f"{source}.wav", SR, clip='clamp') + # save_audio(stem.std() * orig / (1e-6 + orig.std()), folder / f"{source}_orig.wav", + # SR, clip='clamp') + + +if __name__ == '__main__': + main() diff --git a/demucs/tools/bench.py b/demucs/tools/bench.py new file mode 100644 index 00000000..762a7c3f --- /dev/null +++ b/demucs/tools/bench.py @@ -0,0 +1,78 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +""" +benchmarking script, useful to check for OOM, reasonable train time, +and for the MDX competion, estimate if we will match the time limit.""" +from contextlib import contextmanager +import logging +import sys +import time +import torch + +from demucs.train import get_solver, main +from demucs.apply import apply_model + +logging.basicConfig(level=logging.INFO, stream=sys.stderr) + + +class Result: + pass + + +@contextmanager +def bench(): + import gc + gc.collect() + torch.cuda.reset_max_memory_allocated() + torch.cuda.empty_cache() + result = Result() + # before = torch.cuda.memory_allocated() + before = 0 + begin = time.time() + try: + yield result + finally: + torch.cuda.synchronize() + mem = (torch.cuda.max_memory_allocated() - before) / 2 ** 20 + tim = time.time() - begin + result.mem = mem + result.tim = tim + + +xp = main.get_xp_from_sig(sys.argv[1]) +xp = main.get_xp(xp.argv + sys.argv[2:]) +with xp.enter(): + solver = get_solver(xp.cfg) + if getattr(solver.model, 'use_train_segment', False): + batch = solver.augment(next(iter(solver.loaders['train']))) + solver.model.segment = Fraction(batch.shape[-1], solver.model.samplerate) + train_segment = solver.model.segment + solver.model.eval() + model = solver.model + model.cuda() + x = torch.randn(2, xp.cfg.dset.channels, int(10 * model.samplerate), device='cuda') + with bench() as res: + y = model(x) + y.sum().backward() + del y + for p in model.parameters(): + p.grad = None + print(f"FB: {res.mem:.1f} MB, {res.tim * 1000:.1f} ms") + + x = torch.randn(1, xp.cfg.dset.channels, int(model.segment * model.samplerate), device='cuda') + with bench() as res: + with torch.no_grad(): + y = model(x) + del y + print(f"FV: {res.mem:.1f} MB, {res.tim * 1000:.1f} ms") + + model.cpu() + torch.set_num_threads(1) + test = torch.randn(1, xp.cfg.dset.channels, model.samplerate * 40) + b = time.time() + apply_model(model, test, split=True, shifts=1) + print("CPU 40 sec:", time.time() - b) diff --git a/demucs/tools/convert.py b/demucs/tools/convert.py new file mode 100644 index 00000000..dfc022f8 --- /dev/null +++ b/demucs/tools/convert.py @@ -0,0 +1,152 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +# Script to convert option names and model args from the dev branch to +# the cleanup release one. There should be no reaso to use that anymore. + +import argparse +import io +import json +from pathlib import Path +import subprocess as sp + +import torch + +from demucs import train, pretrained, states + +DEV_REPO = Path.home() / 'tmp/release_demucs_mdx' + + +TO_REMOVE = [ + 'demucs.dconv_kw.gelu=True', + 'demucs.dconv_kw.nfreqs=0', + 'demucs.dconv_kw.nfreqs=0', + 'demucs.dconv_kw.version=4', + 'demucs.norm=gn', + 'wdemucs.nice=True', + 'wdemucs.good=True', + 'wdemucs.freq_emb=-0.2', + 'special=True', + 'special=False', +] + +TO_REPLACE = [ + ('power', 'svd'), + ('wdemucs', 'hdemucs'), + ('hdemucs.hybrid=True', 'hdemucs.hybrid_old=True'), + ('hdemucs.hybrid=2', 'hdemucs.hybrid=True'), +] + +TO_INJECT = [ + ('model=hdemucs', ['hdemucs.cac=False']), + ('model=hdemucs', ['hdemucs.norm_starts=999']), +] + + +def get_original_argv(sig): + return json.load(open(Path(DEV_REPO) / f'outputs/xps/{sig}/.argv.json')) + + +def transform(argv, mappings, verbose=False): + for rm in TO_REMOVE: + while rm in argv: + argv.remove(rm) + + for old, new in TO_REPLACE: + argv[:] = [a.replace(old, new) for a in argv] + + for condition, args in TO_INJECT: + if condition in argv: + argv[:] = args + argv + + for idx, arg in enumerate(argv): + if 'continue_from=' in arg: + dep_sig = arg.split('=')[1] + if dep_sig.startswith('"'): + dep_sig = eval(dep_sig) + if verbose: + print("Need to recursively convert dependency XP", dep_sig) + new_sig = convert(dep_sig, mappings, verbose).sig + argv[idx] = f'continue_from="{new_sig}"' + + +def convert(sig, mappings, verbose=False): + argv = get_original_argv(sig) + if verbose: + print("Original argv", argv) + transform(argv, mappings, verbose) + if verbose: + print("New argv", argv) + xp = train.main.get_xp(argv) + train.main.init_xp(xp) + if verbose: + print("Mapping", sig, "->", xp.sig) + mappings[sig] = xp.sig + return xp + + +def _eval_old(old_sig, x): + script = ( + 'from demucs import pretrained; import torch; import sys; import io; ' + 'buf = io.BytesIO(sys.stdin.buffer.read()); ' + 'x = torch.load(buf); m = pretrained.load_pretrained_model(' + f'"{old_sig}"); torch.save(m(x), sys.stdout.buffer)') + + buf = io.BytesIO() + torch.save(x, buf) + proc = sp.run( + ['python3', '-c', script], input=buf.getvalue(), capture_output=True, cwd=DEV_REPO) + if proc.returncode != 0: + print("Error", proc.stderr.decode()) + assert False + + buf = io.BytesIO(proc.stdout) + return torch.load(buf) + + +def compare(old_sig, model): + test = torch.randn(1, 2, 44100 * 10) + old_out = _eval_old(old_sig, test) + out = model(test) + + delta = 20 * torch.log10((out - old_out).norm() / out.norm()).item() + return delta + + +def main(): + torch.manual_seed(1234) + parser = argparse.ArgumentParser('convert') + parser.add_argument('sigs', nargs='*') + parser.add_argument('-o', '--output', type=Path, default=Path('release_models')) + parser.add_argument('-d', '--dump', action='store_true') + parser.add_argument('-c', '--compare', action='store_true') + parser.add_argument('-v', '--verbose', action='store_true') + args = parser.parse_args() + + args.output.mkdir(exist_ok=True, parents=True) + mappings = {} + for sig in args.sigs: + xp = convert(sig, mappings, args.verbose) + if args.dump or args.compare: + old_pkg = pretrained._load_package(sig, old=True) + model = train.get_model(xp.cfg) + model.load_state_dict(old_pkg['state']) + if args.dump: + pkg = states.serialize_model(model, xp.cfg) + states.save_with_checksum(pkg, args.output / f'{xp.sig}.th') + if args.compare: + delta = compare(sig, model) + print("Delta for", sig, xp.sig, delta) + + mappings[sig] = xp.sig + + print("FINAL MAPPINGS") + for old, new in mappings.items(): + print(old, " ", new) + + +if __name__ == '__main__': + main() diff --git a/demucs/tools/export.py b/demucs/tools/export.py new file mode 100644 index 00000000..15795855 --- /dev/null +++ b/demucs/tools/export.py @@ -0,0 +1,71 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +"""Export a trained model from the full checkpoint (with optimizer etc.) to +a final checkpoint, with only the model itself. The model is always stored as +half float to gain space, and because this has zero impact on the final loss. +When DiffQ was used for training, the model will actually be quantized and bitpacked.""" +from argparse import ArgumentParser +from fractions import Fraction +import logging +from pathlib import Path +import sys +import torch + +from demucs import train +from demucs.states import serialize_model, save_with_checksum + + +logger = logging.getLogger(__name__) + + +def main(): + logging.basicConfig(level=logging.INFO, stream=sys.stderr) + + parser = ArgumentParser("tools.export", description="Export trained models from XP sigs.") + parser.add_argument('signatures', nargs='*', help='XP signatures.') + parser.add_argument('-o', '--out', type=Path, default=Path("release_models"), + help="Path where to store release models (default release_models)") + parser.add_argument('-s', '--sign', action='store_true', + help='Add sha256 prefix checksum to the filename.') + + args = parser.parse_args() + args.out.mkdir(exist_ok=True, parents=True) + + for sig in args.signatures: + xp = train.main.get_xp_from_sig(sig) + name = train.main.get_name(xp) + logger.info('Handling %s/%s', sig, name) + + out_path = args.out / (sig + ".th") + + solver = train.get_solver_from_sig(sig) + if len(solver.history) < solver.args.epochs: + logger.warning( + 'Model %s has less epoch than expected (%d / %d)', + sig, len(solver.history), solver.args.epochs) + + solver.model.load_state_dict(solver.best_state) + pkg = serialize_model(solver.model, solver.args, solver.quantizer, half=True) + if getattr(solver.model, 'use_train_segment', False): + batch = solver.augment(next(iter(solver.loaders['train']))) + pkg['kwargs']['segment'] = Fraction(batch.shape[-1], solver.model.samplerate) + print("Override", pkg['kwargs']['segment']) + valid, test = None, None + for m in solver.history: + if 'valid' in m: + valid = m['valid'] + if 'test' in m: + test = m['test'] + pkg['metrics'] = (valid, test) + if args.sign: + save_with_checksum(pkg, out_path) + else: + torch.save(pkg, out_path) + + +if __name__ == '__main__': + main() diff --git a/demucs/tools/test_pretrained.py b/demucs/tools/test_pretrained.py new file mode 100644 index 00000000..fb80cf5a --- /dev/null +++ b/demucs/tools/test_pretrained.py @@ -0,0 +1,43 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +# Script to evaluate pretrained models. + +from argparse import ArgumentParser +import logging +import sys + +import torch + +from demucs import train, pretrained, evaluate + + +def main(): + torch.set_num_threads(1) + logging.basicConfig(stream=sys.stderr, level=logging.INFO) + parser = ArgumentParser("tools.test_pretrained", + description="Evaluate pre-trained models or bags of models " + "on MusDB.") + pretrained.add_model_flags(parser) + parser.add_argument('overrides', nargs='*', + help='Extra overrides, e.g. test.shifts=2.') + args = parser.parse_args() + + xp = train.main.get_xp(args.overrides) + with xp.enter(): + solver = train.get_solver(xp.cfg) + + model = pretrained.get_model_from_args(args) + solver.model = model.to(solver.device) + solver.model.eval() + + with torch.no_grad(): + results = evaluate.evaluate(solver, xp.cfg.test.sdr) + print(results) + + +if __name__ == '__main__': + main() diff --git a/docs/docs/preferences.md b/docs/docs/preferences.md index 9ef6dfb1..1e48e9de 100644 --- a/docs/docs/preferences.md +++ b/docs/docs/preferences.md @@ -100,7 +100,7 @@ combined to produce the final answer. **BUZZ_TRANSLATION_API_KEY** - Api key of OpenAI compatible API to use for translation. -**BUZZ_MODEL_ROOT** - Root directory to store model files. +**BUZZ_MODEL_ROOT** - Root directory to store model files. You may also want to set `HF_HOME` to the same folder as some libraries used in Buzz download their models independently. Defaults to [user_cache_dir](https://pypi.org/project/platformdirs/). **BUZZ_FAVORITE_LANGUAGES** - Coma separated list of supported language codes to show on top of language list. diff --git a/docs/docs/usage/1_file_import.md b/docs/docs/usage/1_file_import.md index 91ccc672..a811fddc 100644 --- a/docs/docs/usage/1_file_import.md +++ b/docs/docs/usage/1_file_import.md @@ -25,3 +25,5 @@ To reduce misspellings you can pass some commonly misspelled words in an `Initia (See the [Live Recording section](https://chidiwilliams.github.io/buzz/docs/usage/live_recording) for more information about the task, language, and quality settings.) [![Media File Import on Buzz](https://cdn.loom.com/sessions/thumbnails/cf263b099ac3481082bb56d19b7c87fe-with-play.gif)](https://www.loom.com/share/cf263b099ac3481082bb56d19b7c87fe "Media File Import on Buzz") + +**💡 Tip:** It is recommended to always select language to transcribe to as automatic language detection may result in unexpected results. diff --git a/docs/docs/usage/5_speaker_identification.md b/docs/docs/usage/5_speaker_identification.md new file mode 100644 index 00000000..72dc7ee6 --- /dev/null +++ b/docs/docs/usage/5_speaker_identification.md @@ -0,0 +1,9 @@ +--- +title: Speaker identification +--- + +When transcript of some audio or video file is generated you can identify speakers in the transcript. Double-click the transcript in the list of transcripts to see additional options for editing and exporting. + +Transcription view screen has option to identify speakers. Click on the "Identify speakers" button so see available options. + +If audio file is still present on the system speaker identification will mark each speakers sentences with appropriate label. You can preview 10 seconds of some random sentence of the identified speaker and rename the automatically identified label to speakers real name. If "Merge speaker sentences" checkbox is selected when you save the speaker labels, all consecutive sentences of the same speaker will be merged into one segment. Speaker identification is available since version 1.4.0 on all platforms except Intel macOS. \ No newline at end of file diff --git a/flatpak/run-buzz.sh b/flatpak/run-buzz.sh index fa136c3b..f32217ec 100644 --- a/flatpak/run-buzz.sh +++ b/flatpak/run-buzz.sh @@ -1,3 +1,5 @@ #!/bin/sh echo "Running buzz..." +echo "Note: ffmpeg errors are safe to ignore" + python -m buzz \ No newline at end of file diff --git a/hatch_build.py b/hatch_build.py index b3469d36..d95e4c66 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -79,6 +79,21 @@ class CustomBuildHook(BuildHookInterface): print(result.stderr, file=sys.stderr) print("Successfully compiled translation files") + # Build ctc_forced_aligner C++ extension in-place + print("Building ctc_forced_aligner C++ extension...") + ctc_aligner_dir = project_root / "ctc_forced_aligner" + result = subprocess.run( + [sys.executable, "setup.py", "build_ext", "--inplace"], + cwd=ctc_aligner_dir, + check=True, + capture_output=True, + text=True + ) + print(result.stdout) + if result.stderr: + print(result.stderr, file=sys.stderr) + print("Successfully built ctc_forced_aligner C++ extension") + # Force include all files in buzz/whisper_cpp directory whisper_cpp_dir = project_root / "buzz" / "whisper_cpp" if whisper_cpp_dir.exists(): @@ -142,6 +157,28 @@ class CustomBuildHook(BuildHookInterface): else: print(f"Warning: {locale_dir} does not exist", file=sys.stderr) + # Force include compiled extensions from ctc_forced_aligner + ctc_aligner_pkg = project_root / "ctc_forced_aligner" / "ctc_forced_aligner" + if ctc_aligner_pkg.exists(): + # Get all compiled extension files (.so, .pyd, .dll) + extension_patterns = ["*.so", "*.pyd", "*.dll"] + extension_files = [] + for pattern in extension_patterns: + extension_files.extend(glob.glob(str(ctc_aligner_pkg / pattern))) + + # Add them to force_include + if 'force_include' not in build_data: + build_data['force_include'] = {} + + for file_path in extension_files: + # Convert to relative path from project root + rel_path = Path(file_path).relative_to(project_root) + build_data['force_include'][str(rel_path)] = str(rel_path) + + print(f"Force including {len(extension_files)} compiled extension(s) from ctc_forced_aligner/") + else: + print(f"Warning: {ctc_aligner_pkg} does not exist", file=sys.stderr) + except subprocess.CalledProcessError as e: print(f"Error building whisper.cpp: {e}", file=sys.stderr) print(f"stdout: {e.stdout}", file=sys.stderr) diff --git a/pyproject.toml b/pyproject.toml index 094ffccd..90c8ce6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [project] name = "buzz-captions" -version = "1.3.4" +# Change also in Makefile and buzz/__version__.py +version = "1.4.0" description = "" authors = [{ name = "Chidi Williams", email = "williamschidi1@gmail.com" }] requires-python = ">=3.12,<3.13" @@ -62,6 +63,11 @@ dependencies = [ "hf-xet>=1.1.5,<2", "hatchling>=1.27.0", "cmake>=3.31.6", + "nemo-toolkit[asr]>=2.5.3; sys_platform != 'darwin' or platform_machine != 'x86_64'", + "nltk>=3.9.2", + "uroman>=1.3.1.1", + "lhotse==1.31.1", + "coverage==7.6.1", ] repository = "https://github.com/chidiwilliams/buzz" documentation = "https://chidiwilliams.github.io/buzz/docs" @@ -131,6 +137,9 @@ include = [ "buzz/whisper_cpp/*", "buzz/locale/*/LC_MESSAGES/buzz.mo", "demucs", + "whisper_diarization", + "deepmultilingualpunctuation", + "ctc_forced_aligner", ] [tool.hatch.build.targets.wheel] @@ -139,12 +148,15 @@ include = [ "buzz/whisper_cpp/*", "buzz/locale/*/LC_MESSAGES/buzz.mo", "demucs", + "whisper_diarization", + "deepmultilingualpunctuation", + "ctc_forced_aligner", ] [tool.hatch.build.hooks.custom] [build-system] -requires = ["hatchling", "cmake>=3.26.4,<4", "polib>=1.2.0,<2"] +requires = ["hatchling", "cmake>=3.26.4,<4", "polib>=1.2.0,<2", "pybind11", "setuptools>=42"] build-backend = "hatchling.build" [tool.ruff] diff --git a/pytest.ini b/pytest.ini index abd57212..36fdeb2a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,5 +7,6 @@ log_date_format = %Y-%m-%d %H:%M:%S addopts = -x -p no:xdist -p no:pytest_parallel timeout = 600 timeout_method = thread +testpaths = tests markers = timeout: set a timeout on a test function. \ No newline at end of file diff --git a/share/applications/buzz.desktop b/share/applications/buzz.desktop new file mode 100644 index 00000000..1e8cf81d --- /dev/null +++ b/share/applications/buzz.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] + +Type=Application + +Encoding=UTF-8 + +Name=Buzz + +Comment=Buzz transcribes and translates audio offline on your personal computer. + +Path=/opt/buzz + +Exec=/opt/buzz/Buzz + +Icon=buzz + +Terminal=false diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 53cc91db..b574b1ae 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -80,15 +80,10 @@ parts: - libportaudio2 - libpulse0 - libasound2 + - libasound2-dev - libasound2-plugins - libasound2-plugins-extra - libyaml-dev - - gstreamer1.0-plugins-good - - gstreamer1.0-plugins-base-apps - - gstreamer1.0-pulseaudio - - libgstreamer1.0-0 - - libgstreamer-plugins-base1.0-0 - - libgstreamer-plugins-good1.0-0 - liboss4-salsa2 # Display - libxkbcommon-x11-0 @@ -121,7 +116,10 @@ parts: # Copy source files cp -r $CRAFT_PART_BUILD/buzz $CRAFT_PART_INSTALL/ + cp -r $CRAFT_PART_BUILD/ctc_forced_aligner $CRAFT_PART_INSTALL/ + cp -r $CRAFT_PART_BUILD/deepmultilingualpunctuation $CRAFT_PART_INSTALL/ cp -r $CRAFT_PART_BUILD/demucs $CRAFT_PART_INSTALL/ + cp -r $CRAFT_PART_BUILD/whisper_diarization $CRAFT_PART_INSTALL/ # Create desktop file mkdir -p $CRAFT_PART_INSTALL/usr/share/applications @@ -155,7 +153,7 @@ apps: PATH: $SNAP/usr/bin:$SNAP/bin:$PATH LD_LIBRARY_PATH: $SNAP/lib/python3.12/site-packages/nvidia/cudnn/lib:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/lapack:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/blas:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/oss4-libsalsa:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$SNAP:$LD_LIBRARY_PATH PYTHONPATH: $SNAP:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/python3/dist-packages:$SNAP/usr/lib/python3.12/site-packages:$SNAP/usr/local/lib/python3.12/dist-packages:$SNAP/usr/lib/python3.12/dist-packages:$PYTHONPATH - QT_MEDIA_BACKEND: gstreamer + QT_MEDIA_BACKEND: ffmpeg PULSE_LATENCY_MSEC: "30" ALSA_CONFIG_PATH: $SNAP/etc/asound.conf plugs: diff --git a/tests/gui_test.py b/tests/gui_test.py index e4ebfb9a..a5e7fda6 100644 --- a/tests/gui_test.py +++ b/tests/gui_test.py @@ -34,7 +34,10 @@ from tests.mock_sounddevice import MockInputStream, mock_query_devices from .mock_qt import MockNetworkAccessManager, MockNetworkReply if platform.system() == "Linux": - multiprocessing.set_start_method("spawn") + try: + multiprocessing.set_start_method("spawn", force=True) + except RuntimeError: + pass @pytest.fixture(scope="module", autouse=True) diff --git a/tests/transcriber/file_transcriber_queue_worker_test.py b/tests/transcriber/file_transcriber_queue_worker_test.py index e48bccb9..da351ee2 100644 --- a/tests/transcriber/file_transcriber_queue_worker_test.py +++ b/tests/transcriber/file_transcriber_queue_worker_test.py @@ -70,10 +70,10 @@ def test_transcription_with_whisper_cpp_tiny_with_speech_extraction(worker): task = FileTranscriptionTask(file_path=str(test_multibyte_utf8_audio_path), transcription_options=options, file_transcription_options=FileTranscriptionOptions(), model_path="mock_path") - with unittest.mock.patch('demucs.api.Separator') as mock_separator_class, \ - unittest.mock.patch('demucs.api.save_audio') as mock_save_audio, \ + with unittest.mock.patch('demucs.demucs.api.Separator') as mock_separator_class, \ + unittest.mock.patch('demucs.demucs.api.save_audio') as mock_save_audio, \ unittest.mock.patch.object(WhisperFileTranscriber, 'run') as mock_run: - # Mock demucs.api.Separator and save_audio + # Mock demucs.demucs.api.Separator and save_audio mock_separator_instance = unittest.mock.Mock() mock_separator_instance.separate_audio_file.return_value = (None, {"vocals": "mock_vocals_data"}) mock_separator_instance.samplerate = 44100 diff --git a/tests/transcriber/whisper_cpp_test.py b/tests/transcriber/whisper_cpp_test.py index 722db718..203421fc 100644 --- a/tests/transcriber/whisper_cpp_test.py +++ b/tests/transcriber/whisper_cpp_test.py @@ -58,5 +58,5 @@ class TestWhisperCpp: segments = WhisperCpp.transcribe(task=task) assert "Mani" in segments[0].text - assert "uzstrau" in segments[1].text + assert "uzstrau" or "ustrau" in segments[1].text assert "laikabstāk" in segments[2].text \ No newline at end of file diff --git a/tests/widgets/speaker_identification_widget_test.py b/tests/widgets/speaker_identification_widget_test.py new file mode 100644 index 00000000..946948dc --- /dev/null +++ b/tests/widgets/speaker_identification_widget_test.py @@ -0,0 +1,90 @@ +import logging +import platform +import time +import uuid +import pytest +from pytestqt.qtbot import QtBot +from unittest.mock import MagicMock, patch +from buzz.db.entity.transcription import Transcription +from buzz.db.entity.transcription_segment import TranscriptionSegment +from buzz.model_loader import ModelType, WhisperModelSize +from buzz.transcriber.transcriber import Task +# Underlying libs do not support intel Macs +if not (platform.system() == "Darwin" and platform.machine() == "x86_64"): + from buzz.widgets.transcription_viewer.speaker_identification_widget import ( + SpeakerIdentificationWidget, + IdentificationWorker, + ) +from tests.audio import test_audio_path + +@pytest.mark.skipif( + platform.system() == "Darwin" and platform.machine() == "x86_64", + reason="Skip speaker identification tests on macOS x86_64" +) +class TestSpeakerIdentificationWidget: + @pytest.fixture() + def transcription( + self, transcription_dao, transcription_segment_dao + ) -> Transcription: + id = uuid.uuid4() + transcription_dao.insert( + Transcription( + id=str(id), + status="completed", + file=test_audio_path, + task=Task.TRANSCRIBE.value, + model_type=ModelType.WHISPER.value, + whisper_model_size=WhisperModelSize.SMALL.value, + ) + ) + transcription_segment_dao.insert(TranscriptionSegment(40, 299, "Bien", "", str(id))) + transcription_segment_dao.insert( + TranscriptionSegment(299, 329, "venue dans", "", str(id)) + ) + + return transcription_dao.find_by_id(str(id)) + + def test_widget_initialization(self, qtbot: QtBot, transcription, transcription_service): + """Test the initialization of SpeakerIdentificationWidget.""" + widget = SpeakerIdentificationWidget( + transcription=transcription, + transcription_service=transcription_service, + ) + qtbot.addWidget(widget) + + assert widget.transcription == transcription + assert widget.transcription_service == transcription_service + assert widget.progress_bar.value() == 0 + + widget.close() + + # Wait to clean-up threads + time.sleep(3) + + @pytest.mark.skipif( + platform.system() == "Linux", + reason="Skip speaker identification worker test on Linux, CI freezes" + ) + @patch("buzz.widgets.transcription_viewer.speaker_identification_widget.IdentificationWorker") + def test_identification_worker_run(self, qtbot: QtBot, transcription, transcription_service): + """Test the IdentificationWorker's run method and capture the finished signal result.""" + worker = IdentificationWorker( + transcription=transcription, + transcription_service=transcription_service, + ) + + result = [] + + def capture_result(data): + result.append(data) + + worker.finished.connect(capture_result) + + with qtbot.waitSignal(worker.finished, timeout= 300000): #5 min timeout + worker.run() + + assert worker.transcription == transcription + assert len(result) == 1 + assert isinstance(result[0], list) + assert result == [[{'end_time': 8904, 'speaker': 'Speaker 0', 'start_time': 140, 'text': 'Bienvenue dans. '}]] + diff --git a/tests/widgets/transcription_viewer_test.py b/tests/widgets/transcription_viewer_test.py index 13d87bc8..a688c40b 100644 --- a/tests/widgets/transcription_viewer_test.py +++ b/tests/widgets/transcription_viewer_test.py @@ -202,9 +202,7 @@ class TestTranscriptionViewerWidget: return_value=mock_result) as mock_transcribe_any, \ patch( 'buzz.widgets.transcription_viewer.transcription_resizer_widget.whisper_audio.load_audio') as mock_load_audio: - result_ready_spy = MagicMock() finished_spy = MagicMock() - worker.result_ready.connect(result_ready_spy) worker.finished.connect(finished_spy) worker.run() @@ -220,15 +218,13 @@ class TestTranscriptionViewerWidget: assert call_kwargs['vad'] is False assert call_kwargs['suppress_silence'] is False - result_ready_spy.assert_called_once() - emitted_segments = result_ready_spy.call_args[0][0] + finished_spy.assert_called_once() + emitted_segments = finished_spy.call_args[0][0] assert len(emitted_segments) == 1 assert emitted_segments[0].start == 100 assert emitted_segments[0].end == 200 assert emitted_segments[0].text == "Hello" - finished_spy.assert_called_once() - # TODO - Fix this test on Windows, should work. # Possibly the `on_loop_toggle_changed` gets triggered on setChecked @pytest.mark.skipif(sys.platform.startswith("win"), reason="Skipping on Windows") diff --git a/uv.lock b/uv.lock index 0c326aa8..644e35b9 100644 --- a/uv.lock +++ b/uv.lock @@ -5,10 +5,20 @@ resolution-markers = [ "platform_machine == 'x86_64' and sys_platform == 'darwin'", "platform_machine == 'arm64' and sys_platform == 'darwin'", "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", + "sys_platform != 'darwin' and sys_platform != 'linux'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", ] +[[package]] +name = "absl-py" +version = "2.3.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, +] + [[package]] name = "accelerate" version = "1.11.0" @@ -29,6 +39,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/85/85951bc0f9843e2c10baaa1b6657227056095de08f4d1eea7d8b423a6832/accelerate-1.11.0-py3-none-any.whl", hash = "sha256:a628fa6beb069b8e549460fc449135d5bd8d73e7a11fd09f0bc9fc4ace7f06f1", size = 375777, upload-time = "2025-10-20T14:42:23.256Z" }, ] +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "aiohappyeyeballs", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "aiosignal", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "attrs", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "frozenlist", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "multidict", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "propcache", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "yarl", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, + { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, + { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "frozenlist", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "alembic" +version = "1.17.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "mako", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sqlalchemy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/a6/74c8cadc2882977d80ad756a13857857dbcf9bd405bc80b662eb10651282/alembic-1.17.2.tar.gz", hash = "sha256:bbe9751705c5e0f14877f02d46c53d10885e377e3d90eda810a016f9baa19e8e", size = 1988064, upload-time = "2025-11-14T20:35:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/88/6237e97e3385b57b5f1528647addea5cc03d4d65d5979ab24327d41fb00d/alembic-1.17.2-py3-none-any.whl", hash = "sha256:f483dd1fe93f6c5d49217055e4d15b905b425b6af906746abb35b69c1996c4e6", size = 248554, upload-time = "2025-11-14T20:35:05.699Z" }, +] + [[package]] name = "altgraph" version = "0.17.4" @@ -80,6 +160,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/b6/c0b5394ec6149e0129421f1a762b805e0e583974bc3cd65e3c7ce7c95444/astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c", size = 278329, upload-time = "2023-09-26T12:40:25.988Z" }, ] +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + [[package]] name = "attrs" version = "25.4.0" @@ -89,6 +178,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] +[[package]] +name = "audioread" +version = "3.1.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/4a/874ecf9b472f998130c2b5e145dcdb9f6131e84786111489103b66772143/audioread-3.1.0.tar.gz", hash = "sha256:1c4ab2f2972764c896a8ac61ac53e261c8d29f0c6ccd652f84e18f08a4cab190", size = 20082, upload-time = "2025-10-26T19:44:13.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/16/fbe8e1e185a45042f7cd3a282def5bb8d95bb69ab9e9ef6a5368aa17e426/audioread-3.1.0-py3-none-any.whl", hash = "sha256:b30d1df6c5d3de5dcef0fb0e256f6ea17bdcf5f979408df0297d8a408e2971b4", size = 23143, upload-time = "2025-10-26T19:44:12.016Z" }, +] + [[package]] name = "autopep8" version = "1.7.0" @@ -126,13 +224,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, ] +[[package]] +name = "bitsandbytes" +version = "0.46.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/27/ec6ee3408e09e01ab05db07af5a97dc76db7bc18824cf5f5dbc98e1e08a4/bitsandbytes-0.46.0-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:ef38883cfd26f36a0dfff1715f620f87cee3813431f33e10e9658205160cb89b", size = 67047276, upload-time = "2025-05-27T21:25:31.299Z" }, + { url = "https://files.pythonhosted.org/packages/f3/06/2ef5f6b28d8fa442c670b5acc1eb09dd57d4edb00b435b35529c3f09936c/bitsandbytes-0.46.0-py3-none-win_amd64.whl", hash = "sha256:121820a6df80ae3b7e361f7ef193279c3204c361a7e21eb43b5ffa7293403979", size = 66452401, upload-time = "2025-05-27T21:25:35.552Z" }, +] + +[[package]] +name = "braceexpand" +version = "0.1.7" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/54/93/badd4f5ccf25209f3fef2573073da9fe4a45a3da99fca2f800f942130c0f/braceexpand-0.1.7.tar.gz", hash = "sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705", size = 7777, upload-time = "2021-05-07T13:49:07.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/93/e8c04e80e82391a6e51f218ca49720f64236bc824e92152a2633b74cf7ab/braceexpand-0.1.7-py2.py3-none-any.whl", hash = "sha256:91332d53de7828103dcae5773fb43bc34950b0c8160e35e0f44c4427a3b85014", size = 5923, upload-time = "2021-05-07T13:49:05.146Z" }, +] + [[package]] name = "buzz-captions" -version = "1.3.3" +version = "1.4.0" source = { editable = "." } dependencies = [ { name = "accelerate" }, { name = "cmake" }, + { name = "coverage" }, { name = "ctranslate2", version = "4.3.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, { name = "ctranslate2", version = "4.6.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' or sys_platform != 'darwin'" }, { name = "darkdetect" }, @@ -150,8 +271,11 @@ dependencies = [ { name = "julius" }, { name = "keyring" }, { name = "lameenc" }, + { name = "lhotse" }, { name = "museval" }, { name = "mypy" }, + { name = "nemo-toolkit", extra = ["asr"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "nltk" }, { name = "numpy" }, { name = "onnxruntime" }, { name = "openai" }, @@ -181,6 +305,7 @@ dependencies = [ { name = "transformers" }, { name = "treetable" }, { name = "urllib3" }, + { name = "uroman" }, { name = "vulkan" }, { name = "yt-dlp" }, ] @@ -212,6 +337,7 @@ dev = [ requires-dist = [ { name = "accelerate", specifier = ">=1.0.1,<2" }, { name = "cmake", specifier = ">=3.31.6" }, + { name = "coverage", specifier = "==7.6.1" }, { name = "ctranslate2", marker = "sys_platform != 'darwin'", specifier = ">=4.6.0,<5" }, { name = "ctranslate2", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = ">=4.6.0,<5" }, { name = "ctranslate2", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==4.3.1" }, @@ -230,8 +356,11 @@ requires-dist = [ { name = "julius", specifier = ">=0.2.7,<0.3" }, { name = "keyring", specifier = ">=25.0.0,<26" }, { name = "lameenc", specifier = ">=1.8.1,<2" }, + { name = "lhotse", specifier = "==1.31.1" }, { name = "museval", specifier = ">=0.4.1,<0.5" }, { name = "mypy", specifier = ">=1.15.0,<2" }, + { name = "nemo-toolkit", extras = ["asr"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = ">=2.5.3" }, + { name = "nltk", specifier = ">=3.9.2" }, { name = "numpy", specifier = ">=1.21.2,<2" }, { name = "onnxruntime", specifier = "==1.18.1" }, { name = "openai", specifier = ">=1.14.2,<2" }, @@ -260,6 +389,7 @@ requires-dist = [ { name = "transformers", specifier = ">=4.49.0,<5" }, { name = "treetable", specifier = ">=0.2.5,<0.3" }, { name = "urllib3", specifier = ">=2.3.0,<3" }, + { name = "uroman", specifier = ">=1.3.1.1" }, { name = "vulkan", specifier = ">=1.3.275.1,<2" }, { name = "yt-dlp", specifier = ">=2025.2.19,<2026" }, ] @@ -353,6 +483,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + [[package]] name = "cloudpickle" version = "3.1.1" @@ -421,25 +563,43 @@ wheels = [ ] [[package]] -name = "coverage" -version = "7.11.0" +name = "contourpy" +version = "1.3.3" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" } +dependencies = [ + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" }, - { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" }, - { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" }, - { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" }, - { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" }, - { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" }, - { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" }, - { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" }, - { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" }, - { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" }, - { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, ] [[package]] @@ -447,7 +607,7 @@ name = "cryptography" version = "46.0.3" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'darwin'" }, + { name = "cffi", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } wheels = [ @@ -475,6 +635,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, ] +[[package]] +name = "ctc-segmentation" +version = "1.7.4" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "cython", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ed/78dd82e0b022ed4cb047188c0b8cead62fa0c27700737fd34e3263d83882/ctc_segmentation-1.7.4.tar.gz", hash = "sha256:19d383ea5f22438ebb1699d72b22078b63f351a33fa50bedb19c14077ba6a116", size = 73584, upload-time = "2022-10-11T19:25:00.575Z" } + [[package]] name = "ctranslate2" version = "4.3.1" @@ -498,7 +669,8 @@ source = { registry = "https://pypi.org/simple/" } resolution-markers = [ "platform_machine == 'arm64' and sys_platform == 'darwin'", "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", + "sys_platform != 'darwin' and sys_platform != 'linux'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", ] dependencies = [ @@ -523,6 +695,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/92/f344ba59f3aeb3bb37d05c445229b2a8c25d7bfa61e2759cde7f14a64d9a/ctypesgen-1.1.1-py3-none-any.whl", hash = "sha256:94cc6c89ccdd93a72a4c915266cde9a82bfe693331d9d880f66fe9d82af1fc87", size = 124193, upload-time = "2022-10-19T07:00:53.227Z" }, ] +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + [[package]] name = "cython" version = "3.1.6" @@ -542,6 +723,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/d5/7a04640bf559bb890455ffb28978daf7d44f667c3f04a4d422c655c1ba92/cython-3.1.6-py3-none-any.whl", hash = "sha256:91dcf7eb9b6a089ce4e9e1140e571d84c3bca834afb77ec269be7aa9d31a8157", size = 1223550, upload-time = "2025-10-23T12:38:16.732Z" }, ] +[[package]] +name = "cytoolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/d4/16916f3dc20a3f5455b63c35dcb260b3716f59ce27a93586804e70e431d5/cytoolz-1.1.0.tar.gz", hash = "sha256:13a7bf254c3c0d28b12e2290b82aed0f0977a4c2a2bf84854fcdc7796a29f3b0", size = 642510, upload-time = "2025-10-19T00:44:56.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ec/01426224f7acf60183d3921b25e1a8e71713d3d39cb464d64ac7aace6ea6/cytoolz-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99f8e134c9be11649342853ec8c90837af4089fc8ff1e8f9a024a57d1fa08514", size = 1327800, upload-time = "2025-10-19T00:40:48.674Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/e07e8fedd332ac9626ad58bea31416dda19bfd14310731fa38b16a97e15f/cytoolz-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a6f44cf9319c30feb9a50aa513d777ef51efec16f31c404409e7deb8063df64", size = 997118, upload-time = "2025-10-19T00:40:50.919Z" }, + { url = "https://files.pythonhosted.org/packages/ab/72/c0f766d63ed2f9ea8dc8e1628d385d99b41fb834ce17ac3669e3f91e115d/cytoolz-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:945580dc158c557172fca899a35a99a16fbcebf6db0c77cb6621084bc82189f9", size = 991169, upload-time = "2025-10-19T00:40:52.887Z" }, + { url = "https://files.pythonhosted.org/packages/df/4b/1f757353d1bf33e56a7391ecc9bc49c1e529803b93a9d2f67fe5f92906fe/cytoolz-1.1.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:257905ec050d04f2f856854620d1e25556fd735064cebd81b460f54939b9f9d5", size = 2700680, upload-time = "2025-10-19T00:40:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/25/73/9b25bb7ed8d419b9d6ff2ae0b3d06694de79a3f98f5169a1293ff7ad3a3f/cytoolz-1.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82779049f352fb3ab5e8c993ab45edbb6e02efb1f17f0b50f4972c706cc51d76", size = 2824951, upload-time = "2025-10-19T00:40:56.137Z" }, + { url = "https://files.pythonhosted.org/packages/0c/93/9c787f7c909e75670fff467f2504725d06d8c3f51d6dfe22c55a08c8ccd4/cytoolz-1.1.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d3e405e435320e08c5a1633afaf285a392e2d9cef35c925d91e2a31dfd7a688", size = 2679635, upload-time = "2025-10-19T00:40:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/9ee92c302cccf7a41a7311b325b51ebeff25d36c1f82bdc1bbe3f58dc947/cytoolz-1.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:923df8f5591e0d20543060c29909c149ab1963a7267037b39eee03a83dbc50a8", size = 2938352, upload-time = "2025-10-19T00:40:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a3/3b58c5c1692c3bacd65640d0d5c7267a7ebb76204f7507aec29de7063d2f/cytoolz-1.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:25db9e4862f22ea0ae2e56c8bec9fc9fd756b655ae13e8c7b5625d7ed1c582d4", size = 3022121, upload-time = "2025-10-19T00:41:01.209Z" }, + { url = "https://files.pythonhosted.org/packages/e1/93/c647bc3334355088c57351a536c2d4a83dd45f7de591fab383975e45bff9/cytoolz-1.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7a98deb11ccd8e5d9f9441ef2ff3352aab52226a2b7d04756caaa53cd612363", size = 2857656, upload-time = "2025-10-19T00:41:03.456Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c2/43fea146bf4141deea959e19dcddf268c5ed759dec5c2ed4a6941d711933/cytoolz-1.1.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dce4ee9fc99104bc77efdea80f32ca5a650cd653bcc8a1d984a931153d3d9b58", size = 2551284, upload-time = "2025-10-19T00:41:05.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/df/cdc7a81ce5cfcde7ef523143d545635fc37e80ccacce140ae58483a21da3/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80d6da158f7d20c15819701bbda1c041f0944ede2f564f5c739b1bc80a9ffb8b", size = 2721673, upload-time = "2025-10-19T00:41:07.528Z" }, + { url = "https://files.pythonhosted.org/packages/45/be/f8524bb9ad8812ad375e61238dcaa3177628234d1b908ad0b74e3657cafd/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3b5c5a192abda123ad45ef716ec9082b4cf7d95e9ada8291c5c2cc5558be858b", size = 2722884, upload-time = "2025-10-19T00:41:09.698Z" }, + { url = "https://files.pythonhosted.org/packages/23/e6/6bb8e4f9c267ad42d1ff77b6d2e4984665505afae50a216290e1d7311431/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5b399ce7d967b1cb6280250818b786be652aa8ddffd3c0bb5c48c6220d945ab5", size = 2685486, upload-time = "2025-10-19T00:41:11.349Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/88619f9c8d2b682562c0c886bbb7c35720cb83fda2ac9a41bdd14073d9bd/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e7e29a1a03f00b4322196cfe8e2c38da9a6c8d573566052c586df83aacc5663c", size = 2839661, upload-time = "2025-10-19T00:41:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8d/4478ebf471ee78dd496d254dc0f4ad729cd8e6ba8257de4f0a98a2838ef2/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5291b117d71652a817ec164e7011f18e6a51f8a352cc9a70ed5b976c51102fda", size = 2547095, upload-time = "2025-10-19T00:41:16.054Z" }, + { url = "https://files.pythonhosted.org/packages/e6/68/f1dea33367b0b3f64e199c230a14a6b6f243c189020effafd31e970ca527/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8caef62f846a9011676c51bda9189ae394cdd6bb17f2946ecaedc23243268320", size = 2870901, upload-time = "2025-10-19T00:41:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/33591c09dfe799b8fb692cf2ad383e2c41ab6593cc960b00d1fc8a145655/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:de425c5a8e3be7bb3a195e19191d28d9eb3c2038046064a92edc4505033ec9cb", size = 2765422, upload-time = "2025-10-19T00:41:20.075Z" }, + { url = "https://files.pythonhosted.org/packages/60/2b/a8aa233c9416df87f004e57ae4280bd5e1f389b4943d179f01020c6ec629/cytoolz-1.1.0-cp312-cp312-win32.whl", hash = "sha256:296440a870e8d1f2e1d1edf98f60f1532b9d3ab8dfbd4b25ec08cd76311e79e5", size = 901933, upload-time = "2025-10-19T00:41:21.646Z" }, + { url = "https://files.pythonhosted.org/packages/ad/33/4c9bdf8390dc01d2617c7f11930697157164a52259b6818ddfa2f94f89f4/cytoolz-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:07156987f224c6dac59aa18fb8bf91e1412f5463961862716a3381bf429c8699", size = 947989, upload-time = "2025-10-19T00:41:23.288Z" }, + { url = "https://files.pythonhosted.org/packages/35/ac/6e2708835875f5acb52318462ed296bf94ed0cb8c7cb70e62fbd03f709e3/cytoolz-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:23e616b38f5b3160c7bb45b0f84a8f3deb4bd26b29fb2dfc716f241c738e27b8", size = 903913, upload-time = "2025-10-19T00:41:24.992Z" }, +] + [[package]] name = "darkdetect" version = "0.8.0" @@ -564,6 +776,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, ] +[[package]] +name = "datasets" +version = "4.4.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "dill", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "filelock", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "fsspec", extra = ["http"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "httpx", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "huggingface-hub", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "multiprocess", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pandas", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyarrow", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "requests", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "xxhash", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/bf/0dae295d6d1ba0b1a200a9dd216838464b5bbd05da01407cb1330b377445/datasets-4.4.1.tar.gz", hash = "sha256:80322699aa8c0bbbdb7caa87906da689c3c2e29523cff698775c67f28fdab1fc", size = 585341, upload-time = "2025-11-05T16:00:38.162Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5e/6f8d874366788ad5d549e9ba258037d974dda6e004843be1bda794571701/datasets-4.4.1-py3-none-any.whl", hash = "sha256:c1163de5211e42546079ab355cc0250c7e6db16eb209ac5ac6252f801f596c44", size = 511591, upload-time = "2025-11-05T16:00:36.365Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + [[package]] name = "diffq" version = "0.2.4" @@ -604,6 +850,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "docopt" +version = "0.6.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } + [[package]] name = "dora-search" version = "0.1.12" @@ -619,6 +871,25 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d5/9d/9a13947db237375486c0690f4741dd2b7e1eee20e0ffcb55dbd1b21cc600/dora_search-0.1.12.tar.gz", hash = "sha256:2956fd2c4c7e4b9a4830e83f0d4cf961be45cfba1a2f0570281e91d15ac516fb", size = 87111, upload-time = "2023-05-23T14:36:24.743Z" } +[[package]] +name = "editdistance" +version = "0.8.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/18/9f4f975ca87a390832b1c22478f3702fcdf739f83211e24d054b7551270d/editdistance-0.8.1.tar.gz", hash = "sha256:d1cdf80a5d5014b0c9126a69a42ce55a457b457f6986ff69ca98e4fe4d2d8fed", size = 50006, upload-time = "2024-02-10T07:44:53.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/4c/7f195588949b4e72436dc7fc902632381f96e586af829685b56daebb38b8/editdistance-0.8.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04af61b3fcdd287a07c15b6ae3b02af01c5e3e9c3aca76b8c1d13bd266b6f57", size = 106723, upload-time = "2024-02-10T07:43:50.268Z" }, + { url = "https://files.pythonhosted.org/packages/8d/82/31dc1640d830cd7d36865098329f34e4dad3b77f31cfb9404b347e700196/editdistance-0.8.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:18fc8b6eaae01bfd9cf999af726c1e8dcf667d120e81aa7dbd515bea7427f62f", size = 80998, upload-time = "2024-02-10T07:43:51.259Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2a/6b823e71cef694d6f070a1d82be2842706fa193541aab8856a8f42044cd0/editdistance-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a87839450a5987028738d061ffa5ef6a68bac2ddc68c9147a8aae9806629c7f", size = 79248, upload-time = "2024-02-10T07:43:52.873Z" }, + { url = "https://files.pythonhosted.org/packages/e1/31/bfb8e590f922089dc3471ed7828a6da2fc9453eba38c332efa9ee8749fd7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24b5f9c9673c823d91b5973d0af8b39f883f414a55ade2b9d097138acd10f31e", size = 415262, upload-time = "2024-02-10T07:43:54.498Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/57423942b2f847cdbbb46494568d00cd8a45500904ea026f0aad6ca01bc7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59248eabfad603f0fba47b0c263d5dc728fb01c2b6b50fb6ca187cec547fdb3", size = 418905, upload-time = "2024-02-10T07:43:55.779Z" }, + { url = "https://files.pythonhosted.org/packages/1b/05/dfa4cdcce063596cbf0d7a32c46cd0f4fa70980311b7da64d35f33ad02a0/editdistance-0.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e239d88ff52821cf64023fabd06a1d9a07654f364b64bf1284577fd3a79d0e", size = 412511, upload-time = "2024-02-10T07:43:57.567Z" }, + { url = "https://files.pythonhosted.org/packages/0e/14/39608ff724a9523f187c4e28926d78bc68f2798f74777ac6757981108345/editdistance-0.8.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2f7f71698f83e8c83839ac0d876a0f4ef996c86c5460aebd26d85568d4afd0db", size = 917293, upload-time = "2024-02-10T07:43:59.559Z" }, + { url = "https://files.pythonhosted.org/packages/df/92/4a1c61d72da40dedfd0ff950fdc71ae83f478330c58a8bccfd776518bd67/editdistance-0.8.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:04e229d6f4ce0c12abc9f4cd4023a5b5fa9620226e0207b119c3c2778b036250", size = 975580, upload-time = "2024-02-10T07:44:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/47/3d/9877566e724c8a37f2228a84ec5cbf66dbfd0673515baf68a0fe07caff40/editdistance-0.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e16721636da6d6b68a2c09eaced35a94f4a4a704ec09f45756d4fd5e128ed18d", size = 929121, upload-time = "2024-02-10T07:44:02.764Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/8c50757d198b8ca30ddb91e8b8f0247a8dca04ff2ec30755245f0ab1ff0c/editdistance-0.8.1-cp312-cp312-win32.whl", hash = "sha256:87533cf2ebc3777088d991947274cd7e1014b9c861a8aa65257bcdc0ee492526", size = 81039, upload-time = "2024-02-10T07:44:04.134Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/65101e51dc7c850e7b7581a5d8fa8721a1d7479a0dca6c08386328e19882/editdistance-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:09f01ed51746d90178af7dd7ea4ebb41497ef19f53c7f327e864421743dffb0a", size = 79853, upload-time = "2024-02-10T07:44:05.687Z" }, +] + [[package]] name = "einops" version = "0.8.1" @@ -628,6 +899,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359, upload-time = "2025-02-09T03:17:01.998Z" }, ] +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + [[package]] name = "faster-whisper" version = "1.2.0" @@ -658,6 +938,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/0c/56be52741f75bad4dc6555991fabd2e07b432d333da82c11ad701123888a/ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5", size = 25024, upload-time = "2019-07-06T00:19:07.215Z" }, ] +[[package]] +name = "fiddle" +version = "0.3.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "absl-py", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "graphviz", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "libcst", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/36/7a4fac76351619b36bbc7937abf59f7b601326dc4efc253b3c16819f782a/fiddle-0.3.0.tar.gz", hash = "sha256:5d083d3299a479868345513385a6c5546141bd92086c15d3dcbf8008a90075d3", size = 277884, upload-time = "2024-04-09T17:23:58.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/98/a38e949a91ff9e15874487fd8329ff53c25f3413c0cfc809eb6ff7eb7fa1/fiddle-0.3.0-py3-none-any.whl", hash = "sha256:f4824541c103a94a2f33f6c93eeddf6007c3a7300440087a95907f3e74362e61", size = 419830, upload-time = "2024-04-09T17:23:56.7Z" }, +] + [[package]] name = "filelock" version = "3.20.0" @@ -691,11 +986,59 @@ wheels = [ ] [[package]] -name = "fsspec" -version = "2025.9.0" -source = { registry = "https://download.pytorch.org/whl/cu128" } +name = "fonttools" +version = "4.60.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } wheels = [ - { url = "https://download.pytorch.org/whl/fsspec-2025.9.0-py3-none-any.whl" }, + { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706, upload-time = "2025-09-29T21:11:41.826Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716, upload-time = "2025-09-29T21:11:43.893Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175, upload-time = "2025-09-29T21:11:46.439Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031, upload-time = "2025-09-29T21:11:48.977Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966, upload-time = "2025-09-29T21:11:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750, upload-time = "2025-09-29T21:11:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026, upload-time = "2025-09-29T21:11:58.852Z" }, + { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2024.12.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/11/de70dee31455c546fbc88301971ec03c328f3d1138cfba14263f651e9551/fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f", size = 291600, upload-time = "2024-12-19T19:57:30.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/86/5486b0188d08aa643e127774a99bac51ffa6cf343e3deb0583956dca5b22/fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2", size = 183862, upload-time = "2024-12-19T19:57:28.258Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] [[package]] @@ -707,6 +1050,79 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, ] +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "smmap", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "gitdb", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -859,6 +1275,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "indic-numtowords" +version = "1.1.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/46/683e92580d9c1752917d2f9ec2a44d2adc21cdfe4deeaa0fe87fc23dbea8/indic_numtowords-1.1.0.tar.gz", hash = "sha256:d1addc21444c332e05bfd8726af427960c096c2a16776d98bee4fbc36ade5d25", size = 44220, upload-time = "2025-08-18T12:24:13.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/16/b2b6491d95a15bda712163b4e27428f2cb1ac3a1b4fb59b140dbc76f6ce5/indic_numtowords-1.1.0-py3-none-any.whl", hash = "sha256:bf4b7b9e539323d9b00bc868caa2d9369170b8f3ac4d19619bf9c6cdc6f89572", size = 71635, upload-time = "2025-08-18T12:24:11.065Z" }, +] + +[[package]] +name = "inflect" +version = "7.5.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "more-itertools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typeguard", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -868,6 +1306,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "intervaltree" +version = "3.1.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/fb/396d568039d21344639db96d940d40eb62befe704ef849b27949ded5c3bb/intervaltree-3.1.0.tar.gz", hash = "sha256:902b1b88936918f9b2a19e0e5eb7ccb430ae45cde4f39ea4b36932920d33952d", size = 32861, upload-time = "2020-08-03T08:01:11.392Z" } + +[[package]] +name = "ipython" +version = "9.7.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "ipython-pygments-lexers", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "jedi", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "matplotlib-inline", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pexpect", marker = "(platform_machine != 'x86_64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "prompt-toolkit", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pygments", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "stack-data", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "traitlets", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115, upload-time = "2025-11-05T12:18:54.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f", size = 618911, upload-time = "2025-11-05T12:18:52.484Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "pygments", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + [[package]] name = "isort" version = "5.13.2" @@ -910,6 +1390,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, ] +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "parso", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + [[package]] name = "jeepney" version = "0.9.0" @@ -955,6 +1447,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697, upload-time = "2025-10-17T11:31:13.773Z" }, ] +[[package]] +name = "jiwer" +version = "3.1.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "click", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "rapidfuzz", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/3e/71b95cf0e2179fb5de8744a79fd36c8bd4e02e1803129a16d423884b6654/jiwer-3.1.0.tar.gz", hash = "sha256:dc492d09e570f1baba98c76aba09baf8e09c06e6808a4ba412dd4bde67fb79ac", size = 103187, upload-time = "2025-01-31T12:14:10.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/f4/35634d9eeff3b0bab51f5b9474ee569b1186bf29cf0d9d67b84acc80c53d/jiwer-3.1.0-py3-none-any.whl", hash = "sha256:5a14b5bba4692e1946ca3c6946435f7d90b1b526076ccb6c12be763e2146237d", size = 22303, upload-time = "2025-01-31T12:14:08.893Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + [[package]] name = "jsonschema" version = "4.25.1" @@ -993,6 +1507,15 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a1/19/c9e1596b5572c786b93428d0904280e964c930fae7e6c9368ed9e1b63922/julius-0.2.7.tar.gz", hash = "sha256:3c0f5f5306d7d6016fcc95196b274cae6f07e2c9596eed314e4e7641554fbb08", size = 59640, upload-time = "2022-09-19T16:13:34.2Z" } +[[package]] +name = "kaldi-python-io" +version = "1.2.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/45/e3e542ffa8970ebd782fcece35e2295de9c60e8c396c2c1a403410d1b24e/kaldi-python-io-1.2.2.tar.gz", hash = "sha256:4ebb4029c6c58296cc0abf96edff02832ba341d290ed37624a8d00105f0f7c00", size = 8814, upload-time = "2021-03-18T12:02:05.832Z" } + [[package]] name = "keyring" version = "25.6.0" @@ -1010,6 +1533,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, ] +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, + { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, +] + [[package]] name = "lameenc" version = "1.8.1" @@ -1023,6 +1567,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/c6/919e890cc135d590dc3b041414ef5a94997f3bc2f614afa16b7c2f0f73ee/lameenc-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:715e0e72ed5429f00042379e48a7903e54ee5dc01069db34338536f3595059c3", size = 152019, upload-time = "2025-01-01T22:07:33.231Z" }, ] +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, +] + [[package]] name = "lazy-object-proxy" version = "1.12.0" @@ -1037,6 +1593,147 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" }, ] +[[package]] +name = "levenshtein" +version = "0.27.3" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "rapidfuzz", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/56/dcf68853b062e3b94bdc3d011cc4198779abc5b9dc134146a062920ce2e2/levenshtein-0.27.3.tar.gz", hash = "sha256:1ac326b2c84215795163d8a5af471188918b8797b4953ec87aaba22c9c1f9fc0", size = 393269, upload-time = "2025-11-01T12:14:31.04Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/8e/3be9d8e0245704e3af5258fb6cb157c3d59902e1351e95edf6ed8a8c0434/levenshtein-0.27.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2de7f095b0ca8e44de9de986ccba661cd0dec3511c751b499e76b60da46805e9", size = 169622, upload-time = "2025-11-01T12:13:10.026Z" }, + { url = "https://files.pythonhosted.org/packages/a6/42/a2b2fda5e8caf6ecd5aac142f946a77574a3961e65da62c12fd7e48e5cb1/levenshtein-0.27.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9b8b29e5d5145a3c958664c85151b1bb4b26e4ca764380b947e6a96a321217c", size = 159183, upload-time = "2025-11-01T12:13:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c4/f083fabbd61c449752df1746533538f4a8629e8811931b52f66e6c4290ad/levenshtein-0.27.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc975465a51b1c5889eadee1a583b81fba46372b4b22df28973e49e8ddb8f54a", size = 133120, upload-time = "2025-11-01T12:13:12.363Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e5/b6421e04cb0629615b8efd6d4d167dd2b1afb5097b87bb83cd992004dcca/levenshtein-0.27.3-cp312-cp312-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:57573ed885118554770979fdee584071b66103f6d50beddeabb54607a1213d81", size = 114988, upload-time = "2025-11-01T12:13:13.486Z" }, + { url = "https://files.pythonhosted.org/packages/e5/77/39ee0e8d3028e90178e1031530ccc98563f8f2f0d905ec784669dcf0fa90/levenshtein-0.27.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23aff800a6dd5d91bb3754a6092085aa7ad46b28e497682c155c74f681cfaa2d", size = 153346, upload-time = "2025-11-01T12:13:14.744Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/c0f367bbd260dbd7a4e134fd21f459e0f5eac43deac507952b46a1d8a93a/levenshtein-0.27.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c08a952432b8ad9dccb145f812176db94c52cda732311ddc08d29fd3bf185b0a", size = 1114538, upload-time = "2025-11-01T12:13:15.851Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ef/ae71433f7b4db0bd2af7974785e36cdec899919203fb82e647c5a6109c07/levenshtein-0.27.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3bfcb2d78ab9cc06a1e75da8fcfb7a430fe513d66cfe54c07e50f32805e5e6db", size = 1009734, upload-time = "2025-11-01T12:13:17.212Z" }, + { url = "https://files.pythonhosted.org/packages/27/dc/62c28b812dcb0953fc32ab7adf3d0e814e43c8560bb28d9269a44d874adf/levenshtein-0.27.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7235f6dcb31a217247468295e2dd4c6c1d3ac81629dc5d355d93e1a5f4c185", size = 1185581, upload-time = "2025-11-01T12:13:18.661Z" }, + { url = "https://files.pythonhosted.org/packages/56/e8/2e7ab9c565793220edb8e5432f9a846386a157075bdd032a90e9585bce38/levenshtein-0.27.3-cp312-cp312-win32.whl", hash = "sha256:ea80d70f1d18c161a209be556b9094968627cbaae620e102459ef9c320a98cbb", size = 84660, upload-time = "2025-11-01T12:13:19.87Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a6/907a1fc8587dc91c40156973e09d106ab064c06eb28dc4700ba0fe54d654/levenshtein-0.27.3-cp312-cp312-win_amd64.whl", hash = "sha256:fbaa1219d9b2d955339a37e684256a861e9274a3fe3a6ee1b8ea8724c3231ed9", size = 94909, upload-time = "2025-11-01T12:13:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d6/e04f0ddf6a71df3cdd1817b71703490ac874601ed460b2af172d3752c321/levenshtein-0.27.3-cp312-cp312-win_arm64.whl", hash = "sha256:2edbaa84f887ea1d9d8e4440af3fdda44769a7855d581c6248d7ee51518402a8", size = 87358, upload-time = "2025-11-01T12:13:22.393Z" }, +] + +[[package]] +name = "lhotse" +version = "1.31.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "audioread" }, + { name = "click" }, + { name = "cytoolz" }, + { name = "intervaltree" }, + { name = "lilcom" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "soundfile" }, + { name = "tabulate" }, + { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/28/ef2fb33e424e29dec83d2a150d76fb1920418a5d93d5268e6ce401cc33ad/lhotse-1.31.1.tar.gz", hash = "sha256:2ebc3c103c3e09313dff0c4e8740584e28ec35d74e985412c6b37279144a9716", size = 654706, upload-time = "2025-09-18T21:43:51.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/dd/4a3698be19e7eca530312afefada5cef524b397d69f8eca5e7cd26a1e4d1/lhotse-1.31.1-py3-none-any.whl", hash = "sha256:d1a8a3d79f7b1ec8d2a9daecc871514999b721bee8ab354db6063864362cc857", size = 866472, upload-time = "2025-09-18T21:43:49.365Z" }, +] + +[[package]] +name = "libcst" +version = "1.8.6" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/cd/337df968b38d94c5aabd3e1b10630f047a2b345f6e1d4456bd9fe7417537/libcst-1.8.6.tar.gz", hash = "sha256:f729c37c9317126da9475bdd06a7208eb52fcbd180a6341648b45a56b4ba708b", size = 891354, upload-time = "2025-11-03T22:33:30.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/3c/93365c17da3d42b055a8edb0e1e99f1c60c776471db6c9b7f1ddf6a44b28/libcst-1.8.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c13d5bd3d8414a129e9dccaf0e5785108a4441e9b266e1e5e9d1f82d1b943c9", size = 2206166, upload-time = "2025-11-03T22:32:16.012Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cb/7530940e6ac50c6dd6022349721074e19309eb6aa296e942ede2213c1a19/libcst-1.8.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1472eeafd67cdb22544e59cf3bfc25d23dc94058a68cf41f6654ff4fcb92e09", size = 2083726, upload-time = "2025-11-03T22:32:17.312Z" }, + { url = "https://files.pythonhosted.org/packages/1b/cf/7e5eaa8c8f2c54913160671575351d129170db757bb5e4b7faffed022271/libcst-1.8.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:089c58e75cb142ec33738a1a4ea7760a28b40c078ab2fd26b270dac7d2633a4d", size = 2235755, upload-time = "2025-11-03T22:32:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/55/54/570ec2b0e9a3de0af9922e3bb1b69a5429beefbc753a7ea770a27ad308bd/libcst-1.8.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c9d7aeafb1b07d25a964b148c0dda9451efb47bbbf67756e16eeae65004b0eb5", size = 2301473, upload-time = "2025-11-03T22:32:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/11/4c/163457d1717cd12181c421a4cca493454bcabd143fc7e53313bc6a4ad82a/libcst-1.8.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:207481197afd328aa91d02670c15b48d0256e676ce1ad4bafb6dc2b593cc58f1", size = 2298899, upload-time = "2025-11-03T22:32:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/35/1d/317ddef3669883619ef3d3395ea583305f353ef4ad87d7a5ac1c39be38e3/libcst-1.8.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:375965f34cc6f09f5f809244d3ff9bd4f6cb6699f571121cebce53622e7e0b86", size = 2408239, upload-time = "2025-11-03T22:32:23.275Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a1/f47d8cccf74e212dd6044b9d6dbc223636508da99acff1d54786653196bc/libcst-1.8.6-cp312-cp312-win_amd64.whl", hash = "sha256:da95b38693b989eaa8d32e452e8261cfa77fe5babfef1d8d2ac25af8c4aa7e6d", size = 2119660, upload-time = "2025-11-03T22:32:24.822Z" }, + { url = "https://files.pythonhosted.org/packages/19/d0/dd313bf6a7942cdf951828f07ecc1a7695263f385065edc75ef3016a3cb5/libcst-1.8.6-cp312-cp312-win_arm64.whl", hash = "sha256:bff00e1c766658adbd09a175267f8b2f7616e5ee70ce45db3d7c4ce6d9f6bec7", size = 1999824, upload-time = "2025-11-03T22:32:26.131Z" }, +] + +[[package]] +name = "librosa" +version = "0.11.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "audioread", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "decorator", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "joblib", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "lazy-loader", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "msgpack", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numba", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pooch", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scikit-learn", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scipy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "soundfile", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "soxr", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/36/360b5aafa0238e29758729e9486c6ed92a6f37fa403b7875e06c115cdf4a/librosa-0.11.0.tar.gz", hash = "sha256:f5ed951ca189b375bbe2e33b2abd7e040ceeee302b9bbaeeffdfddb8d0ace908", size = 327001, upload-time = "2025-03-11T15:09:54.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/ba/c63c5786dfee4c3417094c4b00966e61e4a63efecee22cb7b4c0387dda83/librosa-0.11.0-py3-none-any.whl", hash = "sha256:0b6415c4fd68bff4c29288abe67c6d80b587e0e1e2cfb0aad23e4559504a7fa1", size = 260749, upload-time = "2025-03-11T15:09:52.982Z" }, +] + +[[package]] +name = "lightning" +version = "2.4.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "fsspec", extra = ["http"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "lightning-utilities", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pytorch-lightning", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torchmetrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/d0/78ea244ac044cd4df15aa8294a50ff3561fb177e7e5ba788aaa542046cae/lightning-2.4.0.tar.gz", hash = "sha256:9156604cc56e4b2b603f34fa7f0fe5107375c8e6d85e74544b319a15faa9ed0e", size = 620632, upload-time = "2024-08-07T09:46:44.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/2c/85eaf42c983b0cd81bcda5876da2c8e2a9fd347908666ea9855724369171/lightning-2.4.0-py3-none-any.whl", hash = "sha256:560163af9711cf59055c448232c473150a299089efce0d2be3cc3288082d8768", size = 810971, upload-time = "2024-08-07T09:46:39.874Z" }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/39/6fc58ca81492db047149b4b8fd385aa1bfb8c28cd7cacb0c7eb0c44d842f/lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24", size = 31090, upload-time = "2025-08-06T13:57:39.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/73/3d757cb3fc16f0f9794dd289bcd0c4a031d9cf54d8137d6b984b2d02edf3/lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841", size = 29431, upload-time = "2025-08-06T13:57:38.046Z" }, +] + +[[package]] +name = "lilcom" +version = "1.8.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/b5/97422825e61a2683dd39d78165fc470c1153b4a908dd5cd0711c0e9d262c/lilcom-1.8.1.tar.gz", hash = "sha256:69c62037c92e71e601ac3bb3ae19811f22ceffbdf58b0fdbf81cc6a0ec6fc3c5", size = 45813, upload-time = "2025-04-02T02:49:55.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/1e/4bca706dc50230146b168d5c52403ec4e144650999fae2150a0e60ed7303/lilcom-1.8.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0ace9d4f95199266a36d1144214ff2056679ddb44462f75326a0b9fbf296928", size = 124607, upload-time = "2025-04-02T02:49:22.243Z" }, + { url = "https://files.pythonhosted.org/packages/20/80/0d07c944a09bfed9038121bf3d662e4f5cf56f7f8c2be3669269ceec429b/lilcom-1.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:834df2155ee7cad41cc5cc482bae764c89ebbaee623cb4788a3b7bbdcd2d22d3", size = 86841, upload-time = "2025-04-02T02:50:18.109Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0d/f8be338de1d45d6a6fc26a24d0ad9f6be0a94130b5b7687c2cc08a5be099/lilcom-1.8.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23b45cb5b966999826eb1aa80154d392550902ef26374dc628a8c576d844d548", size = 98796, upload-time = "2025-04-02T02:54:47.92Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6e/7868a5a90e66fefa60642682f5551cb75e489c603f155367003b88363af3/lilcom-1.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08ba2cf1cd9a175ec3b791ea727e981a1cd25a03d39c32264d651bc52e7e5c33", size = 92958, upload-time = "2025-04-02T02:54:48.822Z" }, + { url = "https://files.pythonhosted.org/packages/db/94/9553e967a067e0579dd9be9658a428ae78aebde19d7c56d7478bf68cf48b/lilcom-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:1eefd5b4735fca240aa35000dbe8a71e843fb00d64ccdc468c61eab03f23cf86", size = 69719, upload-time = "2025-04-02T02:50:48.892Z" }, +] + [[package]] name = "llvmlite" version = "0.45.1" @@ -1050,6 +1747,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/94/9ba4ebcf4d541a325fd8098ddc073b663af75cc8b065b6059848f7d4dce7/llvmlite-0.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e6c9949baf25d9aa9cd7cf0f6d011b9ca660dd17f5ba2b23bdbdb77cc86b116", size = 38132231, upload-time = "2025-10-01T18:05:03.664Z" }, ] +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + [[package]] name = "macholib" version = "1.16.3" @@ -1062,6 +1772,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094, upload-time = "2023-09-25T09:10:14.188Z" }, ] +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "markupsafe", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown" +version = "3.10" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "mdurl", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -1093,6 +1836,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, ] +[[package]] +name = "matplotlib" +version = "3.10.7" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "contourpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "cycler", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "fonttools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "kiwisolver", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pillow", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyparsing", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "python-dateutil", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/b3/09eb0f7796932826ec20c25b517d568627754f6c6462fca19e12c02f2e12/matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", size = 8272389, upload-time = "2025-10-09T00:26:42.474Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/1ae80ddafb8652fd8046cb5c8460ecc8d4afccb89e2c6d6bec61e04e1eaf/matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", size = 8128247, upload-time = "2025-10-09T00:26:44.77Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/95ae2e242d4a5c98bd6e90e36e128d71cf1c7e39b0874feaed3ef782e789/matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", size = 8696996, upload-time = "2025-10-09T00:26:46.792Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3d/5b559efc800bd05cb2033aa85f7e13af51958136a48327f7c261801ff90a/matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", size = 9530153, upload-time = "2025-10-09T00:26:49.07Z" }, + { url = "https://files.pythonhosted.org/packages/88/57/eab4a719fd110312d3c220595d63a3c85ec2a39723f0f4e7fa7e6e3f74ba/matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", size = 9593093, upload-time = "2025-10-09T00:26:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/31/3c/80816f027b3a4a28cd2a0a6ef7f89a2db22310e945cd886ec25bfb399221/matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", size = 8122771, upload-time = "2025-10-09T00:26:53.296Z" }, + { url = "https://files.pythonhosted.org/packages/de/77/ef1fc78bfe99999b2675435cc52120887191c566b25017d78beaabef7f2d/matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", size = 7992812, upload-time = "2025-10-09T00:26:54.882Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "traitlets", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + [[package]] name = "mccabe" version = "0.7.0" @@ -1102,6 +1883,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mediapy" +version = "1.1.6" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "ipython", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "matplotlib", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pillow", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/b2/451be65c13d2d69b7601eded7ddd3f150884486715a9b3a705ffb08d0177/mediapy-1.1.6.tar.gz", hash = "sha256:9f44b760400964d8bea5121a213f94dc9a225d026d6a819901283a695e585634", size = 25459, upload-time = "2023-02-24T13:08:42.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/a0/0d55c59ea8a5f1b13b3eb931e1c0eab9c2a07322cad79cb51a596d2d2a5c/mediapy-1.1.6-py3-none-any.whl", hash = "sha256:c74370808b445666f95272bfdf0eb5707a43b7e05e5527f2dd0830e6892f976f", size = 24955, upload-time = "2023-02-24T13:08:40.53Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, +] + [[package]] name = "monotonic" version = "1.6" @@ -1128,6 +1949,66 @@ wheels = [ { url = "https://download.pytorch.org/whl/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c" }, ] +[[package]] +name = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.18" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "dill", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/fd/2ae3826f5be24c6ed87266bc4e59c46ea5b059a103f3d7e7eb76a52aeecb/multiprocess-0.70.18.tar.gz", hash = "sha256:f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d", size = 1798503, upload-time = "2025-04-17T03:11:27.742Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/d8/0cba6cf51a1a31f20471fbc823a716170c73012ddc4fb85d706630ed6e8f/multiprocess-0.70.18-py310-none-any.whl", hash = "sha256:60c194974c31784019c1f459d984e8f33ee48f10fcf42c309ba97b30d9bd53ea", size = 134948, upload-time = "2025-04-17T03:11:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/4b/88/9039f2fed1012ef584751d4ceff9ab4a51e5ae264898f0b7cbf44340a859/multiprocess-0.70.18-py311-none-any.whl", hash = "sha256:5aa6eef98e691281b3ad923be2832bf1c55dd2c859acd73e5ec53a66aae06a1d", size = 144462, upload-time = "2025-04-17T03:11:21.657Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/5f922792be93b82ec6b5f270bbb1ef031fd0622847070bbcf9da816502cc/multiprocess-0.70.18-py312-none-any.whl", hash = "sha256:9b78f8e5024b573730bfb654783a13800c2c0f2dfc0c25e70b40d184d64adaa2", size = 150287, upload-time = "2025-04-17T03:11:22.69Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c3/ca84c19bd14cdfc21c388fdcebf08b86a7a470ebc9f5c3c084fc2dbc50f7/multiprocess-0.70.18-py38-none-any.whl", hash = "sha256:dbf705e52a154fe5e90fb17b38f02556169557c2dd8bb084f2e06c2784d8279b", size = 132636, upload-time = "2025-04-17T03:11:24.936Z" }, + { url = "https://files.pythonhosted.org/packages/6c/28/dd72947e59a6a8c856448a5e74da6201cb5502ddff644fbc790e4bd40b9a/multiprocess-0.70.18-py39-none-any.whl", hash = "sha256:e78ca805a72b1b810c690b6b4cc32579eba34f403094bbbae962b7b5bf9dfcb8", size = 133478, upload-time = "2025-04-17T03:11:26.253Z" }, +] + [[package]] name = "musdb" version = "0.4.3" @@ -1190,6 +2071,82 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "nemo-toolkit" +version = "2.5.3" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "fsspec", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "huggingface-hub", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numba", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "onnx", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "protobuf", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "python-dateutil", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "ruamel-yaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scikit-learn", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tensorboard", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "text-unidecode", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "wget", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "wrapt", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/6c/c6ec69f6bd009f7faf784bcaa473d2ba0c7154f77af898d6236c5f0f6c9b/nemo_toolkit-2.5.3.tar.gz", hash = "sha256:50309a400162cc3dda8fa5eebfe585f51a5cdcbdafc207ac3b56866dd50ff51b", size = 4144730, upload-time = "2025-11-10T22:46:33.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/2a/d5f68c753705c6d8f2767c753df60bdb70301669039d54aadcec760831be/nemo_toolkit-2.5.3-py3-none-any.whl", hash = "sha256:17fb705e002cb38c753c0d0804d66b8cec4d0d86f878ba8a41049bcd9c7ae7d6", size = 5924546, upload-time = "2025-11-10T22:46:29.678Z" }, +] + +[package.optional-dependencies] +asr = [ + { name = "bitsandbytes", marker = "platform_machine == 'x86_64' and sys_platform != 'darwin'" }, + { name = "braceexpand", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "cloudpickle", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "ctc-segmentation", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "datasets", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "editdistance", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "einops", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "fiddle", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "hydra-core", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "inflect", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "jiwer", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "kaldi-python-io", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "lhotse", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "librosa", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "lightning", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "marshmallow", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "mediapy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "num2words", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "nv-one-logger-core", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "nv-one-logger-pytorch-lightning-integration", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "nv-one-logger-training-telemetry", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "omegaconf", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "optuna", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pandas", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "peft", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyannote-core", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyannote-metrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pydub", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyloudnorm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "resampy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "ruamel-yaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sacremoses", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scipy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sentencepiece", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "soundfile", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sox", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "texterrors", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "torchmetrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "transformers", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "wandb", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "webdataset", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "whisper-normalizer", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] + [[package]] name = "networkx" version = "3.5" @@ -1198,6 +2155,21 @@ wheels = [ { url = "https://download.pytorch.org/whl/networkx-3.5-py3-none-any.whl" }, ] +[[package]] +name = "nltk" +version = "3.9.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/76/3a5e4312c19a028770f86fd7c058cf9f4ec4321c6cf7526bab998a5b683c/nltk-3.9.2.tar.gz", hash = "sha256:0f409e9b069ca4177c1903c3e843eef90c7e92992fa4931ae607da6de49e1419", size = 2887629, upload-time = "2025-10-01T07:19:23.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/90/81ac364ef94209c100e12579629dc92bf7a709a84af32f8c551b02c07e94/nltk-3.9.2-py3-none-any.whl", hash = "sha256:1e209d2b3009110635ed9709a67a1a3e33a10f799490fa71cf4bec218c11c88a", size = 1513404, upload-time = "2025-10-01T07:19:21.648Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -1207,6 +2179,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "num2words" +version = "0.5.14" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "docopt", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/58/ad645bd38b4b648eb2fc2ba1b909398e54eb0cbb6a7dbd2b4953e38c9621/num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6", size = 218213, upload-time = "2024-12-17T20:17:10.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/5b/545e9267a1cc080c8a1be2746113a063e34bcdd0f5173fd665a5c13cb234/num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d", size = 163525, upload-time = "2024-12-17T20:17:06.074Z" }, +] + [[package]] name = "numba" version = "0.62.1" @@ -1240,6 +2224,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, ] +[[package]] +name = "nv-one-logger-core" +version = "2.3.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "overrides", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pydantic", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "strenum", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "toml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/37/963095797035f371e0db6ea761f5aaccb624fc786af217115b423baeb0e2/nv_one_logger_core-2.3.1.tar.gz", hash = "sha256:cbb2f87604c78b96a302f32d87199902129d76153a73a20f8455a250b3246c1d", size = 52640, upload-time = "2025-10-29T21:11:55.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/c4/ea91554c4fcbff66057f667690101d7a4b965605741350ac661b03fa6c46/nv_one_logger_core-2.3.1-py3-none-any.whl", hash = "sha256:0c8b77bcdac4daa1ea913bf8d4afd2a057bd5526e3654ac39f67caba157341a6", size = 63066, upload-time = "2025-10-29T21:11:52.753Z" }, +] + +[[package]] +name = "nv-one-logger-pytorch-lightning-integration" +version = "2.3.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "lightning", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "nv-one-logger-core", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "nv-one-logger-training-telemetry", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "strenum", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/d0/3475b7ab17d367362f650fb0419e8669f41e63c1018f4a8ac2fbecfd2e85/nv_one_logger_pytorch_lightning_integration-2.3.1.tar.gz", hash = "sha256:b32d99b6a8f02a16538bcade939b0a7edd7249e936aacefe336b5519447340c3", size = 10979, upload-time = "2025-10-29T21:22:10.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/56/01a55efb365b6864646b4ac941a1d9de66f024e880764510ba5a7a63f62c/nv_one_logger_pytorch_lightning_integration-2.3.1-py3-none-any.whl", hash = "sha256:f92904055fb0082516480cc1e3dd0bb6cedb2b033985ebfd4814b9cbf7da2cb2", size = 9822, upload-time = "2025-10-29T21:22:09.37Z" }, +] + +[[package]] +name = "nv-one-logger-training-telemetry" +version = "2.3.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "nv-one-logger-core", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "strenum", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/21/016fa067967734d52f1ccf5a2a37a1a65216f2d7053bc2b85872cce956ca/nv_one_logger_training_telemetry-2.3.1.tar.gz", hash = "sha256:8c67940ea71799afaf1f46df3ba2f52f93aea26321c6f1c1d54aae02efc2a4af", size = 44435, upload-time = "2025-10-29T21:21:42.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/15/97e6e4ddfe5fc35bcee74a45b7c33fb73abb83713c7dfa26420b971a86c3/nv_one_logger_training_telemetry-2.3.1-py3-none-any.whl", hash = "sha256:5319443829b59378a498c3c62ac98973e14f31be675c229ff2b14e2fe109aa0b", size = 44140, upload-time = "2025-10-29T21:21:40.72Z" }, +] + [[package]] name = "nvidia-cublas-cu12" version = "12.8.3.14" @@ -1277,7 +2307,7 @@ name = "nvidia-cudnn-cu12" version = "9.7.1.26" source = { registry = "https://download.pytorch.org/whl/cu128" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu128/nvidia_cudnn_cu12-9.7.1.26-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:6d011159a158f3cfc47bf851aea79e31bcff60d530b70ef70474c84cac484d07" }, @@ -1288,7 +2318,7 @@ name = "nvidia-cufft-cu12" version = "11.3.3.41" source = { registry = "https://download.pytorch.org/whl/cu128" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu128/nvidia_cufft_cu12-11.3.3.41-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:da650080ab79fcdf7a4b06aa1b460e99860646b176a43f6208099bdc17836b6a" }, @@ -1315,9 +2345,9 @@ name = "nvidia-cusolver-cu12" version = "11.7.2.55" source = { registry = "https://download.pytorch.org/whl/cu128" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu128/nvidia_cusolver_cu12-11.7.2.55-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4d1354102f1e922cee9db51920dba9e2559877cf6ff5ad03a00d853adafb191b" }, @@ -1328,7 +2358,7 @@ name = "nvidia-cusparse-cu12" version = "12.5.7.53" source = { registry = "https://download.pytorch.org/whl/cu128" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu128/nvidia_cusparse_cu12-12.5.7.53-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c1b61eb8c85257ea07e9354606b26397612627fdcd327bfd91ccf6155e7c86d" }, @@ -1380,6 +2410,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, ] +[[package]] +name = "onnx" +version = "1.19.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "ml-dtypes", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "protobuf", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/2f/c619eb65769357e9b6de9212c9a821ab39cd484448e5d6b3fb5fb0a64c6d/onnx-1.19.1.tar.gz", hash = "sha256:737524d6eb3907d3499ea459c6f01c5a96278bb3a0f2ff8ae04786fb5d7f1ed5", size = 12033525, upload-time = "2025-10-10T04:01:34.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/07/f6c5b2cffef8c29e739616d1415aea22f7b7ef1f19c17f02b7cff71f5498/onnx-1.19.1-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:3612193a89ddbce5c4e86150869b9258780a82fb8c4ca197723a4460178a6ce9", size = 18327840, upload-time = "2025-10-10T04:00:24.259Z" }, + { url = "https://files.pythonhosted.org/packages/93/20/0568ebd52730287ae80cac8ac893a7301c793ea1630984e2519ee92b02a9/onnx-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c2fd2f744e7a3880ad0c262efa2edf6d965d0bd02b8f327ec516ad4cb0f2f15", size = 18042539, upload-time = "2025-10-10T04:00:27.693Z" }, + { url = "https://files.pythonhosted.org/packages/14/fd/cd7a0fd10a04f8cc5ae436b63e0022e236fe51b9dbb8ee6317fd48568c72/onnx-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:485d3674d50d789e0ee72fa6f6e174ab81cb14c772d594f992141bd744729d8a", size = 18218271, upload-time = "2025-10-10T04:00:30.495Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/cc8b8c05469fe08384b446304ad7e6256131ca0463bf6962366eebec98c0/onnx-1.19.1-cp312-cp312-win32.whl", hash = "sha256:638bc56ff1a5718f7441e887aeb4e450f37a81c6eac482040381b140bd9ba601", size = 16345111, upload-time = "2025-10-10T04:00:34.982Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5e/d1cb16693598a512c2cf9ffe0841d8d8fd2c83ae8e889efd554f5aa427cf/onnx-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc7e2e4e163e679721e547958b5a7db875bf822cad371b7c1304aa4401a7c7a4", size = 16465621, upload-time = "2025-10-10T04:00:39.107Z" }, + { url = "https://files.pythonhosted.org/packages/90/32/da116cc61fdef334782aa7f87a1738431dd1af1a5d1a44bd95d6d51ad260/onnx-1.19.1-cp312-cp312-win_arm64.whl", hash = "sha256:17c215b1c0f20fe93b4cbe62668247c1d2294b9bc7f6be0ca9ced28e980c07b7", size = 16437505, upload-time = "2025-10-10T04:00:42.255Z" }, +] + [[package]] name = "onnxruntime" version = "1.18.1" @@ -1457,12 +2507,39 @@ wheels = [ ] [[package]] -name = "packaging" -version = "25.0" +name = "optuna" +version = "4.6.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +dependencies = [ + { name = "alembic", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "colorlog", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sqlalchemy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/81/08f90f194eed78178064a9383432eca95611e2c5331e7b01e2418ce4b15a/optuna-4.6.0.tar.gz", hash = "sha256:89e38c2447c7f793a726617b8043f01e31f0bad54855040db17eb3b49404a369", size = 477444, upload-time = "2025-11-10T05:14:30.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/3d8455b08cb6312f8cc46aacdf16c71d4d881a1db4a4140fc5ef31108422/optuna-4.6.0-py3-none-any.whl", hash = "sha256:4c3a9facdef2b2dd7e3e2a8ae3697effa70fae4056fcf3425cfc6f5a40feb069", size = 404708, upload-time = "2025-11-10T05:14:28.6Z" }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] @@ -1486,6 +2563,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, ] +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + [[package]] name = "pathspec" version = "0.12.1" @@ -1504,6 +2590,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" }, ] +[[package]] +name = "peft" +version = "0.18.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "accelerate", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "huggingface-hub", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "psutil", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "safetensors", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "transformers", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/0c/f2938db546ac7fc961ab5917cd50fcf5d0d70b406de93e3faccaa504e152/peft-0.18.0.tar.gz", hash = "sha256:c81c80b2056ab40c23d58ef25f74daab417ac653970718589a11a8af28218588", size = 634141, upload-time = "2025-11-13T11:13:06.603Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/55/481bf25613d40ef53534f664deba7b138fe566356b6ca10304e2b3b2529c/peft-0.18.0-py3-none-any.whl", hash = "sha256:624f69ca6393b765ccc6734adda7ca57d80b238f0900a42c357d8b67a03d62ff", size = 556427, upload-time = "2025-11-13T11:13:03.664Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "ptyprocess", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "12.0.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, +] + +[[package]] +name = "plac" +version = "1.4.5" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/23/09/26ef2d614cabdcc52a7f383d0dc7967bf46be3c9700898c594e37b710c3d/plac-1.4.5.tar.gz", hash = "sha256:5f05bf85235c017fcd76c73c8101d4ff8e96beb3dc58b9a37de49cac7de82d14", size = 38988, upload-time = "2025-04-04T14:03:25.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/36/38676114a0dbee137ec366daa86603d667a07e9a52667d5ebf5c580100ba/plac-1.4.5-py2.py3-none-any.whl", hash = "sha256:87187786b4e446688b1cf5112e18fed8a23ab3b316c25fe91266a10bd1736b16", size = 22468, upload-time = "2025-04-04T14:03:24.761Z" }, +] + [[package]] name = "platformdirs" version = "4.5.0" @@ -1531,6 +2679,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/99/45bb1f9926efe370c6dbe324741c749658e44cb060124f28dad201202274/polib-1.2.0-py2.py3-none-any.whl", hash = "sha256:1c77ee1b81feb31df9bca258cbc58db1bbb32d10214b173882452c73af06d62d", size = 20634, upload-time = "2023-02-23T17:53:59.919Z" }, ] +[[package]] +name = "pooch" +version = "1.8.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "platformdirs", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "requests", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/77/b3d3e00c696c16cf99af81ef7b1f5fe73bd2a307abca41bd7605429fe6e5/pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10", size = 59353, upload-time = "2024-06-06T16:53:46.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47", size = 64574, upload-time = "2024-06-06T16:53:44.343Z" }, +] + [[package]] name = "posthog" version = "3.25.0" @@ -1565,18 +2727,53 @@ wheels = [ ] [[package]] -name = "protobuf" -version = "6.33.0" +name = "prompt-toolkit" +version = "3.0.52" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } +dependencies = [ + { name = "wcwidth", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, - { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, - { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, - { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "protobuf" +version = "5.29.5" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226, upload-time = "2025-05-28T23:51:59.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963, upload-time = "2025-05-28T23:51:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818, upload-time = "2025-05-28T23:51:44.297Z" }, + { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091, upload-time = "2025-05-28T23:51:45.907Z" }, + { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824, upload-time = "2025-05-28T23:51:47.545Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942, upload-time = "2025-05-28T23:51:49.11Z" }, + { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" }, ] [[package]] @@ -1595,6 +2792,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/8d/8a9a45c8b655851f216c1d44f68e3533dc8d2c752ccd0f61f1aa73be4893/psutil-7.1.1-cp37-abi3-win_arm64.whl", hash = "sha256:5457cf741ca13da54624126cd5d333871b454ab133999a9a103fb097a7d7d21a", size = 243944, upload-time = "2025-10-19T15:44:20.666Z" }, ] +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + [[package]] name = "py-cpuinfo" version = "9.0.0" @@ -1616,6 +2831,81 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/ee/a878f2ad010cbccb311f947f0f2f09d38f613938ee28c34e60fceecc75a1/pyaml-25.7.0-py3-none-any.whl", hash = "sha256:ce5d7867cc2b455efdb9b0448324ff7b9f74d99f64650f12ca570102db6b985f", size = 26418, upload-time = "2025-07-10T18:44:50.679Z" }, ] +[[package]] +name = "pyannote-core" +version = "5.0.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scipy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sortedcontainers", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/03/feaf7534206f02c75baf151ce4b8c322b402a6f477c2be82f69d9269cbe6/pyannote.core-5.0.0.tar.gz", hash = "sha256:1a55bcc8bd680ba6be5fa53efa3b6f3d2cdd67144c07b6b4d8d66d5cb0d2096f", size = 59247, upload-time = "2022-12-15T13:02:05.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/c4/370bc8ba66815a5832ece753a1009388bb07ea353d21c83f2d5a1a436f2c/pyannote.core-5.0.0-py3-none-any.whl", hash = "sha256:04920a6754492242ce0dc6017545595ab643870fe69a994f20c1a5f2da0544d0", size = 58475, upload-time = "2022-12-15T13:02:03.265Z" }, +] + +[[package]] +name = "pyannote-database" +version = "5.1.3" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "pandas", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyannote-core", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typer", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/ae/de36413d69a46be87cb612ebbcdc4eacbeebce3bc809124603e44a88fe26/pyannote.database-5.1.3.tar.gz", hash = "sha256:0eaf64c1cc506718de60d2d702f1359b1ae7ff252ee3e4799f1c5e378cd52c31", size = 49957, upload-time = "2025-01-15T20:28:26.437Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/64/92d51a3a05615ba58be8ba62a43f9f9f952d9f3646f7e4fb7826e5a3a24e/pyannote.database-5.1.3-py3-none-any.whl", hash = "sha256:37887844c7dfbcc075cb591eddc00aff45fae1ed905344e1f43e0090e63bd40a", size = 48127, upload-time = "2025-01-15T20:28:25.326Z" }, +] + +[[package]] +name = "pyannote-metrics" +version = "3.2.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "docopt", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "matplotlib", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pandas", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyannote-core", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyannote-database", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scikit-learn", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scipy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sympy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tabulate", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/2b/6c5f01d3c49aa1c160765946e23782ca6436ae8b9bc514b56319ff5f16e7/pyannote.metrics-3.2.1.tar.gz", hash = "sha256:08024255a3550e96a8e9da4f5f4af326886548480de891414567c8900920ee5c", size = 49086, upload-time = "2022-06-20T14:10:34.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/7d/035b370ab834b30e849fe9cd092b7bd7f321fcc4a2c56b84e96476b7ede5/pyannote.metrics-3.2.1-py3-none-any.whl", hash = "sha256:46be797cdade26c82773e5018659ae610145260069c7c5bf3d3c8a029ade8e22", size = 51386, upload-time = "2022-06-20T14:10:32.621Z" }, +] + +[[package]] +name = "pyarrow" +version = "22.0.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", size = 35989906, upload-time = "2025-10-24T10:05:29.485Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", size = 45021677, upload-time = "2025-10-24T10:05:38.274Z" }, + { url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", size = 47726315, upload-time = "2025-10-24T10:05:47.314Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", size = 47990906, upload-time = "2025-10-24T10:05:58.254Z" }, + { url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", size = 50306783, upload-time = "2025-10-24T10:06:08.08Z" }, + { url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", size = 27972883, upload-time = "2025-10-24T10:06:14.204Z" }, +] + +[[package]] +name = "pybind11" +version = "3.0.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/7b/a6d8dcb83c457e24a9df1e4d8fd5fb8034d4bbc62f3c324681e8a9ba57c2/pybind11-3.0.1.tar.gz", hash = "sha256:9c0f40056a016da59bab516efb523089139fcc6f2ba7e4930854c61efb932051", size = 546914, upload-time = "2025-08-22T20:09:27.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/8a/37362fc2b949d5f733a8b0f2ff51ba423914cabefe69f1d1b6aab710f5fe/pybind11-3.0.1-py3-none-any.whl", hash = "sha256:aa8f0aa6e0a94d3b64adfc38f560f33f15e589be2175e103c0a33c6bce55ee89", size = 293611, upload-time = "2025-08-22T20:09:25.235Z" }, +] + [[package]] name = "pycodestyle" version = "2.14.0" @@ -1678,6 +2968,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, ] +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + [[package]] name = "pyflakes" version = "3.4.0" @@ -1687,6 +2986,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + [[package]] name = "pyinstaller" version = "6.16.0" @@ -1746,6 +3054,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/49/cea450a83079445a84f16050e571a7c383d3f474b13c5caedfebd4e35def/pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87", size = 537178, upload-time = "2023-09-30T21:25:07.527Z" }, ] +[[package]] +name = "pyloudnorm" +version = "0.1.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "future", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scipy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/b5/39d59c44ecd828fabfdbd796b50a561e6543ca90ef440ab307374f107856/pyloudnorm-0.1.1.tar.gz", hash = "sha256:63cd4e197dea4e7795160ea08ed02d318091bce883e436a6dbc5963326b71e1e", size = 8588, upload-time = "2023-01-05T16:11:28.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f5/6724805521ab4e723a12182f92374031032aff28a8a89dc8505c52b79032/pyloudnorm-0.1.1-py3-none-any.whl", hash = "sha256:d7f12ebdd097a464d87ce2878fc4d942f15f8233e26cc03f33fefa226f869a14", size = 9636, upload-time = "2023-01-05T16:11:27.331Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + [[package]] name = "pyqt6" version = "6.9.1" @@ -1902,6 +3233,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "pytorch-lightning" +version = "2.5.6" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "fsspec", extra = ["http"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "lightning-utilities", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torchmetrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/1f/94a441d30779e1ffa5f7dc2ac5fa374c142d8b96c347a49a30226264124e/pytorch_lightning-2.5.6.tar.gz", hash = "sha256:c428faaceef74be50b870814d0d7e9f9c6ee748b8769a2afd3366bc69daf3a0f", size = 642830, upload-time = "2025-11-05T20:53:04.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/e4/32ed2f33c1b634f7c2895369222f4f8cb345044f4642bbff718e7dd1e0b7/pytorch_lightning-2.5.6-py3-none-any.whl", hash = "sha256:037bad1e2fd94d5eb6c5144f045fd4c1070c3d38fc9c14d9f3774a3a9be54dff", size = 831555, upload-time = "2025-11-05T20:53:03.316Z" }, +] + [[package]] name = "pytz" version = "2025.2" @@ -1947,6 +3298,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] +[[package]] +name = "rapidfuzz" +version = "3.14.3" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/28/9d808fe62375b9aab5ba92fa9b29371297b067c2790b2d7cda648b1e2f8d/rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f", size = 57863900, upload-time = "2025-11-01T11:54:52.321Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/8e/3c215e860b458cfbedb3ed73bc72e98eb7e0ed72f6b48099604a7a3260c2/rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226", size = 1945306, upload-time = "2025-11-01T11:53:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/36/d9/31b33512015c899f4a6e6af64df8dfe8acddf4c8b40a4b3e0e6e1bcd00e5/rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb", size = 1390788, upload-time = "2025-11-01T11:53:08.721Z" }, + { url = "https://files.pythonhosted.org/packages/a9/67/2ee6f8de6e2081ccd560a571d9c9063184fe467f484a17fa90311a7f4a2e/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941", size = 1374580, upload-time = "2025-11-01T11:53:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/83/80d22997acd928eda7deadc19ccd15883904622396d6571e935993e0453a/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382", size = 3154947, upload-time = "2025-11-01T11:53:12.093Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cf/9f49831085a16384695f9fb096b99662f589e30b89b4a589a1ebc1a19d34/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43", size = 1223872, upload-time = "2025-11-01T11:53:13.664Z" }, + { url = "https://files.pythonhosted.org/packages/c8/0f/41ee8034e744b871c2e071ef0d360686f5ccfe5659f4fd96c3ec406b3c8b/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db", size = 2392512, upload-time = "2025-11-01T11:53:15.109Z" }, + { url = "https://files.pythonhosted.org/packages/da/86/280038b6b0c2ccec54fb957c732ad6b41cc1fd03b288d76545b9cf98343f/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed", size = 2521398, upload-time = "2025-11-01T11:53:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7b/05c26f939607dca0006505e3216248ae2de631e39ef94dd63dbbf0860021/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc", size = 4259416, upload-time = "2025-11-01T11:53:19.34Z" }, + { url = "https://files.pythonhosted.org/packages/40/eb/9e3af4103d91788f81111af1b54a28de347cdbed8eaa6c91d5e98a889aab/rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a", size = 1709527, upload-time = "2025-11-01T11:53:20.949Z" }, + { url = "https://files.pythonhosted.org/packages/b8/63/d06ecce90e2cf1747e29aeab9f823d21e5877a4c51b79720b2d3be7848f8/rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329", size = 1538989, upload-time = "2025-11-01T11:53:22.428Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6d/beee32dcda64af8128aab3ace2ccb33d797ed58c434c6419eea015fec779/rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f", size = 811161, upload-time = "2025-11-01T11:53:23.811Z" }, +] + [[package]] name = "referencing" version = "0.37.0" @@ -1998,6 +3368,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "resampy" +version = "0.4.3" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numba", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/f1/34be702a69a5d272e844c98cee82351f880985cfbca0cc86378011078497/resampy-0.4.3.tar.gz", hash = "sha256:a0d1c28398f0e55994b739650afef4e3974115edbe96cd4bb81968425e916e47", size = 3080604, upload-time = "2024-03-05T20:36:08.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b9/3b00ac340a1aab3389ebcc52c779914a44aadf7b0cb7a3bf053195735607/resampy-0.4.3-py3-none-any.whl", hash = "sha256:ad2ed64516b140a122d96704e32bc0f92b23f45419e8b8f478e5a05f83edcebd", size = 3076529, upload-time = "2024-03-05T20:36:02.439Z" }, +] + [[package]] name = "retrying" version = "1.4.2" @@ -2007,6 +3390,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/f3/6cd296376653270ac1b423bb30bd70942d9916b6978c6f40472d6ac038e7/retrying-1.4.2-py3-none-any.whl", hash = "sha256:bbc004aeb542a74f3569aeddf42a2516efefcdaff90df0eb38fbfbf19f179f59", size = 10859, upload-time = "2025-08-03T03:35:23.829Z" }, ] +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "markdown-it-py", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pygments", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + [[package]] name = "rpds-py" version = "0.28.0" @@ -2030,6 +3426,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/91/f3fb250d7e73de71080f9a221d19bd6a1c1eb0d12a1ea26513f6c1052ad6/rpds_py-0.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:1f0cfd1c69e2d14f8c892b893997fa9a60d890a0c8a603e88dca4955f26d1edd", size = 217624, upload-time = "2025-10-22T22:22:26.914Z" }, ] +[[package]] +name = "ruamel-yaml" +version = "0.18.16" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "(platform_machine != 'x86_64' and platform_python_implementation == 'CPython') or (platform_python_implementation == 'CPython' and sys_platform != 'darwin')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/c7/ee630b29e04a672ecfc9b63227c87fd7a37eb67c1bf30fe95376437f897c/ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a", size = 147269, upload-time = "2025-10-22T17:54:02.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/73/bb1bc2529f852e7bf64a2dec885e89ff9f5cc7bbf6c9340eed30ff2c69c5/ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba", size = 119858, upload-time = "2025-10-22T17:53:59.012Z" }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.15" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, + { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, + { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, + { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, + { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, +] + [[package]] name = "ruff" version = "0.1.15" @@ -2054,6 +3480,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/bd/c196493563d6bf8fe960f10b83926a3fae3a43a96eac6b263aecb96c61d7/ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360", size = 6998592, upload-time = "2024-01-29T23:06:01.904Z" }, ] +[[package]] +name = "sacremoses" +version = "0.1.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "click", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "joblib", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "regex", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/51/fbdc4af4f6e85d26169e28be3763fe50ddfd0d4bf8b871422b0788dcc4d2/sacremoses-0.1.1.tar.gz", hash = "sha256:b6fd5d3a766b02154ed80b962ddca91e1fd25629c0978c7efba21ebccf663934", size = 883188, upload-time = "2023-10-30T15:56:20.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/f0/89ee2bc9da434bd78464f288fdb346bc2932f2ee80a90b2a4bbbac262c74/sacremoses-0.1.1-py3-none-any.whl", hash = "sha256:31e04c98b169bfd902144824d191825cd69220cdb4ae4bcf1ec58a7db5587b1a", size = 897476, upload-time = "2023-10-30T15:56:18.121Z" }, +] + [[package]] name = "safetensors" version = "0.6.2" @@ -2076,6 +3517,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, ] +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "joblib", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "scipy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "threadpoolctl", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, +] + [[package]] name = "scipy" version = "1.16.2" @@ -2102,14 +3562,43 @@ name = "secretstorage" version = "3.4.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "cryptography", marker = "sys_platform != 'darwin'" }, - { name = "jeepney", marker = "sys_platform != 'darwin'" }, + { name = "cryptography", marker = "sys_platform == 'linux'" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, ] +[[package]] +name = "sentencepiece" +version = "0.2.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/15/15/2e7a025fc62d764b151ae6d0f2a92f8081755ebe8d4a64099accc6f77ba6/sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad", size = 3228515, upload-time = "2025-08-12T07:00:51.718Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/be/32ce495aa1d0e0c323dcb1ba87096037358edee539cac5baf8755a6bd396/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57cae326c8727de58c85977b175af132a7138d84c764635d7e71bbee7e774133", size = 1943152, upload-time = "2025-08-12T06:59:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/88/7e/ff23008899a58678e98c6ff592bf4d368eee5a71af96d0df6b38a039dd4f/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56dd39a3c4d6493db3cdca7e8cc68c6b633f0d4195495cbadfcf5af8a22d05a6", size = 1325651, upload-time = "2025-08-12T06:59:41.536Z" }, + { url = "https://files.pythonhosted.org/packages/19/84/42eb3ce4796777a1b5d3699dfd4dca85113e68b637f194a6c8d786f16a04/sentencepiece-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9381351182ff9888cc80e41c632e7e274b106f450de33d67a9e8f6043da6f76", size = 1253645, upload-time = "2025-08-12T06:59:42.903Z" }, + { url = "https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99f955df238021bf11f0fc37cdb54fd5e5b5f7fd30ecc3d93fb48b6815437167", size = 1316273, upload-time = "2025-08-12T06:59:44.476Z" }, + { url = "https://files.pythonhosted.org/packages/04/88/14f2f4a2b922d8b39be45bf63d79e6cd3a9b2f248b2fcb98a69b12af12f5/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdfecef430d985f1c2bcbfff3defd1d95dae876fbd0173376012d2d7d24044b", size = 1387881, upload-time = "2025-08-12T06:59:46.09Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b8/903e5ccb77b4ef140605d5d71b4f9e0ad95d456d6184688073ed11712809/sentencepiece-0.2.1-cp312-cp312-win32.whl", hash = "sha256:a483fd29a34c3e34c39ac5556b0a90942bec253d260235729e50976f5dba1068", size = 999540, upload-time = "2025-08-12T06:59:48.023Z" }, + { url = "https://files.pythonhosted.org/packages/2d/81/92df5673c067148c2545b1bfe49adfd775bcc3a169a047f5a0e6575ddaca/sentencepiece-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4cdc7c36234fda305e85c32949c5211faaf8dd886096c7cea289ddc12a2d02de", size = 1054671, upload-time = "2025-08-12T06:59:49.895Z" }, + { url = "https://files.pythonhosted.org/packages/fe/02/c5e3bc518655d714622bec87d83db9cdba1cd0619a4a04e2109751c4f47f/sentencepiece-0.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:daeb5e9e9fcad012324807856113708614d534f596d5008638eb9b40112cd9e4", size = 1033923, upload-time = "2025-08-12T06:59:51.952Z" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.45.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "certifi", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "urllib3", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/89/1561b3dc8e28bf7978d031893297e89be266f53650c87bb14a29406a9791/sentry_sdk-2.45.0.tar.gz", hash = "sha256:e9bbfe69d5f6742f48bad22452beffb525bbc5b797d817c7f1b1f7d210cdd271", size = 373631, upload-time = "2025-11-18T13:23:22.475Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/c6/039121a0355bc1b5bcceef0dabf211b021fd435d0ee5c46393717bb1c09f/sentry_sdk-2.45.0-py2.py3-none-any.whl", hash = "sha256:86c8ab05dc3e8666aece77a5c747b45b25aa1d5f35f06cde250608f495d50f23", size = 404791, upload-time = "2025-11-18T13:23:20.533Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -2119,6 +3608,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "simplejson" version = "3.20.2" @@ -2150,6 +3648,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -2159,6 +3666,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "sounddevice" version = "0.4.7" @@ -2193,6 +3709,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, ] +[[package]] +name = "sox" +version = "1.5.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/a2/d8e0d8fd7abf509ead4a2cb0fb24e5758b5330166bf9223d5cb9f98a7e8d/sox-1.5.0.tar.gz", hash = "sha256:12c7be5bb1f548d891fe11e82c08cf5f1a1d74e225298f60082e5aeb2469ada0", size = 63905, upload-time = "2024-03-20T16:59:37.385Z" } + +[[package]] +name = "soxr" +version = "1.0.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/7e/f4b461944662ad75036df65277d6130f9411002bfb79e9df7dff40a31db9/soxr-1.0.0.tar.gz", hash = "sha256:e07ee6c1d659bc6957034f4800c60cb8b98de798823e34d2a2bba1caa85a4509", size = 171415, upload-time = "2025-09-07T13:22:21.317Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/c7/f92b81f1a151c13afb114f57799b86da9330bec844ea5a0d3fe6a8732678/soxr-1.0.0-cp312-abi3-macosx_10_14_x86_64.whl", hash = "sha256:abecf4e39017f3fadb5e051637c272ae5778d838e5c3926a35db36a53e3a607f", size = 205508, upload-time = "2025-09-07T13:22:01.252Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1d/c945fea9d83ea1f2be9d116b3674dbaef26ed090374a77c394b31e3b083b/soxr-1.0.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:e973d487ee46aa8023ca00a139db6e09af053a37a032fe22f9ff0cc2e19c94b4", size = 163568, upload-time = "2025-09-07T13:22:03.558Z" }, + { url = "https://files.pythonhosted.org/packages/b5/80/10640970998a1d2199bef6c4d92205f36968cddaf3e4d0e9fe35ddd405bd/soxr-1.0.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8ce273cca101aff3d8c387db5a5a41001ba76ef1837883438d3c652507a9ccc", size = 204707, upload-time = "2025-09-07T13:22:05.125Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/2726603c13c2126cb8ded9e57381b7377f4f0df6ba4408e1af5ddbfdc3dd/soxr-1.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8f2a69686f2856d37823bbb7b78c3d44904f311fe70ba49b893af11d6b6047b", size = 238032, upload-time = "2025-09-07T13:22:06.428Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/530252227f4d0721a5524a936336485dfb429bb206a66baf8e470384f4a2/soxr-1.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:2a3b77b115ae7c478eecdbd060ed4f61beda542dfb70639177ac263aceda42a2", size = 172070, upload-time = "2025-09-07T13:22:07.62Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.44" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or (platform_machine == 'x86_64' and sys_platform != 'darwin')" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/c4/59c7c9b068e6813c898b771204aad36683c96318ed12d4233e1b18762164/sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250", size = 2139675, upload-time = "2025-10-10T16:03:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ae/eeb0920537a6f9c5a3708e4a5fc55af25900216bdb4847ec29cfddf3bf3a/sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29", size = 2127726, upload-time = "2025-10-10T16:03:35.934Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d5/2ebbabe0379418eda8041c06b0b551f213576bfe4c2f09d77c06c07c8cc5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44", size = 3327603, upload-time = "2025-10-10T15:35:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/5aa65852dadc24b7d8ae75b7efb8d19303ed6ac93482e60c44a585930ea5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1", size = 3337842, upload-time = "2025-10-10T15:43:45.431Z" }, + { url = "https://files.pythonhosted.org/packages/41/92/648f1afd3f20b71e880ca797a960f638d39d243e233a7082c93093c22378/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7", size = 3264558, upload-time = "2025-10-10T15:35:29.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/e27d7ee61a10f74b17740918e23cbc5bc62011b48282170dc4c66da8ec0f/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d", size = 3301570, upload-time = "2025-10-10T15:43:48.407Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3d/3116a9a7b63e780fb402799b6da227435be878b6846b192f076d2f838654/sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4", size = 2103447, upload-time = "2025-10-10T15:03:21.678Z" }, + { url = "https://files.pythonhosted.org/packages/25/83/24690e9dfc241e6ab062df82cc0df7f4231c79ba98b273fa496fb3dd78ed/sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e", size = 2130912, upload-time = "2025-10-10T15:03:24.656Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, +] + [[package]] name = "srt" version = "3.5.3" @@ -2229,6 +3792,20 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/94/d9/d326f9dbbb7da6806aa8cfc080342e5f78dc33552f4339bdc8a6251d11a3/stable_ts-2.19.1.tar.gz", hash = "sha256:0ecaf1ed93e029839569618d2da9a57b883ad04db21f0680146e0650caaf4f52", size = 189132, upload-time = "2025-08-16T16:53:48.811Z" } +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "asttokens", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "executing", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pure-eval", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + [[package]] name = "stempeg" version = "0.2.4" @@ -2242,6 +3819,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/07/ce07799f7cc0af533d58b5380957638cc049d66a6a18736094b78cd08d1a/stempeg-0.2.4-py3-none-any.whl", hash = "sha256:83c9e4ac73edcc61a2a807eded0ae2c9f0b99ea3110e46756b3fff153a063838", size = 963032, upload-time = "2025-05-28T08:42:11.181Z" }, ] +[[package]] +name = "strenum" +version = "0.4.15" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff", size = 23384, upload-time = "2023-06-29T22:02:58.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851, upload-time = "2023-06-29T22:02:56.947Z" }, +] + [[package]] name = "submitit" version = "1.5.3" @@ -2266,6 +3852,87 @@ wheels = [ { url = "https://download.pytorch.org/whl/sympy-1.14.0-py3-none-any.whl" }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tensorboard" +version = "2.20.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "absl-py", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "grpcio", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "markdown", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pillow", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "protobuf", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "tensorboard-data-server", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "werkzeug", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple/" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, +] + +[[package]] +name = "termcolor" +version = "3.2.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/87/56/ab275c2b56a5e2342568838f0d5e3e66a32354adcc159b495e374cda43f5/termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58", size = 14423, upload-time = "2025-10-25T19:11:42.586Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/d5/141f53d7c1eb2a80e6d3e9a390228c3222c27705cbe7f048d3623053f3ca/termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6", size = 7698, upload-time = "2025-10-25T19:11:41.536Z" }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +name = "texterrors" +version = "0.5.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "levenshtein", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "loguru", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "plac", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pybind11", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "regex", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "termcolor", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/47/9a391643961698df3c804172f005e8b56c9693c14c4170abd9d3c961e971/texterrors-0.5.1.tar.gz", hash = "sha256:7fa24b2ca6ed5e05681b5cfdbb6c1fd0e4ae6518f8939e9782294f620d4eb3b1", size = 23813, upload-time = "2024-06-19T15:43:06.889Z" } + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + [[package]] name = "tiktoken" version = "0.12.0" @@ -2287,27 +3954,27 @@ wheels = [ [[package]] name = "tokenizers" -version = "0.22.1" +version = "0.21.4" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/46/fb6854cec3278fbfa4a75b50232c77622bc517ac886156e6afbfa4d8fc6e/tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9", size = 363123, upload-time = "2025-09-19T09:49:23.424Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/33/f4b2d94ada7ab297328fc671fed209368ddb82f965ec2224eb1892674c3a/tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73", size = 3069318, upload-time = "2025-09-19T09:49:11.848Z" }, - { url = "https://files.pythonhosted.org/packages/1c/58/2aa8c874d02b974990e89ff95826a4852a8b2a273c7d1b4411cdd45a4565/tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc", size = 2926478, upload-time = "2025-09-19T09:49:09.759Z" }, - { url = "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a", size = 3256994, upload-time = "2025-09-19T09:48:56.701Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/fbfecf42f67d9b7b80fde4aabb2b3110a97fac6585c9470b5bff103a80cb/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7", size = 3153141, upload-time = "2025-09-19T09:48:59.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/a9/b38f4e74e0817af8f8ef925507c63c6ae8171e3c4cb2d5d4624bf58fca69/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21", size = 3508049, upload-time = "2025-09-19T09:49:05.868Z" }, - { url = "https://files.pythonhosted.org/packages/d2/48/dd2b3dac46bb9134a88e35d72e1aa4869579eacc1a27238f1577270773ff/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214", size = 3710730, upload-time = "2025-09-19T09:49:01.832Z" }, - { url = "https://files.pythonhosted.org/packages/93/0e/ccabc8d16ae4ba84a55d41345207c1e2ea88784651a5a487547d80851398/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f", size = 3412560, upload-time = "2025-09-19T09:49:03.867Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c6/dc3a0db5a6766416c32c034286d7c2d406da1f498e4de04ab1b8959edd00/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4", size = 3250221, upload-time = "2025-09-19T09:49:07.664Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a6/2c8486eef79671601ff57b093889a345dd3d576713ef047776015dc66de7/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879", size = 9345569, upload-time = "2025-09-19T09:49:14.214Z" }, - { url = "https://files.pythonhosted.org/packages/6b/16/32ce667f14c35537f5f605fe9bea3e415ea1b0a646389d2295ec348d5657/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446", size = 9271599, upload-time = "2025-09-19T09:49:16.639Z" }, - { url = "https://files.pythonhosted.org/packages/51/7c/a5f7898a3f6baa3fc2685c705e04c98c1094c523051c805cdd9306b8f87e/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a", size = 9533862, upload-time = "2025-09-19T09:49:19.146Z" }, - { url = "https://files.pythonhosted.org/packages/36/65/7e75caea90bc73c1dd8d40438adf1a7bc26af3b8d0a6705ea190462506e1/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390", size = 9681250, upload-time = "2025-09-19T09:49:21.501Z" }, - { url = "https://files.pythonhosted.org/packages/30/2c/959dddef581b46e6209da82df3b78471e96260e2bc463f89d23b1bf0e52a/tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82", size = 2472003, upload-time = "2025-09-19T09:49:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138", size = 2674684, upload-time = "2025-09-19T09:49:24.953Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" }, + { url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" }, + { url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" }, + { url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" }, + { url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" }, + { url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, ] [[package]] @@ -2328,6 +3995,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, ] +[[package]] +name = "toolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, +] + [[package]] name = "torch" version = "2.2.2" @@ -2373,7 +4049,8 @@ name = "torch" version = "2.7.1+cu128" source = { registry = "https://download.pytorch.org/whl/cu128" } resolution-markers = [ - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", + "sys_platform != 'darwin' and sys_platform != 'linux'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", ] dependencies = [ @@ -2454,7 +4131,8 @@ version = "2.7.1+cu128" source = { registry = "https://download.pytorch.org/whl/cu128" } resolution-markers = [ "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", + "sys_platform != 'darwin' and sys_platform != 'linux'", ] dependencies = [ { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, @@ -2465,6 +4143,22 @@ wheels = [ { url = "https://download.pytorch.org/whl/cu128/torchaudio-2.7.1%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:4586e3106701b06a4f9377f5c1da9e1d8555e16bd58fd7d810aa3f6cf50bd713" }, ] +[[package]] +name = "torchmetrics" +version = "1.8.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "lightning-utilities", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, +] + [[package]] name = "tqdm" version = "4.67.1" @@ -2477,9 +4171,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + [[package]] name = "transformers" -version = "4.57.1" +version = "4.53.3" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "filelock" }, @@ -2493,9 +4196,9 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/68/a39307bcc4116a30b2106f2e689130a48de8bd8a1e635b5e1030e46fcd9e/transformers-4.57.1.tar.gz", hash = "sha256:f06c837959196c75039809636cd964b959f6604b75b8eeec6fdfc0440b89cc55", size = 10142511, upload-time = "2025-10-14T15:39:26.18Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/5c/49182918b58eaa0b4c954fd0e37c79fc299e5643e69d70089d0b0eb0cd9b/transformers-4.53.3.tar.gz", hash = "sha256:b2eda1a261de79b78b97f7888fe2005fc0c3fabf5dad33d52cc02983f9f675d8", size = 9197478, upload-time = "2025-07-22T07:30:51.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/d3/c16c3b3cf7655a67db1144da94b021c200ac1303f82428f2beef6c2e72bb/transformers-4.57.1-py3-none-any.whl", hash = "sha256:b10d05da8fa67dc41644dbbf9bc45a44cb86ae33da6f9295f5fbf5b7890bd267", size = 11990925, upload-time = "2025-10-14T15:39:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/d7520cc5cb69c825599042eb3a7c986fa9baa8a8d2dea9acd78e152c81e2/transformers-4.53.3-py3-none-any.whl", hash = "sha256:5aba81c92095806b6baf12df35d756cf23b66c356975fb2a7fa9e536138d7c75", size = 10826382, upload-time = "2025-07-22T07:30:48.458Z" }, ] [[package]] @@ -2528,6 +4231,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd", size = 14158, upload-time = "2025-09-11T17:07:49.886Z" }, ] +[[package]] +name = "typeguard" +version = "4.4.4" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/68/71c1a15b5f65f40e91b65da23b8224dad41349894535a97f63a52e462196/typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74", size = 75203, upload-time = "2025-06-18T09:56:07.624Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e", size = 34874, upload-time = "2025-06-18T09:56:05.999Z" }, +] + +[[package]] +name = "typer" +version = "0.20.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "click", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "rich", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "shellingham", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -2578,6 +4308,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] +[[package]] +name = "uroman" +version = "1.3.1.1" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/03/7d23f79d9b259861c31437ed76007eb8dc6f6c419b709f5b2ef37d4fa7da/uroman-1.3.1.1.tar.gz", hash = "sha256:6aaf2d5265f24f15201cbbf92c86720b2b804ac53294ce43a3307fcd242387d5", size = 896697, upload-time = "2024-06-28T06:03:34.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/e1/43722c41eebab0592c6f83410e5e35edc1d6e333f44feb0a543bd38dba3e/uroman-1.3.1.1-py3-none-any.whl", hash = "sha256:394f965f7011fd56a84aca098a6c3b50082f365324f5d94c992852137918c8f5", size = 930684, upload-time = "2024-06-28T06:03:32.578Z" }, +] + [[package]] name = "virtualenv" version = "20.35.3" @@ -2604,6 +4346,99 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/e5/7b28a123d33fc9c3d55383628fc38322c890a97dfa2c538a7638cd71d57f/vulkan-1.3.275.1-py3-none-any.whl", hash = "sha256:e1e0ddf57d3a7d19f79ebf1e192b20dbd378172b027cad4f495d961b51409586", size = 399747, upload-time = "2024-02-27T10:12:32.705Z" }, ] +[[package]] +name = "wandb" +version = "0.23.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "click", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "gitpython", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "platformdirs", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "protobuf", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pydantic", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "requests", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "sentry-sdk", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/8b/db2d44395c967cd452517311fd6ede5d1e07310769f448358d4874248512/wandb-0.23.0.tar.gz", hash = "sha256:e5f98c61a8acc3ee84583ca78057f64344162ce026b9f71cb06eea44aec27c93", size = 44413921, upload-time = "2025-11-11T21:06:30.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/61/a3220c7fa4cadfb2b2a5c09e3fa401787326584ade86d7c1f58bf1cd43bd/wandb-0.23.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:b682ec5e38fc97bd2e868ac7615a0ab4fc6a15220ee1159e87270a5ebb7a816d", size = 18992250, upload-time = "2025-11-11T21:06:03.412Z" }, + { url = "https://files.pythonhosted.org/packages/90/16/e69333cf3d11e7847f424afc6c8ae325e1f6061b2e5118d7a17f41b6525d/wandb-0.23.0-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:ec094eb71b778e77db8c188da19e52c4f96cb9d5b4421d7dc05028afc66fd7e7", size = 20045616, upload-time = "2025-11-11T21:06:07.109Z" }, + { url = "https://files.pythonhosted.org/packages/62/79/42dc6c7bb0b425775fe77f1a3f1a22d75d392841a06b43e150a3a7f2553a/wandb-0.23.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e43f1f04b98c34f407dcd2744cec0a590abce39bed14a61358287f817514a7b", size = 18758848, upload-time = "2025-11-11T21:06:09.832Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/d6ddb78334996ccfc1179444bfcfc0f37ffd07ee79bb98940466da6f68f8/wandb-0.23.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5847f98cbb3175caf5291932374410141f5bb3b7c25f9c5e562c1988ce0bf5", size = 20231493, upload-time = "2025-11-11T21:06:12.323Z" }, + { url = "https://files.pythonhosted.org/packages/52/4d/0ad6df0e750c19dabd24d2cecad0938964f69a072f05fbdab7281bec2b64/wandb-0.23.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6151355fd922539926e870be811474238c9614b96541773b990f1ce53368aef6", size = 18793473, upload-time = "2025-11-11T21:06:14.967Z" }, + { url = "https://files.pythonhosted.org/packages/f8/da/c2ba49c5573dff93dafc0acce691bb1c3d57361bf834b2f2c58e6193439b/wandb-0.23.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df62e426e448ebc44269140deb7240df474e743b12d4b1f53b753afde4aa06d4", size = 20332882, upload-time = "2025-11-11T21:06:17.865Z" }, + { url = "https://files.pythonhosted.org/packages/40/65/21bfb10ee5cd93fbcaf794958863c7e05bac4bbeb1cc1b652094aa3743a5/wandb-0.23.0-py3-none-win32.whl", hash = "sha256:6c21d3eadda17aef7df6febdffdddfb0b4835c7754435fc4fe27631724269f5c", size = 19433198, upload-time = "2025-11-11T21:06:21.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/33/cbe79e66c171204e32cf940c7fdfb8b5f7d2af7a00f301c632f3a38aa84b/wandb-0.23.0-py3-none-win_amd64.whl", hash = "sha256:b50635fa0e16e528bde25715bf446e9153368428634ca7a5dbd7a22c8ae4e915", size = 19433201, upload-time = "2025-11-11T21:06:24.607Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/5ecfae12d78ea036a746c071e4c13b54b28d641efbba61d2947c73b3e6f9/wandb-0.23.0-py3-none-win_arm64.whl", hash = "sha256:fa0181b02ce4d1993588f4a728d8b73ae487eb3cb341e6ce01c156be7a98ec72", size = 17678649, upload-time = "2025-11-11T21:06:27.289Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "webdataset" +version = "1.0.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "braceexpand", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/3a/68800d92e065cf4750ebecf973b13979c0c929b439e1293012938862038d/webdataset-1.0.2.tar.gz", hash = "sha256:7f0498be827cfa46cc5430a58768a24e2c6a410676a61be1838f53d61afdaab4", size = 80090, upload-time = "2025-06-19T23:26:21.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/00/aca6beb3658dab4ed3dbb41a78e6e7f31342e0b41d28088f205525751601/webdataset-1.0.2-py3-none-any.whl", hash = "sha256:3dbfced32b25c0d199c6b9787937b6f85742bc3c84f652c846893075c1c082d9", size = 74956, upload-time = "2025-06-19T23:26:20.354Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "markupsafe", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] + +[[package]] +name = "wget" +version = "3.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip", hash = "sha256:35e630eca2aa50ce998b9b1a127bb26b30dfee573702782aa982f875e3f16061", size = 10857, upload-time = "2015-10-22T15:26:37.51Z" } + +[[package]] +name = "whisper-normalizer" +version = "0.1.12" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "indic-numtowords", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "more-itertools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "regex", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/81/d4a23d67e9356f1c2d6fe9aa7e99f42078b5e3845b181412a5582f168af4/whisper_normalizer-0.1.12.tar.gz", hash = "sha256:484dcedbfeba2ee94cf9412d57ab1e66b847e91f80c15ffc4c6ab82ad5484b8c", size = 39630, upload-time = "2025-06-06T19:03:50.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/d7/2bf42cb3f19da0aec48052a6e3bc3a592afc182fe98c011a0e0ae5fbe1f5/whisper_normalizer-0.1.12-py3-none-any.whl", hash = "sha256:2cd7276d2599c05147a50cf86d240e6cd27623f5ccfe8b20ccea6a518274989a", size = 36748, upload-time = "2025-06-06T19:03:49.182Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + [[package]] name = "wrapt" version = "1.17.3" @@ -2623,6 +4458,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "idna", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "multidict", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "propcache", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + [[package]] name = "yt-dlp" version = "2025.10.22" diff --git a/whisper_diarization b/whisper_diarization new file mode 160000 index 00000000..fcbd1930 --- /dev/null +++ b/whisper_diarization @@ -0,0 +1 @@ +Subproject commit fcbd1930d8a2fb2dc4e7cd0b7a0f2bffb786e8d3 From 97408c6a98fab1658f863450037562e6d98b4958 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Wed, 3 Dec 2025 21:39:00 +0200 Subject: [PATCH 13/73] Fixes for app cleanup during close (#1298) --- .github/workflows/ci.yml | 15 ++++++- buzz/buzz.py | 4 ++ buzz/db/db.py | 8 ++++ buzz/transcriber/whisper_file_transcriber.py | 26 +++++++------ buzz/widgets/application.py | 8 +++- buzz/widgets/main_window.py | 39 +++++++++++++++++-- .../speaker_identification_widget.py | 25 ++++++++++++ 7 files changed, 106 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54e7158d..7cf8c250 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,8 +67,9 @@ jobs: ~/Library/Caches/Buzz ~/.cache/whisper ~/.cache/huggingface + ~/.cache/Buzz ~/AppData/Local/Buzz/Buzz/Cache - key: whisper-models + key: whisper-models-${{ runner.os }} - uses: AnimMouse/setup-ffmpeg@v1 id: setup-ffmpeg @@ -88,7 +89,13 @@ jobs: if [ "$(lsb_release -rs)" == "22.04" ]; then sudo apt-get install libegl1-mesa + + # Add ubuntu-toolchain-r PPA for newer libstdc++6 with GLIBCXX_3.4.32 + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 libstdc++-13-dev fi + sudo apt-get install libyaml-dev libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 libportaudio2 gettext libpulse0 libgl1-mesa-dev libvulkan-dev ccache if: "startsWith(matrix.os, 'ubuntu-')" @@ -166,7 +173,13 @@ jobs: if [ "$(lsb_release -rs)" == "22.04" ]; then sudo apt-get install libegl1-mesa + + # Add ubuntu-toolchain-r PPA for newer libstdc++6 with GLIBCXX_3.4.32 + sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y + sudo apt-get update + sudo apt-get install -y gcc-13 g++-13 libstdc++-13-dev fi + sudo apt-get install libyaml-dev libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-shape0 libxcb-cursor0 libportaudio2 gettext libpulse0 libgl1-mesa-dev libvulkan-dev ccache if: "startsWith(matrix.os, 'ubuntu-')" diff --git a/buzz/buzz.py b/buzz/buzz.py index 6c4750d6..289304b3 100644 --- a/buzz/buzz.py +++ b/buzz/buzz.py @@ -8,6 +8,9 @@ from typing import TextIO from platformdirs import user_log_dir, user_cache_dir, user_data_dir +# Will download all Huggingface data to the app cache directory +os.environ.setdefault("HF_HOME", user_cache_dir("Buzz")) + from buzz.assets import APP_BASE_DIR # Check for segfaults if not running in frozen mode @@ -60,6 +63,7 @@ def main(): logging.getLogger("matplotlib").setLevel(logging.WARNING) logging.getLogger("graphviz").setLevel(logging.WARNING) logging.getLogger("nemo_logger").setLevel(logging.ERROR) + logging.getLogger("nemo_logging").setLevel(logging.ERROR) logging.getLogger("numba").setLevel(logging.WARNING) logging.getLogger("torio._extension.utils").setLevel(logging.WARNING) logging.getLogger("export_config_manager").setLevel(logging.WARNING) diff --git a/buzz/db/db.py b/buzz/db/db.py index 99b4c20b..05c038bf 100644 --- a/buzz/db/db.py +++ b/buzz/db/db.py @@ -41,3 +41,11 @@ def _setup_db(path: str) -> QSqlDatabase: db.exec('PRAGMA foreign_keys = ON') logging.debug("Database connection opened: %s", db.databaseName()) return db + + +def close_app_db(): + db = QSqlDatabase.database() + if db.isOpen(): + logging.debug("Closing database connection: %s", db.databaseName()) + db.close() + QSqlDatabase.removeDatabase(QSqlDatabase.defaultConnection) diff --git a/buzz/transcriber/whisper_file_transcriber.py b/buzz/transcriber/whisper_file_transcriber.py index c5533397..08a20426 100644 --- a/buzz/transcriber/whisper_file_transcriber.py +++ b/buzz/transcriber/whisper_file_transcriber.py @@ -273,27 +273,29 @@ class WhisperFileTranscriber(FileTranscriber): if self.started_process: self.current_process.terminate() - # Use timeout to avoid hanging indefinitely + + if self.read_line_thread and self.read_line_thread.is_alive(): + self.read_line_thread.join(timeout=5) + if self.read_line_thread.is_alive(): + logging.warning("Read line thread still alive after 5s") + self.current_process.join(timeout=10) if self.current_process.is_alive(): logging.warning("Process didn't terminate gracefully, force killing") self.current_process.kill() self.current_process.join(timeout=5) - - # Close pipes to unblock the read_line thread + try: - if hasattr(self, 'send_pipe'): + if hasattr(self, 'send_pipe') and self.send_pipe: self.send_pipe.close() - if hasattr(self, 'recv_pipe'): + except Exception as e: + logging.debug(f"Error closing send_pipe: {e}") + + try: + if hasattr(self, 'recv_pipe') and self.recv_pipe: self.recv_pipe.close() except Exception as e: - logging.debug(f"Error closing pipes: {e}") - - # Join read_line_thread with timeout to prevent hanging - if self.read_line_thread and self.read_line_thread.is_alive(): - self.read_line_thread.join(timeout=5) - if self.read_line_thread.is_alive(): - logging.warning("Read line thread didn't terminate gracefully") + logging.debug(f"Error closing recv_pipe: {e}") def read_line(self, pipe: Connection): while True: diff --git a/buzz/widgets/application.py b/buzz/widgets/application.py index 80c9c595..69af8f9c 100755 --- a/buzz/widgets/application.py +++ b/buzz/widgets/application.py @@ -56,9 +56,9 @@ class Application(QApplication): else: self.setFont(QFont(self.font().family(), font_size)) - db = setup_app_db() + self.db = setup_app_db() transcription_service = TranscriptionService( - TranscriptionDAO(db), TranscriptionSegmentDAO(db) + TranscriptionDAO(self.db), TranscriptionSegmentDAO(self.db) ) self.window = MainWindow(transcription_service) @@ -91,3 +91,7 @@ class Application(QApplication): def add_task(self, task: FileTranscriptionTask, quit_on_complete: bool = False): self.window.quit_on_complete = quit_on_complete self.window.add_task(task) + + def close_database(self): + from buzz.db.db import close_app_db + close_app_db() diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 8c605f94..306bc3f8 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -421,19 +421,50 @@ class MainWindow(QMainWindow): self.save_geometry() def closeEvent(self, event: QtGui.QCloseEvent) -> None: + logging.debug("Starting MainWindow closeEvent") + self.save_geometry() + self.settings.settings.sync() + + if self.folder_watcher: + try: + self.folder_watcher.task_found.disconnect() + if len(self.folder_watcher.directories()) > 0: + self.folder_watcher.removePaths(self.folder_watcher.directories()) + except Exception as e: + logging.warning(f"Error cleaning up folder watcher: {e}") + + try: + self.transcriber_worker.task_started.disconnect() + self.transcriber_worker.task_progress.disconnect() + self.transcriber_worker.task_download_progress.disconnect() + self.transcriber_worker.task_error.disconnect() + self.transcriber_worker.task_completed.disconnect() + except Exception as e: + logging.warning(f"Error disconnecting signals: {e}") self.transcriber_worker.stop() self.transcriber_thread.quit() - # Only wait if thread is actually running + if self.transcriber_thread.isRunning(): - if not self.transcriber_thread.wait(5000): # Wait up to 5 seconds - logging.warning("Transcriber thread did not finish within timeout") + if not self.transcriber_thread.wait(10000): + logging.warning("Transcriber thread did not finish within 10s timeout, terminating") + self.transcriber_thread.terminate() + if not self.transcriber_thread.wait(2000): + logging.error("Transcriber thread could not be terminated") if self.transcription_viewer_widget is not None: self.transcription_viewer_widget.close() - logging.debug("Closing MainWindow") + try: + from buzz.widgets.application import Application + app = Application.instance() + if app and hasattr(app, 'close_database'): + app.close_database() + except Exception as e: + logging.warning(f"Error closing database: {e}") + + logging.debug("MainWindow closeEvent completed") super().closeEvent(event) diff --git a/buzz/widgets/transcription_viewer/speaker_identification_widget.py b/buzz/widgets/transcription_viewer/speaker_identification_widget.py index cbbe6216..97bdce3d 100644 --- a/buzz/widgets/transcription_viewer/speaker_identification_widget.py +++ b/buzz/widgets/transcription_viewer/speaker_identification_widget.py @@ -150,6 +150,15 @@ class IdentificationWorker(QObject): # Step 3 - Diarization self.progress_update.emit(_("6/8 Identifying speakers")) + # Silence NeMo's verbose logging + logging.getLogger("nemo_logging").setLevel(logging.ERROR) + try: + # Also try to silence NeMo's internal logging system + from nemo.utils import logging as nemo_logging + nemo_logging.setLevel(logging.ERROR) + except (ImportError, AttributeError): + pass + try: diarizer_model = MSDDDiarizer(device) speaker_ts = diarizer_model.diarize(torch.from_numpy(audio_waveform).unsqueeze(0)) @@ -228,6 +237,7 @@ class SpeakerIdentificationWidget(QWidget): self.thread = None self.worker = None + self.needs_layout_update = False self.setMinimumWidth(650) self.setMinimumHeight(400) @@ -403,6 +413,12 @@ class SpeakerIdentificationWidget(QWidget): self.speaker_preview_row.addLayout(speaker_layout) + # Trigger layout update to properly size the new widgets + self.layout().activate() + self.adjustSize() + # Schedule update if window is minimized + self.needs_layout_update = True + def on_speaker_preview(self, speaker_id): if self.player_timer: self.player_timer.stop() @@ -498,6 +514,15 @@ class SpeakerIdentificationWidget(QWidget): self.close() + def changeEvent(self, event): + super().changeEvent(event) + + # Handle window activation (restored from minimized or brought to front) + if self.needs_layout_update: + self.layout().activate() + self.adjustSize() + self.needs_layout_update = False + def closeEvent(self, event): self.hide() From 454a03bb59952d086981f364341f8bfc3bcd86e1 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Thu, 4 Dec 2025 09:41:08 +0200 Subject: [PATCH 14/73] Fix for app cleanup (#1299) --- .github/workflows/ci.yml | 12 ------------ buzz/db/db.py | 8 +++++--- buzz/settings/settings.py | 1 - buzz/widgets/application.py | 1 + 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cf8c250..fe24f1b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,18 +59,6 @@ jobs: path: .venv key: venv-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/uv.lock') }} - - name: Load cached Whisper models - id: cached-whisper-models - uses: actions/cache@v4 - with: - path: | - ~/Library/Caches/Buzz - ~/.cache/whisper - ~/.cache/huggingface - ~/.cache/Buzz - ~/AppData/Local/Buzz/Buzz/Cache - key: whisper-models-${{ runner.os }} - - uses: AnimMouse/setup-ffmpeg@v1 id: setup-ffmpeg with: diff --git a/buzz/db/db.py b/buzz/db/db.py index 05c038bf..692e25c9 100644 --- a/buzz/db/db.py +++ b/buzz/db/db.py @@ -45,7 +45,9 @@ def _setup_db(path: str) -> QSqlDatabase: def close_app_db(): db = QSqlDatabase.database() + if not db.isValid(): + return + if db.isOpen(): - logging.debug("Closing database connection: %s", db.databaseName()) - db.close() - QSqlDatabase.removeDatabase(QSqlDatabase.defaultConnection) + logging.debug("Closing database connection: %s", db.connectionName()) + db.close() \ No newline at end of file diff --git a/buzz/settings/settings.py b/buzz/settings/settings.py index d95f79a2..3d6c46ac 100644 --- a/buzz/settings/settings.py +++ b/buzz/settings/settings.py @@ -12,7 +12,6 @@ class Settings: def __init__(self, application=""): self.settings = QSettings(APP_NAME, application) self.settings.sync() - logging.debug(f"Settings filename: {self.settings.fileName()}") class Key(enum.Enum): RECORDING_TRANSCRIBER_TASK = "recording-transcriber/task" diff --git a/buzz/widgets/application.py b/buzz/widgets/application.py index 69af8f9c..571257a5 100755 --- a/buzz/widgets/application.py +++ b/buzz/widgets/application.py @@ -39,6 +39,7 @@ class Application(QApplication): self.setStyle(QStyleFactory.create("Fusion")) self.settings = Settings() + logging.debug(f"Settings filename: {self.settings.settings.fileName()}") # Set BUZZ_FORCE_CPU environment variable if Force CPU setting is enabled force_cpu_enabled = self.settings.value( From 5eea1fe72130304f831c55546c30aa50246b97a3 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Fri, 5 Dec 2025 22:57:14 +0200 Subject: [PATCH 15/73] Fix speech separation (#1301) --- .gitmodules | 4 +- buzz/file_transcriber_queue_worker.py | 16 +- demucs/.github/ISSUE_TEMPLATE/bug.md | 33 - demucs/.github/ISSUE_TEMPLATE/question.md | 10 - demucs/.github/workflows/linter.yml | 36 - demucs/.github/workflows/tests.yml | 36 - demucs/.gitignore | 17 - demucs/CODE_OF_CONDUCT.md | 76 -- demucs/CONTRIBUTING.md | 23 - demucs/Demucs.ipynb | 153 ---- demucs/LICENSE | 21 - demucs/MANIFEST.in | 13 - demucs/Makefile | 36 - demucs/README.md | 319 ------- demucs/conf/config.yaml | 304 ------- demucs/conf/dset/aetl.yaml | 19 - demucs/conf/dset/auto_extra_test.yaml | 18 - demucs/conf/dset/auto_mus.yaml | 20 - demucs/conf/dset/extra44.yaml | 8 - demucs/conf/dset/extra_mmi_goodclean.yaml | 12 - demucs/conf/dset/extra_test.yaml | 12 - demucs/conf/dset/musdb44.yaml | 5 - demucs/conf/dset/sdx23_bleeding.yaml | 10 - demucs/conf/dset/sdx23_labelnoise.yaml | 10 - demucs/conf/svd/base.yaml | 14 - demucs/conf/svd/base2.yaml | 14 - demucs/conf/svd/default.yaml | 1 - demucs/conf/variant/default.yaml | 1 - demucs/conf/variant/example.yaml | 5 - demucs/conf/variant/finetune.yaml | 19 - demucs/demucs.png | Bin 339294 -> 0 bytes demucs/demucs/__init__.py | 7 - demucs/demucs/__main__.py | 10 - demucs/demucs/api.py | 393 -------- demucs/demucs/apply.py | 322 ------- demucs/demucs/audio.py | 266 ------ demucs/demucs/audio_legacy.py | 17 - demucs/demucs/augment.py | 111 --- demucs/demucs/demucs.py | 447 ---------- demucs/demucs/distrib.py | 100 --- demucs/demucs/ema.py | 66 -- demucs/demucs/evaluate.py | 174 ---- demucs/demucs/grids/__init__.py | 0 demucs/demucs/grids/_explorers.py | 64 -- demucs/demucs/grids/mdx.py | 33 - demucs/demucs/grids/mdx_extra.py | 36 - demucs/demucs/grids/mdx_refine.py | 34 - demucs/demucs/grids/mmi.py | 69 -- demucs/demucs/grids/mmi_ft.py | 55 -- demucs/demucs/grids/repro.py | 50 -- demucs/demucs/grids/repro_ft.py | 46 - demucs/demucs/grids/sdx23.py | 19 - demucs/demucs/hdemucs.py | 796 ----------------- demucs/demucs/htdemucs.py | 661 -------------- demucs/demucs/pretrained.py | 98 -- demucs/demucs/py.typed | 0 demucs/demucs/remote/files.txt | 32 - demucs/demucs/remote/hdemucs_mmi.yaml | 2 - demucs/demucs/remote/htdemucs.yaml | 1 - demucs/demucs/remote/htdemucs_6s.yaml | 1 - demucs/demucs/remote/htdemucs_ft.yaml | 7 - demucs/demucs/remote/mdx.yaml | 8 - demucs/demucs/remote/mdx_extra.yaml | 2 - demucs/demucs/remote/mdx_extra_q.yaml | 2 - demucs/demucs/remote/mdx_q.yaml | 8 - demucs/demucs/remote/repro_mdx_a.yaml | 2 - .../remote/repro_mdx_a_hybrid_only.yaml | 2 - .../demucs/remote/repro_mdx_a_time_only.yaml | 2 - demucs/demucs/repitch.py | 87 -- demucs/demucs/repo.py | 166 ---- demucs/demucs/separate.py | 228 ----- demucs/demucs/solver.py | 405 --------- demucs/demucs/spec.py | 47 - demucs/demucs/states.py | 163 ---- demucs/demucs/svd.py | 83 -- demucs/demucs/train.py | 252 ------ demucs/demucs/transformer.py | 839 ------------------ demucs/demucs/utils.py | 149 ---- demucs/demucs/wav.py | 255 ------ demucs/demucs/wdemucs.py | 9 - demucs/docs/api.md | 204 ----- demucs/docs/linux.md | 28 - demucs/docs/mac.md | 28 - demucs/docs/mdx.md | 73 -- demucs/docs/release.md | 114 --- demucs/docs/sdx23.md | 61 -- demucs/docs/training.md | 290 ------ demucs/docs/windows.md | 67 -- demucs/environment-cpu.yml | 28 - demucs/environment-cuda.yml | 28 - demucs/hubconf.py | 11 - demucs/mypy.ini | 5 - demucs/outputs.tar.gz | Bin 1885 -> 0 bytes demucs/requirements.txt | 19 - demucs/requirements_minimal.txt | 10 - demucs/setup.cfg | 8 - demucs/setup.py | 75 -- demucs/test.mp3 | Bin 802480 -> 0 bytes demucs/tools/__init__.py | 5 - demucs/tools/automix.py | 343 ------- demucs/tools/bench.py | 78 -- demucs/tools/convert.py | 152 ---- demucs/tools/export.py | 71 -- demucs/tools/test_pretrained.py | 43 - demucs_repo | 1 + pyproject.toml | 6 +- snap/snapcraft.yaml | 2 +- .../file_transcriber_queue_worker_test.py | 6 +- uv.lock | 55 ++ 109 files changed, 78 insertions(+), 9624 deletions(-) delete mode 100644 demucs/.github/ISSUE_TEMPLATE/bug.md delete mode 100644 demucs/.github/ISSUE_TEMPLATE/question.md delete mode 100644 demucs/.github/workflows/linter.yml delete mode 100644 demucs/.github/workflows/tests.yml delete mode 100644 demucs/.gitignore delete mode 100644 demucs/CODE_OF_CONDUCT.md delete mode 100644 demucs/CONTRIBUTING.md delete mode 100644 demucs/Demucs.ipynb delete mode 100644 demucs/LICENSE delete mode 100644 demucs/MANIFEST.in delete mode 100644 demucs/Makefile delete mode 100644 demucs/README.md delete mode 100644 demucs/conf/config.yaml delete mode 100644 demucs/conf/dset/aetl.yaml delete mode 100644 demucs/conf/dset/auto_extra_test.yaml delete mode 100644 demucs/conf/dset/auto_mus.yaml delete mode 100644 demucs/conf/dset/extra44.yaml delete mode 100644 demucs/conf/dset/extra_mmi_goodclean.yaml delete mode 100644 demucs/conf/dset/extra_test.yaml delete mode 100644 demucs/conf/dset/musdb44.yaml delete mode 100644 demucs/conf/dset/sdx23_bleeding.yaml delete mode 100644 demucs/conf/dset/sdx23_labelnoise.yaml delete mode 100644 demucs/conf/svd/base.yaml delete mode 100644 demucs/conf/svd/base2.yaml delete mode 100644 demucs/conf/svd/default.yaml delete mode 100644 demucs/conf/variant/default.yaml delete mode 100644 demucs/conf/variant/example.yaml delete mode 100644 demucs/conf/variant/finetune.yaml delete mode 100644 demucs/demucs.png delete mode 100644 demucs/demucs/__init__.py delete mode 100644 demucs/demucs/__main__.py delete mode 100644 demucs/demucs/api.py delete mode 100644 demucs/demucs/apply.py delete mode 100644 demucs/demucs/audio.py delete mode 100644 demucs/demucs/audio_legacy.py delete mode 100644 demucs/demucs/augment.py delete mode 100644 demucs/demucs/demucs.py delete mode 100644 demucs/demucs/distrib.py delete mode 100644 demucs/demucs/ema.py delete mode 100755 demucs/demucs/evaluate.py delete mode 100644 demucs/demucs/grids/__init__.py delete mode 100644 demucs/demucs/grids/_explorers.py delete mode 100644 demucs/demucs/grids/mdx.py delete mode 100644 demucs/demucs/grids/mdx_extra.py delete mode 100644 demucs/demucs/grids/mdx_refine.py delete mode 100644 demucs/demucs/grids/mmi.py delete mode 100644 demucs/demucs/grids/mmi_ft.py delete mode 100644 demucs/demucs/grids/repro.py delete mode 100644 demucs/demucs/grids/repro_ft.py delete mode 100644 demucs/demucs/grids/sdx23.py delete mode 100644 demucs/demucs/hdemucs.py delete mode 100644 demucs/demucs/htdemucs.py delete mode 100644 demucs/demucs/pretrained.py delete mode 100644 demucs/demucs/py.typed delete mode 100644 demucs/demucs/remote/files.txt delete mode 100644 demucs/demucs/remote/hdemucs_mmi.yaml delete mode 100644 demucs/demucs/remote/htdemucs.yaml delete mode 100644 demucs/demucs/remote/htdemucs_6s.yaml delete mode 100644 demucs/demucs/remote/htdemucs_ft.yaml delete mode 100644 demucs/demucs/remote/mdx.yaml delete mode 100644 demucs/demucs/remote/mdx_extra.yaml delete mode 100644 demucs/demucs/remote/mdx_extra_q.yaml delete mode 100644 demucs/demucs/remote/mdx_q.yaml delete mode 100644 demucs/demucs/remote/repro_mdx_a.yaml delete mode 100644 demucs/demucs/remote/repro_mdx_a_hybrid_only.yaml delete mode 100644 demucs/demucs/remote/repro_mdx_a_time_only.yaml delete mode 100644 demucs/demucs/repitch.py delete mode 100644 demucs/demucs/repo.py delete mode 100644 demucs/demucs/separate.py delete mode 100644 demucs/demucs/solver.py delete mode 100644 demucs/demucs/spec.py delete mode 100644 demucs/demucs/states.py delete mode 100644 demucs/demucs/svd.py delete mode 100644 demucs/demucs/train.py delete mode 100644 demucs/demucs/transformer.py delete mode 100755 demucs/demucs/utils.py delete mode 100644 demucs/demucs/wav.py delete mode 100644 demucs/demucs/wdemucs.py delete mode 100644 demucs/docs/api.md delete mode 100644 demucs/docs/linux.md delete mode 100644 demucs/docs/mac.md delete mode 100644 demucs/docs/mdx.md delete mode 100644 demucs/docs/release.md delete mode 100644 demucs/docs/sdx23.md delete mode 100644 demucs/docs/training.md delete mode 100644 demucs/docs/windows.md delete mode 100644 demucs/environment-cpu.yml delete mode 100644 demucs/environment-cuda.yml delete mode 100644 demucs/hubconf.py delete mode 100644 demucs/mypy.ini delete mode 100644 demucs/outputs.tar.gz delete mode 100644 demucs/requirements.txt delete mode 100644 demucs/requirements_minimal.txt delete mode 100644 demucs/setup.cfg delete mode 100644 demucs/setup.py delete mode 100644 demucs/test.mp3 delete mode 100644 demucs/tools/__init__.py delete mode 100644 demucs/tools/automix.py delete mode 100644 demucs/tools/bench.py delete mode 100644 demucs/tools/convert.py delete mode 100644 demucs/tools/export.py delete mode 100644 demucs/tools/test_pretrained.py create mode 160000 demucs_repo diff --git a/.gitmodules b/.gitmodules index 1c0c8b24..5ce5bc73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,8 +4,8 @@ [submodule "whisper_diarization"] path = whisper_diarization url = https://github.com/MahmoudAshraf97/whisper-diarization -[submodule "demucs"] - path = demucs +[submodule "demucs_repo"] + path = demucs_repo url = https://github.com/MahmoudAshraf97/demucs.git [submodule "deepmultilingualpunctuation"] path = deepmultilingualpunctuation diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index b056981f..88d62eaa 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -7,7 +7,7 @@ from uuid import UUID from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot -from demucs.demucs import api as demucsApi +from demucs import api as demucsApi from buzz.model_loader import ModelType from buzz.transcriber.file_transcriber import FileTranscriber @@ -37,6 +37,7 @@ class FileTranscriberQueueWorker(QObject): self.tasks_queue = queue.Queue() self.canceled_tasks: Set[UUID] = set() self.current_transcriber = None + self.speech_path = None @pyqtSlot() def run(self): @@ -75,10 +76,10 @@ class FileTranscriberQueueWorker(QObject): _, separated = separator.separate_audio_file(Path(self.current_task.file_path)) task_file_path = Path(self.current_task.file_path) - speech_path = task_file_path.with_name(f"{task_file_path.stem}_speech.mp3") - demucsApi.save_audio(separated["vocals"], speech_path, separator.samplerate) + self.speech_path = task_file_path.with_name(f"{task_file_path.stem}_speech.mp3") + demucsApi.save_audio(separated["vocals"], self.speech_path, separator.samplerate) - self.current_task.file_path = str(speech_path) + self.current_task.file_path = str(self.speech_path) except Exception as e: logging.error(f"Error during speech extraction: {e}", exc_info=True) @@ -166,6 +167,13 @@ class FileTranscriberQueueWorker(QObject): if self.current_task is not None: self.task_completed.emit(self.current_task, segments) + if self.speech_path is not None: + try: + Path(self.speech_path).unlink() + except Exception as e: + logging.error(f"Error deleting temporary speech file: {e}", exc_info=True) + self.speech_path = None + def stop(self): self.tasks_queue.put(None) if self.current_transcriber is not None: diff --git a/demucs/.github/ISSUE_TEMPLATE/bug.md b/demucs/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index 217654a9..00000000 --- a/demucs/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: 🐛 Bug Report -about: Submit a bug report to help us improve -labels: 'bug' ---- - -## 🐛 Bug Report - -(A clear and concise description of what the bug is) - -## To Reproduce - -(Write your steps here:) - -1. Step 1... -1. Step 2... -1. Step 3... - -## Expected behavior - -(Write what you thought would happen.) - -## Actual Behavior - -(Write what happened. Add screenshots, if applicable.) - -## Your Environment - - - -- Python and PyTorch version: -- Operating system and version (desktop or mobile): -- Hardware (gpu or cpu, amount of RAM etc.): diff --git a/demucs/.github/ISSUE_TEMPLATE/question.md b/demucs/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 85a007e4..00000000 --- a/demucs/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: "❓Questions/Help/Support" -about: If you have a question about the paper, code or algorithm, please ask here! -labels: question - ---- - -## ❓ Questions - -(Please ask your question here.) diff --git a/demucs/.github/workflows/linter.yml b/demucs/.github/workflows/linter.yml deleted file mode 100644 index 64f235fb..00000000 --- a/demucs/.github/workflows/linter.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: linter -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - if: ${{ github.repository == 'facebookresearch/demucs' || github.event_name == 'workflow_dispatch' }} - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - uses: actions/cache@v2 - with: - path: env - key: env-${{ hashFiles('**/requirements.txt', '.github/workflows/*') }} - - - name: Install dependencies - run: | - python3 -m venv env - . env/bin/activate - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install '.[dev]' - - - - name: Run linter - run: | - . env/bin/activate - make linter diff --git a/demucs/.github/workflows/tests.yml b/demucs/.github/workflows/tests.yml deleted file mode 100644 index b31e3dd6..00000000 --- a/demucs/.github/workflows/tests.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: tests -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - if: ${{ github.repository == 'facebookresearch/demucs' || github.event_name == 'workflow_dispatch' }} - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - uses: actions/cache@v2 - with: - path: env - key: env-${{ hashFiles('**/requirements.txt', '.github/workflows/*') }} - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y ffmpeg - python3 -m venv env - . env/bin/activate - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Run separation test - run: | - . env/bin/activate - make test_eval diff --git a/demucs/.gitignore b/demucs/.gitignore deleted file mode 100644 index 179cf0dd..00000000 --- a/demucs/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -*.egg-info -__pycache__ -Session.vim -/build -/dist -/lab -/metadata -/notebooks -/outputs -/release -/release_models -/separated -/tests -/trash -/misc -/mdx -.mypy_cache diff --git a/demucs/CODE_OF_CONDUCT.md b/demucs/CODE_OF_CONDUCT.md deleted file mode 100644 index f049d4c5..00000000 --- a/demucs/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to make participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies within all project spaces, and it also applies when -an individual is representing the project or its community in public spaces. -Examples of representing a project or community include using an official -project e-mail address, posting via an official social media account, or acting -as an appointed representative at an online or offline event. Representation of -a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at . All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/demucs/CONTRIBUTING.md b/demucs/CONTRIBUTING.md deleted file mode 100644 index f14f4af3..00000000 --- a/demucs/CONTRIBUTING.md +++ /dev/null @@ -1,23 +0,0 @@ -# Contributing to Demucs - -## Pull Requests - -In order to accept your pull request, we need you to submit a CLA. You only need -to do this once to work on any of Facebook's open source projects. - -Complete your CLA here: - -Demucs is the implementation of a research paper. -Therefore, we do not plan on accepting many pull requests for new features. -We certainly welcome them for bug fixes. - - -## Issues - -We use GitHub issues to track public bugs. Please ensure your description is -clear and has sufficient instructions to be able to reproduce the issue. - - -## License -By contributing to this repository, you agree that your contributions will be licensed -under the LICENSE file in the root directory of this source tree. diff --git a/demucs/Demucs.ipynb b/demucs/Demucs.ipynb deleted file mode 100644 index 9ebcfd5a..00000000 --- a/demucs/Demucs.ipynb +++ /dev/null @@ -1,153 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "Be9yoh-ILfRr" - }, - "source": [ - "# Hybrid Demucs\n", - "\n", - "Feel free to use the Colab version:\n", - "https://colab.research.google.com/drive/1dC9nVxk3V_VPjUADsnFu8EiT-xnU1tGH?usp=sharing" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 139 - }, - "colab_type": "code", - "executionInfo": { - "elapsed": 12277, - "status": "ok", - "timestamp": 1583778134659, - "user": { - "displayName": "Marllus Lustosa", - "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GgLl2RbW64ZyWz3Y8IBku0zhHCMnt7fz7fEl0LTdA=s64", - "userId": "14811735256675200480" - }, - "user_tz": 180 - }, - "id": "kOjIPLlzhPfn", - "outputId": "c75f17ec-b576-4105-bc5b-c2ac9c1018a3" - }, - "outputs": [], - "source": [ - "!pip install -U demucs\n", - "# or for local development, if you have a clone of Demucs\n", - "# pip install -e ." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "5lYOzKKCKAbJ" - }, - "outputs": [], - "source": [ - "# You can use the `demucs` command line to separate tracks\n", - "!demucs test.mp3" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# You can also load directly the pretrained models,\n", - "# for instance for the MDX 2021 winning model of Track A:\n", - "from demucs import pretrained\n", - "model = pretrained.get_model('mdx')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Because `model` is a bag of 4 models, you cannot directly call it on your data,\n", - "# but the `apply_model` will know what to do of it.\n", - "import torch\n", - "from demucs.apply import apply_model\n", - "x = torch.randn(1, 2, 44100 * 10) # ten seconds of white noise for the demo\n", - "out = apply_model(model, x)[0] # shape is [S, C, T] with S the number of sources\n", - "\n", - "# So let see, where is all the white noise content is going ?\n", - "for name, source in zip(model.sources, out):\n", - " print(name, source.std() / x.std())\n", - "# The outputs are quite weird to be fair, not what I would have expected." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# now let's take a single model from the bag, and let's test it on a pure cosine\n", - "freq = 440 # in Hz\n", - "sr = model.samplerate\n", - "t = torch.arange(10 * sr).float() / sr\n", - "x = torch.cos(2 * 3.1416 * freq * t).expand(1, 2, -1)\n", - "sub_model = model.models[3]\n", - "out = sub_model(x)[0]\n", - "\n", - "# Same question where does it go?\n", - "for name, source in zip(model.sources, out):\n", - " print(name, source.std() / x.std())\n", - " \n", - "# Well now it makes much more sense, all the energy is going\n", - "# in the `other` source.\n", - "# Feel free to try lower pitch (try 80 Hz) to see what happens !" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# For training or more fun, refer to the Demucs README on our repo\n", - "# https://github.com/facebookresearch/demucs/tree/main/demucs" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "authorship_tag": "ABX9TyM9xpVr1M86NRcjtQ7g9tCx", - "collapsed_sections": [], - "name": "Demucs.ipynb", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.8" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/demucs/LICENSE b/demucs/LICENSE deleted file mode 100644 index a45a376f..00000000 --- a/demucs/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Meta Platforms, Inc. and affiliates. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/demucs/MANIFEST.in b/demucs/MANIFEST.in deleted file mode 100644 index 96e5f54f..00000000 --- a/demucs/MANIFEST.in +++ /dev/null @@ -1,13 +0,0 @@ -recursive-exclude env * -recursive-include conf *.yaml -include Makefile -include LICENSE -include demucs.png -include outputs.tar.gz -include test.mp3 -include requirements.txt -include requirements_minimal.txt -include mypy.ini -include demucs/py.typed -include demucs/remote/*.txt -include demucs/remote/*.yaml diff --git a/demucs/Makefile b/demucs/Makefile deleted file mode 100644 index 0474d587..00000000 --- a/demucs/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -all: linter tests - -linter: - flake8 demucs - mypy demucs - -tests: test_train test_eval - -test_train: tests/musdb - _DORA_TEST_PATH=/tmp/demucs python3 -m dora run --clear \ - dset.musdb=./tests/musdb dset.segment=4 dset.shift=2 epochs=2 model=demucs \ - demucs.depth=2 demucs.channels=4 test.sdr=false misc.num_workers=0 test.workers=0 \ - test.shifts=0 - -test_eval: - python3 -m demucs -n demucs_unittest test.mp3 - python3 -m demucs -n demucs_unittest --two-stems=vocals test.mp3 - python3 -m demucs -n demucs_unittest --mp3 test.mp3 - python3 -m demucs -n demucs_unittest --flac --int24 test.mp3 - python3 -m demucs -n demucs_unittest --int24 --clip-mode clamp test.mp3 - python3 -m demucs -n demucs_unittest --segment 8 test.mp3 - python3 -m demucs.api -n demucs_unittest --segment 8 test.mp3 - python3 -m demucs --list-models - -tests/musdb: - test -e tests || mkdir tests - python3 -c 'import musdb; musdb.DB("tests/tmp", download=True)' - musdbconvert tests/tmp tests/musdb - -dist: - python3 setup.py sdist - -clean: - rm -r dist build *.egg-info - -.PHONY: linter dist test_train test_eval diff --git a/demucs/README.md b/demucs/README.md deleted file mode 100644 index 1bc16ee6..00000000 --- a/demucs/README.md +++ /dev/null @@ -1,319 +0,0 @@ -# Demucs Music Source Separation - -![tests badge](https://github.com/facebookresearch/demucs/workflows/tests/badge.svg) -![linter badge](https://github.com/facebookresearch/demucs/workflows/linter/badge.svg) - - -**This is the officially maintained Demucs** now that I (Alexandre Défossez) have left Meta to join [Kyutai](https://twitter.com/kyutai_labs). -Note that I'm not actively working on Demucs anymore, so expect slow replies and no new feature for now. - - - -This is the 4th release of Demucs (v4), featuring Hybrid Transformer based source separation. -**For the classic Hybrid Demucs (v3):** [Go this commit][demucs_v3]. -If you are experiencing issues and want the old Demucs back, please file an issue, and then you can get back to Demucs v3 with -`git checkout v3`. You can also go [Demucs v2][demucs_v2]. - - -Demucs is a state-of-the-art music source separation model, currently capable of separating -drums, bass, and vocals from the rest of the accompaniment. -Demucs is based on a U-Net convolutional architecture inspired by [Wave-U-Net][waveunet]. -The v4 version features [Hybrid Transformer Demucs][htdemucs], a hybrid spectrogram/waveform separation model using Transformers. -It is based on [Hybrid Demucs][hybrid_paper] (also provided in this repo), with the innermost layers -replaced by a cross-domain Transformer Encoder. This Transformer uses self-attention within each domain, -and cross-attention across domains. -The model achieves a SDR of 9.00 dB on the MUSDB HQ test set. Moreover, when using sparse attention -kernels to extend its receptive field and per source fine-tuning, we achieve state-of-the-art 9.20 dB of SDR. - -Samples are available [on our sample page](https://ai.honu.io/papers/htdemucs/index.html). -Checkout [our paper][htdemucs] for more information. -It has been trained on the [MUSDB HQ][musdb] dataset + an extra training dataset of 800 songs. -This model separates drums, bass and vocals and other stems for any song. - - -As Hybrid Transformer Demucs is brand new, it is not activated by default, you can activate it in the usual -commands described hereafter with `-n htdemucs_ft`. -The single, non fine-tuned model is provided as `-n htdemucs`, and the retrained baseline -as `-n hdemucs_mmi`. The Sparse Hybrid Transformer model decribed in our paper is not provided as its -requires custom CUDA code that is not ready for release yet. -We are also releasing an experimental 6 sources model, that adds a `guitar` and `piano` source. -Quick testing seems to show okay quality for `guitar`, but a lot of bleeding and artifacts for the `piano` source. - - -

    -Schema representing the structure of Hybrid Transformer Demucs,
-    with a dual U-Net structure, one branch for the temporal domain,
-    and one branch for the spectral domain. There is a cross-domain Transformer between the Encoders and Decoders.

    - - - -## Important news if you are already using Demucs - -See the [release notes](./docs/release.md) for more details. - -- 22/02/2023: added support for the [SDX 2023 Challenge](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023), - see the dedicated [doc page](./docs/sdx23.md) -- 07/12/2022: Demucs v4 now on PyPI. **htdemucs** model now used by default. Also releasing - a 6 sources models (adding `guitar` and `piano`, although the latter doesn't work so well at the moment). -- 16/11/2022: Added the new **Hybrid Transformer Demucs v4** models. - Adding support for the [torchaudio implementation of HDemucs](https://pytorch.org/audio/stable/tutorials/hybrid_demucs_tutorial.html). -- 30/08/2022: added reproducibility and ablation grids, along with an updated version of the paper. -- 17/08/2022: Releasing v3.0.5: Set split segment length to reduce memory. Compatible with pyTorch 1.12. -- 24/02/2022: Releasing v3.0.4: split into two stems (i.e. karaoke mode). - Export as float32 or int24. -- 17/12/2021: Releasing v3.0.3: bug fixes (thanks @keunwoochoi), memory drastically - reduced on GPU (thanks @famzah) and new multi-core evaluation on CPU (`-j` flag). -- 12/11/2021: Releasing **Demucs v3** with hybrid domain separation. Strong improvements - on all sources. This is the model that won Sony MDX challenge. -- 11/05/2021: Adding support for MusDB-HQ and arbitrary wav set, for the MDX challenge. For more information -on joining the challenge with Demucs see [the Demucs MDX instructions](docs/mdx.md) - - -## Comparison with other models - -We provide hereafter a summary of the different metrics presented in the paper. -You can also compare Hybrid Demucs (v3), [KUIELAB-MDX-Net][kuielab], [Spleeter][spleeter], Open-Unmix, Demucs (v1), and Conv-Tasnet on one of my favorite -songs on my [soundcloud playlist][soundcloud]. - -### Comparison of accuracy - -`Overall SDR` is the mean of the SDR for each of the 4 sources, `MOS Quality` is a rating from 1 to 5 -of the naturalness and absence of artifacts given by human listeners (5 = no artifacts), `MOS Contamination` -is a rating from 1 to 5 with 5 being zero contamination by other sources. We refer the reader to our [paper][hybrid_paper], -for more details. - -| Model | Domain | Extra data? | Overall SDR | MOS Quality | MOS Contamination | -|------------------------------|-------------|-------------------|-------------|-------------|-------------------| -| [Wave-U-Net][waveunet] | waveform | no | 3.2 | - | - | -| [Open-Unmix][openunmix] | spectrogram | no | 5.3 | - | - | -| [D3Net][d3net] | spectrogram | no | 6.0 | - | - | -| [Conv-Tasnet][demucs_v2] | waveform | no | 5.7 | - | | -| [Demucs (v2)][demucs_v2] | waveform | no | 6.3 | 2.37 | 2.36 | -| [ResUNetDecouple+][decouple] | spectrogram | no | 6.7 | - | - | -| [KUIELAB-MDX-Net][kuielab] | hybrid | no | 7.5 | **2.86** | 2.55 | -| [Band-Spit RNN][bandsplit] | spectrogram | no | **8.2** | - | - | -| **Hybrid Demucs (v3)** | hybrid | no | 7.7 | **2.83** | **3.04** | -| [MMDenseLSTM][mmdenselstm] | spectrogram | 804 songs | 6.0 | - | - | -| [D3Net][d3net] | spectrogram | 1.5k songs | 6.7 | - | - | -| [Spleeter][spleeter] | spectrogram | 25k songs | 5.9 | - | - | -| [Band-Spit RNN][bandsplit] | spectrogram | 1.7k (mixes only) | **9.0** | - | - | -| **HT Demucs f.t. (v4)** | hybrid | 800 songs | **9.0** | - | - | - - - -## Requirements - -You will need at least Python 3.8. See `requirements_minimal.txt` for requirements for separation only, -and `environment-[cpu|cuda].yml` (or `requirements.txt`) if you want to train a new model. - -### For Windows users - -Everytime you see `python3`, replace it with `python.exe`. You should always run commands from the -Anaconda console. - -### For musicians - -If you just want to use Demucs to separate tracks, you can install it with - -```bash -python3 -m pip install -U demucs -``` - -For bleeding edge versions, you can install directly from this repo using -```bash -python3 -m pip install -U git+https://github.com/facebookresearch/demucs#egg=demucs -``` - -Advanced OS support are provided on the following page, **you must read the page for your OS before posting an issues**: -- **If you are using Windows:** [Windows support](docs/windows.md). -- **If you are using macOS:** [macOS support](docs/mac.md). -- **If you are using Linux:** [Linux support](docs/linux.md). - -### For machine learning scientists - -If you have anaconda installed, you can run from the root of this repository: - -```bash -conda env update -f environment-cpu.yml # if you don't have GPUs -conda env update -f environment-cuda.yml # if you have GPUs -conda activate demucs -pip install -e . -``` - -This will create a `demucs` environment with all the dependencies installed. - -You will also need to install [soundstretch/soundtouch](https://www.surina.net/soundtouch/soundstretch.html): on macOS you can do `brew install sound-touch`, -and on Ubuntu `sudo apt-get install soundstretch`. This is used for the -pitch/tempo augmentation. - - -### Running in Docker - -Thanks to @xserrat, there is now a Docker image definition ready for using Demucs. This can ensure all libraries are correctly installed without interfering with the host OS. See his repo [Docker Facebook Demucs](https://github.com/xserrat/docker-facebook-demucs) for more information. - - -### Running from Colab - -I made a Colab to easily separate track with Demucs. Note that -transfer speeds with Colab are a bit slow for large media files, -but it will allow you to use Demucs without installing anything. - -[Demucs on Google Colab](https://colab.research.google.com/drive/1dC9nVxk3V_VPjUADsnFu8EiT-xnU1tGH?usp=sharing) - -### Web Demo - -Integrated to [Hugging Face Spaces](https://huggingface.co/spaces) with [Gradio](https://github.com/gradio-app/gradio). See demo: [![Hugging Face Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)](https://huggingface.co/spaces/akhaliq/demucs) - -### Graphical Interface - -@CarlGao4 has released a GUI for Demucs: [CarlGao4/Demucs-Gui](https://github.com/CarlGao4/Demucs-Gui). Downloads for Windows and macOS is available [here](https://github.com/CarlGao4/Demucs-Gui/releases). Use [FossHub mirror](https://fosshub.com/Demucs-GUI.html) to speed up your download. - -@Anjok07 is providing a self contained GUI in [UVR (Ultimate Vocal Remover)](https://github.com/facebookresearch/demucs/issues/334) that supports Demucs. - -### Other providers - -Audiostrip is providing free online separation with Demucs on their website [https://audiostrip.co.uk/](https://audiostrip.co.uk/). - -[MVSep](https://mvsep.com/) also provides free online separation, select `Demucs3 model B` for the best quality. - -[Neutone](https://neutone.space/) provides a realtime Demucs model in their free VST/AU plugin that can be used in your favorite DAW. - - -## Separating tracks - -In order to try Demucs, you can just run from any folder (as long as you properly installed it) - -```bash -demucs PATH_TO_AUDIO_FILE_1 [PATH_TO_AUDIO_FILE_2 ...] # for Demucs -# If you used `pip install --user` you might need to replace demucs with python3 -m demucs -python3 -m demucs --mp3 --mp3-bitrate BITRATE PATH_TO_AUDIO_FILE_1 # output files saved as MP3 - # use --mp3-preset to change encoder preset, 2 for best quality, 7 for fastest -# If your filename contain spaces don't forget to quote it !!! -demucs "my music/my favorite track.mp3" -# You can select different models with `-n` mdx_q is the quantized model, smaller but maybe a bit less accurate. -demucs -n mdx_q myfile.mp3 -# If you only want to separate vocals out of an audio, use `--two-stems=vocals` (You can also set to drums or bass) -demucs --two-stems=vocals myfile.mp3 -``` - - -If you have a GPU, but you run out of memory, please use `--segment SEGMENT` to reduce length of each split. `SEGMENT` should be changed to a integer describing the length of each segment in seconds. -A segment length of at least 10 is recommended (the bigger the number is, the more memory is required, but quality may increase). Note that the Hybrid Transformer models only support a maximum segment length of 7.8 seconds. -Creating an environment variable `PYTORCH_NO_CUDA_MEMORY_CACHING=1` is also helpful. If this still does not help, please add `-d cpu` to the command line. See the section hereafter for more details on the memory requirements for GPU acceleration. - -Separated tracks are stored in the `separated/MODEL_NAME/TRACK_NAME` folder. There you will find four stereo wav files sampled at 44.1 kHz: `drums.wav`, `bass.wav`, -`other.wav`, `vocals.wav` (or `.mp3` if you used the `--mp3` option). - -All audio formats supported by `torchaudio` can be processed (i.e. wav, mp3, flac, ogg/vorbis on Linux/macOS, etc.). On Windows, `torchaudio` has limited support, so we rely on `ffmpeg`, which should support pretty much anything. -Audio is resampled on the fly if necessary. -The output will be a wav file encoded as int16. -You can save as float32 wav files with `--float32`, or 24 bits integer wav with `--int24`. -You can pass `--mp3` to save as mp3 instead, and set the bitrate (in kbps) with `--mp3-bitrate` (default is 320). - -It can happen that the output would need clipping, in particular due to some separation artifacts. -Demucs will automatically rescale each output stem so as to avoid clipping. This can however break -the relative volume between stems. If instead you prefer hard clipping, pass `--clip-mode clamp`. -You can also try to reduce the volume of the input mixture before feeding it to Demucs. - - -Other pre-trained models can be selected with the `-n` flag. -The list of pre-trained models is: -- `htdemucs`: first version of Hybrid Transformer Demucs. Trained on MusDB + 800 songs. Default model. -- `htdemucs_ft`: fine-tuned version of `htdemucs`, separation will take 4 times more time - but might be a bit better. Same training set as `htdemucs`. -- `htdemucs_6s`: 6 sources version of `htdemucs`, with `piano` and `guitar` being added as sources. - Note that the `piano` source is not working great at the moment. -- `hdemucs_mmi`: Hybrid Demucs v3, retrained on MusDB + 800 songs. -- `mdx`: trained only on MusDB HQ, winning model on track A at the [MDX][mdx] challenge. -- `mdx_extra`: trained with extra training data (**including MusDB test set**), ranked 2nd on the track B - of the [MDX][mdx] challenge. -- `mdx_q`, `mdx_extra_q`: quantized version of the previous models. Smaller download and storage - but quality can be slightly worse. -- `SIG`: where `SIG` is a single model from the [model zoo](docs/training.md#model-zoo). - -The `--two-stems=vocals` option allows separating vocals from the rest of the accompaniment (i.e., karaoke mode). -`vocals` can be changed to any source in the selected model. -This will mix the files after separating the mix fully, so this won't be faster or use less memory. - -The `--shifts=SHIFTS` performs multiple predictions with random shifts (a.k.a the *shift trick*) of the input and average them. This makes prediction `SHIFTS` times -slower. Don't use it unless you have a GPU. - -The `--overlap` option controls the amount of overlap between prediction windows. Default is 0.25 (i.e. 25%) which is probably fine. -It can probably be reduced to 0.1 to improve a bit speed. - - -The `-j` flag allow to specify a number of parallel jobs (e.g. `demucs -j 2 myfile.mp3`). -This will multiply by the same amount the RAM used so be careful! - -### Memory requirements for GPU acceleration - -If you want to use GPU acceleration, you will need at least 3GB of RAM on your GPU for `demucs`. However, about 7GB of RAM will be required if you use the default arguments. Add `--segment SEGMENT` to change size of each split. If you only have 3GB memory, set SEGMENT to 8 (though quality may be worse if this argument is too small). Creating an environment variable `PYTORCH_NO_CUDA_MEMORY_CACHING=1` can help users with even smaller RAM such as 2GB (I separated a track that is 4 minutes but only 1.5GB is used), but this would make the separation slower. - -If you do not have enough memory on your GPU, simply add `-d cpu` to the command line to use the CPU. With Demucs, processing time should be roughly equal to 1.5 times the duration of the track. - -## Calling from another Python program - -The main function provides an `opt` parameter as a simple API. You can just pass the parsed command line as this parameter: -```python -# Assume that your command is `demucs --mp3 --two-stems vocals -n mdx_extra "track with space.mp3"` -# The following codes are same as the command above: -import demucs.separate -demucs.separate.main(["--mp3", "--two-stems", "vocals", "-n", "mdx_extra", "track with space.mp3"]) - -# Or like this -import demucs.separate -import shlex -demucs.separate.main(shlex.split('--mp3 --two-stems vocals -n mdx_extra "track with space.mp3"')) -``` - -To use more complicated APIs, see [API docs](docs/api.md) - -## Training Demucs - -If you want to train (Hybrid) Demucs, please follow the [training doc](docs/training.md). - -## MDX Challenge reproduction - -In order to reproduce the results from the Track A and Track B submissions, checkout the [MDX Hybrid Demucs submission repo][mdx_submission]. - - - -## How to cite - -``` -@inproceedings{rouard2022hybrid, - title={Hybrid Transformers for Music Source Separation}, - author={Rouard, Simon and Massa, Francisco and D{\'e}fossez, Alexandre}, - booktitle={ICASSP 23}, - year={2023} -} - -@inproceedings{defossez2021hybrid, - title={Hybrid Spectrogram and Waveform Source Separation}, - author={D{\'e}fossez, Alexandre}, - booktitle={Proceedings of the ISMIR 2021 Workshop on Music Source Separation}, - year={2021} -} -``` - -## License - -Demucs is released under the MIT license as found in the [LICENSE](LICENSE) file. - -[hybrid_paper]: https://arxiv.org/abs/2111.03600 -[waveunet]: https://github.com/f90/Wave-U-Net -[musdb]: https://sigsep.github.io/datasets/musdb.html -[openunmix]: https://github.com/sigsep/open-unmix-pytorch -[mmdenselstm]: https://arxiv.org/abs/1805.02410 -[demucs_v2]: https://github.com/facebookresearch/demucs/tree/v2 -[demucs_v3]: https://github.com/facebookresearch/demucs/tree/v3 -[spleeter]: https://github.com/deezer/spleeter -[soundcloud]: https://soundcloud.com/honualx/sets/source-separation-in-the-waveform-domain -[d3net]: https://arxiv.org/abs/2010.01733 -[mdx]: https://www.aicrowd.com/challenges/music-demixing-challenge-ismir-2021 -[kuielab]: https://github.com/kuielab/mdx-net-submission -[decouple]: https://arxiv.org/abs/2109.05418 -[mdx_submission]: https://github.com/adefossez/mdx21_demucs -[bandsplit]: https://arxiv.org/abs/2209.15174 -[htdemucs]: https://arxiv.org/abs/2211.08553 diff --git a/demucs/conf/config.yaml b/demucs/conf/config.yaml deleted file mode 100644 index d2597cb5..00000000 --- a/demucs/conf/config.yaml +++ /dev/null @@ -1,304 +0,0 @@ -defaults: - - _self_ - - dset: musdb44 - - svd: default - - variant: default - - override hydra/hydra_logging: colorlog - - override hydra/job_logging: colorlog - -dummy: -dset: - musdb: /checkpoint/defossez/datasets/musdbhq - musdb_samplerate: 44100 - use_musdb: true # set to false to not use musdb as training data. - wav: # path to custom wav dataset - wav2: # second custom wav dataset - segment: 11 - shift: 1 - train_valid: false - full_cv: true - samplerate: 44100 - channels: 2 - normalize: true - metadata: ./metadata - sources: ['drums', 'bass', 'other', 'vocals'] - valid_samples: # valid dataset size - backend: null # if provided select torchaudio backend. - -test: - save: False - best: True - workers: 2 - every: 20 - split: true - shifts: 1 - overlap: 0.25 - sdr: true - metric: 'loss' # metric used for best model selection on the valid set, can also be nsdr - nonhq: # path to non hq MusDB for evaluation - -epochs: 360 -batch_size: 64 -max_batches: # limit the number of batches per epoch, useful for debugging - # or if your dataset is gigantic. -optim: - lr: 3e-4 - momentum: 0.9 - beta2: 0.999 - loss: l1 # l1 or mse - optim: adam - weight_decay: 0 - clip_grad: 0 - -seed: 42 -debug: false -valid_apply: true -flag: -save_every: -weights: [1., 1., 1., 1.] # weights over each source for the training/valid loss. - -augment: - shift_same: false - repitch: - proba: 0.2 - max_tempo: 12 - remix: - proba: 1 - group_size: 4 - scale: - proba: 1 - min: 0.25 - max: 1.25 - flip: true - -continue_from: # continue from other XP, give the XP Dora signature. -continue_pretrained: # signature of a pretrained XP, this cannot be a bag of models. -pretrained_repo: # repo for pretrained model (default is official AWS) -continue_best: true -continue_opt: false - -misc: - num_workers: 10 - num_prints: 4 - show: false - verbose: false - -# List of decay for EMA at batch or epoch level, e.g. 0.999. -# Batch level EMA are kept on GPU for speed. -ema: - epoch: [] - batch: [] - -use_train_segment: true # to remove -model_segment: # override the segment parameter for the model, usually 4 times the training segment. -model: demucs # see demucs/train.py for the possibilities, and config for each model hereafter. -demucs: # see demucs/demucs.py for a detailed description - # Channels - channels: 64 - growth: 2 - # Main structure - depth: 6 - rewrite: true - lstm_layers: 0 - # Convolutions - kernel_size: 8 - stride: 4 - context: 1 - # Activations - gelu: true - glu: true - # Normalization - norm_groups: 4 - norm_starts: 4 - # DConv residual branch - dconv_depth: 2 - dconv_mode: 1 # 1 = branch in encoder, 2 = in decoder, 3 = in both. - dconv_comp: 4 - dconv_attn: 4 - dconv_lstm: 4 - dconv_init: 1e-4 - # Pre/post treatment - resample: true - normalize: false - # Weight init - rescale: 0.1 - -hdemucs: # see demucs/hdemucs.py for a detailed description - # Channels - channels: 48 - channels_time: - growth: 2 - # STFT - nfft: 4096 - wiener_iters: 0 - end_iters: 0 - wiener_residual: false - cac: true - # Main structure - depth: 6 - rewrite: true - hybrid: true - hybrid_old: false - # Frequency Branch - multi_freqs: [] - multi_freqs_depth: 3 - freq_emb: 0.2 - emb_scale: 10 - emb_smooth: true - # Convolutions - kernel_size: 8 - stride: 4 - time_stride: 2 - context: 1 - context_enc: 0 - # normalization - norm_starts: 4 - norm_groups: 4 - # DConv residual branch - dconv_mode: 1 - dconv_depth: 2 - dconv_comp: 4 - dconv_attn: 4 - dconv_lstm: 4 - dconv_init: 1e-3 - # Weight init - rescale: 0.1 - -# Torchaudio implementation of HDemucs -torch_hdemucs: -# Channels - channels: 48 - growth: 2 - # STFT - nfft: 4096 - # Main structure - depth: 6 - freq_emb: 0.2 - emb_scale: 10 - emb_smooth: true - # Convolutions - kernel_size: 8 - stride: 4 - time_stride: 2 - context: 1 - context_enc: 0 - # normalization - norm_starts: 4 - norm_groups: 4 - # DConv residual branch - dconv_depth: 2 - dconv_comp: 4 - dconv_attn: 4 - dconv_lstm: 4 - dconv_init: 1e-3 - -htdemucs: # see demucs/htdemucs.py for a detailed description - # Channels - channels: 48 - channels_time: - growth: 2 - # STFT - nfft: 4096 - wiener_iters: 0 - end_iters: 0 - wiener_residual: false - cac: true - # Main structure - depth: 4 - rewrite: true - # Frequency Branch - multi_freqs: [] - multi_freqs_depth: 3 - freq_emb: 0.2 - emb_scale: 10 - emb_smooth: true - # Convolutions - kernel_size: 8 - stride: 4 - time_stride: 2 - context: 1 - context_enc: 0 - # normalization - norm_starts: 4 - norm_groups: 4 - # DConv residual branch - dconv_mode: 1 - dconv_depth: 2 - dconv_comp: 8 - dconv_init: 1e-3 - # Before the Transformer - bottom_channels: 0 - # CrossTransformer - # ------ Common to all - # Regular parameters - t_layers: 5 - t_hidden_scale: 4.0 - t_heads: 8 - t_dropout: 0.0 - t_layer_scale: True - t_gelu: True - # ------------- Positional Embedding - t_emb: sin - t_max_positions: 10000 # for the scaled embedding - t_max_period: 10000.0 - t_weight_pos_embed: 1.0 - t_cape_mean_normalize: True - t_cape_augment: True - t_cape_glob_loc_scale: [5000.0, 1.0, 1.4] - t_sin_random_shift: 0 - # ------------- norm before a transformer encoder - t_norm_in: True - t_norm_in_group: False - # ------------- norm inside the encoder - t_group_norm: False - t_norm_first: True - t_norm_out: True - # ------------- optim - t_weight_decay: 0.0 - t_lr: - # ------------- sparsity - t_sparse_self_attn: False - t_sparse_cross_attn: False - t_mask_type: diag - t_mask_random_seed: 42 - t_sparse_attn_window: 400 - t_global_window: 100 - t_sparsity: 0.95 - t_auto_sparsity: False - # Cross Encoder First (False) - t_cross_first: False - # Weight init - rescale: 0.1 - -svd: # see svd.py for documentation - penalty: 0 - min_size: 0.1 - dim: 1 - niters: 2 - powm: false - proba: 1 - conv_only: false - convtr: false - bs: 1 - -quant: # quantization hyper params - diffq: # diffq penalty, typically 1e-4 or 3e-4 - qat: # use QAT with a fixed number of bits (not as good as diffq) - min_size: 0.2 - group_size: 8 - -dora: - dir: outputs - exclude: ["misc.*", "slurm.*", 'test.reval', 'flag', 'dset.backend'] - -slurm: - time: 4320 - constraint: volta32gb - setup: ['module load cudnn/v8.4.1.50-cuda.11.6 NCCL/2.11.4-6-cuda.11.6 cuda/11.6'] - -# Hydra config -hydra: - job_logging: - formatters: - colorlog: - datefmt: "%m-%d %H:%M:%S" diff --git a/demucs/conf/dset/aetl.yaml b/demucs/conf/dset/aetl.yaml deleted file mode 100644 index 7c983160..00000000 --- a/demucs/conf/dset/aetl.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# @package _global_ - -# automix dataset with Musdb, extra training data and the test set of Musdb. -# This used even more remixes than auto_extra_test. -dset: - wav: /checkpoint/defossez/datasets/aetl - samplerate: 44100 - channels: 2 -epochs: 320 -max_batches: 500 - -augment: - shift_same: true - scale: - proba: 0. - remix: - proba: 0 - repitch: - proba: 0 diff --git a/demucs/conf/dset/auto_extra_test.yaml b/demucs/conf/dset/auto_extra_test.yaml deleted file mode 100644 index 056183a5..00000000 --- a/demucs/conf/dset/auto_extra_test.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# @package _global_ - -# automix dataset with Musdb, extra training data and the test set of Musdb. -dset: - wav: /checkpoint/defossez/datasets/automix_extra_test2 - samplerate: 44100 - channels: 2 -epochs: 320 -max_batches: 500 - -augment: - shift_same: true - scale: - proba: 0. - remix: - proba: 0 - repitch: - proba: 0 diff --git a/demucs/conf/dset/auto_mus.yaml b/demucs/conf/dset/auto_mus.yaml deleted file mode 100644 index 9a2d9df5..00000000 --- a/demucs/conf/dset/auto_mus.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# @package _global_ - -# Automix dataset based on musdb train set. -dset: - wav: /checkpoint/defossez/datasets/automix_musdb - samplerate: 44100 - channels: 2 -epochs: 360 -max_batches: 300 -test: - every: 4 - -augment: - shift_same: true - scale: - proba: 0.5 - remix: - proba: 0 - repitch: - proba: 0 diff --git a/demucs/conf/dset/extra44.yaml b/demucs/conf/dset/extra44.yaml deleted file mode 100644 index f0adc467..00000000 --- a/demucs/conf/dset/extra44.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# @package _global_ - -# Musdb + extra tracks -dset: - wav: /checkpoint/defossez/datasets/allstems_44/ - samplerate: 44100 - channels: 2 -epochs: 320 diff --git a/demucs/conf/dset/extra_mmi_goodclean.yaml b/demucs/conf/dset/extra_mmi_goodclean.yaml deleted file mode 100644 index fe47bcf2..00000000 --- a/demucs/conf/dset/extra_mmi_goodclean.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# @package _global_ - -# Musdb + extra tracks -dset: - wav: /checkpoint/defossez/datasets/allstems_44/ - wav2: /checkpoint/defossez/datasets/mmi44_goodclean - samplerate: 44100 - channels: 2 - wav2_weight: null - wav2_valid: false - valid_samples: 100 -epochs: 1200 diff --git a/demucs/conf/dset/extra_test.yaml b/demucs/conf/dset/extra_test.yaml deleted file mode 100644 index 1e7d05ad..00000000 --- a/demucs/conf/dset/extra_test.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# @package _global_ - -# Musdb + extra tracks + test set from musdb. -dset: - wav: /checkpoint/defossez/datasets/allstems_test_44/ - samplerate: 44100 - channels: 2 -epochs: 320 -max_batches: 700 -test: - sdr: false - every: 500 diff --git a/demucs/conf/dset/musdb44.yaml b/demucs/conf/dset/musdb44.yaml deleted file mode 100644 index c5623468..00000000 --- a/demucs/conf/dset/musdb44.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# @package _global_ - -dset: - samplerate: 44100 - channels: 2 \ No newline at end of file diff --git a/demucs/conf/dset/sdx23_bleeding.yaml b/demucs/conf/dset/sdx23_bleeding.yaml deleted file mode 100644 index 5f7fd1e4..00000000 --- a/demucs/conf/dset/sdx23_bleeding.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# @package _global_ - -# Musdb + extra tracks -dset: - wav: /shared/home/defossez/data/datasets/moisesdb23_bleeding_v1.0/ - use_musdb: false - samplerate: 44100 - channels: 2 - backend: soundfile # must use soundfile as some mixture would clip with sox. -epochs: 320 diff --git a/demucs/conf/dset/sdx23_labelnoise.yaml b/demucs/conf/dset/sdx23_labelnoise.yaml deleted file mode 100644 index 367769e6..00000000 --- a/demucs/conf/dset/sdx23_labelnoise.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# @package _global_ - -# Musdb + extra tracks -dset: - wav: /shared/home/defossez/data/datasets/moisesdb23_labelnoise_v1.0 - use_musdb: false - samplerate: 44100 - channels: 2 - backend: soundfile # must use soundfile as some mixture would clip with sox. -epochs: 320 diff --git a/demucs/conf/svd/base.yaml b/demucs/conf/svd/base.yaml deleted file mode 100644 index e4de8685..00000000 --- a/demucs/conf/svd/base.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# @package _global_ - -svd: - penalty: 0 - min_size: 1 - dim: 50 - niters: 4 - powm: false - proba: 1 - conv_only: false - convtr: false # ideally this should be true, but some models were trained with this to false. - -optim: - beta2: 0.9998 \ No newline at end of file diff --git a/demucs/conf/svd/base2.yaml b/demucs/conf/svd/base2.yaml deleted file mode 100644 index b88a7519..00000000 --- a/demucs/conf/svd/base2.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# @package _global_ - -svd: - penalty: 0 - min_size: 1 - dim: 100 - niters: 4 - powm: false - proba: 1 - conv_only: false - convtr: true - -optim: - beta2: 0.9998 \ No newline at end of file diff --git a/demucs/conf/svd/default.yaml b/demucs/conf/svd/default.yaml deleted file mode 100644 index 03bfe3db..00000000 --- a/demucs/conf/svd/default.yaml +++ /dev/null @@ -1 +0,0 @@ -# @package _global_ diff --git a/demucs/conf/variant/default.yaml b/demucs/conf/variant/default.yaml deleted file mode 100644 index 03bfe3db..00000000 --- a/demucs/conf/variant/default.yaml +++ /dev/null @@ -1 +0,0 @@ -# @package _global_ diff --git a/demucs/conf/variant/example.yaml b/demucs/conf/variant/example.yaml deleted file mode 100644 index 9b38aeca..00000000 --- a/demucs/conf/variant/example.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# @package _global_ - -model: hdemucs -hdemucs: - channels: 32 \ No newline at end of file diff --git a/demucs/conf/variant/finetune.yaml b/demucs/conf/variant/finetune.yaml deleted file mode 100644 index c3ea21ed..00000000 --- a/demucs/conf/variant/finetune.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# @package _global_ - -epochs: 4 -batch_size: 16 -optim: - lr: 0.0006 -test: - every: 1 - sdr: false -dset: - segment: 28 - shift: 2 - -augment: - scale: - proba: 0 - shift_same: true - remix: - proba: 0 diff --git a/demucs/demucs.png b/demucs/demucs.png deleted file mode 100644 index d043f64442f24d1825dfabb3eed57ff0f843f64a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 339294 zcmeFYg;O0*(=dv=90>02PH=bk;LgF_J-BmlC%6;b-3byL0tB}p!4B^7@q1o-?)~0> z;8xwOn%dsho!;K*o!RMVRb?4eBmyJ|2nbX;SxI#W20Rcgm1T-W%t~w=-$v^y-m^Amq7Fqo5)zIG7!BOpAq;9-=`%$Ucdppg8ChpUjg#tOssbIiwyvXfX_i4|+vA$UgWF zXc&v^88NXYC5$RBemG(k8H#9H`oM7{9S$Z(o}W&xh7NLW`VlrwD(f#kHBx*(Kwk4= zQekO9jg5ni!^{LCoJaEZkZ{}C9BWTBs`T8%gv|yHX;U)Fq7P{DyD2I-7)&g#s7X53 zH`VV7gBW<(mmeeVeew`BAF9*AaTVzO**^5FOS{=T*L{{G$t3J!k6 zfgTEWg5WqcIV_)5$op`qmW7U-rIHc^!v~B20U2on0rLSt{^RVA_Tl;D2nhI(Gv-H= zEP(niDs*@O^nb&@{$Ui;kdTx6IBNi1Ei4?}texCR<;j#kR4v(j(Q(&NQsf6ZIk1?T zJDFLqcsn@%BLX4l&Hn*9Sh$;#c{|uUy77ApQT&U8{{#L9%}PP`FBW$@AqpKORWb=D zR|_(37B&_(3SlHNGBQC|b4z}8N$LL-|F{yOuy%KM=4WN~^73Nw;$(4hwPI!GcJpy`H}z(Abff$aCI6*I(!vesYUAu~q^=ZI>pwmJH&6VB zoBxIV@Ut+IAnSiWGhrmV!H8=J2vG<*NwF{9kf(<5-dLKrJuCL&;GX-<*n%5|s2DmN ztS?xn6~9HLu)5xU`-x#mi2<+yQojFi_(f86WxeMRM@&vnFWFO&g9D5kyEHbt`K}Lo z)|^0am{LfP;X&e}Rs1KcJ zRx-AJN+C|>!s0R6e5IMa8JSf-m8o1A5<;Yz;J(GG?ftdO)3(vd$$D9@k6#y!^lB?T zpQU8{poA*v^_gp>Q7Q%XB!!xs{AwacxMut>1YWlOX2c2cu*br;R(rX0POJI%&c9#t zD?2u)bI0@8ld`!f_`C*59r-+&nys2#B&@;NeE}CFe4AdUvgv!H5?y6|twk*xXE<5H z^tuoDpoh)rynRyWR(8AZ@2~cO13BiV0s}{but{0gjHv;zc8u$W zHLP$w{>@zdOWS_A0;>}**~lY8*(^&*LVE`O8R6H?*ztSG<0koCDRe1dHI{} zri!xeX=Pt8vA_Qot=?NC;VTSSX$djvckS=}n(hB0mRzm#)9NVIP5>5MT#A^V-xufU zgV+0g(X7OBN{8M64=9{#v(ag;+lDhZ<*Bsl6_v*Cp?)8bbO~Ok7i-*3`@1qhpX($q zq+BTKiZ&Uz(TI38aPQ(egc@9z73Os`(u&A&v+PZ8B#Q%M*=?iSb#c>hCrImFQWL zd5l5rDO`Mh(&j__O7dy0viE*wm+RuB*MCI&UMH!sGDrgiZ~Rse$s5;{5IF)|ap-l% za8&|latCjIVmtcm;xmoJ{4-{=!iWb#_b6+KU_oW!2Ggf(xDrf08R--bV*HvOvfYch zHtfC&>umW`B-Q|QnfPJ1XUlm+8+1THI_c?!2R01Y{SzK#A5a@~JsNTKaa z8wKy2lbidN==og3ykPZ+HUnpij_ zc+m}{maVx)h>Tzj6{>MVA+tZVU3-77RZ>crVJd!q5mkBVeH)}kCE#OoWMjch*)Oi! z9mGVDNpKw>1$R-A_D+5+atznA>d4A;Jk?JYHY+@(;Y! zg_}`ls^T1J$3xop2^X{wf zb5^(f1K$Y1>8ncQC~+oQnHTd_WGH#XbReqB!lTfqT_hJc?|p2Cd}gGaFh+ANci>02tsU zw>6q#KAEq}MjPg-nSq=snnVHV7_2pbcUo>XOx6p=o!-fKr|#$db#l$imIpU*5pC7Y zT}Aql$RPmrflb(-|4Hf0Q0{v3QAQbttDn@#jzGGb4@&K7zb(lO6(5WYh z>Xz`w7#{-6K-9VKV-VzM(8RlQ_pix1N`pqn(lKwr(Zf9X)KJq-;OpHtWT+qwUlBZ3 zE?eFb9rj2RQj!r=qhqOrOfIrNdhFc!Sr4%* z!s|vieRuEKiUg015=j7+l#F3hnZ$Xhru#(~OG1?y?>#{c(B`c4nJavKU!gh3aovB* zQ=D!x%h>$Z)~`AW=*o1!L??NAbYY4hbaAYMU@8c>)N6Kc31NHK1~y!A9>h5xM2I@9 zofVteZU<4>z*(%^4_PDL&2W46{GeA6IZ1{0qYgX8RNisK-CQ_>T+-;oX4N|qvg|1H zO(iy+7FyfR>qbRFsb&AyaODK^(zmfWT50^RxGK0kqY8`j&0T1s?3cJxKmxS4r5fSMR@ekL&#y_l}B7-!DdVgBy4^3!0k1^Kq z?X$tqay*&y&WF6+J0}17{)sZJinj!;;nIkw)gdZ=6un2G&Eqmwt3Nih6(}zWCu^pgz-=Ip*u9m{Z$c5nibhQ`8bfH69?tj_9xZ6&}5oH{3oGjpX z6q=_M{(X_iWVUB(Nc1a}v_KmY(?SDD#xb`8P>7|Vu=f)vFqcy!rPaZGhlX}NP$4AP z5@b*!sleyLpruIqsky`xjBz-SFzH?)Nq#Xv3*5op9?oGU~Tc`z=v-*_C+`x_I9+6GxKV^HL*=qlKtN#(M`c42$ZZIt0yA=mU%} zt8IHbLU2ClWWzkkrTCb`^btY3( zDT6h9@Oj}erk2A%P(URJd$T|5ceHpk zP5Oz{)=cbdaV;Ygtmc7`*nu1?Q)MR<4^qopr`TsObQfwQGHEAFvdR7+kQMEhAeqg| z@`NDf;DumQtNQJnuOV4pPq3zx+)F5rr;KL&Et>e1qB1f*+?OJheQ9#G@SPKmC6d%1 zi}ew3tx;kQQp`OT=$$-cd%Gb(*ufgMnM0MN?RU12@VA9za|I%La`C&d|&QiGMa6^xvJQgQ*~qAkzUow zHX6n5nT}f0>DDf-RjPAoJ)l5S->-x|AAAMG7D)laz{MhF6qKDN%aySYTk&WsUGRva zY#CwViu_#YA*@>(NLQ9cjRi_wIvv*}k^V6c+a^o$*BzCw!mcp8Y3zN?wxx{twT_sK zgr-wBGFCg^r+xb5n($V-=k9rtd~)vBTHMCCMW7zpPwPT;2CMtO;wGOz6xXqhYS&@! z`Ea|hInH5*JR|}ppWg^>nsViS=J^r0jHnVBdF953=1cT&Uw8DpS$4}@;>Ij`5BN{Q zUqp-}1}_*l)=?{L*E%*lf!Ft?QEWl>&D%oo;{gZ2o=X{!aSy7?YTp~C4-`+JL)95l zFepCg-Zo_pEPZQCo;ZSFFDoC1JHAXSFXR|y*hz4e7RN=8-oQ9_6)j;RvrpJDtRNM>RtL3q(1n}D|Il=t?n^&; zT%8M9WhAR7h;I^!rg*5Op%GcnQcw6a>b$h8)s6&L`n(PzSVTe4AQYO-!t+eB)8UiB zS)N$VZ;ct=Qlv1GKuPi#MfQ!A5R8q>-KO!l;7`HVZBl7?Z zYe*VKF}`Af^RlcV^%E|~?HhOI zNdwzUzSu-sDQ3@<7UbhzhWTCQNRZQr((CI6l7jY{D1>iiAx^e}Un$Z6v5M5nK2+_i zc>UC=4Xz(=k?hY9ub1(1v;BnxiBCu(p&s|=c@3>U0XI7!_>?&h|E>5tu6ZlCM$X3o;JPl)zvX``|TwPXq3^%VjFskngX#BN*p8 z#S89H04t6fZ{gO*i#XlV-^2ZQ)-93Aiw!ufH_z5vJ)!Fa zP2#@|5Kky`2Er{*p;r;sB>P+ssqc=4JN-jF^*5GxS~wugzmJrK zxkiB*ESpjf!|1AN2&9(hH_wZcTJaT35lana8}K9N8g$DgCDRmEDE%c3Rd8Crscxf& zR~w^vvy(s*GFRo;IHn0J(Z)ZVxqM5fpNOI)mfP8T{|&p=#A42A#3)?Cy{j0z(3BF) z-DH&zT*?BMuJXP&*^agaL>gJn%5*R@Dlh@fBH@FM8a@B;VoPs+E!c0fAdGzZ%;Zg4 zz5MWm+_wH(3U_ky`N6=hU;VSCqP0UUti(p6$4_;YSc8wHP9h1SN)a;@T;`e*_4*N} z=3%$286XX?)0j|le~^7rbKs}EDtQwml-uDqbr_;z)N5Mi{e&S+AC9m{;{e37ys5*; z=dbtY=5s{Z+FF(j2)rzn_KlSBNoK0+bKj2#A6hpB!sQqqtyd!mpKc+k3SflcpoF4ll(Ovh^O!*jW&;#-Ey}U2n+KTzlN>Nk8W*8j@b!N3vaz?V z$LLV#K%(*^!c{}8PUUPQPM%Tc#sjrdRY0_L2}gypVcW$4^Ru{y#Y{I-5j5#Og!3$d z4QRiflBc1UHon8a=q<^_K*b^&)sz%U6A@iYjO0uwKM*09-RJm_FiD* z#qNa0Z!U~E?gY^&rVJ)+XH-ny`uu??_CTOCJouL@RU;kD&a`#YXBcMNrt=y{M2BgGk5-;r z&ephC22{c>1Rzk4CBk^JonLmJAw5A%tOoKuOj}Ox&gv zt!lhb0#}MJ-YauEp;23(Ev%{GEEuOswu`~K;Z9O=l{f2iF8JJ&%&6paham!B$Kh?l;YUy@tMUMF^d&$e>#2XJ;i}Io z*`KMa`jcWQ?q$~rOS%1s9}emyNVrlZTvS@7-|t}J$gB9N4j?N5^Cs#k(RrEl2ns)y zg1aKZaph$9j8D(91BAN5Ok!qnur!vdnC&p`1u<$>=aj+%BnQYaNau=DTt;$bb7w(5(Z49zP!pKlocWbvHOa{x|-0%DvXI)cvW98 zp*R#ob)wG`bax`8Ykb+Mjth`$xZZ4*mD>{rlVwd)(rN92&u(R198JUqVx`U>i6)2L z?#%qIaW+vnHx9fja>M8S14_1hK3=SYE1d)1evf!|-a^&T2`o`fX83b4NywNuOZUa>=gay1YTGDxCMXK$eEb~_F2-$={zdns9hOp>Bfg%LXSAp-E z0@7tF3Ij8|mIsO%sVsF{$PF&y50>2OoN7Y`Fd*h=$C9PSPv0=C#F25L1q-THH z*N^djB4WFnRRpDVK|1=`B!#D<{3#!6%%aSonYoKi$-&8>a}%lFssvSdVJDBkG?dJV z8=}d>akfP2v%y+0&YjH#e}0s9#XhIKo^o-}&IZE5Oh`Nb-FIWx5Eyox&f0%wni|as zF$)VJuVH!nh)SQNeoKPn-Y|Lh%Bs+IIRP^ffdNdSvA8&#tXvamd`5ff_*{S0vFadA zMgTjI5s3FXck8TB3cx~AQ{4)(TI>-h0YlvMOEg@`05ZhP&LuhB2R?sl%DN(MpwzRC zO$7|M8FrKUf3_i^1iq#w75<2@;F$ac4DoyeZ~2A&Vkhk{O!=PF;V_y!N0* zd?I|~Sa<|1M$C1DUb}@SX1DAF{@yESJJbdpsADKfk4%m%V#Ml_*bT;5JiWS*cyQJd z{ftrHXG>?*E4>FRq@M!1uT9Z>j^=c-IL7^^^;?ig1k!F!FhdR8F%kB6_|G?5nGpvd}3OJLtT6Pm|KE zD`aH3?%ymk_@Qll-wSb;waf(Jr_{`6DnA&m>630Ozl!*Pmr@r|mDd2^Qllf4P5kwu<-YP8g6;lxAt44Ui}s_WBIRWvD5OR8VD;HJtUY{%=YVdk zj?s+bx%(!PF+dDL6#m(d1Wu@xDD8B#gEyl#TzB9_-%vO1b%a)-V)pyt40Glvd!;6Z4ns{0IzwVSc~iq} z?lvEA$8oKs^1i3p37lFQ>t8dP=aux6vEBDubWHe1R_@%Ug#$xXzhedbWx;1r6UBS< z>-N7c)u(x}5Fw3ND{~Rtx?jj-thO+1&tK781}fLnn3^rEaGz;heAn3wW`lN$T{des z$Ou)HptxIi%G0-;Sz@8bepvtO>U1k!XeB`&E48$+{Ugxb#}Hk6N>)ga7bhUR%6;@i zV~Ne}Imy4gK)bPej00g>YxnmG^0H-d81ntX&_Mmu^qRD+T&gk#O!;u;HKv`DF zN8(JPU74Wyt3F5b%n0l7SrC5FNh&dO(1al@TizVS(t+=B7VqN&bVfbEpXz1--Lc~^ z;GISG*Au7o!ZBJ0(y9riDSQJHn%!SMgkU(e_5zRDiY!eMr~w-`UAVa?DJr_esiP4N;Gd0IS1z%72$mp2E4~EOG+vY(~!i_fTtgg zNSbyhEV&2VuA1-7-5m_`7#2R zozJ>foE(iWRC9RMqZ8adQx>KB`4e+2pDTswCm4NB=5Rt<{P$CwSe~^qQCW*jVu#?g zWZf@k^7X%ooi?N)E4Hqc@!?g%So=glgWoLD#f%{T8!KiobSG9Q zQ{>?y=)FYy3-QxGHH=$HfCp-KLv0DrVr!e{sAyc848Ii3E9V3WWQqL%>rBkON zm9LMNO+Og#R5Ug0@MfKJ>?bO&@iciIn(Md>DU}qHKYVBUYB}Jc4nx*Q;O!)@;)D@q z9ugACe5MelUr31fYV-MWp5SkH{t5V|{l=y6(kfS{CpkP^1BcZ!ZS}$dH(VTNyzCor zN{@c>%Oynv2`-rgWezG7`MWirxtM2XWMey5G)Qzvn%zi_Ig@N~vVz;Tns%rDF3B;# zDxOm7N9yUof;GeY95FpkZMdh)=Yed?J9!+$vb7?Sq6@WNOgxQkM}VPEg&21uzB-+G zH|B;2svcyO^-8Rsm*a%-EV%%=JAN|(l>fLZ0K_uw*nF%Nl>XSpzMaW}SvT;Og^vj& z74g|9`T=l);#vF!$g)Q=Qn`~${g!wW23s&yo4wPjH2IU<6vK{i6+>g8X|o_L3X=06 zFG$pehQ^5P^T~+krT}boH~?+Z9cvF|XoYPs383`Cr5(_1pEP zeX8`x!ikoww-C`u&uU3+t9Z@8^triWLs^J@#%jGa#G7xE@6V(^5P{o9#HmNxN9eyI zJhsq9+66Bg-s+tY!^gyRUQt$K*$3>4BfJpWG|0+se*=(a208yQZ3>K}rZk%Qcd@-Y zqfyLVd3&QLGnvLyT&}`y$9K7M(V%~F2x;smAhcaYErn@38_a$b<{%(k<_4qR565UI3?Sxr^DgY9@~f=beF#XML0*$@Djtq zoG4hkv1ri?(odJSirmpswSqZ56CPImGhveGe~z29?$LhLLICqt|+OUXV`UvdVN3vcjUNW8#<+UP>-$%JyWh= ztToK*fS*v6&Ir;g;D&v<_wJu#c z?6ps!e_vPmLTJ`Nf0!#8KIl>>igXx?07O8!=^Q)WDGAwNiwbVCwqO^@;Hom+fnKmz zHDYV9jL03w$#nUp*ggFqqLJBALbZV%Ut9TBfmF?fbwojp#60wo>ON1*2p*;ql4D7S z5`2b82_L4@GVAo`;Ef}EHi|@1kH(b!V>0gBj(vvb=x}gHCtKZ0RYuM!UHz&S>f11; zZbK?EY<95!Q>?!{2ph@}9aodasStaHv?eT~j|tBva4^S(WGl9D-z@R*ixpbj*~sC{ znB$mlMsNj3wVWMgoUaY?Qe}`3eI-)@2f+2342p({s{z zl@AA|60$-y6hMA2K9V@}Asr7b+R5fVc;o)n%VpR$B)eucTI$y>JO&BB4d@_H$wLO^ zxOBcxN(N^R)?>)D5$v_2jGzP4hG-ujmemdZeyrIUlO#2g5_Z;MwQpMb?>7J0R3g^9O;(cpEy zs0FE`;5I<{=sQPrlBo|f^1!*Epq#Q8KqSg*t{P?suNKma7;mLngxF?|O zLwZAF7E&+({$Z>>uljHve-W|_Pc1c-G8~ijRP!<90_yW$->Wt&P8eY30g#;ms#WV% zZFTkH$Z)a73HWD(6Yfy4Qytgivp0UMV+N|3+u`gXlV4UkuW7z&(Q_X%-1G%)OMWw9r^BzePjnnSIL|LpWQ{Y79pd zu+7A4cyVqJ^8ei&NO`=ycp0FjXsEm_Q=G`x1kHgH$0Ge!fnp}{Hy#U7jV+)sFxh_S zIuIPnt3oUuxD@HURt{)G0#&AYl5tu)g7`W+bfcFoLtx#m$W)}YDmH6Yee?8t1=V{b z=w09tlTnIFr|Rtq*Cz9szBLlgY_PA!E@=yA1g%@p~fF zC^2A^DPCHn^3hfgWFpfP%!V{9nNb7tZEP&ANIN6oDbopbIazDd$0u3<2{!LzaAFZFTR zZ6w1&DZ|>gVUFsL&qpDKURagtR;c_`OtuWqxXpA9(Ab%KF68|ifeFoeqmV3;1fVrg zz^@zC15L6J%eo9uoRpK<6FU9|McbKSIJy;|hHbZ|u=etQaby#y6ElEzHA7{?A|Wh5 z04j-avI~1Kry56?jDLkMm^FQIBHQ)9dxKZTzlvL<1vd3vTfY*=!SI8>0>IHe_g)4i zYxM275ot5^5Cmu5VHWK<-{cZ3q^FG9`#EYeV?JMZkDJ}P|JZ7aYzTX5-@Eo2w#x%G zOPL~m9-VPLD@f$MByAfd?kTe{Ea=z8#<61no>>zdfVzAO(9_AMna0ny`ECZbo0${L zmT6m*$P?2MbRvB9jO1HBLKzGwxVTx&kQ0wh!iY6FQ=&CTSVxU3JwrEe{E+`! zD|ry$DS?T`(Ezv5gmP}pw~ceeO5FRnF5Bxi=TG3#a0GR`QZAQakoVFe6>ApAX1PqB z+aY54%bx|gkuw-7MVU?GgyvUVV<&WaL=XUZJwWnKo8#AL5?SaaH5_}ABFd6G(o6I7 z*&4Gtx)vj6l8f~mHyomb`+x}t&T}%rvu5U(zw(q`b*U_4gV2zP@Y2Kd;b1agql>zq ze?JkjUKHMpBkp?e(Hy5YWo*PYE@=%ewkoM$v{q=ulsyXrR%>3Q;$91y-NqA-ZAFTY zZEPX9QVfeOZu090_ciTSq_>Vh^~%eGf!6?qkI;x@#W0K_mLl$S+h|@0%AQY^A!rC` zi6xD&%hMaVL(qz0M67AacO^#Dp^`Qeje&Bnm&Q?WhZ==;(lGlyUVHF`XWQe~T!5G#B) z5#5+G3mN+w6D)hz<SZv)MP+hIOfTY@aCj&f(okI&Qe8#M8qo=dICfn3i0lh zj|f!D%=43-f8)o0Ba>5~I#^_hLU`CuTenkqp-g&CW%l({YCDoYHUsEjaHp3`G^2_g~rkSBt~r8#Wp#?7;$J+73Aq^?kPS|?-q_QZ9FvR7pA zGpx%DOs@C~l-I|=N%l~R!D-*W#NDz9fKVwl_yf_h#;_uWeEmNM2&m=l?#=89xezk3 z?B5F}BUw-l! zcmA0ZR`xlzv6@9m9%=cA?LM8Riv<<;h&kR2#id2=%dn9E%P;0v#jw{N2cVV<>&jah zW<7p${&@1-$oeVwdywvxOx}IDBTASTIAE?wyOAh1wOo)h&;f7lF}~GyFX+dRuyD+| z$w$3GMR)idj>z9D%}-DlePx{i{((4T(+%2n+hBB9|0M#hjK2kdALjTi{TC7P_xI9s zJ|H0^8Le!-A?D?Rb@+TW-d}h#FOh|ts03WxabZett9}<^<3gI*c<{y=hrtPQ-W zLv~#?7LX8*?xDAdcn%BC^JuNB1r^F_91tVJ0uWBNyVMxo1~5NWIkUphsoKIPI>aYr z?JLngmas_~AH_Q%jv-_lLPZ;#%9y$Mt%eYtIa8BL>h``oru8^dC0DU{c;StdnLrOS z;5tv0@d9Qxe?I*KumrcvQvz}V36o9-5~%g$#qcFfsjxcnXTn)t6iN0H@K~pG4!JLc zZRRK6-EO=Bc-Ov}fLO%t^pM(0u*amcajyljeJzXDE)fy*6p<_s=QMDC_D*H0?BYe3 z9&=zjO{DkVwsqL-{fMX&cV%H9{;Oq2Rmg7K#BQMIC2I3SQWZ{3?D}t{Y-$95V{pac ztB#jR(xE}oKkF1^fL$y%>h=PTd z+VxaaO}uh^^sU-i*~h1B73Sfu*X7@{51ze4ZK*jKwB0#D1uPL^#4 z)jt;^@r?NF_^Z!)ce9##JGqa2N}%@s=?Wu|kMp;Te7ZPoXI1WAYMN5l6u%9LyBo)@ z;tlP`W^qO3@1Dp7Mwa+zgs*7<@6C@hk1vlu^Ct zbeCF-Gd-HsrK1w{tKetMoiq$aL(|fd5KiXkpEFe^)TH6F2=gjBGCIwh{Afo{S?Gfl zCseBz*ZVI+zvV5lxIb-3@|f7K(06#FGPL745nDC2davup4qf-BcE@t@eQR-k&|#Qb z{^NyoU9Rjbq=LV<1pMXnF^>}`8%s_~a+JE&BsWRu|7?B0sD;zM1WS5e9jsYhA(2Tu zLpI{1%lVn$`Dk?v57Q2X@ID2RRSgNzV##;8r^s$LL^tYPoQcTLu6o!(BtAZ+>Xizm z5}^v#?(}R6o`sj9l=ow$Bypk-Li%u3Y?(Ll;1rY>aZmY*+&`FGCCJ&J6Q zg|s3L)sd&Y;Y+4OtG(OAm~l#pxMq9n+Pd~W5W$s zY}*(TJyMn9p5ROs-fkMtl-Hz$=IVg{3<^mqzcDB);hpV!7-;wY&c5+38Jjn1nEf;j}t=~e;C ztjnTQxF{V$V(>R4xE4m7KGi5Y>3mvXLtF13`8|Wc*S$k$qLGR7cZqqq?hOf;zOCb`70#@xdIJ-KXh^I(XsLx`!{n=5J#fdWH^mvPv;6!kr*FYLRyIFl=nE2PH;qhU_&X$V}ukG`b<{4)K zdHs*i1EF&_ELU0cQ<3N2%&em9CZ6MHrOcgD^Kl-ZGl57CpugW87|1_W$MHKemw)5L z#sk@zfjA~YGdF&Rv<$pzQ|_QBtkl1RilwMV6hUz?U%keJ8F{lSXHHsS!#H=^acF_G z%1k7K%P^RD`})uP$nkbCsiE_Cq7}jkF9qqa@i@osnE~uJN@sfP9GEebmMt~ z-;mwKjrR#ZP@y@_IwmkSZg|MVx%>SjnU&(jDslzMX^fC_C$BJ{MO}X>pwgmFvM$ex zt150`MoO^TF70>0wKi(XvwEq8qdxow9xnWnJam1|X;~*Rs^jf8EO(8wL$3+qrhGMj zgkMr8dP_mhy(Dq{HrT#YOjf;%I3Af=Z9uKXe8k?9O%f+7rZf>Lwz%n1f7M1XkPNap0s?`9G0&#X z&-WrD6SSlfLr-eS{fHAzW!;?sBvPMs?L8a8pk$qwF>e=k?kGhmCY(0)3Rql3mqKII zw%$u%5}GhnTeobjjc}yiQKLz1UHCUUnG$R8Z+fjvUG~KA0sr1xKQ$d0v_a|$bc34t z?6J)1;BT9^JYfOM97OJ#L{EmvwC7<^up6&qVInW0$$}fKAE_~!5`|~bdfm#xd`rNC zU-$ehk8Z=4R}jUPjV5=w=h3o!GTXeavhA)4pQ#NfLp`yl5*Cm9iHArSvSz z!pAynYc0I>UE1>Z>MTIpNvT)lB&5_hvtzHoM;hVABtWo$V`gQ}Hn0pQ{q$p(P6QMw)V(8>GHq@i0kKQogK1zZ_{p(g%=hK)i))D_C#M8s4 z`l5S~J=hy~v0Kp8XRcU^eFOOa^a=VL_3ks2gSnS$&^WM@(W7npDfE z)h^XAmh>$f3B&;(CVe+?tq8OGfjYHTT#bJ{tuwY=5Cfl9+7mAi+<3nw@_afv$x6%Scf`eLmvMUdLaNOID$ zWcYV6xHlogLe_m?SVJ1}RCTQj9!hz9xAVl7t;)WDd#%-NurY{A?Vf7TbuTemL&M3U z%+EAX=VXg$JNd0HqPJP&;yi+>6d0hGo@^2yiZS7Lx)q%K36kmRfLlkaoOL&ev{OJW z0u=SKlC_3%FA!d!BAjqf1^al>Lp9UohgBNk-3A`8l?KPDMtkoEnHVD(!57k*iaRWy z^R@;m5k_J$IJEKXIRJ|?nA$4|3l%|OA3GkHqL-ZGLsXgWq~$6_vBq2#mgk|o%Xr8a zJgIKY`EWVY1#u5Fr9L*(f*_95IFQ7WKI%(NA|a5O^HoH(CJ{C|e;*9@ZICe6biB%t z$|WWUPw#5G*$6M4KPNh+gq_=KlcV}c({?^IvS_x~mM5+AEc^|;O;gAVrs2gjb4@m^ zw^esj;VGA`DNZt2J11IF0Fpwn3oczB#oV3Lv2?t8TFo|+yJuPa^Y1$85-}|^EYvY= z4&-HViVIw!13kF0BDVxq^2ak;9&v`dx1Oh?}_j_ z$NY(mYKV8~JjD*M4A;5*zPz_k8)*AYoU8|+DsJjaUQ?T5gRva~G_3%TumYjTm6 zd>K>&iAADq?~!q1yKhC3{x;oY!@b38u5r>r4MhS`3ddF`Rf@jfA$*~h>A?$hKhGKK zysep7C)HO=Oc^vs4wsfsKGmZ{U3#qwGm8eihq`B8IBk>`q3}NuW_UNpQxKJa#c-G zVegInaKqbHqt@4kx`NGNGAA=DIUf~gbeq!0m=MCHROza~LltHTiXZuV(x1(T;k$L4 z)b|;Co3C7p0VN2(oLH{*v?rG+Ij+3e4y`1ggeZlx^w5X^_!n@EhugY1Bbof6om6PJ zt+(2}+ev2mONZh`NZ;JY8W2$voub5I%z=5DIr7Kb-U&r2#71EK^@lGIj zWvyvS<)gYEKHK8`SR<`#l}3j|3JcuP#byZFEsTR|I~nwQJF!lxS(94F)6i6FZ#kLr zRv!4o3G1kHriox+^s%qC?!01?4(5##W_l26)(#>pTyRV&*Z9x1ljK|O3#o^+;SwuU zY)0|L3Ngj~cAYJDlBsbl=n;BpC~mWy3A*X_{F%ZD4bulY&}$>Y3JH!G7w@V3$%pit zR-fmf-o-CbggR!)JBb2S_eb*j0O+v(YYMLKlpdxwbSNYkd~j`l5KVBVB$wwMd!%X| z=s6yUlimOX@Pe?^mYvd?zrH;g&_%vS?46q>ad^sA!gZ-X~o#Tb-Q zdR&XxF+G0b7eQv$a9HY=MfFc}v=sNZ*ggn*$0_I+&&)b`>?;0`X^*nELN$Pb<&Pr2 z9T%wZ-QO;6b%T57XRrYfGne0=DB=iXULv@x-UHqbD}<1OQDQ_-$yDLax{h1L$$SwM zGs*rBS#KE@NAR_I2ZFo1yK8_D+%*JAU~qSLcMC9haQEQu?(XgmK?fgfVEOMpyZgNR zew(YSub!%|I@ML@+~-$&A-q?MDX@)XlIg}O@6YgvYytWIn*|W>^_UGGl%MtwO)_dR zvXSZ|)@rjFnXg+TYPuvUCjFK;_ibih92(DFu;ff2@?f%DKM{&<)j9Gt@Io_Bx8!W8 zVU=nd1=sV1M{M!wj~{^(R*qwm92Kf#tPKnOxFyo>Vr%? zAM`|90|{t$#_;h)Oj!~mh%oh8(4(r=*_)jBLjMl!uZCJ7VhDV~JYu=iFF@|DT_*Z+ zar-|SV2Pnf#9asSQXm_nCR=K1u_}N3F`R+UVjynY?=5L9NC^d}XZnb(5NEQ1Z<)#m zK7tG7g>V`g_yjl6yzT~V4|uQp49`v5=OthT963`)NMeQsPnt`S!RrN62AB@kSL%%V zxZHLB$e^tAKxGL=?|FvL5;zOeK^5Ek&QOSUj#&yPwZsLyfA@t+F-0JjsOj|agz!5- z{_1~-ZZ)L7H4Z=i*smprv`+(A=ft(!Dn`sfq8RT=8Z7E%zp488RWFTujjmwq^C@g> zbz5bnXkvHkJ<*L%;VPsuxp5d_sxUry2p*w=vaZCXk~@j^Dv$><1b5o|u$u#^SHdqh z`lwK9+l)nT{TrTCpKE0^g#!maD_RFa8(8!XC_NGbXl0WtDIUe^zvSY1*1FuoSE>kdaic#?uTH8S+H+*~*4GfbQ}~5AkGq zU`;R4^)J=+JJgLI_7bc2!AZ&{&vCX9rxCL z1j_azs#G>PLmla*ktyZlW~ATj;DlCx0masPnMYG_?}qcMxk5kKe~E;lY^V1M?)iS1 zCY}aievy*E3~uk`8taeXW_vFw_U+{mssrlq#(a%knQ*Low)?n2uGIO14(?q86cx*EadNZ2WxEHOND@?Z)8&)dRW^2meNlX zP@|!vwl_Qcq)?k}&{5QwAH6IX=y}g@RMeo}bETDnJbUvF5CNET@+I}uTdm8xqxv3m zw?tolA<76LQJe+nK*mZ?%gGl!7vuI{qPJB#b+lUm^OxSD^Gsis z8irr2)`Y;8TAO(3ORX8jdAUYmMFlO*_#8@QIz^WHK1G(%YgTK`+m=K~9hS$_XZ{Mw zR$8d%??@gFHfbjZa^Jo09KWyEFL&^?ZS&d9HH4GhZkv5IZUu3BBuBkAC#?yEqZ=!3 zKHNAsLYZM@*?zdV>7vrWt{Lnp31 zr`Am=9C3e5X>6C9ciScTSXVN`K$*9Q7k<;DbQq+p@eP*^t$S$ZAy~>5s@cjEj_&4e zvu12;HOrSee#>r?ex3vyVm@P36osIVywcn0L9DN{6jVCb+e&TB;=4-AM6k ze1CW++!!DGb){GX3})logY@dWKA{sx@L+1cQXgF-y}f9)^H!AC^x1D2fuwfcCuSZh zbYqWa1g~oI7FZ9SRf&!LkyuogV6+0Y(S6yM1-0Op4Z5bo`F~$K-r1~l;#XCppW=^93%kUZ~cxBcd!NBEzw41G6|@2Y0Tyy=e`8M!fty_)T{t)j#!K2zm}^r1Y? z{7fkT_Ly;~HlD8W5ru0Ml7!?d{~J2nUAm9G99o$Pn?)^(em_k`7lGpT`~-Yi_d;*F z_*BnNO1Mn1VIzPrM3zC`MIN<7fdG`cGoE-S{Nbvhk@?Q3V<*A~ir51C(?)u&bSj$C=?Z)KvS)*g4>$irmHGq7iPV{YB920+DnLb-B@odtkb3G$p6dUx5Y|E! zE6C0P3vXC+s@q}>nQLL_ZYEcSeS$=T0eR&w0>Kmq0izI1XsUg3?Ix^Nh&qrJ$jn6e zkInPEg)CUc{VS96zl4nV--P6(tCEot7`)iJo+t8K#YxT9IZp()?qvS-I2S&)u5Ed+Dlc_oHSeqy_eXqUja&ccM%11-?Oq6@e?LiS;G?i z`lJOIEfW}@z#qENxKT_%tKvb-x+*#}{;hT{e+FA=Y-IE81S}63H z591%oQ9DBFqjk@hI0Em%LZXi(AvjyXkUf#%2$z$J9OBA%mY>}SL~q|_9%O>Qxk#RZ zH7GOlbggxMSgn-|x}puKfnFfZ;zN=CfxnGU(uWMV?8Qh{F$QEiGdWYyVoJ;@UOTT? zLa;b)%=QTK>4bmg=jT}y7oNXwEz9Fr5eLQ;m>#QlRL|z_{X5eGXwC)kzxq~;Y^LU% z?LPt??Ui%NWM9Vr1bl{}~2AdpEgdWfSs!t30i|A}zYP5n{-kkVHO= zf>3+~iAw7-OdPSz9KXW3a=f(}cMSYP3R|?6Bn#=$>d39CF|4TM*^O=0Z3p4xYvx=( z$_XXgAEB7s2o*N>dkAX{&NDGC3nC1lKh&N5dR+fYA>=`0yY+JAT`;ALP)PKVP+d$U zz9dl#kCeDo}?QSo#@Xo1(`M0l(J5op!m zc>=09&XsYzHy?}DXbx_fFbgVHI)ikbTAS(1EHXajtlYjuLu!+)Vn`#9M_9WVIOX~* zj0V+?o;oKG{|Q?BdxOO~FFztQvd-+fLd=MuUk(4@7*+vx;fJdEaw4$-3Ay>5<*b#uKPj%N;bFuCtd?*P zT`7LD4z|47l5mN<)7~KX!OMF|4pJprT=Z?n=qiib4Zno*0Fhd$lyMqu0&9cKB?6OP za)ON_T*)$cYtO_E*1wwlFz~Aax^fc@NIEN3Wawl>q)~;6!pVGWvIi!F`2g>9VqOYM-B&pJ2U&V}5D=6| z0RHpPFovWu^4)7Itpk&bc;IvEW-HefZzvV5^IHxS{FtAt!vLZR2#T!l~$XUK*TAEDQbwnTDg z43P%(YKX?U^j_fIDt_KsuI1Uf|L!#!qgjse1CpG5&#j!1cUiy#iblWU`vm&=pgfJ_ zL$c3&YkXPgN}n#i+x1x)(CdWvJ@0CXZNz62SyLmi04%IBdNqa7@t&%nx_>DPOL6?{ zb$@rG7%LdzdQ4<;=L2xRExIvSl<(X+K8GE_Ll&!sf%#j8n>#Y_^19+Xf)d4(vg(D3 z@4P>%f>C&X4b>qNybY{co*d2*H(XR?$2HPn=gDbMU9N_cx~f(V6V6FbUT8%M9ET1h zpqprA@yh>}haqbZvjMOX*aD8ep8V6~^+1=2HKhDp|HYQ2LfN#HmWxVFGFlMXl3gG5 zXovq@Yd=I1AxzuFz2MhZw@EY$j^2*NzmZ)Afvix)b;?je{3wkF-nqNvS6Diu6c$aE zxSH>;S@)ynEg!-}I+ovXwKl!q!T%z7`drnJTp*)r@iYcVksIBl|NdS*2z`c%QLiJe zLI-si$cP|^?Gth6x)jtK9|<}due)mK8+VUKYgrI{hDQ&`)34Tc!{Rt14rDY=L*a76 zIruvC2DPtFfTA_q*OsB@Ln7QOT0f-~C@#CNk0~z6uj^^lV1LQI593c-u>4H)D1H~h zs%JbT*n-9$c)#QL;>`!9{sZ!JRWh5UX%p;W;AC*W|z?=T9B;vWK-_wDu z%@mw32lR%#(JzdDp0^!9n71~f9_qvCz$amE{~tyO8R^wv1nKZLT zt`(%`p&5H?EF@DNFz`ZwZOi9ZVS5ekR^wpf1pD4)Z*Gk=t1$L;3B!ufY295K?7-+; zc2H}vo?dLY5FeHBQ7HbB^KCb||2HUZcwyT>_&q3OO6*IkarvbN+FH1+TR{w&5`YD*#9GjVj&gu8Y-9>=5dk75iZorG4@ zTlcm*B|R8g3IE%re?HwHy?AghMWe;ug>S ze9d;I#zMn;gZI~Fmy52XDmyQnopj{5L=%9~Zb^h6IJsyNUmqbqfb_uSNL-cr@^beM zblrY}l(Ez{s22D3Yr8JJmhJJ}>2BL-y$!Ve20*It0*q?jLo?pce3!&yKeypUy~T_X zkbcNnDkGh5F~__)ythH4za2KT2UG9`0L)Nly}#}oVB1}&qTW}cye+rKw0?kL5w~bI z<@O~@jeP+Gd$I}~l^Pk0l>Z7l%n71Cq7v96cr(XJ&~U9^&uFvt0r`_)>gL5c9xM@b zF5Ih%wDaSLkyOuOAZZ>akwLqR19@i?Hjh<=kimMu_aInRkgr4(Rp|N4vq4OMYCe8U zQp;3mnMrEM{oN7oEH1(-xz>8D=+1#}3X9=I-iHi!Zu_(h*b#;h^;uB+icza|no-9L=I`}UX+R+}ZPvGpG<{OT z{Rn0ie(DivODLvSVF4hK(tD z&mw8JWZrylSW}4hgK?|OG;4GJfuhdZMPe)x2fW;AEv}eIH^(__A>G4=HH%q z2|&uzmx&S-!I(w?5g143_^UT7s*Uz1Fq5NW5<;6o8)H^Z8|{iW#KSCiwC(DO{3%HD zytARI$Rl*(gSOwPe5}s7|v*Yk7)3=WDuX1imYt_6kOMcZG9-&C~&eKqw-i6 zB#h4XY!;(kxo3?}g};^mg0nZt#uK>0u(rC|i_e{fpFoFOBhE!}Uk1p14P z(6dhR4nq}395^JPdmT8xG<~Fb7Qo{WJPms^0~LXojPOHIRThB+amo!H1IE}eX99Ne z#FBPxWTPZQ#d-`TBL43(g(4vh4HR6H3+eMx7l+NeONi1T5#Rk@)eFs{8(Q-1Gl1`{ z-Sl6;VDYQoN7Ih=(Rr_AEv=II$jp{yGYpchN2Ys60Y#HqJL1&-lKJB1fiNB>`?FJh zS#;Bl9O2+pCV=ea0EyA$9?@hr_vKf>xg*VTfrBW^qd6U2zU8J?v-BM8V|`u?-rkcc zL8TgYA5qAtZM#%gV8VV>?5nYb9d`l02{5w-r(6>_Ra*; zp(M&F3LBVJ0y8`(d@cgREX=xy^KkKdi+=mlS)NC@Dzna7V@~q=WhcBI%0K^Zd`@+a zDnRoM!qz^o2IVShjefYR>x731BUy1uttGm3uB$BsAKH<(qE`y3`1=pb$#;nTrowR9 zj%)^VnK~}CWl%s4A*d&BH0Eh}SpPhB6y-Rg`gnF#bQ+=Og< z^20T*&J2Y0boNxutD#>i-nA-~AJk^uSv}HHL~8&?|07)Cj3}NSgx}iK3FbV~2l^VR z*jYz&-Ex*b`AN_NZ2fE8U5k*3L!Xp4D4Rh6-qbMt&-beH89T>yLnNDbBM3{d*pK=Q z7>_>MO*8L7;IH3gbk@=OB=G?m&re*94f34DrHX{xzDwox46Y>ErQOfJm-hQ1y4}JX z^xYB@mt-v=DfdF&*G4UW{GmQ*g?niNkC(@Ibf1VG>MtYlDia7FG@e0CLp?|fE%8+x z#-!p)eJ?lGEu?sT0t=t|*1~1eL$9;c7hvU!?fn;Y&o4rZ+8)$u5R!Mo#0ihm7Mi06nBgCNx4(K3PcEk};opg*sB0rxf^mj?jh^%eAI8Frvxh zM&9%w(AkWnf|BM|=q!N8gm`FnhCB|{6*{5%K12idNS^6T`1z*^VZ}VqwTZ4Vo&l3v z!e2-cZ1!6{p_ak*J5K=y0zA~j#Q!oJOEuRp8rDz_IxUYI6#(s4xLF4xK_{hbDl{cc zy6ZLDvdr?QiR-m2Su8GvSVv=#legt#%Xq%Gl6F!wFF5t;=C~{9C`LkjW)%u=+=+?r z%dpk)7D9Xr+ZDU&ts!L1#zNtTB38Uo6Th4hZf9WTHy_1=?isSK3E-teG%w~Pkf&FA z%JXR7KNySK?LTr@8@_VmMI*s-t59^b^Wm^6H~oR5B`s0!w%6_RGZhYT@WNWDZTKd#;EK(E>>2y%;5DMUsG0LW|fwf3n(E*#KgJGfsVsi zJodr2L@y^&|KUP2f#o8!?{;|YbfwOi){qRS)rl}7UqNz-)H;Bt5uqxEm}f9G-nMG|6~U=2Q~&% zl8B(qju#g$rc#lsK&y{Pr0RcozPUJ{nO-d5{5;CeFsU5SkWW~J@@C0xswZP>G`mYw z;tZJXCZscXB?OT3KwAP*YfX|U2qC?Kt?@fhTKdM0!d^J5_Y_~uKPS48!&KLqswq=B zzuzes*t~rg$cxh;e!zwi@Ogl~HNhY(dt>i6MN||d#NvwV?v)xvEbc}pkS@pp{(3ra z7V=9#G@BcPHoar!_D6%?S4aVA=*_55a0`DDmuyv~`XN|`dNE6x9H7(e-^2oM7>g-*4?rlm}O;Rpox9*Zc5S5D};iQ@S(+ zA0{O;CiA9#Ll2^U{=wXSon6>a*d>Jwr42Q40}g@AP1CH4Ht?S7L|2K>Z)^8Azi zST4i2$z|t9_EU>+_oQnpK;<&S|ev9&=A?y|&G`c|uq+1qOV`UxKMH1Q7m+ zy;QYTVj#qPii+U=#T&IaB$m-^hRT8*ODT;QWpaErAAXXL$H|R_UA0gis-C&reV81`oM5o@KFiPX!t5y4Go#A&T7GV6F(n%4U|%>zPCkX@ix^?qzS3HGS5b zN?7DRE-1r%UKWE_VOOFdP8|4mZ(QT{4&h_vmfUr!&A`^^ecv-)-BO3o6~vQOO4?Gg z2k!2NnWG1?*pwA~p$<|hk{@cWf0T1zu}#wWTMg`q?~4>9Xan{8f;}`mVFn|TMj4a{ z8~*NVZIF68+d-tOG7mK{#-rocit*HNukB-R-KmmaGx|tCMb#xtXv+OK2(3Y@Q+6>Z z>*|9`u-}$LHsG<^;Pm5r>|6N%8t`an-U`hCA%CmChs3tU zJzv8=Xwcn$^(VtRv6%%OPcEOws%aO*_7K|__>m6Hw<0Tl{TW)rgAnvnc>9baZ$d&@ zH1jK*0vf^oBC+8nYaTPLPi)Lv^7eZ`E+3w{DFOkzv5hYnlO_Ey-=B2R=m3F(?CP)= zBb(^os`K_c))-6$s$1G`Lh5Ihqwp!dlOW2?O>z%OlXF=F8(eCT#8>l};G(A}`<MIlJ=0Q}8QjcETu=dhk9{n_azn7n2PS zf9-Emman-}_QD)~1^H7BwZ_=-D^{PCo&84*kOueR*fL9(skHm~!9Bl<>S-eNqP*B} zU^l@^P=*0?N0*YOkU}>g5!(5mEVhamPL3vFkFsf%yf@CDk#Rq}{{3oDnh5U|n%xp% zJWBB6M5q{eX6CZ->m3*RWBZQv@o>H5SSeTe{}-zMAJlmvI&{okE3hq6oWhtY4rqdl zh_7TaF;%X|{D0x={|kvOF8bl;p$sTQ8}&hG(M7ED9T&N|FZph=HgLSl3X?+l|0A~t zGGqMbuLIG4ZK|GPeDyZ#eieAbTx~W_+i(OsM7R!(_4+EPEi+6hwK(XYkpd{;AJ~&1 zFL-iw0*s5cWpMuo{{Fv_?4?o^Sx7B~O&O;?p`yasl$0?v!20ngJrs^zQ*)1wh20(W4SoLl7BWA z5q<1%{(ECbeEzfJxfODD9frb?RTN}hZ=RC5xB-38ThUo{DqU{x|-8ATwUz$RwEt3(e^e7dJHg z4xLtoBu~V2!V4D4^>u5ZRdj~QsR%@wh}9=$`oJW`W?NA1l;`{c6Jgcifkdj%qtvU; zdiY#n84-*OyRyg}CKpbumssTvVWE}hNgBn@4#IV^ZTap=n&Yrr$dbtoz?&pvxi#xT zX^GeanCkQOo?NpNl3#lA`kolnoVsoNxu6HbhIg zSZRmyMz?h9eI8)f<5C^umB_Z)2IYBgzBT9!KGYeXNmkqLzOJ+xl32OpOSfw%EZM*o z>J*(7+736HtnQHyWRY}TS?PFB0mTwNi(6+X1sBQRSJioJm0$>m6SY%6(=;KQbu&Ag zcd%j)3kN><#T}R|AVfja(qx8sgSKVi{*Z(&{lxzN)R&9=V@{axnbz5?C5PF#lx+EZ z`A6_c{)Gf{|F;xyx1i2;{H`W6mS?Zg}WI#W{7OoPPan=&&t7vHXYF3=2&o@trg(SSS1I*aM#r z%{U%PBKB~z2Pye!Y=O+u_=<|V*M_70`E&5b>@7LJk3%#|CPXAJ!-}R*EO(>Pw^%V} zMdR%aV%P$HW83#@N4}^Uyp7OVacRdEv^`(>Cc#@p%Bs!#N*jS#LVc3I%%5HB>Xnlz z;EaJ7t`sH|)^e0gcJ3G;er#*5$ji->8(O@F=i!u?9 zui_1U*^zThKPLM+?>npMSo8HNzD0l41vkZ`ZIW3fJf-FLjUtKtJtx_&*CNyTHb>R` zk3>WxR(4X19R<}rP0h9|XPUEC39E&Z7{w}0LE99TO&CpphGa)_skJ=*hckhBA2i8-T>`4^sBYD$;VIM0(vPWa%(0Ri8ejD`l_HNkvjQ##1|W>Oiy1l&7Ev zC%GhK^4H54<&os>QSyw&tvI#`kcZ9%f!=`VI`x|)R#y7hi<(XQW=F`HifZfHJsDW39nu=l)1bI2jUh?5e$lk) z_PE0oF^(0LIzZR-!swvVB7`B$LIImXs$C-5<25jTW1*A#b2S+Q4~v4vmWwy^POf(v zGf_%D+49uYha}`w`g=}7FnkJil~dbtmx0JXA_42Qan;JH%HQucG&WJtZliOPhv~@> z)6rM^!R|Yw&DTHmpOW@b<`bQiaTB|dNHdN#zjGm9TH&GgVkSySLU#t8~0z?f*tN3$o zE#060l)t_I0p>*Kn~a}p>NfzyzZ#n~Orw-1(g~uC?=!2`D5DzJFCSkQWEjf5A`N%o zB+#<$_93QmK1tCmTC(t*ZJjM9U+?d9(aOf%#xz{q1s+W8pCQ?hnETA#w80^Iqmk;+ zv#xg@IcD%Yoa7L}ET`33onTmQJ-Ci0HfMtEM7ke9nheY-5QfIn&4@SNTYjsHF>=%Q zt)#i!>wb^yB%g^L(B3bb~G)7 z2;KR9gjwHh*R1iTASHd1YdQ}VluJU(&ps-?$G%efTBvm)P$b%GMX5;@agz7B++@FP zn}+kc1BX=|`B?oLK<<8#8f+q=Z7|77k6Z52xAK>;uIO=~wz3X>-{C#m^Y1NTxZhEr zDh2V@Hg(G)%=A}gm9|nGwmy`OyxFH)8Nu2C8MLYphgL#p=JcQhN;+u?@xMMbSbT9o zq0F8Y(|O6ha`NBkCs2I$R+Jb%@s1OAAUEvHsbHv6)Yk z+zXZ$h8SZ7g9Vt^CK7l`4#s{dIvt}{AE3UtKHupHrhHBaInFtEQ{XzPdi2=vU)u1n zd!jX;!ZOs%*Id4TZIy6BgLDRXiYh3DX#^ap?3s?H!`p$LXtZO*uUbBo!IMvaoFk9X zZyT9Uf`+}O&2izG`B_1)$oH*h0BeURkz+?}dbC%iP}p%|N869DIWoZka?8|;5VzJ< zYjMqYd>o0_$r-4oQIuKDJ?HT24YjJCx7MAZX&Nh{H5yOBZ#8(c3IinBaZc(x_BP=c zs)2_p41wb=19jD!LS&pM!w5NF9NV$jn>;xs13);JITtrs(IEuuQ|{v}?kE8YEz)z@ z-()D`;xRvDxzc`KWgry^1D?G%hI1avYo{JwLXBpRH<`Jvd2^`PeI>Jtjkarc1T1?P z!Q`m9^HO&Ci|5W@BzU%E&orJxDZpxWaJz|P_eFwc`VAOcHSfmNA^iS(rm=ARu+n2^ z$%3UvH%DHK2vvcI_ZqW7LU~_;8G9Vzae`qQZ*4aY`)UuhV!$A;o2M@quftP!;(qM}>Q4`XfzZ7hgzt$%lP`VW8>(~@4a7I0Od^YabQa41Y>%n`a<&zPo}0 zpuNNCr@(Ud#8L>ibZmFg(Pqb^)*RSx3ZkkXNvk$^d`7S|r9?Tg>mvb`{J02Q^vpP4 z<@tWimdN$geR^JnG1ZNfKw$dk`FUi5Ugs z9#3KKNwOoSQs`bj3xnX|ci)?m2bRq?7k0a@Y9T-u!4j9U;-HUUWDnCsM-CCgqIqOj zg85+Mq~7E5{_4^lTKWO(zx(RQ@U3`s>cUy5Poe9Tc85xj)D|yHHlvl9tf zg*ZJ=__cm4WUtsF9!@J*S+*KVL#Nk#LD{-Bz|%Zk&P#0ib+O_90nb%I~tjtS}OSXUER z`IUUwz9wW}wu8I2iN76ZJMw$h1?wT1d)Nv7eGFB}1Ln1Vp)UsLPN}gaUA}gF!8tg- zZ6z4Bt_tTw#3Wf;HP9dZB}g?lZ$z1>Qm1aCB~S9ga^(1O&Ay@>bfCvxfb69r#(Q(i z{iHE5pdHwuv`)=ZflLDwz4T{iQk=^cOdO07eKar5F*Q#@|{yW^wKj;#2m6%B<1p;Bo> zJT%h@BuXp?wUb6M;ZvAbxl{m0rA?r9*&o}n!YDF_2U2(@9K%{cTl@Zsz_&Pf|IsSECLfhvX!7<5rhApf;%GXd|!kj3s zQVOsSR1R&ZU^6W2LGM}7pXG1I9Uh7{i5DvVrn+xQ8GkLjuUiL3p`4O{2V@<5CYfA} zO!9>~1x~0qxJLxblCgUAs9Qfp($&G^Vi=bMMI?GNvtw1aKyYAxsCLM zF2P|nD%$W=94v3odP@*e*zBgpUqz-TzQ(%!4_QM?T0gor+Rw0=X#8Awvh=Jsx#|6T z(THkqzJB`J_L7g8@2j>A=bfUV^(mEBVLgqWkQ`4z_8XbDhezSG zJiYEy&jvdYAX_gsN^g%=zY(S0-J))b*&6fDk+T>H;HF3{UJWEHqs-N`KGQL6xYlAz z$mW(Er|?DltZA(2pl}n23Hi)D+$C%M2LLX^x4eZp6AWbz-?V2DQtgz!pWe%E+Zh*d z7|a!zZCoZna=D>m+iDP=SnY0ki6HeUw8nwaqL zHXQ0U!V_UM#Mk}dAFKzl&*g!n@H{szJvX8H7bH2+hQ)Z`a8`yBL02%mI#ov;N>{89 zFLw+$^CM`W`DQNHxgL(zT~I4-HJJ8yTRxEtAcnl41hzXF<;Ml>ax&S4KTPxCU{QxP zP4SLfW^G7<*N#0|J#)2VEhioXqM<2<#Tivu0Pqj5vB`Hu6MBLu!C@lurZslVjXKOC zc2D$5WSbNFXW{CV;VOBAqK9-zkv*0JgSJu|rCEY8PzRoTtjV2c9iT(MweFYCL(5ag z+=K9j@mU_W?p)bW`7MR>UTN`eXtrzm5qh1+6m^41yr5Y5;=Lmupk14caSGM-h6)pR ze5Ncy@h39rP|jK96(r=S*}QRs{)@I>#AepT9D3TAP(qgrM43A#3a@FK48?T^-=*aK zpvFJdbIq^#QRHjii^6a9+pfhPaxU1x;!q03k8h2@C{ZP97SUvJRw!`AaA7`rR`pS9 z5peeH+0N8WIxx}CHlohApv|kIIRJ?*RJNp~ShJ7mLXp%danXM;k?A2I#BkA#Q$4+6 zWgBTGIegZZ!ihxDGEc;Io0BlC&U-yPG|i=8k3xzRNRnXU>k?~tb=&c1za@DnoZA{S zFm9hzm59J|zi3zh^ylluSMk< zNh56Vy6t4}FUkzT;PdmW`psp1R45~=%&b)^K*i8NJxpd+FZa}mODmi)%~0!eeDwRC zFAXV!h7>yqC{K;cVL5+}Kl<_)_x0p@cR1pccK7}V&nYJds3SaW+QBshO?StY5K=2* zR+Z)6zbw#LaHVtBgByTs}uFt9M(RZ}_`8Rw%g7i>@N61} z4{C8kZGXDLd}3pHs4dx5^MpkpQlK*%(Y7mp@RUg_W=M?ziK{F5?ZeHX7&Yb8|7|@| zyrPI_urB%(6uP<;UF}dGfwvAN{aS>eYuS)MGw^N!*z^K-M{J{(c9l;~JE6+g6}6}l zTgF=sM#QUM?N0cpdqG#CfA~N3V#s{WsQaM6-w6{D>iQ~MXf~A}zh9amWk2C`sEtxZ z9y=ri+xFGyWqzxTRPVaLX^28?7LDguP}pR^a4$^YOi)i(a=XQ0$p2eR11d;zCdSRf zUMLuEnI)yuLN_vMtDT(1NhmiXY(kvhi+TXWhHx6H|Xc~li_ebE@RQW(w! zxwugnsdNHQMdb_K&EL*n2u8(Riwok^-=lp;IuSej%|J zB$kG&CW_iEY1-(3xhgrPiP^|MPT#QmX&v9Ec?cB@YJ6~ww%rT5yxvD_6|oA@^2SFUAg^<@N=i@DXMSBPN1l%2|`LOX@#$a;I~-Ei%mT&1V9YzJVi z))YZ(>U5*0Wtd&3C502;A;{zrd@kT-DXHVdTk%GnSo0bdDW(|h2EOo$wUcBd_;PMo zE^i!7>bHjB?I1jtXpG7nb!byQ0bOnG@7ExINA4rC-T+!D(v0ZUhCr}gUJCYP0z2yt zIJQp0L4Go!o=v-E8YxK?P9mPT?qk>uA!Hl3{9&=4VfB}~D5i$&u`dLh%Hgr`1Y=}j z7*?(Nwjk37L4h-p%J1Y2&lQ<%bINx*@Z8b?s^$u0#4%?pxSd{OZMG@@q~2y z@5nfKwdSakf0-^_jZUy=<^FuOXKSQlb46u!njZ5^^i~s! z$+D7$&htX zEYo7lIQ5Ba&!;IZyXN|H_so4pv2ELt0;_F{{zmzU5x;>a)&QW%YrDVCvO^RR-^pSd zajhzK)~FnB)f0BTOH3}Yy1se;GDn3G0J)Ur8z4o=`v+aqU* zqJh!88l2{MS&~9)O^ASQ_jKVUaoStwxb* z{obw;^ToZZsz#u}UU*$-+#><6!D(>LiZ=wWllTni%fBrf&9}$pnR+eY{a2@$fX!;t zTi03x#W1?K%;xV17Xuz!dgQ;D<1>z7rpXid|I=tuL>(GvGW=5g3w6XVSWtXg;cZ;l z|KQ5y4e{_(QqD~uSac2e4G;ssM(V9BOyRqj_K3 zla7J&Kx!Z$YfLzn{AmdwQFBvIU0rEG$!vDS6QSARweda+nM+JVN#RO7GH~Wz z`R#f&X4uV(m!n^MU`QPi*ghIgD44@lcf!vlJl@tzNm`mKGEfLL5i??fx#>N<*J`O- z&8cUR!fB#NMeUE(O?Nz^2VO_E1$fh^eL6d4vaICUEdH# zb-DGHUE393q+QGazs}n^WD`hDqK-wQ#8Lbat~3psy!wrN?N!QH`;)6aHO)c3Gv-Hf z8UotoMCd_x$=S)hv|=iY_3k05q5E8NI%zv$Rid0Z*kB%=ovNE6^s-bq)pgz+Rn>~- zvRYS0E)uKffcRxLWBg}}qqam8gH3{G74f{cK=<#LLI`KX6i8qD=l`@)sQ>hZ=sfj| z17g()X=02l;!jEn&Fj}zyF?SJ^BK#zClFT4NNb@5dG7$lJtONn%7?z_MX}83;q?aP zV}Lj^@YZ^DCrB1)SD&T290^xFHAY9;2v?dR67ZdXP*u%n&h)wi-8m{>t%^IU0l&3b z!loD!I_Kh1lBfQut^cp7;(t~m0Ly_4Dw@%SOu@Dk5&P@-pLcCb+)U0FXX8J0F`K8R z-R6QDx;lSW*47*zNGC2y?Wx?hq1#l21FrsUJ=*mer@HlPyRf3xR6`Lra&g5tlG4QH zBn!4+Xj>Gt*{T2E#{S3J?&)%q`@9O~N!e+kXD47zTOrg#vz_KGv}0`nTU}j>ymPf; z;j{JA7ehq4h#!a2ZC{4|RQtaESP#vQs+xVAoGV;x9>QsAO<^QP-tw9+mrY#4nWdY%l%QJ$d zg^X}B>TImF;8#X@rWe8`nQ$4#x1_(k|7~zV(asRSEU2y!{ArA(lga&q9HVf9%le;r z_rEIj@n&;dv6YKzTJ@p4UjJJ`%LKxolq7CzKI}%zs;2StiPPPd$bh!kw#bU6^8bup z26p=|a8#f2;@g;+86`X|_g&Q0b;9QI=cIQTAUMZ(Jo5UqPlF7y)AoP5BI}Ujo!QD& zp2vPnN{sauN}n+Eal+H9lUV-ga>A;sJ?6*L9u}xw|x}entWS=Y)5|l+V>z* ze>KCDN0Qgs(tn=)$?|q6Xp*Vq?y0{Ph}EKl@K@ENHB(L~CQ6z@q|y;a0P~UiQDT`u zD&#GM^g4PriIQTVQec+BH=@Vi=V@2^L;>bnLLU@bjjf}Jqh+w12yT}luK;PMXEfFJpIs^78@wyD$kN*$o$b<>C@M^Q{A z`;5-h#HfNZ8^k8U|Izrzh>xAL7b8QU7gij3&aWSAgL^MWi}kQC?%39&bK%GP^R1xu zZZ#q6WV~d9QR>_4?lk-5{}eM9n3^4`DO}B>f>yiLK3i3Jnehu-!h~@RfXH9)a<<
    - I have no coding experience and these are too difficult for me - -> Then a GUI is suitable for you. See [Demucs GUI](https://github.com/CarlGao4/Demucs-Gui) - -
    - -### If you want to use your GPU - -If you have graphic cards produced by NVIDIA with more than 2GiB of memory, you can separate tracks with GPU acceleration. To achieve this, you must install Pytorch with CUDA. If Pytorch was already installed (you already installed Demucs for instance), first run `python.exe -m pip uninstall torch torchaudio`. -Then visit [Pytorch Home Page](https://pytorch.org/get-started/locally/) and follow the guide on it to install with CUDA support. Please make sure that the version of torchaudio should no greater than 2.1 (which is the latest version when this document is written, but 2.2.0 is sure unsupported) - -### Installation - -Start the Anaconda prompt, and run the following - -```cmd -conda install -c conda-forge ffmpeg -python.exe -m pip install -U demucs SoundFile -``` - -### Upgrade - -To upgrade Demucs, simply run `python.exe -m pip install -U demucs`, from the Anaconda prompt. - -### Usage - -Then to use Demucs, just start the **Anaconda prompt** and run: -``` -demucs -d cpu "PATH_TO_AUDIO_FILE_1" ["PATH_TO_AUDIO_FILE_2" ...] -``` -The `"` around the filename are required if the path contains spaces. A simple way to input these paths is draging a file from a folder into the terminal. - -To find out the separated files, you can run this command and open the folders: -``` -explorer separated -``` - -### Separating an entire folder - -You can use the following command to separate an entire folder of mp3s for instance (replace the extension `.mp3` if needs be for other file types) -``` -cd FOLDER -for %i in (*.mp3) do (demucs -d cpu "%i") -``` - -## Potential errors - -If you have an error saying that `mkl_intel_thread.dll` cannot be found, you can try to first run -`conda install -c defaults intel-openmp -f`. Then try again to run the `demucs` command. If it still doesn't work, you can try to run first `set CONDA_DLL_SEARCH_MODIFICATION_ENABLE=1`, then again the `demucs` command and hopefully it will work 🙏. - -**If you get a permission error**, please try starting the Anaconda Prompt as administrator. - - -[install]: https://www.anaconda.com/download -[prompt]: https://docs.anaconda.com/anaconda/user-guide/getting-started/#open-prompt-win diff --git a/demucs/environment-cpu.yml b/demucs/environment-cpu.yml deleted file mode 100644 index 2419bf35..00000000 --- a/demucs/environment-cpu.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: demucs - -channels: - - pytorch - - conda-forge - -dependencies: - - python>=3.8,<3.10 - - ffmpeg>=4.2 - - pytorch>=1.8.1 - - torchaudio>=0.8 - - tqdm>=4.36 - - pip - - pip: - - diffq>=0.2 - - dora-search - - einops - - hydra-colorlog>=1.1 - - hydra-core>=1.1 - - julius>=0.2.3 - - lameenc>=1.2 - - openunmix - - musdb>=0.4.0 - - museval>=0.4.0 - - soundfile - - submitit - - treetable>=0.2.3 - diff --git a/demucs/environment-cuda.yml b/demucs/environment-cuda.yml deleted file mode 100644 index 0d61d33d..00000000 --- a/demucs/environment-cuda.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: demucs - -channels: - - pytorch - - conda-forge - -dependencies: - - python>=3.8,<3.10 - - ffmpeg>=4.2 - - pytorch>=1.8.1 - - torchaudio>=0.8 - - cudatoolkit>=10 - - tqdm>=4.36 - - pip - - pip: - - diffq>=0.2 - - dora-search - - einops - - hydra-colorlog>=1.1 - - hydra-core>=1.1 - - julius>=0.2.3 - - lameenc>=1.2 - - openunmix - - musdb>=0.4.0 - - museval>=0.4.0 - - soundfile - - submitit - - treetable>=0.2.3 diff --git a/demucs/hubconf.py b/demucs/hubconf.py deleted file mode 100644 index 0cdb553e..00000000 --- a/demucs/hubconf.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -dependencies = ['dora-search', 'julius', 'lameenc', 'openunmix', 'pyyaml', - 'torch', 'torchaudio', 'tqdm'] - -from demucs.pretrained import get_model - diff --git a/demucs/mypy.ini b/demucs/mypy.ini deleted file mode 100644 index c4e17f16..00000000 --- a/demucs/mypy.ini +++ /dev/null @@ -1,5 +0,0 @@ -[mypy] - -[mypy-treetable,torchaudio.*,diffq,yaml,tqdm,lameenc,musdb,museval,openunmix.*,einops,xformers.*] -ignore_missing_imports = True - diff --git a/demucs/outputs.tar.gz b/demucs/outputs.tar.gz deleted file mode 100644 index 51933ac9ad898690980dd85c53ff4160567dbc8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1885 zcmXBOdo+}ZAIEW*sYbge%2qPJ8Y0wUT5Y1}sdkl=r3({l4BG`I6kU`&>#~#)>ryh) zxeHXGzKj$9vs$XA!6A^8K0p`!|RO z8?oBiPloDb@*-X zZR5<&T}Xc@Tzrtv*xO+0=pN_N1T*nxBfe zEg6K6{z%BYQU<9wncxTxM=Lx^L!jDJz3VHk+S7rv$v{Q|k*d0G2X5XaL7J%!`wFy3 zrHmduNY-nXBD*Vs_l;je`{dva+q)!%R}QubsMBAkW^F-Df7$1 zRp0P{xF!oF&VEFewx!2T!X_n}phQgm(exbR+Li@MfDPO)4+(tq=bqo&1d*D7>1vxH z*cOC&?Kidjao=(y*ps38YAR1ZzDCmlJ2uJahd@`)8ggtdUMSGyL<&QkclECFxGd4^ zX`in}_&#AUXd8kqpmxHu373tPv}`XSD|};>X$kL{88KoiLe9yyF@Ut|4Hb^dtSDB> zvkf*+TWV>|pPl{f1z~mX?CA=zFYAr%GdWTIL~x`wzQ7*)ZE4zZj-Q)x?K?|sI$>`j zhc%NKH-5HlU-u+Y&u{8`X(%}YHw|ILp#JJ?&dIQdw63P_{47T|40V(SW5P37>Xw50 z{0Q+Rudru*2D8j&!0X|$m39o#pG@TPxMLZ^$KBvFUNu?IV~)kn^3Rih0mBoJ8|FF< z{_Yua!-Q0EfuFB0Nnre{Nh{Csn@ka>q`VjZZ)tnuqXvh%nK^_%z5cCk*~^i)M`GRQ z(?{~hDGPU7n_!{MjxSvfUf&rtrk}6OvRjB*B~Ps-Y+1LMb+YD==XF1Kt83~?$7@&j z+X}aA`0FUkXHNT&#DI7;>5*@AXo08vcr^1^q{5qA*F5QAW!(_#pMPfYef1+xto>fU zx0YUboJqAk^)OEuN)0coq*cvyv+?*||H}54M_4C6-U+S}XIXa(oX#CA&mZa@omd|9 zSH|6EDJ6MUWfsY`V0aI1_Zh0d|MVNwDBTfPZ}S!~MdB5{vMXFDons`rV!n zufC=ur~d@e&0Ba+2 zTW>(cA8jhcC|w9!63=tbgLV4*%!+fc^(F?cLU0^Ii?u%m*U3RFMHI$BzY*soLUkWO zl8RDn(}xgto&bwZpl1`DM4;CR`z!I5tFXo=6pT!&v=`D|HjLg5!9%eNfln$1<|t#U(V4Qn@?g5i@K})ZLw04<+b-ft=-VoJFDDL z&atf?hzFQ_2mn$eKJWrIj-e;q)`OwmE1+$|r#f8^kEHs!@LzH+78##DcL2g@OE1MJ zZ%ry`BaE))m%i&9Mq5V_3*lTOrZEfv-Gw3}IIJDcQh8C)vMOEjRl^k!lc1fLyW!RE ziYWuID~lzGm@;zJSPN?yInMq8^b^NqP%w&gPIYnaR}HY?Jp#s!_yK?e3N7dV?|PkLvrybzEm(ESxSS?1G9G;z@|>`9Z6 z&FTZSWbWKl$VTy=~q5n+u zQ(8lAU;s=sE@^zXx}e-LW(%um^;~w+&C4Ytd|=0.1.12 -diffq>=0.2.1 -einops -flake8 -hydra-colorlog>=1.1 -hydra-core>=1.1 -julius>=0.2.3 -lameenc>=1.2 -museval -mypy -openunmix -pyyaml -submitit -torch>=1.8.1 -torchaudio>=0.8 -tqdm -treetable -soundfile>=0.10.3 diff --git a/demucs/requirements_minimal.txt b/demucs/requirements_minimal.txt deleted file mode 100644 index 8c6f1e57..00000000 --- a/demucs/requirements_minimal.txt +++ /dev/null @@ -1,10 +0,0 @@ -# please make sure you have already a pytorch install that is cuda enabled! -dora-search -einops -julius>=0.2.3 -lameenc>=1.2 -openunmix -pyyaml -torch>=1.8.1 -torchaudio>=0.8 -tqdm diff --git a/demucs/setup.cfg b/demucs/setup.cfg deleted file mode 100644 index d54d56a0..00000000 --- a/demucs/setup.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[pep8] -max-line-length = 100 - -[flake8] -max-line-length = 100 - -[yapf] -column_limit = 100 diff --git a/demucs/setup.py b/demucs/setup.py deleted file mode 100644 index 47163d79..00000000 --- a/demucs/setup.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -# author: adefossez -# Inspired from https://github.com/kennethreitz/setup.py - -from pathlib import Path - -from setuptools import setup - - -NAME = 'demucs' -DESCRIPTION = 'Music source separation in the waveform domain.' - -URL = 'https://github.com/facebookresearch/demucs' -EMAIL = 'defossez@fb.com' -AUTHOR = 'Alexandre Défossez' -REQUIRES_PYTHON = '>=3.8.0' - -HERE = Path(__file__).parent - -# Get version without explicitely loading the module. -for line in open('demucs/__init__.py'): - line = line.strip() - if '__version__' in line: - context = {} - exec(line, context) - VERSION = context['__version__'] - - -def load_requirements(name): - required = [i.strip() for i in open(HERE / name)] - required = [i for i in required if not i.startswith('#')] - return required - - -REQUIRED = load_requirements('requirements_minimal.txt') -ALL_REQUIRED = load_requirements('requirements.txt') - -try: - with open(HERE / "README.md", encoding='utf-8') as f: - long_description = '\n' + f.read() -except FileNotFoundError: - long_description = DESCRIPTION - -setup( - name=NAME, - version=VERSION, - description=DESCRIPTION, - long_description=long_description, - long_description_content_type='text/markdown', - author=AUTHOR, - author_email=EMAIL, - python_requires=REQUIRES_PYTHON, - url=URL, - packages=['demucs'], - extras_require={ - 'dev': ALL_REQUIRED, - }, - install_requires=REQUIRED, - include_package_data=True, - entry_points={ - 'console_scripts': ['demucs=demucs.separate:main'], - }, - license='MIT License', - classifiers=[ - # Trove classifiers - # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers - 'License :: OSI Approved :: MIT License', - 'Topic :: Multimedia :: Sound/Audio', - 'Topic :: Scientific/Engineering :: Artificial Intelligence', - ], -) diff --git a/demucs/test.mp3 b/demucs/test.mp3 deleted file mode 100644 index 668604d876880d39913ef73bcf9df4b4308a9d70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 802480 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

    +RjCwR>N)iXJXs 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%0Aqy_xmoqZfCK1|6r_B#CSPwnR~(-p z7^HCr=10l|z^U@EsJQ~{Db^I#H@qsvlsBo)L)iIjizQ!)ian!g3W+OKwGVH#7l@lH zkc-Do)E@|&=uM-aTrl19NL0^o9eeX^wDn1{?)YrFj(P63cWim!yG!1~DvybP%JHP$>r`?hjKtA-U!~9`p8&# zwGhjLj2ysjUvO0sB0)^FYvlSE1`x0~Ci(8?NHA0j%hj6OH^fLHv?0*M{0Q#y(8#Du zB0@y+aaB1JyVAn^rH#mNTDyzZe)cFGnfgbJ66Krq7e*7jWg&A5i`;NKxw{(5|q_o;gR z!p72Un`flXTyztu$NZ`E<>mMJ&rh5Vg2%UJx;B<@oh>;W;ltD3y+CU$?OrWzU3)JI zy7nO?mU5dFh+l!bkpi*6As2ALFSsK@^w6DFa*U3IFp!rZ@&c-m1?>u@3>F?Dk!}GZ z`)E160)^0dxw(l$Id7r6gVg?yo#~zKoi6Ly=gXeG__%tf%6A57?y~-Ja_fACMz@xR zPIg%V)AZiychR130 zA6z~4rsy&SYtAM7AlWcBKo#|}Qrsg6J@8O%B#4u{9d~ovxZ>4}2O4G^;S5sO3Iey>Z z9+%WP#*a*+^A~1SdznfUn&Q>T37(e~`iq#q?o85bn`*~qIgUf=2BFzd+RTZTo!c){ z^s`^-*Fj%2J<8f~X=Z=&U4Z1hrzgJcljp7bu7wX>KCPd99OB%z^4pQW%Gvzbb8(>L z@o&4HoXe?_%hikyNXjg)eKV_xHhUa1o?v^Jq5U z`9IaGaS?zX3=Ct@gnccYe8!n%Z#C)sc2blgMG=_np|_}v(%4pE&e$s8sgh#B&CD2) z2vF(s29QhsI1`D3X*&sLy51e0Cv%E(RMl~Y(joo0L`Zn6>DC7I&Qm$c2=+f#M z1Iw0Q^HaH?6iJ6~B|v6-)oHj@@VyRblj2T;Ol|i{j}z6zBC5)R`^L>xSmT7f2X%Qj zouC2K@gdSV$o|MxF_!cN-TA{!WgY36mFeP&DSETasu^OR9>A_m@ry=^?c^hk# z8oH}llk@IRZNWq7vL~8s=Tw?ZogQYQ*)>TkPs?kXFANizb`=xQu0pnnz4zCQ@DOq#UFGP&WBCx4APUg+^^`0& zl!(M8gc>sS59gt<)llh&{p_V8xnob#{yLX`tO$vsFwfA%9C&DAJl?MaL4=tFnG9E? zKXxf(uCJPRVho!~)7HD<2x$$biyd1IcDH=1bex)uhngnKtgqgR7CptvA3v(O%>B;) z{*UMPN0%%pGk=)TE2K3`29t85)LRNETEr!y(r1$~wN_FXvUy_r2GD|G zuSKP%!U-TB)Z(?_2(Bp`C*UZLKW8LS5IF;pV>O-1;$Z9n5^Qk1BW091LLnVQ08Cls ziKXt*vdyXFoZ4unR_jWldHbpEd@*>uc{tDk_@dHR@Cwl*9ElLVC|^|roV5NFa!)pR*y{84X>ePn#x?$U~X zw&ma7WL}M}gN>>4m~aZ>u-ql1&8#uYX@%Umxx&E2sHLbc>#31TF`RU14)XEEMrCUU z5H%?t6-UE$|5)h*Ws*$2O%uaKn948l!aJl?I0Zcjn{xW8+8CG-O%o9@8mB_R(nFd- zomdxL_%*upl6yJwhSid3^S{qWG+&LHBTD84g!xj(mH{zduX++i}dBelZ3B@^Y+eN%M*m>Llb$q{%w?eMwMC3S|_R5Kwhr{76)7qY4uwkJn?l1ndUF@H4 z9|V=;+P@*yb&@2aEowaKL&7VKbglo!MRmQX7(f5DzvHn`J=Wc#Uz7XyK+3o)I^02piXo7|B?fHZDo5mJCu47x;U6?a;V4W|qgVzsl4BiExnv*kK(aoE$7m z_!;DzOZTOoL#dmMo*aq~+W`2i2|T)-&$d*?>e~(1vqn);PljFzNn=D~Zd^-XsqEW> zJSHBfDPD}8=M4-(O%7+KrKoQt1TLSZP=FGg7`gkJ z2lmi+APE=CD%%JzObbN295P6vSlIUOMFpyI6@8L+~ zKE9|el4LiU6WJtvn|@uC*-sBFV@lHUi5jz%cV4;98{RW*BZStQvCpO0qUftLJ}~UZ zYzH5vcoFYsob3Pk7V~#Ze9kpPDWl|mX)}iLsHJ9GbNGS0$xD?zMjxF|K?CikVw5n{d0zFB&N;)m{0ms7{|w~p%O(6Lu|dHjcg+DS4Z%!k4C@aewsD;wEB4}l0qjhqLc zj3ArBX~{R-C~Z89wvAF?(jD^@3HPeX5>;0p7!Oom?v-*x+6lNkQG3$;oeWm5*>?`2;bL#tCu?Bl|T$LXdKQoCX(SsdcF9FcSQ|&&C_6&D8xI|WYQTQ zCGon);Fh_z?2me$3@1mR0W~m0meNc~B>SzT8n8wrFrExWYPgQ1GG_GxON&I4DEI3H z&5groTIbj}nQKKROLPM#ZjR>Hm~pX#n6xbE%Bf(+n&x6u>1*YNy%x0;Ywcw`rfQ|s zeQKcvRT&jR&vF=&O_%!ytbPdAW$;p7U|)#Ww1q}H7{rvp+S=-bvc_Dh8DA}asG>2s zAHB@sGLXhr`j3CfJv@@nU4M0d=$uh%wOz}ztn*0A*IvwQ&k()ey*4@^GF5~lnJme zgJT4@rq$;pw9

    I04kLkdX|T(P_Xa{5%1uDK61pqw4Hj=|7aIEc>HW@_NfxLAzqt znv*!Q+I#=GDL5~tj2S=EqFf36R_>-v7bKV@RKLY3@i1*HypW%26O|0!2 zJr~+d%r@$$lpd|8oQ?LhW48WuVwx3aZ?Wl{*&He;EZ08mxyoig+!(Q+jOl!Tx&Kur zh=_;4tRnSFiC;E%fk}46*fNNxdeeQ#vTS6C+#0GO6@TC1`2t+_?>k^^t_^KY{tWyUe|Z;sB#!e1oR z$@gazR0#}9wOM8tbbc;P3r0gP>)DdktF+}fwLnc~ZX5dYd=Ubm7VV`w`d;!f^sS+{ zCGXy<(zs?}Y0d?DR^xa=sxr0dbt900bV3EEV|Z=;LKXVN>RFJ>PTfF9jRjAHieb{@ zVx7As>1XsOoOjQMD>fZ;t~6P(kH4zW)4U_fQTtc$Zp9g6 zqWMOU0PnFZ-B2<>n?jEONc}=~-g`rDZ2f$C!);P+aZ09R!d#4a%s7lw8bq7EZ?x?> z&>JS`SEgpfxWqT4;fi@|b0w*Yl-tN0bx6!7qo1Qs_Q#*AR+yw`GJR~b+VE}J?9-;YZVz#w!e72Q5C6mI4`gRCU2c6~lAs~Jmf4sFuMA6fQ!UT8;igoQU%$B)K-{iHP3Hu^$$U(fGd#Sgiy z_xs;%tSh#Bp3+jt(PmPKcDPmWyJbTSvYALdn%kxJul-`qd!r#!0(|fPo^O|gzt&u`;Ik8Ug0uC|&l6YOkXRBS!mkUmle3dunOYh(F zFMcNW<_syLc?}9`JJh?4<{J4c(?*cGJ*gnEI)3#c_ z3^X>wp6qU<|G_Gkn2Ji_qP+~)nx6VS3f{kNqabX;1V*$<4V*D!10&ArecKt1m2N$RN=vgEH%reQ>`)Tj z0!EQH&OwZRh?83ZZ49aetOcNH(E=$3d2&2K0`ku_F(PiPM6e%nG#|W8m&)0cfZ+?t zirl^lW*r&w^l^wK0J!{supt^*1gsy>kGy8Wq>98W8X?TY6@lAXG zu!8PSn|=ct3GTmg-Wz$m+$s?8P>!y13{c!^@a*2|Osacu^!D4})%@8%*SfG({|7&B z*IFpP`rQMQ&y9qV<02<1(r|gQA<=kvNDN6iMIr#z85Is$O8h{Ii1wig-3gGm6GCcwuPIcSt5!WxGa208nu3%FpvIifI75RuPH zpMx28n-oS$#)M)l%WJ)jN3)ll82TK?Q1jsdj7(tV&9)wC75Y)`Ca(BNB~~q@^9V`} zs=@eF8jPEVra6ICR6fjXQ!#=#5IYrJKn9eWb|Fv?6w8ocmCJ|AeJHXX~?ABYw#{9SDT04VGFJ~$GiM!~~EDHBNbf?;3w(KO56FAYgUu%rnG zDxwyUj}h+F8!ip31aL{BzX^>BaNU(c&d`H^sOB@jK}jPg(gG=Z)4=ADBsH}Xs+X$n zuZ2k*`zv;+@1QkQ&~h+2vi;ls%#gy~@~Ww5l76Tc4>>KCj$F0zybdaR;i_y<_nm9d zvlJXrXi_zXF?DK>oCN)HUtqp7Qlz8u(bL~qNZxemfHM8gz}Gq^6Vnrq>fuM|CwF2m zE4;hR+WNFPgOi_`3JBbM&-d`4^o5vO-94(WY?-GUTfBG8U-$R?tlUj4cKDEz_FCDM zBlZo{01W_`Y?1-?kOCC`6dQxc90-E|xt&xzke9;u9pV+W38mo<;x;L&m<%t5{RIK) zk)ZVYy5n(#|3D!Jw$orSlK(Nx?{a+4B+M(tE+qEaq48+E9^sOF^tS0P6R;0ELkR#z z`LK<{=&`np+334u8jsT&E69+}eo1?}_t}B)H%P7^GOg^P^o~X#_SHspu5zn>qj1%= z{t(vx{IJ8>Fu02&wtARyEl<}n727qN78b?xUgn-?-QVd2FYTZl_YFypzcWD|OF;ol zZzj6l*39+&6V>DLSeRFT+nD*HEux8Ub@iTbgD~BfdiK$;PrgRg?r4^S)YE8u?V0IW zFQeAx0^jGkUfTR%x6AuNxZbJlpQ{b)!w9vzDsln%6=7Tet=_{Vm|kk|`1#GGVoI$^ z>D;6{Y8~_CGQ_*a!F}wqdJuvVp$eJ>R_lwCGaHdXcM8yYX_YkPuferOh%7rmjiS`j zX2*{~yclIjO9{?#{L@KkcYN)56_9)XL2}i)?&ToBghDcRgXsT5l*P+rNw|T zoRQ45TxjkMd({ZCcM8}tP>dJ}+?IPKqHopJ0pK|G?e)}xBxsuFm#(tm8CcUL@!uJm zPcW8Ch*dp-;d5p0ASWmDuNmx^)Tv&I>D<&&$%_x|cI+AYx=EhOg$JMYZq(jwi)KK+ zezC%mq?VTaV%c=AlK8 z%e$UYOQcKUW{=V;iBz@U6S`Bb?p+C<&oyljKF5gl>RdnTNrY93lfE^89-*9Tq;HC< zm~cx4J}^pu*jvSTy}Ly_($7s(PRIod_4)w~RY3bR{Jzl!EDjmH8$1`4gi;=>(g{+m zZyNbf`b}2ps_-;JFzQrP8%X51?@Fyz1sO0v)9n%fryEk<4w1c)$^w+6GVP`VTN;y# zY=9&I2VW4Ky^ss|Ic?FBb6g`0C)2m?>R=6TwVSkJmf5?LcV@PAxu;19BaXb2tfR+ozn=rT;8Sltv>tRD+o_+Q57cV=U3;lZQOgpi zErvsr#L2+HxOJQy0dJC)mhgcdFWt2C9KgZ$f+jD*fmKu%Zve$U?*{<}W9b1@5Owt^ zJ@(y?A51#>{_|aOu1y*F=rElBC(N&ETUaXWjR0@RGtNi8$9=AN=lE3~L?2#i22pQ_z*%-=*>@zJzW>z~e{K8}F!DMHRQOLTS#JfV}p5e<6N++H? zoC*5sS^Jc=VlJE1J4l}QSyuHM*`DvnXIB@RKmU39a=W~B|MoQK8?Uj1$sq-Zn`=zl z2+x>tktTEtzS+T@lUi`&#yjqj(z9H)tm;X_4c0Ci*4Sb(d<#(9kY0}k1p%`Nn5h}- z-KYeXR5~lbDy4bzEg@ivf|wyWSwyGv=K3ZC3+z>AL#&cFq90-d&2CdiEC zqw=|b6V0Y0bu?!7BWcT3W@}`2qH6dmp<3(Gx|>-!Iym$!JXNq#E5r|`_~LFl7gt!I^r<( z0Y@$G>0%nA#9u+*Uu)pFkNH+s;+?~HgKbTYoG9ORKLn4>`Oe=&YL?C{zEfCTfG z&R|^r;mzZetB8dC`s}c)B|QKjJ(EzSM0^kd9}?qD`b{c5%Mh6ObdaCmN(L7z*@CMJ=j$#yZB(*%|*&{GsAWQ*C0|e zLH69ARAKEIaarl(3gqQr;pm&lMC4c7bvZlBS4!g%4XWnTH_BlLxz-)G%_clXN(6~Q zu6=>c8cq@t#G&fbI)>1@YD@u2Q>S?LnhQ!58~wp4tY6)zrx^Wy%)x<`oGvO0ShJh|xU0rf`Hk+sIrDMXTo1@l#QoEQj;EUz zXU9TqehpVw+Xcc@CBJLz9=2I~-uH-ucOPM39QxX~SkvL&XS>C|LdpEBxb#^)sU{8K4a*Y|_?yr6D99ZoU00@kRC0lLjSERr-sEV^ z5l*P-#;Ku)u>%vvwDc5s0ay`jDK4^w19T?F=!a9H;*IW)6t}}RdO5gBa&W@AWMX*6 zG(f72sqs8%v;E@z(R0Q907?KE4T@UJ)jJ<9>F7gkmnGiK3)|B z;=2D?`C3J!crp&1IFABP+v@N)*NG~#7X_vkXPHxEG@5;#zk_C|&=Gc#7~8VZ-+23E zL48cabfF(`OQ*Z<@O5oZK&_FP2+*c;g^Os*9;LY zm$_GW>-8&Kk4&q)8*;bXsa}OznN@H)Jn9y=yN{$~5g+q(^3at%zGE!^cZatxU!s#_ zZs_<&eJFl0!7Bde1)4OV=t}Il1qwiu<2DhX3{%SN23Ke>v@JTAo(b5Nrab&dfuY-o zK)>KZmxv*F$y)#fCKBxW^Nw%^Kqn5%h~FX&7SZ~YIzmo;TaK_qMn_Y?2i3^!jGG)2 zXO6t@4KPy|@&Ou9Nz-D8MuBxby`zR7)^eZikdV4DATv-DgF&r>X^tOTBH6-xqK`I{ z-ej~D-w&1eVDawvNJ3RS6Y@{$%3xLy8{w-&?W>`;moIgn-JRmF8)mST2@iO9_<7LY zY;Jg($d~kZ!H;GlpUGK^U%}x&>4VfQBm!afe+>Z8i3a%y8OqU5w>Kkc!s)NB^H&a( zB1O8@CMRXg%jp!3P-8IRSmQ?0f>4P25rCB`uxqltCL$5`O-PlEmDpWJMmf12tpnmX5Gf&iIs`8;qtX@&~V5Txv^C#2aV91xWCpuM`NX#sP?4ic5E|cKn`O z!NXi0C@aT^rwoVoHlqP|4e_j_kTJ$FLM%*J$Eoi|{E!1x7(jHK=8b7Dw}$8uY$HZv-e9SGQqtr5^=fT+7fW_E-n$!2yDd4mP~V2!h&)A6Mdlt@n5_V&KzA@U&SN6F{! z-PNsV0kyoZ1-12WU)!;^h(cd6s41hdol5JA#tOFJ{ z6y8_rFipD+D^+mhyP=V79XP;EPQI8TPQ*x&KfdL{Tg>#!5Rt=z8w6QARSwyp#MmCUdJI5YAxe zPF26#l_mI1rn%yqOFj0^H<8nEuymO?JFCS5v2d3$ zQ&`d{gC4sbm6Mp>QTfE$SJ=Grb9NzO9bXazmLvtx^MLBnk*ES&`1<(S!BN|aaS^fL zW=OBtz%yX{-&E8Vs#$B$-Rj9``j;KBN;znBQf_9GYV}=7qF?;4=$(~ig>XV<;Ze|6 zi(8eI)!hBm4)1nie|G;3$~y8BAS0z3?C)`5vlNNGovt98irf8H*_|sT1ZSonSQoLS zsf&oe?i&KA$w?fhn$CB5DI%e?e)z8^VhA}NqD2x^cM zO7f>9oW&nROGNOIW`GST*l04?XonH|uzp%dGMkzdP^^y>_^3q(r^^MCEevo)gJZ@>=>moX0C-zYujakzm`Y`q$ms2tT9M2^ zpMH5;U1?JdL$1JlImpvt4g`dQ{03|Lb22(XR&2KmPC12qh~-Z1p)0JC66r!KE!UR8 zJ&a>+xnu{Z%C4}JKd9{<-ENW1(TlmkN`{a7<<4VNKjEylNd+p|e}r{?c=~NtUTbv@dp4rWx^?BbmOeptD4U{`6b%MHRa{gy``_-r8??+NX z=a6CUimK=o+ZHJN%_F$9HqUeD7+4YHEkjIXIrYJyCIT|WWnSJkQ1O{QFYBlvYjV^y? zPIYS=1t9BH^}%=8i&I4q0Hbrft;%{auVtPZssoTa|AE34mWDo&=pBXy-Z30~IOzp& zo>($l?FK+Ys&{|4qW}X#bu$wAG=)m!D(~wHCAq+KtjIo7?MsZ*dOVc|DK}fAm9SMz zBwjg}J{Zh5NkX7R16rc)N|%H9*_lWs6jey+n;1xes-!7J+&)n~k;uoC!I>lgz`7i5 z9io@Cw~s50FEn&P3aNhWHmMo$AXKAXUA9^mTR0X0e?0ZKpgF`bzsl)AF`867-*4xb z)%Nu&cQ~9LjY|2#HuKdpOXdZ~Yj*pA3r>AJz-!cfA z%}h@nJKkW|Uf5MyAYPRH-^BgTIN4_ZeD&{XidVhF?Au~V83|IrtpvfRp_YI9pZ%ZT z{C}U<4xs=3&sTqbr&0U+T|7lnW8L}yaCK>(r|9OAw6~9L_>N}WV-vZikip@G>75B# zU>Uq{@CTwKxAkGm;n~me*G+#yK9BeOoqscNmhaDtsYq+Vzz$nEegIf$k5Oe+%AkKUMeT*zV00lj2(IiZ`)NAp|aDe3E zNODLkz$EJMLjEWrZ=7D$!A+u}-l{e;)s$L~{%v;$zRWSLI+v+c?K_Vr^o&}>=9u+4 zE56^2d5yWwD%8-JO@C5bpSfF_$YXa$o#&g5){;zl}9P zdLnMGyg_|<9WPYA;f*-SzD_J3DzlarQRA^$M`XA5t@ zgI$|XYsM%n|AFWuTM#2F>{{+p&XMwJiOuz_G{Znd^CzWa0TlVaC9 z^?r(y7|QZ*22<*_iwV|!TQ6R{8rwel{N=m2cvf`Fo7~kF*5RWd%O4e<0X^>=tnaX_ zezLD6wm(0(x$y7m^r}`y^I9x>O=ygcfLDY}iRKej%4@6Be2?k$w5Z9K-=OaV88{}5$RY8Wb-Im$IGBl{j)*t`^#y?Qp{arv6kNZ zcXe-m^mNGv9bI^|ze=J+j^vkV>pWCQbq(a4_0Y=sVDoTVcdy4Nb-t)f_olXBn|qH| zScIhYYZeLt|96&$uKz8qBsQ7H&MDLk4qKUD;!o-CNmc%B`8U?Cw zUYgr*#8M7w63;3sp7w~(F*xeyGACRt2+?;-j1{531%0QkTpx&X zQ}L9RIy_b8ZDukbiY9jYhUsPkp* z>nZ3EpNhM@%aI@7zaEio;vH@imh~NH(Jd_HZRYTjmw1$}ob$|-#dd3d#L@T1?^+8} zMl5trbiOvCFv_nz zH#cnJ`^DYgOeX)XSPb9B)&z2l^l49hw0pGQE8jI~*Lp4Q5!agq#qA~O#q%@I>%Y(A zpPiq6`g{J9!PAN+a5R4SM0`Lar*NrpYboPxd*V5*05;g?G zbH_1)U{N8X6Gku%ys@Ad&(fh)9ErSeT%~=~1Rd;nmvGgRDrPYt;)J2`TOUS7Qp4cC zLZL6c#vp8xd39|4f?dLeCC-b`bqF(rio`femoKdGG`+;iRQy9~Pd}SY2#jJ0Y@HmS zgMA*7VRgRa)lGT7=B*SlhxB{B!-N%b4FB2_@s6xbsoLzd9NX+`(7W`pqwJn)n#=k5 zzvPAIES$D!Uy>P9^J$-tr2~&RG-GBHXFhT`6Ms)A?fd~0P^-BH2j9~NczHRY0^pkB zQhjBE1OU#NO%{PE(E&k`)Eq3L9uOZoStvCy8cGdQjnxM;YPWyo2_Xv}SMx2gpSmJ> z2iOoEeEjck?@Efz+5eD81M15I(H#^XK`WJ&+l9I5^D@1nwF6G22Q89T?l$Yy=scqU zzx0@<6rFMV)UH?y*-l}ekm5ht3)o>EGq-b5C*sj%Vrn=v-@9*o{c$4S$50{Qazi{qw*}34ObIP!Pe{o!<{MIXTZS5x3 zcRbNVO%z}Z24KgWkmabn6>!mV?N6hQbUSi-vl5lK9_(woQMz%76_& ztbgir4fyn$-oCg3YM|YhxsY&eaE|e*t_0thr(9I_a~Hp>I-YCe1DB({8(CNxE#nvT zGctY3+leybf^_zEtk{Rr3yM3FQ-Ut?61Qv*Z@&KsG2MOoY{u_qZSdwxJ*UI-Y(-w< z+t?2vE{w019hQ; z$5X8LV3&`?^mpbX%sYMo{5c{C};pPfcIS|l;@u&-ZY-Eav693h_43wZ?R6Rqj?sDD0_!?+i&G!sA^=S< zDV`D0e6hx=SsM_Mq=Usvme3xJ!2za&uB&x-SWVHO!y(Lq*AaU9Odv2XHHBexw(CDp zI7Ezpiy8s{du?8AYyb||3lwSo!f*D|ZCsDFZ0K!)==vjt)OL$rtA9CpRdqLQW?$4s zmbPBqdHj*-LC2+=tJazc*OKo#j%KG%idq#To>MwJ?TnR>jK<8x!bE4xbj(=aE)dv# z64_-2>{RPS3t(NI%CGat9}9hOeXbi<>K;FmMsnL6wlyr%6wyM60OB|z0O)eEGJ`!! zpEglcoMkMTU}+=hIxqNA^!@?+c3(~hTmfh&8{aecw6sQ(U8}hg-EN)aT31>mv#M%h zz3uWf34HW@Io(?#{89X}*HGM;GnU=cskn!K=3gmSn(f=Jnd*PGcdj{%-N5d3c4Yd-Txb&P;l z`n5K?0k_=FL7wJ0?m&NT-{K~FC^pAL5NyoN1}?409npy-5VobYzEk2AZ?`?dMPHhpSOdGyM$G=nR#E8SSloL4Q`+(vBHbZA+1PRB zc)TI~`_1^^^_rYw$EWOd*+qM+djQ)|YW25Th&Wy_l{w=kO?4nMl;U}U_@%D}sHfZH z0zg}l_F}OhZU!r$v-k<19Qu%2TL1)-10k)LGzEkOH}fEyFt8khMkqvEi;LBYSpyIx zjrUP=OTqwk5XHbjT+kQzc>`WwoRx}VLOl*p=6P?5n2#ObIpy&-sk)Jy4K_FaKz*B2$6pkntbumrVrv9n)l-myqTXG#^xf^ zR_bb2%@e4`qpv*G6(G$bMnY5Kw{&%>IKIV;XAXvNn#=WRWY2v!z00m7XfbnoWq|eM zB<`c8Q>qeli0|w=OY-Q)uSBwKvWA_c4&xPm-KHgpzXW6oKi2>PD}~V)2OIqgk*(Gm z03=+;y3o(LXei4|`w1|8QNggIwLk=Yj2r;4j>g151|XRnm;_9jKp!n;kGhrC+yxMm z6+`||$cFZS;$8nB?A6pf;M|gQcjPc=i=E-yF!^8d;zHRFHaI=(tMG5YmY9dA<|9IB zeWbe4R=FdulBhm3k*YT;l#MUt&f(Bz`}YpLJoTFj?=vmu3n02PcR7{+R`8~!Ija9B z3V+h!;yxn}PwyO0!+*6$X%Yq1%l-oz2jHxmHlIDo9wt*PFPf(h1DvMxc1~B;B>58J z-{WUUC$x>)E$5C`Zru3jH3q15zkg>Ki!{dHZLf^lZ1QmS8V1K_d%uu^3bd+iZ$_y5>ToVC|*7Qde5ETjRoNaE*8eqQ&nOFDDH$(E!ULF%xmtQfPg-fMLM`C zGM-mdBZl@pG4lBlS+PVpBAD5^KMBIyBl&-4)~$`3V+_j6~VWra{3*1 zOeLDS{kNvB2%*^63k5FoAZDK4n_Ik zFWHmKgxA#{`|b6tKtPOT`99Tw0D4ty?VqLHbo} zOfNQd@r0XdVn=yWA2YD1?Rfj*EiQCbnO00Vj+|dk3_zwJ65dL8mo`ztbo8=rWD{ho zTy)$vwkAYBjZJ6ywhoi!&U{|7mTzCuHgf@RJ+x+@#f+-RsWW0_H$|A$qy6M9>|6O_ z6jk_Wt-?Hh%AzlwK!9*e2ZY?IQxZ(Xd1pbR6Gwk@ab`9X@unnrv_&*oB%hy|2hwQF7Nz0o6^^$C?TMko$a!PSFsw*aAwz4)KwF8;T}6+PFXAA^w<)1JDDb*aLkUn3>ItSD?r(5 zjb*#VCeyxvJF}=}I<9+9R8HnCBegeJR7`GaMdQ$R%|;H3o8}WA4xfKXD%-7qCu}`a zar$lgweN%>+O1{B9Ie3!|Dd>x322eUbj^7nZErmPCsjSvv!t6K!&#KGl8_j7M1g-hvLZ81E%iNJ^hF-#vMyO zj)+?kjP3D*q zQKT5-mwULOHbKCER+F51?JKKOEBt)g$xijTpw>iANmugi_Pk$vhuv~Tiqmgw z!mb8LH1`g6)G28_*W=j69Di|7dpdce`5VK+O@KzR@&&Gv=zZtogHs&jaWjvR+HBc# z*C)MqvE!rkC=ezH&5pqJm-p5CAiAao2dyQDLXe1lq6kHTkkdy*P)djX4d9RzB#xV` zt%@*;E+jXBiQZcCefn$^Q_N3pgmv}d^pu72SR#d}eBs{E@Aa-y%?)@($yGxb)8A?$ zA-zZ7=E7SVG&Nb&jibfsZ`t($0{7o=v+|0B&(K%;?($!<3jf!hfsPYwX=hs%^xmcQ zG!esc&dV0utXqyln&uBOLxl!TY$0uHOU$i^&Su+9kw@dS1G}$t4dTq`4N@h{OxspxORwq6$$w2co`<~*Oc+S&E2eoA#Rpzb;Am)0Nm54T@d@w*PCYpT$+i|v7&uzwnVF8Jeg#VP=x_tEMB?-O_8>Q5t~W%KQd5yd^%<&t{gS(i?JXNM-f070M7(HivP(w3vqwd!sBs}riTUY* zAFYk9iF9`6OniW1lTln#Oyz`GQMQi#j-#<$ANYZ}l1=e6ytb#amPtJUDi(kt4n2OH5=sN||XRW*q@V&=AqA za6lKq=PJq`zmmYU|BLV^pQ94Q?~pjBWO5}ZGOy}@niWdu|1^Y&<~@<{f`q*IIu}ia-+w; zH%NZ-AMQqmRaAjj9wvHKPt%nW2e+iXKs=ANopPvj9t+8SlJeNIB37C~ZUM zYQ6C|^v_xc<@X*b@Reul_kiJ@>yG!t*L@KGiNXqTc-r3JB1m%Pz4(|`lz$*^m(72o z5W6VuK6%*ON%hCdCFdppR9^mOuFZHa$$5PHU0M-y{jhbQu?#JFCreR&}1M5Wt=F{ngbCsikOyU;nOH#|>vnroMxPP8qFOXfyaV$xAm$X%> z${e>VLLHs`^-FXyp)jvOTcAra?^aNRYcf58p3l)t>Y-+^B*pI{-XxxM5f07VeE5BI z`h8dMu=uA`gyPv-;pc%@8>R}(ugl5h*1V03^eG@Kf^f^XuZ4!&={>Wlxn!8c_49oj z0Q^|M+{jMOjWJa3tY0<8hgphI0DFs$nTMQ%NrbgZd@;tv1DUCj*QsexnY1JpP*ys| zE59j;Z+YbRt?gYqf<7$+a>YN?swM5HfS4cOIk^Gsj1?=j?OJk*_$CP!_=!C@d^EW z+^1y_9nnP+ij_;EC~rzL81dXtO&oK`BCPIMbbjY@{?lw`EOTrbWa8Q7%a;}GnxCqS z_mH1>8D-y);Pn4DxXT13M-({a$9(de7&*Fi z28=iOA=La4Hb9cOAzF@rIUc}bB!r~8k}El8WN!cxH6cW8(J2@Lz?dc;)XC3T@=*5< zLXd-xOPsBnb*%&mKlG+u+pZH5qH4d-n^5yu&v%g|oaWi$$6;J+z@)diIXJfP7)if$ zA|hh&R=sNuIwQrD;+PJ4=#ng!Nw+C%ro{;@w=3qx~Yq3Mw@ z@VLL>G0<(x7<*(IEmpSu!VA>|?8J%;H((oa{Mb(22MR5;s+b|&FPH}@92jTv8sKdHIFKv=}`%?2Nu|U9{3z5B!qFhK4L5(ZSn935n($ zyOCb5+o?>a?E7XuvP(ImT@L4Y?4RxgI9(=QR%>r5C=pg#nW_$+#^3d)X0?T-DK$DvabmxE4WzUHp3k`H0u6mgNsZnlu;Cmlkq=35lS7T6H z^uy%YARxYILw$eP4Yl2l@sehRsF(@DRz)n9Vyf+*NZh~>5IIoJ9r(7!hFk<#_$pKooc;=>h4 zm;{>t8LNJOWhTny0q4p7*G8XiNB@AZaeLvGUD;izn?USKKyO2v5-f+DC3g3Ap!}T6 z&%?FEnl8teM}>dw0euI!XJ2+T6K5(>goM1k9~_`_fMiZ_>VwN zeBHAc4;sc{S6{?1J|eo-O*)zhZvpt_>XPDv#}kx&KHRzK(1=H&@nSJv5(Eq#IB8B! zz+HyyE|y_Mr>SM00p?AU2hMaxM!!LkKsc13|b-Ve?&kF_eG@)ygry6FmPoK zSUE1-56_hjd>DRkeXTA${N{_J@F?fS2FI~JbW(|Xob+hBG9)J8edBK%NvYYkKa-b$ zOsxM)9q|9KP5R1ql_QIQm6=SUl z=={u)EGs#B7>=-YA0&dYxQV+(vUVhvbcb9 zFvu;-`v$gIj4QrcG90yPS`SEaB`c9kKQp-Q_RG4>eW%Ltp29REPAlR=O1W(A#lylh zjhIE(m%fbu(3taX&LsuH9zWkXaNVQ)RA=+x{I612N*@Wbx!68G*%xJ0MGWEPB# z4X@oYIedxfJx$U2_Ab5gvvPtT5?5rnv!XUcPru^ZkY z=_9|xK`B5uV2<<6Ll)S}CMbOm7qD?ZSM2Ez4mV~m(~}op^#|UY2^Wu<1Z!vA`=OFS;{G%3`bQZmIiZa+E$2?)QW= zH_p4$T;>in)Ke7pH!p$E6zN?SCy-3Y}WXY*%?<%2g6L5|Y&qLwt~F>zmP$t25PF?iwu( z^>FUCtC<|y`T&|Di8i$6c?J=;9<+#|GMB}4++J`k$=~s-2~DfxJDhRD9Mi7h|5bq6|5*SDt>x5R1)EC( zA~{ewcIQ@F3oW_PiaCZ>EvzE4Op47?zQQi_YBmGrm|+u=iHIhOfIEFeh=4wpVo~Cg zAIdgCb17P(-iQKD*%S26Tx>EUki2eQJ<{hoTa#zFqr=RJE%fKVI83gTg~I{kz!)G$ zoIaFb$qBn#B1#oDLhz#EARWZeF>?Ua@at|&C@KMl_^MzZ0c$CT@7~gs+v4LKLt#Y> zx=(lE3XXXkIYYO&GfKs>VQ{F9Mg@l0=kcvTjYsVhEuU;x7{4y2(L}22?<3A^ z2x27FK!7T-`^7ldBpeH^B)%4e^M(K<*vC%%Ma!ikn#2(jaE9C6hBPdvR19=t{SQqpfif9N2&3-_39^6M+kw{l$yO?|iY)?#)CEVOQMoHt zt$u?EIxK0!Lf=?05JTH@U3c@x!3MlG0=EjgloZm&-@o(LSqpiYc5TPO>cRfQr;SH{ zGn5|urgZ=7%1l_K1?UK6@OZ@RKL+U2TtG&E31zv=(H1)UAyJe$*-3VJ?3_NRkB!59nZ>AN81qf_uN=)Gv7@4KZ_4WdD4U^$c|2z4O*;y~9} zQv-1@0Gyc?ZHVyzl~Dum7+%0P+9Z@DI3%0|AXs}?112)@Es($xBzhtmr>G4rLo>Nx z2$wJjFx=tK(XCTwfkpnW5_x+F1VOp4nic^h$0<`&pg1*MU;-FNmF6sS9V6 z8RLka^ns4SEss>jtx=$#F9C-JgManPwi|&_-p{bqpippm0M?3GOIKrGe)U2WE`Ub* z&#A+_56D0zV$#WU5J0I##;ofl0Q>;s8+ADoASA~fma32|qQ#hPA$!cUiNL@yCM*MB zb7RaN=NvWe78XK37hsr%xT-i2N1;85@=c=!UtaDU_c~z=N-NNp#}~d^Zv`gA$q$%n zQ$EW_Xc7RTY6eNyVx{0IC6MXGu)8L1aHnF ziAagp*x}{6O8e&8VZ9bj>NS4!A?;NR8U2E2qPogTXVAs!#O9VAIC?x)Cd3aHSyzUg z7@|$wr;pB$?lrqdo$fQHFb&26GVL?DTLarh&QHFdPyajq7x^^uyka-^Q;YVN5zA{Q zcAtuqvj>;opPzp`IxhV8^I1njsLZ*qe}x{iC00o!BBz8~fR6XQ*@M>E=_neiRA2L) zF%J*f3%g_71P^pr6GZ>5aJXN?yK)5WOaAB<;iFfnyyJ<-#h!$kVX zMx6YC^Y02o#V<69 zTO2JN)YT6VET&)+ZbL6T;L-F&Hw^w{$n&2ROWIp*2ZH!^b%Q7*RuQO5m#}=?%VB%$ zh@Y=PGBpsXsc8!ZUU`65ZO(N!Rg zNA4CGIzAg-uD^!8*1vjl_P8Mv^x4>b1~UiEZ-q*v{nLb?-{iL>WlM@+SuUpAR%J@# z+|_-1AN$CxV5R9p=T9Dn%I+N{HmBV`Tz&Rd+}*Z;?NveI(?3&dckYKX)R;IG9$~f? zd+FWHih2jG_cW(d9CK~9rca5oe-;~DLt$aEY@Uf8$`4NV^nUFqI^FmB zcWS?ORVBVVYJlN9=|Rxc>CxIRg`L8>2XCUPTI?)! zC>rpjt`>DT0}x>XYS6nz#UwX=v{);O5YkSDbLK*6!mf`-Iu?dgP0H9SvtN`yXFd7j zU4fD+UQJ4SRSJ(a6RO%!^CkX#5hikPJbNZl-7#H7torYTzqzQ9zKQ(`EX9H|%@-^p znW6VNUFhP&6@D4@eDxhoEgAN0@4w3K2}@)2MRx5KI3%ssNP3(8_41tx@Ip|0*LK7H z=2=2ipoUc+dAnyQYKKljI-(&7s2ka@+8)?p zGY1ueoSacyKerW2Ol>MHuZ>^rRgg&OUb+}@vRP#pXeQO4uH1B^6Whoekk2S2yK2nWoJO;HCu;Sj`R?d3DY$;Ts2 zAeob-M4f29i5F}+=BOk>lsq&MFx0Y+QYhw>WPw3aV20P&k+g|a#7w53dRu<&;Gkrd zL{Y^P4*y93jUui+Zp)&OWCM|~iw3FV>*9}W_ujSg&!#LdX44w@|6#8EF1UO3=WU~~ zQ{Bf)b&{=1PT$7``@WZ2i(~}|y;)Q|t8xhbe!g}-fd^PGQct4-7Xytg@(iHjvYhuI zqK|m*6nJvGf7q2b{Gd4k2F5mZRgad+!GVS){XaZQ0-FVq_a;+=S$j&;;4OB(>W~*0u=Y>#s zsX{=FzI4mzTV?mM)7uSF`;-0GFXzff#!DRp+9m5&OKz^hWfHM#QhN+PCHG&itQEAe z?FC#Ly+c32>R7T=^R->#YuMWYFrso^HXMf<5&O~2YCLGAzGI?R+x$dpEXYg znva-!+`555NXS})vHF@AgcKUi!>k}6>hA^MQ3TR~9t%cbF(5XFp%9XhpS}GhVY=^j zw!gU)a#WgAEoA@c)U_jg50u4%zhS2W@9Aj(Mdva!6S=>K)H`vd(!X8LDKC-i%Kk2Y zD-t27!)OB2-#K}DGehi*>wHMirq%d?JbVq}aZ`4U_M~R>^wJ*2l}COZhmnX>J$;NV z6vQ<1D+L{4aPi8WnplvA9JSmdWzAoh>12&}P(|_;UE`DxzN$-ZmSnU?fOfcc8-5P`#{zwJY?#7gHfu8#6&8u=Z8AA ztyyJ`l+QKgHOn!_3x4I5&HCRX-^>oaF7GzFx%fI#H>lV5N2Wt>4YJz$N63ikRC|x# zkC>sAKz2VXS2J}-D1N^4{H)~ytBgE9uWwnimwj;Q)U#VwU1K4p9uCep*5=D8LIt`9 z^}Xn?tPDcDnnJ!)w3DAKVHUr{sb8!-pgB{F9M~3c%%K7#R3j?#V@8W{PJFVz?$$z3 zRG6lk2K**~V2>&SH-!H_yc2*E=7Zv%+fyq4sYmq4Go`3HebKR(z4Y7L)kr!4vEdag znmS=yNO3VKtag`NobRe)5iay`ad3#FoRwM3b(Qe-elg(Ob=sz>l#rW=+C6*9dZp;e z*CC8CdK35|7E5mz3Ldy9cddTxqm5G4ySTK`2G?Px#+?)j(%DHvN ztz~c8f*)HlG9=s#Lv0Cq2>YhQRZ^*-Zh3e_x~}x`r+nGb7low^^Mir9J09SMK`<*| zd=$gVCd~IDs>+ZoC~gl(7}sZlV3Z*vaF&<8l~iD`1|NO{@p!g6Rno~rL~d!T*T7?u zR&8v}OYj=wcCLd5DnsCe?Jn&t2)ExL5@$5F)P5qAh|tgzT)p-|djg)~J9Bx3aM zsxIl5-y)3aoI0)zy)CtT#1^jhpVXhba_|ey@F7}l7?Iy%2uCfJ)YfVTz zL~nS&BBo%fYIfU1u(B2Y%;i<(m7t?S`XjwxM>F3};@|r9Ub?AwKK{)H1OU;-=M4^M z+YzmSs5!~sY5JV&&Pf6RY2)-gZN!T9FEfWa z7zI^Uwckz%eB602rPuqSs`_|#JaB&gb8_LHM%>*d3?S3ln7qNg;e#1-?HSE29AOfr z#M&=ET7PsK_iP>gX$qYveeqE6;hAmXfag zaMynv7N-2Ziig*QrFq3SZRa=ZeQPk?_*EJ4XETosJ4DYF!V5p0ZTgF5GE1!(2VAH8 zQ8Urp&v^KnQq4$FkEWX6gshesXq|{*AAY=Z8Mm<>WZ?tDktd9C_9rxx+Q^L(3K2cb zfB?4j4bqi=8`2*{ide!@Qo$WHW3=VI;(lfIF9!8%b(QxTb>=G}x;W5?rS!)8Wc$Gt zt}%vM-0gx-QA|0K;=4C%q~oL0Vef^-k2H#}*XE0I9DJEcYV7AZ)zw#z$ zUfl6uz{0@~=8|In?<(*L))BT8DHS*qb!sO6R}GWE`xoOypuHP*)bjnZS$Ev2Q_vIk zqF=4T`AO~7!vEYBQhr&rWgm+N@1T7oA(}A(H zUizgbx(Y)?R6l*2D#h_W-Ml3|@nq^rqAXE;NgM`q*!zd?-)N%I#i1Yj)I-sz4J0=s zR&@QR$8oT7j;IFODAX2PNa{?O5$~2M?Qacxvfhb*&9v~NX-37D2^RMyOMEFO5@^-3 zo0fF%tJRlBc`nDB#oM@il-|0WRBnJ+S%|80P6pAa#c zC`-dO+gug>w)ad*Q}i08`*fa-1zZ%=e|r?pc@?d zY-feZpG+snr{-J7=zXU+g6km7FW+L{D&qTPsxe>3{;hgnb^40-TRP6}qFuFF^^v-D zJKomib`tuQU+p)o_!kWI62*R%sXnf1Nx$sauG_u;P2DMLetCIQ&M$=4_1*ARRZ^^( zEFaR@X<@$tv75jqq_-w#y?D%Y1T%9$~T_3 z<(Ab5z1Z-Wc5irae)hZ0fiidg&XoVy2SOEZ0)Emf2Xk{TPIp&zI}xgtYf9`xw8^BDKyI_pLKoce~g&S<_f|*{x5-r)%1gMExtS0{52#4Yb@?kl}hd8WZi|VI| z#R;u%Z0bRT*|C|T3ZpuEpcW}EgUZ0+aU5p3bqSHLJ{W<54;3Tr*u zFF6=}?D1G$@RnS(I|d?x)uvBsb0tU>pfGl%DJ5FS&V|#xM!b7<6wo}@N;{1G#xR$l zpT=RCNGiI~zTwAy3vlF>OIX?Lo0$oPW~YwtcBsgCLY?a3e%Fezx8vI6rWSYZW&-=Z z*6gax6w7t4lB5kO)54$b5~njyBopGbb?1bbHq~njhP4F*YFjkhW=i;g+V!WUH2m6c z#c~ZCx(w>_oIU+YG>wy)#BRQ^wuDtDdscj>fwSUMuH|J5l_%Heojdup%2aa#-aqdg zZ809$^cV}eweQ_F!Dv11VZ5we+#g%s7ZU`3RG20lKi(#n^@)K`lG!O}CR^!36tt6x zBLE*0t}f%CHUHnS;bw+TFYQDF4>JaSi&sn@?(WK)6GT~HtVYD$TT0rjP-L=+{>n1z zVfnUxZsK(GTKo9rK>73y_165m*GXIzC}VwCbXy5G6u|{Un81xe%(yv&#A4D7 z%OPq*Ne!BnSMelYP<3^6`oSBBMZlE-(g=_Mp0Gg);E`E;!LG0XJgw+62M@$Q{+PR0 zzl_jnyn$TB6~or|W{lhes{&Ph?>Bx5**xMreSI~{z-y(wkWh`*y1@#m`tt$xxu1o8 zH)e%=j8iVIyd2=TJ25geFr=``>mpS0XL{h{T$ht!U4A!M@1fb1SD*P3NskM7+4 zbJ_V*t>UCAK-K6T<`!g*2J;Hj&eO`{`T@$i&Lg*)MNWhycO9 zocI#D&s1XI0&{)OES#qmhk&E7;hT^~;cGZJld*i9ZvmU+JwHJUg8U6uIy%iE30Xhf z^lT{@r;&+{6D9Wz15o`0K?WB9ktU5k58&#n<|D+tNoI1xWeI{H=oqmIT-@GMM?bwz z%SVI`zopeWTx${{QJnJ8#4Xy18D!+wN;vk8yT?5LD#qqb%dhkxFyeu5%_a7;#ovxo z;SyIeh7Q-NQf|^suD(-$wtD^5N$=XnG+*u3yQKazt|>e9nv@?=@Bj2IG=)BqC5N`Y z+XjFtkI4ibM5tPBsJ=IcpLP2!$Ol zQq!UMvIc2mwHcPJG35)Ud7{91dkiA;xkUQ%u;*Zm&@0Sbt0Zkw`;tAk-l8ZrqI^4X~oSA!L1*A!l7j>3F}MZ(=GvLL4VYJ>4xV%c(KP* zqx{Tf{D#xojO-fExjmrtStSH$?&`teMLiVa>ZT#Ad;{+Dk6O6!0JT1R+jXpb5NeD? zp;f0r#c~&&m}^WjDw&B1XlB&<8(SF0)j|f!x}(Yph1-Jk5ze5V$pXjc1y4oHjmUE- zBsUH6dyR$woqW^lQJ-q`EQh814+AhELu1|%?IV9jYP`bY%YNWYJ~<9oyN}TSZ%9(R z9WlIZW5{*kyR>Q86&h6_$+>1DdNbhDK$L_K-C<*pt&_X1KCQ{fLz$Z@dnRDdb3MMJ z!=5K+HV;}R+UTV|-wq7ztr7fj=BEc+`mE*6b@^6<_nKAs11;gR{d#XT$pNd*ug@LM z!$n&|Z}Z`s9kEA4KrzJ=?U(x_2T}Mo%IX~ zxmzP;W~^eM`_k<1qKj58;rnz(^s4a1*ilnYYSg4Y#g) zTC{-jY1{Wa;Q^=!`m;;6%)Q@pADs(!Jot9xcRPt!HhQu{d*W-=9kfm22k58ZF&@mW zdApB)Mzf4WHSV})kvCtUCg`ha7)$$V$iOm(cm=!l^3X*(SlqBrAti8%dm@>aKF*LZ zlqF{Z?c>6fj+h#M5T2RR*O|;xBvvNSaz;No8Qf%3pvo#XQ-(SiWn0eIIw{Nx@7|jT7J&V zl=uWKX9`UP>Y9zc*)0xGE%fU*8qCtNrAdVBtpj%tR1m z%}i-Lxfqne?madHRe*dSdwk*ymA=tv{+QBG_DkF?Z*P0Z--YwOo9>RCWw7SxbL%v1 zP)B!>B7>;7;;_7#QKH8IHvk}Ek&NCa{Q>Qzt|J~}Nm-3^Cxt^fdntc3Q0$q(17p6O zYZj$w-*T;A$GlVkh751&csU>h3$S-@I`i64GZ!XF@(4I2_T?-7aYJF`*{U`F(CBZc z#8C~HsE}9>9>GB3P||jUvY@q4QYMfyzuxuOdVt?6&}VM1BrAH>J=vPpP~2RbmDUAM0q*Y8ET0{3i-u zi65)1bRWMl!F`7tfTOxqc~_h+92&D?muojCKOfIvUYz{31W*}SB;wRjOQe$MIa$nBD{u1wvK znLNE|#u}P%?Z6Hv#U7)r*snZq zrhEIfaRNQIdu7e#cRgP`kx(2uKA&?y1}A!M;)}_!kYTC>;RFERAe&3tMrJUkbSYx6 z$T#&CsCak#FzTVg17#{rF|Q*%@rC})5Wi*HDR{*IkNgei;ma&_93Nli>(#Ej3?+S{ z3v_KVtaZKZ`lGlx_u%(xo`QF~ih8}jyX|35d%XO4)c4>zIc)r&Um@>hwwM#RT^a;4 z55NFW?cyEzt|`XRIqZl>XHk86zDZ4?q)UX7oMSf>Lri9n z^S}oB6=vuIm^#`R#D>AokGhswNk{kX+y((j^8A9Aut@6{vd6{RWU?k%y8|Wyiox&b zo|kGh@YQgzLRsK^EbLbI41ZhN3HY$>?6|#6v*=@79-O>9{raPh#>uDap3lY}adz2) z(|zyK&sOnPwM#_!OYkI1H`tGT?cMz<)V87g_2FHOf`9K`|G0J@<;l2((~zoEVs?~M zQJl>L0E*)JjHrV83t{%;74D>g`TxVzc}KJPhiyCvB7%q&vx!w(#3-t5VviWLS2gxl zdvs{Tp0$Od_NHcwYHQC}HHvC$&sI^|(&6=c&w0;zpFi_W{z%Tr_1yP!U-#$Ya_hUL zIo11yQG0*|YScXm66rs?+)#Ge5w;{yiuQ3vn)6i6qQa(+H)cKOYQ5xwNGjnKa zwq#}1(2KBSQ7$WrQg6*~$b7LSNv^grALDNLf!_CZ6wNl?6iXZvAAP1_^=YA^#p7c1 zRHEMJR#o(VdPl8tyj|Oa=I6DC8wPv?-`dXR-9OvUmoxRkT#!os^R=#T!Y%iAn+7kZ z@ecoq!gt8_&h1MWo73!B&G7o;SDJiCEuMScX=**yrTSl+QI9iis>hUk{`$y_D;Ydv z{(=FpNzl+l4mzJBV-y8=oK4d!SR+H7eJyGJ(W>Gd5`}N9o;d#zp+Ptx@Qgyj6aWmK zk3v`i8;GKDfk*%IFuE#CTjb>wP8$QYUvY4-j{ULb!mN2U*wRv_k)v^RJ;IEtUp1n~ z2#Wk=x(tD?NtEydK!`YockWo)buDpF5RpJ|gQcIiHA32f943{AhDs_-!{8cex+`@6spAGE=E zXK9`P=HmHDu;t#@#p~mPpK71c`zTpZBB#qf|GeS0MDGx% zPoyX%{-`i^9PxnoSu=-Q(NGmxyN}?SX5Ae3xs5j=Ial&d0s?gBe!uCm`#ZtT%rmMO z7T)Z^@xIl#G5htj5?!)8_s`CqbVpJChsTUpP0MW`=dd3fpD5h*Na1|w$J-@Q4$=1&Qi2?w)J=~Le1Lqm(HFRo2-i`Y*6r~M0A)?-A<|JX0A5M^f zIR3e+VZz-cPE7uyP!quDV>t^W9XW|w8A~3QVo~IU*jgy$LHaN=RD|Ro4l><{Uj7yY zFiAB+9&nou*k4^XADD3SVt{a@dux&92{vB02&iV`t6|8ScFr2br$?s=e{e_f%^_05dMTMGX!Ejjetkyi=K~hz)qIZSFYp*3mz({K zU}@Cgw@^ShToIg`9QTY9*E{mc*ekEoQvGVNxbNH7V`V3atrJWlL(VD3CKZr)4*l9Sj2r(hSUK#mW8wNR>*?CiHVO#~cl z-+)YW#H18{Bb;`T*0-FxE>I?)O*4PvAf++I{oXy1!Ao4#^ z_?jbeE;x|M_liNCkY5|J;Wu;S*=G@F!^hRqC!;>EU@q zMw{~t1918P8WEA=Ev{|@JS#xMxXMtzA=G)>28DcdUQ41pY_MeE#8sA+Q)VdYp+;~d)%iN zB{P+pwaqQtxK(tP4;grS%iMWcmP>j%r~CcQ*~9dsRT=L;5?JG<(FHNk;wz{0risrn z9d|FlQ{qfY{b}F6b$xQ1`h%miiz6#<{0kysOZVhB%2IDVy+s)z7?%%z9+ zZ>|Q2Idz4S(dIBdhFy$^~8)3z=&oD>M zjVs6W3l5=adD5p_?_3l#o$c~R&(rl0uY8qy>r;jVug{hhQmPcu z(#+AsV_f4ys#15TN=GTtN&6=!x57I%oy)pz@#aim^5A?_lla1*kW`rH^ExuG2zv-a zv74Hnm4Gfqbof-S`b``?En)=llw3HNKY^bk_mfw~|L`${q{@w1WkakZSqJ~XapKsM z9tjv3faxAe!jJ(Gz+Q+a8LJgHQr^M?qaX-GZYK3Au<_&-DY_>qc6h@g(JRj`J%~j`d-UIbCTiO$%DVCAUhYqY_6_wb@t@0*boqajGsh=Rw=drs zAD{e7oj7NIdP?IG7kSiS3jjsb6)}=vQN63&pRCJj(c@DDqj}*e;yr|YaYDO zk#gLkK^7Ha&3Jk5;M>LGtFE>f>sOQWukKp6oW1|+dHt8?O`k!o-mTxyf*k$|{^wn~ zAkbO54HrBh0M@|nY7Jiiw|b>-SbZb_3pHFnUrVKM=oXe^k$>WOzcA|tQ}Sg^f64Sj zA~6Jhs;C*|`E{Ym8TSPiCQ+7~zt?pq0tEW0VB<4L$_ydx8OHb?fCFB8%L(pbk6OT= zK^=5h{d%{^62gPtdV{0Z&b5Q;p$xo_AdrXSl#qPeOKbna&X^HpSzKJHIs$1-1%uPT z^Qt;_vpAXQTDqtfsEQ$Vm=Qo?sv??3gP&enu{XZwd7h5_!i@+SoNn3Yg~V()<6)D$ zmvIXd5Y24D%M0OFFl!Q8XYob_%_!hxVQNhD@@4J3FJDW?9_T@SHFj%5NSDB9NT%W> zBr)AplNLk7JKGPtK$M+|V_1!Qgo0%UKOBEWsUaYY00$QoltBQU8lWPDM)Y5eq>nJr zR9yxb>Nqko(j=uufEefc-tE_N@0xWz-qYCQOl?YJoMqY=~>`pH3KOB>@%J&5M<@@*H%I#R=Go=xKcYXB zI1cJDT6PPn6wa6cQ%E+#b%4DDak)T-Qqe>q07j7qOie++pkOST*HC~`hjz3(i|Ucw z{C5+uoOuR2SVpIDfACLl@{lSHk9-Qkg6cU%(5Y!$tNA)ql+0J`9Tu+NFQ}P%M~4wn zkn8wZ!jCuam*5gflTy)f=A%~Q@?T}0d6LNDu-coNf&zO=TL`2pXZ;SoJO7r$&U?;S z$@oWr&)qD~(C?tgGl{o4gO*v(|C|I%d>Q?=TCI`%lg>Hmi^Yq7dJ5;*uS*OSnLZmK zKR;c5vsH;q4bF$Rd}1DhLqAr9i=J}VxukpWCO^JByY=kiLb~do#?_zI=Nyk(P(KTw zUA0Nt-ZeTab^xw&7oBYEsKc(jCrAD7i z{i*>nOyL0yN(j2Hfr3@K1ZsjF1oCS%Mk_7ZFKHram{#8zIjn6Dg4kw)dLzvI<*P*d zpU+wwR2t4&VrapDoL7hRIt2H%X?W&`BWMzOM-A&Kv%nZk?{|06Z0RbjwB~E0#p&@#}s(N6zo+2jl^u$*PwL-2iOCq*QW ze8M{`FWD>EeKSyD|7tcl>lfdao{MI`ZYnhX8HO9;Uv#-F6rAh;iZyPpMVC^S(o6Rb_F1PU=kFcv4HzpX&1IoVl{bgYh}F+TT*yyWK z74repBIN5>AEO`st@6oZv)4}5%-?Y5=vI5LXV!9BNd61G$MY(t`R`oIv(>vBf0J{f zZ#_tQ51>$`08@IcVci03IVChn@|?v9#0tV)8o2SL4zbIa<+9M2KM`>&(ko#DxUCKo zqlw>`8w2}E(}-Bw#hc2S+WwlkiU!Hk+8nV{C;BWxLi1KU#K75h(?lgb>#HWx)zn;k znC?zv?K#Sk(2uYTrJv&F9r%|m`cHVOUK?wQDTnTfJpc3Ihs$5hVM)RJ+JD_|Xs|v$ zh@a^VV;F50y1VBxjV!4l(jdRU#LnGhu zj5N+y)|_sxR*)dpX0V)Pi@lSJM}l^*t8nSKAIi;*Zz^><*JN%S$;B%1YTMsWWF~Q3 zmDvd|Oqg{N*8bjz46T8mmYurJYA+c>6b}r`%Q*L85&~RYS?Ox4OiupE6pP%HiRz1T zOzobh#HqsQ4|B=Hz0l&TDhEax2^G6fRZTbQU9~H%q~>47{4_*W5&qKIHkT}zZjrwx z1WLL#wY}0b1x!&J$rB*GFcaE%90&n;o;1oUheQm!bQ|%!)WmajNuNFGK0*Qjmy0ia z&=tA@%6Ll^Zdbi^o%SssjuDG6Hq&Dds|{FZz|?qw&<~cb-XBdDl>LfdAwhqHTa4Jfx9>& zev)g?l+OjS3CGuzHV z%1CFhuSG&Ph<`fVFz57on{@~0OT+iBM@2_HTL#2$oTTr$Sg=^NHCD)?489q65W?1y z`aPESbwT^!)LmdWap~emdE0_%}Y8J~zf5C$|T}Bu6k*KI!_Sd+sF8*)2Y#9Om+}(8F>b`1) zo6gBSQM)Uvm1bXWWF!=D?MQZ&yI~{6P0pzq6j6Q$qwqf*fWh({t|vozJ|%|Xtgw;5 z>oSs^>4+D~3ZYmfBY~=MsNqAiv~jJcr+LTomW(SC{?QK)-%667 zi#znoOI+n=VvyUU2I=MBHg;@rVEy}|?Y67kH=94NHUOBsJ34?OHGj2OQ+^C`v7Qg4 z%<<4+25wwgYcD#J2NzJ=ni^;3|3|G|zMPzio+j*nwYU}i4dVPD!S=#bIM;pvxkk>B zAK43ISC0eg{oGpgMb(S!tFJuFltmvU4sO-68?qHCj&k4Y4nHk*_k^&+ZfJ2TU$>5S zoo;BFn~}4XL|^DF=#cPoON3L)&=dmeU|RaqQ3{Q{rPODqI2>sE;e>+X_JkG%|*x|js-~EsmsJ6 z_~Xncm=Y3rEj4{ra|eYjyK8)}0j8$&cIcJlbu-#O$}Oco7kTKD@nEjQ#Ivq>(~_CJ zsyKtSyn;bbq4mjVw4iO^T+L&UJ9}5NY3N_Sei^s^g-lMfgMi{q0F^re0BCwHP{G7T zDpGNUt|E*k90P`-ngd}qgo!L8FP%RT?c?bw3ozXvGOiRl767#PIIF&3@aQ~!FN zX#em^U3Zh9P*Rn`u=XpYz#m?ysSDTlA>p63{Z!e>H|HV@{OQ~(Y?Bx)#z5LVpGRy$RAvVhCc*Z@bNJnl zw!C1U%Llmqiw|rfSU;u-FMW^>CDcWO%-ZAiDJ*Rk*c^+fQOF;yzhuh*-Cx%$-Ay{s z*>B#OzTQo4JO7?o0a|Y6geA*er?jBy;R5^-C~WMQ|ID8HzKqXNB3^4h9iG8Bbi%e> zwq`$LVVPCB#Gbln;8$O3lpbr*QSxChbfin}np^M4XH(DjcJJBJ`t6zBNm)l;?f%BE z#7}A)&~8?ej6njr{ZnrSguI`YFn_z!7zAUTt};P7e~askiT@C~$qlaSban_aZ;$E( zU@%ZhNRJ}wPti1i3VD@GQkKbWU89bqXCs4nU_jC@a5I~G3hQ)Ed} zCzJ}-IGo^f49r`^#{Az4#23(z z$)ujE>L+B71y<#hRm<3B`y`rYol-sjH7)_pGExiu z7lA+sfW4NPT_-|m`f_pDIF&2Jv$v{XMncs17q|qVnP7O&cL%Y1A5-!HhdQJXpuJN_ ztqO|gr(eul4l5KVkDuC&q(vM?J>W&DD(6oEWdn&>J0c* zZ@_e-<>b7;#ylgx3_^`|FST2Nm6l`u@!C?P% zyB{HhHjZJa_2>Iwy6{c6Kwxsn_@Do^1`Vl+BdMXtgHkT7`1ki-{P<54{t}c5iN=n3 zp~qw1yZzpgozCg>S`(L|fNrm3e!;l=*5ZJL07%r100Y4JqCAXY6vi>ujPQIuM;gG0 zh^7o`VH}GP`{mUUY~toEmBXCszp9>;6m;dUeb%mxc|~z{o3LFlJ2%3D z=k4mfGkZ5tV?x$-M{gU>$KM>c=ilNaIQy*-(LEi|hFlrE0S{w8<;{BY)fuoMt$%8+ z$?e&S@R&2(7n^aT{iW-E%bEAHhkW@ud4@OgUzo*|1)Rvhbx~bu21zUhh57lK51pD7 z;l*KD!=H_d1KsbKb}bs4c}&tr%L}oVeQ$Z`w0Af<*HB}-+s6~L$5L?1b?V<!bT{C z46x6Pg_!T+q$0=oZmF0MmjI9U=9ZH@}o?-Tte`tkH3U z2)$Zvnd8Z3T+-1K$)QaU40p6lIro~_fH!D&a zQGy8dh~G$1k^!rttuvK&A2y67OHCZ2sBNGnD-y5E&&{;AvxyD>GLp_Y%Ssx(>6!px ziU{pnOT@S|mDXqg&1FADNVa^83?;QATwJgb(_6Ii`M4syY^V`J9V%nVd{2f71z4`_ z9~+|cEzV6NI$%R8JaGF&XT$#x#enFwg!yf>Y9R%VUgm8$*28>_x3^N>eYfd8r4sA&%clG)f&7p zZLY35o9KFk>?z{s?N6iA6{$>AiT7oa7ODxiW)Az?05g+n+4i^%lFj{36wV@*nut@S z_Y9lIysd`)1iD<$=`VLAGsc*jDv>XkIHkpZejR-gUKw;n{dm;a2+)t?QbeQjyAMnZ zsBjWlXcgcTwVDkI5@!d|BBiQ>h2g9>8Cghzrmu7~2KivMty-6NQZC@-e?{yHMe2uJ z!D`^b8htNGS8gEHOQ+v1;7uP8xTgRpC|&XQF81~|Fe>G@SCJPdld-eH%vW0D>Z~#z z)*H|@dDx$NR+-YfjCnUiG8pilY;lb>&NqQnuSM_rv(pUU)pe?gS^6!SHv1^_)!lX* zj9oVA>0hSyCmWU5n?d~z@Pt7J7^nnOr@|Cfw9sQu|IxaQh}jCiULhmNSI74ks4c-! zV~Xa_aH~^MMRId27~i=iaT)BBSb`Rk^!v%E|K9S%k9jyMW-v2oQ11x?iZ@#-EupUI zdyrGUS2M^$NUUc%pJV6}66FbP?XVB|@~~LNsjal}V0G7L`Y3s>_@-|sRkmsb1*NA} zjJi@GbDgVVY~+`;l*Oy#VnaeFXmH3`(;+@DB`5*~nqfs~23{e$QIq z?GYI}<`mP}Q1B`}@WcLNRszm40?s)bWnMnQM%lgB8>0@~Oc!|XEPvpOU2t~E&jNS2 zuxNTcyCmNS>Q?@MThw}1yun^^dm4H3;>qi`9V!sR5c<%|C9uoK%{hO1ivfaNma_;! zcw@6O$L+)7ttkGM+XT0%FA%U#1wYR#q!B{&Q+bVTsf&;rsZs2eXN9hi9HO1ojRG=n zo481w`30$3An$QW^|O_Aw_y~O3&lm{fQ?!yWt8`u#6G6~eVUD+=c|ZKO)f?Fd8Lwi zV3Oi=Fvj~SLj;VGQ@!d2Z>Zi^-U7=r9X?_I)}^596F)k~l~vyIi-Q}cuvBk-O07fi zF5Tax-6F}q)snW*yMw${iAnE@e|(u0GUmn1+@p>)H_Q-^bP8-Jt=3~;iNOB}kilh! z-Yo(5y9%Nd@A4!^A^#FKPGea(W)MmAC7eV~%to_Qlvl9yCAW!7Qnll3Z)4a+G^II? zg~II3(rrhvA%AAF?l=P?Y!`Po`JpTAM6cqLZNn$wzM)}$BSz__y5ArAm-GgZ^$DC^g1UcNN$dWaFdkqFPsl;ZG&dsM=Utlg;yzB0v8#9nHr3*0f zb%qrG+YW(_fI0*+Y%sR|&Zsw+fXU0lvt~e8(CFKmdOi;Ew|^#dN0GZixp#!b%O9Jt z+%M{-^%-m}{GbWIZHr@!H|d6Yolbv=4nF(tROC`?!L?8C*yZwz3N5`;F^l*zn? z8lFT4%Af@IP)Gw*J*8q*0MklT)KDKx|0-&rwGQNWCN9x4c%g3g@l}Y8LCil!5 zy-oP~DCN!T!3ga#au0;Y9+jbeI=U`q7XB8XiS)vohEe+Ca&Cw~L*L*X=^qFiXrR>0 zAxR$HgrK5V1nSbdlt-o}>S)0R&px3(;&5@tgJ+n9Te>C~1A2T)QaU4ZC3zTI=axat zl}ch?>rUn^KeCSPQ%6dHd-noTnnMLVXdII2dHRdhc}gF?zAg%Q2F@Vd&|u_lz!ucZRfU8=}-af?~#@xogDQX6kvoN2maU*29GD$2HdFgQUbpRUQ?3)2oqM=fYxV*8% z8GY(Kq7uG#7z1?@rOA{S>H~~Wo$c%_Zo8K^5B|cVv_)ero-hKMW+XzL8gE{4xGred z9p)i-B(PC$UaK_Vr)PaP*E?T#9n7f~;Cb>=JftN2&Z8>VpPd=Y+9HtHDT8)~NIA#@=Ca$wVU!vY}op41^HDru2P|01-z z6D13J1sHm`+oZ-#yPpEoPJbe+bbFnN1l@RTfqJ!Go3JS%xSC8ou9WJyRhq`*UDa`D;Iav0a>&bGl@GlNym*ZFUdk~`Tz z)wZ^R6oGQCYg)e(#Wr*OOO2~+CgmwVRE_OX>%F?{h;2Vfjn|K5*&oztR6838nQ`As zSp;i%20E>C2@POA$^tFEw%%z`d3aiVPr^GH_QuQ9UU-sWbUNTB2BA$U;wM{1Ea&IS zO%u(LiUuVSB%q=78^ze+(W7LdE_ZvkeKxR`M<xQknBE?Hx{sfjNpYYb$VZ@XI zB(an}*ivcFHaSY0XbND1D=fk(^BsJk< zJJwif#87Znpo+So--;|&CKOrFhs2Oo{#5NokVR#={NEoFikNM*X2(9cGVfWs#XT!B zoqvkgHZ*jYyDlggz+G}kFQGSHLwg;1|Gnz%%Y2*%AHd;73RfgWrl;#2A{w3`fu3tb z+rDuE%h~gCT$BFD8eH}1s5A3gaCB8bYw_mttL*PRq}Nws!EU2hs8_|jvwOJI1;(RW zB*Rw03fy*$?8)WDnY+J{`PW!oja$>6k;{8{)i!oiaivz)rGA>PP2ZxDotpEwD>ThU zyS6je63nx=24_~Aj{TI~-smXd(wubaD$j|ON;f}+h-FGJPR$Ry-8bNh*DY0^n){H+ z>I?x2o_YE8O!(}v(pebtD|)MzS=4Me-0xla6m<2IqSS*I>MDP|jGW`k|Bb`HIo@f! ztSiCu^v$^{$Wdlm4$kIjI_7JrrUgY__ej;HB@*Y<8<9GH{=i)M?}}J{Eqt||vQ&;Q ze-;zq=jSn_nN}4WL}7X#tb^ZS0%udUfi7%by!9w-Dnj&w*J&6r;5=hOC!5vsJ)&cj z$NlZm*<)+N?+*@6d~UN9B}!^l27G(PZG$brX^03aV`HLZdEBEJ_OYj~+qp5Up9o{X zL$5M(&scvUK;tW&$cl@9LI4T`pa3K1fs%=^q|s)C|Gnu3sD3)8)ryYOb%D^r{}|!B zYf-vsPC~F|8dm<7pI9Jtw6R|JIyfDu4c>Ra!;ys^*k z>kY*^JXbJc2VOGPzUZ7()p~B-2yb?jXU3F7dS+S$ZCWT=gN%=bo@%gehMsoBfV4kE zx3{O19NaB^j+~IxnHgX*lTXH0%+`ky5A-?2)NXkf|Eq1h-xE!#ynYZI)05S1|Fi8mX_1i5sRi>Uag(( zT9b}EIt|VGwvQT_cAQH*n5OiMtA3lpz8D-!m_{w<^H+(H(seXF44w<7ANlTXjOxZ( zXQS6>4d-KnWxX{a{>}HHka|^eP3ms|$@rp5-vODH7f;_TS-ZFse=r{jR zA4lc0646DYs5oAkhY)s#xCGcKrw_RNh=}xy!|Gl^`L>%nQvf-rBM{w!)IhLsz9>|9 zM{b18uH0O!2#uhNkU^~0IK&F;_S-ZzEAdw&gi@xOGeAf-y@Z_luwKilb3*IFMm&pc z8?}4-H4Ti7r3Y22adQs44YhcH7Y9uDh zavgogQ9Vv0-jK^Qa=(29tVfH#m!OXIGLRIHQ7y3$PZa%9U5_%_sN3@Y;IY?AY?ARZ zhpm4W63$ylh{3W`V`RbELW!Vg)rvhc=_{SpAX8y0%8Y(J-m}ZB(V{9bFtHWZ_x4}G zjc2D$&C)vn5P-UZY-mjuK;pz>aKuX&i86y4ngI?>`3z@>7fysEOs2L{5CX``*m?no|4mteXt? z>*69kjSsZec@shB#ZRjZMUvjhjq(|e9ln@5EH5f8&wFLY1=Ju%Cg!>es8`a(nab51 zK&7Hx4!2muHgt=G4ZP~*#5}4UgqE9qtQ4{$=PI%y#T7<{r$z#`@$cA~*&M`gWIk0b zbttLh#~s18Q}ex|HRDVb)bFGVO{Fn6tCsMy0{cbi;wn^)2O;Y<4hu*Z4rH%m6jKFn zAH*peJSa+-@Q7g>yayQoSX=QfDs zn=jtrkFFi?P={*TL~8~BisW0FlZ?XDZpmqYf#X+`-4b`$pmnlsrE@zmlvne|f!8!t z&zV4(WZkN8^TJTO4v%{Gz1>Ns^nM=3&5VuMZ2|1@JVT02oX4%8fPqnmKE}%jm;o_i*Pd3_%L)q>77C}KD-CyRGlBc&E>iE8J*Z^IO+B@1)p89z)=QkdW zfhL{?Rm?X;&t&smPB5%h0v}&o>;Y&p)U}_9#bNR4?q!r3HqCMQ$Fm42C#&HT#QM_6tRE^ zfv>E*FDNJ(?HO@vMl*inFyGU9$8c$D`8n;jhEOuUW0XqSS3L{rVy6n|-X5>@Ug?YjzQZ zW;*r&0gAsE+x*I34pu+>wl8V?*EX}< z?V(jW7xeLbH-)`!(iYG1_=YG%z}&0v@KKWB)Jo5NF*4q~Y?w|A5*MM^#|p-Y5-`2p zTuI<=IG%!h10(lZ6WF8dxC1Hb=0e$0^Z`~hP{Xn<;8YzHGhnTX2M(jX{TDz$l@`A- z-A^-!rjKKa^T-PpW<>+^0^^P4ofQsec}r?I*7!nko`=3UtINqt@$_6lnOhz-k;)=Q z{jte6hj(_B+(>k|ba~#9%86vHc@~m$K@XPU2m6I$*8}CZogHt$FMw~CuXob@^UQ3s zBW2|mvWsY0RS7T?z;WG8A?y?dCOVqQ6`nPsC7?Y1o4s&0xslYd9Q6@;>3`+_oTalx z`#OQXo?cjL>Puid?C;f5T6Mu0polUHXBQ_6zlxjyZz@j)0^$)M(Jfm_?Hdru>}9Y+K zh8VDy7|C3j?nm^kCQZumbZ_j;^iFl(7P*{_y+aRo7u7+8NVW{QdyRSK`lJK_k~%Zo z8k|8LLF7!(=*cGb5b+}km_@IT>a)7h!E6)|e#$6a-|AKARGi&OH@=uP&F@5YqzT#z^CnLY%Ep#Ab9s0* zy2~!&aC+|O@KV;{umi&Q;!H)Vdu61Azn?1r7W+OUf#&u+Fb!NfrAK z=R(SlyB4R^+kV6K_PF3?`Y!9OJW44?_q{?Mb~ti>?s8UfasPV>URrVb0|4uo4P-qo z0xY|C2wWN(LxrUk&OQNv!vkBMN=AU}?;+JfY%ld*p}zZ-W$Ztg48GMK0P(tKvvvLb z~FVFQc%%rIz)KEQIWZLnH3c#IXaLw*~MsjGAc&jF4sv6e;kk8K>mQM zvvTG37kRKEi?S%O>5ZcCbeJhlW-o08r|DcM>u7(UET?{U@+cIh!)U|krzR)7QI7q_A${x8e8Rz zE+LU0hBF3o(fi|;Knc^`swAf0igIUNkVP5olM#Lswj{EIk1Q&zQ+bB{dobcBWg(4i zoZCNPpfG=)eYfmYwxm=9m@p19fTrSzZ0vIIhnmWOY)8Y6yFuiZ^?$$R20EBf8r44O z=||7V6(*9oUE!V^P&!3NKezOnff}9Z`z?EX#Z7e z|JJ=A=7RpU0Y@4NT<{ecgrcUNoR#g8wxcW?V4#mRb}-Vp&ccNmdimM^cWr zFQoI|KxLC@=tr8owAt zVOcEKvtGxch>22{ZS$uhQZD17(GVA|_pGsG6blN#Dh0l*+POf?@q`i8sY}ZUL{$JF zxWj2{OQXR_fF)5tRvcP(XpIPcms#Mq1tEI?Dy~>UUo^rQ!wh47JdGqn_Xfl-OyU8UwiMPaO4F-u& zKtymU4!P5!u=g&SrHD+8)`|{|o85{a8R~45x|D>&OQvTRe=q)_o%CVCro0a83`SC_ z2=&SirC6s!!msO7Hky`1DFtzlu!zKI3pcSd6s87s+2h)3fCLmk*zUE zWZ=}cb(%sQp`hF{)0sVy+aH}t086lYw38;M9wS7Ao8eJheMxs3yXr`4VTJhMcO#!k zpru-O)$^`#6T?lp+|Zl>HLF^RM+ND}W&QA<>hB#5Z6|*F-IdsEP@5h)|N4_cJpJsP z^tgI zl@sXh{M|TmHcvE)Z6j;(t;EQSXa98%t_0-_;=7*#06^Lvm)+NHz*X$_&4zD6;Nga? z2y*c++wu6cVj&sqU_*K9_z33dk5_^Q&hjUl!Wqd-Quxf z>Tq6_!K(Qv(U3C*;wi-&@$ps9B=Dfs%x5vyOk{;3B73nu%>;BaFZKnS(bag_zQ(f} zS$<&DxeoiRl??~8hN;fb1Ie9wRwE0nV5f)9Rg;U~KReSkvQt`b8{1{<&u1(e>gZ{( zc*qNW)!`DJ)GnuqC>bbe*t6$)dmaG*C<^n%ph?2qvGC|)^l2cvyzf@OVlazzItqb^ z6;|JlhsV$WDY=ZSpJYRwR=P2zw3k7-NW&+ZE_Ko#y>$p7Y7-2Vq<$>JgDPKcOG)%} zb`n0w-ZAcwoJHZzr|HdvK6C4c!zCZpHV))fsi9SV^uxAmQ))w)($)JB>7`S0>$Y~m>t=g89n1HnKO1`pNS7^F_@{`&6Vp}(v7#Hv7yvA*>qu;6OVIhO=*s4qlw(Q$8 zqq!P?P_KhD3hg$NRjm69Wf=$bY9&A7_9r^zUS-{_wh+%X>{FR|e#OK2*6;29op0ZA zAK&8pc3Kz~0=lXac9CsyZB@;YT1Y)Ou`ICpcB-pO7o0OQ(Ce~l+QOtVFAZ-=NpY$G z@P{fTaQ>Umd*F}>eD88pojRJ95)mHJxRlu>i|}i|i^RZO%;gGTaWCIHI_M}h+B_3< zXSj1Br6EW8zEaX=!f4X-o_Mf*$j!em`mS39v`>17UIZ7xby7-zAGC?eB_A!ZPY)#c z&?jexX2u?FerzF%&?np0uZ*0xUOYah!9TVGpC=FL0Aj;;<_;^f)6X7%9{4q8=D0Hn4R!bFPs1(wEr zdRT2wNn>E(NYh=F!3tJmVLPH?;cP~w-mghd$TjLlwf53bcDrjnV*~+>JS2c@ybg)l zBDv0SLk*srLrwcXOJ^FE)~i`A3fZz_|`x&n@1PFO^rdoj=Zqtxt}b zR@g_4?HfhKr>jXj$dXcnXe2;NYl1X#p#C}lzDR_1dzjQ=h498q`|80}+A54EM>#v` zk2>Gq<%fS*INmPeq1eDRA~U^4@-e5S9%T%EVel9xh{y>{0;?fIK$UO9l7#Ev|WMlqF)1iQ)a1? z-s`{!oH8XPJG_sMD3$w8*9sEd19a`K>Mx2IhBy$v zOKl{7Eog5aaz<-9sK^4|C-t1v1X2pe>~+>Kw(&DpF>#zV^sYWDN?tO1zTud&d5VgZ z(t&t^+0^KgHSf+-*Vrhxv9)R4($QDa^2t$#hva@guu59JAV0o)k$q?EpUa9=vlia7 zXoi6R1K@$Iw^ImLNxqyttwAz=8T!JC#f8>rpKYDRRADc!2+UTAu!xcobLTS!JOeMK zQ<=e5Nz;I7H=Y5*x;Y~RCh^Buag#X^o@t#`5lr%_1Vh=@#|(+rJ!62tj=Gy>cRoO8 zeBX94V1%zI>oUSyCG=GusPz0JdW0E&F4&v&8fiVb$oorc@-O!Mh2CEq4H7)?bJ+IoT%dcs5b#?Kex;N53dnUIY9+6Z6=vf^&Y%XGrI*q2$g$UHG zMM4}x2lbLcffrK*P0g?vdL7hzBf@2~-K3@gqGu-M!ZeXR52WyTH=`z<#@S)hBh8u> z5Y^_$Lwn>#FHdfBx%NNnLEra|`l5w;o438MOOFqpeIXtU8*LhLl_m*qypF>uLrIT* zWOrW=EcfSN{N5qmhO4sw6};JhRkvQtnQ_kbC`T>zXcwuTsq zI2>_z(e-1KbkcElx|v;GMu_YykylbpAz2oF7u8V6~?d?A<|s< zTYXFMCZ$Fn8F}Tb(#oMmyU#N?)VY`d;L^C>9OZ195b+}nN2Z&8cPesfa45B7$~>pM z8lh|O+N-($Wo5;^^*f zMvPABa&+V9fq+cEDg^!(S^8#G~97g;T?hPlexTPsjPLflpWfc@-k znHMuNBPYX?x~Z5!pCL8D8M@i*`FUL}uPDqE7CQ5opqU&1fe%D3oDd%S1 z?J+|1OTB?hxe^8HH>zMJu z*mjl{7*<*4ED!xkqfbxW>{QTa#C6l@9mn73(^*&IchbM8+I><|IsTt0JWHR<3%cVj zg?_G|?qLgZPUTl1rWgFFu3=19KKs6=o6fP*+J#JGDG6MXp0285B56!Jmn*ilY-T!9 zYr>1Va{YHndV712^z+U0f1XLWqvK&2CZJREZgSjdUhZJRZ?a-FWcj#o=BKM0f|8=s zk{-2>R4801K$Kz#!CPK$$WM#vY8v2@~rYkA^YvtpJMb zr!ldjR1hbqNtslaZ;9KYyhE??TrNMQkAi=fz`M6;V!dv5!4T`27qCXj9}}S){AW@o z|7!OWCfyiZWf5wJxa*VC^uE5QvobF}-2S{Pt1q5^eHZF|u_5?gO^YT!4F>bkNF{c7 z4&%qo!nk%AQW*y#wH7~*&Um7wV4ednW^{dGl3MVP+L;67q|~r&>Jf*M)Q5w8Y&|h0 zzc$e3MG7@(+UCiC5G+W)wN@$$7ZrPV25BWE#_&jpGXOSPR_SQebB=a+izbbi&BPSk zKO3Y8X#h7)Z<9JEqSRzzNF!|JASNG=G=`FPG9_|laWBGzoNE;+U?kU&4g-2VVG4R& zSJqyOY~~-Qb86L=oZb7Lrt4cVERxUuRWo-QnJ3@Y@7MK@8}RBW8Fe2#?ltC+pQ$4K z>n&jo^U&6V(4`FyQ!&wuNsm0PIn&bipc;EUV|5RAF4PPCwVF@)F?`xO2AdtU5zK48 zztiL?z6u((PH*i^*{vTuiFD%*)YQ~sa6y;oQ!@_cFGnr>!NB4QUfvpzTv06~1L1OOaei9lwAPZspl z6!2)&4r%W$#JK2dtmD6z^TdhCEslZn%f?bNb946wA#eiyW|^!Hv-5J77n-lqyFM9O z<_$x9F~#OT;{g9^vYnQmO{U-eZ90~Ve$AMR{&K z&xY9nDyz&@@6H(?3GmDG%%Z^wZJa-)aDW?*n>P!m5n?Dq`EP#Jp$Qo^5!3qSMyUTe zam{U8ivB(OM|XbqFZ{iiyDI%#B#pvhiF$;5fQ2wMyup=ADu9$#BxFoFe(Y8 zl@3XqypTJ2Z2FbX2J)jm5EcW+&~jOx*`rWa%*@0xqb}jh@{Y8~5YM7~^Y@Cz z-`E!FIg&~-Z1vlTb64T38tX99Dj`J)J0Q^mF?iUNb#@sMmQtjspuEt03`hVfO8|({ zGnAm#V@0xXG6C$4qfq*K3#{UWU#a_G#{H( zFo=^B<7W4+K*=GTQaPzhPne@EhSv+#7??a#AQ%rvbbMu~)4@?ClNZlJk7l2-1pj6S zmTJj5MhVW{0cuz{%q3P*3k*f=-({%KlDdq?FT)*|5rI!*IK%)0OwG#(#|BJg3 zh>;{*s16+kC_%K4m2lAQM4?|6AX^4O^k(_hc>ugrZJ}Wzt?MI#)zI;_>&R}G@{$<8 z7$$0AATt@lt~Z+>VW*!;OcY0lfLmpcD^FiLveqTj3UpIysK5U7>hJBJ+uNNzMKJ*p z`7%lX>pcJgZ8IFR=_jJ<(oI-x&+MVaY&05uWQZL?Ps{GqmlJY0FbCLWR%g}ReVzaB z5e(vUb_hHc?wa~>Dphpxzmbj6#}Wp0rhH3C16yr(`c4!Y47LLS5ojMtS||{(u>945 zFU=%K*b1;@^c6ogmEpZqwbqG)qF5%$Hvgl6@&UZ3WEg}}MpRM~A|9y9Csl3bL`)Av zI{~`%nsVSnvJ1r2&34ORce>K` z>XB7nCGBZYQrdH3cmffmrbuV$eYTDpG8>VZgd!WG-Q*OTT5#t0>XYlwQTX%0?a)7l zwc#)S&h(|cDw5Skjpg-H(zy3&xW18 z_oY0Yir?6v|K1QSZHA)1WTHkFGG7()eKEPETqw ze6PxtjY>;Yg>gIOjEF6I)~LT+B8Fz+dg=w4(~n>5a_ z^Iu3u}qTd8<6R8yLKf+Mfyvq-w7pnzE zqg<-kjSpuJUXT6io4YCC{1Mh=@y+7CyHVGL%`~xuy+IAWRtHbPn0vw6lhCRR2~Bg& zzV6E+lQzvTqBVBIZi}`LtKzl$7C#=;uHyiB!f+t{#uzh#?7DfM9>CB(*u``g^~O)h z{3pQuydA61LmVy8r!Sxp1&wa5I(`0#xc&!TRdflD-NqDDe{^F+;@c?A(kI!$qVeyH z8yP~>%{iJoXwk$i+E)Q_3XURTkNvncBDy%)H|{PnC?3P((nNB^-6uM2C*Eo68eGCf z7FmG8q}gQAt|=H4{lrzbrqZ`l78t}_18#arp#D(CvcAGinLL)f;?$1sWF^9D6U`5c>?;}?ltM%= z$`R*1Jjn7uPM+e5Q2mWI`Mi?YY11}}&%=LIora>VD&wcvCk6I-iL3kbqoFM8$Y%?! zt4KzBPvmakni%C)^@0Wo9bt+X<%;#BV{^LSjT4``{Mfdd8nQI3+_1u@k)O$Zj`OYw z#%z@q7oo6{psw?58#wj($HTG7GiL_m|>m4}Bw3G1p=7u^WMGt%$#`n=*&X2RE#i_S*Kp{_e29r6T0RII;r( zWT}zT{)qz&*Bok#^0|m8R{0r&Se|kx6)<$+=V1&X=~MbXz3^gpt@o>~hYC;2Fcx!| z*ZOkOc_1N-ZWdm+P?DMr)LrN$VbAM%TeJT}fd}-E#bJ40*U9HFwYSJ!kk8UL&ou3^ z=rC5~Ot`X{^C0|IgusFP@yHUF(N-NdhhQa zy%7PjJ@R-eyJLsMdK>;BOurMy+!vPt??<`Sewb}VCzFP`hw6}@3#DU74x{ZFp}V}H zo|DqIuiHp;-x^r+z9i*+h14bM8+Xz6s7mL6w1B|7?~RW-E|$huarR0)A1vY>lF2FE zmeBwJrLgXdkG^6Fa3-?ZBw8nt=AN3?vPIItP0~x&pu~6Ta09{}RvtGMt?UO>q%tIC zHv+M#E~_YA_*jK>RCpe@P83OmVuL{QTD3~%#fgHBB9&w)am^DN0tcS8K76 z=JnzO+)T(>Sg2*|D*noh$FbGVD0A>C5ervor6NGPY&(i48p4D_dn3>3MZja$K>@57 zLZ~GRxR1Z9`X&Z5AZFE@EC1R*12Qr#VMxp-4W zK2cU-q)Rm$yUgxnU-<7yy5r=CjY-DwdH!;_O!ir!PaR~lGB^Ssg+C8mE>2){pfb?#Z4&|r|4t7=!gZB z227LG0`q{c&CDk)n*UhwI*^aF19{l@qL?^Ug_>yDxsr@=&!38QF(mjEDdxdr$fN&- z*b|0%*$UQ*cRGakJfs{Ld^9>g&-|S1k)BXB=44^}19-M1@xwh=iO>0JfsVO(z)TGn)qO2aTGct+ zVGjaduUn8O4?q)?S~h^9Y~fq~v1P=v9`> zyPbkJE9faxtyEW~88d!C79P05-jvsD#~x^<`cL~oIFp8{FKy<>>|wY7TN$^z=P7_U zmnmU7+O9%GF<|rRw+n8w_`PXJW_mXJZ2dud^ND}S=mKvV%lMj`K|^=%GmDC>I%#A9 zILkGrKV{$ZsXn)=lA1k$3@j3kr~uJ3WQ_l@i-k|DNC1>2Di_JmjL@l4Db}g2?60Dz z{Gw|VBu5?Q1_R!yNy~s5oenM9D=#cQc$uxZNH8W>2sXxso9nQ{F7P9llx3P3~rz-U(Mi60~E$sUWFz-LZA`+rwf z{M3}NQ9?eN+-3^$`~nk5(H##x-yT&wr%Jf*2SvWa&uqbR~x37l>tRK2kFh7^U^3=IpGp z>8Pv-E3|+brquFUpGZtILJUHH^OdM- zGG+yaZWS<0?8gKBo!xF9bt*B-?6}`*5SPEa?xm^lr!nS=IlM8dOd7GKc{2jkzwQEE zAGGX!;lwE_p=ZFFbM)DwPz?gPS^sQ-ItBh(5d!=@f^ADWg&Z~8@+O`%-p$q|N@TZ` zc0FL(luriS5m#GV({7F5xWMoWB*!JHNUhY%A+k9F&?^e3PP?1vN*8ri5KJ4Nn4XTS3NF5G5rAE5lug&6u~^)6ra-jh#(gMQOt48wQ*|2){*m{ zrWzef?%6~q6fZH7!>fH!HqdEUa?RK$xlH@dh^w^>jU2JV9G$B5ryeohep;1u0W1E^ z{i&f|FIjI9=S3V@N{J+`Z;4irTdXOvCw>;ddC|i74$YGa{Cs%M4~O-7qyeAI#l-T$ zp<`(iq{6f{;FO7pm?p@Q#KkJd^NCi?2fZWg8XH9ex29qmc#K6MHjE(l?6Eu9pQI;V zR^M3c6+O7!TgC6Y>x%P{fadp-Ju>CFI(jAomHHTdo2{%xWe(5wJ$!>2{rH8nclG@V z>2&V*?d8XPk~Wt~9#RGu|EMCp^iH*Ov}|uH`}3aN?C9Zhr@A8m0DOHMt(lzc!$-cj z)5^*#d2l^#-NOdy`pE2Ma=uXv`8LrB51fax6|kVIk*APvFOeM;><`+|nY`tt9kv;jZY*#Q~g|Dzf#RX}o2JKGBZ|4l1%clg? zqQ2~Gxz5f@l(bMso3T2Y@oX(q2$KZ7!#~Blv#aNk0h+6$2BJ1c zQZrK(EOAs=Spb6nWrLW2Qh*SUPTxNQg}(w)prevR=f$bUMz`rP2l83l4a5#3Sd?6| zW`avAVG?T`KGW)v^yGIr-^2h=lKz*4G9Ja!6kMem{iHDkq21h5tu(dw-y`0AePj~7 z17-WJ^6r6)go5M4SX`~-PS!d{dp}6u%9)A`9>BH4GeSp!~Ej7Zqsou^T&F3P?ThNQgW84dznBCUp48xbXh z-$KFj`x$ytKC~cO2*Q_3In$9k6~d24NOQ$zC>_ZJiE45iaLbR1Yzd)R(NH7Fazwe3vd_C7Q^v?CdghB zB0z30WPo$*ctMVft7Dd?!4}6ojI^LT-}|;%3Or+)E;y0eU1e&9Y%SWkZ3nkE)%0bW zH<5)G>j=Sq>^7_56$W}+e68Y?&ovu20ggXA$tC-fBhVd;Ga7!zMq+O>p1w}D0VfWY zHk@Ofw=XRg?WK&*4CFsK&dB=cX0Rn^W(a;SQQ)qS!V$gBS9@S2A`we1PE5kuxMlN_ z3$J@MjU4l1g`C}2FEYEQx+f-vgE8_!_p4(7RMjALi@7DIC$pn*YQ%w4cmk@_@i2I1kjHS-T88ha@7#J*-fSr~ z@q%P$fpmsvMqK~Y$`gpJ{u$VH4nbWyA7Tzg&`rAVKn6ZWs`Sx3sKaujhrt&-m9>zM zYhqrH*Z9(nkyknIM!23E|8bL~elbxJVB}wFE1AE4b^iCHOEEL|>wN(>Pyr`_4{o@L z{g#!btK16;kc<_Z+M1oCJetr_52uCb{YBc1sgT;iP%dC(X{Dr`8B9pOd4~lclvqE2 zrXh<4G#hy-R9`upmC9nFL?t=HQ$JKey?h~;USypMtFCRD_QatNeoBBM7!zL0)7MYv z@50LUW~_}Xt1CQFMP+@>{^AgJeepbzV~gh#LUmtLdrih2h>O1Ux#=vXj7+gEcF%~@ zx{6HD?OHatzUVM1Q4DzRiM&pg-YN_P|CF41FBoX0&RA*i8vDmQRWPt&EQi^CeUyPK z#lj#bqM$0AR;Kn{m$ULyxmoc*TsR)6*5080aO>536Ri=ul|#8N9RxM6RJ!z=>Q)&V zCK!=3D-qYS?Fr4EZGbf~=8NyLmuq((qAfU-d`DWeWcFw02y5vYZ0dUfrs*HQNPJ2s6<`rZqltE^ADKqG9glHM1g$Ar=Z zvWA)DGFgs?{;n%r&(})a**n-%ih5oo9nY2~37sJ8R-FIaDEtOUt%Mml1XKIZHx;D>LMD(T&21!%aliQ@IyIC9jIx63;3UTS`yj)RTZJpVP z?Jh#WJ(f$}VPrWzdVOY_`Xp*fm&4um-AT;owJM~)&Ap~%{UtWSI`E?>JBdlEa{1e! zjZ`;i56p)r97 zaMz`bk(9JTXh1xjgfO^9F$Nl!6VC9Z^1h@+wBo(LVjCRu^w&bn1S9~D9%-gYE#I|h z=5aTvY&xhbpLl{sNoDr=pv}SL>2cQTX!p(Ub0|n7YJG+i;x9S8;?rm)DVwwV-R-HA zxz3Zg4(wNkEk zKvVF{TMpfdG67C9@fv+cA%9%1glW)Te+b;<~&FL#9V5!hSO`@TGp5@*~vwGz4z1veHZ zQ^y-VOjX-jD@}j?^oqE73%=clu}mw>Ejt-DN%qjUEwtC;aR1ir`uR zjyJ3J*kN~!^ZKB_NA;0`AC3rZ9P1!p6<;S+)`B7xR!T+#Hga+>0QUIs9}C2M?&~@V zTqSh*l`nxOfC0~mS%CAEfDjN zHg}^dmW7Yaey(htrzQ$UceWpAblGVUMhNGgixr}GN@r(Zh{$-EQ1O)ettAcca@%)w zjj^FzL#3~*3_7Lc;f(mbHCDFtjB2loxfDlL;vLgEB)Ia$c6T3_I zM>>qmy)_=3v^Y?fUum(qQ?Zv+x)59^G~Qc?d~Hc_SS86p1&HnL!+me!We1||(v9nGu5I~slMhI>tY=seLl@yL$5XLDBsY#0X zJP?u{5DKRuF5f{b4M9zhLI_2w>}X{}-n|!bq{_9fwKp$gH2l4%?Em{+=u zL{OaZld3KLs;>hDh+joZej5H;MdY?t)XiwTEiJ!;Fv#!iJ=$5}P-^!3#eZ&C|61$I zh?7CsS5IwwWs|?X#ZI&N2m2v}_L0MNTBEhgNh;f+Op3D44)neDrxrhc`g$q$C5KAJ zr)BrX)&&TF(a(ryLf!gZe)lA##0$~$B6|RpzUs7A^psBd*-RD9ZFeCi50w-ET#(B1 zATZ3_GWh@xi~#seS*PTb*-!)6LFNx7=g~8c!oA(h%u&H1dHiGS3a>jDrPC8f?3*Cn zChSJam*%iX53qzTRaQxPE-U8qiHI&K1xue}bs^pA4ZX#Ir$V(^vp%bq`Q{|1b@$d* z)>PD%Bl<_KL(R@OXhc~xo}9U7F;bBy9=Kx;*w7wk?Z!BqQ*QHN!aZ&i^@)$?|wgOzTwB^qT^&x(^S`v0QSYYYIRPO5smkYqB9EGoCebb^HbHtIE!4Ew(nW;jtV z&>qhhmBO4%nI$h&73HhkX9PB z@Y2648}#uTy@YV?4HS#B{$d?|36FG)a5MSyl6KXbC{(>~@A1D< zmDfaE^rJ^HDw2_S05kx?$k$9KAWv(?R1jQ^rQOAJLW$JGG=Zr-~F+ZcG@EHEw9If zbtSfm@#lB6VaF(1H&G`;{3ZbpUGZ5F5c2}|S_SBahffP8blbozXKrAhEG8-JLMeDs zDH+qA@Tn9Bl3$3dqAo>7bqb<@wi-S2)(wRJtWvGt1v_OX1h_rXG*1va+WDZ^8ZSBO ze9CnwVg?K|?@2!mGq>b=SNblmQ2KRv`QKV9C#5xkFW+hu$mqnNypdROZF1pGG=o-Y z)AUo5U-8TrEr;P%f^9a%iDGm=GI%1%z1pKQ3#!})aFF@m3?r;joby^C!P!O}=o|)h zF;U2BWXispneR`WqG*n1OLN;L-z$o9s?OhQ(vOkxYYY)zWQ^_UKiwu+7dbG{5N}9?)*x*BplO{!ginPp7n6hW3_42iv@5w8}n-bYH2kclv5W$)n=aR{gqPEp=hUO_zV zDMKV{pp)Uugy@;&?GhdXjrn8l1&&K4+`Zv`fwJclctXkw%UZ)|q;ONFJf8UCL%pBW zZAEhp=LE=lO--^##)wBV;;zpnUNGz9sWz8)9O$G6?5D4D@q?=$gPuT%D65!!fA6pOBl*h@A?k-c(WkxCA`Oi^Mf{a8Z zyhf?q3qqQOlVE;#6a|39wZm1S3@@8h82aIOk6fV{mU+oXKcq-Axv7!KK14t1tq3*> zGuEvTGo-PW$!9moFwfb`?#!UMuCKAWXg`c%zF$UB&L)8R&w8yMQoVp@pnzz zv_IIa;`XlmJXFdWBV} zB?zWvHT4~SCY`Y1cNRBYhUg^ED$lD`+>uU7OoxlhC>_f1aD1a3UhePINKGo-GGcK| zcQ&oLR~h|#PEQVTmM?HZwGS*Rf}FYNQMoR4Olv{xFM1#9J-I)F*He9s(mU0y*(`q^ z{8cHbY$I_Q_2EMT6ZqRLT>>HIF75;X8gp26bNmH&0&m$RMLH!iBcUd)xD#*vZhM3q z%FVaVWs!7>reWdDV(7SOH z&$D2aq>(&4X9Vg}QI*R&A4Qk_^~?X`c`irF4qauePA%Cml5q zAhr+lCrlidfl*^e3F>$WsQWVUd`i3>WIiVb^C|-^DJVuMQ&zHfYCyW!Weyzo!{j#M z-|WoX_~y@)?J8sgNdYF!2hrpPf%ziyM}A(8jUtw$Uu3^(W(_fS7DNwt3*;PJR65gn zK-*wb)VtwQiJJ1o!CBzJc^4Q8T+?aBOwpb(7kmGpBVR{xx8%4aQ5c)%=M`VATP{`U zs5#;ic~9dFkNw`BL)%@#eAm^5o?ny=tnjBb>ErvsaKza;vNw-3K-z zg%i_U9vA0(?C?^F0)9!pF?ajpvRj z73<(Sg48>3@hR}7?{+#t*uCzw$D|tp=r+*Ngy;b&E=oHbN{R3uK%;qR-Vv+raB78g z8Pdh+0ixGA5~}-fN<-5?MS2dMsGsz!=qm*k|5f{A#$Vp-OjF-;nzcj#v-RCVkNIezw-m? z&#mJjc3buDe<|~hd>8F*XXTu=2{s;QbT2`Q#TR9|C%s*D#e=J|s%L6e3cg&`uLwPJ znO$mlMcy9QWt6iFkrA*ku-?K&YmVei5dTwJ;NL~O#>L-PM*-sga7%pVz$C-VXp_wCuDo1 zY_Y$H6yK%%`ouu+4e>H7Est7cbGuqdx70*h$?UJ>4wQ&+fgiZuP2)@FsK-l3rFzvL zS;wvqS6$>AqCGWP5e~ICV&XJB7~AJz60d{&I-54TwztNJp|6%OS#P8zk8>Iaf`2rAZ1S#I(rx|e7yNP!RMllw-C4OUeo8IpWwt;!im zm{+s*y>E+rh%7~t4rMTvJpyC(YiE%`{N>#r?vL>GQ%+{;sA(DsO947^gr!v0S%eof z{KjOBk6{s>3ZdL&8jn7e(UTG>fzoDqJ`fhrBp0JH9~ZI;4Vg{{u3u;TCkkIc-OA2y zr@O=Kew=UE1?hURsK0!ie)ws4uc>0;@>O?cy-AGNA1@~l^qJ+N^OeJzpR=%VVYo9> zb3Ze_!(9`Rm&Kf;-ns@sTwfL=^ed&j19=RyAB@>8RTKatXFw0PB<+|ZqFElWYVz?A z5yUY+9T?(Z5c&yz>97;s%pn9|!QZBBb!g;0e4Q_0VCIeSrx8&t3ktU@0J(CzrQKm6jr0k0}_SPragERzz^`CZ3a^om`><0J=9R&Us zyb1jw`(aazuf!f0bz)14#bhMSo6*Yfl*B_rXq7R#E@i7(kpNAAD0a{AH0v3e@RpdaW zAY9oF?MxUyx_O^HBCERlhk2X7!({9qk)sh9Sl>v_yyDhzB0Wfq++l=pmmNw(A6l-3 zjrpV`RB&%e{vOqm(Grzf*q0Utx1B^!pfE{fLLC8iSzpqh$Xdn5T9B8JzJs}}C-Z2@ znTyZsnJ!$AmSNSKu9bb8sgHMSP56aF+i(~;>F0Cr2M&9rk9rJWKC{t2zr7ufT>&C^ zxG@0mR9NOiP%bJ8vasVI5D(R{8?E#zaBiwVN2%>ZIaHWuQxI9&Daw)Ltdfb~XOB;C zl6;B%Ofrnrg4C=lZZkF(tS(*);Qk3rfHQkdlv*Qx=O><6H*;pQN_k`OlvI9=x-wcNZM-!0*p!>9*+#** z@Q2B%ysWMkkKb;T&9kq6+*v7M+YpA)Ou$%%Ihf0v`BOp8!-S&6IFAcsLE?Ej@J?2?yaQn7}}`tLg#FMa1ctt z8lF?IEIUfsFkXVv`DWnMY++kJ#(X1kFd9njSE85-x*4%yC24xJ;L+nIIr6e5OxJ96 zk)PPAg30hRrDwKeu7$r1IG7AbcIw6L@HBQw^u(#Dd+xx5E?rmV>_fK6zExS_oKuzT zN0kq1NG&=by!4fK^`-V#gQg^-vNy`{bp73iOMba1`KfjNx1R(e`7Ag>+oFHoiGtE8 zAi>m8?>soP^+v0vGsuj`Ryae7)aldkwg_tS{p0K24flM9IX`C!p23i3KWC);F5bT^ zUPMDcfPyE?PBNznx(^-P^QAnPAJBqDAkO!Z2xJstnkk?X$0ggVzwoILA4IX2jB#sa zeXtq}VVC|^r?#vdc%l=KsG%xh-(=(DX{!&lQmqc}X6L$-NOMnB6DW zF=DVeL9Zs``C}$M8v-O>24v&Rs;A@Mm>9$mz-}H?xwPXz--$$bNlQQX(TIQbR0t_g zaut*n*(7bdSmz(Cjz{G!hR`V-RUnGE+%A8C6bPatCJ6-&e(22A2=FbbNKyIbbCZN$ zIZyS(n;iW(o(VG#=zW8V$5!%dBBxIOI`KnXTWsCjkB%K-hPSsl#|i*EI+a&+3bq6^ zRbz#mk<&0toDo&r3X%&?NQtzBv|1yNCkvau-5y7h3Kq!)#7;@o~F2+i{<6agIl!K4?t4Elq zNSo}mGc;RTOjQA~W0!B|ypby~UvjM6lIQCo0r-dMw~ zqn@WC`JX;}B#SjceYm9`VaOrDus%z*oSIBLAlJi~urB zk2)m9q_Q#zo5$iC3w86G84%sIRG{H@`YlzGfH@@sJtGD%bzA{YF*|*hDorzunmHPv z(@T;0{(DMS^H;u@Ldtq|6nGFj)XXv%odnA8>FgYz{>6t!y$C;$6@m`Bru=Bep&@C5 zVL0XcIe(JGVa?ezF8mvz{6Bq_)-yba2qG6777%W+Mj!J%QB)2cWaa{Z3jshvMRdbN030!puCx?VxZLjn z(W8wAp|EDTP)2M|rVA$jsqN?U#)DW2LMGM}tF_0hftc!U)IN2$0z0T)P1@}Z8mD}q zqZ;BWDsCLKev$Oo<|+>;QS>lvF+4lOt+HMxVmf5wz*=(;>Zd7kO(3n%$rFt)S@JY1 zf+f#V%)?v25wV-o-fgjYp<&W!JzB)8Bu?o0pc=Tiek};SW+b>DJEk z;xHAH)nnhbdZ#DT8Tnv0;YV*j+^)1rWD8kfpfKunEdPal21}e)3fBUwa zm_-a6fePu1QNjhy%ou(dW&tw~8-&>)_cD7~UjcBtyKF+aN-v%55@q|d@Ncp*7cz_A zVV%Kyh<=#|2cPNi)0ICP!JoQ9)r^=mvV8|iV`J%+s7^>9TA1C)ns-X>tq=9Wlidwnz5JxFfw>(Pa=Q6y*4>_jw2coE-%;`+PWdhoXFa$~=fntAGUh%spg3F7V6 z@>bzp-SbA@ykfYqGRoIDcxTzR(UAI6YvYw?`%$3Z$c1MU! zVAJ(wKhS(|do)`~$KFkW@xHya29VzUA7^ZWVGXRQI}fztV`h9Lhl%V<)D#4j~!#K1e1(HX9IgyrYS_?I<&D=p)u}D}-l-yIqA*t0vn{=Ib12!uF!kTVe%X#?#p6>4>@dqOm^q^GJVohVc$iS%`1kU8mX1V`YYE|q zTWHM)(p<)-tr%89ZG#l}4%-L_ixfS6Ww$>}lCnN*_vVkLz`!aux1}J6FVB^Z9qKY^Q*TNAeZ#F?Hs2qkQMzzmu^`S~)4I^_uknv)KaZ6D27 z3Eg9I1pP*F;xF2DvQ-AtV3jba1z@_iiq&iSEupu1P!QjfIz1ytt=MpsoD%`%FZ z?y-4H3=LBAa9dGA`U^2@2T<){TnTeUi_*DK`5@Q1rdCi*MY~d_yB67*HJ8a2^M4JE zO23k;H&9yBy+ek)GaLG0`2z37^JLOq6>Da-9^JvX&A*gu=_)1LafdJFx99tt=e3W6 zD8Gsv^dvqC4p^~xTW(MCVLP`EK3t<-NRxfcc-r{1mbCB>>&~*F`#k?3+;rDUC5UhY?>P(5a&2D#>xt3C8CeE z7BkjMsQF36(|T=>LEKq15>_p3?dXJ5UQas^=Okj2b$xXD}HeQisD3lXSH0qOj2TIFnMoT7T13j!B$=oyED7G!r0b%#?ZRC455~` z_1XF&xXzRXp?ZD(%wd^TVRXvUJcVnht=e9DQD;P~2AN-+Z;5aNKtuz&m!^l=3!dk6 zilQk{g#pbDrF6QTN5h1s#>ZuQ}X5r0>Z;qy(%uo2m2Y6jIMj(Qt3G& zvEL#~1;`pU4T9{=qP7>qGs8#U66?glA!j2hiY>gbkM zM3ilGcXzj>h)qj(3rb2WX)6!k-;4jN=e#=CIq%Ln*L^;5hisP99z3Q?lR7!S@Z1$l zK#?e1iCIVe$Wg#G7at>nW)`D8VN*;n@^f%(x{Sg#PG|&bh{L&xEa%nU{n*2GTJsl- z3w3b>Q|4cx9I#gRhGBDz<(82njou6JWF{@6)DKQ{f)mX-0EC>%U?-=&|H-u-Cxyh8 zRR8*}rQQLy^S%xu_vFjQT$q`1JI0f`7y;@qGtmA&^x3yF^zV*0R=>#+K-Em^&X!@~ zGxhctRt>FkR9bl^vLbWPgQmxa8P6^9VQx>MN#(4;oD0sSasn660`Wf%@7xo+hrXXX z?l8{qS3F5qh+Fq1DE;Z8?R`+npSN$6E(>dQJ+^6|*yd$gAI>t7-TA0oU3)q4_3~f! z4>AC`vKh7HAE-TAMedb|8aFmg>M=(zR5bxMCMRtaJM5`2$qAzcRF+Jyqb(S_?Nf$C zxHWlLn5ko)*aR?M8*8;}G4eagKOpr=Pod_1{WZOvE44aDR8_c`kP6QD_3&mBIgc?F zwSK0F(Y}C?|K5U;WCz$C{uJi2Ji%B!3p>;;pEXa^T$JEPL04}3tqSwV?^*G zuGANWYK|#O5_apU2UJvkcyNbKMk6}&Sss`i0l7UUq&Q2>Go3~q)40&G+W(+Tq4E9BMvh?JZ~L!&0_d{E9w7Yp1S?PXZ?$MXLYe6Q1W1*fn9ascs!9J! z8psoU^O2ON0kywOunpPt>wJ*g!b+=ID>i&&>p3j3l7u^ZcFPBOo+jtYaaP?T^wmZ2 zx@l%^WrNu8+1C$=z3(1CxnZ&$vpTabM|^ye;gd3f2{KaMGl~>ERd3ErMmCw$!)XEC zJK$a^a7>w`t+q5CMh=9bQl74w*$Ok}liKB@0%{=W2A0WX3Y*T+2De~wqpX&AlfBBV zannV1C#=Gz2Z2cJf}Y%<`}hU6gmiEO_` zzad7gJiUIkK2HNVH&nzde9RmPS`65o{G_e-6HL=j(y~)u&9SJWQ&7bCMo=pG33=X4 zQ9$&!(Kr~Sqgwwdf7vQ+?F#TKw6kn$i&JEM_d;K6F~S8u}z{OeQ~y`QUHU_;seg>q9Z^9kca9`pdeUhMzg>V1lv(YSQ8SC_D-VhJrA| zz8n+Hj9uInL(-u=*v@sguAbr7?*YVQI@h=i5I3(nEEpeTDxUB|ko=nT7$_AH&tc$% zBhP0=^YjpVO45m)v&Q7>IE1V57-J{|?o+!LvL*Le&m ztKhf$^85TBO1h<3_?}1pMb+z^-AQ2ST2-u6vpO#TW%YP8Sk|gYS}4BaZV4jSotlRY z`HCB$s;ZZpRxWfu>$~FRyMLa=S6cA@>A`;mhK6yzF9H;n5l~vWlXdti#tuyplGkrK*K;$OuMc000IcDM|S_j0=M!l@L!N&3}22_xwbY|6m&T8Qn7{RQ~`ks()8%zo7TzYtVbH+blkEbT)~z`m}P- zJ22mcgJ)@q|D8=-(%NekyuJA8{G&fM?9GX*-C7vG^O@iK{eJBip>L0V>3gIB?q1Uy zAX?HbY50_gsuYh3Wgn?@IEX*fy$^yMVwAQZGOxJNr9I*&A50LNf9TXgc5sW@Pc+Y% z@9Eq~Yovw|Xcz_I$^NTGSwOFHd@@o&kS)RWoVKw1kivaa5FR~zN5smw{IS+vrHzD% zw3MMTpTH7onDIcQvB6J2{KU8Pj zCW_xr8#9Yg8ezkJ)Z>FD$$s`)C<$_M_g3s^=-+8*3f5=1x7A+#=F1)D4ZYhzN9kj) zCI@!ymUJ6#hzdHZRXa$zttcD+B-yg?!#3UunV!$pYt@H7mB|>Hh^&>9yIH!r{c7c_ zr`5AWxtoeMIT;7a*D}R9UX8wc))aQY-tpvbh}im{kMd8p?_K^a{^QrS$X%%Y)G3v= zS=&R>&6lH08l^q~m;t3F@KZ`5e=JroNVkkeMM&MKfi6le9uN2W_IL-|ougz}P=jjm zJsfnik_FIN-Y4lWJgl;usT2yuykhH{wJ0RrvoXEzp_O^EGR7wi!s)GCgR76eW8RTo zocx))vV&H+wZc?`)!S}qog_Hf@ix0X6P$z6db}uLatkg#(o@xlGX1$Xz6X|GiMw7E z&u>l(>C_Cbo!=Ch$twJ}mBo9bcg*R-_b*ct7Rv5_?&;or+@^4zcXT9Etgn{Iy6vr) z9a=i_&p;lOrcQpvppXQ}7f>=u6IEUK{|H+AS@Yk_F|&ML+AYdZ+HDM&Pq*^0*&2V%wnifqu8mc-`87n=%{6-Mxjfh2{pu)g{GQRO z!0q#-`BWL?%-f0`nV*Y(Xg{F9DsBhSViYb11!Y30aXW$b5t6#pYHRy7Hg-2=h1fQh zjX6_3Wxqz${I0ds%n z-)^?ZCTEEMsGw1z z-sR43NBWty3*ipo$bn#YqoW0vXkvBO>@p)@21E8jw{G(d(djYE9LvyuFF6lv5~g40 zXPUk<7*VI&vZ9=0NzYKwk*0w~)Z5eGn!3uel044f*O`9(s$h=+pI;w|_j)vUE@;eN z?HSJWqQ0HSq4zPoRhX4ugnF!dj3&TGkUgbRjbhq$S?%Lw)_LC&WvJxP%}uj!{Xqam zFaQofClkp?0K*-MS%QME+aJ7b4r85WoQCQEr-4nT9_b|CyR&AG+yC5$lf7a4ZF@$I zD;7;IozmI9Met`+Ud-8tOr_Ap=xP%Llw5CmfaLe2T=jWHU&h{9Zw{)lYJN#Lr9%YI z)08b2f(6qubKq(TDp!`@3Siu}RP^bRw8)$emdiJ?6q9z7#uq~v_RIpV5jY26zitg} z3K!(WkX2&qYzaS6`#9>QYFFNvAJ1*e0SY2a7o4BSS@ z6fzT;wd6J0#frAcrDKhChKjwo*Rk$pFCHF#v+r#VHqd(%S$sKU?wiB-pP51|hVEGZ z^){^na2760KAGQ7BK0GaY4{jpJm~JYRj9=I&L|hL(x|doGOESB`H~n6O*tp$i8_!0 zQF29?_v0#m71db8AHK#?+ysaL{sRT!aDiRtS;wpBn12Z`Eq$O*??&btB&^Hzp0x@& zzm|S+NUOvW@Uf)vVGvLA?A!~n=_e1b6BYt&Ps*AtOyM$_2a&O~G|R*7`>USPFuNbO#^s`raH$=SR>a5#7h=oRoI-cxC7B-|X(E=i2s1!}m;d z>J4U;6S-0EHjaPumLB}OoS1p971eUMrR}@+MDpeiPr8|MLG^w9)tJ8S0lkP>8I8C@ z|Ku-iRL>@Jrbg@zgdepwVSB=_==rB8SY33kH1#xnG64Kur<^3sCxuB6bTZ+rrX9R&TZF>6(dFxSG;ICtr?QEL$mjfjDo)P_Tx&saq+apB}B{0 zVTbV?nVY&DfjI{Q)AHm=5h8Rdx0%?TU0FLDy*Q-9yLEaE{wj-)(&bllS?+_ITWXl% zhiv&X=J}*As*;U&Y*7ZPfuAy7C&+aXBnE{F+JkNWU8UoGIj10n{PQ$eS|9aM=S}qU;RK=2YC5&eGu+z8BO_4`=?y&M`QGpGl9s;OTOM5z)c`$JzKMT4L zi_U-48CM^0JksAl04f;OZjwy3O=YdT*|xmG)v^I`lrpnplN*jQ8FKf#N_06@?KJnm zY>)hKflp~T10;-7(1nVAy2{KrV{QV)(=z|@+K-|1kH3XGO3(iD6$ z{e?SJHdn6n{ouUGE#O>YccPO!Zy!z#L?ZI^I+SS&QmjXiHL-g0h{emzXIfBOM-W7# z1TPokWFqwnQg?sr;D;WG9Vi%Ygb$|#_E0?>vB9ISV~Fk5)Y;8*gsO+8Ne}hrCedPW z+*|L7SO`?Cq82lWFXaBije5?&%$P?uQK19U`z!rW+0GmFuqHj$*t7Zh)L;-o<0C%4 z|9f;nWY5yltn`;*#z+LR_i1_G(Begil`Z9mscu84#$RD#pHFFZt7sRVHI&XAoq_c? zX_vtH(RPks$e39pXsV6n+oBz6{ylU|FXOaPx^}YgRn6G+bw_8KN;?&y;U`lc8~}g; zS+#9$$h?r@At9+%H-kXZWEOwA`GG^T>f@5a*(}tg(w0Z$6#eR@9fEl=U?h-@f*WQS z!iDlO7nm1gJ3vBkT}&F!t7hWr;eCV9Z-S%52113wQwH0h5tvK!Z`-7gK4l3Yg8p|G=0ib}#{Ut?d1;5LCm zg?vqtv&h$LjVEMI)Mco}e030_)2v{)z}M+qPh;+xw5D_txx*t?nB<<)I~7S^FA-|_ zM~&`osu8B`Jagk7h(-GFk?`E6EF$mw@3-H>vM=`yFC_lmx{Qn9seB*{RRuhNIvu-7 zPUS%*gXH?L<51L6T@P&nk5QLuBng*fI=3tdD@D0@y+-Iy7TXF-o=%UePLG^8d<79Q z-o#)Oud`o?K`mtW&dWkf!c)HC5T+e80pFt_-hc*g{kNnBcm6K|4ARf3adiV*`8FzJ z!`n+Gu2SU_yh65aiq+=OL-@6JISy}_OVkqedCPa@g5zH#wvxk@xAhmARJv9F&~YFt39Qwi*L_233P%w=Ni&P`@0HHfYkFuOw@4=o%1VC@&y;vtGp&XiMP$nsTw?9?x zBa=clw>GMBL1%@gNX13+5{$`C`%P`?>b`^1I(7c-ntDF3%Fd!YXOh}yypKxx(h9>? z8dqM!yss*(mu9j^cc#5L!*<^;`WuO-sgq>86 z(Ft6K#EfBR)@{Bdv()~|g!`<`(=}Eq zoUsQ*VpU|xAs6T&p^bj}f(84nyndGY9lUcI&Vob%2 zo;x?mD!TST&s;KF8n^6xZR0UvQvWjv4^-z4ccKbTIJkk=j*9M56^av%w{5%a{`}#x z4g`+8B1<^0LOEktjwBZK5$Zhm%^i$OKIbESvb|W^0BF?i-cv_q4}jK%Cz(0ez|gElUtDwC~q!y5GN?)3R=jQq`y$COeu=Yi8x}&6&RE zy-R&jn0&J!z^V)0+qY8u5vrXuFU(^;kszb@A}2@2(q>(#*)Df%QMcmcZLv4k_KEXb z1Ty1xsc@!B3XhD4UC)zmPA~gn&nBqo*fk^LSo#e`BNYz?6r2uGXl~lW%vlY=5ms-O zyQ}4YJ#Ga)%uyV^9wrux}- z(E0u;-t3S`c1hX6Q?ZY&*VS@ueR?N0Sj(a1Hob|1_|@;N9K*M+#WGf- zrR~9|+miQmzK8v4Om(^Z^L|D+S51vM5K%%yWtyg-p4cbWJDZR*nADwi-&p~q8tLB% zAJR;@%1BQ+G`~i{n;45%;ux-gkENc=&x@dNqp)|{HL>6#rq6oh?RqpX#5ANKZvC*u zF|AGVycfSBv(;3GaK61y8g76Non`#bz3OwhDpZ)vLVFo)djsORojc8-z= z>9=`6#cp4`+V8=1E36?brlQ7W-pt!+mE}g3>108zzy4Jd5XFOhM2r>Va)RfO-?1SMXXTSm0;K?@%DRkc9bD>R6%71d5N+eeRu~EtEjd`XpQCIUS2CQ zROyZ@ll}T`zE|qOG@1U~sj!u5Jmpgk=h+RNlvt+t&!s%7DA0sSH3uGXi2;H;JFt2L-Wq8F%)ZKrIpSxOuiW^7Li-9L!f#JQ`weg2TjAJ_qQKCp0 z#NDrDDKk8xxBxp+7S$>HA z^l=sE?}hcK7I|>Y?K#?}n9HPZ^^a`&8j`^4 zwETTcefL}&M=IrFF{n@iF-`ULO>K+7VR)7c` zlmtwN32xe$#cOX@Z|d6ZzC+cs2H#c|d$KE;N4)Vl^VV^IL_uY;!@pqx7QIoHYiUXs z#k)=DpFhQg!=<4qKWgL+ChbqWE!Segt=dJ-8G71#!$8Dz?(qg!p2KbVizUJS@kR|Z z#@m8571_God@<_0-H>^1K!J+5OAqTnT31@Il8TdkPIKUNXHi>j+gmh%tg%=|STKCB zFVYaJ>*8OqQQF%lWy3huPYp2YOLe)&i&uOl87Qj&Nps!UA#%ob`TL zEG-D{tL+Du^F|nI=Q`kCpJ;h6w^7}QTyfJiely~ox$6!s+^Nkv)vYcKEc+ei{|FiX zLZMnT((X>o!rb?y$9eb0iY&^lr|Jyn_vEtn)?FjnFE1iF2?-@#eHi`jO(XDbK;L={ zRXAs@LNVYfa8 zr@2ahdi=WMwHW~b06Xkd$r{Z?{svlU(d*#uc!LJ8gQQ++V<2*hn3)1#0*!-(aD7Lu z&CNuV51Qb}&_d20T;ZyH*f9fFwV(XLQ@CwRGX{L4^ zXhiyFY|YsXo>J5{!*vI}Li-zO6KJDPibH4~E@R)5u8`BMm-p{Ati#Rq=t29bm?OP9 zZRCfu`rB08^sJ{Pj5@vcMgM`qJ(2>Klj%Y5qvI}!_06EjQ!ekTLF0P*@M=lT&o?)O zn51$>_}ZU?f@hVlbbCQd5n2{h?PZ#ZI8?>HxK&V6s>U^wYyad*^oV^p3;=)xP*HR_ zZGApVc~1}>BrSGza}0Im3>f=%8yR7)Qvx)h)I2D+NgXtRobth2^2iq+Z( z;d9itfSR1L1En;;y~E7qXvY;>v$IAn{z!hpF99e}{?FN%g(#D&v|D%hN1D zF$vzPd0fF-obDqLeMIyeBTb`fKlQ`kThUHZBM>v@LwR%TT4eJk%5%fKXJvM~&?AsQ!oemvL-17N;N7@p^1BtoMuOsoiIZ zIi4co>;b7lZN^>_2jfk72S2K0$IzI&mF{g`Kd+s^3G02s{iZ6TPU4R@0A z6;zl9+O0&o`N3?wu?JA^r;R!W*wcjD4b6g z6>`kQVm7!=AbRv9ul6tjCoMH^WwRrJ(it=BRI{M=%x^wnPJCM|LpvZf)7gWOSFb+r z>OOd#giN+I4Q^N(xWj^e>G$E1dDdQ9ye%x+Dk~J^Oc~+qtWE$>>9MH^Wwg1ZFSe0; zt@JzHXwe5rO9Dc>xlhr61YQnNgUL?75K1=uYD>(9rm3P~^JdB_Nzvdk+csl0Ku6IA z?ib)v$l%F(Oyr*lz&1|xP_5@GyIN?SN;$4)^cCm~1168>6uj1S2&d9i%tW}*FQa)pF>JgLqKzF(JV+g{kIUi2BHY3r4v}F zT2HKppEs8j4ELJe_0O)qy_3FL8BM8H>0+r30O(P2$jtCgtZy{tFbYfM?DkcYi^b@v zpVNeocAln2ftsmtT~nmBHX2Bjp+i3fBD1bF6a;+bU^F6}p^eK znkBX1Z^cH)DEw7#R`xW0aigL#@nIpYua;Cyc)nwkA7kn zlYvk@wRe@Q*ZaHSkHLQK5$GWkPRS6{f}!%sWQ$h=(TM!|mjeI*vy)M5HahaDq_o1o zTAM{Gl#U8)%39Gg6QRz`&&YAA0aNEIDixDni9*D=0L!#W%3E!vfc8br=UYx59~Xw< zkOB_Z)*SEP=M!+Vl1}l>0frNl9f?xTK!CCjTd`-U*ggC-CkbE_Y1$c=^a$>v^z&k zw^wU+C^oFJoeF0r!P~!!B{0q3UPR|WQ*sNR8Xdl9QJq8Q5Aa5ZKl(dEMzfn?_4QOPR$M}f3@nqF-u!*+n?}D(G-r%U zIqn{==-c=}Iedu56G5CzXL(?&xedQtWF%o3nviwllp4ZDwxB9uopa)^PrZe&@mDNigch;ClL z^Rn<5)!V`jU0{GPtnnc8;I_D$re-xa#1so3NCScMb=|4m?h$zf#6QRU?iJM@7I(Bv zZ>2a-=pGxUaU~m3q7Ayv+_S63TN*W!DaJKlG{63w_HCPpN3)Sq*ELorB3N-X0v+gH zHk8)6DH!pQ!@)($W_S11aFW5g$>X}Eg(SqU;8J*Tna7WryBVLI8ng~g1VF*t;(;qu zd(O6Xht_1fEiVy@ zrkO?=@m+h(2G#XFwzwSt_CsNitUNHyenvaq+f(3Sy(T{3Y=9_lZ15w_E~SiU$w{&D znwRye-|k6fr#|50{$fU!qCm6%6NfcT-0?pdo|n}1M?_S1SCtthIUfH}(`^kAe4>PV z2Em-!^3AW1o5f)=z{IVy!&MDoT4(W;)ALJ)T_qN^!EAZVib1OVFCUZF5(asxYKjhl zZRz0%I}Q`AYX*b*%vS$_!U;j4;I2u)!_d;jLl= zNQAm{j~?6Y>t08zvg!I?94hmyD)fcx?)18EEaaMM5OSPk$0`%H!4{lLipYv?YO5Ij zm=696$e9N|`w9q3goIZ(X|#l#x|*OAib@|>W~i0AigctR0M5x|XreB=;*-TYbJ}gz zkT)LOEoQ@w7JYF)h-v*z=|R`eio-5{6nmfm0RUxwOC?#9cjG{k!9!Y%y;>QKJbOv_ zrh+a*^mQCLy(BNIFgQ;UMnPNn+PIqYdsKF)mlKt9UKGWVY?dWA%$QHJ=!=YrWssar zWvqD&9+dURfr$b~agQ5)$xQu9ObS-{&u)ltH#1xIGp{iK`l3@R`^lZZf@HbR`0#GW z>;vggaWx@x^~YJFIeCC|O=Jva;um>c&IjY*mjl{Q<8sPVv2x_ICnv%Pg!6==%6*-m=XAtYexteWBvod| zo}UV9rE!r{DPl-usU}ar4B{r4jhHd^uvy?O^|%nxkdd_p&LL-2uq@~e)Q#`RzTrk+ zth%N3G}Tm}&D4S@wSzn0z{lHO{BUbYF!+dgGhFP1XMAh4E93r-sLK8FT;WjrLP-a* z%opijTOae#QQ+3iwL5;qP)C=D+5h6Jni-Gqk`(XPxqHQ+`Mw4qj6_n!mD8W34j~<5 zi_MYEsgaPJMboEDJbII*N(fUiP7>YpFUhD;^?unhyV(sMek)jBAm2i%2JbgR%@%?3 znt?shTF;^m_WT8SG_*|Hx#XQ)Htn9XsM)KDv)y7acSfaB0XOuVPCqzkHj@-Jsg4pG zWJQrxh;oF!5#A&Su!l?HiW#t$OoVFw#NLDn#+tg!&r8hA8ajsk0c8f$5aD&86#gX5sIajFdjh0oOLFN9Oz zwx52}DvnFz=gp_vs%7JXl}GF&7m5WoHTouSUCZS=4dYHTaKB>PS4@cNv%QW zY1y!JQtF+@+ejhFom@^P)uiCQg$vH>R&G`^@n*->bNQ$KqsYzfGLukKa&C~TG!%R( z+*xau@AFFQv3V7JRZ(S;TZS~#>4Co8liTsOkM6#2edYG&W>fwR!t%vPeter%)`)09 z@cz=s_0zl?w+bA}pDwziLl=S)Ebfsfykpth&a8F0SblP+*H;hA_BBjY*zw7g-&ljr znF$CMY=%uoqt*Zb#0(I!Y22;`(vV@-b)*|L)w4QdsUa`WD4lA+D&;fZ$?1|{OQC1o zRY96Kg$S4t)G7}%c zTf0d4NNOh49xrH>$ih06(%Ry!T8`%~mlczCmf-|)DNBX}3HJ5_aTpFfiV|n(q>icO zQQ$D_Q>6l<3KbBKd1eJzVoNdW=`6euD_(}b2B2+6MSrETP!Oi8B{4liM?WB?|T36AhZO7&n4M>)*N z5&%ZfFS6h*5`aV)e1Zq~E_Y}aFEVX-JvYtcPK*oWp~OF(JwQCkR?}#(1K-1&Za@%QhOu!;X& z>kpBBU1r4F7~_bGqCQc`J0#uLSz=d%0jM6Bg7MPS7{JwF1OQBPbp1OjPb0bCh)IC_ zS16nU=w#z3zdgVM&HWTF$Grf)`wX{#jsa*En)YrfmjF2AQrwo-Wz@^{B5a_7$VSv- zhpS+nf$f`lm^BA~zno81cQ|fng02-)0jSAXwSfKC*!Cxd^m?dOOR256jb^-fX;RA9 z*g^+-b^B~(AY54NLvB2Z!)^C{_^6Ny>a~JOPcjtQJVR2|EXD9IIdoom5SgmFBvZlw zy?SX(5{s*t6n-_;~xug{ZVy8nTNeB&qsf}lDLVAB%vi|2H*-2%CVnm>Ufv@?ufs?0 z^kStV*&|OY^5%=M!%b7=;=h+~LXK{>&Oa*-JN|n&N~I7y zJzNi{yJjC1tg!ExYIHTvjQ0WMaog=r2$ zq^vxKh-qdtm?=po0ET)NNJ6KQP@XuBnhBuiv97sC?eT{f5(kzJDAE9cD6`fqdmHQS znznb-0xHRN#Ekge$R~`OjRZjCu%lopJO<@PD8O0N%pxNH*m)M}X2w8;c&<^#9JW7~ z%XwDGPjdPEPq2Yw!F&XBzpSNlc1P`No3yFB%*OUN`D9f;93zy>cCyn+8goGsJ;uA& zBy6diRQ$OEjCc;Z^@KWe?_bK7>VeD#?ibMtDmV%^Utq9xsH`-AfNcrKpoMDQQKE!G z@O-?sHGT%GozgcCr(c>PW)PB4Yxb;qe_PXMi_#1fI4;|@kaKvw(fQkcaha8QriUVp zMPdKFh5*s}CoD&fg}-xGMsj!l_hLq#f3|$<_-(U4wkAneg1e+hLA zTh?7@uaSAqy}JoAEb$iaBvS~ReDB?K3cG@~f6ZBo{`UA&PkY7|JfKva=o(2URa~&3 zxVn?QKXJM4!DlTal-FqJ-~Qe2ulcK$hxLCyUOWxEoEY4;dsb(6CQ@u8(UX1c%iTAZ zmj{=^)7Rj@M13K$PkUjPfA0a|^4YSCI3Xq|RP=-SSVVI-u*(=Dy6NqM;*@c4>Xr>Y z)1;9)Yeub^t+;IsV|9mfv_d4}Qt^6)vj2es7XbOe!z|1PMfO@p==>@kiYM~7ZsP05 zI&XvpS~_S_rcc~O%xK|eb#<^_X3|nTjZIP$ag)_~Web1w2j9m(83GV-bXBU+m`7CL)o6G|`kFT~BpmgxtSJ=1ih|7|~D z9!@oO*O2D0_Il~aW@Aia<;)v@YfSFX8x8Sy4o|Y%h3iEt-?3$k2sz80Wb`7Itw2^W zV@tMK4ps84lJBlf6}En+65OUNT7GIev5f+P&;&i9YMcB$f|01@`A533a>+eSy&dFO zMz;jsDIIfHx`mutSKJLu63rB-#HRDr*A+nZzIj5jO`TGQi)hNLvvhJ-_|votZkG9~ zaF1e1*W6ZrTc^ugy{>GQrMe?RKBXh!Om8&N=jMD`ztG@(^Pho7{=uz`j@|wBiFXC> zJ->{_2hSZ995}z-Z~E8#<d=h=z(mUP3oS13~QDLUkKW z!({0Bokp0iQ%xrkZxXw`r^)d(cwX|3gbnFO@aoLedBeb6Pkq0qvemRt8>x2>= z%u%aVf1Tb<$QM<;p)nO1PF2?Cz|E}K!S$(rj4`s~>WpRtBgWn$wX8KzcPK(b$ zH>WL_rGNYcTU*GaW#a;C^@OjMHn!-`K*F!KKD-83 z06MK%Nm-kZuV5{;Xf)5H?PYN3e!3WTyhc;){^*Gfb+En2X8F_gENkJakiTI6Xn3pH zX5O<9lo9Mp&VcbPc;Lc;%-#3tFWsw`1PY-iu<3b~w$_LMG~dm%Fp6L!3-FQS@tcisYcy1VtQ-=o;s)QUj}Y zZg`_4&Rb*E=>owm_dZx@aVZHy!qx8sy>G8yo0`pg`RH2BmqhJ|H)ZiRlz!)PBPht~ zaRjm@;x4}p8MsSRQ8t2EPe)aXWZT`fNl$ID6cM*R7{Hh~OjgALjUYFzutc7dCkgco zq-)TOjAO~*1a+cbWI$;d;pP`aZCwLm#AN8vOM{J&=es!Tqz?#l3;$+xSD>0*ozf3m z5K7-{Fv3Lrb*$=uArtmbJwvY^g+p*Gu7)P~xXph2eSNix|?c4^Jl+^6sn)#T{_ zX?wr9u47u_hOVxHX+k$gzer{@K|o8(Pj5|odr+Wp1v$TD4Yz?#?bWhy>TO2$?6Y4^ zLd(V(i+vB}2)ftBGoJ2~rs*4aw>*tLnoMIOX~(zOsVbh)rE4uk>XdExk81S;w)`ZW zMpNDh+3<)l)5T5eaoKcF7VCX~}XrW<;^fT1tNa zFC(*n$Kbb;0ZPbfmM-Gq++dAmSKhmn7J(PKX{$jpk>Qs1C5rU+_Ts?t4o#d8AC#=m zk}wI6Py_M`fZVQh_J<%qSd67;%5g8BK!8F(7BIww^g1GWCRjbbwCwJn5g`HL8&9V? z9UQlH1aZo@^09-t3gG;`y%Ta-%;J|--6J1z8zft%BoK6JX@I4QLf-?BbHRtUqdq4i7R-3DW<%h1lau{Y7w zv2%gB)yCuVaWm-w_ogk_UIu?kpEEn~6TS-^&kt1E7sB!~c{sguVyk2|M^q~9kk zGjvw`5rS~JlSf5-FRQ1P<|#bkHhgsh3|?vy6{!glcN^>*-D0LBr(lOt&q@;#Be4!? zYFJb$&^cB_3P4U3J2>rQ{)lQ#jJTLqfq-eWYh~N}_3_ksO2-`yRrfQ5A(B@Xdi>Kr z?iXo(73v5Sq#+pW>4xvqs#rX`cn~DM_VA^PRviVSx7U^H-1lctP!5W^EC$?{P)o5B z)6KoXdum_C1lUArqdjOq!sp^pt7o2)&G=^45c8kDx}Tp7U>$r$bo|CUQYJ+qu4I31 zq`44^vU>k~hG(21>;axEO0Q=&V5oRp)n8&0`VhzMo++>I^> zV({a>5es1}F{~T$PqpY*ym7m;P`Ll&$2zDcWuWF{hq`?7-8W^4dl#jx8-9RFhylaU zL3D$cJnBxJ;J_qP2n&1(PL63sP##Y+_CcXY&Gk77=5vc%jJ##@SO=gK@Y?90dN)zaAmhY0 z>Aeiks~ZRCt(rOm7r%W=Dg9V-I2BmSpIPh%0}lc)pp3-lfwUwK5TydvjWSyPIB5H` zx(G8WlvM~$iw|GhBCsYwPdYFF`EuZZUBOOqE**pMgYLRcljp&umU=>=ucAE3J7|aw$~aqE`}?pUuEx=XXJA`TMro6Y`Iro(k@)y<{K# zey9D7!<&=GoYa7RY|py00-c534wyrL5$)IanT7oIk~(wggH(F~6F*kbdrQqw)sn~S zGS%N;m;-rz8Mp{MJm>Twe6KT~9fwjg0^mFjyR8X5QDh_wW=0s+65Oev9hS~^-I+ej z<@SuY%uH0A??9i)i3r5ZjM`O?Jsv^Hu`laG>&swz(;uOigJG~|+uH51q4)J(|nuU%tuetIFNNw-x~v2pm;yPoDuV`r1;v z=g4%&MBr0R!Jk`L*>lmnN=rN^e2=Jh4xFhGubqE%xD+i|EP6_E$In9jGhcE@OK&SY zepR~jrbY`mU688Jul=dGpl#<{#%%pFgSpvbOjFa(dkY}?nkSW8rN1d+iK^;ekZRT7@gf2fl|$She?1xr*f__^RD>e61|GwbU*Lpmxg zNYHH{wYtbq?bptIK4BKA(QKU;=(=swd3C-du2mJ=#zt{A6^RGq4}7CRZ5}lN4ktEM zzAm%z9&F0DM=bs2#ml+vrAvLLYiPpg z4#1{6TscPquvJkpq4)8mWbesdHCpwIxkzY@o26h7#>%KK4a-2>1cy?JOABi+V$aNR zy7G)G`L}u01a9fSceLYBnzDLdFjq+T>i9Iu@qd*<6G0G9GVlkgFB;MJi~ zPkf`&6O>;Af5W?`R5_mTqW8OvcU7M8vnXe8(T;18M`5BDH#@J1sc;&tW7KmDSznu& zwi|pHlsfxM5UP2TRf$_=@x~phTWSA(f1+O-PSJH--Ba=I? zM5oO53cz{(&zWt7DN2((eulv|NO2eGz3v{>CRD_iWD(KCAHemrms!UuYmS9d97Q!$id`kv4i3hL!;_hMarrmC z=QSb84m7=7G8pc(0|rs zcvW#d^t4VOEw8N))jqnFO$T}{bdEHYhOcnoR)Wkhfa{pEa4*_92B1!-CXuN%^qvL1 zvV{$!V4lHh&jZFsOmVKIR%`Z5nuB}JjohIS$g#zOs~)QYiEYDY8Ot<8c@|hEhHPS( z+z`KN7KXlHERiy9tp%o?SCi3%^Gi=ZNwf4Bo(SDpwkamc;K^VqDp?<3ej`U&(}5eb zcV&dbRj6m`BB1|#B)l=>dmt)VIrdAO;PpARfoK@_2C7kHkuA&X;^mAM+6$?E+rFhh z%2;$ITkjjKiHv5bi=Sdv`~vvy;&@}4&xe~F@RS8l=kEDmTRwIdG9`#2D*Y*5aS0xA zQO)8sZm3RHAEh4I5DvQWYUjzA=-hh2ceTe>9XCvLp7n1zmr$e{q$2R>UP3!Iu z0+q!b;644<_I)tDwMP7rkmIA5*J~}KlQi9l0ulG!E@XW>@b606Ap(5KU=jf7`?4eH z195U_wgBmin)BXrSMx?-33u2;xj4r=-1k)xY3ttvJ!n|JMZ1qT#<5e2d=74)& zu&5C38J4yNp8Zrg*_tn;@#6RNQxO*fK=~&}M&MfJ727vLa>bODw~)(^)U4*DItrey zy!fwE_#VlNpiBvc-VLgL{~PYNO_i8o*GP8G>D_L4pEk&1w+sNNAk-SwX9fDsGrq%X}cY5mUD&c-!i+ET%>E{?AHZabJx?p}CZje8z3#=YFPfDvSbE_rc# z1XWs2PGba&d?xAipgoV-{>tunR%v+`Nk^1~qCGz|aGjua2Q#%KOSJAgMt-XpAL#Z< z1>Sro^#gN;McfC*JF=P?u8C@&N~mS8tD?cY@4pt)sAJhJYGL8A^=Y%XXh;eU!;;IWU-e?W{iWlxNeV9^UQ zwY%))lE;(9`!M#y^m%zIS(~8`jx#ZuRkCmz0=59jRDA%;6Y8P~E+VcEWCiCj5@bWA zf%?dko~a(zvaapD8%+^%aRh_;SY%j8B1jv`u%bWv*6k<@ML^DN1g$Kz$Y}Q&T{2Kb zU)w#*GeD+|sj&5bcrijKqSy^)WgBdMksj$|%S?E`3o5@Kqo-cesZ9)i9d@oZU_bE2 z+|k{T{5S6Jv1;vT1w^!1*30f~SI#m&J|=k1joqhK5~2N`39jJPwZ_&kaF2l?|Pq%x|;^Sjh5V>Y^%K5dph=ZrkW*!ahV*kUT#}kGy7h8`rs(WGi(jRT5 zIM{W$3+W|i%l|>Ex-@BEr7T=XGT|~lVUNO=;=XlpQoiV5zU+2`b=;(GanRylp@0<2 z7-BC4Pgn*=6t$3gLL}pvv;_sEjMo{`iHwlbFk8v8ks1TR_iw7~uJNWvO2$zi-A!vB ztjO2YL{at%6gjcD?Y#8I_qO!(57Uk}!EANSOa&9->uZTP`P$B~EP z_$Q>w4637F8QlF5vts$opVVc?T&)-u9<|i}c}DxI@6a9jCLiU|h|mB}Yx|g+lRjZ* zGuoef?i?^Uy>Ey6JpD37I~=W?(S17l+qmQQ;Xm5ZquZ4~9(J5;oF9I1P4vM+%0a^m zrT;)-FA`l8VZK5$325>5+6|V~5cOu(N_Nibr7L(8x2B}JY#e2NC~BFf8qd{R2NM7V zJs_#8^a(=GQ52O-h{%nq(Bdhy+i?|Ajb?~ZW#@TK@5w^T6hP_38z|r5 zdTaYLkjF+>f%Ht^ngMt@Srvi2W?{qW^;llHHtbdGoa*l)T48KpX=HiLt=Uh+mfyg> zV-2MuTQlK&^{2=yn~9T#o4BAs1z#HRO!^cNUG~Nk-xbS+aA}GA1bhUWw{Rc7+coL85A_4u_jP@8Mr?ja=uLRcer-E{ zL%QnfYwQ(rcjd`(^nX<^FC&M;+1S_s$OQm2GRPZG{}M1NnkFnzqU|1;$PeBp^_5i9 z_7avMKnM^kBBg=?zdXNnwTG&X6=l^YiEG^G+9xu5H45bdx~j%R8MB|GqRak}4Cel7 zMrse`j+hFj%`B*0jT1T{71R`vKAg-Z}c%RI6l5)U2bH72cc z>v8_Y{hq9)0Q2qitsdjfA=t6Rn6tmQtAb*|i6n}AoKTYfEB8lx9?@8)%%QQ3d; zQ+nu`Os=oYMo6rz23x{h+A}C(Mc4J9%P?|bmF_>iW_%W%Qd<4Rt8EGz-4bEp87fhs zDoax{MZ~60CEV)>>QIXIIvU0XW%M3>*UjiN%0A&PmG0eY?(b82t9?aB1m9}J!<}!uzH>owJdPnO3d8j7x}9?paoY*TNFQ`M zpBw1y6)~N|F?`MiL4)2go92y8bp=E)1^8bG%&>08lDuNhR$6Xw52mT#1t+taWcpu= zot=_cfn`>Ne&*}{^MEC0JJrTtS`{s9-MwSDaiQw|(~CB+10T-11gd=4gOB*#Cp%JC zu5{b`jr~o&xcawj?2~+#B+o|n+b)ytUzU`E*GKxZN+ac4&ktbh{?)M!Gu_t$uRZ_t z-ps@7KTtS|F6JaGJQcHOW4HVL;?E1~e{=w3Jp!<3 z_Kr}6LAkA4L~Dr>Sa~^kf~UWf8oTtI^4fZ_y62%hAtjbrMqCl}pg8a>2ofI?ot&>~ zBl_f^*}ivsYjS5dIGR#U#Xw$7*|O`q+VCrDSsO8bW_O_%g6_^@o^l4>VEiI45JUSW zokI5F#Ka=#qrNh1k$@FVp^Y+0;pUbTA3C@@H*OdzZ9AFnx^RbjG=I>{e( zEez6Vz`u9jQ6`FM$fLifhA}B+BF8iHI5&7!an#K~Bz7hVUeT{OA$>`?$@)_%H}jy^ za>$XP$d4Rj(}xldMHcJ_Edyt2>MR_z3oJu+{@z}&RSx`Vb$48Mgbpwhh=lPg{ph>T zFE_L0tMa+;L4n4**+_XDu9Ul0XPS~~jlIRaPI7EQifFrI4B5o)V{J@V?y|P>s>Wr% z`=;Gv*}-IIB*^k+)yXetMWF1c!0F3af9vSez-e(yjxVY=wLr+RXUu9F$tuaV($|<| zAA0{h>5^LBH(Usd)@yanGpT`4ndOUBez^;9w_MtN{&zy~`GKzb5b|S|^r+Zz_ARO1 zkO|4Ww60wCw3A<#xvTYlX9386Sx?Efg=vA8Jt)@+fj(3mH9=1~4?gr#TbxBVy24U5 zQDlsXmlX^oV?L*zX^Q&83 z`^+5kR896h=b!lrEj<#tqnqzv7%J^|)hU);YOOzKVcH`0*r@o!++2e2iILZqXNt5x ze8l7`rE8a?GJMs(Aiw*$`>P|H+p(&32h$3WYtkBZf}zv^6woU0V$3d`6RR#luZfw4kgrC}emzaeZlY zw^wjycDW!6+hSYKxmC<)RJN}s$)ObpndaknP0g`tkIELbmJ^xmZf#Dpalh^Pk<+#z z>HR$2UeR;FYvAFKkW*X38-uh7zx%w6#tRR>J-O9ry!Xm@tKRELeOJ>M+`_8pcwVB-m@nh074gq zAaqyTI4|DS-SlD%2F^obOl14Uh2s?9paxJ>+NlEog1PXhw)50_gjO-#p~jF%R21b> zKuQ0|zIlo7*LL;BeoMMKlF%q$bTVC)cT-upeA$+>w+1q?7 z`AWZA-nUe8htn>M6&LAt{9+(SGfY@)I|Zjm661Hqp`f$i*Mh(^*!gOYVv$1A9 zk@6*c#V_zV?`GIZ!|R;B<&_5$B4vN|;-z1lygHczAP}((2mpwG3LjD`jgrf~jykVM zF4>^|-BG$@;Nzr@}L7pl9EaL_I;Mw!{&6tNIVHPH?+3IEW=L|WR z^LV(3W1K&)sUe4>kS1mE_=^tIkedRmwFj@vEi~bM!5zD-Jtz;6(wXG=x{H|y*%i-S z??1LY)2|8>8i{WE4$a(1{B*Tg;Cr-mk!nMs9x%eXaqC-yPtc{4VVL{F|3kt zkW&g$&Ml`d?WxVPvjBtk@P18{`fE3q>>qMd<|A)reRT_d`2kA$Mlt9buPV2kVG=A< zu+K4qySZg=^W9co+~S^G+b8DN#v`EjrYXb1!SB*adVhKALrjfs6ZKVjo}cjZM=_Q0 z-q`s$!6|eVeWp*Rm6!>1$(e9_O~f z882&lkr8`etsrXmlLn!`YIplnI9~r)s~D<_-W#0*kt)1Jdim0W(mHk&2KE z;UVo>N=qcm^^2fB^kxx9+;x=@cMD;CAqqf<0g&~upc0CRW2g(@NE&^v4-5ttJ#`S0 z<>8;*aw*XQ4du;SEu5e0arZSXZ~wYiVm59E%i#Bs^RljmNWOst-PZ=|c$7^6ll&U{ zCMzAfy0{)TIN0CORJ?0(vshl>RbAcO>%TjP_qz{rWcp}!dll|E8cQ*5!FRi$U5WO-jMQPX!gA5m)CXPZ6A&_E121x!!qqZwSSspNS_>A61kOgz1gt9z zWMtCu;GR4)2G5tLa__RGkQ7s41=9W|wTK)L*V|=m{^2z!iL^LmI28ppC+g;xVko-Q zC|q$ijjSau)(9aZbOjk*+#nJKJw!$p9g0p084!QRWy6(R-eI!VbE-)l9fJ$=)5&mS zM5u4*uO3ID786Vu$OQm1_OQlzj=;vTnN=Eibw@#ZD3l;`&SRT_WCa93EOG6e*E!a# z$FDj^3>u=aLHzvy7;cKevPD7&oKS=$sncJg8m)=64^IFLAQlc1frK-tGH>UEC}2s9 zn3=;>0Tq5u7wv5%Cme|4Ad>02cG=?CAN8A~xKy6hc3V2B$Vmiat|R5aBG7$DE5Zff zxn>tGw`o{d*@s;{Yh<}pyh^eZ(OlMg<8MMu>4rTZ}88v zhP9D&`v2*0A)6tSXLcNiq33fmrIuxnRdd#J)^D8L-T&F}pJr>`;h)>zmw!%vx5Nhg zcmHS6`jn?BcdJ>k&;&Yzu&iUm%$f|A53 znGps9N%>!)P!VRHbMY~N@{fqwu^6A`r0h4T3Qq^*oR)2bo2?liHy*v$DDz{^x>BG# zBd#*UkNYT|A8?IhnLp;2lkwkL zBo7?@P$9glp5Z$4{R+!Co|=P;>=MJ_F6{74%lh&{pANZ4b-O-q^3ba@3wxW7ryYz3|e`C*`^Lk#`(V_h0{2zeLnOIDOG% zNZeS(I(jiFi`NUACeC1q?`Z7noyAL;@ND35zeei6GCVTozsUTZ`b$Znr`^@gM_4t7 z7}FP$_wG>23*wPyN88~jdBFfqE_LE2amy=fbA_AeHvf*EHPAGOj39_2r7qgKfdFad z3edy^MoT~sU{g*&)5W|P*JwbFK?e#XC-EXaI{|o^gkchJ7r+N&9OfG@KaUUOK2gHq zdzFieE>>Y75(p3&9X8UWZ+-J@ie^ASFv2J$SAx5YlG9pM`m&6w9hyo-M9bDd! z=!#lYr9?%>j3J}={h0u87>-_Uw^9Y<(gRKX1sg0nJ6C%~Vs%NF=$ZQt*3wnQky3ER zhso(+2}LN61e#Tzqgg0|)clTDd^ney!c3+B$eMr{UZ6vu$eK8ySv4jCpTW$)3~zW> zgc9b?GhDnr4n~oyAR=ci0IrBS9_LPZUeU~zw&}}CSC1|2UKorij)twgR6lSw_^%}P zSw1epm@>w9_S&w=zTh9R3KHY{@A)t2o7ke^l~+&C7bO_?EPb`NS_8Waz7pL75>Am1 z005CWip8T@NWvWCVWwa+wDDEv!;XiErK2EB+!%mg6qgmj_IO<$A{^%JOu>2=?M1Zh zFx~Uy`$g#A7N)pIEl$8cADrPRzM`ie?-Y+t7x}t{M0&cK3Q1$9{%}vAN`{+4+qXsM zL*SW%NDC9gimfeqkCY2)wZU593L}}=dgZQ1nx3VPOG9Sml?g|Ih5Fg1Wf4jgvC+Y? zEGk0rX2GS9@#WHxA6v~mPRqHq^>1r~QuRw%$v6et< zRD~UP1dYoYGRLWo#dW&S)#O|e!gfBaFqL#g9j%xsOV|) zxdf*K!+9qcM>)PM<@i;w>0n{PV!!846Hy;?c=~qr4rco{-@m&^IA8aN0{AtK?u*%I1jKQ&z zxR?PZ5AkXiAKv!#>Bh0Ta5ECA`bu(Ir#?m;l4HIgxh5n%BEZ>#E1vurl1WJg-$93X zA$mhE2CAg4w<`~IVAxPZOL0UWgQu+cM41J*;Fr+u_{KrBc)6eyG2PNTNFwc4VnJD; zu+hML{Wwddxx^~S<Up)}vaDP{(vbXhKcZQN1+c#E$rl4~^Gnc<7cUy+`Gh<6xVCS=6w2w+! zd6=oC4}*WUxA670N!_t;rBcPrNVe>5_@V@3h=Q=!}giqc^|wc zU7hW@3N5;JyiNR{a-1)ooREq2ywZJ`$DX@$d*sqTjkm`pKmatR?@L+@G`&A8YR4dv z6rU#GywU@GD(FnTO`{o9c<298aSe-$18`9nk6W3}fmxCG@>T;}zn}$WK_S9CL*^V5 zO9)Y_VKmqDW)K#==;&YtVxm5yz9VW2P2bIKI`t-1+!Q#Avz+^!^q|~3SWfV$5@){I zY^L+A48zT2V=aAssBSDx<*5mc*!x^};PTBygYff~mQzgxC*pHXrT&P2-ig-w3;!QJ z*ibsvZkbAU4R5CGp!)+|21U1?D$5+O^cq)Ha1Z1lF37R!l-7M*6l8HNUap} zlY+Q5kJU#rlCM125A6)dJIoPCEnYI~!?R>gWU`>UDrpj~&$E8K3DQwY8?NvklCtVn zU8q)kDCZN1x{$kgW?zBCoMJtjbH-=Sy*`SS;zO1lgtc}mmB|Gcc~;gcu3H>k>*`o@)8Bd|CUWYh zBmDSx<`2hjvp38s7)!ZLrt?nv?OZup6GJ}I8O!DyEKw%d-jR6^*Ee+s*N(?75grc> z$h_G8PWp5L(fD(WeaPJ*rSs##Hvk*}qD?iSI$dak+GwX|py(F0NbU0X8M163Bqs<- z2*$(~DY$xTiSnztR#haM??vjJ(d>!WqK2eei?lWK@Zls0sXXWM5^Pgrh!R+J1%>J? z5LB4M0hX+_Cnioh2f8~G{kSxwOKvVX00m8`M)?rhDO z>X7B1sj5^BFJCR^(R$S{r4@;suw#*lKriXp4e?kzYdM8i0#(qCww&_R-6!%)8VcX9 z0IPZDJZ`#^wn~k#4_5W2NwjBTBp=6^?RQ}VTCoK=J)i3B7v23Ovt)g4z%*>DW4)K= zJWd`x;9HG}GL6;tQI)bzITUVm`SWER!tBh2{sJ)fk(HKBr(%?oKYWfGQ7BT zk(6A5!x;uP%IJT~<=kvw8p5z~XOay-=qLB<>$x2nM{qHa1v4zOe?z!|m#lvZ*L6g5 z2>_tZ#grmNQ9Ol?kfm%F7*#Cn{k7G>W_TgC6EmXEhmi%=4=eX;Y3Q|0Vv!Z z^M1`fC@cv9#Q{S4$QvAN#=ji+VPt#f)2VHTF%uT|mp%csM5uf~S)YT$(Dyh=U*rwR zdSn^#sMIyeY_k%L%`;%i(l4SUTj#GlgNg~f_*&_qk$e-UFah76N8IG5^q1zeioPRA z+0*f+We~gkKy!GxRrH!>&z8}hoSLS}^b#gXCa1>BG|p1J)ZK|Jh8s=)bqe7C z*mUS9==I1{YUObBZQZMtNu@Fq2a>?x;;pS!fzWJgXEwM+VP}rO4@f+#9Q#SHqMtcD70Lu7|ngXf8o#Zn+JCtX!{PM zSKe=}p8P!fQ|5N=wfS2QWPew2R2mO^kja4ljG~#nsSyS2%g>*Yr(zp}+=*lw6@lt( zr3E2T)|F@%D3F2uoYER1o#`GP#fe$!gM*!mIe@yujyG8#X4%viF7W>NP1XF5cfOh> zOz)4RdMDhOQ2#M-A^MNa_onLCN(UOmzYZgPc{UFN+7?`&?Cca7)b(Zb#Pc5x7ZeY|$+2+hO7)-Qd;x$3$1A<-B9T;R4z5ON@|MU+G@B6ze_ z=)1dMH_QpeMF@cm7T)O{4|F3P>YEB>qB9OE)0xCr0QIg%Xz^4!e z4n3|09OzL;=qI<52{A>M7G9T$sQPieMU8H+zb#ce!HW*VzxE{2gwOa9Ux|@DEj#sB zLQVmMo~O3RqWxW~#7lEQOKnN;a-aG$)tIODztg6~pOnS7O*4}-*DrGC?6-CXTXA-O zcxrV1S+uriZ&vhTnR#Ga9AhyCFvuCd4It=#XLt7&c4sHMd&doGssZuvNf(3+kY+iF zih{9TkXn4(GmY^80JZS%9}92<+b&`#D>n_v(=(l)uczY3ftiAzFTsRI{j+b~${R~< z^BdL*K3O@DD(Pb*f0^vZ>I$KEFf`UOT?9{+=Iq^c0sH%p&)w(w^5nwhODCYklAoeKy;@a6*QBwtP@xK@Jm6jKaF7)-KejDeRZIZK4=My{c5 zoKI`-(G6GSw}bnXqNwK1eLbyA09=-+3dbiH5b6E{eVVW03qaUaH{M0EoVf*c$%`h_ z_2H)G!Ky8Q7`rI-a#5yttcE^P>HIbe=Z`ptpRlTXmB8$XT?%a`BF-$4c2Py=heYg9 zk$*XyK0*1&JuLSL0-`mNK417Lnt=H_VBsEP`!oG7I46CPdACX6{x#N~U^!3iQf7?n z8Ndi%6uS-=t+j{Ln(x8ZbLXAu_vqH9g)=S<{2RZg*8gzwooS{_QFNyqU;U2oYFzIb ztGG(PqAHY;apg9l-_9OkEfHW}_qzU9xNBWq4FD|Sr5wL@B38p8GFF%{icaTKw*C(k zGU8y#o?||*r%UdEKaK~RsYw=>%bML_8R5!vD_+$MK$%A6-)c)5IeJ!{rrrEOM(N~g zwS-GhP4b%F`r_^Q=%%mdub9d=x}N+D0RmbHRse9-yAT!&rJjQR3JgMFl3WauVq{Wl z1J$E7$ln+>37pJZd6{Tu7tDk>&l%s;-%#_-waS_uxz9RT*jQ1I){}X43`)oJ=xZ6vl7+Vd!5C4~WbU&j{(Siy<7$_%`pH%vAOGE zO&I{fz{_C|{cLEgu<&Kj2Dz<|tsU#;3+gHOwfltuluw8sJLceK);@|V5D_F8JgR8! zN+ST$fR^!0cB*%m=twMO%uQlwebCNu%d|p=F`*uLW=+NCNduu*LEs+Gn~`@)&cn!X zK~B8sbe4Ie1(a*2D^@LLANEvH^wtpH!|n50I%+#`>skh%^2VT*mQRvbup?B2`i_UN zizbJ9r?8Hv%ET)9%!T%S{48X-*WcLSs>mgfy5Dh%QiK*B+Wfb<*4D8bHnURj{OYmc z9z7)l-Yxy&^X;>n;;tI|hs#&&o=d+S>aGG{`N7VWa$l3=S}Ahh%5hj`eE$RaRRA7! zM@|!p14D7GR)PtvzS;ozl6qVQFH15S6JQn#Q;22Eky z%DO<9{3Xoz9h;{>=VZkArYjJscO`EdCp6IVkyqS5Ao*^n?7pbAyw%}0S^3od;N+|6 z=kE*{GT0eLuF!e8?0R@kl%%spl3dp$E=`;fqp+Bvi!m8i>o-tCiogewnC19tTaak;A z;$b!hI|6pQ9P3ifxnv2gOC4VVOR0p7c4PFN9`Uj~i7k#xo>~dMA~Nb{C&3rp??7b6 zTQ}h2J(AH62J;0kUy3ghbb)xe)amQu&f4(eTRtdoGHqwoMBuH`V2yj4vN=|Kd1>{2 z^0^-eZ6|VnJKK}5SVSo6{CI7Bearp6&05_aviDEM<+=cca(o+y`@)D$N&~H69MucD6~TI zp~3nsE4_y?L+E?KI{p$LABo8Z8;soGtMFTu{;bWYhy%K5RY9nfc7Fff8zXHhki-x69DriDcr;_T}3OLab#)}6on zSA8WRn*O$SXnyoC)#9sS^H-nmF>9lLc6fn0xiL@dqP%KS!kJ!4)Ycxo7`&5ka?sFx zFndb;*pqX0XqDV)u_}(a>`_gz=(~KSx|)5KLb>`Z(A26>)V;{!iYWI1Mc&jY*T%5y zo|sGJkN*88>Bz;545q=OU~m~*MM!I3PbotLfxyKo$)@&5PLt3hI8y|W5C!B@>kv4a zlsSMXEEmx@#Zbx=ZP9cux@TKQG$KjY^FnBLg{g|JB0|5sWPW_tQpDFrXzeQsD$0#N zo4H7ut|4BhbeX*FET1&<5o9PE&F@HI;%B9kmM)p!y&hx)B$E+}rCoK+Suls|6Ju#! z&;ze~R!18TrWuZWCCK~=p};k*r-#x2HFn7mpYOb&gfj>&pdPCcYFV?nviz~)0;QzW zMErt|=e^q>R^#%QC4hd@5x2T8@7f!`a_aFNA5TXFMn|+<=COYBbyroNEv;PbRmN1q z%P=CgLCOA~Mtv_sl{MSq2JdRUafkfkgP(i4g)Dacnj|U*PR$&@DJO>%#SPV}^3*G> zCd(C!;~MQifC^BmC@9jopAf)=2cl4h=rL;)n?NL6#NZ{j2!UZfzR!E8HnBjAf|nOY zWf;>`yw_Z#$zU9rUnx{6;md?e{jB@!0zVU2pYc5PW(N(!=Er~Q{OQ@nY$e!Ob*qgN zt^XLxQ2ghs$*UB%ua{0BHn)ra*uLqu^Emm}FaAx0bU-PM6XGI45@chn z8pLp*^nv_rz7nt9=0-r49nQlPpz;?A8OnIR6T_9j7Z>r)NQYvYn~d@DN*G_HacDLW z$JYm}^dq%$$qGNGyZP1KKvj~-oAh-SJJ={c8ygSSI_%F}q-NjwXy5W6_w_P=hp}AI ztlj>u%^Ug0ePr@{d?W|o&9|?vI|W5sC#69x%JqklPolNiOTOKFy#SMkV`R+4OWnOj zl{tLtz2z(bX^WzU!?o?4$19~uclU2!Iw>nG4i`z4>V({@vj27oJaIC=(!$@6GyVgG7i`cO zkKx4EGa*$kiD5Lhx^a3P|$l_|93-aPWM68E!riu>yXfLR)?>pp~!Iq8+f0&oFG&0`3 zS^^&l8w__ntan_Rl35O||0>yXeT`WWY-kyrOVz6a! zoZL&kNFc@vCzY!_bw-XLhpXb0gFDL6$1XK#%B@KV!{K+4nwsJn!m1oIpSl651es;t zDK1b1fL#6e+O`HVIvocFoCWJd_OVQnIRWK2ggi_^(u_e<@o0S|eJ^Fe3vmua1y$Vq z+?PA5lg4-#p~rUOT{Z0@q^4*u9;Vl6p5SMqS6ZO}fWGmKM@(fI z+L_we-&?jc7xv~~j!Xntj$ssKV&GJsdD&u_gxk7s)cJi`USifD_IlksgKMg_bB8ug z`GEE9jY@pY-vRN%lX;bmpDVTP4K=qzUc^fMxZ56bZNK#7Hvs_Q2LdQn;1(!jse(|T`nHgc60^^d#ksk z5D&j~$?*U)!g6snj@~4OB>oBvy*?bO=5_ZR)sW~?nfR?$sYbYGGO$r!hxo$70V9$a ztQx0KwB(ozAEJffr7FHX2J0CRRNP~XLbWBW$`t0#pB%nD`Db`)X>dBw6j3ALxwj!} zze!l+Hg~B%>6lG7kvZM<(0KbQLmbH$DGH-wNY=ap3@&co!=yBFE~w)sam!E6oIyZd z)CwQOfj}xnysPcss>4egguEPGt+wLxa6uGToOx((;|3LIztP``XXU*q@Z|6qi%6~I zvj$>?C$?>?`XdOpY1?kY61j8`W(JqRs1nZWIWQ9+IM99^9H>^Rp?X+E?sSvW6SM@C zu(t_3H<_;By^{O(G-q&-Z|os^S;1Px0ngkUG!K z+ID4#u!QxO+}U6?{rfx2p?ab8jsVJCGt-zRRFY5U(!{-^O(XGHDQ!l|b` z8Bk0EyP@13&~~mRr*5+)RL;)61c~V$F}Wk5jPp3(Q_~3+yrwQQ4bYo2nYkHn7_^4b zpC?PI{GCuE#gqj-x;;uP8WYr4stf-FgEHGT+{)DXL|DdxPgrD z35-A0eEXb4*Q!2WI?POhvFC?QoG1QBOgO&3+IObz(xzF6&B^7Hzoh>_VJ0E&zN^vh z%ALc^3RR!mW>>L)ADuJ1E#3z$66XF?{97)WqXsP+Z4ie#M&IM_Ne;DrR8=Wg)q0oj zFQ0fu4+0el>`2Eh#cGE|xY((f2rwDDNt)#=Q?0;5T2^v^&j1zY!vH4E=u-^_E3+}P zU{VQidb(j=gmKJRForCWEXeSZf`b?Wt%wn#h9{gr0TMlgRSSYZpksf{)hu^fx7(&+ zt}cOet%`?g&oG%g^G&d7ApCUB%QCjD5dY1yf`t9jSv`ttuZq5|$F^-|KDESe`Juupsu!Rb*<7%?-RQ9%i6H z8}*4B>Hhh?5#{3gK~^5a7N%XHPj4CnAP4}1s1=w)OAd^TDCU(jgK{w!O)BbhMgm-n zDO%fanP;UMQ5ifTbP@k#m zFtaYa=3F3qErY%#Q{xd&*&Msu@{+h(w`ltOj^?1nZ-=vWHBk~5IOT$ds>s6?Tf^jG zjr>-g-A@@im} zR>7BN!#wRVf}0(~yDQl}6^a-JG60|&vv`DlrEQV%;RF?!P#x)+dzLj_eXG;YY%`=m`2AmocC(u8*i>3?iTcisns?rATi(@~nE>>CvKiV?A|( zMkZ2+oPkjw@(AKE*D-($Z9Ycl&-)7QFyXlCy|N)B>k|0o!;gm59Gv?8oIqwCoYydG zC%7TbL+|;8$vd!repxFd+k>4=?NG=y^EkWpClc4&K6n|bcq!hvM@<&pjC)b?UR&C` z>ZG9q|FF~HoyNZE{)x=Utv`&q*1GtI{~Xk ziy!ZL-WNK`7m@Wl=yMhk68Kd7>!TE^3u2H?@}}vgGStNKw7$;tG^D#82om_oh`IOt8;`%Z_<-{r8lW=Hq`Kyj) zKrWYaFW7{p|J|^+ny1F<-1NDf4`T;jT+!@Hr&(AFubz9i4Sh8S7{mLF$KG%yNmBZ8 zG`J|E^?Qar>grpC*}8rz2TNw3xvVi_I;4lrJ*b8CWQD^4L-b9Qaf24N2gzvcRPc6g z%$)o=jE#gsibm)nGOsri6a_dTpGDH>Tv#y>bcLe;9ZRG5Fc^7%3Cvr3$C4}I~n z#Za99?y_-MbYw)p5+t?7lMa8J?LK6FvFL4=P646-^)M$=SA7Pa>xa~f9(qiB_B`Io188i*4o>>Kx!Fl0^p~` z&tSzv%Ei^`$fMT<@zrA50D+4*Kv18R{PoTMZwg;)$M41N ziwT)mvl$QY=eDT%Eqs%1c+or>IGCO$`BrV>^{H*>HTWkJAItD1x7xb22AogWY%dZ3 zYT*PJVn@fA@%g)Y3w;5l6bjqU>__HNORKjrT5%@J;h|zx3KopEs4Bb9n?)6gvD=6tn|5(56j1i1FRR!raq?omTVcAM>{RcccBqQW znV&x;hH&ddHp#}%Ax5h*E6IbK*yqnu;zM#jbKVg3c|z-YKQI*UIz63zXN?PXs3pGx zl9x<1lh3oxfCRo%>28-FU#`*!F>J9~aK?p-frO(u6-54P6#gYZ*F1)-PAP>Po#B+* zj0xg@AMrE<%YvlNUiaJgBC%YD($m=ZO2KO!=U~C|snWO?Ok7fwq@ZxQ(5Y1=kP>I^ z@+t#_>NlzCi5kMZYuqe?oyDtHHJ2|+#PkbTK+6VtM2b+EOw}p>6_wG zcQlne(6$Rsjj{6__LW+Rq~W3t$6p_Mj(#@Qv@ZAF=T96ZVQ;bDJSqJ5&!&0qI+*;Q z)fb}Lhqj?Ap1#O~!hna0)^bC7)ri{f@3nV%*qIjOeT>y$g4mxi* z|4Uoj&?GaGn|d{PJ+TTkdbS}hi0QaAT_+GD+4IU-4uqV!F^*Bj)2#y)z-F1Zf2DqV zT&JO7*9Fru><>jFf*=t=wf#ha>Z=pSVS}Pj<&`$eJV%BN|2YTnle5pBz3Tdt^Vjsv z$#IzD&!li8|D_IjpHm;wpYa*hHMugS&-TmfvvYmY#VwS4=br$3h_4O$8g6J@*l$Kndg6RqpaHQuBF>GrRq!{)J?zL|u62l;1 zZ34?q$DkM(d0)1THCl!8fYeEi*sOp>F?$>_Wmm zn*p@J_8t#^2(LD=&}Nh0`>7r{|Gc%ZZTo2{d>at)NQd3b(}?TZgmu{G_-7NQ9D@#d z*%HdF=|iIF?){MUhDuHW_NP2K{CS4e>b5d}=plaRBBpMwn}6N;rx4TlTjy=;p-CaP z-(=_1@tnNus6#|cKP7WioJ-foR!?&G>iSa9j!7M?YC{4T2%2;hzD zMV1oE!sS?`wRr;N<%Z6#nGezUXqqv03^Z+uePn<-iW{g&CS61sL_$a`(Rvik{uWT>OZh~HfjpieA}zrh!9 zDM41}XaWF^*AhEXB{2k?!=?3`FZ;|bB|CZmrFpxjER^JWF;myIl}qWO*C&PH<&z2& zIG{!i3YqvQlsi2}%a)L(sx`SVaRG95&iNlgV^dRVu0Kq#}TpD-bN# zI5p;GbqUfVb{zxKOLQR!9?NL!__Z7ju2cQ0{+Bon=(hZ`{Tg zFh-9aA&zd?=n_Xa(j6NeO1G#R-Q6W2NH7QQ&P5K0#-r!r!nFz5Xvw0W04&ad5jX-yV5?}A zFg{e_{geeAyE12kp8(a}2+R1slTH#h8e<3_ZJx?Ur=}IOvA40q=3I2lhzcV)Lo?6_+owk;mXw3>3ELU~5>N&IKymHu$&dW=^MB87Zr<%P{};TxsSc%( zNskTnWMim|*DQ$>H&`i%*HgcL`{ZwlXxwmkmW=M3v>!nQ>b^#h2@Dxa&TaA?gl+=% zA;17M8is&_X)AGc%4GFAN5Wz_*jgNoNM=N(S~x|7nln^87e4FLC)2qJ0Rzf3vjAvz zB-}#>;L3}Sr)4IL(P2PC!3cA*aP+3qphiM`-*{pP6$q^YOvK!hHxp~LXh4aIqanf! z=!ZmaB-M!pHDmQH2KrfA924afzxWEPWv_(LTTn`fYc%*QNQbcGB9xn*9S8Uk+&l?0 z0Ul2dQT!^SNIeH(=?P998Fq>Bk%PcXH)O}| z1h5Iw<|_V`%`z~Om?QC?-qePcI$M4jC&Bw2)NQf4bu-5j@a#ph*g9Rz29dAzu1&!E zgbX6|8Ex^O<|m{SM#6z+5AU$Iqj%;;YSm6Am?krr!VY<=`0{oQ`AiyKdB_+%(BKW> z9WmniQho3KhEwB2?uds%)3I~lo0fnZb*n{Q!W9 z$r^%_DFFME&+5sT>wzla{j8x{UK6EH%kP5%eT~fQ_*$Pob`d-=vRv#YeU|h0(Z|tm zH>1}#H#sujoBusE&FwWPMZv(j?dD?!`K7>IGEV6)@02BU+QlcP$dPX*LAnr@CBT>w z;9Xst3WR`3+4m}txP)fblZuO_Pb`z3Sfxw3-!n&FKT}`PA^&0g=(zVvo4xnU&{k_b zn0-w^s8!+A=SzOs=MRgYX(PYwx9GfWUwuD^vY|gTo^0VQYp-5T{GM)Dqf5sk0I%jT zdauczrm#x>;#KpafujLRyWXUs_0*mD`~90;03b7q9|Unw?A%Xu)?&ydm~!MC&m&f% zsyd(T8zp!KD>k-<(y`u`!~zf+1e{zRwZCxvWoI~&Ng^Og1Y2q|a;E4^gZoV!|4X5| zEA+#twJgcT3yq))Z?o={fGQ3TDSWAjwSu5CBWKVDzG}HI8W)32I_~!PcM9A~70>t8n&1*oy?S^Wez1DCxVkRE$&h&+%V@_UlXU(xrYV zvZU$Sa1&b5GH|qvGRq$ZG1gmkXf?Q48Ll|f@YNX1$lUw1xL`4tR@M1)Ys>-4)#_J?bFiq4_G^kw>g zEk&wJnJM*EU#ZL!DzCMa`)D>fLD>t(K}KF4x5HRFqiT)5Iyq(+1*T=+qePXNY-j#_ z3705dyo@K`4O3&-a92>cxcNaJzO}_j@PXxK@(KCfR{e(oZG?Jbh`Y0+7Z1LwgeH3k z{h+e+@w%*m5g78~$yQ&;tG7@_l1z;V$MjHg{H{}@L3(%6c$sJb;&}KFLTfus zFI>SZ$Ehq$RV3Gt2xe1v+CNdkdWp?{94pQFeh+g%(k7iay9@pII&kaBsy_#k)Qum1 z`1$Dmqo}A-Ba2#wrtwl#%{xDvlAONzmuhM^BOXJ7*0@82rKN|yo4KjL$k_zjx=kfk zw>>Y==p@^Qe7s$5ggS|)C$mc2Fx=rfe7c9|Xuk%R_fW&efed(ovS(V)&q%n-mL`_{o8RKq6uM^Zk6|?R`ec9fBX2=^z`qx(|?gx-bl`y`$zrOwXe*OCzsj;txu^fG0qZZ~)6GpH_OJFEz>ec80?!B%GnW^Qhnz^GslGgdW7-9XL zTqd;wdx-`^tqi62L&Gy1wA@+IOhY!qVTE$5^oEK18ZE6gbKaRLqJyk=*4KBWSgt*z zsGryZS>NoNXQa^mo^&J_+*dU=^3k$%`a>VA&Z}tasgEh`^soy6(zi0w?}%?NI*Fi@ zvN_`7S<%vgPt}yRC(4HJ6|C$<34ar8!b$9Ev-xkICKzGZ8YTEg&*FP4FpFh*j63nVwr4vBg3!V$88hv6s>^A;pzUC^# z!%2Q$FiTOwFf1lR^1D;g6An|nwV-)eK6>?XOHF6}bZ?ycr)oe`w87q((O(GfQD9{D z(&P!%5BZGqo6z-}-kaW8D9NoK2}Q^{Li4$4F>f*PgwaH}A%{QAl`M+ISSB!p0OJaI zP!7|NMo@rnZzD)nP<_QN+hM+^Hom0~+9C-8R?S7^wzCJG-6=XRlD1N2ECxew!mQzdd5j)$`GCGPRPiV} z2lVh61HGgJpHApY;~4y(#QCO~uBalGmTL>sR|{19Bi~y@{j?aj+nGEJ>`m`pnPz@b z<##HZ^fFq1I9F)HN*w|3nEE8JANj5BpFDArQ#n;&dTd~SR^Hg9JnSWbwnQ>B?4h0Hh;Uq`@DEM2m^F1d7t8qZZ@tS6CxWO+}?grKcwD1qg{Q z9;3{5#ifIhsb4edP&m+dbT|;*A&X|A5r3b4ZLk6+Bi7$GVN*nHq5pR5WrrK5@xhp- zv;#y{@HX()0G421fH2vs9#rQ5R6b>7IUX{Q_LC{OjhqFnk5}wpag`;rbhS=qg%jZvw}Y{+GM72{^EI znekW)J$tKNO%+5M%D(knsxe3r+|^hPJV9NB8Flu4*^;2?j3Xe>6sJXV);Y*8jDWY% z*i>8lP7R;nWw&Y#r_FqpbS8K}t~)V2*#?j@2zKQxzYAnX+H22#(1MQR6Q<&p+^sQ$ z?77x)Rsj|bJL963{0y>)YG&sjVDlLk(LZ8OYaJCbhC8COY{^oMMv5890?9)6L_!^F zDJZ`@VZ!J&;lwvGcz&4UUUfA%q>=|IHcm859xLiJKU?yo{AZjn9dQtRjuxp-O+x}> z7P*G8=I#Kipo}V@J!iH~=d`gW|Q>!D<>V)F6uMp+JWzl1b zQJURgolK#M@~JOV7J9XEONbZ;?RMf8*UP6ztPRWB*wwI4&f~0#gyy313y$MoLD?8> zpFZc&zce&N{}3V;5xIp9)P@HS^Xx-lC$=IKhmW9(st>u7B8|=U3n;Mz+rE{0#TOZ` zF%MWsPA(QuH~l9m%T9L!eNVnWxz-tfG8FL0;)1E5X zEjA9sNKYVhrlh9EnfH^Ej+`-R;^8K*^sXz0x+NE*(5RlmFPPYX$Hyt&@|Z}L$;en~ z730X53R>yBGe7t-nE30;YFgt)a;PoSqOd`Qi(g?N$+O&v59{W1Z?hD329@&Ujce%V z9Ef=*>GR-spNO)RNYg~t5EN!L?img)F*!Leu)9%Ym~Fg@oUsl+d*d)1w)#dfT`Hu% z+P1vbrZlPNLDP%U(dLoWq zf!u!j)!j^6>)nB%o;xs*e}Cz@>654_WY-a|yYID^$t6>7MRmvTxwo&27%c~9?x#V0 zb*{#^v&%#G<-gJYd!7-$cUs9bq87t-{)Nx(L95N#=P_Q93l7{e(yoK?h04 zd*aX8l*#AtVupkcNQQ7!6k#iKwu_hs=>;9ledOgB(si()lPOtifs}(xK#VZc)JXG1 zHJ~6xW{lC?_iU5c_%2)7ZZR2*OH7~BJFZ_D&yL)cX0T`v$<~oCsk$M1MH>@2DH^6s zN|E>--3cp|a(zgYF2rt>fQnR@Xt_LoSYMnmWL7&PFICUhOkwLj;#<`J2mIeo;Vibg z*LBipqu=|a)^+%?m#zJDxmaTm8|ZVjWBo?`0aNnh&HNt zlEJ{Z1Mwsh2oLaO0I)JBaUahZEr{nPEMpt0#;I51`dk~a=j~h~Y)~1vYY1OzbpK4l z&^*!-(N^d7>wM)&fWLI-Ri*cFnT&jQ&hGJ2_Fsf~OI{oNi&1g7T~#`J7lk8VSIsE) ziW>Od@yoxW(AVCYps_4v<={UmN}>w){KUV8F+c==Fz{!KsAN7kp!@QqWNa69-Ag(2 zG~~OI4cUdfJ9WI}R8)xk`5JF}Yq!?AM~#vvPp5Ou{raL+cbbO8yTY$!gilnq9SUZ0 zeDgf&Mh>~|bOYp&J58JK#ygco8o^`C-Fj}*z(C_PH>;wFZ@*-vcg|a#KdD##VUT0? zwjgiloj4D-~<_@H& zF=T8zvJxRS6C;+lCPCN^f<-@)j)unU-=i+!`Xi`ZC1(Qy?t=l!D$M63C|s!n?$6uU zHEV_^$sOmTZw1F-s?H(^q4PamEjLmn)ym*hr?-&Ic7vr*X*TRKHFK7Bba?@;IA}#$ z^|C!J%|ISVEU?)Tj*mU%-tB`!y?LucgpXDt$`xb#O;j>HOR>3@xh=ziF7_sbt>-}f z;lMu(#2v`pIwUbWd?XrdHl2zHRjd4~o-=RvdQ8I}fhGp9LKNZ;W`CE~bSe4vS_=Hl zk@g0^jdCkWmdmMx3sC4t6?abx z+Zq`{mNgmUEuO3=+S*Cb&p7jS5?Y!Nzx$cpU|JtW2!N1pijuQB-0|i{7IKK)1%E{4 zQMD5E;gJdC98!cZBolcny!2Y^0f>fR^B))@s|Y8KxDLlK^0u<-Ez;xy2@XCyzsBG< z8(B2^hYdRm0IcO^6v5-BvAdHbIr16FRLc)FVjKLEDotYRsuC6eRlicD1~Y$WxZ{h1 zMfh_x3}(D|)eN+PvqwUs-#~@<1BXZNGn+Ir4oW3 ze(E|g_+bq5O)z%vF=Z?#Hb+?R3xE7cH5bBNuP1FEYtT~Xym4R{{r@2G>rm_4?-UPldN^MGr9iv{q48j@81ijZyJ9r0&_j1 z7!5@7VwmqTZZfs(G7iFWhhD;WMe`;7a;aJ1nPE^>j!ffQD&X3(t@EAI^=#Lf7y5{_ zD$olgMEJIt!qq`et~9%kiI3l;ndMy~|ES*9m|^2!K>c@s7M0yf-DE5u>}Fx?l00~N zHps8|po}C-_&QH z7yn#)+k+xz@_N&ISk>Uw-*iGXe^zl*^VDpO!@+m~(egn}gv>8fsTuwHGwLTpHuV3T zk_{I6nk?nTWeDYoGGARBI=`WuKG!6Z*Ly-CSxt95=&J$s^rR~?k97L<>aF!F3*kRj zt-Rq3Vj0dx1g{!sdVYROp7cOJC=B^f*)zf61jMNcvZ#&7sWcLAcUi>Mg}l&FjM!wv z_4T(qtR~yfMUc!gy^}djT#2~dD^;$b;)VU$nDE-W{o!II7UrBJjP$W&Q0cU2fJzn* z87ykWR>cVfQjXu(AbdxOO2fet8oL|GtH zmn!C6-)*;n$v%cEeU^w5N5h~`{#{m;g>{;J)<|RQwKV9%$R;TqB90j(Oo6u~kfrnu zX(aw+8+%6+_yUj$bl{Id0tZ22rl9C(ZZ3BKxmqa_Ip(`1=})6Iu*cdwdpI;S`75^) zm8L=f#Oai33yZn|2bSWT69FbB#y~Pw^>jvtdeKC0nCp-9u{HN|Y%M#!u%B>I>$N4Z z<=&weVQlZiLmbzaGzD9yU0jL2iJ2)6U*!GcRpJ_C8+FpAJpYd zqV|1WpmqRyINPvQrDYk#spX&Ndcbg6U+3OSS?0v>p-}q;Doz)SsWkMn(_PK==7nTS zJ)d3h5Yp-V-T=`>-=>QJ&;bI*M9wjIKLHcDK-|=fpplLMzR;ML%vkBqNUnhnB~GWW zFO$q@Ah>UeGLaAmhmjoAU^wgut(ww4fA9z*uX@ryL;Cb0sO6&=t z+Ca$bGURNN?6gzKoGB@z3H3MS#(BB6LwX?2Ey1iV^wjP2-{j#Q?V6pJu|(wY+JB<3 zk7K-aoM_#cQ(7Tim3i#uiNIz^=^cKFpsuYzos@R;!D*qf{R^USDXmN!Hgu4ogvPCz zwwRt`@TA9u?28FJpqff>kH2uq_d%7@i56>b{bM=G!!KMnm%oO7-u&f!BXx6B@cX9X z>6!QtkN{*O;h`kHL5PGsmP){jV{XKubfV(HNSvCebuJOap620D2`&Go{OX%lPK!Pg zrDKvlWoL?qY7@1PpqSG1esR9C5(7${WNAxDJd?61v~Z%% zlrz$QGEx6U-U#qY+C|zy-AV~pCqc#8CLBj~WE&$O+woC&6!0gH)Ji|z;zeT!@Xv7U zp6p_kbF_nu@ZH3rXYn!WZ+PO;r^b*&9&0l^R2t7T;zer->-Npe1|qwYtGT)Ml<2zB zkDN+05MqsFvZQGT59r)tyH=0a{8Lg(cwqxv(#=3>$*(=jN=Rh7^A8F0uDHt9TE!51 z00;!cVG4=V?v%@7t?40F{K59_k`U?=e{z@@H8K|Yfl(RU1VZw#mO}vi$oqs%u!nHP zsLe4xTr|mWW>O_TkW3=YP7Tyo_P!d)f-n}OhL|#W55Ygi<91cHyxFI7)68eBp&Hco zxRVJtwZrW!{ebYm`pNh2r=D4;2V@-03a{ZUX*=HuC9gR{Ao z6ys4j_~6%+_$;K`rymYJ&jc4nr3~A8uO@EvI8t^@7#a0SP5E!fi~&GX5-QNqREjAk zB~1TeC0RF`b7TXq>*^+cCO@?R>#xpc=XQaHlYlfik;KL#3#kY z5emP|HY%o;Zpv5^2HhF#z~Uzy>D$<@Cs~8e!B!kvNx$S1&>tPhTll8pp(dB7%;O9R zeFN|Gdmphghgwhx4C=^e>p`>}3EBMF9(UI*!qQ=NW_;W{`TCf;&^q1vw)MBG7RRIj zeKPj93pDG{wgYNb=FSI_IIIwl5SCmz9q>TNRJk$IMA4)i#tyT< z+ZL87rVd=mi1)N7s0?#eG$nc~bZ#7>qf8D#D?&%e{?RkAqERbAeB_5aAaT*p7!58( zrMm^G@P{hm;zF_oB?fG0$oGd6bPAD}q&7nnu)dw=YnIo&9gYKLWL*=J6zraqkVMW{ z)WLOLb72udIBj4@q$Co?^G-ls#@H0wED>MOI;Svk<-XJ7$@Ri;)kRR3W#AUfvhDa4 z2OW2voZJ}K-=FFve(d4N-kych+qzIrZ{}D%=?NBylT6nx7~iY!{c(`+<*%4kczNS? z?&o1cP5!m$XD1ow0B@KE02E`tQOQd>!b|W{_mA`AJ0pHn5RWJb0#pfS)ueSmZMaJ7 z5P$$Bib;S!BzA+4fq@90k$i4g9@o!HS0^O{Bmpx;LG@iA$rDpaNePLjQEY(mEp1`{~b*cP$LSV6Hwxvw@d4Mi* zGRS;STqwJLA_J}V3JHC?tWJ1bJ})&pyjr}!(0q9I*-KIy>O8G~Ovh)!S_ZhysVO_M z{TF%T*_Fy{&^Pzr+UX0BHU-JVwsLUzS%@PjS=CagH2BmpF1(^6Rm%tzC|5a+_w}(l z_`W*bdIEKw`lQVCzjqFHve)DD1+boXH>WqIsSyBx8u8{V9jyc9S(pUNlk$7tV#Itv zU_wQZSRmB`-S$&t9bkbpbxu?e)JjuLBBA6hwMRX!QUs_&+5z!e|uf?tY$@jU%Iu0 zA+Cv>&Qk1Z7#0wB7<%m4R%h~E7YchO9cS0p!`OZZqb(h9%=J3O!E`qcwV48>6@TcU zX3QPz4_J`!KVQ|B{GnjMTunc3H2qwwzm(rL0Uc=%`p$~;bf<2?FVku_?x%EcnMH^$^rK%W#?YXV64=)`2gmz zvJ?gH0wEDLP*&^Z4-%&l$Z8MaCziX6K&!u=<{J((zZ~RF33E5Tc+UvkqcGYMvRn*v z(`)~_;9a%doAM<~AY0S!^}KFpQ1GwOJycNiF(G_X$Rsb&Dm`b)eAy&G=}4>a`LFYA zIp?F}EnJir=Km$GaaFx^ z&p_Mm`w&rEZJOb`<_>utMY6x@d^w5!18k^&b`Hm%IG>YkW{ zy1X$^!^|A1V8*SPX1`^rZF(>itgK5#spaqWQ;Cw1HW#T&+qmdBM%0ogsGk zP${~2UB5?zzc8$=rrqG4{b#%Ms~S$LCD*r)(&gHOYeki{wD=M-O4___{(2Z23d{sN zvXy^jkW#gGKTAWbWI;RjnMRdrUQXg+5;Bj`=e)E+|0*|x*C0|esX*=S9KK{ZhJZM^ z#VBt$BJPzIvjuQMjBrlxHhfb-A+9m{PJe1GMf-*_KAb7Oj2N#DNJ~ZGolR6`#;IvZ z1XK9YZ=>O4@du*=CHNRvkLYC(6MzcX?J>>hvOJjHt%YK;f8%OAm&jN9x?vB`${!^z zpRy&PpVn4RyeGFxWxq}sVq9tO92^_}fp8ExyS!Vwe2Y-&^fu{k|3}Yb>Ez_OVc9OD zTd>m~(Di=djyk=VjeWffYmof(!wwUaK!hsWP2INkCp)`m7C&yb#!0;5+n?XS04o36 zk^GAL>4eo0>v(+jHQ`nhylfHu&qW?Q`m6#Mu~$6lX@Vj^TS?U9Q6TxgDcVGbxTeWq zQ8HC@N8ch#|A>+cCwDmP5xC^?tTxvHUj{+KFhJv70?GNp3`(A-!Fxn>vzA7P9KKg{ z$J?~dhy8t@c?vVBSCafhmYQZMxTp>e)0|8p2?ZGzW$6mAmSCSd5zCP|s)vpr1hMTt(hm=&2^vcNdj}?KRjZ(?BU2{ zANVW%r{RuT1G4WaAFGToEy=`>=5wP$#JfPxTz5&oYrPSD#U%=V{5@LWiq;DJ<&Y%Z z+{Ar7S_gLeO^xT3+nO{X!x6>unV$9^rMetGvyzINz{RMlbLUxd(L`=z8V_|VM z>xLLln`7!zOkjelPsPIK!HK*qThO6!9e9!C8Y~t8Re-{L>tg$pRETX}tlMzY(UKUC z&;F(XvaK$8a&{=$(O+u2r!|Mf2`W&Cb4h2@U&NM^%u8DtipfXCytL@gjP#Jnb+`YO z6vw{%&!(d~M|I;O+)$!+hw8OA>vBL&P1Ta|TG1nxqm>H8ld*G1nD~(`$7gxGAd4juS(Lsf$Ylv7+bp&IE1!8i%g0tztW z03Aw+&>J%i7b2>Py+6eTjP3XKBfE>%A)&$ocrkhakuP>gAwpyhwr`_cUxMV5e~NbG zdi;ID-{G{U)c=HNfBLPJ3*(zwDf#?E*)O|JCh9Eas|9OAD`JvapLsvEC^t5t{n7MN zo7~{qt1@Gng0JLrQ#+TF;?~tVRe*&F2eQP)kaHlwfwrDInZ4R#K{$5Idh|MnR}NlK zfwLIe@gEtgzr6-7YjrOOvxXiXTfA)7qFb${5 zu8-0%<4h6;0tV@?65}d?mIt!mb{k3-cayRwnp{#Ps>wDMi?*VfL*e(K9~697!BMNK z9v5HROzSQ59#>2hg!Sk$<&2A6=WEk41audT-1qn$7`5A@nQYuu{9&7IVzrLQ1A~VKrnVyn*6r+t9B~^yvtYAi=DV0~^{Lq7#3@|iV$q%J5 zHnzE=!;nZ2fWlPuz1+co$&|cE2@>V$P%`@cE@{|rFgN}w1rYLVL)Z|7AvDYdP|yMi zDA-zQh^BBTDrN>6Lw2Z|GB~4Rix4zJV2V1^AKJR$ri0g5#pBl6wqv{bqB)7e63KXx zRgP0?JB+0umby89*1$6~NZp^PPtX!;X?UgKsiTB~@%EL_kl}SODn=%MR&)LAFz&jt zgt+AHj(>L1B&BCqPQ;H|HGCC_2uv6EDS$K0)rO>{Yc@=Fdg+eob8wFh5uuLz9h834 zbeR}5ybm`h5ZMPHcx)x8nNV-dSM@yldOf~kXyZ|Ay(2_`KrKY^V+2jgRJtn4oM5`5 zl@tdmX(fV81u_!=$fQM4xDc98I{6&@fdvwO+Yz7wmy;{a+> z6FZ~Y;W5ZZ*^xMK0R&J4hzn_02-1a%Lrw8BN}Fqkan=I)6c_=TbwMV9o5nYVD*36N z%wSPM7&ylj8Q%`s0W5bo3dJ|l0#s6c9vjx{pe``D$qu9D`}Z;Eo;xls7lGq-t<1sgdo=$XwfXO>58mXN@BiK)gI@6KXjoJz z;HReElsI@m0Wc8b^MAd!pSb-6lP|0<)8yU0?=GRLH1-fSn*`bS!GtOP*f6+JNtNv?*KaHV%-p`cm7Pn(Qcn)r977Z01f zC&ep0h3nL8$1D=Fla}!>x5@Lq3G-jOAjN+L5TYf6v_&>idJ#g{^-p98GA_gOxiUbHgr zTX+sj%8lCfJhi)7m&^RH$p3zf?2Z3o?R07)w8eJ^oz}{m#<(FmJ8Um;*?OQXs%~@- zgi1$vXN<96!2FB^N_}slH!K;O-hNJ(5GGT0++Amndst&{k9qJxEYPCbhv9{-z?@-r zmWM_1nnvC5D@jdl#VU279T$LTWhvU6(VLYb}fnv459S!xyRtrlq((~q>`;tkX1 zHQ8Avl@4VBlQfI{b!4gegI^yiOWaiVY3U&BRpVl+_9You=J7jeJ9a;yt>Kdon5W^2?6+4Lt=`=ygk{%GI~1H0a0Jw-jGB}&Yk%Z;q- z8u+)wtE5}0$5+A4ce&3$7r8$aD${ul`3ipR+;)jPAdXNgWH9NL$>*7LXS|MiKrLR1YKlZHSJc9scIp~s)yZ7!7W$Z`=8Nd zafxeu##t zjv67@iE^bK9VRRVvWg!lBIamgeT+egd2_dL76?6@pZ1ndnLeh22>L+=p?%Fp@KCev zS~PQ5ry5Yj@Xb46Mkm}8I+fTzAm1cgj<@vCP1TP z;lU^PIu?8bNKFOTVuBJjPNwU`>6p!@-Cov>ER!^pNWdR+xe5CGaq*rilBMc-XDQ6y z>A!hsUDW&NtLYCe;Y^11j&|N-Q)ln5+(jZqIiE6(#-Bfgpd+oEOk6X1VvQqCCX2bb zJyeJUaf9sTB7bTBIOq9FJ~j~a;MV#(G5zJI-L0jt?HCyzG7#`iIe(z9Nk}s*fM>B% z=({AvW_JXc1Q+nQy2HjvkAf_wRmQC7ai`zorFw^n`4gwEu8n)e->%(PRO1a_B7@?NA{}SLzWN?juJX^xpfikRM z|16bVP7^=y#hRAj8J_;U=@plgn`5kN!V-rJ(O?QZ*ZGM(&&L8)?cr!AsHjA_D zsoC&BUUjab7c**n)i0gB`t|83*n{P{*`K?Y#Xs=7MYOl%Mt*KNP&NKw#@ck0aYiEd zkDDR*FlqKI?2+1wq(?-gS1(?F=(y+Advk55Tl<&WsLq*+Tg^C}dnNMoF)2itKnXNNRP%8Jag8cf7e;&+JxE ziVl>mFQN=C=K?C<$F~bFr5_RGWvkn~zb%?3H`tjPR@tW-@p(MjWoCNfltS}~-BL4N z+-&~QuSB5!U-UE9gaJSzj-nzWrC1@6_A zED^^UxzCX9YKbz1AR^P{zgX^jOzb^dS`OWweD=Gu_V=+5Nxesh%}ij<_ab2nr@FVF zgFKYc8BJ#g8jP3z_y1_#ElfT!Qh7d)RFib9wsuh9;bu{8bFBpKqc+hPCE!R9whV*j zF2S%NJLt;kXVJHCCJ>^9kO+-ZS}t%(VYNS~7_Fkb7Mmp=*(>*1^~BfG@yjjJLZQx7;DKNt`oI2lTr^_fzK`;j=}mRwxCo(FZ;^DF^QSd&w1 z0tb_7q1qZEb^hw0MJ+~YP6OjOZ&&gH4UD0_Xjvq|r=H(gpzttwX-oY+9^`V`#Poh! z73w$37u57QnT&@@Qt#^YjIf*9>xPWRhQEi3+cLi%9C*y5MDm)o zUagV-UH7ZupLR%3VEoLVrT;!9MnEpJ=B?;^=G>+3r=I_bLUQck;3MsfHxv4=Jvv1n zt9x>kmW!W$B?Xf-l#idVb<`Iy&M){*+k9sHjOt9L9l|Ovf}3X3kG1^zZu2WxY2p#r z>MJJ|wh^V$U)YbbBVu?src!!eYX^-9djP4$*`>uzJ^&G0=J98XFi`6Zr$1JqjY^RU*tNOM2 z+vr}Sq0I5ZVkx0LtIyn}#l;#b+`BEg2c&t)RPYW__%pk^kmrnR6!16xc|#VAuc&i* z`9TKCjCe4OmpDur$_&eA&|nc~ku36(W^BVq=ulmbffKnmS$wyIBKvHn%+U)5Y{FBz ze|&P@beT~`=6<3)s?hQS3cGDDEGM?VhsaW+HNu901#d8-ULx^I*o~SN5SiRA$f1g^ZzJT6pK3@#e1!QYve5FHLDCT_&c7av;U%w$&H9E8$UNKD9@X@QmJ$Lq1K6@9*_TbKi6`y^4aNB(+ z#uc4xo~WodR{`{_#z!yd6PGMIwK^vXiA0;He{C}x9l)%gd-A=%(^+9cB5ImsU`*;` zNm^F@C9~;nL!Jv(Suil&6cnZLyi2o)y}*umD4nT#(BCgGpU6?+p4SZJh<3|*P^*g2bzkhE0B-NtdqBj2%h38~(hhgIx8=>c~5~|K0 zD|vFW6LE?>N}D)pEU!4B>ja>pP|J)4GkG`zgyv7!@2SbU2`0GSjR?JvYsl*&`faSZ6_XAyR+Pw@V7V|MDzZvTg!ThL9| z{)ntHm+;uVDukP9Q^oP2N3?*$gk$K#5jnswtgPcceP1*tTx5i-1dag^SAsVaF$kzL zuq4-pYSZ6ehd3f^EKUbT_>3_WC4vdc07TPBJjUT;)s_dp2;vM*B#b0;YbZ8~&8@t{ zE&M|w;ILaH(6C9%uK@%Rw&$@={5_QEuzY!ffP*trjehoPm2ZCeW0Rx4?JsyT zO6V9^U)hQomT%uLzfb>?i$|~}Nz3~x*W1%R^l8a>E6w+~;yctraiN8ZWsJ6j$2I)& zZ#v??Sz;d7iwGtql=6K{zL=GwTl;90*{dV{y%y>5`$3*#8ZH(zLjAJOm5y+~+L^>r zuZ}y#Wy}F7zHbQCa6OmeE~=Er7K7B7JZJ2^nJdA!SCI06ePI6^2fI51MBkcU2|Hyb zAtd}*l=<(u!Cye+TRP2weI+I1mff{^WnVBFV&!oGrUC2;L`2DB`a)^4bIxi2u%<`a zn$X-hx7&ShRk|zl!(XJtNLwGX>Xsn3i%(rtlP`7CPUE{(0?QvL|32_HHf*a|ceZaR z8+udW(mZi0wi-lS%S6P_G^LhwFkDKQSUifHfApAwWPG>(MOb2wruCW%JsEs{ag-N6 zr9%A2ymu<>&DeON_{tywTIHp!FS0TV?6tZ5!$P^H;Sr?m_|`SX#4aM9yRKa)ZJ(z3QXZ+H7Nx zn9kJRbx~(_;K}eKM=Sq&thlkhe!Y~cOzQh4Shek|HmO<{b3a?>Db8CpDI(!+Uo<~r zdChJOH-5J*eA=)e8erM?vFoweNwtlF;ar8ot=}QK#Gx}9KpkCZe4_NPX<1{`NsWTL z3-OWMfZRR`dI*n5%+ln&9Sp079>UD=JL=R#sZ6>POiK>F8MsBPY&*V8yr%aaOIQy* zo8v!ddhF`S6;Q$N6{HNlU$TQg>GAfQl0^R*m{nE-k=KoF^{MBOyA|`gpd$>+!c`48 znDw=;`V#fk3`qoqG^`=K3>2_%F8lb>p-2nFr>qDvxu<(o0la{qM;r)ae%2R>{Y?ea zf2({y`G(d70^CMY73v35FIQM};tmRJ4J-01zrOw$Jwg2@@W=OIdBNK00NtStxev0! zuV0_M54fkZc5|4GTW8{RZQ2eB?KB=B8JxU<#E2|&`8##k7@u~}(&xLR%|Pv?Nb z*6YZ*D1G84M}4{)@mNDK3w~4^8r76Y#zjUC(DC>tu1<^}_7pmqMzN7$0eHbW-Y1;Y zRkJMAByV;PpTb!*GD2WN>^a$Q0nwODPN9K$q2c38L~17#J(W?0$IPdNo3{SzHjDkqZQLJ6@fkm7!d5lHRO9=M}= zDEw&bA!0cI4sJJvR=;`->h}I{FwaXG0`Rd1Pt!#XtE!+eeQaeI*YN~`eB!tmd^C4^ zD;Xyd90*$M0D!P;7|?C??#c4wa}ojc8QA^Y8wCiWLX2^{Ac1Y)1g-BlQ;C~)i|k!_ zGavj?c=~G8C81S#DS2eP&Mk>F=&s1|I0fsU)}tRU@l}3bnO^94JYxk%c2EBH`qLY# zLaARQBF7VI{~+%%|3!%3-~Z?jxKw@zK%W~*570`6@J7Le%Vn^eqF;R$sM$i8+`%+y zkCEBH2#9*(7@26*N-3I<7_Ql3|Jx^HsFI5cPOBdm&2GSmaJR>F;(&zcF=}{N+P8S9 zGnygr<}#-NkUqPh)WvIDBB#S+3*0O{JB{=$w;9kYAu&l0qp;^+%Y?EM#-cEStE zkb}w3ELLli?bV4{l>0i$sJQiDw?`8ti)N50oQVKK6@3nJw$A}%zSAVz^iM#R+n8MT zFb`RvHcR@qAM8LP@&u?Nm;|@I+n0(-Ko{9**l2VFfbE==VXt1m?=_Gok&s*Hw=Ysz zKb;-~Vk#1B&uj%~hz~~RN~}y7thjtpfw3Db_jTJkPdI|P3oQZ`jE8%C63%0NLzL_D z0NjyB$*BR)UEC$W$_rx%A`r@HG`#}^6X>T(!0F%l`@nzTKT)WHCglECeVF$i`g}`h z3^C`xJ{L=V91c1XacbVku2OoWnWIrfE6A5mKrw3t$qD9uI^ax#wBOV_L{#>~DEYp3%Z!R4-awe`r-AS*oD0XF zCa0FPF%6Sz-g32-#)d#r5Zxin6pO+y6+K-f1+l7v-T6-pQdnldFZp?gc z7LRX!W}Ppe{nEYpD)8z0uk=F6|Fn1I-%#*fd=~2%jI|*=V=z%@>_QpKCJ4n8$t&mbuNGQ9wdc@jN2=4g(1S@ zjX=X3B{?+Pz3=X6b!rY?G%kN+`jY^5Z~k7fCX1?MpQu?m@f z+J<{UCnqBuw`#Y^8Ywr&UA8 zjlxb@*aqmQ3JD0&vebq7bK2nNJYa%>{D*mjieQ8E)KqDd2YTn2B*Y_7tKCj5Q$BxS zdB}<+yFHSMB2)m@@ct$%>Rx)Qcwa21N|z$jF;;Z`?zl8wJwQ!BT9D-t6+LwE+rqsk zWp2@jJ;V0um?*;Azb9XN$yX6W-|POzxt`@_v|=V*EuyYJNG!aCi}u-Jk<@kE^XY}1 zhThl`a}Fa*@8lND@WadOw{Nfi^oh*-5qM8*?EJd9p~aab;s@t8zi?t5&-U zo#OB0l&XLro=@ik%v+*E`9KsVnjfr4=|tN==m^nzB5`2d>}KO(v|?#O0vJRm71Uhf zd;W3hLW2x-OP{emY6w#nG&OD6c%H&$sf zRA8NFL$CaBXJ)eltXSJwc9Q%@bEJ&ZnIecSyt%1@=+Mb4hmudP zK>`8}^G;8DjD$h7;lf!!5G5hWoUUvZ%^5&qhG(v2uPq*{;P zrp}B}QvRfj*Z)$a&6(C(Gjeb-uwvJPHy7igHlP8U`7-G83msc!H1ARn5_Q_Mi zL+>t!J;7-X$A~B1XiR+QP>s+c!1Cp-2ILAMN%d)3@8RDP42j+{q+}W+tf>I(-MwQ9 z)ymZEL4a9so3ZCRLbd{!!eOH;PZ|?ZUV(eGpL+E#;g5iFhi?NZMeQ9zTdAn$`!8dIwATB)6-PoF(v&9y3p6b|K zRj$E5YD7@LcvuR-)-|XV3#}CffAM21^(DEnNC#v+A+_=jEli{>7r(#F?ka! zcn>L}{eq$e>ICu^JEDg?Wh2TrB|$b&mx@}nkxHSkjU@Ei)2p!(qP^KNx26w-BpuBp z?*XaI&9YP|cNNUE-xEsOoIiW-$@SS02vahK?2YixHPB;GAoYri`b-R<@_ai0#;R>`6bVN}?KCg#ojRFTt7wZOwN$LcJ>4z5FjR@iEPu>a<)^Y7aoD$I})4ehl z5~&rKg?lNL*WKjTaZlW@TM6(i%I0pAn0b&2&F!v{p;t{T!R1VTweDbm)Wxzht=pkz zb*-JAhHdeTBgb@hJ+Gdt-08%;Ty+m{kesVy7nPuB%r3A_6ierF;pMLnH7`$fIQMAW zm#doj%Vi#y!M@?era7-CQ<-=+Dd7IY~n=-AQQ7-f`U(wNor5H zB>Yo|tSc0SCXT27(L%=#)5=ffwI|T#IX5;s$ErV6 zCjL1Q?5vH8^k;TnCD<+9oidBStXX&d>e)EG;HS_klQ1&5+J4^F%nqxgsf9+n{wx2y z8vp=Qn^FJ(Gr0EL{9F=N4-GfopF>2LHc-LMgpCj}Dj3r(00;jP()A7Fz6T1IB)Jbvwsj}Qa}x#2BLIPyvBUNe7$AzwoMZ=a`&fY0a@@&S z06;g1JNtIfUBU9`Sr-K%Ro83O4VQcG9{eIm!Vt+;lC6V7%u0KmUU$O5!EAzSs~ls{ zGV8Vr+wstMnbOoQZs$ziz45D;dP6d^?+mGBjOGI|?AzoA?%a=%gHKo9yNtBNm=9n0 z__w=)v#~JgXd1(5#opQUxAxb^JH}yzES`ClMB~uViO{Wg)BP=Cad-`zvbwyJVh!3~ z`p!@NHsng2iv8kL`k^@OgYCWIIF*rwrEmUwzZHLvPiDH+Jk1{;_BvPiSV#0qdb0YzmeqXY)mAXGxiMk0uCa7gv}r*3peA z2th`M6x`n`e#bECn+4M3X7jJ(%~fb{`~eFd2BU`oXEC$TKBflN)wA~Cpe9&CLXx>Q z;rVI^gn!MfKg3;Vx~yAfj%spHXF@Bo7pGg_S&8@)xEKYTZx;V)7;>__c67IPVknj# zkXzEc{~P_Dfu}I-bA(E$*^dhwoV#M_PrRw7uT@)C^G^R(AACwN@Pl+u$l18otvoW| zO&Y_KElyDIZVMUK4VbfIU@s$%ujcf6W;jjK10seV@|9?dEU-e4q0RR91 diff --git a/demucs/tools/__init__.py b/demucs/tools/__init__.py deleted file mode 100644 index 0952fcc3..00000000 --- a/demucs/tools/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. diff --git a/demucs/tools/automix.py b/demucs/tools/automix.py deleted file mode 100644 index a839345e..00000000 --- a/demucs/tools/automix.py +++ /dev/null @@ -1,343 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -""" -This script creates realistic mixes with stems from different songs. -In particular, it will align BPM, sync up the first beat and perform pitch -shift to maximize pitches overlap. -In order to limit artifacts, only parts that can be mixed with less than 15% -tempo shift, and 3 semitones of pitch shift are mixed together. -""" -from collections import namedtuple -from concurrent.futures import ProcessPoolExecutor -import hashlib -from pathlib import Path -import random -import shutil -import tqdm -import pickle - -from librosa.beat import beat_track -from librosa.feature import chroma_cqt -import numpy as np -import torch -from torch.nn import functional as F - -from dora.utils import try_load -from demucs.audio import save_audio -from demucs.repitch import repitch -from demucs.pretrained import SOURCES -from demucs.wav import build_metadata, Wavset, _get_musdb_valid - - -MUSDB_PATH = '/checkpoint/defossez/datasets/musdbhq' -EXTRA_WAV_PATH = "/checkpoint/defossez/datasets/allstems_44" -# WARNING: OUTPATH will be completely erased. -OUTPATH = Path.home() / 'tmp/demucs_mdx/automix_musdb/' -CACHE = Path.home() / 'tmp/automix_cache' # cache BPM and pitch information. -CHANNELS = 2 -SR = 44100 -MAX_PITCH = 3 # maximum allowable pitch shift in semi tones -MAX_TEMPO = 0.15 # maximum allowable tempo shift - - -Spec = namedtuple("Spec", "tempo onsets kr track index") - - -def rms(wav, window=10000): - """efficient rms computed for each time step over a given window.""" - half = window // 2 - window = 2 * half + 1 - wav = F.pad(wav, (half, half)) - tot = wav.pow(2).cumsum(dim=-1) - return ((tot[..., window - 1:] - tot[..., :-window + 1]) / window).sqrt() - - -def analyse_track(dset, index): - """analyse track, extract bpm and distribution of notes from the bass line.""" - track = dset[index] - mix = track.sum(0).mean(0) - ref = mix.std() - - starts = (abs(mix) >= 1e-2 * ref).float().argmax().item() - track = track[..., starts:] - - cache = CACHE / dset.sig - cache.mkdir(exist_ok=True, parents=True) - - cache_file = cache / f"{index}.pkl" - cached = None - if cache_file.exists(): - cached = try_load(cache_file) - if cached is not None: - tempo, events, hist_kr = cached - - if cached is None: - drums = track[0].mean(0) - if drums.std() > 1e-2 * ref: - tempo, events = beat_track(y=drums.numpy(), units='time', sr=SR) - else: - print("failed drums", drums.std(), ref) - return None, track - - bass = track[1].mean(0) - r = rms(bass) - peak = r.max() - mask = r >= 0.05 * peak - bass = bass[mask] - if bass.std() > 1e-2 * ref: - kr = torch.from_numpy(chroma_cqt(y=bass.numpy(), sr=SR)) - hist_kr = (kr.max(dim=0, keepdim=True)[0] == kr).float().mean(1) - else: - print("failed bass", bass.std(), ref) - return None, track - - pickle.dump([tempo, events, hist_kr], open(cache_file, 'wb')) - spec = Spec(tempo, events, hist_kr, track, index) - return spec, None - - -def best_pitch_shift(kr_a, kr_b): - """find the best pitch shift between two chroma distributions.""" - deltas = [] - for p in range(12): - deltas.append((kr_a - kr_b).abs().mean()) - kr_b = kr_b.roll(1, 0) - - ps = np.argmin(deltas) - if ps > 6: - ps = ps - 12 - return ps - - -def align_stems(stems): - """Align the first beats of the stems. - This is a naive implementation. A grid with a time definition 10ms is defined and - each beat onset is represented as a gaussian over this grid. - Then, we try each possible time shift to make two grids align the best. - We repeat for all sources. - """ - sources = len(stems) - width = 5e-3 # grid of 10ms - limit = 5 - std = 2 - x = torch.arange(-limit, limit + 1, 1).float() - gauss = torch.exp(-x**2 / (2 * std**2)) - - grids = [] - for wav, onsets in stems: - le = wav.shape[-1] - dur = le / SR - grid = torch.zeros(int(le / width / SR)) - for onset in onsets: - pos = int(onset / width) - if onset >= dur - 1: - continue - if onset < 1: - continue - grid[pos - limit:pos + limit + 1] += gauss - grids.append(grid) - - shifts = [0] - for s in range(1, sources): - max_shift = int(4 / width) - dots = [] - for shift in range(-max_shift, max_shift): - other = grids[s] - ref = grids[0] - if shift >= 0: - other = other[shift:] - else: - ref = ref[shift:] - le = min(len(other), len(ref)) - dots.append((ref[:le].dot(other[:le]), int(shift * width * SR))) - - _, shift = max(dots) - shifts.append(-shift) - - outs = [] - new_zero = min(shifts) - for (wav, _), shift in zip(stems, shifts): - offset = shift - new_zero - wav = F.pad(wav, (offset, 0)) - outs.append(wav) - - le = min(x.shape[-1] for x in outs) - - outs = [w[..., :le] for w in outs] - return torch.stack(outs) - - -def find_candidate(spec_ref, catalog, pitch_match=True): - """Given reference track, this finds a track in the catalog that - is a potential match (pitch and tempo delta must be within the allowable limits). - """ - candidates = list(catalog) - random.shuffle(candidates) - - for spec in candidates: - ok = False - for scale in [1/4, 1/2, 1, 2, 4]: - tempo = spec.tempo * scale - delta_tempo = spec_ref.tempo / tempo - 1 - if abs(delta_tempo) < MAX_TEMPO: - ok = True - break - if not ok: - print(delta_tempo, spec_ref.tempo, spec.tempo, "FAILED TEMPO") - # too much of a tempo difference - continue - spec = spec._replace(tempo=tempo) - - ps = 0 - if pitch_match: - ps = best_pitch_shift(spec_ref.kr, spec.kr) - if abs(ps) > MAX_PITCH: - print("Failed pitch", ps) - # too much pitch difference - continue - return spec, delta_tempo, ps - - -def get_part(spec, source, dt, dp): - """Apply given delta of tempo and delta of pitch to a stem.""" - wav = spec.track[source] - if dt or dp: - wav = repitch(wav, dp, dt * 100, samplerate=SR, voice=source == 3) - spec = spec._replace(onsets=spec.onsets / (1 + dt)) - return wav, spec - - -def build_track(ref_index, catalog): - """Given the reference track index and a catalog of track, builds - a completely new track. One of the source at random from the ref track will - be kept and other sources will be drawn from the catalog. - """ - order = list(range(len(SOURCES))) - random.shuffle(order) - - stems = [None] * len(order) - indexes = [None] * len(order) - origs = [None] * len(order) - dps = [None] * len(order) - dts = [None] * len(order) - - first = order[0] - spec_ref = catalog[ref_index] - stems[first] = (spec_ref.track[first], spec_ref.onsets) - indexes[first] = ref_index - origs[first] = spec_ref.track[first] - dps[first] = 0 - dts[first] = 0 - - pitch_match = order != 0 - - for src in order[1:]: - spec, dt, dp = find_candidate(spec_ref, catalog, pitch_match=pitch_match) - if not pitch_match: - spec_ref = spec_ref._replace(kr=spec.kr) - pitch_match = True - dps[src] = dp - dts[src] = dt - wav, spec = get_part(spec, src, dt, dp) - stems[src] = (wav, spec.onsets) - indexes[src] = spec.index - origs.append(spec.track[src]) - print("FINAL CHOICES", ref_index, indexes, dps, dts) - stems = align_stems(stems) - return stems, origs - - -def get_musdb_dataset(part='train'): - root = Path(MUSDB_PATH) / part - ext = '.wav' - metadata = build_metadata(root, SOURCES, ext=ext, normalize=False) - valid_tracks = _get_musdb_valid() - metadata_train = {name: meta for name, meta in metadata.items() if name not in valid_tracks} - train_set = Wavset( - root, metadata_train, SOURCES, samplerate=SR, channels=CHANNELS, - normalize=False, ext=ext) - sig = hashlib.sha1(str(root).encode()).hexdigest()[:8] - train_set.sig = sig - return train_set - - -def get_wav_dataset(): - root = Path(EXTRA_WAV_PATH) - ext = '.wav' - metadata = _build_metadata(root, SOURCES, ext=ext, normalize=False) - train_set = Wavset( - root, metadata, SOURCES, samplerate=SR, channels=CHANNELS, - normalize=False, ext=ext) - sig = hashlib.sha1(str(root).encode()).hexdigest()[:8] - train_set.sig = sig - return train_set - - -def main(): - random.seed(4321) - if OUTPATH.exists(): - shutil.rmtree(OUTPATH) - OUTPATH.mkdir(exist_ok=True, parents=True) - (OUTPATH / 'train').mkdir(exist_ok=True, parents=True) - (OUTPATH / 'valid').mkdir(exist_ok=True, parents=True) - out = OUTPATH / 'train' - - dset = get_musdb_dataset() - # dset2 = get_wav_dataset() - # dset3 = get_musdb_dataset('test') - dset2 = None - dset3 = None - pendings = [] - copies = 6 - copies_rej = 2 - - with ProcessPoolExecutor(20) as pool: - for index in range(len(dset)): - pendings.append(pool.submit(analyse_track, dset, index)) - - if dset2: - for index in range(len(dset2)): - pendings.append(pool.submit(analyse_track, dset2, index)) - if dset3: - for index in range(len(dset3)): - pendings.append(pool.submit(analyse_track, dset3, index)) - - catalog = [] - rej = 0 - for pending in tqdm.tqdm(pendings, ncols=120): - spec, track = pending.result() - if spec is not None: - catalog.append(spec) - else: - mix = track.sum(0) - for copy in range(copies_rej): - folder = out / f'rej_{rej}_{copy}' - folder.mkdir() - save_audio(mix, folder / "mixture.wav", SR) - for stem, source in zip(track, SOURCES): - save_audio(stem, folder / f"{source}.wav", SR, clip='clamp') - rej += 1 - - for copy in range(copies): - for index in range(len(catalog)): - track, origs = build_track(index, catalog) - mix = track.sum(0) - mx = mix.abs().max() - scale = max(1, 1.01 * mx) - mix = mix / scale - track = track / scale - folder = out / f'{copy}_{index}' - folder.mkdir() - save_audio(mix, folder / "mixture.wav", SR) - for stem, source, orig in zip(track, SOURCES, origs): - save_audio(stem, folder / f"{source}.wav", SR, clip='clamp') - # save_audio(stem.std() * orig / (1e-6 + orig.std()), folder / f"{source}_orig.wav", - # SR, clip='clamp') - - -if __name__ == '__main__': - main() diff --git a/demucs/tools/bench.py b/demucs/tools/bench.py deleted file mode 100644 index 762a7c3f..00000000 --- a/demucs/tools/bench.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -""" -benchmarking script, useful to check for OOM, reasonable train time, -and for the MDX competion, estimate if we will match the time limit.""" -from contextlib import contextmanager -import logging -import sys -import time -import torch - -from demucs.train import get_solver, main -from demucs.apply import apply_model - -logging.basicConfig(level=logging.INFO, stream=sys.stderr) - - -class Result: - pass - - -@contextmanager -def bench(): - import gc - gc.collect() - torch.cuda.reset_max_memory_allocated() - torch.cuda.empty_cache() - result = Result() - # before = torch.cuda.memory_allocated() - before = 0 - begin = time.time() - try: - yield result - finally: - torch.cuda.synchronize() - mem = (torch.cuda.max_memory_allocated() - before) / 2 ** 20 - tim = time.time() - begin - result.mem = mem - result.tim = tim - - -xp = main.get_xp_from_sig(sys.argv[1]) -xp = main.get_xp(xp.argv + sys.argv[2:]) -with xp.enter(): - solver = get_solver(xp.cfg) - if getattr(solver.model, 'use_train_segment', False): - batch = solver.augment(next(iter(solver.loaders['train']))) - solver.model.segment = Fraction(batch.shape[-1], solver.model.samplerate) - train_segment = solver.model.segment - solver.model.eval() - model = solver.model - model.cuda() - x = torch.randn(2, xp.cfg.dset.channels, int(10 * model.samplerate), device='cuda') - with bench() as res: - y = model(x) - y.sum().backward() - del y - for p in model.parameters(): - p.grad = None - print(f"FB: {res.mem:.1f} MB, {res.tim * 1000:.1f} ms") - - x = torch.randn(1, xp.cfg.dset.channels, int(model.segment * model.samplerate), device='cuda') - with bench() as res: - with torch.no_grad(): - y = model(x) - del y - print(f"FV: {res.mem:.1f} MB, {res.tim * 1000:.1f} ms") - - model.cpu() - torch.set_num_threads(1) - test = torch.randn(1, xp.cfg.dset.channels, model.samplerate * 40) - b = time.time() - apply_model(model, test, split=True, shifts=1) - print("CPU 40 sec:", time.time() - b) diff --git a/demucs/tools/convert.py b/demucs/tools/convert.py deleted file mode 100644 index dfc022f8..00000000 --- a/demucs/tools/convert.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -# Script to convert option names and model args from the dev branch to -# the cleanup release one. There should be no reaso to use that anymore. - -import argparse -import io -import json -from pathlib import Path -import subprocess as sp - -import torch - -from demucs import train, pretrained, states - -DEV_REPO = Path.home() / 'tmp/release_demucs_mdx' - - -TO_REMOVE = [ - 'demucs.dconv_kw.gelu=True', - 'demucs.dconv_kw.nfreqs=0', - 'demucs.dconv_kw.nfreqs=0', - 'demucs.dconv_kw.version=4', - 'demucs.norm=gn', - 'wdemucs.nice=True', - 'wdemucs.good=True', - 'wdemucs.freq_emb=-0.2', - 'special=True', - 'special=False', -] - -TO_REPLACE = [ - ('power', 'svd'), - ('wdemucs', 'hdemucs'), - ('hdemucs.hybrid=True', 'hdemucs.hybrid_old=True'), - ('hdemucs.hybrid=2', 'hdemucs.hybrid=True'), -] - -TO_INJECT = [ - ('model=hdemucs', ['hdemucs.cac=False']), - ('model=hdemucs', ['hdemucs.norm_starts=999']), -] - - -def get_original_argv(sig): - return json.load(open(Path(DEV_REPO) / f'outputs/xps/{sig}/.argv.json')) - - -def transform(argv, mappings, verbose=False): - for rm in TO_REMOVE: - while rm in argv: - argv.remove(rm) - - for old, new in TO_REPLACE: - argv[:] = [a.replace(old, new) for a in argv] - - for condition, args in TO_INJECT: - if condition in argv: - argv[:] = args + argv - - for idx, arg in enumerate(argv): - if 'continue_from=' in arg: - dep_sig = arg.split('=')[1] - if dep_sig.startswith('"'): - dep_sig = eval(dep_sig) - if verbose: - print("Need to recursively convert dependency XP", dep_sig) - new_sig = convert(dep_sig, mappings, verbose).sig - argv[idx] = f'continue_from="{new_sig}"' - - -def convert(sig, mappings, verbose=False): - argv = get_original_argv(sig) - if verbose: - print("Original argv", argv) - transform(argv, mappings, verbose) - if verbose: - print("New argv", argv) - xp = train.main.get_xp(argv) - train.main.init_xp(xp) - if verbose: - print("Mapping", sig, "->", xp.sig) - mappings[sig] = xp.sig - return xp - - -def _eval_old(old_sig, x): - script = ( - 'from demucs import pretrained; import torch; import sys; import io; ' - 'buf = io.BytesIO(sys.stdin.buffer.read()); ' - 'x = torch.load(buf); m = pretrained.load_pretrained_model(' - f'"{old_sig}"); torch.save(m(x), sys.stdout.buffer)') - - buf = io.BytesIO() - torch.save(x, buf) - proc = sp.run( - ['python3', '-c', script], input=buf.getvalue(), capture_output=True, cwd=DEV_REPO) - if proc.returncode != 0: - print("Error", proc.stderr.decode()) - assert False - - buf = io.BytesIO(proc.stdout) - return torch.load(buf) - - -def compare(old_sig, model): - test = torch.randn(1, 2, 44100 * 10) - old_out = _eval_old(old_sig, test) - out = model(test) - - delta = 20 * torch.log10((out - old_out).norm() / out.norm()).item() - return delta - - -def main(): - torch.manual_seed(1234) - parser = argparse.ArgumentParser('convert') - parser.add_argument('sigs', nargs='*') - parser.add_argument('-o', '--output', type=Path, default=Path('release_models')) - parser.add_argument('-d', '--dump', action='store_true') - parser.add_argument('-c', '--compare', action='store_true') - parser.add_argument('-v', '--verbose', action='store_true') - args = parser.parse_args() - - args.output.mkdir(exist_ok=True, parents=True) - mappings = {} - for sig in args.sigs: - xp = convert(sig, mappings, args.verbose) - if args.dump or args.compare: - old_pkg = pretrained._load_package(sig, old=True) - model = train.get_model(xp.cfg) - model.load_state_dict(old_pkg['state']) - if args.dump: - pkg = states.serialize_model(model, xp.cfg) - states.save_with_checksum(pkg, args.output / f'{xp.sig}.th') - if args.compare: - delta = compare(sig, model) - print("Delta for", sig, xp.sig, delta) - - mappings[sig] = xp.sig - - print("FINAL MAPPINGS") - for old, new in mappings.items(): - print(old, " ", new) - - -if __name__ == '__main__': - main() diff --git a/demucs/tools/export.py b/demucs/tools/export.py deleted file mode 100644 index 15795855..00000000 --- a/demucs/tools/export.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -"""Export a trained model from the full checkpoint (with optimizer etc.) to -a final checkpoint, with only the model itself. The model is always stored as -half float to gain space, and because this has zero impact on the final loss. -When DiffQ was used for training, the model will actually be quantized and bitpacked.""" -from argparse import ArgumentParser -from fractions import Fraction -import logging -from pathlib import Path -import sys -import torch - -from demucs import train -from demucs.states import serialize_model, save_with_checksum - - -logger = logging.getLogger(__name__) - - -def main(): - logging.basicConfig(level=logging.INFO, stream=sys.stderr) - - parser = ArgumentParser("tools.export", description="Export trained models from XP sigs.") - parser.add_argument('signatures', nargs='*', help='XP signatures.') - parser.add_argument('-o', '--out', type=Path, default=Path("release_models"), - help="Path where to store release models (default release_models)") - parser.add_argument('-s', '--sign', action='store_true', - help='Add sha256 prefix checksum to the filename.') - - args = parser.parse_args() - args.out.mkdir(exist_ok=True, parents=True) - - for sig in args.signatures: - xp = train.main.get_xp_from_sig(sig) - name = train.main.get_name(xp) - logger.info('Handling %s/%s', sig, name) - - out_path = args.out / (sig + ".th") - - solver = train.get_solver_from_sig(sig) - if len(solver.history) < solver.args.epochs: - logger.warning( - 'Model %s has less epoch than expected (%d / %d)', - sig, len(solver.history), solver.args.epochs) - - solver.model.load_state_dict(solver.best_state) - pkg = serialize_model(solver.model, solver.args, solver.quantizer, half=True) - if getattr(solver.model, 'use_train_segment', False): - batch = solver.augment(next(iter(solver.loaders['train']))) - pkg['kwargs']['segment'] = Fraction(batch.shape[-1], solver.model.samplerate) - print("Override", pkg['kwargs']['segment']) - valid, test = None, None - for m in solver.history: - if 'valid' in m: - valid = m['valid'] - if 'test' in m: - test = m['test'] - pkg['metrics'] = (valid, test) - if args.sign: - save_with_checksum(pkg, out_path) - else: - torch.save(pkg, out_path) - - -if __name__ == '__main__': - main() diff --git a/demucs/tools/test_pretrained.py b/demucs/tools/test_pretrained.py deleted file mode 100644 index fb80cf5a..00000000 --- a/demucs/tools/test_pretrained.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -# Script to evaluate pretrained models. - -from argparse import ArgumentParser -import logging -import sys - -import torch - -from demucs import train, pretrained, evaluate - - -def main(): - torch.set_num_threads(1) - logging.basicConfig(stream=sys.stderr, level=logging.INFO) - parser = ArgumentParser("tools.test_pretrained", - description="Evaluate pre-trained models or bags of models " - "on MusDB.") - pretrained.add_model_flags(parser) - parser.add_argument('overrides', nargs='*', - help='Extra overrides, e.g. test.shifts=2.') - args = parser.parse_args() - - xp = train.main.get_xp(args.overrides) - with xp.enter(): - solver = train.get_solver(xp.cfg) - - model = pretrained.get_model_from_args(args) - solver.model = model.to(solver.device) - solver.model.eval() - - with torch.no_grad(): - results = evaluate.evaluate(solver, xp.cfg.test.sdr) - print(results) - - -if __name__ == '__main__': - main() diff --git a/demucs_repo b/demucs_repo new file mode 160000 index 00000000..4273070a --- /dev/null +++ b/demucs_repo @@ -0,0 +1 @@ +Subproject commit 4273070a70ded308ddfd0879d267bbd06f89a1b7 diff --git a/pyproject.toml b/pyproject.toml index 90c8ce6e..8451e0f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ dependencies = [ "uroman>=1.3.1.1", "lhotse==1.31.1", "coverage==7.6.1", + "demucs", ] repository = "https://github.com/chidiwilliams/buzz" documentation = "https://chidiwilliams.github.io/buzz/docs" @@ -106,6 +107,7 @@ default-groups = [ ] [tool.uv.sources] +demucs = { path = "demucs_repo", editable = true } torch = [ { index = "PyPI", marker = "sys_platform == 'darwin'" }, { index = "pytorch-cu128", marker = "sys_platform != 'darwin'" }, @@ -136,7 +138,7 @@ include = [ "buzz", "buzz/whisper_cpp/*", "buzz/locale/*/LC_MESSAGES/buzz.mo", - "demucs", + "demucs_repo", "whisper_diarization", "deepmultilingualpunctuation", "ctc_forced_aligner", @@ -147,7 +149,7 @@ include = [ "buzz", "buzz/whisper_cpp/*", "buzz/locale/*/LC_MESSAGES/buzz.mo", - "demucs", + "demucs_repo", "whisper_diarization", "deepmultilingualpunctuation", "ctc_forced_aligner", diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b574b1ae..47b96448 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -118,7 +118,7 @@ parts: cp -r $CRAFT_PART_BUILD/buzz $CRAFT_PART_INSTALL/ cp -r $CRAFT_PART_BUILD/ctc_forced_aligner $CRAFT_PART_INSTALL/ cp -r $CRAFT_PART_BUILD/deepmultilingualpunctuation $CRAFT_PART_INSTALL/ - cp -r $CRAFT_PART_BUILD/demucs $CRAFT_PART_INSTALL/ + cp -r $CRAFT_PART_BUILD/demucs_repo $CRAFT_PART_INSTALL/ cp -r $CRAFT_PART_BUILD/whisper_diarization $CRAFT_PART_INSTALL/ # Create desktop file diff --git a/tests/transcriber/file_transcriber_queue_worker_test.py b/tests/transcriber/file_transcriber_queue_worker_test.py index da351ee2..e48bccb9 100644 --- a/tests/transcriber/file_transcriber_queue_worker_test.py +++ b/tests/transcriber/file_transcriber_queue_worker_test.py @@ -70,10 +70,10 @@ def test_transcription_with_whisper_cpp_tiny_with_speech_extraction(worker): task = FileTranscriptionTask(file_path=str(test_multibyte_utf8_audio_path), transcription_options=options, file_transcription_options=FileTranscriptionOptions(), model_path="mock_path") - with unittest.mock.patch('demucs.demucs.api.Separator') as mock_separator_class, \ - unittest.mock.patch('demucs.demucs.api.save_audio') as mock_save_audio, \ + with unittest.mock.patch('demucs.api.Separator') as mock_separator_class, \ + unittest.mock.patch('demucs.api.save_audio') as mock_save_audio, \ unittest.mock.patch.object(WhisperFileTranscriber, 'run') as mock_run: - # Mock demucs.demucs.api.Separator and save_audio + # Mock demucs.api.Separator and save_audio mock_separator_instance = unittest.mock.Mock() mock_separator_instance.separate_audio_file.return_value = (None, {"vocals": "mock_vocals_data"}) mock_separator_instance.samplerate = 44100 diff --git a/uv.lock b/uv.lock index 644e35b9..6ca4afda 100644 --- a/uv.lock +++ b/uv.lock @@ -258,6 +258,7 @@ dependencies = [ { name = "ctranslate2", version = "4.6.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' or sys_platform != 'darwin'" }, { name = "darkdetect" }, { name = "dataclasses-json" }, + { name = "demucs" }, { name = "diffq" }, { name = "dora-search" }, { name = "einops" }, @@ -343,6 +344,7 @@ requires-dist = [ { name = "ctranslate2", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==4.3.1" }, { name = "darkdetect", specifier = ">=0.8.0,<0.9" }, { name = "dataclasses-json", specifier = ">=0.6.4,<0.7" }, + { name = "demucs", editable = "demucs_repo" }, { name = "diffq", specifier = ">=0.2.4,<0.3" }, { name = "dora-search", specifier = ">=0.1.12,<0.2" }, { name = "einops", specifier = ">=0.8.1,<0.9" }, @@ -810,6 +812,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] +[[package]] +name = "demucs" +version = "4.1.0a3" +source = { editable = "demucs_repo" } +dependencies = [ + { name = "dora-search" }, + { name = "einops" }, + { name = "julius" }, + { name = "lameenc" }, + { name = "openunmix" }, + { name = "pyyaml" }, + { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, + { name = "torchaudio", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "tqdm" }, +] + +[package.metadata] +requires-dist = [ + { name = "diffq", marker = "extra == 'dev'", specifier = ">=0.2.1" }, + { name = "dora-search" }, + { name = "dora-search", marker = "extra == 'dev'", specifier = ">=0.1.12" }, + { name = "einops" }, + { name = "einops", marker = "extra == 'dev'" }, + { name = "flake8", marker = "extra == 'dev'" }, + { name = "hydra-colorlog", marker = "extra == 'dev'", specifier = ">=1.1" }, + { name = "hydra-core", marker = "extra == 'dev'", specifier = ">=1.1" }, + { name = "julius", specifier = ">=0.2.3" }, + { name = "julius", marker = "extra == 'dev'", specifier = ">=0.2.3" }, + { name = "lameenc", specifier = ">=1.2" }, + { name = "lameenc", marker = "extra == 'dev'", specifier = ">=1.2" }, + { name = "museval", marker = "extra == 'dev'" }, + { name = "mypy", marker = "extra == 'dev'" }, + { name = "openunmix" }, + { name = "openunmix", marker = "extra == 'dev'" }, + { name = "pyyaml" }, + { name = "pyyaml", marker = "extra == 'dev'" }, + { name = "soundfile", marker = "extra == 'dev'", specifier = ">=0.10.3" }, + { name = "submitit", marker = "extra == 'dev'" }, + { name = "torch", specifier = ">=1.8.1" }, + { name = "torch", marker = "extra == 'dev'", specifier = ">=1.8.1" }, + { name = "torchaudio", specifier = ">=0.8" }, + { name = "torchaudio", marker = "extra == 'dev'", specifier = ">=0.8" }, + { name = "tqdm" }, + { name = "tqdm", marker = "extra == 'dev'" }, + { name = "treetable", marker = "extra == 'dev'" }, +] +provides-extras = ["dev"] + [[package]] name = "diffq" version = "0.2.4" From 76b8e52fe54b4c65b6b7a211dd323cdf40ec5ce9 Mon Sep 17 00:00:00 2001 From: Shlomi <81555468+shlomi-dr@users.noreply.github.com> Date: Sat, 6 Dec 2025 07:14:05 -0600 Subject: [PATCH 16/73] Shlomi/main panel improvements (#1239) Co-authored-by: Raivis Dejus --- buzz/db/dao/transcription_dao.py | 71 ++- buzz/db/entity/transcription.py | 2 + buzz/db/service/transcription_service.py | 9 + buzz/file_transcriber_queue_worker.py | 37 +- buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 184 ++++-- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 183 ++++-- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 184 ++++-- buzz/locale/en_US/LC_MESSAGES/buzz.po | 178 ++++-- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 187 ++++-- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 184 ++++-- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 183 ++++-- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 183 ++++-- buzz/locale/nl/LC_MESSAGES/buzz.po | 184 ++++-- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 185 ++++-- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 184 ++++-- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 183 ++++-- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 185 ++++-- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 185 ++++-- buzz/schema.sql | 4 +- buzz/settings/settings.py | 6 + buzz/transcriber/whisper_file_transcriber.py | 15 +- buzz/widgets/main_window.py | 1 + .../transcription_tasks_table_widget.py | 568 ++++++++++++++++-- tests/db/dao/transcription_dao_test.py | 240 ++++++++ tests/db/entity/transcription_test.py | 286 +++++++++ .../db/service/transcription_service_test.py | 211 +++++++ tests/settings/settings_test.py | 133 ++++ tests/widgets/main_window_test.py | 72 ++- .../transcription_tasks_table_widget_test.py | 267 +++++++- 29 files changed, 3539 insertions(+), 955 deletions(-) create mode 100644 tests/db/dao/transcription_dao_test.py create mode 100644 tests/db/entity/transcription_test.py create mode 100644 tests/db/service/transcription_service_test.py create mode 100644 tests/settings/settings_test.py diff --git a/buzz/db/dao/transcription_dao.py b/buzz/db/dao/transcription_dao.py index f35e0db1..928099c3 100644 --- a/buzz/db/dao/transcription_dao.py +++ b/buzz/db/dao/transcription_dao.py @@ -34,7 +34,9 @@ class TranscriptionDAO(DAO[Transcription]): whisper_model_size, hugging_face_model_id, word_level_timings, - extract_speech + extract_speech, + name, + notes ) VALUES ( :id, :export_formats, @@ -50,7 +52,9 @@ class TranscriptionDAO(DAO[Transcription]): :whisper_model_size, :hugging_face_model_id, :word_level_timings, - :extract_speech + :extract_speech, + :name, + :notes ) """ ) @@ -95,6 +99,8 @@ class TranscriptionDAO(DAO[Transcription]): ":extract_speech", task.transcription_options.extract_speech ) + query.bindValue(":name", None) # name is not available in FileTranscriptionTask + query.bindValue(":notes", None) # notes is not available in FileTranscriptionTask if not query.exec(): raise Exception(query.lastError().text()) @@ -132,7 +138,9 @@ class TranscriptionDAO(DAO[Transcription]): whisper_model_size, hugging_face_model_id, word_level_timings, - extract_speech + extract_speech, + name, + notes ) VALUES ( :id, :export_formats, @@ -148,7 +156,9 @@ class TranscriptionDAO(DAO[Transcription]): :whisper_model_size, :hugging_face_model_id, :word_level_timings, - :extract_speech + :extract_speech, + :name, + :notes ) """ ) @@ -239,3 +249,56 @@ class TranscriptionDAO(DAO[Transcription]): query.bindValue(":time_ended", datetime.now().isoformat()) if not query.exec(): raise Exception(query.lastError().text()) + + def update_transcription_name(self, id: UUID, name: str): + query = self._create_query() + query.prepare( + """ + UPDATE transcription + SET name = :name + WHERE id = :id + """ + ) + + query.bindValue(":id", str(id)) + query.bindValue(":name", name) + if not query.exec(): + raise Exception(query.lastError().text()) + if query.numRowsAffected() == 0: + raise Exception("Transcription not found") + + def update_transcription_notes(self, id: UUID, notes: str): + query = self._create_query() + query.prepare( + """ + UPDATE transcription + SET notes = :notes + WHERE id = :id + """ + ) + + query.bindValue(":id", str(id)) + query.bindValue(":notes", notes) + if not query.exec(): + raise Exception(query.lastError().text()) + if query.numRowsAffected() == 0: + raise Exception("Transcription not found") + + def reset_transcription_for_restart(self, id: UUID): + """Reset a transcription to queued status for restart""" + query = self._create_query() + query.prepare( + """ + UPDATE transcription + SET status = :status, progress = :progress, time_started = NULL, time_ended = NULL, error_message = NULL + WHERE id = :id + """ + ) + + query.bindValue(":id", str(id)) + query.bindValue(":status", FileTranscriptionTask.Status.QUEUED.value) + query.bindValue(":progress", 0.0) + if not query.exec(): + raise Exception(query.lastError().text()) + if query.numRowsAffected() == 0: + raise Exception("Transcription not found") diff --git a/buzz/db/entity/transcription.py b/buzz/db/entity/transcription.py index 3692bbcc..ffb1b11a 100644 --- a/buzz/db/entity/transcription.py +++ b/buzz/db/entity/transcription.py @@ -30,6 +30,8 @@ class Transcription(Entity): output_folder: str | None = None source: str | None = None url: str | None = None + name: str | None = None + notes: str | None = None @property def id_as_uuid(self): diff --git a/buzz/db/service/transcription_service.py b/buzz/db/service/transcription_service.py index 20ef3bf4..4c500800 100644 --- a/buzz/db/service/transcription_service.py +++ b/buzz/db/service/transcription_service.py @@ -47,6 +47,15 @@ class TranscriptionService: ) ) + def update_transcription_name(self, id: UUID, name: str): + self.transcription_dao.update_transcription_name(id, name) + + def update_transcription_notes(self, id: UUID, notes: str): + self.transcription_dao.update_transcription_notes(id, notes) + + def reset_transcription_for_restart(self, id: UUID): + self.transcription_dao.reset_transcription_for_restart(id) + def replace_transcription_segments(self, id: UUID, segments: List[Segment]): self.transcription_segment_dao.delete_segments(id) for segment in segments: diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index 88d62eaa..6866ef7c 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -31,6 +31,7 @@ class FileTranscriberQueueWorker(QObject): task_error = pyqtSignal(FileTranscriptionTask, str) completed = pyqtSignal() + trigger_run = pyqtSignal() def __init__(self, parent: Optional[QObject] = None): super().__init__(parent) @@ -38,14 +39,20 @@ class FileTranscriberQueueWorker(QObject): self.canceled_tasks: Set[UUID] = set() self.current_transcriber = None self.speech_path = None + self.is_running = False + self.trigger_run.connect(self.run) @pyqtSlot() def run(self): + if self.is_running: + return + logging.debug("Waiting for next transcription task") # Clean up of previous run. if self.current_transcriber is not None: self.current_transcriber.stop() + self.current_transcriber = None # Get next non-canceled task from queue while True: @@ -53,6 +60,7 @@ class FileTranscriberQueueWorker(QObject): # Stop listening when a "None" task is received if self.current_task is None: + self.is_running = False self.completed.emit() return @@ -61,6 +69,9 @@ class FileTranscriberQueueWorker(QObject): break + # Set is_running AFTER we have a valid task to process + self.is_running = True + if self.current_task.transcription_options.extract_speech: logging.debug("Will extract speech") @@ -123,14 +134,27 @@ class FileTranscriberQueueWorker(QObject): self.current_transcriber.completed.connect(self.on_task_completed) # Wait for next item on the queue - self.current_transcriber.error.connect(self.run) - self.current_transcriber.completed.connect(self.run) + self.current_transcriber.error.connect(lambda: self._on_task_finished()) + self.current_transcriber.completed.connect(lambda: self._on_task_finished()) self.task_started.emit(self.current_task) self.current_transcriber_thread.start() + def _on_task_finished(self): + """Called when a task completes or errors, resets state and triggers next run""" + self.is_running = False + self.run() + def add_task(self, task: FileTranscriptionTask): + # Remove from canceled tasks if it was previously canceled (for restart functionality) + if task.uid in self.canceled_tasks: + self.canceled_tasks.remove(task.uid) + self.tasks_queue.put(task) + # If the worker is not currently running, trigger it to start processing + # Use signal to avoid blocking the main thread + if not self.is_running: + self.trigger_run.emit() def cancel_task(self, task_id: UUID): self.canceled_tasks.add(task_id) @@ -149,8 +173,13 @@ class FileTranscriberQueueWorker(QObject): self.current_task is not None and self.current_task.uid not in self.canceled_tasks ): - self.current_task.status = FileTranscriptionTask.Status.FAILED - self.current_task.error = error + # Check if the error indicates cancellation + if "canceled" in error.lower() or "cancelled" in error.lower(): + self.current_task.status = FileTranscriptionTask.Status.CANCELED + self.current_task.error = error + else: + self.current_task.status = FileTranscriptionTask.Status.FAILED + self.current_task.error = error self.task_error.emit(self.current_task, error) @pyqtSlot(tuple) diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index aaf56614..49f8ef9a 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -29,7 +29,7 @@ msgstr "https://exemple.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "D’acord" @@ -37,7 +37,7 @@ msgstr "D’acord" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Cancel·lar" @@ -308,7 +308,10 @@ msgid "Download failed" msgstr "Descàrrega fallida" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Error" @@ -436,54 +439,113 @@ msgstr "Obre una transcripció" msgid "Cancel Transcription" msgstr "Cancel·la la transcripció" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Neteja l'historial" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "En progrés" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Completat" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Ha fallat" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Cancel·lat" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "A la cua" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Nom del fitxer / URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Model" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Tasca" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Estat" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Data de finalització" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Data d'addició" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Data de finalització" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Cancel·la la transcripció" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +#, fuzzy +msgid "Rename" +msgstr "Vietnamita" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Cancel·la la transcripció" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Cancel·la la transcripció" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -521,7 +583,7 @@ msgstr "Comprova si hi ha actualitzacions" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "Estàs al dia!" @@ -554,68 +616,68 @@ msgstr "Veure" msgid "Timestamps" msgstr "Marqua de temps" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Exporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Traduir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "Cerca" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/amaga la barra de cerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "Cerca:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "Introduïu el text a cercar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "Coincidència anterior (Maj+Retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "Coincidència següent (retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "Neteja" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "Controls de reproducció:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "Segment de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Activa/desactiva el bucle en fer clic als segments de transcripció" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "Segueix l'àudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -623,44 +685,44 @@ msgstr "" "Activa/desactiva seguint la posició d'àudio actual a la transcripció. Quan " "està activada, es desplaça automàticament al text actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "Desplaça't fins a l'actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "Desplaçar-se fins al text que es parla actualment" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "1 de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr " coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "No s'ha trobat cap coincidència" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr " de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "Clau API necessària" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Introduïu la clau API d'OpenAI a les preferències" @@ -717,48 +779,48 @@ msgstr "Cancel·la la transcripció" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Desa el fitxer" @@ -826,7 +888,7 @@ msgstr "Ajuda" msgid "File" msgstr "Fitxer" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -834,11 +896,11 @@ msgstr "" "Esteu segur que voleu suprimir les transcripcions seleccionades? Aquesta " "acció no es pot desfer." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Selecciona un fitxer d'àudio" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "No s'ha pogut desar la clau OpenAI API a l'anell de claus" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index fe698374..9ebdebcb 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -26,7 +26,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "OK" @@ -34,7 +34,7 @@ msgstr "OK" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Afbryd" @@ -307,7 +307,10 @@ msgid "Download failed" msgstr "Download mislykkedes" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Fejl" @@ -434,54 +437,112 @@ msgstr "Åben transkription" msgid "Cancel Transcription" msgstr "Afbryd transkription" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Ryd historik" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "Arbejder" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Færdig" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Mislykkedes" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Afbrudt" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "Sat i kø" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Filnavn / URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Model" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Opgave" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Status" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Dato for færdiggørelse" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Dato for tilføjelse" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Dato for færdiggørelse" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Afbryd transkription" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Afbryd transkription" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Afbryd transkription" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -519,7 +580,7 @@ msgstr "Tjek for opdateringer" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "Du er opdateret!" @@ -552,111 +613,111 @@ msgstr "Vis" msgid "Timestamps" msgstr "Tidsstempler" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Eksporter" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Oversæt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Behandel størrelse" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "API-nøgle påkrævet" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Indtast venligst OpenAI API-nøgle i indstillinger" @@ -713,48 +774,48 @@ msgstr "Afbryd transkription" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Gem fil" @@ -822,7 +883,7 @@ msgstr "Hjælp" msgid "File" msgstr "Fil" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -830,11 +891,11 @@ msgstr "" "Er du sikker på at du vil slette den valgte transkription? Denne handling " "kan ikke fortrydes." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Vælg audio-fil" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "Kan ikke gemme OpenAI API-nøgle i nøgleringen" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index 1b547455..2e9294f4 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -28,7 +28,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "OK" @@ -36,7 +36,7 @@ msgstr "OK" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Abbrechen" @@ -307,7 +307,10 @@ msgid "Download failed" msgstr "Der Download ist fehlgeschlagen" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Fehler" @@ -434,54 +437,113 @@ msgstr "Transkript öffnen" msgid "Cancel Transcription" msgstr "Transkription abbrechen" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Verlauf löschen" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "Im Gange" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Fertiggestellt" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Fehlgeschlagen" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Abgebrochen" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "In der Warteschlange" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Dateiname/URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Modell" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Aufgabe" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Status" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Datum abgeschlossen" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Datum hinzugefügt" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Datum abgeschlossen" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Transkription abbrechen" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +#, fuzzy +msgid "Rename" +msgstr "Vietnamesisch" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Transkription abbrechen" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Transkription abbrechen" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -519,7 +581,7 @@ msgstr "Nach Updates suchen" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "Sie sind auf dem Laufenden!" @@ -552,111 +614,111 @@ msgstr "Anzeigen" msgid "Timestamps" msgstr "Zeitstempel" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Export" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Übersetzen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Größe ändern" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "API-Schlüssel erforderlich" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Bitte geben Sie den OpenAI-API-Schlüssel in den Einstellungen ein" @@ -713,48 +775,48 @@ msgstr "Transkription abbrechen" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Datei speichern" @@ -822,7 +884,7 @@ msgstr "Hilfe" msgid "File" msgstr "Datei" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -830,11 +892,11 @@ msgstr "" "Sind Sie sicher, dass Sie die ausgewählte(n) Transkription(en) löschen " "möchten? Diese Aktion kann nicht rückgängig gemacht werden." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Audiodatei auswählen" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "" "Der OpenAI-API-Schlüssel kann nicht im Schlüsselbund gespeichert werden" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index 02bac9f4..d16cb5c7 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,7 +29,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "" @@ -37,7 +37,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "" @@ -299,7 +299,10 @@ msgid "Download failed" msgstr "" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "" @@ -424,53 +427,108 @@ msgstr "" msgid "Cancel Transcription" msgstr "" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +msgid "Restart Transcription" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +msgid "Rename Transcription" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +msgid "Failed to restart transcription: {}" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 @@ -507,7 +565,7 @@ msgstr "" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "" @@ -540,111 +598,111 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -700,48 +758,48 @@ msgstr "" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 msgid "Save" msgstr "" @@ -804,17 +862,17 @@ msgstr "" msgid "File" msgstr "" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "" -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index 1c7d3e0c..2166f396 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -29,7 +29,7 @@ msgstr "https://ejemplo.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Ok" @@ -37,7 +37,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Cancelar" @@ -314,7 +314,10 @@ msgid "Download failed" msgstr "Descarga fallida" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Error" @@ -460,57 +463,119 @@ msgid "Cancel Transcription" msgstr "Cancelar transcripción" # automatic translation -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Vaciar historial" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "En Progreso" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Completado" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Fallido" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Cancelado" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "En cola" # automatic translation -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Nombre de archivo / URL" # automatic translation -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Modelo" # automatic translation -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Tarea" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Estado" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Fecha de finalización" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Fecha de adición" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Fecha de finalización" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +# automatic translation +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Cancelar transcripción" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +#, fuzzy +msgid "Rename" +msgstr "Vietnamita" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +# automatic translation +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Cancelar transcripción" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +# automatic translation +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Cancelar transcripción" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" # automatic translation #: buzz/widgets/recording_transcriber_widget.py:83 @@ -555,7 +620,7 @@ msgid "Show logs" msgstr "" # automatic translation -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "¡Estás al día!" @@ -589,70 +654,70 @@ msgstr "Ver" msgid "Timestamps" msgstr "Marcas de tiempo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Traducir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Cambiar el tamaño" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "Buscar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar barra de búsqueda (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "Encontrar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "Introducir texto para encontrar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "Coincidencia anterior (Mayús+Intro)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "Siguiente coincidencia (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "Limpiar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "Controles de reproducción:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "Segmento de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Activar/desactivar la reproducción en bucle al hacer clic en segmentos de la " "transcripción" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "Seguir audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -661,44 +726,44 @@ msgstr "" "transcripción. Cuando está activado, se desplaza automáticamente al texto " "actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "Desplácese hasta Actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "Desplazarse hasta el texto hablado actualmente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "1 de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr " coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "No se encontraron coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr " de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "Clave de API requerida" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Ingrese la clave API de OpenAI en las preferencias" @@ -756,49 +821,49 @@ msgstr "Cancelar transcripción" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" # automatic translation -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Guardar archivo" @@ -873,7 +938,7 @@ msgid "File" msgstr "Archivo" # automatic translation -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -882,11 +947,11 @@ msgstr "" "no se puede deshacer." # automatic translation -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Seleccionar archivo de audio" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "No se puede guardar la clave de la API de OpenAI en el llavero" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index fd231e6a..efb8cb82 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -28,7 +28,7 @@ msgstr "https://esempio.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Ok" @@ -36,7 +36,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Annulla" @@ -308,7 +308,10 @@ msgid "Download failed" msgstr "Download non riuscito" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Errore" @@ -437,54 +440,113 @@ msgstr "Apri trascrizione" msgid "Cancel Transcription" msgstr "Annulla trascrizione" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Elimina la cronologia" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "In corso" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Completato" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Non riuscito" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Annullato" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "In coda" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Nome file / URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Modello" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Compito" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Stato" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Data completata" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Data aggiunta" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Data completata" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Inizio trascrizione..." + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +#, fuzzy +msgid "Rename" +msgstr "Vietnamita" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Annulla trascrizione" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Inizio trascrizione..." + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -522,7 +584,7 @@ msgstr "Controlla gli aggiornamenti" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "Il programma è aggiornato!" @@ -555,69 +617,69 @@ msgstr "Visualizza" msgid "Timestamps" msgstr "Timestamp" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Esporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Tradurre" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Ridimensionare" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "Trova" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/Nascondi barra di ricerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "Trova:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "Inserisci il testo per trovare..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "Corrispondenza precedente (Maiusc+Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "Prossima corrispondenza (Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "Elimina" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "Controlli di riproduzione:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "Ciclo di segmento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Abilita/disabilita il loop quando si fa clic sui segmenti della trascrizione" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "Segui Audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -626,44 +688,44 @@ msgstr "" "trascrizione. Quando abilitato, scorre automaticamente fino al testo " "corrente." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "Scorri fino al Corrente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "Scorrere fino al testo attualmente pronunciato" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "1 di 100+ corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "1 di" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "Nessuna corrispondenza trovata" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr " di oltre 100 corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr " di " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "Chiave API richiesta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Inserisci la chiave API OpenAI nelle preferenze" @@ -720,48 +782,48 @@ msgstr "Inizio trascrizione..." msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Salva file" @@ -829,7 +891,7 @@ msgstr "Aiuto" msgid "File" msgstr "File" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -837,11 +899,11 @@ msgstr "" "Sei certo di voler eliminare le trascrizioni selezionate? Questa azione non " "può essere annullata." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Seleziona file audio" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "Impossibile salvare la chiave API OpenAI nel portachiavi" diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index 2bdda8b2..574c9d6c 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -24,7 +24,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Ok" @@ -32,7 +32,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "キャンセル" @@ -303,7 +303,10 @@ msgid "Download failed" msgstr "ダウンロード失敗" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "エラー" @@ -430,54 +433,112 @@ msgstr "文字起こしを開く" msgid "Cancel Transcription" msgstr "文字起こしをキャンセルする" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "履歴を削除する" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "進行中" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "完了" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "失敗" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "キャンセル済み" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "キュー済み" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "ファイル名 / URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "モデル" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "タスク" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "ステータス" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "完了日" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "追加日" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "完了日" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "文字起こしをキャンセルする" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "文字起こしをキャンセルする" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "文字起こしをキャンセルする" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -515,7 +576,7 @@ msgstr "アップデートを確認する" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "最新の状態です!" @@ -548,111 +609,111 @@ msgstr "表示" msgid "Timestamps" msgstr "タイムスタンプ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "出力" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "翻訳" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "リサイズ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "APIキーが必要" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "設定画面でOpenAI APIキーを入力してください" @@ -710,48 +771,48 @@ msgstr "文字起こしをキャンセルする" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "ファイルを保存" @@ -819,17 +880,17 @@ msgstr "ヘルプ" msgid "File" msgstr "ファイル" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "本当に選択された文字起こしを削除しますか? この操作は元に戻せません。" -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "音声ファイルを選択" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "OpenAI API キーをkeyringに保存できません" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index df528784..600fdf03 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 13:02+0200\n" -"PO-Revision-Date: 2025-11-23 12:58+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"PO-Revision-Date: 2025-12-06 11:34+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -29,7 +29,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Labi" @@ -37,7 +37,7 @@ msgstr "Labi" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Atcelt" @@ -310,7 +310,10 @@ msgid "Download failed" msgstr "Lejupielāde neizdevās" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Kļūda" @@ -439,54 +442,110 @@ msgstr "Atvērt transkriptu" msgid "Cancel Transcription" msgstr "Atcelt atpazīšanu" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Notīrīt vēsturi" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "Apstrādā" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Pabeigts" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Neizdevās" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Atcelts" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "Ierindots" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Fails / URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Modelis" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Uzdevums" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Statuss" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Pabeigts" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Pievienots" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Pabeigts" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "Piezīmes" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "Atjaunot kolonas" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +msgid "Restart Transcription" +msgstr "Sāk atpazīšanu" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "Pārddēvēt" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "Rediģēt piezīmes" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +msgid "Rename Transcription" +msgstr "Pārdēvēt" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "Ievadiet jauno nosaukumu šim atpazīšanas ierakstam:" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "Ievadiet noderīgas piezīmēs par šo ierakstu:" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "Neizdodas sākt" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "Atkārtoti sākt var tikai kļūdainus vai atceltus ierakstus." + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +msgid "Failed to restart transcription: {}" +msgstr "Neizdevās sākt atpazīšanu: {}" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" +"Neizdevās sākt atpazīšanu: modelis nav pieejams un to nevar lejupielādēt." + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "Neizdevās sākt atpazīšanu: Kļūda lietotnē, pārstartējiet." #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -524,7 +583,7 @@ msgstr "Pārbaudīt atjauninājumus" msgid "Show logs" msgstr "Parādīt sistēmas žurnālu" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "Jums ir jaunākā versija!" @@ -557,68 +616,68 @@ msgstr "Skats" msgid "Timestamps" msgstr "Laiks" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Eksportēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Tulkot" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Mainīt garumu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "Noteikt runātājus" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "Meklēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Rādīt/Slēpt meklēšanas joslu (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "Meklēt:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "Ievadiet meklējamo..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "Iepriekšējais rezultāts (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "Nākamais rezultāts (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "Notīrīt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "Atskaņošanas iespējas:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "Atkārtot segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Nosaka vai atkārtot izvēlēto segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "Sekot audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -626,44 +685,44 @@ msgstr "" "Nosaka vai atskaņojot audio iezīmētajam segmentam vajadzētu automātiski " "sekot tam kas tiek atskaņots." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "Pāriet uz tekošo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "Pāriet uz šobrīd atskaņojamo tesktu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "1 no 100+ " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "1 no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr " " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "Nekas nav atrasts" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr " no 100+" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr " no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "API atslēgas kļūda" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Lūdzu ievadiet OpenAI API atslēgu iestatījumos" @@ -719,48 +778,48 @@ msgstr "5/8 Sagatavo transkripcijas" msgid "6/8 Identifying speakers" msgstr "6/8 Nosaka runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "0/0 Kļūda nosakot runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "7/8 Marķē runātāju teikumus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "8/8 Runātāju noteikšana pabeigta" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "1. solis: Runātāju noteikšana" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "Noteikt" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "Gatavs noteikt runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "Audio datne nav atrasta" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "2. solis: Runātāju identifikācija" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "Atskaņot paraugu" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "Apvienot secīgus runātāja teikumus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 msgid "Save" msgstr "Saglabāt" @@ -825,7 +884,7 @@ msgstr "Palīdzība" msgid "File" msgstr "Fails" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -833,11 +892,11 @@ msgstr "" "Vai tiešām vēlaties dzēst izvēlētos transkriptus? Šī ir neatgriezeniska " "darbība." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Izvēlieties audio failu" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī" diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index 75b59ea1..7ae6ad7d 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -31,7 +31,7 @@ msgstr "https://voorbeeld.nl/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Oké" @@ -39,7 +39,7 @@ msgstr "Oké" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Annuleren" @@ -309,7 +309,10 @@ msgid "Download failed" msgstr "Het downloaden is mislukt" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Foutmelding" @@ -436,54 +439,113 @@ msgstr "Transcriptie openen" msgid "Cancel Transcription" msgstr "Transcriptie wissen" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Geschiedenis wissen" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "In behandeling" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Afgerond" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Mislukt" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Afgebroken" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "In wachtrij" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Bestandsnaam/Url" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Model" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Taak" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Status" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Afgerond op" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Toegevoegd op" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Afgerond op" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Transcriptie wissen" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +#, fuzzy +msgid "Rename" +msgstr "Vietnamees" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Transcriptie wissen" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Transcriptie wissen" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -519,7 +581,7 @@ msgstr "Controleren op updates" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "De software is actueel!" @@ -552,111 +614,111 @@ msgstr "Bekijken" msgid "Timestamps" msgstr "Tijdstippen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Exporteren" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Vertalen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Grootte" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "Api-sleutel vereist" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Voer de OpenAI-api-sleutel in in de instellingen" @@ -713,48 +775,48 @@ msgstr "Transcriptie wissen" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Bestand opslaan" @@ -821,7 +883,7 @@ msgstr "Hulp" msgid "File" msgstr "Bestand" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -829,11 +891,11 @@ msgstr "" "Weet u zeker dat u de gekozen transcriptie(s) wilt verwijderen? Deze actie " "is onomkeerbaar." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Kies een audiobestand" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "De OpenAI-api-sleutel kan niet worden bewaard in de sleutelbos" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index 261fcd5b..fb2ab0c0 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -29,7 +29,7 @@ msgstr "https://przyklad.pl/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "" @@ -37,7 +37,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Anuluj" @@ -310,7 +310,10 @@ msgid "Download failed" msgstr "Pobrany" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Błąd" @@ -438,59 +441,117 @@ msgstr "Otwórz transkrypt" msgid "Cancel Transcription" msgstr "Anuluj transkrypcję" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Wyczyść historię" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Ukończono" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Anulowano" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "Kolejka" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 #, fuzzy msgid "File Name / URL" msgstr "Nazwa pliku" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 #, fuzzy msgid "Model" msgstr "Model:" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 #, fuzzy msgid "Task" msgstr "Zadanie:" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Status" -#: buzz/widgets/transcription_tasks_table_widget.py:129 -msgid "Date Added" -msgstr "" - -#: buzz/widgets/transcription_tasks_table_widget.py:140 +#: buzz/widgets/transcription_tasks_table_widget.py:133 #, fuzzy msgid "Date Completed" msgstr "Ukończono" +#: buzz/widgets/transcription_tasks_table_widget.py:145 +msgid "Date Added" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Anuluj transkrypcję" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Anuluj transkrypcję" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Anuluj transkrypcję" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" + #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" msgstr "Nagrywanie na żywo" @@ -527,7 +588,7 @@ msgstr "Sprawdź aktualizacje" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "Posiadasz najnowszą wersję!" @@ -561,111 +622,111 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -722,48 +783,48 @@ msgstr "Anuluj transkrypcję" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Zapisz plik" @@ -831,7 +892,7 @@ msgstr "Pomoc" msgid "File" msgstr "Plik" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -839,11 +900,11 @@ msgstr "" "Czy na pewno chcesz usunąć zaznaczone transkrypcje? Tej operacji nie można " "cofnąć." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Wybierz plik audio" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 39ae4c38..3ec83eb3 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -29,7 +29,7 @@ msgstr "https://exemplo.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Ok" @@ -37,7 +37,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Cancelar" @@ -307,7 +307,10 @@ msgid "Download failed" msgstr "Falha ao baixar" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Erro" @@ -434,54 +437,113 @@ msgstr "Abrir Transcrição" msgid "Cancel Transcription" msgstr "Cancelar Transcrição" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Limpar Histórico" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "Em Progresso" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Concluído" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Falhou" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Cancelado" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "Na fila" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Nome do Arquivo / URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Modelo" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Tarefa" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Status" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Data de Conclusão" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Data de Adição" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Data de Conclusão" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Iniciando transcrição..." + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +#, fuzzy +msgid "Rename" +msgstr "Vietnamita" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Cancelar Transcrição" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Iniciando transcrição..." + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -519,7 +581,7 @@ msgstr "Verificar atualizações" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "Você está atualizado!" @@ -552,68 +614,68 @@ msgstr "Visualizar" msgid "Timestamps" msgstr "Marcações de tempo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Traduzir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "Procurar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar a Barra de Pesquisa" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "Procurar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "Digite o texto a procurar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "Encontro prévio (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "Póximo encontro (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "Limpar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "Controles de Reprodução:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "Segmento de Loop" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Habilitar/desabilitar loop ao clicar em segmentos de transcrição" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "Siga o Áudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -621,44 +683,44 @@ msgstr "" "Ativar/desativar a opção de seguir a posição atual do áudio na transcrição. " "Quando ativado, rola automaticamente para o texto atual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "Rolar para o Atual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "Role até o texto falado no momento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "1 de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr " encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "Nada encontrado" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr " de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "Chave API Necessária" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Insira a chave API OpenAI nas preferências" @@ -715,48 +777,48 @@ msgstr "Iniciando transcrição..." msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Salvar Arquivo" @@ -824,7 +886,7 @@ msgstr "Ajuda" msgid "File" msgstr "Arquivo" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -832,11 +894,11 @@ msgstr "" "Tem certeza que deseja excluir a(s) transcrição(ões) selecionada(s)? Esta " "ação não pode ser desfeita." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Selecionar arquivo de áudio" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "Não foi possível salvar a chave da API OpenAI no cofre de chaves" diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index 2ef57f95..f99cb036 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -26,7 +26,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Гаразд" @@ -34,7 +34,7 @@ msgstr "Гаразд" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "Скасувати" @@ -305,7 +305,10 @@ msgid "Download failed" msgstr "Невдале завантаження" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "Помилка" @@ -432,54 +435,112 @@ msgstr "Відкрити транскрипцію" msgid "Cancel Transcription" msgstr "Скасувати транскрипцію" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "Очистити історію" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "В процесі" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "Завершено" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "Невдача" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "Скасовано" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "У черзі" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 msgid "File Name / URL" msgstr "Назва файлу / посилання" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 msgid "Model" msgstr "Модель" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 msgid "Task" msgstr "Завдання" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "Стан" -#: buzz/widgets/transcription_tasks_table_widget.py:129 +#: buzz/widgets/transcription_tasks_table_widget.py:133 +msgid "Date Completed" +msgstr "Дата завершення" + +#: buzz/widgets/transcription_tasks_table_widget.py:145 msgid "Date Added" msgstr "Дата додавання" -#: buzz/widgets/transcription_tasks_table_widget.py:140 -msgid "Date Completed" -msgstr "Дата завершення" +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "Скасувати транскрипцію" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "Скасувати транскрипцію" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "Скасувати транскрипцію" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" @@ -517,7 +578,7 @@ msgstr "Перевірити оновлення" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "У вас актуальна версія!" @@ -550,111 +611,111 @@ msgstr "Вигляд" msgid "Timestamps" msgstr "Позначки часу" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "Експорт" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "Перекласти" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "Потрібен API-ключ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "Будь ласка, введіть API-ключ OpenAI в налаштуваннях" @@ -711,48 +772,48 @@ msgstr "Скасувати транскрипцію" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "Зберегти файл" @@ -819,18 +880,18 @@ msgstr "Допомога" msgid "File" msgstr "Файл" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "" "Ви впевнені, що хочете видалити вибрані транскрипції? Це незворотна дія." -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "Вибрати аудіофайл" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "Не вдається додати до звʼязки ключів API-ключ OpenAI" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index 5cdb3a7b..ef775c6c 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -29,7 +29,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "Ok" @@ -37,7 +37,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "取消" @@ -313,7 +313,10 @@ msgid "Download failed" msgstr "下载模型失败" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "错误" @@ -441,59 +444,117 @@ msgstr "打开识别结果" msgid "Cancel Transcription" msgstr "取消识别" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "清除历史纪录" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "运行中" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "完成" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "失败" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "取消" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "队列中" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 #, fuzzy msgid "File Name / URL" msgstr "文件名称/URL" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 #, fuzzy msgid "Model" msgstr "模型" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 #, fuzzy msgid "Task" msgstr "任务" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "状态" -#: buzz/widgets/transcription_tasks_table_widget.py:129 -msgid "Date Added" -msgstr "添加日期" - -#: buzz/widgets/transcription_tasks_table_widget.py:140 +#: buzz/widgets/transcription_tasks_table_widget.py:133 #, fuzzy msgid "Date Completed" msgstr "完成时间" +#: buzz/widgets/transcription_tasks_table_widget.py:145 +msgid "Date Added" +msgstr "添加日期" + +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "取消识别" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "取消识别" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "取消识别" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" + #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" msgstr "实时录制" @@ -528,7 +589,7 @@ msgstr "检查更新" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "已经是最新版本" @@ -562,111 +623,111 @@ msgstr "查看" msgid "Timestamps" msgstr "时间戳" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "导出" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "翻译" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "调整大小" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "需要API Key" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "请在偏好设置中输入OpenAI API Key" @@ -724,48 +785,48 @@ msgstr "取消识别" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "保存文件" @@ -833,17 +894,17 @@ msgstr "帮助" msgid "File" msgstr "文件" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "您确定要删除所选录制吗?此操作无法撤消。" -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "选择音频文件" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "无法将OpenAI API密钥保存到密钥串" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index fd0fe400..62e33959 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-23 12:55+0200\n" +"POT-Creation-Date: 2025-12-06 11:29+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -29,7 +29,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:238 +#: buzz/widgets/main_window.py:239 msgid "Ok" msgstr "" @@ -37,7 +37,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:240 msgid "Cancel" msgstr "取消" @@ -308,7 +308,10 @@ msgid "Download failed" msgstr "下載模型" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/main_window.py:295 buzz/model_loader.py:539 +#: buzz/widgets/transcription_tasks_table_widget.py:665 +#: buzz/widgets/transcription_tasks_table_widget.py:735 +#: buzz/widgets/transcription_tasks_table_widget.py:766 +#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 #: buzz/model_loader.py:553 msgid "Error" msgstr "" @@ -436,59 +439,117 @@ msgstr "打開轉換結果" msgid "Cancel Transcription" msgstr "取消錄製" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:227 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 #: buzz/settings/shortcut.py:36 msgid "Clear History" msgstr "清除歷史紀錄" -#: buzz/widgets/transcription_tasks_table_widget.py:69 +#: buzz/widgets/transcription_tasks_table_widget.py:71 msgid "In Progress" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:72 +#: buzz/widgets/transcription_tasks_table_widget.py:74 msgid "Completed" msgstr "完成" -#: buzz/widgets/transcription_tasks_table_widget.py:79 +#: buzz/widgets/transcription_tasks_table_widget.py:81 msgid "Failed" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:82 +#: buzz/widgets/transcription_tasks_table_widget.py:84 msgid "Canceled" msgstr "取消" -#: buzz/widgets/transcription_tasks_table_widget.py:84 +#: buzz/widgets/transcription_tasks_table_widget.py:86 msgid "Queued" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:91 +#: buzz/widgets/transcription_tasks_table_widget.py:93 #, fuzzy msgid "File Name / URL" msgstr "檔案名稱" -#: buzz/widgets/transcription_tasks_table_widget.py:103 +#: buzz/widgets/transcription_tasks_table_widget.py:106 #, fuzzy msgid "Model" msgstr "模型:" -#: buzz/widgets/transcription_tasks_table_widget.py:112 +#: buzz/widgets/transcription_tasks_table_widget.py:115 #, fuzzy msgid "Task" msgstr "任務:" -#: buzz/widgets/transcription_tasks_table_widget.py:121 +#: buzz/widgets/transcription_tasks_table_widget.py:124 msgid "Status" msgstr "狀態" -#: buzz/widgets/transcription_tasks_table_widget.py:129 -msgid "Date Added" -msgstr "" - -#: buzz/widgets/transcription_tasks_table_widget.py:140 +#: buzz/widgets/transcription_tasks_table_widget.py:133 #, fuzzy msgid "Date Completed" msgstr "完成" +#: buzz/widgets/transcription_tasks_table_widget.py:145 +msgid "Date Added" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:156 +#: buzz/widgets/transcription_tasks_table_widget.py:624 +msgid "Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:174 +msgid "Reset Column Order" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:301 +#, fuzzy +msgid "Restart Transcription" +msgstr "取消錄製" + +#: buzz/widgets/transcription_tasks_table_widget.py:305 +msgid "Rename" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:308 +msgid "Add/Edit Notes" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:597 +#, fuzzy +msgid "Rename Transcription" +msgstr "取消錄製" + +#: buzz/widgets/transcription_tasks_table_widget.py:598 +msgid "Enter new name:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:625 +msgid "Enter some relevant notes for this transcription:" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:652 +msgid "Cannot Restart" +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:653 +msgid "Only failed or canceled transcriptions can be restarted." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:666 +#, fuzzy +msgid "Failed to restart transcription: {}" +msgstr "取消錄製" + +#: buzz/widgets/transcription_tasks_table_widget.py:736 +msgid "" +"Could not restart transcription: model not available and could not be " +"downloaded." +msgstr "" + +#: buzz/widgets/transcription_tasks_table_widget.py:767 +msgid "Could not restart transcription: transcriber worker not found." +msgstr "" + #: buzz/widgets/recording_transcriber_widget.py:83 msgid "Live Recording" msgstr "現場錄製" @@ -523,7 +584,7 @@ msgstr "檢查更新" msgid "Show logs" msgstr "" -#: buzz/widgets/about_dialog.py:118 +#: buzz/widgets/about_dialog.py:119 msgid "You're up to date!" msgstr "你是最新的!" @@ -557,111 +618,111 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:215 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:234 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:257 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:269 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:337 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:343 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:356 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:364 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 msgid "Next match (Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:372 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:399 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:404 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:406 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:412 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:414 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:463 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:785 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:787 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:792 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:851 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:853 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1208 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1209 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -718,48 +779,48 @@ msgstr "取消錄製" msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:160 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:207 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:243 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:255 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:265 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:267 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:283 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:391 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:318 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 #, fuzzy msgid "Save" msgstr "檔案" @@ -827,17 +888,17 @@ msgstr "幫助" msgid "File" msgstr "檔案" -#: buzz/widgets/main_window.py:231 +#: buzz/widgets/main_window.py:232 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "您確定要刪除所選錄製嗎?此操作無法撤消。" -#: buzz/widgets/main_window.py:259 +#: buzz/widgets/main_window.py:260 msgid "Select audio file" msgstr "選擇聲音檔案" -#: buzz/widgets/main_window.py:295 +#: buzz/widgets/main_window.py:296 msgid "Unable to save OpenAI API key to keyring" msgstr "" diff --git a/buzz/schema.sql b/buzz/schema.sql index ea603128..a6a11bad 100644 --- a/buzz/schema.sql +++ b/buzz/schema.sql @@ -17,7 +17,9 @@ CREATE TABLE transcription ( whisper_model_size TEXT, hugging_face_model_id TEXT, word_level_timings BOOLEAN DEFAULT FALSE, - extract_speech BOOLEAN DEFAULT FALSE + extract_speech BOOLEAN DEFAULT FALSE, + name TEXT, + notes TEXT ); CREATE TABLE transcription_segment ( diff --git a/buzz/settings/settings.py b/buzz/settings/settings.py index 3d6c46ac..288b0c76 100644 --- a/buzz/settings/settings.py +++ b/buzz/settings/settings.py @@ -53,6 +53,12 @@ class Settings: TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY = ( "transcription-tasks-table/column-visibility" ) + TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER = ( + "transcription-tasks-table/column-order" + ) + TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS = ( + "transcription-tasks-table/column-widths" + ) MAIN_WINDOW = "main-window" TRANSCRIPTION_VIEWER = "transcription-viewer" diff --git a/buzz/transcriber/whisper_file_transcriber.py b/buzz/transcriber/whisper_file_transcriber.py index 08a20426..8faf016b 100644 --- a/buzz/transcriber/whisper_file_transcriber.py +++ b/buzz/transcriber/whisper_file_transcriber.py @@ -72,9 +72,11 @@ class WhisperFileTranscriber(FileTranscriber): self.read_line_thread = Thread(target=self.read_line, args=(self.recv_pipe,)) self.read_line_thread.start() - self.current_process.join() + # Only join the process if it was actually started + if self.started_process: + self.current_process.join() - if self.current_process.exitcode != 0: + if self.started_process and self.current_process.exitcode != 0: self.send_pipe.close() # Join read_line_thread with timeout to prevent hanging @@ -94,7 +96,14 @@ class WhisperFileTranscriber(FileTranscriber): ) if self.current_process.exitcode != 0: - raise Exception("Unknown error") + # Check if the process was terminated (likely due to cancellation) + # Exit codes 124-128 are often used for termination signals + if self.current_process.exitcode in [124, 125, 126, 127, 128, 130, 137, 143]: + # Process was likely terminated, treat as cancellation + logging.debug("Whisper process was terminated (exit code: %s), treating as cancellation", self.current_process.exitcode) + raise Exception("Transcription was canceled") + else: + raise Exception("Unknown error") return self.segments diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 306bc3f8..88252032 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -110,6 +110,7 @@ class MainWindow(QMainWindow): self.setMenuBar(self.menu_bar) self.table_widget = TranscriptionTasksTableWidget(self) + self.table_widget.transcription_service = self.transcription_service self.table_widget.doubleClicked.connect(self.on_table_double_clicked) self.table_widget.return_clicked.connect(self.open_transcript_viewer) self.table_widget.selectionModel().selectionChanged.connect( diff --git a/buzz/widgets/transcription_tasks_table_widget.py b/buzz/widgets/transcription_tasks_table_widget.py index 24f5c7f2..0d79b35b 100644 --- a/buzz/widgets/transcription_tasks_table_widget.py +++ b/buzz/widgets/transcription_tasks_table_widget.py @@ -50,6 +50,8 @@ class Column(enum.Enum): HUGGING_FACE_MODEL_ID = 16 WORD_LEVEL_TIMINGS = 17 EXTRACT_SPEECH = 18 + NAME = 19 + NOTES = 20 @dataclass @@ -92,9 +94,10 @@ column_definitions = [ column=Column.FILE, width=400, delegate=RecordDelegate( - text_getter=lambda record: record.value("url") - if record.value("url") != "" - else os.path.basename(record.value("file")) + text_getter=lambda record: record.value("name") or ( + record.value("url") if record.value("url") != "" + else os.path.basename(record.value("file")) + ) ), hidden_toggleable=False, ), @@ -122,19 +125,9 @@ column_definitions = [ column=Column.STATUS, width=180, delegate=RecordDelegate(text_getter=format_record_status_text), - hidden_toggleable=False, - ), - ColDef( - id="date_added", - header=_("Date Added"), - column=Column.TIME_QUEUED, - width=180, - delegate=RecordDelegate( - text_getter=lambda record: datetime.fromisoformat( - record.value("time_queued") - ).strftime("%Y-%m-%d %H:%M:%S") - ), + hidden_toggleable=True, ), + ColDef( id="date_completed", header=_("Date Completed"), @@ -147,6 +140,26 @@ column_definitions = [ if record.value("time_ended") != "" else "" ), + ), ColDef( + id="date_added", + header=_("Date Added"), + column=Column.TIME_QUEUED, + width=180, + delegate=RecordDelegate( + text_getter=lambda record: datetime.fromisoformat( + record.value("time_queued") + ).strftime("%Y-%m-%d %H:%M:%S") + ), + ), + ColDef( + id="notes", + header=_("Notes"), + column=Column.NOTES, + width=300, + delegate=RecordDelegate( + text_getter=lambda record: record.value("notes") or "" + ), + hidden_toggleable=True, ), ] @@ -156,28 +169,71 @@ class TranscriptionTasksTableHeaderView(QHeaderView): def contextMenuEvent(self, event): menu = QMenu(self) + + # Add reset column order option + menu.addAction(_("Reset Column Order")).triggered.connect(self.parent().reset_column_order) + menu.addSeparator() + + # Add column visibility toggles for definition in column_definitions: - if not definition.hidden_toggleable: - continue - action = menu.addAction(definition.header) - action.setCheckable(True) - action.setChecked(not self.isSectionHidden(definition.column.value)) - action.toggled.connect( - lambda checked, column_index=definition.column.value: self.on_column_checked( - column_index, checked + if definition.hidden_toggleable: + action = menu.addAction(definition.header) + action.setCheckable(True) + action.setChecked(not self.parent().isColumnHidden(definition.column.value)) + action.toggled.connect( + lambda checked, column_index=definition.column.value: self.on_column_checked( + column_index, checked + ) ) - ) menu.exec(event.globalPos()) def on_column_checked(self, column_index: int, checked: bool): - self.setSectionHidden(column_index, not checked) + # Find the column definition for this index + column_def = None + for definition in column_definitions: + if definition.column.value == column_index: + column_def = definition + break + + # If we're hiding the column, save its current width first + if not checked and not self.parent().isColumnHidden(column_index): + current_width = self.parent().columnWidth(column_index) + if current_width > 0: # Only save if there's a meaningful width + self.parent().settings.begin_group(self.parent().settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS) + self.parent().settings.settings.setValue(column_def.id, current_width) + self.parent().settings.end_group() + + # Update the visibility state on the table view (not header view) + self.parent().setColumnHidden(column_index, not checked) + + # Save current column order before any reloading + self.parent().save_column_order() + + # Save both visibility and widths after the change self.parent().save_column_visibility() + self.parent().save_column_widths() + + # Ensure settings are synchronized + self.parent().settings.settings.sync() + + # Force a complete refresh of the table + self.parent().viewport().update() + self.parent().repaint() + self.parent().horizontalHeader().update() + self.parent().updateGeometry() + self.parent().adjustSize() + + # Force a model refresh to ensure the view is updated + self.parent().model().layoutChanged.emit() + + self.parent().reload_column_order_from_settings() class TranscriptionTasksTableWidget(QTableView): return_clicked = pyqtSignal() def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) + self.transcription_service = None self.setHorizontalHeader(TranscriptionTasksTableHeaderView(Qt.Orientation.Horizontal, self)) @@ -193,57 +249,66 @@ class TranscriptionTasksTableWidget(QTableView): self.settings = Settings() - self.settings.begin_group( - Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY - ) + # Set up column headers and delegates for definition in column_definitions: self.model().setHeaderData( definition.column.value, Qt.Orientation.Horizontal, definition.header, ) - - visible = True - if definition.hidden_toggleable: - visible = self.settings.settings.value(definition.id, "true") in {"true", "True", True} - - self.setColumnHidden(definition.column.value, not visible) - if definition.width is not None: - self.setColumnWidth(definition.column.value, definition.width) if definition.delegate is not None: self.setItemDelegateForColumn( definition.column.value, definition.delegate ) - self.settings.end_group() + + # Load column visibility + self.load_column_visibility() self.model().select() self.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.verticalHeader().hide() self.setAlternatingRowColors(True) + + # Enable column sorting and moving + self.setSortingEnabled(True) + self.horizontalHeader().setSectionsMovable(True) + self.horizontalHeader().setSectionsClickable(True) + self.horizontalHeader().setSortIndicatorShown(True) - # Show date added before date completed - self.horizontalHeader().swapSections(11, 12) + # Connect signals for column resize and move + self.horizontalHeader().sectionResized.connect(self.on_column_resized) + self.horizontalHeader().sectionMoved.connect(self.on_column_moved) + + # Load saved column order and widths + self.load_column_order() + self.load_column_widths() + + + # Reload column visibility after all reordering is complete + self.load_column_visibility() def contextMenuEvent(self, event): menu = QMenu(self) - for definition in column_definitions: - if not definition.hidden_toggleable: - continue - action = menu.addAction(definition.header) - action.setCheckable(True) - action.setChecked(not self.isColumnHidden(definition.column.value)) - action.toggled.connect( - lambda checked, - column_index=definition.column.value: self.on_column_checked( - column_index, checked - ) - ) - menu.exec(event.globalPos()) + + # Add transcription actions if a row is selected + selected_rows = self.selectionModel().selectedRows() + if selected_rows: + transcription = self.transcription(selected_rows[0]) - def on_column_checked(self, column_index: int, checked: bool): - self.setColumnHidden(column_index, not checked) - self.save_column_visibility() + # Add restart/continue action for failed/canceled tasks + if transcription.status in ["failed", "canceled"]: + restart_action = menu.addAction(_("Restart Transcription")) + restart_action.triggered.connect(self.on_restart_transcription_action) + menu.addSeparator() + + rename_action = menu.addAction(_("Rename")) + rename_action.triggered.connect(self.on_rename_action) + + notes_action = menu.addAction(_("Add/Edit Notes")) + notes_action.triggered.connect(self.on_notes_action) + + menu.exec(event.globalPos()) def save_column_visibility(self): self.settings.begin_group( @@ -255,6 +320,194 @@ class TranscriptionTasksTableWidget(QTableView): ) self.settings.end_group() + def on_column_resized(self, logical_index: int, old_size: int, new_size: int): + """Handle column resize events""" + self.save_column_widths() + + def on_column_moved(self, logical_index: int, old_visual_index: int, new_visual_index: int): + """Handle column move events""" + self.save_column_order() + # Refresh visibility after column move to ensure it's maintained + self.load_column_visibility() + + def on_double_click(self, index: QModelIndex): + """Handle double-click events - trigger notes edit for notes column""" + if index.column() == Column.NOTES.value: + self.on_notes_action() + + def save_column_widths(self): + """Save current column widths to settings""" + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS) + for definition in column_definitions: + # Only save width if column is visible and has a meaningful width + if not self.isColumnHidden(definition.column.value): + width = self.columnWidth(definition.column.value) + if width > 0: # Only save if there's a meaningful width + self.settings.settings.setValue(definition.id, width) + self.settings.end_group() + + def save_column_order(self): + """Save current column order to settings""" + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER) + header = self.horizontalHeader() + for visual_index in range(header.count()): + logical_index = header.logicalIndex(visual_index) + # Find the column definition for this logical index + for definition in column_definitions: + if definition.column.value == logical_index: + self.settings.settings.setValue(definition.id, visual_index) + break + self.settings.end_group() + + def load_column_widths(self): + """Load saved column widths from settings""" + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS) + for definition in column_definitions: + if definition.width is not None: # Only load if column has a default width + saved_width = self.settings.settings.value(definition.id, definition.width) + if saved_width is not None: + self.setColumnWidth(definition.column.value, int(saved_width)) + self.settings.end_group() + + def load_column_visibility(self): + """Load saved column visibility from settings""" + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY) + for definition in column_definitions: + visible = True + if definition.hidden_toggleable: + value = self.settings.settings.value(definition.id, "true") + visible = value in {"true", "True", True} + + self.setColumnHidden(definition.column.value, not visible) + self.settings.end_group() + + # Force a refresh of the table layout + self.horizontalHeader().update() + self.viewport().update() + self.updateGeometry() + + def load_column_order(self): + """Load saved column order from settings""" + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER) + + # Create a mapping of column IDs to their saved visual positions + column_positions = {} + for definition in column_definitions: + saved_position = self.settings.settings.value(definition.id) + if saved_position is not None: + column_positions[definition.column.value] = int(saved_position) + + self.settings.end_group() + + # Apply the saved order + if column_positions: + header = self.horizontalHeader() + for logical_index, visual_position in column_positions.items(): + if 0 <= visual_position < header.count(): + header.moveSection(header.visualIndex(logical_index), visual_position) + + def reset_column_order(self): + """Reset column order to default""" + + # Reset column widths to defaults + for definition in column_definitions: + if definition.width is not None: + self.setColumnWidth(definition.column.value, definition.width) + + # Show all columns + for definition in column_definitions: + self.setColumnHidden(definition.column.value, False) + + # Restore default column order + header = self.horizontalHeader() + # Move each section to its default position in order + # To avoid index shifting, move from left to right + for target_visual_index, definition in enumerate(column_definitions): + logical_index = definition.column.value + current_visual_index = header.visualIndex(logical_index) + if current_visual_index != target_visual_index: + header.moveSection(current_visual_index, target_visual_index) + + # Clear saved settings + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER) + self.settings.settings.remove("") + self.settings.end_group() + + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS) + self.settings.settings.remove("") + self.settings.end_group() + + # Save the reset state for both visibility and widths + self.save_column_visibility() + self.save_column_widths() + + # Force a refresh of the table layout + self.horizontalHeader().update() + self.viewport().update() + self.updateGeometry() + + def reload_column_order_from_settings(self): + """Reload column order, width, and visibility from settings""" + + # --- Load column visibility --- + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY) + visibility_settings = {} + for definition in column_definitions: + vis = self.settings.settings.value(definition.id) + if vis is not None: + visibility_settings[definition.id] = str(vis).lower() not in ("0", "false", "no") + self.settings.end_group() + + # --- Load column widths --- + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS) + width_settings = {} + for definition in column_definitions: + width = self.settings.settings.value(definition.id) + if width is not None: + try: + width_settings[definition.id] = int(width) + except Exception: + pass + self.settings.end_group() + + # --- Load column order --- + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER) + order_settings = {} + for definition in column_definitions: + pos = self.settings.settings.value(definition.id) + if pos is not None: + try: + order_settings[definition.column.value] = int(pos) + except Exception: + pass + self.settings.end_group() + + # --- Apply visibility, widths, and order --- + header = self.horizontalHeader() + + # First, set visibility and width for each column + for definition in column_definitions: + is_visible = visibility_settings.get(definition.id, True) + width = width_settings.get(definition.id, definition.width) + self.setColumnHidden(definition.column.value, not is_visible) + if width is not None: + self.setColumnWidth(definition.column.value, max(width, 100)) + + # Then, apply column order + # Build a list of (logical_index, visual_position) for ALL columns (including hidden ones) + all_columns = [ + (definition.column.value, order_settings.get(definition.column.value, idx)) + for idx, definition in enumerate(column_definitions) + ] + # Sort by saved visual position + all_columns.sort(key=lambda x: x[1]) + + # Move sections to match the saved order + for target_visual, (logical_index, _) in enumerate(all_columns): + current_visual = header.visualIndex(logical_index) + if current_visual != target_visual: + header.moveSection(current_visual, target_visual) + def copy_selected_fields(self): selected_text = "" for row in self.selectionModel().selectedRows(): @@ -267,6 +520,17 @@ class TranscriptionTasksTableWidget(QTableView): selected_text = selected_text.rstrip("\n") QApplication.clipboard().setText(selected_text) + def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent) -> None: + """Override double-click to prevent default behavior when clicking on notes column""" + index = self.indexAt(event.pos()) + if index.isValid() and index.column() == Column.NOTES.value: + # Handle our custom double-click action without triggering default behavior + self.on_double_click(index) + event.accept() + else: + # For other columns, use default behavior + super().mouseDoubleClickEvent(event) + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if event.key() == Qt.Key.Key_Return: self.return_clicked.emit() @@ -309,4 +573,196 @@ class TranscriptionTasksTableWidget(QTableView): result = f"{mm}m {result}" if hh == 0: return result - return f"{hh}h {result}" \ No newline at end of file + return f"{hh}h {result}" + + def on_rename_action(self): + selected_rows = self.selectionModel().selectedRows() + if not selected_rows: + return + + # Get the first selected transcription + transcription = self.transcription(selected_rows[0]) + + # Get current name or fallback to file name + current_name = transcription.name or ( + transcription.url if transcription.url + else os.path.basename(transcription.file) if transcription.file + else "" + ) + + # Show input dialog + from PyQt6.QtWidgets import QInputDialog + new_name, ok = QInputDialog.getText( + self, + _("Rename Transcription"), + _("Enter new name:"), + text=current_name + ) + + if ok and new_name.strip(): + # Update the transcription name + from uuid import UUID + self.transcription_service.update_transcription_name( + UUID(transcription.id), + new_name.strip() + ) + self.refresh_all() + + def on_notes_action(self): + selected_rows = self.selectionModel().selectedRows() + if not selected_rows: + return + + # Get the first selected transcription + transcription = self.transcription(selected_rows[0]) + + # Show input dialog for notes + from PyQt6.QtWidgets import QInputDialog + current_notes = transcription.notes or "" + new_notes, ok = QInputDialog.getMultiLineText( + self, + _("Notes"), + _("Enter some relevant notes for this transcription:"), + text=current_notes + ) + + if ok: + # Update the transcription notes + from uuid import UUID + self.transcription_service.update_transcription_notes( + UUID(transcription.id), + new_notes + ) + self.refresh_all() + + def on_restart_transcription_action(self): + """Restart transcription for failed or canceled tasks""" + selected_rows = self.selectionModel().selectedRows() + if not selected_rows: + return + + # Get the first selected transcription + transcription = self.transcription(selected_rows[0]) + + # Check if the task can be restarted + if transcription.status not in ["failed", "canceled"]: + from PyQt6.QtWidgets import QMessageBox + QMessageBox.information( + self, + _("Cannot Restart"), + _("Only failed or canceled transcriptions can be restarted.") + ) + return + + try: + self.transcription_service.reset_transcription_for_restart(UUID(transcription.id)) + self._restart_transcription_task(transcription) + self.refresh_all() + except Exception as e: + from PyQt6.QtWidgets import QMessageBox + QMessageBox.warning( + self, + _("Error"), + _("Failed to restart transcription: {}").format(str(e)) + ) + + def _restart_transcription_task(self, transcription): + """Create a new FileTranscriptionTask and add it to the queue worker""" + from buzz.transcriber.transcriber import ( + FileTranscriptionTask, + TranscriptionOptions, + FileTranscriptionOptions, + Task + ) + from buzz.model_loader import TranscriptionModel, ModelType + from buzz.transcriber.transcriber import OutputFormat + + # Recreate the transcription options from the database record + from buzz.model_loader import WhisperModelSize + + # Convert string whisper_model_size to enum if it exists + whisper_model_size = None + if transcription.whisper_model_size: + try: + whisper_model_size = WhisperModelSize(transcription.whisper_model_size) + except ValueError: + # If the stored value is invalid, use a default + whisper_model_size = WhisperModelSize.TINY + + transcription_options = TranscriptionOptions( + language=transcription.language if transcription.language else None, + task=Task(transcription.task) if transcription.task else Task.TRANSCRIBE, + model=TranscriptionModel( + model_type=ModelType(transcription.model_type) if transcription.model_type else ModelType.WHISPER, + whisper_model_size=whisper_model_size, + hugging_face_model_id=transcription.hugging_face_model_id + ), + word_level_timings=transcription.word_level_timings == "1" if transcription.word_level_timings else False, + extract_speech=transcription.extract_speech == "1" if transcription.extract_speech else False, + initial_prompt="", # Not stored in database, use default + openai_access_token="", # Not stored in database, use default + enable_llm_translation=False, # Not stored in database, use default + llm_prompt="", # Not stored in database, use default + llm_model="" # Not stored in database, use default + ) + + # Recreate the file transcription options + output_formats = set() + if transcription.export_formats: + for format_str in transcription.export_formats.split(','): + try: + output_formats.add(OutputFormat(format_str.strip())) + except ValueError: + pass # Skip invalid formats + + file_transcription_options = FileTranscriptionOptions( + url=transcription.url if transcription.url else None, + output_formats=output_formats + ) + + # Get the model path from the transcription options + model_path = transcription_options.model.get_local_model_path() + if model_path is None: + # If model is not available locally, we need to download it + from buzz.model_loader import ModelDownloader + ModelDownloader(model=transcription_options.model).run() + model_path = transcription_options.model.get_local_model_path() + + if model_path is None: + from PyQt6.QtWidgets import QMessageBox + QMessageBox.warning( + self, + _("Error"), + _("Could not restart transcription: model not available and could not be downloaded.") + ) + return + + # Create the new task + task = FileTranscriptionTask( + transcription_options=transcription_options, + file_transcription_options=file_transcription_options, + model_path=model_path, + file_path=transcription.file if transcription.file else None, + url=transcription.url if transcription.url else None, + output_directory=transcription.output_folder if transcription.output_folder else None, + source=FileTranscriptionTask.Source(transcription.source) if transcription.source else FileTranscriptionTask.Source.FILE_IMPORT, + uid=UUID(transcription.id) + ) + + # Add the task to the queue worker + # We need to access the main window's transcriber worker + # This is a bit of a hack, but it's the cleanest way given the current architecture + main_window = self.parent() + while main_window and not hasattr(main_window, 'transcriber_worker'): + main_window = main_window.parent() + + if main_window and hasattr(main_window, 'transcriber_worker'): + main_window.transcriber_worker.add_task(task) + else: + # Fallback: show error if we can't find the transcriber worker + from PyQt6.QtWidgets import QMessageBox + QMessageBox.warning( + self, + _("Error"), + _("Could not restart transcription: transcriber worker not found.") + ) \ No newline at end of file diff --git a/tests/db/dao/transcription_dao_test.py b/tests/db/dao/transcription_dao_test.py new file mode 100644 index 00000000..6a0aa1fc --- /dev/null +++ b/tests/db/dao/transcription_dao_test.py @@ -0,0 +1,240 @@ +import pytest +from unittest.mock import Mock, patch +from uuid import UUID, uuid4 +from PyQt6.QtSql import QSqlDatabase, QSqlQuery + +from buzz.db.dao.transcription_dao import TranscriptionDAO +from buzz.db.entity.transcription import Transcription + + +@pytest.fixture +def db(): + """Create an in-memory SQLite database for testing""" + db = QSqlDatabase.addDatabase("QSQLITE") + db.setDatabaseName(":memory:") + assert db.open() + + # Create the transcription table with the new schema + query = QSqlQuery(db) + query.exec(""" + CREATE TABLE transcription ( + id TEXT PRIMARY KEY, + error_message TEXT, + export_formats TEXT, + file TEXT, + output_folder TEXT, + progress DOUBLE PRECISION DEFAULT 0.0, + language TEXT, + model_type TEXT, + source TEXT, + status TEXT, + task TEXT, + time_ended TIMESTAMP, + time_queued TIMESTAMP NOT NULL, + time_started TIMESTAMP, + url TEXT, + whisper_model_size TEXT, + hugging_face_model_id TEXT, + word_level_timings BOOLEAN DEFAULT FALSE, + extract_speech BOOLEAN DEFAULT FALSE, + name TEXT, + notes TEXT + ) + """) + + yield db + db.close() + + +@pytest.fixture +def transcription_dao(db): + """Create a TranscriptionDAO instance for testing""" + return TranscriptionDAO(db) + + +@pytest.fixture +def sample_transcription(): + """Create a sample transcription for testing""" + return Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Transcription", + notes="This is a test transcription" + ) + + +class TestTranscriptionDAO: + def test_insert_transcription_with_name_and_notes(self, transcription_dao, sample_transcription): + """Test inserting a transcription with name and notes fields""" + transcription_dao.insert(sample_transcription) + + # Verify the transcription was inserted + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT * FROM transcription WHERE id = :id") + query.bindValue(":id", sample_transcription.id) + assert query.exec() + assert query.next() + + # Check that name and notes were stored + assert query.value("name") == "Test Transcription" + assert query.value("notes") == "This is a test transcription" + + def test_update_transcription_name(self, transcription_dao, sample_transcription): + """Test updating transcription name""" + # Insert the transcription first + transcription_dao.insert(sample_transcription) + + # Update the name + new_name = "Updated Transcription Name" + transcription_dao.update_transcription_name(UUID(sample_transcription.id), new_name) + + # Verify the name was updated + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT name FROM transcription WHERE id = :id") + query.bindValue(":id", sample_transcription.id) + assert query.exec() + assert query.next() + assert query.value("name") == new_name + + def test_update_transcription_notes(self, transcription_dao, sample_transcription): + """Test updating transcription notes""" + # Insert the transcription first + transcription_dao.insert(sample_transcription) + + # Update the notes + new_notes = "Updated transcription notes with more details" + transcription_dao.update_transcription_notes(UUID(sample_transcription.id), new_notes) + + # Verify the notes were updated + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT notes FROM transcription WHERE id = :id") + query.bindValue(":id", sample_transcription.id) + assert query.exec() + assert query.next() + assert query.value("notes") == new_notes + + def test_update_transcription_name_nonexistent_id(self, transcription_dao): + """Test updating name for non-existent transcription ID""" + nonexistent_id = uuid4() + + # This should raise an exception + with pytest.raises(Exception): + transcription_dao.update_transcription_name(nonexistent_id, "New Name") + + def test_update_transcription_notes_nonexistent_id(self, transcription_dao): + """Test updating notes for non-existent transcription ID""" + nonexistent_id = uuid4() + + # This should raise an exception + with pytest.raises(Exception): + transcription_dao.update_transcription_notes(nonexistent_id, "New Notes") + + def test_update_transcription_name_empty_string(self, transcription_dao, sample_transcription): + """Test updating transcription name to empty string""" + # Insert the transcription first + transcription_dao.insert(sample_transcription) + + # Update the name to empty string + transcription_dao.update_transcription_name(UUID(sample_transcription.id), "") + + # Verify the name was updated to empty string + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT name FROM transcription WHERE id = :id") + query.bindValue(":id", sample_transcription.id) + assert query.exec() + assert query.next() + assert query.value("name") == "" + + def test_update_transcription_notes_empty_string(self, transcription_dao, sample_transcription): + """Test updating transcription notes to empty string""" + # Insert the transcription first + transcription_dao.insert(sample_transcription) + + # Update the notes to empty string + transcription_dao.update_transcription_notes(UUID(sample_transcription.id), "") + + # Verify the notes were updated to empty string + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT notes FROM transcription WHERE id = :id") + query.bindValue(":id", sample_transcription.id) + assert query.exec() + assert query.next() + assert query.value("notes") == "" + + def test_update_transcription_name_with_none(self, transcription_dao, sample_transcription): + """Test updating transcription name to None (should be stored as NULL)""" + # Insert the transcription first + transcription_dao.insert(sample_transcription) + + # Update the name to None + transcription_dao.update_transcription_name(UUID(sample_transcription.id), None) + + # Verify the name was updated to None (NULL in database) + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT name FROM transcription WHERE id = :id") + query.bindValue(":id", sample_transcription.id) + assert query.exec() + assert query.next() + # In SQLite, None values are returned as empty strings + assert query.value("name") == "" + + def test_update_transcription_notes_with_none(self, transcription_dao, sample_transcription): + """Test updating transcription notes to None (should be stored as NULL)""" + # Insert the transcription first + transcription_dao.insert(sample_transcription) + + # Update the notes to None + transcription_dao.update_transcription_notes(UUID(sample_transcription.id), None) + + # Verify the notes were updated to None (NULL in database) + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT notes FROM transcription WHERE id = :id") + query.bindValue(":id", sample_transcription.id) + assert query.exec() + assert query.next() + # In SQLite, None values are returned as empty strings + assert query.value("notes") == "" + + def test_insert_transcription_without_name_and_notes(self, transcription_dao): + """Test inserting a transcription without name and notes (should be NULL)""" + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER" + # name and notes not provided + ) + + transcription_dao.insert(transcription) + + # Verify the transcription was inserted with NULL name and notes + query = QSqlQuery(transcription_dao.db) + query.prepare("SELECT name, notes FROM transcription WHERE id = :id") + query.bindValue(":id", transcription.id) + assert query.exec() + assert query.next() + + # In SQLite, NULL values are returned as empty strings + assert query.value("name") == "" + assert query.value("notes") == "" + + def test_database_error_handling(self, transcription_dao): + """Test that database errors are properly handled""" + # Mock a database error by using an invalid query + with patch.object(transcription_dao, '_create_query') as mock_create_query: + mock_query = Mock() + mock_query.prepare.return_value = True + mock_query.bindValue.return_value = None + mock_query.exec.return_value = False + mock_query.lastError.return_value.text.return_value = "Database error" + mock_create_query.return_value = mock_query + + # This should raise an exception with the database error message + with pytest.raises(Exception, match="Database error"): + transcription_dao.update_transcription_name(uuid4(), "Test Name") diff --git a/tests/db/entity/transcription_test.py b/tests/db/entity/transcription_test.py new file mode 100644 index 00000000..1023719c --- /dev/null +++ b/tests/db/entity/transcription_test.py @@ -0,0 +1,286 @@ +import pytest +from uuid import uuid4 + +from buzz.db.entity.transcription import Transcription + + +class TestTranscription: + def test_transcription_creation_with_name_and_notes(self): + """Test creating a transcription with name and notes fields""" + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Transcription Name", + notes="This is a test transcription with notes" + ) + + assert transcription.name == "Test Transcription Name" + assert transcription.notes == "This is a test transcription with notes" + + def test_transcription_creation_without_name_and_notes(self): + """Test creating a transcription without name and notes (should be None)""" + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER" + ) + + assert transcription.name is None + assert transcription.notes is None + + def test_transcription_creation_with_empty_name_and_notes(self): + """Test creating a transcription with empty name and notes""" + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="", + notes="" + ) + + assert transcription.name == "" + assert transcription.notes == "" + + def test_transcription_name_assignment(self): + """Test assigning values to name field""" + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER" + ) + + # Test assigning a name + transcription.name = "New Name" + assert transcription.name == "New Name" + + # Test assigning None + transcription.name = None + assert transcription.name is None + + # Test assigning empty string + transcription.name = "" + assert transcription.name == "" + + def test_transcription_notes_assignment(self): + """Test assigning values to notes field""" + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER" + ) + + # Test assigning notes + transcription.notes = "New notes" + assert transcription.notes == "New notes" + + # Test assigning None + transcription.notes = None + assert transcription.notes is None + + # Test assigning empty string + transcription.notes = "" + assert transcription.notes == "" + + def test_transcription_with_unicode_name_and_notes(self): + """Test creating transcription with unicode characters in name and notes""" + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Transcription avec des caractères spéciaux: ñáéíóú", + notes="Notes avec des caractères spéciaux: ñáéíóú et émojis 🎵🎤" + ) + + assert transcription.name == "Transcription avec des caractères spéciaux: ñáéíóú" + assert transcription.notes == "Notes avec des caractères spéciaux: ñáéíóú et émojis 🎵🎤" + + def test_transcription_with_long_name_and_notes(self): + """Test creating transcription with very long name and notes""" + long_name = "A" * 1000 # 1000 character name + long_notes = "B" * 5000 # 5000 character notes + + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name=long_name, + notes=long_notes + ) + + assert transcription.name == long_name + assert transcription.notes == long_notes + assert len(transcription.name) == 1000 + assert len(transcription.notes) == 5000 + + def test_transcription_name_with_special_characters(self): + """Test transcription name with special characters""" + special_name = "Transcription with special chars: !@#$%^&*()_+-=[]{}|;':\",./<>?" + + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name=special_name + ) + + assert transcription.name == special_name + + def test_transcription_notes_with_newlines(self): + """Test transcription notes with newlines and special formatting""" + notes_with_newlines = """This is a multi-line note +with newlines and special characters: +- Bullet point 1 +- Bullet point 2 +- Bullet point 3 + +And some more text after the empty line.""" + + transcription = Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + notes=notes_with_newlines + ) + + assert transcription.notes == notes_with_newlines + assert "\n" in transcription.notes + + def test_transcription_equality_with_name_and_notes(self): + """Test transcription equality when name and notes are included""" + transcription_id = str(uuid4()) + + transcription1 = Transcription( + id=transcription_id, + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Name", + notes="Test Notes" + ) + + transcription2 = Transcription( + id=transcription_id, + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Name", + notes="Test Notes" + ) + + # Two transcriptions with same ID should be equal + assert transcription1 == transcription2 + + def test_transcription_inequality_with_different_name_and_notes(self): + """Test transcription inequality when name and notes are different""" + transcription_id = str(uuid4()) + + transcription1 = Transcription( + id=transcription_id, + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Name 1", + notes="Test Notes 1" + ) + + transcription2 = Transcription( + id=transcription_id, + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Name 2", + notes="Test Notes 2" + ) + + # Two transcriptions with different name/notes should not be equal + assert transcription1 != transcription2 + + def test_transcription_id_as_uuid_property(self): + """Test that id_as_uuid property works with name and notes fields""" + transcription_id = uuid4() + + transcription = Transcription( + id=str(transcription_id), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Name", + notes="Test Notes" + ) + + assert transcription.id_as_uuid == transcription_id + assert isinstance(transcription.id_as_uuid, type(transcription_id)) + + def test_transcription_string_representation_with_name_and_notes(self): + """Test string representation of transcription includes name and notes""" + transcription = Transcription( + id="test-id-123", + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Transcription", + notes="Test notes" + ) + + str_repr = str(transcription) + # The string representation should include the ID + assert "test-id-123" in str_repr + + def test_transcription_with_none_values_in_other_fields(self): + """Test transcription with None values in other fields but valid name and notes""" + transcription = Transcription( + id=str(uuid4()), + file=None, + url=None, + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Valid Name", + notes="Valid Notes" + ) + + assert transcription.name == "Valid Name" + assert transcription.notes == "Valid Notes" + assert transcription.file is None + assert transcription.url is None diff --git a/tests/db/service/transcription_service_test.py b/tests/db/service/transcription_service_test.py new file mode 100644 index 00000000..2d267c52 --- /dev/null +++ b/tests/db/service/transcription_service_test.py @@ -0,0 +1,211 @@ +import pytest +from unittest.mock import Mock, patch +from uuid import UUID, uuid4 + +from buzz.db.service.transcription_service import TranscriptionService +from buzz.db.entity.transcription import Transcription + + +@pytest.fixture +def mock_transcription_dao(): + """Create a mock TranscriptionDAO for testing""" + return Mock() + + +@pytest.fixture +def mock_transcription_segment_dao(): + """Create a mock TranscriptionSegmentDAO for testing""" + return Mock() + + +@pytest.fixture +def transcription_service(mock_transcription_dao, mock_transcription_segment_dao): + """Create a TranscriptionService instance for testing""" + return TranscriptionService(mock_transcription_dao, mock_transcription_segment_dao) + + +@pytest.fixture +def sample_transcription(): + """Create a sample transcription for testing""" + return Transcription( + id=str(uuid4()), + file="/path/to/test.mp3", + status="completed", + time_queued="2023-01-01T00:00:00", + task="TRANSCRIBE", + model_type="WHISPER", + name="Test Transcription", + notes="This is a test transcription" + ) + + +class TestTranscriptionService: + def test_update_transcription_name(self, transcription_service, mock_transcription_dao): + """Test updating transcription name through service""" + transcription_id = uuid4() + new_name = "Updated Transcription Name" + + # Call the service method + transcription_service.update_transcription_name(transcription_id, new_name) + + # Verify the DAO method was called with correct parameters + mock_transcription_dao.update_transcription_name.assert_called_once_with(transcription_id, new_name) + + def test_update_transcription_notes(self, transcription_service, mock_transcription_dao): + """Test updating transcription notes through service""" + transcription_id = uuid4() + new_notes = "Updated transcription notes with more details" + + # Call the service method + transcription_service.update_transcription_notes(transcription_id, new_notes) + + # Verify the DAO method was called with correct parameters + mock_transcription_dao.update_transcription_notes.assert_called_once_with(transcription_id, new_notes) + + def test_update_transcription_name_with_empty_string(self, transcription_service, mock_transcription_dao): + """Test updating transcription name to empty string""" + transcription_id = uuid4() + empty_name = "" + + # Call the service method + transcription_service.update_transcription_name(transcription_id, empty_name) + + # Verify the DAO method was called with empty string + mock_transcription_dao.update_transcription_name.assert_called_once_with(transcription_id, empty_name) + + def test_update_transcription_notes_with_empty_string(self, transcription_service, mock_transcription_dao): + """Test updating transcription notes to empty string""" + transcription_id = uuid4() + empty_notes = "" + + # Call the service method + transcription_service.update_transcription_notes(transcription_id, empty_notes) + + # Verify the DAO method was called with empty string + mock_transcription_dao.update_transcription_notes.assert_called_once_with(transcription_id, empty_notes) + + def test_update_transcription_name_with_none(self, transcription_service, mock_transcription_dao): + """Test updating transcription name to None""" + transcription_id = uuid4() + + # Call the service method + transcription_service.update_transcription_name(transcription_id, None) + + # Verify the DAO method was called with None + mock_transcription_dao.update_transcription_name.assert_called_once_with(transcription_id, None) + + def test_update_transcription_notes_with_none(self, transcription_service, mock_transcription_dao): + """Test updating transcription notes to None""" + transcription_id = uuid4() + + # Call the service method + transcription_service.update_transcription_notes(transcription_id, None) + + # Verify the DAO method was called with None + mock_transcription_dao.update_transcription_notes.assert_called_once_with(transcription_id, None) + + def test_update_transcription_name_propagates_dao_exception(self, transcription_service, mock_transcription_dao): + """Test that DAO exceptions are propagated from service""" + transcription_id = uuid4() + new_name = "Updated Name" + + # Configure the mock to raise an exception + mock_transcription_dao.update_transcription_name.side_effect = Exception("Database error") + + # Call the service method and expect the exception to be raised + with pytest.raises(Exception, match="Database error"): + transcription_service.update_transcription_name(transcription_id, new_name) + + def test_update_transcription_notes_propagates_dao_exception(self, transcription_service, mock_transcription_dao): + """Test that DAO exceptions are propagated from service""" + transcription_id = uuid4() + new_notes = "Updated notes" + + # Configure the mock to raise an exception + mock_transcription_dao.update_transcription_notes.side_effect = Exception("Database error") + + # Call the service method and expect the exception to be raised + with pytest.raises(Exception, match="Database error"): + transcription_service.update_transcription_notes(transcription_id, new_notes) + + def test_update_transcription_name_with_string_uuid(self, transcription_service, mock_transcription_dao): + """Test updating transcription name with string UUID (should be converted to UUID)""" + transcription_id_str = str(uuid4()) + new_name = "Updated Name" + + # Call the service method + transcription_service.update_transcription_name(transcription_id_str, new_name) + + # Verify the DAO method was called with UUID object + mock_transcription_dao.update_transcription_name.assert_called_once() + call_args = mock_transcription_dao.update_transcription_name.call_args[0] + assert isinstance(call_args[0], str) # The service should pass the string as-is + assert call_args[1] == new_name + + def test_update_transcription_notes_with_string_uuid(self, transcription_service, mock_transcription_dao): + """Test updating transcription notes with string UUID (should be converted to UUID)""" + transcription_id_str = str(uuid4()) + new_notes = "Updated notes" + + # Call the service method + transcription_service.update_transcription_notes(transcription_id_str, new_notes) + + # Verify the DAO method was called with UUID object + mock_transcription_dao.update_transcription_notes.assert_called_once() + call_args = mock_transcription_dao.update_transcription_notes.call_args[0] + assert isinstance(call_args[0], str) # The service should pass the string as-is + assert call_args[1] == new_notes + + def test_update_transcription_name_multiple_calls(self, transcription_service, mock_transcription_dao): + """Test multiple calls to update transcription name""" + transcription_id = uuid4() + + # Make multiple calls + transcription_service.update_transcription_name(transcription_id, "Name 1") + transcription_service.update_transcription_name(transcription_id, "Name 2") + transcription_service.update_transcription_name(transcription_id, "Name 3") + + # Verify all calls were made + assert mock_transcription_dao.update_transcription_name.call_count == 3 + + # Verify the last call has the correct parameters + last_call = mock_transcription_dao.update_transcription_name.call_args_list[-1] + assert last_call[0] == (transcription_id, "Name 3") + + def test_update_transcription_notes_multiple_calls(self, transcription_service, mock_transcription_dao): + """Test multiple calls to update transcription notes""" + transcription_id = uuid4() + + # Make multiple calls + transcription_service.update_transcription_notes(transcription_id, "Notes 1") + transcription_service.update_transcription_notes(transcription_id, "Notes 2") + transcription_service.update_transcription_notes(transcription_id, "Notes 3") + + # Verify all calls were made + assert mock_transcription_dao.update_transcription_notes.call_count == 3 + + # Verify the last call has the correct parameters + last_call = mock_transcription_dao.update_transcription_notes.call_args_list[-1] + assert last_call[0] == (transcription_id, "Notes 3") + + def test_update_transcription_name_with_unicode(self, transcription_service, mock_transcription_dao): + """Test updating transcription name with unicode characters""" + transcription_id = uuid4() + unicode_name = "Transcription avec des caractères spéciaux: ñáéíóú" + + # Call the service method + transcription_service.update_transcription_name(transcription_id, unicode_name) + + # Verify the DAO method was called with unicode string + mock_transcription_dao.update_transcription_name.assert_called_once_with(transcription_id, unicode_name) + + def test_update_transcription_notes_with_unicode(self, transcription_service, mock_transcription_dao): + """Test updating transcription notes with unicode characters""" + transcription_id = uuid4() + unicode_notes = "Notes avec des caractères spéciaux: ñáéíóú et émojis 🎵🎤" + + # Call the service method + transcription_service.update_transcription_notes(transcription_id, unicode_notes) + + # Verify the DAO method was called with unicode string + mock_transcription_dao.update_transcription_notes.assert_called_once_with(transcription_id, unicode_notes) diff --git a/tests/settings/settings_test.py b/tests/settings/settings_test.py new file mode 100644 index 00000000..54a9439a --- /dev/null +++ b/tests/settings/settings_test.py @@ -0,0 +1,133 @@ +import pytest +from unittest.mock import Mock, patch + +from buzz.settings.settings import Settings + + +class TestSettings: + def test_transcription_tasks_table_column_order_key(self): + """Test that TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER key is defined""" + assert hasattr(Settings.Key, 'TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER') + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value == "transcription-tasks-table/column-order" + + def test_transcription_tasks_table_column_widths_key(self): + """Test that TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS key is defined""" + assert hasattr(Settings.Key, 'TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS') + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value == "transcription-tasks-table/column-widths" + + def test_transcription_tasks_table_column_visibility_key_exists(self): + """Test that TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY key still exists""" + assert hasattr(Settings.Key, 'TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY') + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value == "transcription-tasks-table/column-visibility" + + def test_all_transcription_tasks_table_keys_are_strings(self): + """Test that all transcription tasks table keys are strings""" + assert isinstance(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, str) + assert isinstance(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, str) + assert isinstance(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value, str) + + def test_transcription_tasks_table_keys_have_correct_prefix(self): + """Test that all transcription tasks table keys have the correct prefix""" + prefix = "transcription-tasks-table/" + + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value.startswith(prefix) + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value.startswith(prefix) + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value.startswith(prefix) + + def test_transcription_tasks_table_keys_are_unique(self): + """Test that all transcription tasks table keys are unique""" + keys = [ + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + ] + + assert len(keys) == len(set(keys)), "All transcription tasks table keys should be unique" + + def test_settings_key_enum_values(self): + """Test that Settings.Key enum values are properly defined""" + # Test that the keys exist and have expected values + expected_keys = { + 'TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY': 'transcription-tasks-table/column-visibility', + 'TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER': 'transcription-tasks-table/column-order', + 'TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS': 'transcription-tasks-table/column-widths' + } + + for key_name, expected_value in expected_keys.items(): + assert hasattr(Settings.Key, key_name) + assert getattr(Settings.Key, key_name).value == expected_value + + def test_settings_key_immutability(self): + """Test that Settings.Key values cannot be modified""" + # This test ensures that the keys are defined as constants + original_visibility = Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY + original_order = Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER + original_widths = Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS + + # Attempting to modify these should not work (they should be immutable) + # If they were mutable, this test would fail + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY == original_visibility + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER == original_order + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS == original_widths + + def test_settings_key_format_consistency(self): + """Test that all transcription tasks table keys follow the same format""" + keys = [ + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + ] + + for key in keys: + # All keys should start with the same prefix + assert key.startswith("transcription-tasks-table/") + # All keys should contain only lowercase letters, hyphens, and forward slashes + assert all(c.islower() or c in '-/' for c in key) + # All keys should end with a descriptive suffix + assert key.endswith(('visibility', 'order', 'widths')) + + def test_settings_key_length(self): + """Test that transcription tasks table keys have reasonable length""" + keys = [ + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + ] + + for key in keys: + # Keys should be long enough to be descriptive but not excessively long + assert 20 <= len(key) <= 50, f"Key '{key}' has unexpected length: {len(key)}" + + def test_settings_key_naming_convention(self): + """Test that transcription tasks table keys follow proper naming convention""" + keys = [ + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + ] + + for key in keys: + # Keys should use kebab-case (lowercase with hyphens) + assert '-' in key, f"Key '{key}' should use kebab-case with hyphens" + assert not any(c.isupper() for c in key), f"Key '{key}' should not contain uppercase letters" + assert not '_' in key, f"Key '{key}' should use hyphens instead of underscores" + + def test_settings_key_usage_in_code(self): + """Test that the settings keys can be used in typical settings operations""" + # Mock a settings object to test key usage + mock_settings = Mock() + mock_settings.begin_group = Mock() + mock_settings.end_group = Mock() + mock_settings.settings = Mock() + + # Test that the keys can be used with begin_group + mock_settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value) + mock_settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value) + mock_settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value) + + # Verify that begin_group was called with the correct keys + assert mock_settings.begin_group.call_count == 3 + call_args = [call[0][0] for call in mock_settings.begin_group.call_args_list] + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value in call_args + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value in call_args + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value in call_args diff --git a/tests/widgets/main_window_test.py b/tests/widgets/main_window_test.py index 00341927..5bbb194a 100644 --- a/tests/widgets/main_window_test.py +++ b/tests/widgets/main_window_test.py @@ -1,7 +1,7 @@ import logging import os from typing import List -from unittest.mock import patch +from unittest.mock import patch, Mock import pytest from PyQt6.QtCore import QSize, Qt @@ -21,9 +21,6 @@ from buzz.db.service.transcription_service import TranscriptionService from buzz.widgets.main_window import MainWindow from buzz.widgets.snap_notice import SnapNotice from buzz.widgets.transcriber.file_transcriber_widget import FileTranscriberWidget -from buzz.widgets.transcription_viewer.transcription_viewer_widget import ( - TranscriptionViewerWidget, -) mock_transcriptions: List[Transcription] = [ Transcription(status="completed"), @@ -154,8 +151,22 @@ class TestMainWindow: @pytest.mark.parametrize("transcription_dao", [mock_transcriptions], indirect=True) def test_should_load_tasks_from_cache( - self, qtbot, transcription_dao, transcription_segment_dao + self, qtbot, transcription_dao, transcription_segment_dao, monkeypatch ): + # Mock the queue worker to prevent it from processing tasks + mock_queue_worker = Mock() + mock_queue_worker.task_started = Mock() + mock_queue_worker.task_progress = Mock() + mock_queue_worker.task_download_progress = Mock() + mock_queue_worker.task_error = Mock() + mock_queue_worker.task_completed = Mock() + mock_queue_worker.completed = Mock() + mock_queue_worker.cancel_task = Mock() + mock_queue_worker.add_task = Mock() + mock_queue_worker.stop = Mock() + + monkeypatch.setattr("buzz.widgets.main_window.FileTranscriberQueueWorker", Mock(return_value=mock_queue_worker)) + window = MainWindow( TranscriptionService(transcription_dao, transcription_segment_dao) ) @@ -164,17 +175,19 @@ class TestMainWindow: table_widget = self._get_tasks_table(window) assert table_widget.model().rowCount() == 3 - assert self._get_status(table_widget, 0) == "completed" - table_widget.selectRow(0) - assert window.toolbar.open_transcript_action.isEnabled() + # Get all statuses and verify they match expected values + statuses = [self._get_status(table_widget, i) for i in range(3)] + expected_statuses = {"completed", "canceled", "failed"} + assert set(statuses) == expected_statuses, f"Expected {expected_statuses}, got {statuses}" - assert self._get_status(table_widget, 1) == "canceled" - table_widget.selectRow(1) - assert window.toolbar.open_transcript_action.isEnabled() is False - - assert self._get_status(table_widget, 2) == "failed" - table_widget.selectRow(2) - assert window.toolbar.open_transcript_action.isEnabled() is False + # Test that completed transcriptions enable the open action, others don't + for i in range(3): + table_widget.selectRow(i) + status = self._get_status(table_widget, i) + if status == "completed": + assert window.toolbar.open_transcript_action.isEnabled() + else: + assert window.toolbar.open_transcript_action.isEnabled() is False window.close() @pytest.mark.parametrize("transcription_dao", [mock_transcriptions], indirect=True) @@ -218,12 +231,20 @@ class TestMainWindow: qtbot.add_widget(window) table_widget = self._get_tasks_table(window) - table_widget.selectRow(0) + + # Find and select the completed transcription row + completed_row = None + for i in range(table_widget.model().rowCount()): + if self._get_status(table_widget, i) == "completed": + completed_row = i + break + + assert completed_row is not None, "No completed transcription found" + table_widget.selectRow(completed_row) window.toolbar.open_transcript_action.trigger() - transcription_viewer = window.findChild(TranscriptionViewerWidget) - assert transcription_viewer is not None + assert window.transcription_viewer_widget is not None window.close() @@ -237,7 +258,17 @@ class TestMainWindow: qtbot.add_widget(window) table_widget = self._get_tasks_table(window) - table_widget.selectRow(0) + + # Find and select the completed transcription row + completed_row = None + for i in range(table_widget.model().rowCount()): + if self._get_status(table_widget, i) == "completed": + completed_row = i + break + + assert completed_row is not None, "No completed transcription found" + table_widget.selectRow(completed_row) + table_widget.keyPressEvent( QKeyEvent( QKeyEvent.Type.KeyPress, @@ -247,8 +278,7 @@ class TestMainWindow: ) ) - transcription_viewer = window.findChild(TranscriptionViewerWidget) - assert transcription_viewer is not None + assert window.transcription_viewer_widget is not None window.close() diff --git a/tests/widgets/transcription_tasks_table_widget_test.py b/tests/widgets/transcription_tasks_table_widget_test.py index 44dc9d2d..017a54f9 100644 --- a/tests/widgets/transcription_tasks_table_widget_test.py +++ b/tests/widgets/transcription_tasks_table_widget_test.py @@ -10,6 +10,7 @@ from PyQt6.QtGui import QKeyEvent from PyQt6.QtSql import QSqlDatabase, QSqlQuery, QSqlRecord, QSqlTableModel from PyQt6.QtWidgets import QApplication, QMenu, QStyledItemDelegate +from buzz.locale import _ from buzz.widgets.transcription_tasks_table_widget import ( TranscriptionTasksTableWidget, format_record_status_text, @@ -46,18 +47,36 @@ def mock_dependencies(monkeypatch): mock_settings = Mock() settings_store = {} + current_group = [""] + + def begin_group(group): + current_group[0] = group + "/" + + def end_group(): + current_group[0] = "" + + def set_value(k, v): + settings_store[current_group[0] + k] = v + + def get_value(k, default=None): + return settings_store.get(current_group[0] + k, default) + mock_settings.settings = Mock() - mock_settings.settings.setValue.side_effect = lambda k, v: settings_store.update({k: v}) - mock_settings.settings.value.side_effect = lambda k, default: settings_store.get( - k, default - ) + mock_settings.settings.setValue.side_effect = set_value + mock_settings.settings.value.side_effect = get_value + mock_settings.begin_group.side_effect = begin_group + mock_settings.end_group.side_effect = end_group monkeypatch.setattr( "buzz.widgets.transcription_tasks_table_widget.Settings", Mock(return_value=mock_settings), ) monkeypatch.setattr( "buzz.widgets.transcription_tasks_table_widget.Settings.Key", - Mock(TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY="visibility"), + Mock( + TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY="visibility", + TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER="order", + TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS="widths" + ), ) @@ -87,13 +106,15 @@ def db(): "whisper_model_size TEXT," # 16 "hugging_face_model_id TEXT," # 17 "word_level_timings BOOLEAN DEFAULT FALSE," # 18 - "extract_speech BOOLEAN DEFAULT FALSE" # 19 + "extract_speech BOOLEAN DEFAULT FALSE," # 19 + "name TEXT," # 20 + "notes TEXT" # 21 ")" ) query.exec( - "INSERT INTO transcription (id, file, url, status, time_queued, task, model_type) VALUES " - "('1', '/a/b/c.mp3', '', 'QUEUED', '2023-01-01T00:00:00', 'TRANSCRIBE', 'WHISPER')," - "('2', '', 'http://example.com/d.wav', 'QUEUED', '2023-01-02T00:00:00', 'TRANSCRIBE', 'WHISPER')" + "INSERT INTO transcription (id, file, url, status, time_queued, task, model_type, name, notes) VALUES " + "('1', '/a/b/c.mp3', '', 'QUEUED', '2023-01-01T00:00:00', 'TRANSCRIBE', 'WHISPER', 'Test Audio File', 'This is a test transcription')," + "('2', '', 'http://example.com/d.wav', 'QUEUED', '2023-01-02T00:00:00', 'TRANSCRIBE', 'WHISPER', 'URL Audio', 'URL-based transcription')" ) yield db db.close() @@ -233,5 +254,233 @@ class TestTranscriptionTasksTableWidget: assert mock_menu.addAction.call_count > 0 menu_add_action_call_count = mock_menu.addAction.call_count + # Select a row so the widget context menu will add actions + widget.selectRow(0) widget.contextMenuEvent(Mock()) assert mock_menu.addAction.call_count > menu_add_action_call_count + + def test_new_column_definitions(self): + """Test that new NAME and NOTES columns are properly defined""" + # Check that NOTES column is defined + notes_column_def = next((col for col in column_definitions if col.column == Column.NOTES), None) + assert notes_column_def is not None + assert notes_column_def.id == "notes" + assert notes_column_def.header == _("Notes") + assert notes_column_def.width == 300 + assert notes_column_def.hidden_toggleable == True # Notes column should be toggleable + + # Check that FILE column has been updated to include name functionality + file_column_def = next((col for col in column_definitions if col.column == Column.FILE), None) + assert file_column_def is not None + assert file_column_def.id == "file_name" + assert file_column_def.header == _("File Name / URL") + assert file_column_def.width == 400 + assert file_column_def.hidden_toggleable == False # File column should not be toggleable + + def test_file_column_text_getter_with_name(self, widget): + """Test that file column displays name or falls back to file/url""" + # Test with name present + record_with_name = mock_record({"name": "Custom Name", "url": "http://example.com", "file": "/path/file.mp3"}) + file_column_def = next((col for col in column_definitions if col.column == Column.FILE), None) + text = file_column_def.delegate.callback(record_with_name) + assert text == "Custom Name" + + # Test fallback to URL when no name + record_url_fallback = mock_record({"name": None, "url": "http://example.com/audio.mp3", "file": "/path/file.mp3"}) + text = file_column_def.delegate.callback(record_url_fallback) + assert text == "http://example.com/audio.mp3" + + # Test fallback to filename when no name or URL + record_file_fallback = mock_record({"name": None, "url": "", "file": "/path/to/audio.mp3"}) + text = file_column_def.delegate.callback(record_file_fallback) + assert text == "audio.mp3" + + def test_notes_column_text_getter(self, widget): + """Test that notes column displays notes or empty string""" + notes_column_def = next((col for col in column_definitions if col.column == Column.NOTES), None) + + # Test with notes present + record_with_notes = mock_record({"notes": "Important transcription notes"}) + text = notes_column_def.delegate.callback(record_with_notes) + assert text == "Important transcription notes" + + # Test with no notes + record_no_notes = mock_record({"notes": None}) + text = notes_column_def.delegate.callback(record_no_notes) + assert text == "" + + def test_column_visibility_management(self, widget): + """Test column visibility save/load functionality""" + # Test saving column visibility + widget.setColumnHidden(Column.NOTES.value, True) + widget.save_column_visibility() + + # Create new widget to test loading + new_widget = TranscriptionTasksTableWidget() + assert new_widget.isColumnHidden(Column.NOTES.value) + + def test_column_width_management(self, widget): + """Test column width save/load functionality""" + # Test saving column widths + widget.setColumnWidth(Column.FILE.value, 500) + widget.save_column_widths() + + # Create new widget to test loading + new_widget = TranscriptionTasksTableWidget() + # Width should be loaded from settings (mocked to return 500) + assert new_widget.columnWidth(Column.FILE.value) == 500 + + def test_column_order_management(self, widget): + """Test column order save/load functionality""" + # Test saving column order + widget.save_column_order() + + # Test loading column order + widget.load_column_order() + + # Test resetting column order + widget.reset_column_order() + # After reset, columns should be in default order + header = widget.horizontalHeader() + for i, definition in enumerate(column_definitions): + assert header.visualIndex(definition.column.value) == i + + def test_context_menu_rename_action(self, widget, monkeypatch): + """Test rename action in context menu""" + # Mock the transcription service + mock_service = Mock() + widget.transcription_service = mock_service + + # Mock the transcription method to return a proper transcription object + mock_transcription = Mock() + mock_transcription.id = "12345678-1234-5678-1234-567812345678" # Valid UUID + mock_transcription.name = "Old Name" + mock_transcription.url = "http://example.com" + mock_transcription.file = "/path/file.mp3" + monkeypatch.setattr(widget, "transcription", Mock(return_value=mock_transcription)) + + # Mock QInputDialog + mock_dialog = Mock() + mock_dialog.getText.return_value = ("New Name", True) + monkeypatch.setattr("PyQt6.QtWidgets.QInputDialog", mock_dialog) + + # Select a row + widget.selectRow(0) + + # Call rename action + widget.on_rename_action() + + # Verify service was called + mock_service.update_transcription_name.assert_called_once() + mock_dialog.getText.assert_called_once() + + def test_context_menu_notes_action(self, widget, monkeypatch): + """Test notes action in context menu""" + # Mock the transcription service + mock_service = Mock() + widget.transcription_service = mock_service + + # Mock the transcription method to return a proper transcription object + mock_transcription = Mock() + mock_transcription.id = "12345678-1234-5678-1234-567812345678" # Valid UUID + mock_transcription.notes = "Old notes" + monkeypatch.setattr(widget, "transcription", Mock(return_value=mock_transcription)) + + # Mock QInputDialog + mock_dialog = Mock() + mock_dialog.getMultiLineText.return_value = ("New notes", True) + monkeypatch.setattr("PyQt6.QtWidgets.QInputDialog", mock_dialog) + + # Select a row + widget.selectRow(0) + + # Call notes action + widget.on_notes_action() + + # Verify service was called + mock_service.update_transcription_notes.assert_called_once() + mock_dialog.getMultiLineText.assert_called_once() + + def test_context_menu_restart_action_success(self, widget, monkeypatch): + """Test restart action for failed/canceled transcriptions""" + # Mock the transcription service + mock_service = Mock() + mock_service.reset_transcription_for_restart = Mock() + widget.transcription_service = mock_service + + # Mock QMessageBox + mock_messagebox = Mock() + monkeypatch.setattr("PyQt6.QtWidgets.QMessageBox", mock_messagebox) + + # Mock the _restart_transcription_task method to avoid complex setup + mock_restart = Mock() + monkeypatch.setattr(widget, "_restart_transcription_task", mock_restart) + + # Mock the transcription record to return failed status + mock_transcription = Mock() + mock_transcription.status = "failed" + mock_transcription.id = "12345678-1234-5678-1234-567812345678" # Valid UUID + monkeypatch.setattr(widget, "transcription", Mock(return_value=mock_transcription)) + + # Select a row + widget.selectRow(0) + + # Call restart action + widget.on_restart_transcription_action() + + # Verify service and restart were called + mock_service.reset_transcription_for_restart.assert_called_once() + mock_restart.assert_called_once_with(mock_transcription) + + def test_context_menu_restart_action_wrong_status(self, widget, monkeypatch): + """Test restart action shows error for non-failed/canceled transcriptions""" + # Mock QMessageBox + mock_messagebox = Mock() + monkeypatch.setattr("PyQt6.QtWidgets.QMessageBox", mock_messagebox) + + # Mock the transcription record to return completed status + mock_transcription = Mock() + mock_transcription.status = "completed" + monkeypatch.setattr(widget, "transcription", Mock(return_value=mock_transcription)) + + # Select a row + widget.selectRow(0) + + # Call restart action + widget.on_restart_transcription_action() + + # Verify error message was shown + mock_messagebox.information.assert_called_once() + + def test_column_resize_event(self, widget): + """Test column resize event handling""" + # Mock the save_column_widths method + with patch.object(widget, 'save_column_widths') as mock_save: + # Simulate column resize + widget.on_column_resized(0, 100, 200) + mock_save.assert_called_once() + + def test_column_move_event(self, widget): + """Test column move event handling""" + # Mock the save methods + with patch.object(widget, 'save_column_order') as mock_save_order, \ + patch.object(widget, 'load_column_visibility') as mock_load_vis: + # Simulate column move + widget.on_column_moved(0, 0, 1) + mock_save_order.assert_called_once() + mock_load_vis.assert_called_once() + + def test_reload_column_order_from_settings(self, widget): + """Test reloading column order from settings""" + # Mock settings to return specific values + widget.settings.settings.value.side_effect = lambda key, default=None: { + "file_name": "0", + "notes": "1", + "status": "2" + }.get(key, default) + + # Call reload method + widget.reload_column_order_from_settings() + + # Verify the method completes without error + assert True # If we get here, no exception was raised From 11e59dba2bbe794c48dbc3472d1b2f41bd8b06bd Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sat, 6 Dec 2025 18:51:40 +0200 Subject: [PATCH 17/73] 1292 fix speech dependencies (#1302) --- .gitignore | 1 + Buzz.spec | 30 +++++++++++++++++++++++++- buzz/file_transcriber_queue_worker.py | 31 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 66f3b3ec..291ecb53 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ coverage.xml .idea/ .venv/ venv/ +.claude/ # whisper_cpp whisper_cpp diff --git a/Buzz.spec b/Buzz.spec index c2d93bb1..fca6db93 100644 --- a/Buzz.spec +++ b/Buzz.spec @@ -22,6 +22,19 @@ datas += copy_metadata("tokenizers") datas += copy_metadata("huggingface-hub") datas += copy_metadata("safetensors") datas += copy_metadata("pyyaml") +datas += copy_metadata("julius") +datas += copy_metadata("openunmix") +datas += copy_metadata("lameenc") +datas += copy_metadata("diffq") +datas += copy_metadata("einops") +datas += copy_metadata("hydra-core") +datas += copy_metadata("hydra-colorlog") +datas += copy_metadata("museval") +datas += copy_metadata("submitit") +datas += copy_metadata("treetable") +datas += copy_metadata("soundfile") +datas += copy_metadata("dora-search") +datas += copy_metadata("lhotse") # Allow transformers package to load __init__.py file dynamically: # https://github.com/chidiwilliams/buzz/issues/272 @@ -92,7 +105,22 @@ a = Analysis( pathex=[], binaries=binaries, datas=datas, - hiddenimports=[], + hiddenimports=[ + "dora", "dora.log", + "julius", "julius.core", "julius.resample", + "openunmix", "openunmix.filtering", + "lameenc", + "diffq", + "einops", + "hydra", "hydra.core", "hydra.core.global_hydra", + "hydra_colorlog", + "museval", + "submitit", + "treetable", + "soundfile", + "_soundfile_data", + "lhotse", + ], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index 6866ef7c..99b43af7 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -1,12 +1,42 @@ import logging import multiprocessing import queue +import sys from pathlib import Path from typing import Optional, Tuple, List, Set from uuid import UUID from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot +# Patch subprocess for demucs to prevent console windows on Windows +if sys.platform == "win32": + import subprocess + _original_run = subprocess.run + _original_check_output = subprocess.check_output + + def _patched_run(*args, **kwargs): + if 'startupinfo' not in kwargs: + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + si.wShowWindow = subprocess.SW_HIDE + kwargs['startupinfo'] = si + if 'creationflags' not in kwargs: + kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW + return _original_run(*args, **kwargs) + + def _patched_check_output(*args, **kwargs): + if 'startupinfo' not in kwargs: + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + si.wShowWindow = subprocess.SW_HIDE + kwargs['startupinfo'] = si + if 'creationflags' not in kwargs: + kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW + return _original_check_output(*args, **kwargs) + + subprocess.run = _patched_run + subprocess.check_output = _patched_check_output + from demucs import api as demucsApi from buzz.model_loader import ModelType @@ -95,6 +125,7 @@ class FileTranscriberQueueWorker(QObject): logging.error(f"Error during speech extraction: {e}", exc_info=True) logging.debug("Starting next transcription task") + self.task_progress.emit(self.current_task, 0) model_type = self.current_task.transcription_options.model.model_type if model_type == ModelType.OPEN_AI_WHISPER_API: From 1c146631c9c558b4073cc36e610e379d7d7527af Mon Sep 17 00:00:00 2001 From: David Olowomeye <100958002+greatdaveo@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:18:00 +0000 Subject: [PATCH 18/73] Added video support in transcription playback #906 (#1295) Co-authored-by: Raivis Dejus --- buzz/transcriber/file_transcriber.py | 6 + buzz/widgets/audio_player.py | 56 ++- .../transcription_viewer_widget.py | 460 +++++++++++++----- buzz/widgets/video_player.py | 160 ++++++ tests/widgets/transcription_viewer_test.py | 18 +- tests/widgets/video_player_test.py | 274 +++++++++++ 6 files changed, 832 insertions(+), 142 deletions(-) create mode 100644 buzz/widgets/video_player.py create mode 100644 tests/widgets/video_player_test.py diff --git a/buzz/transcriber/file_transcriber.py b/buzz/transcriber/file_transcriber.py index 87f2d636..5943c8a0 100755 --- a/buzz/transcriber/file_transcriber.py +++ b/buzz/transcriber/file_transcriber.py @@ -203,3 +203,9 @@ def to_timestamp(ms: float, ms_separator=".") -> str: sec = int(ms / 1000) ms = int(ms - sec * 1000) return f"{hr:02d}:{min:02d}:{sec:02d}{ms_separator}{ms:03d}" + +# To detect when transcription source is a video +VIDEO_EXTENSIONS = {".mp4", ".mov", ".mkv", ".avi", ".m4v", ".webm", ".ogm", ".wmv"} + +def is_video_file(path: str) -> bool: + return Path(path).suffix.lower() in VIDEO_EXTENSIONS diff --git a/buzz/widgets/audio_player.py b/buzz/widgets/audio_player.py index cf865e57..7e1ac94b 100644 --- a/buzz/widgets/audio_player.py +++ b/buzz/widgets/audio_player.py @@ -4,10 +4,11 @@ from typing import Tuple, Optional from PyQt6 import QtGui from PyQt6.QtCore import QTime, QUrl, Qt, pyqtSignal from PyQt6.QtMultimedia import QAudioOutput, QMediaPlayer -from PyQt6.QtWidgets import QWidget, QSlider, QPushButton, QLabel, QHBoxLayout +from PyQt6.QtWidgets import QWidget, QSlider, QPushButton, QLabel, QHBoxLayout, QVBoxLayout from buzz.widgets.icon import PlayIcon, PauseIcon from buzz.settings.settings import Settings +from buzz.transcriber.file_transcriber import is_video_file class AudioPlayer(QWidget): @@ -21,10 +22,13 @@ class AudioPlayer(QWidget): self.duration_ms = 0 self.invalid_media = None self.is_looping = False # Flag to prevent recursive position changes + self.is_slider_dragging = False # Flag to track if use is dragging slider # Initialize settings self.settings = Settings() + self.is_video = is_video_file(file_path) + self.audio_output = QAudioOutput() self.audio_output.setVolume(100) @@ -32,6 +36,13 @@ class AudioPlayer(QWidget): self.media_player.setSource(QUrl.fromLocalFile(file_path)) self.media_player.setAudioOutput(self.audio_output) + if self.is_video: + from PyQt6.QtMultimediaWidgets import QVideoWidget + self.video_widget = QVideoWidget(self) + self.media_player.setVideoOutput(self.video_widget) + else: + self.video_widget = None + # Speed control moved to transcription viewer - just set default rate saved_rate = self.settings.value(Settings.Key.AUDIO_PLAYBACK_RATE, 1.0, float) saved_rate = max(0.1, min(5.0, saved_rate)) # Ensure valid range @@ -40,6 +51,11 @@ class AudioPlayer(QWidget): self.scrubber = QSlider(Qt.Orientation.Horizontal) self.scrubber.setRange(0, 0) self.scrubber.sliderMoved.connect(self.on_slider_moved) + self.scrubber.sliderPressed.connect(self.on_slider_pressed) + self.scrubber.sliderReleased.connect(self.on_slider_released) + + # Track if user is dragging the slider + self.is_slider_dragging = False self.play_icon = PlayIcon(self) self.pause_icon = PauseIcon(self) @@ -54,10 +70,23 @@ class AudioPlayer(QWidget): self.time_label.setAlignment(Qt.AlignmentFlag.AlignRight) # Create main layout - simplified without speed controls - main_layout = QHBoxLayout() - main_layout.addWidget(self.play_button, alignment=Qt.AlignmentFlag.AlignVCenter) - main_layout.addWidget(self.scrubber, alignment=Qt.AlignmentFlag.AlignVCenter) - main_layout.addWidget(self.time_label, alignment=Qt.AlignmentFlag.AlignVCenter) + if self.is_video: + #Vertical layout for video + main_layout = QVBoxLayout() + main_layout.addWidget(self.video_widget, stretch=1) # As video takes more space + + controls_layout = QHBoxLayout() + controls_layout.addWidget(self.play_button, alignment=Qt.AlignmentFlag.AlignVCenter) + controls_layout.addWidget(self.scrubber, alignment=Qt.AlignmentFlag.AlignVCenter) + controls_layout.addWidget(self.time_label, alignment=Qt.AlignmentFlag.AlignVCenter) + + main_layout.addLayout(controls_layout) + else: + # Horizontal layout for audio only + main_layout = QHBoxLayout() + main_layout.addWidget(self.play_button, alignment=Qt.AlignmentFlag.AlignVCenter) + main_layout.addWidget(self.scrubber, alignment=Qt.AlignmentFlag.AlignVCenter) + main_layout.addWidget(self.time_label, alignment=Qt.AlignmentFlag.AlignVCenter) self.setLayout(main_layout) @@ -75,7 +104,12 @@ class AudioPlayer(QWidget): self.update_time_label() def on_position_changed(self, position_ms: int): - self.scrubber.setValue(position_ms) + # Don't update slider if user is currently dragging it + if not self.is_slider_dragging: + self.scrubber.blockSignals(True) + self.scrubber.setValue(position_ms) + self.scrubber.blockSignals(False) + self.position_ms = position_ms self.position_ms_changed.emit(self.position_ms) self.update_time_label() @@ -150,6 +184,16 @@ class AudioPlayer(QWidget): if position_ms < (start_range_ms - 2000) or position_ms > (end_range_ms + 2000): self.range_ms = None + def on_slider_pressed(self): + """Called when the user starts dragging the slider""" + self.is_slider_dragging = True + + def on_slider_released(self): + """Called when user releases the slider""" + self.is_slider_dragging = False + # Update the position where user released + self.set_position(self.scrubber.value()) + def set_position(self, position_ms: int): self.media_player.setPosition(position_ms) diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index 51b4e67c..71d1ae46 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -4,7 +4,7 @@ import platform from typing import Optional from uuid import UUID -from PyQt6.QtCore import Qt, QThread, pyqtSignal +from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer from PyQt6.QtGui import QTextCursor from PyQt6.QtMultimedia import QMediaPlayer from PyQt6.QtSql import QSqlRecord @@ -22,6 +22,8 @@ from PyQt6.QtWidgets import ( QComboBox, QScrollArea, QSizePolicy, + QStackedWidget, + QSplitter ) from buzz.locale import _ @@ -32,7 +34,9 @@ from buzz.settings.shortcuts import Shortcuts from buzz.settings.shortcut import Shortcut from buzz.settings.settings import Settings from buzz.store.keyring_store import get_password, Key +from buzz.transcriber.file_transcriber import is_video_file from buzz.widgets.audio_player import AudioPlayer +from buzz.widgets.video_player import VideoPlayer from buzz.widgets.icon import ( FileDownloadIcon, TranslateIcon, @@ -104,11 +108,13 @@ class TranscriptionViewerWidget(QWidget): self.search_results = [] # Loop functionality - self.segment_looping_enabled = self.settings.settings.value("transcription_viewer/segment_looping_enabled", False, type=bool) - + self.segment_looping_enabled = self.settings.settings.value( + "transcription_viewer/segment_looping_enabled", False, type=bool) # UI visibility preferences - self.playback_controls_visible = self.settings.settings.value("transcription_viewer/playback_controls_visible", False, type=bool) - self.find_widget_visible = self.settings.settings.value("transcription_viewer/find_widget_visible", False, type=bool) + self.playback_controls_visible = self.settings.settings.value( + "transcription_viewer/playback_controls_visible", False, type=bool) + self.find_widget_visible = self.settings.settings.value( + "transcription_viewer/find_widget_visible", False, type=bool) # Currently selected segment for loop functionality self.currently_selected_segment = None @@ -117,7 +123,8 @@ class TranscriptionViewerWidget(QWidget): segments = self.transcription_service.get_transcription_segments( transcription_id=self.transcription.id_as_uuid ) - self.has_translations = any(segment.translation.strip() for segment in segments) + self.has_translations = any(segment.translation.strip() + for segment in segments) self.openai_access_token = get_password(Key.OPENAI_API_KEY) @@ -156,46 +163,82 @@ class TranscriptionViewerWidget(QWidget): parent=self ) self.table_widget.segment_selected.connect(self.on_segment_selected) - self.table_widget.timestamp_being_edited.connect(self.on_timestamp_being_edited) + self.table_widget.timestamp_being_edited.connect( + self.on_timestamp_being_edited) self.text_display_box = TextDisplayBox(self) + # Determine if source is video + self.is_video = is_video_file(transcription.file) if transcription.file else False + self.audio_player = AudioPlayer(file_path=transcription.file) + self.video_player = None + + # Stack widget is to switch between audio and video + self.media_player_stack = QStackedWidget() + self.media_player_stack.addWidget(self.audio_player) + + # Only create video player if source is a video file + if self.is_video: + self.video_player = VideoPlayer(file_path=transcription.file) + self.media_player_stack.addWidget(self.video_player) + + self.current_media_player = None + self.load_transcription_media() + + # Connect audio player signals self.audio_player.position_ms_changed.connect( self.on_audio_player_position_ms_changed ) + + # Connect video player signals (only if video player exists) + if self.video_player: + self.video_player.position_ms_changed.connect( + self.on_audio_player_position_ms_changed + ) + # Connect to playback state changes to automatically show controls self.audio_player.media_player.playbackStateChanged.connect( self.on_audio_playback_state_changed ) + if self.video_player: + self.video_player.media_player.playbackStateChanged.connect( + self.on_audio_playback_state_changed + ) + # Create a better current segment display that handles long text self.current_segment_frame = QFrame() self.current_segment_frame.setFrameStyle(QFrame.Shape.NoFrame) segment_layout = QVBoxLayout(self.current_segment_frame) - segment_layout.setContentsMargins(4, 4, 4, 4) # Minimal margins for clean appearance + # Minimal margins for clean appearance + segment_layout.setContentsMargins(4, 4, 4, 4) segment_layout.setSpacing(0) # No spacing between elements # Text display - centered with scroll capability (no header label) self.current_segment_text = QLabel("") - self.current_segment_text.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) + self.current_segment_text.setAlignment( + Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) self.current_segment_text.setWordWrap(True) - self.current_segment_text.setStyleSheet("color: #666; line-height: 1.2; margin: 0; padding: 4px;") - self.current_segment_text.setMinimumHeight(60) # Ensure minimum height for text - + self.current_segment_text.setStyleSheet( + "color: #666; line-height: 1.2; margin: 0; padding: 4px;") + self.current_segment_text.setMinimumHeight( + 60) # Ensure minimum height for text # Make it scrollable for long text self.current_segment_scroll_area = QScrollArea() self.current_segment_scroll_area.setWidget(self.current_segment_text) self.current_segment_scroll_area.setWidgetResizable(True) self.current_segment_scroll_area.setFrameStyle(QFrame.Shape.NoFrame) - self.current_segment_scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) - self.current_segment_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) - self.current_segment_scroll_area.setStyleSheet("QScrollBar:vertical { width: 12px; } QScrollBar::handle:vertical { background: #ccc; border-radius: 6px; }") - + self.current_segment_scroll_area.setHorizontalScrollBarPolicy( + Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.current_segment_scroll_area.setVerticalScrollBarPolicy( + Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.current_segment_scroll_area.setStyleSheet( + "QScrollBar:vertical { width: 12px; } QScrollBar::handle:vertical { background: #ccc; border-radius: 6px; }") # Ensure the text label can expand to show all content - self.current_segment_text.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) - + self.current_segment_text.setSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) # Add scroll area to layout (simplified single-widget layout) segment_layout.addWidget(self.current_segment_scroll_area) @@ -211,7 +254,8 @@ class TranscriptionViewerWidget(QWidget): self.has_translations, self.translator.translation, ) - view_mode_tool_button.view_mode_changed.connect(self.on_view_mode_changed) + view_mode_tool_button.view_mode_changed.connect( + self.on_view_mode_changed) toolbar.addWidget(view_mode_tool_button) export_tool_button = QToolButton() @@ -229,7 +273,8 @@ class TranscriptionViewerWidget(QWidget): self ) export_tool_button.setMenu(export_transcription_menu) - export_tool_button.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup) + export_tool_button.setPopupMode( + QToolButton.ToolButtonPopupMode.MenuButtonPopup) export_tool_button.clicked.connect(export_tool_button.showMenu) toolbar.addWidget(export_tool_button) @@ -270,11 +315,15 @@ class TranscriptionViewerWidget(QWidget): # Add Find button self.find_button = QToolButton() self.find_button.setText(_("Find")) - self.find_button.setIcon(VisibilityIcon(self)) # Using visibility icon for search - self.find_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) + # Using visibility icon for search + self.find_button.setIcon(VisibilityIcon(self)) + self.find_button.setToolButtonStyle( + Qt.ToolButtonStyle.ToolButtonTextBesideIcon) self.find_button.setToolTip(_("Show/Hide Search Bar (Ctrl+F)")) - self.find_button.setCheckable(True) # Make button checkable to show state - self.find_button.setChecked(False) # Initially unchecked (search hidden) + # Make button checkable to show state + self.find_button.setCheckable(True) + # Initially unchecked (search hidden) + self.find_button.setChecked(False) self.find_button.clicked.connect(self.toggle_search_bar_visibility) toolbar.addWidget(self.find_button) @@ -285,21 +334,32 @@ class TranscriptionViewerWidget(QWidget): # Search frame (minimal space) layout.addWidget(self.search_frame, 0) # Stretch factor 0 (minimal) - # Table widget should take the majority of the space - layout.addWidget(self.table_widget, 1) # Stretch factor 1 (majority) - + # Use splitter for resizable media player + self.media_splitter = QSplitter(Qt.Orientation.Vertical) + self.media_splitter.setHandleWidth(8) # Make splitter handle easier to grab + self.media_splitter.addWidget(self.table_widget) + self.media_splitter.addWidget(self.media_player_stack) + # Make splitter collapsible but with minimum sizes + # Don't allow tabe to collapse completely + self.media_splitter.setCollapsible(0, False) + # Don't allow media player to collapse completely + self.media_splitter.setCollapsible(1, False) + # Connect splitter to save sizes when user resizes + self.media_splitter.splitterMoved.connect(self.on_splitter_moved) # Loop controls section (minimal space) self.create_loop_controls() - layout.addWidget(self.loop_controls_frame, 0) # Stretch factor 0 (minimal) - - # Audio player (minimal space) - layout.addWidget(self.audio_player, 0) # Stretch factor 0 (minimal) + # Stretch factor 0 (minimal) + layout.addWidget(self.loop_controls_frame, 0) + # Add splitter to layout (table + media player) + layout.addWidget(self.media_splitter, 1) # Stretch factor 1 (majority) # Text display box (minimal space) - layout.addWidget(self.text_display_box, 0) # Stretch factor 0 (minimal) + # Stretch factor 0 (minimal) + layout.addWidget(self.text_display_box, 0) # Add current segment display (minimal space) - layout.addWidget(self.current_segment_frame, 1) # Stretch factor 0 (minimal) + # Stretch factor 0 (minimal) + layout.addWidget(self.current_segment_frame, 1) # Initially hide the current segment frame until a segment is selected self.current_segment_frame.hide() @@ -317,6 +377,27 @@ class TranscriptionViewerWidget(QWidget): self.reset_view() + def load_transcription_media(self): + if self.is_video and self.video_player: + self.media_player_stack.setCurrentWidget(self.video_player) + self.current_media_player = self.video_player + else: + self.media_player_stack.setCurrentWidget(self.audio_player) + self.current_media_player = self.audio_player + + # Load splitter sizes after determining media type + if hasattr(self, 'media_splitter'): + self.load_splitter_sizes() + + def on_transcript_segment_clicked(self, segment): + if not self.current_media_player: + return + + start_time_ms = int(segment.start_time) + self.current_media_player.set_position(start_time_ms) + if self.current_media_player.media_player.playbackState() != QMediaPlayer.PlaybackState.PlayingState: + self.current_media_player.media_player.play() + def restore_ui_state(self): """Restore UI state from settings""" # Restore playback controls visibility @@ -360,7 +441,8 @@ class TranscriptionViewerWidget(QWidget): self.search_prev_button.clicked.connect(self.search_previous) self.search_prev_button.setEnabled(False) self.search_prev_button.setMaximumWidth(40) - self.search_prev_button.setMinimumHeight(30) # Ensure consistent height + self.search_prev_button.setMinimumHeight( + 30) # Ensure consistent height search_layout.addWidget(self.search_prev_button) self.search_next_button = QPushButton("↓") @@ -368,14 +450,16 @@ class TranscriptionViewerWidget(QWidget): self.search_next_button.clicked.connect(self.search_next) self.search_next_button.setEnabled(False) self.search_next_button.setMaximumWidth(40) - self.search_next_button.setMinimumHeight(30) # Ensure consistent height + self.search_next_button.setMinimumHeight( + 30) # Ensure consistent height search_layout.addWidget(self.search_next_button) # Clear button - make it bigger to accommodate different language translations self.clear_search_button = QPushButton(_("Clear")) self.clear_search_button.clicked.connect(self.clear_search) self.clear_search_button.setMaximumWidth(80) # Increased from 60 to 80 - self.clear_search_button.setMinimumHeight(30) # Ensure consistent height + self.clear_search_button.setMinimumHeight( + 30) # Ensure consistent height search_layout.addWidget(self.clear_search_button) # Results label @@ -396,8 +480,8 @@ class TranscriptionViewerWidget(QWidget): loop_layout = QHBoxLayout(self.loop_controls_frame) loop_layout.setContentsMargins(10, 5, 10, 5) - loop_layout.setSpacing(8) # Add some spacing between elements for better visual separation - + # Add some spacing between elements for better visual separation + loop_layout.setSpacing(8) # Loop controls label loop_label = QLabel(_("Playback Controls:")) loop_label.setStyleSheet("font-weight: bold;") @@ -406,16 +490,20 @@ class TranscriptionViewerWidget(QWidget): # Loop toggle button self.loop_toggle = QCheckBox(_("Loop Segment")) self.loop_toggle.setChecked(self.segment_looping_enabled) - self.loop_toggle.setToolTip(_("Enable/disable looping when clicking on transcript segments")) + self.loop_toggle.setToolTip( + _("Enable/disable looping when clicking on transcript segments")) self.loop_toggle.toggled.connect(self.on_loop_toggle_changed) loop_layout.addWidget(self.loop_toggle) # Follow audio toggle button - self.follow_audio_enabled = self.settings.settings.value("transcription_viewer/follow_audio_enabled", False, type=bool) + self.follow_audio_enabled = self.settings.settings.value( + "transcription_viewer/follow_audio_enabled", False, type=bool) self.follow_audio_toggle = QCheckBox(_("Follow Audio")) self.follow_audio_toggle.setChecked(self.follow_audio_enabled) - self.follow_audio_toggle.setToolTip(_("Enable/disable following the current audio position in the transcript. When enabled, automatically scrolls to current text.")) - self.follow_audio_toggle.toggled.connect(self.on_follow_audio_toggle_changed) + self.follow_audio_toggle.setToolTip( + _("Enable/disable following the current audio position in the transcript. When enabled, automatically scrolls to current text.")) + self.follow_audio_toggle.toggled.connect( + self.on_follow_audio_toggle_changed) loop_layout.addWidget(self.follow_audio_toggle) # Visual separator @@ -432,7 +520,8 @@ class TranscriptionViewerWidget(QWidget): self.speed_combo = QComboBox() self.speed_combo.setEditable(True) - self.speed_combo.addItems(["0.5x", "0.75x", "1x", "1.25x", "1.5x", "2x"]) + self.speed_combo.addItems( + ["0.5x", "0.75x", "1x", "1.25x", "1.5x", "2x"]) self.speed_combo.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) self.speed_combo.currentTextChanged.connect(self.on_speed_changed) self.speed_combo.setMaximumWidth(80) @@ -463,10 +552,13 @@ class TranscriptionViewerWidget(QWidget): # Scroll to current button self.scroll_to_current_button = QPushButton(_("Scroll to Current")) self.scroll_to_current_button.setIcon(ScrollToCurrentIcon(self)) - self.scroll_to_current_button.setToolTip(_("Scroll to the currently spoken text")) - self.scroll_to_current_button.clicked.connect(self.on_scroll_to_current_button_clicked) + self.scroll_to_current_button.setToolTip( + _("Scroll to the currently spoken text")) + self.scroll_to_current_button.clicked.connect( + self.on_scroll_to_current_button_clicked) self.scroll_to_current_button.setMinimumHeight(30) - self.scroll_to_current_button.setStyleSheet("QPushButton { padding: 4px 8px; }") # Better padding + self.scroll_to_current_button.setStyleSheet( + "QPushButton { padding: 4px 8px; }") # Better padding loop_layout.addWidget(self.scroll_to_current_button) loop_layout.addStretch() @@ -480,7 +572,8 @@ class TranscriptionViewerWidget(QWidget): # Save the visibility state to settings self.playback_controls_visible = True - self.settings.settings.setValue("transcription_viewer/playback_controls_visible", self.playback_controls_visible) + self.settings.settings.setValue( + "transcription_viewer/playback_controls_visible", self.playback_controls_visible) def hide_loop_controls(self): """Hide the loop controls when audio is not playing""" @@ -488,7 +581,8 @@ class TranscriptionViewerWidget(QWidget): # Save the visibility state to settings self.playback_controls_visible = False - self.settings.settings.setValue("transcription_viewer/playback_controls_visible", self.playback_controls_visible) + self.settings.settings.setValue( + "transcription_viewer/playback_controls_visible", self.playback_controls_visible) def toggle_playback_controls_visibility(self): """Toggle the visibility of playback controls manually""" @@ -499,10 +593,10 @@ class TranscriptionViewerWidget(QWidget): def toggle_audio_playback(self): """Toggle audio playback (play/pause)""" - if self.audio_player.media_player.playbackState() == QMediaPlayer.PlaybackState.PlayingState: - self.audio_player.media_player.pause() + if self.current_media_player and self.current_media_player.media_player.playbackState() == QMediaPlayer.PlaybackState.PlayingState: + self.current_media_player.media_player.pause() else: - self.audio_player.media_player.play() + self.current_media_player.media_player.play() def replay_current_segment(self): """Rewind current segment to its start and play if not already playing""" @@ -513,7 +607,8 @@ class TranscriptionViewerWidget(QWidget): start_time = self.currently_selected_segment.value("start_time") # Set position to the start of the segment - self.audio_player.set_position(start_time) + if self.current_media_player: + self.current_media_player.set_position(start_time) # If audio is not playing, start playing if self.audio_player.media_player.playbackState() != QMediaPlayer.PlaybackState.PlayingState: @@ -557,7 +652,8 @@ class TranscriptionViewerWidget(QWidget): start_col = Column.START.value end_col = Column.END.value - current_start_time = self.table_widget.model().record(current_row).value("start_time") + current_start_time = self.table_widget.model().record( + current_row).value("start_time") current_end_time = self.table_widget.model().record(current_row).value("end_time") # Calculate new value based on CURRENT database value @@ -593,7 +689,8 @@ class TranscriptionViewerWidget(QWidget): # Check overlap with next segment if current_row < len(segments) - 1: - next_start = self.table_widget.model().record(current_row + 1).value("start_time") + next_start = self.table_widget.model().record( + current_row + 1).value("start_time") if new_value > next_start: # Update next segment's start time self.table_widget.model().setData( @@ -615,7 +712,10 @@ class TranscriptionViewerWidget(QWidget): if self.segment_looping_enabled: updated_start = self.currently_selected_segment.value("start_time") updated_end = self.currently_selected_segment.value("end_time") - self.audio_player.set_range((updated_start, updated_end)) + + if self.current_media_player: + self.current_media_player.set_range( + (updated_start, updated_end)) def on_audio_playback_state_changed(self, state): """Handle audio playback state changes to automatically show/hide playback controls""" @@ -633,12 +733,13 @@ class TranscriptionViewerWidget(QWidget): """Initialize the speed control with current value from audio player""" try: # Get current speed from audio player - current_speed = self.audio_player.media_player.playbackRate() - # Ensure it's within valid range - current_speed = max(0.1, min(5.0, current_speed)) - # Set the combo box text - speed_text = f"{current_speed:.2f}x" - self.speed_combo.setCurrentText(speed_text) + if self.current_media_player: + current_speed = self.current_media_player.media_player.playbackRate() + # Ensure it's within valid range + current_speed = max(0.1, min(5.0, current_speed)) + # Set the combo box text + speed_text = f"{current_speed:.2f}x" + self.speed_combo.setCurrentText(speed_text) except Exception as e: logging.warning(f"Could not initialize speed control: {e}") # Default to 1.0x @@ -664,11 +765,12 @@ class TranscriptionViewerWidget(QWidget): self.speed_combo.blockSignals(False) # Set the playback rate on the audio player - self.audio_player.media_player.setPlaybackRate(speed_value) - + if self.current_media_player: + self.current_media_player.media_player.setPlaybackRate( + speed_value) # Save the new rate to settings - self.settings.set_value(self.settings.Key.AUDIO_PLAYBACK_RATE, speed_value) - + self.settings.set_value( + self.settings.Key.AUDIO_PLAYBACK_RATE, speed_value) except ValueError: logging.warning(f"Invalid speed value: {speed_text}") # Reset to current valid value @@ -776,7 +878,8 @@ class TranscriptionViewerWidget(QWidget): pos = text_lower.find(search_text_lower, start) if pos == -1 or result_count >= max_results: break - self.search_results.append(("text", pos, pos + len(self.search_text))) + self.search_results.append( + ("text", pos, pos + len(self.search_text))) start = pos + 1 result_count += 1 @@ -787,7 +890,8 @@ class TranscriptionViewerWidget(QWidget): if len(self.search_results) >= 100: self.search_results_label.setText(_("1 of 100+ matches")) else: - self.search_results_label.setText(_("1 of ") + str(len(self.search_results)) + _(" matches")) + self.search_results_label.setText( + _("1 of ") + str(len(self.search_results)) + _(" matches")) self.search_prev_button.setEnabled(True) self.search_next_button.setEnabled(True) self.highlight_current_match() @@ -815,14 +919,15 @@ class TranscriptionViewerWidget(QWidget): # Select the row containing the match self.table_widget.selectRow(row_index) # Scroll to the row - self.table_widget.scrollTo(self.table_widget.model().index(row_index, 0)) + self.table_widget.scrollTo( + self.table_widget.model().index(row_index, 0)) def highlight_text_match(self, start_pos: int): """Highlight a match in the text display""" cursor = QTextCursor(self.text_display_box.document()) cursor.setPosition(start_pos) - cursor.setPosition(start_pos + len(self.search_text), QTextCursor.MoveMode.KeepAnchor) - + cursor.setPosition(start_pos + len(self.search_text), + QTextCursor.MoveMode.KeepAnchor) # Set the cursor to highlight the text self.text_display_box.setTextCursor(cursor) @@ -834,7 +939,8 @@ class TranscriptionViewerWidget(QWidget): if not self.search_results: return - self.current_search_index = (self.current_search_index + 1) % len(self.search_results) + self.current_search_index = ( + self.current_search_index + 1) % len(self.search_results) self.highlight_current_match() self.update_search_results_label() @@ -843,7 +949,8 @@ class TranscriptionViewerWidget(QWidget): if not self.search_results: return - self.current_search_index = (self.current_search_index - 1) % len(self.search_results) + self.current_search_index = ( + self.current_search_index - 1) % len(self.search_results) self.highlight_current_match() self.update_search_results_label() @@ -851,9 +958,11 @@ class TranscriptionViewerWidget(QWidget): """Update the search results label with current position""" if self.search_results: if len(self.search_results) >= 100: - self.search_results_label.setText(str(self.current_search_index + 1) + _(" of 100+ matches")) + self.search_results_label.setText( + str(self.current_search_index + 1) + _(" of 100+ matches")) else: - self.search_results_label.setText(str(self.current_search_index + 1) + _(" of ") + str(len(self.search_results)) + _(" matches")) + self.search_results_label.setText(str( + self.current_search_index + 1) + _(" of ") + str(len(self.search_results)) + _(" matches")) def clear_search(self): """Clear the search and reset highlighting""" @@ -884,43 +993,55 @@ class TranscriptionViewerWidget(QWidget): # Save the visibility state to settings self.find_widget_visible = False - self.settings.settings.setValue("transcription_viewer/find_widget_visible", False) + self.settings.settings.setValue( + "transcription_viewer/find_widget_visible", False) def setup_shortcuts(self): """Set up keyboard shortcuts""" from PyQt6.QtGui import QShortcut, QKeySequence # Search shortcut (Ctrl+F) - search_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.SEARCH_TRANSCRIPT)), self) + search_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.SEARCH_TRANSCRIPT)), self) search_shortcut.activated.connect(self.focus_search_input) # Scroll to current text shortcut (Ctrl+G) - scroll_to_current_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.SCROLL_TO_CURRENT_TEXT)), self) - scroll_to_current_shortcut.activated.connect(self.on_scroll_to_current_button_clicked) + scroll_to_current_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.SCROLL_TO_CURRENT_TEXT)), self) + scroll_to_current_shortcut.activated.connect( + self.on_scroll_to_current_button_clicked) # Play/Pause audio shortcut (Ctrl+P) - play_pause_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.PLAY_PAUSE_AUDIO)), self) + play_pause_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.PLAY_PAUSE_AUDIO)), self) play_pause_shortcut.activated.connect(self.toggle_audio_playback) # Replay current segment shortcut (Ctrl+Shift+P) - replay_segment_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.REPLAY_CURRENT_SEGMENT)), self) + replay_segment_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.REPLAY_CURRENT_SEGMENT)), self) replay_segment_shortcut.activated.connect(self.replay_current_segment) # Playback controls visibility shortcut (Ctrl+Alt+P) - playback_controls_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.TOGGLE_PLAYBACK_CONTROLS)), self) - playback_controls_shortcut.activated.connect(self.toggle_playback_controls_visibility) + playback_controls_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.TOGGLE_PLAYBACK_CONTROLS)), self) + playback_controls_shortcut.activated.connect( + self.toggle_playback_controls_visibility) # Segment timestamp adjustment shortcuts - decrease_start_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.DECREASE_SEGMENT_START)), self) + decrease_start_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.DECREASE_SEGMENT_START)), self) decrease_start_shortcut.activated.connect(self.decrease_segment_start) - increase_start_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.INCREASE_SEGMENT_START)), self) + increase_start_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.INCREASE_SEGMENT_START)), self) increase_start_shortcut.activated.connect(self.increase_segment_start) - decrease_end_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.DECREASE_SEGMENT_END)), self) + decrease_end_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.DECREASE_SEGMENT_END)), self) decrease_end_shortcut.activated.connect(self.decrease_segment_end) - increase_end_shortcut = QShortcut(QKeySequence(self.shortcuts.get(Shortcut.INCREASE_SEGMENT_END)), self) + increase_end_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.INCREASE_SEGMENT_END)), self) increase_end_shortcut.activated.connect(self.increase_segment_end) def focus_search_input(self): @@ -935,7 +1056,8 @@ class TranscriptionViewerWidget(QWidget): # Save the visibility state to settings self.find_widget_visible = True - self.settings.settings.setValue("transcription_viewer/find_widget_visible", True) + self.settings.settings.setValue( + "transcription_viewer/find_widget_visible", True) def toggle_search_bar_visibility(self): """Toggle the search bar visibility""" @@ -946,7 +1068,8 @@ class TranscriptionViewerWidget(QWidget): # Save the visibility state to settings self.find_widget_visible = self.search_frame.isVisible() - self.settings.settings.setValue("transcription_viewer/find_widget_visible", self.find_widget_visible) + self.settings.settings.setValue( + "transcription_viewer/find_widget_visible", self.find_widget_visible) def show_search_bar(self): """Show the search bar and focus the input""" @@ -957,7 +1080,8 @@ class TranscriptionViewerWidget(QWidget): # Save the visibility state to settings self.find_widget_visible = True - self.settings.settings.setValue("transcription_viewer/find_widget_visible", True) + self.settings.settings.setValue( + "transcription_viewer/find_widget_visible", True) def eventFilter(self, obj, event): """Event filter to handle keyboard shortcuts in search input""" @@ -974,10 +1098,14 @@ class TranscriptionViewerWidget(QWidget): return super().eventFilter(obj, event) def reset_view(self): + if hasattr(self, 'media_splitter'): + self.load_splitter_sizes() + if self.view_mode == ViewMode.TIMESTAMPS: self.text_display_box.hide() self.table_widget.show() - self.audio_player.show() + if self.current_media_player: + self.current_media_player.show() # Show playback controls in timestamps mode if self.playback_controls_visible: self.loop_controls_frame.show() @@ -989,7 +1117,8 @@ class TranscriptionViewerWidget(QWidget): combined_text = "" previous_end_time = None - paragraph_split_time = int(os.getenv("BUZZ_PARAGRAPH_SPLIT_TIME", "2000")) + paragraph_split_time = int( + os.getenv("BUZZ_PARAGRAPH_SPLIT_TIME", "2000")) for segment in segments: if previous_end_time is not None and (segment.start_time - previous_end_time) >= paragraph_split_time: @@ -1000,12 +1129,13 @@ class TranscriptionViewerWidget(QWidget): self.text_display_box.setPlainText(combined_text.strip()) self.text_display_box.show() self.table_widget.hide() - self.audio_player.hide() + if self.current_media_player: + self.current_media_player.hide() # Hide playback controls in text mode self.loop_controls_frame.hide() # Hide current segment display in text mode self.current_segment_frame.hide() - else: # ViewMode.TRANSLATION + else: # ViewMode.TRANSLATION segments = self.transcription_service.get_transcription_segments( transcription_id=self.transcription.id_as_uuid ) @@ -1014,7 +1144,8 @@ class TranscriptionViewerWidget(QWidget): ) self.text_display_box.show() self.table_widget.hide() - self.audio_player.hide() + if self.current_media_player: + self.current_media_player.hide() # Hide playback controls in translation mode self.loop_controls_frame.hide() # Hide current segment display in translation mode @@ -1048,19 +1179,27 @@ class TranscriptionViewerWidget(QWidget): # Ensure the scroll area updates properly and shows scrollbars when needed self.current_segment_scroll_area.updateGeometry() - self.current_segment_scroll_area.verticalScrollBar().setVisible(True) # Ensure scrollbar is visible + self.current_segment_scroll_area.verticalScrollBar( + ).setVisible(True) # Ensure scrollbar is visible - start_time = segment.value("start_time") - end_time = segment.value("end_time") + start_time_ms = segment.value("start_time") + end_time_ms = segment.value("end_time") - if self.audio_player.position_ms < start_time or self.audio_player.position_ms > end_time: - self.audio_player.set_position(start_time) + if not self.current_media_player: + return + + if self.current_media_player.position_ms < start_time_ms or self.current_media_player.position_ms > end_time_ms: + self.current_media_player.set_position(start_time_ms) + + # Start playing if not yet playing + if self.current_media_player.media_player.playbackState() != QMediaPlayer.PlaybackState.PlayingState: + self.current_media_player.media_player.play() if self.segment_looping_enabled: - self.audio_player.set_range((start_time, end_time)) + self.current_media_player.set_range((start_time_ms, end_time_ms)) # Reset looping flag to ensure new loops work - self.audio_player.is_looping = False + self.current_media_player.is_looping = False else: segments = self.table_widget.segments() for i, seg in enumerate(segments): @@ -1120,8 +1259,8 @@ class TranscriptionViewerWidget(QWidget): # Ensure the scroll area updates properly and shows scrollbars when needed self.current_segment_scroll_area.updateGeometry() - self.current_segment_scroll_area.verticalScrollBar().setVisible(True) # Ensure scrollbar is visible - + self.current_segment_scroll_area.verticalScrollBar( + ).setVisible(True) # Ensure scrollbar is visible # Update highlighting based on follow audio and loop settings if self.follow_audio_enabled: # Follow audio mode: highlight the current segment based on audio position @@ -1136,7 +1275,8 @@ class TranscriptionViewerWidget(QWidget): if current_segment.value("id") != self.currently_selected_segment.value("id"): for i, segment in enumerate(segments): if segment.value("id") == current_segment.value("id"): - self.table_widget.highlight_and_scroll_to_row(i) + self.table_widget.highlight_and_scroll_to_row( + i) break else: # Don't follow audio: keep highlighting on the selected segment @@ -1192,7 +1332,8 @@ class TranscriptionViewerWidget(QWidget): def load_preferences(self): self.settings.settings.beginGroup("file_transcriber") - preferences = FileTranscriptionPreferences.load(settings=self.settings.settings) + preferences = FileTranscriptionPreferences.load( + settings=self.settings.settings) self.settings.settings.endGroup() return preferences @@ -1215,7 +1356,8 @@ class TranscriptionViewerWidget(QWidget): return if self.transcription_options.llm_model == "" or self.transcription_options.llm_prompt == "": - self.transcription_options_dialog.accepted.connect(self.run_translation) + self.transcription_options_dialog.accepted.connect( + self.run_translation) self.transcription_options_dialog.show() return @@ -1259,10 +1401,10 @@ class TranscriptionViewerWidget(QWidget): """Handle loop toggle state change""" self.segment_looping_enabled = enabled # Save preference to settings - self.settings.settings.setValue("transcription_viewer/segment_looping_enabled", enabled) - + self.settings.settings.setValue( + "transcription_viewer/segment_looping_enabled", enabled) if enabled: - # If looping is re-enabled and we have a selected segment, return to it + # If looping is re-enabled,and we have a selected segment, return to it if self.currently_selected_segment is not None: # Find the row index of the selected segment segments = self.table_widget.segments() @@ -1271,31 +1413,35 @@ class TranscriptionViewerWidget(QWidget): # Highlight and scroll to the selected segment self.table_widget.highlight_and_scroll_to_row(i) - # Get the segment timing - start_time = self.currently_selected_segment.value("start_time") - end_time = self.currently_selected_segment.value("end_time") - + start_time_ms = self.currently_selected_segment.value( + "start_time") + end_time_ms = self.currently_selected_segment.value( + "end_time") # Set the loop range for the selected segment - self.audio_player.set_range((start_time, end_time)) + if self.current_media_player: + self.current_media_player.set_range( + (start_time_ms, end_time_ms)) - # If audio is currently playing and outside the range, jump to the start - current_pos = self.audio_player.position_ms - playback_state = self.audio_player.media_player.playbackState() - if (playback_state == QMediaPlayer.PlaybackState.PlayingState and - (current_pos < start_time or current_pos > end_time)): - self.audio_player.set_position(start_time) + # If audio is currently playing and outside the range, jump to the start + current_pos = self.current_media_player.position_ms + playback_state = self.current_media_player.media_player.playbackState() + if (playback_state == QMediaPlayer.PlaybackState.PlayingState and + (current_pos < start_time_ms or current_pos > end_time_ms)): + self.current_media_player.set_position( + start_time_ms) - break + break else: # Clear any existing range if looping is disabled - self.audio_player.clear_range() + if self.current_media_player: + self.current_media_player.clear_range() def on_follow_audio_toggle_changed(self, enabled: bool): """Handle follow audio toggle state change""" self.follow_audio_enabled = enabled # Save preference to settings - self.settings.settings.setValue("transcription_viewer/follow_audio_enabled", enabled) - + self.settings.settings.setValue( + "transcription_viewer/follow_audio_enabled", enabled) if enabled: # When follow audio is first enabled, automatically scroll to current position # This gives immediate feedback that the feature is working @@ -1311,7 +1457,9 @@ class TranscriptionViewerWidget(QWidget): def on_scroll_to_current_button_clicked(self): """Handle scroll to current button click""" - current_pos = self.audio_player.position_ms + if not self.current_media_player: + return + current_pos = self.current_media_player.position_ms segments = self.table_widget.segments() # Find the current segment based on audio position @@ -1334,7 +1482,7 @@ class TranscriptionViewerWidget(QWidget): self.highlight_table_match(1) self.highlight_table_match(current_segment_index) - self.audio_player.set_position(current_pos) + self.current_media_player.set_position(current_pos) def auto_scroll_to_current_position(self): """ @@ -1346,7 +1494,9 @@ class TranscriptionViewerWidget(QWidget): if self.view_mode != ViewMode.TIMESTAMPS: return - current_pos = self.audio_player.position_ms + if not self.current_media_player: + return + current_pos = self.current_media_player.position_ms segments = self.table_widget.segments() # Find the current segment based on audio position @@ -1371,13 +1521,22 @@ class TranscriptionViewerWidget(QWidget): def resizeEvent(self, event): """Save geometry when widget is resized""" self.save_geometry() + self.save_splitter_sizes() super().resizeEvent(event) def closeEvent(self, event): """Save geometry when widget is closed""" self.save_geometry() + + # save splitter sizes before closing + self.save_splitter_sizes() + self.hide() + # Stop media playback when closing + if self.current_media_player: + self.current_media_player.stop() + if self.transcription_resizer_dialog: self.transcription_resizer_dialog.close() @@ -1392,7 +1551,8 @@ class TranscriptionViewerWidget(QWidget): # Wait up to 35 seconds for graceful shutdown # (30s max API call timeout + 5s buffer) if not self.translation_thread.wait(35_000): - logging.warning("Translation thread did not finish gracefully, terminating") + logging.warning( + "Translation thread did not finish gracefully, terminating") # Force terminate the thread if it doesn't stop self.translation_thread.terminate() # Give it a brief moment to terminate @@ -1417,3 +1577,47 @@ class TranscriptionViewerWidget(QWidget): # Default size if no saved geometry self.resize(1000, 800) self.settings.end_group() + + def save_splitter_sizes(self): + """Save splitter sizes to settings""" + if not hasattr(self, 'media_splitter'): + return + + sizes = self.media_splitter.sizes() + self.settings.begin_group(Settings.Key.TRANSCRIPTION_VIEWER) + + # Save separately for video and audio + if self.current_media_player == self.video_player: + self.settings.settings.setValue("video_splitter_sizes", sizes) + else: + self.settings.settings.setValue("audio_splitter_sizes", sizes) + + self.settings.end_group() + + def load_splitter_sizes(self): + """Load splitter sizes from settings""" + if not hasattr(self, 'media_splitter'): + return + + self.settings.begin_group(Settings.Key.TRANSCRIPTION_VIEWER) + + # Load sizes based on media type + if self.current_media_player == self.video_player: + sizes = self.settings.settings.value("video_splitter_sizes") + if sizes is None: + sizes = [800, 200] + else: + sizes = self.settings.settings.value("audio_splitter_sizes") + if sizes is None: + sizes = [950, 50] + + self.settings.end_group() + + # Apply sizes + if sizes: + self.media_splitter.setSizes([int(s) for s in sizes]) + + def on_splitter_moved(self, pos: int, index: int): + """Called when user moves the splitter""" + # Save sizes after a short delay to avoid saving on every pixel move + QTimer.singleShot(100, self.save_splitter_sizes) diff --git a/buzz/widgets/video_player.py b/buzz/widgets/video_player.py new file mode 100644 index 00000000..daaa6912 --- /dev/null +++ b/buzz/widgets/video_player.py @@ -0,0 +1,160 @@ +from typing import Tuple, Optional +from PyQt6.QtCore import Qt, QUrl, pyqtSignal, QTime +from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput +from PyQt6.QtMultimediaWidgets import QVideoWidget +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QSlider, QPushButton, QHBoxLayout, QLabel, QSizePolicy +from buzz.widgets.icon import PlayIcon, PauseIcon + +class VideoPlayer(QWidget): + position_ms_changed = pyqtSignal(int) + + def __init__(self, file_path: str, parent=None): + super().__init__(parent) + + self.range_ms: Optional[Tuple[int, int]] = None + self.position_ms = 0 + self.duration_ms = 0 + self.is_looping = False + self.is_slider_dragging = False + self.initial_frame_loaded = False + + self.audio_output = QAudioOutput(self) + self.audio_output.setVolume(100) + + self.media_player = QMediaPlayer(self) + self.media_player.setSource(QUrl.fromLocalFile(file_path)) + self.media_player.setAudioOutput(self.audio_output) + + self.video_widget = QVideoWidget(self) + self.media_player.setVideoOutput(self.video_widget) + + # Size constraints for video widget + self.video_widget.setMinimumHeight(200) + self.video_widget.setMaximumHeight(400) + self.video_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + + self.scrubber = QSlider(Qt.Orientation.Horizontal) + self.scrubber.setRange(0, 0) + self.scrubber.sliderMoved.connect(self.on_slider_moved) + self.scrubber.sliderPressed.connect(self.on_slider_pressed) + self.scrubber.sliderReleased.connect(self.on_slider_released) + + #Track if user is dragging the slider + self.is_slider_dragging = False + + self.play_icon = PlayIcon(self) + self.pause_icon = PauseIcon(self) + + self.play_button = QPushButton("") + self.play_button.setIcon(self.play_icon) + self.play_button.clicked.connect(self.toggle_playback) + self.play_button.setMaximumWidth(40) + self.play_button.setMinimumHeight(30) + + self.time_label = QLabel() + self.time_label.setAlignment(Qt.AlignmentFlag.AlignRight) + + controls = QHBoxLayout() + controls.addWidget(self.play_button) + controls.addWidget(self.scrubber) + controls.addWidget(self.time_label) + + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(4) + layout.addWidget(self.video_widget, stretch=1) + layout.addLayout(controls) + + self.setLayout(layout) + + + self.media_player.positionChanged.connect(self.on_position_changed) + self.media_player.durationChanged.connect(self.on_duration_changed) + self.media_player.playbackStateChanged.connect(self.on_playback_state_changed) + self.media_player.mediaStatusChanged.connect(self.on_media_status_changed) + + def on_media_status_changed(self, status: QMediaPlayer.MediaStatus): + # Only do this once on initial load to show first frame + if self.initial_frame_loaded: + return + # Start playback when loaded to trigger frame decoding + if status == QMediaPlayer.MediaStatus.LoadedMedia: + self.media_player.play() + # Pause immediately when buffered to show first frame + elif status == QMediaPlayer.MediaStatus.BufferedMedia: + self.initial_frame_loaded = True + self.media_player.pause() + + def toggle_playback(self): + if self.media_player.playbackState() == QMediaPlayer.PlaybackState.PlayingState: + self.media_player.pause() + else: + self.media_player.play() + + def on_slider_moved(self, position): + self.set_position(position) + + def on_slider_pressed(self): + """Called when user starts dragging the slider""" + self.is_slider_dragging = True + + def on_slider_released(self): + """Called when user releases the slider""" + self.is_slider_dragging = False + # Update position to where use released + self.set_position(self.scrubber.value()) + + def set_position(self, position_ms: int): + self.media_player.setPosition(position_ms) + + def on_position_changed(self, position_ms: int): + # Don't update slider if user is currently dragging it + if not self.is_slider_dragging: + self.scrubber.blockSignals(True) + self.scrubber.setValue(position_ms) + self.scrubber.blockSignals(False) + + self.position_ms = position_ms + self.position_ms_changed.emit(position_ms) + self.update_time_label() + + # If a range has been selected and video has reached the end of range + #loop back to the start of the range + if self.range_ms is not None and not self.is_looping: + start_range_ms, end_range_ms = self.range_ms + #Check if video is at or past the end of range + if position_ms >= (end_range_ms - 50): + self.is_looping = True + self.set_position(start_range_ms) + self.is_looping = False + + def on_duration_changed(self, duration_ms: int): + self.scrubber.setRange(0, duration_ms) + self.duration_ms = duration_ms + self.update_time_label() + + def on_playback_state_changed(self, state: QMediaPlayer.PlaybackState): + if state == QMediaPlayer.PlaybackState.PlayingState: + self.play_button.setIcon(self.pause_icon) + else: + self.play_button.setIcon(self.play_icon) + + def update_time_label(self): + position_time = QTime(0, 0).addMSecs(self.position_ms).toString() + duration_time = QTime(0, 0).addMSecs(self.duration_ms).toString() + self.time_label.setText(f"{position_time} / {duration_time}") + + def set_range(self, range_ms: Tuple[int, int]): + """Set a loop range. Only jump to start if current position is outside the range.""" + self.range_ms = range_ms + start_range_ms, end_range_ms = range_ms + + if self.position_ms < start_range_ms or self.position_ms > end_range_ms: + self.set_position(start_range_ms) + + def clear_range(self): + """Clear the current loop range""" + self.range_ms = None + + def stop(self): + self.media_player.stop() diff --git a/tests/widgets/transcription_viewer_test.py b/tests/widgets/transcription_viewer_test.py index a688c40b..883390fe 100644 --- a/tests/widgets/transcription_viewer_test.py +++ b/tests/widgets/transcription_viewer_test.py @@ -716,19 +716,21 @@ class TestTranscriptionViewerWidget: ) qtbot.add_widget(widget) - # Test that main layout has proper stretch factors - # Table widget should have stretch factor 1 (majority of space) - # Other widgets should have stretch factor 0 (minimal space) + # Test that main layout has proper structure + # Table widget should be in the media_splitter (not directly in main layout) main_layout = widget.layout() - # Find the table widget in the layout - table_widget_index = None + # Find the media_splitter in the layout + splitter_index = None for i in range(main_layout.count()): - if main_layout.itemAt(i).widget() == widget.table_widget: - table_widget_index = i + if main_layout.itemAt(i).widget() == widget.media_splitter: + splitter_index = i break - assert table_widget_index is not None, "Table widget should be in main layout" + assert splitter_index is not None, "Media splitter should be in main layout" + + # Verify table_widget is inside the splitter + assert widget.media_splitter.indexOf(widget.table_widget) != -1, "Table widget should be in media splitter" # Test that current segment frame has minimal stretch current_segment_index = None diff --git a/tests/widgets/video_player_test.py b/tests/widgets/video_player_test.py new file mode 100644 index 00000000..2bb9fe11 --- /dev/null +++ b/tests/widgets/video_player_test.py @@ -0,0 +1,274 @@ +import os +import pytest + +from PyQt6.QtCore import QTime +from PyQt6.QtMultimedia import QMediaPlayer +from PyQt6.QtWidgets import QVBoxLayout +from pytestqt.qtbot import QtBot + +from buzz.widgets.video_player import VideoPlayer +from tests.audio import test_audio_path + + +def assert_approximately_equal(actual, expected, tolerance=0.001): + """Helper function to compare values with tolerance for floating-point precision""" + assert abs(actual - expected) < tolerance, f"Value {actual} is not approximately equal to {expected}" + + +class TestVideoPlayer: + def test_should_load_media(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + actual = os.path.normpath(widget.media_player.source().toLocalFile()) + expected = os.path.normpath(test_audio_path) + assert actual == expected + + def test_should_update_time_label(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + widget.on_duration_changed(2000) + widget.on_position_changed(1000) + + position_time = QTime(0, 0).addMSecs(1000).toString() + duration_time = QTime(0, 0).addMSecs(2000).toString() + + assert widget.time_label.text() == f"{position_time} / {duration_time}" + + def test_should_toggle_play_button_icon(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + widget.on_playback_state_changed(QMediaPlayer.PlaybackState.PlayingState) + assert widget.play_button.icon().themeName() == widget.pause_icon.themeName() + + widget.on_playback_state_changed(QMediaPlayer.PlaybackState.PausedState) + assert widget.play_button.icon().themeName() == widget.play_icon.themeName() + + widget.on_playback_state_changed(QMediaPlayer.PlaybackState.StoppedState) + assert widget.play_button.icon().themeName() == widget.play_icon.themeName() + + def test_should_have_basic_video_controls(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + assert widget.play_button is not None + assert widget.scrubber is not None + assert widget.time_label is not None + assert widget.video_widget is not None + + # Verify the widget loads media correctly + assert widget.media_player is not None + assert os.path.normpath(widget.media_player.source().toLocalFile()) == os.path.normpath(test_audio_path) + + def test_should_change_playback_rate_directly(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + widget.media_player.setPlaybackRate(1.5) + assert_approximately_equal(widget.media_player.playbackRate(), 1.5) + + def test_should_handle_various_playback_rates(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + widget.media_player.setPlaybackRate(0.5) + assert_approximately_equal(widget.media_player.playbackRate(), 0.5) + + widget.media_player.setPlaybackRate(2.0) + assert_approximately_equal(widget.media_player.playbackRate(), 2.0) + + def test_should_use_vertical_layout(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Verify the layout structure - VideoPlayer uses VBoxLayout + layout = widget.layout() + assert isinstance(layout, QVBoxLayout) + # video_widget + controls layout + assert layout.count() == 2 + + def test_should_handle_range_looping(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Test range setting and looping functionality + widget.set_range((1000, 3000)) # 1-3 seconds + assert widget.range_ms == (1000, 3000) + + # Clear range + widget.clear_range() + assert widget.range_ms is None + + def test_should_stop_playback(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Test stop functionality + widget.stop() + assert widget.media_player.playbackState() == QMediaPlayer.PlaybackState.StoppedState + + def test_should_set_position(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Test position setting + widget.set_position(1000) + # Position may not be exactly 1000 due to media player internals + # but the method should execute without error + + def test_should_track_slider_dragging(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Initially not dragging + assert widget.is_slider_dragging is False + + # Simulate slider press + widget.on_slider_pressed() + assert widget.is_slider_dragging is True + + # Simulate slider release + widget.on_slider_released() + assert widget.is_slider_dragging is False + + def test_should_emit_position_changed_signal(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Track signal emission + with qtbot.waitSignal(widget.position_ms_changed, timeout=1000): + widget.on_position_changed(500) + + assert widget.position_ms == 500 + + def test_should_update_scrubber_range_on_duration_change(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + widget.on_duration_changed(5000) + assert widget.scrubber.maximum() == 5000 + assert widget.duration_ms == 5000 + + def test_should_toggle_playback(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Test toggle functionality exists + assert hasattr(widget, 'toggle_playback') + + def test_should_have_video_widget_constraints(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Verify video widget size constraints + assert widget.video_widget.minimumHeight() == 200 + assert widget.video_widget.maximumHeight() == 400 + + def test_should_have_audio_output(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + assert widget.audio_output is not None + assert widget.media_player.audioOutput() == widget.audio_output + + def test_should_handle_range_with_position_outside(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Set position outside the range first + widget.position_ms = 5000 + + # Set range - should jump to start since position is outside + widget.set_range((1000, 3000)) + assert widget.range_ms == (1000, 3000) + + def test_should_handle_range_with_position_inside(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Set position inside the range first + widget.position_ms = 2000 + + # Set range - should NOT jump since position is inside + widget.set_range((1000, 3000)) + assert widget.range_ms == (1000, 3000) + + def test_should_loop_at_range_end(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Set a range + widget.set_range((1000, 3000)) + + # Simulate reaching end of range + widget.is_looping = False + widget.on_position_changed(2960) # Just before end (within 50ms threshold) + + # The looping flag should be set during the loop operation + # After on_position_changed completes, is_looping should be False again + assert widget.is_looping is False + + def test_should_not_update_scrubber_while_dragging(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # First set a valid range for the scrubber + widget.on_duration_changed(5000) + + # Set a known value within the range + widget.scrubber.setValue(1000) + + # Start dragging + widget.on_slider_pressed() + + # Position change while dragging should not update scrubber + widget.on_position_changed(2000) + + # Scrubber value should still be 1000 (not updated during drag) + assert widget.scrubber.value() == 1000 + + # Release slider + widget.on_slider_released() + + def test_should_update_scrubber_when_not_dragging(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # First set a valid range for the scrubber + widget.on_duration_changed(5000) + + # Ensure not dragging + widget.is_slider_dragging = False + + # Position change when not dragging should update scrubber + widget.on_position_changed(2000) + + assert widget.scrubber.value() == 2000 + + def test_initial_frame_loading(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + # Initial state + assert widget.initial_frame_loaded is False + + # Simulate media loaded - should trigger play + widget.on_media_status_changed(QMediaPlayer.MediaStatus.LoadedMedia) + + # Simulate buffered - should pause and set flag + widget.on_media_status_changed(QMediaPlayer.MediaStatus.BufferedMedia) + assert widget.initial_frame_loaded is True + + # Further status changes should be ignored + widget.on_media_status_changed(QMediaPlayer.MediaStatus.LoadedMedia) + # Should still be True (not reset) + assert widget.initial_frame_loaded is True + + def test_play_button_sizing(self, qtbot: QtBot): + widget = VideoPlayer(test_audio_path) + qtbot.add_widget(widget) + + assert widget.play_button.maximumWidth() == 40 + assert widget.play_button.minimumHeight() == 30 From 20ed2be44c16fec3576bd3fcc203d42a284e60a7 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Thu, 11 Dec 2025 21:28:10 +0200 Subject: [PATCH 19/73] Search improvement (#1307) --- buzz/file_transcriber_queue_worker.py | 4 +- buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 85 ++++++++++-------- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/en_US/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 87 ++++++++++-------- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 85 ++++++++++-------- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 88 +++++++++--------- buzz/locale/nl/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 85 ++++++++++-------- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 90 ++++++++++--------- buzz/settings/shortcut.py | 2 + .../transcription_viewer_widget.py | 29 +++++- tests/widgets/shortcuts_editor_widget_test.py | 2 + tests/widgets/transcription_viewer_test.py | 8 +- 19 files changed, 715 insertions(+), 570 deletions(-) diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index 99b43af7..06ab099f 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -230,8 +230,8 @@ class FileTranscriberQueueWorker(QObject): if self.speech_path is not None: try: Path(self.speech_path).unlink() - except Exception as e: - logging.error(f"Error deleting temporary speech file: {e}", exc_info=True) + except Exception: + pass self.speech_path = None def stop(self): diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index 49f8ef9a..7f797f14 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -435,12 +435,12 @@ msgstr "Nova transcripció d'URL" msgid "Open Transcript" msgstr "Obre una transcripció" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Cancel·la la transcripció" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Neteja l'historial" @@ -616,68 +616,69 @@ msgstr "Veure" msgid "Timestamps" msgstr "Marqua de temps" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Exporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Traduir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "Cerca" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/amaga la barra de cerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "Cerca:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "Introduïu el text a cercar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "Coincidència anterior (Maj+Retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#, fuzzy +msgid "Next match (Ctrl+Enter)" msgstr "Coincidència següent (retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "Neteja" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "Controls de reproducció:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "Segment de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Activa/desactiva el bucle en fer clic als segments de transcripció" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "Segueix l'àudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -685,44 +686,44 @@ msgstr "" "Activa/desactiva seguint la posició d'àudio actual a la transcripció. Quan " "està activada, es desplaça automàticament al text actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "Desplaça't fins a l'actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "Desplaçar-se fins al text que es parla actualment" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "1 de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr " coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "No s'ha trobat cap coincidència" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr " de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "Clau API necessària" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Introduïu la clau API d'OpenAI a les preferències" @@ -1328,34 +1329,42 @@ msgid "Search Transcript" msgstr "Cerca una transcripció" #: buzz/settings/shortcut.py:26 +msgid "Go to Next Transcript Search Result" +msgstr "" + +#: buzz/settings/shortcut.py:27 +msgid "Go to Previous Transcript Search Result" +msgstr "" + +#: buzz/settings/shortcut.py:28 msgid "Scroll to Current Text" msgstr "Desplaça't fins al text actual" -#: buzz/settings/shortcut.py:27 +#: buzz/settings/shortcut.py:29 msgid "Play/Pause Audio" msgstr "Reproduir/posar en pausa l'àudio" -#: buzz/settings/shortcut.py:28 +#: buzz/settings/shortcut.py:30 msgid "Replay Current Segment" msgstr "Reprodueix el segment actual" -#: buzz/settings/shortcut.py:29 +#: buzz/settings/shortcut.py:31 msgid "Toggle Playback Controls" msgstr "Commuta els controls de reproducció" -#: buzz/settings/shortcut.py:31 +#: buzz/settings/shortcut.py:33 msgid "Decrease Segment Start Time" msgstr "Disminuir l'hora d'inici del segment" -#: buzz/settings/shortcut.py:32 +#: buzz/settings/shortcut.py:34 msgid "Increase Segment Start Time" msgstr "Augmenta l'hora d'inici del segment" -#: buzz/settings/shortcut.py:33 +#: buzz/settings/shortcut.py:35 msgid "Decrease Segment End Time" msgstr "Disminueix l'hora de finalització del segment" -#: buzz/settings/shortcut.py:34 +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "Augmenta l'hora de finalització del segment" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index 9ebdebcb..f1638540 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -433,12 +433,12 @@ msgstr "Ny URL transkription" msgid "Open Transcript" msgstr "Åben transkription" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Afbryd transkription" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Ryd historik" @@ -613,111 +613,111 @@ msgstr "Vis" msgid "Timestamps" msgstr "Tidsstempler" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Eksporter" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Oversæt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Behandel størrelse" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "API-nøgle påkrævet" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Indtast venligst OpenAI API-nøgle i indstillinger" @@ -1320,34 +1320,42 @@ msgid "Search Transcript" msgstr "Åben transkription" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index 2e9294f4..574689a8 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -433,12 +433,12 @@ msgstr "Neue URL-Transkription" msgid "Open Transcript" msgstr "Transkript öffnen" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Transkription abbrechen" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Verlauf löschen" @@ -614,111 +614,111 @@ msgstr "Anzeigen" msgid "Timestamps" msgstr "Zeitstempel" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Export" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Übersetzen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Größe ändern" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "API-Schlüssel erforderlich" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Bitte geben Sie den OpenAI-API-Schlüssel in den Einstellungen ein" @@ -1322,34 +1322,42 @@ msgid "Search Transcript" msgstr "Transkript öffnen" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index d16cb5c7..4bc17730 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -423,12 +423,12 @@ msgstr "" msgid "Open Transcript" msgstr "" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "" @@ -598,111 +598,111 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -1294,34 +1294,42 @@ msgid "Search Transcript" msgstr "" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index 2166f396..a8c0b3e4 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -458,13 +458,13 @@ msgid "Open Transcript" msgstr "Abrir transcripción" # automatic translation -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Cancelar transcripción" # automatic translation #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Vaciar historial" @@ -654,70 +654,71 @@ msgstr "Ver" msgid "Timestamps" msgstr "Marcas de tiempo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Traducir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Cambiar el tamaño" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "Buscar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar barra de búsqueda (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "Encontrar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "Introducir texto para encontrar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "Coincidencia anterior (Mayús+Intro)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#, fuzzy +msgid "Next match (Ctrl+Enter)" msgstr "Siguiente coincidencia (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "Limpiar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "Controles de reproducción:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "Segmento de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Activar/desactivar la reproducción en bucle al hacer clic en segmentos de la " "transcripción" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "Seguir audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -726,44 +727,44 @@ msgstr "" "transcripción. Cuando está activado, se desplaza automáticamente al texto " "actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "Desplácese hasta Actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "Desplazarse hasta el texto hablado actualmente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "1 de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr " coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "No se encontraron coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr " de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "Clave de API requerida" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Ingrese la clave API de OpenAI en las preferencias" @@ -1387,34 +1388,42 @@ msgid "Search Transcript" msgstr "Buscar transcripción" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" -msgstr "Desplazarse al texto actual" +msgid "Go to Next Transcript Search Result" +msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 +msgid "Scroll to Current Text" +msgstr "Desplazarse al texto actual" + +#: buzz/settings/shortcut.py:29 +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 msgid "Replay Current Segment" msgstr "" -#: buzz/settings/shortcut.py:29 +#: buzz/settings/shortcut.py:31 msgid "Toggle Playback Controls" msgstr "Alternar controles de reproducción" -#: buzz/settings/shortcut.py:31 +#: buzz/settings/shortcut.py:33 msgid "Decrease Segment Start Time" msgstr "" -#: buzz/settings/shortcut.py:32 +#: buzz/settings/shortcut.py:34 msgid "Increase Segment Start Time" msgstr "" -#: buzz/settings/shortcut.py:33 +#: buzz/settings/shortcut.py:35 msgid "Decrease Segment End Time" msgstr "" -#: buzz/settings/shortcut.py:34 +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index efb8cb82..d8c13272 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -436,12 +436,12 @@ msgstr "Nuova trascrizione URL" msgid "Open Transcript" msgstr "Apri trascrizione" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Annulla trascrizione" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Elimina la cronologia" @@ -617,69 +617,70 @@ msgstr "Visualizza" msgid "Timestamps" msgstr "Timestamp" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Esporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Tradurre" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Ridimensionare" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "Trova" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/Nascondi barra di ricerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "Trova:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "Inserisci il testo per trovare..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "Corrispondenza precedente (Maiusc+Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#, fuzzy +msgid "Next match (Ctrl+Enter)" msgstr "Prossima corrispondenza (Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "Elimina" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "Controlli di riproduzione:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "Ciclo di segmento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Abilita/disabilita il loop quando si fa clic sui segmenti della trascrizione" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "Segui Audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -688,44 +689,44 @@ msgstr "" "trascrizione. Quando abilitato, scorre automaticamente fino al testo " "corrente." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "Scorri fino al Corrente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "Scorrere fino al testo attualmente pronunciato" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "1 di 100+ corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "1 di" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "Nessuna corrispondenza trovata" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr " di oltre 100 corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr " di " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "Chiave API richiesta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Inserisci la chiave API OpenAI nelle preferenze" @@ -1329,34 +1330,42 @@ msgid "Search Transcript" msgstr "Cerca trascrizione" #: buzz/settings/shortcut.py:26 +msgid "Go to Next Transcript Search Result" +msgstr "" + +#: buzz/settings/shortcut.py:27 +msgid "Go to Previous Transcript Search Result" +msgstr "" + +#: buzz/settings/shortcut.py:28 msgid "Scroll to Current Text" msgstr "Scorri fino al testo corrente" -#: buzz/settings/shortcut.py:27 +#: buzz/settings/shortcut.py:29 msgid "Play/Pause Audio" msgstr "Riproduci/Pausa audio" -#: buzz/settings/shortcut.py:28 +#: buzz/settings/shortcut.py:30 msgid "Replay Current Segment" msgstr "Riproduci il segmento corrente" -#: buzz/settings/shortcut.py:29 +#: buzz/settings/shortcut.py:31 msgid "Toggle Playback Controls" msgstr "Attiva/disattiva i controlli di riproduzione" -#: buzz/settings/shortcut.py:31 +#: buzz/settings/shortcut.py:33 msgid "Decrease Segment Start Time" msgstr "Riduci l'ora di inizio del segmento" -#: buzz/settings/shortcut.py:32 +#: buzz/settings/shortcut.py:34 msgid "Increase Segment Start Time" msgstr "Aumenta l'ora di inizio del segmento" -#: buzz/settings/shortcut.py:33 +#: buzz/settings/shortcut.py:35 msgid "Decrease Segment End Time" msgstr "Diminuisci l'ora di fine del segmento" -#: buzz/settings/shortcut.py:34 +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "Aumenta l'ora di fine del segmento" diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index 574c9d6c..82c7ef2c 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -429,12 +429,12 @@ msgstr "新しい文字起こし" msgid "Open Transcript" msgstr "文字起こしを開く" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "文字起こしをキャンセルする" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "履歴を削除する" @@ -609,111 +609,111 @@ msgstr "表示" msgid "Timestamps" msgstr "タイムスタンプ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "出力" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "翻訳" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "リサイズ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "APIキーが必要" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "設定画面でOpenAI APIキーを入力してください" @@ -1315,34 +1315,42 @@ msgid "Search Transcript" msgstr "文字起こしを開く" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index 600fdf03..6c095e93 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" -"PO-Revision-Date: 2025-12-06 11:34+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"PO-Revision-Date: 2025-12-11 20:23+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -438,12 +438,12 @@ msgstr "Jauna saites atpazīšana" msgid "Open Transcript" msgstr "Atvērt transkriptu" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Atcelt atpazīšanu" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Notīrīt vēsturi" @@ -616,68 +616,68 @@ msgstr "Skats" msgid "Timestamps" msgstr "Laiks" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Eksportēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Tulkot" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Mainīt garumu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "Noteikt runātājus" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "Meklēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Rādīt/Slēpt meklēšanas joslu (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "Meklēt:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "Ievadiet meklējamo..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "Iepriekšējais rezultāts (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" -msgstr "Nākamais rezultāts (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" +msgstr "Nākamais rezultāts (Ctrl+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "Notīrīt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "Atskaņošanas iespējas:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "Atkārtot segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Nosaka vai atkārtot izvēlēto segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "Sekot audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -685,44 +685,44 @@ msgstr "" "Nosaka vai atskaņojot audio iezīmētajam segmentam vajadzētu automātiski " "sekot tam kas tiek atskaņots." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "Pāriet uz tekošo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "Pāriet uz šobrīd atskaņojamo tesktu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "1 no 100+ " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "1 no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr " " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "Nekas nav atrasts" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr " no 100+" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr " no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "API atslēgas kļūda" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Lūdzu ievadiet OpenAI API atslēgu iestatījumos" @@ -1323,34 +1323,42 @@ msgid "Search Transcript" msgstr "Meklēt tekstā" #: buzz/settings/shortcut.py:26 +msgid "Go to Next Transcript Search Result" +msgstr "Pāriet uz nākamo meklēšanas rezultātu" + +#: buzz/settings/shortcut.py:27 +msgid "Go to Previous Transcript Search Result" +msgstr "Pāriet uz iepriekšējo meklēšanas rezultātu" + +#: buzz/settings/shortcut.py:28 msgid "Scroll to Current Text" msgstr "Pāriet uz atskaņojamo tesktu" -#: buzz/settings/shortcut.py:27 +#: buzz/settings/shortcut.py:29 msgid "Play/Pause Audio" msgstr "Atskaņot/Apturēt audio" -#: buzz/settings/shortcut.py:28 +#: buzz/settings/shortcut.py:30 msgid "Replay Current Segment" msgstr "Atskaņot segmentu no sākuma" -#: buzz/settings/shortcut.py:29 +#: buzz/settings/shortcut.py:31 msgid "Toggle Playback Controls" msgstr "Pārslēgt atskaņošanas iespējas" -#: buzz/settings/shortcut.py:31 +#: buzz/settings/shortcut.py:33 msgid "Decrease Segment Start Time" msgstr "Samazināt segmenta sākuma laiku" -#: buzz/settings/shortcut.py:32 +#: buzz/settings/shortcut.py:34 msgid "Increase Segment Start Time" msgstr "Palielināt segmenta sākuma laiku" -#: buzz/settings/shortcut.py:33 +#: buzz/settings/shortcut.py:35 msgid "Decrease Segment End Time" msgstr "Samazināt segmenta beigu laiku" -#: buzz/settings/shortcut.py:34 +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "Palielināt segmenta beigu laiku" diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index 7ae6ad7d..d895e113 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -435,12 +435,12 @@ msgstr "Nieuwe url-transcriptie" msgid "Open Transcript" msgstr "Transcriptie openen" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Transcriptie wissen" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Geschiedenis wissen" @@ -614,111 +614,111 @@ msgstr "Bekijken" msgid "Timestamps" msgstr "Tijdstippen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Exporteren" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Vertalen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Grootte" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "Api-sleutel vereist" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Voer de OpenAI-api-sleutel in in de instellingen" @@ -1320,34 +1320,42 @@ msgid "Search Transcript" msgstr "Transcriptie openen" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index fb2ab0c0..6df447cf 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -437,12 +437,12 @@ msgstr "Nowa transkrypcja" msgid "Open Transcript" msgstr "Otwórz transkrypt" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Anuluj transkrypcję" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Wyczyść historię" @@ -622,111 +622,111 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -1335,34 +1335,42 @@ msgid "Search Transcript" msgstr "Otwórz transkrypt" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 3ec83eb3..22fbb3e3 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -433,12 +433,12 @@ msgstr "Nova Transcrição de URL" msgid "Open Transcript" msgstr "Abrir Transcrição" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Cancelar Transcrição" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Limpar Histórico" @@ -614,68 +614,69 @@ msgstr "Visualizar" msgid "Timestamps" msgstr "Marcações de tempo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Traduzir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "Procurar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar a Barra de Pesquisa" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "Procurar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "Digite o texto a procurar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "Encontro prévio (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#, fuzzy +msgid "Next match (Ctrl+Enter)" msgstr "Póximo encontro (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "Limpar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "Controles de Reprodução:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "Segmento de Loop" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Habilitar/desabilitar loop ao clicar em segmentos de transcrição" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "Siga o Áudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -683,44 +684,44 @@ msgstr "" "Ativar/desativar a opção de seguir a posição atual do áudio na transcrição. " "Quando ativado, rola automaticamente para o texto atual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "Rolar para o Atual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "Role até o texto falado no momento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "1 de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr " encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "Nada encontrado" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr " de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "Chave API Necessária" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Insira a chave API OpenAI nas preferências" @@ -1324,34 +1325,42 @@ msgid "Search Transcript" msgstr "Pesquisar Transcrição" #: buzz/settings/shortcut.py:26 +msgid "Go to Next Transcript Search Result" +msgstr "" + +#: buzz/settings/shortcut.py:27 +msgid "Go to Previous Transcript Search Result" +msgstr "" + +#: buzz/settings/shortcut.py:28 msgid "Scroll to Current Text" msgstr "Role até o Texto Atual" -#: buzz/settings/shortcut.py:27 +#: buzz/settings/shortcut.py:29 msgid "Play/Pause Audio" msgstr "Tocar/Pausar o Áudio" -#: buzz/settings/shortcut.py:28 +#: buzz/settings/shortcut.py:30 msgid "Replay Current Segment" msgstr "Repetir o Segmento Atual" -#: buzz/settings/shortcut.py:29 +#: buzz/settings/shortcut.py:31 msgid "Toggle Playback Controls" msgstr "Alternar Controles de Reprodução" -#: buzz/settings/shortcut.py:31 +#: buzz/settings/shortcut.py:33 msgid "Decrease Segment Start Time" msgstr "Diminuir o Inicio do Segmento" -#: buzz/settings/shortcut.py:32 +#: buzz/settings/shortcut.py:34 msgid "Increase Segment Start Time" msgstr "Aumentar o Início do Segmento" -#: buzz/settings/shortcut.py:33 +#: buzz/settings/shortcut.py:35 msgid "Decrease Segment End Time" msgstr "Diminuir o Final do Segmento" -#: buzz/settings/shortcut.py:34 +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "Estender o Final do Segmento" diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index f99cb036..ba783992 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -431,12 +431,12 @@ msgstr "Нова транскрипція" msgid "Open Transcript" msgstr "Відкрити транскрипцію" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "Скасувати транскрипцію" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Очистити історію" @@ -611,111 +611,111 @@ msgstr "Вигляд" msgid "Timestamps" msgstr "Позначки часу" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "Експорт" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "Перекласти" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "Потрібен API-ключ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "Будь ласка, введіть API-ключ OpenAI в налаштуваннях" @@ -1316,34 +1316,42 @@ msgid "Search Transcript" msgstr "Відкрити транскрипцію" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index ef775c6c..0beb6668 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -440,12 +440,12 @@ msgstr "新增URL识别" msgid "Open Transcript" msgstr "打开识别结果" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "取消识别" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "清除历史纪录" @@ -623,111 +623,111 @@ msgstr "查看" msgid "Timestamps" msgstr "时间戳" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "导出" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "翻译" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "调整大小" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "需要API Key" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "请在偏好设置中输入OpenAI API Key" @@ -1335,34 +1335,42 @@ msgid "Search Transcript" msgstr "打开识别结果" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index 62e33959..508a94f1 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-06 11:29+0200\n" +"POT-Creation-Date: 2025-12-11 20:21+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -435,12 +435,12 @@ msgstr "新錄製" msgid "Open Transcript" msgstr "打開轉換結果" -#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:37 +#: buzz/widgets/main_window_toolbar.py:63 buzz/settings/shortcut.py:39 msgid "Cancel Transcription" msgstr "取消錄製" #: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 -#: buzz/settings/shortcut.py:36 +#: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "清除歷史紀錄" @@ -618,111 +618,111 @@ msgstr "" msgid "Timestamps" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:218 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:265 msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:237 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:247 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:260 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:272 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:340 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:346 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:359 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:367 -msgid "Next match (Enter)" +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:402 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:407 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:409 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:415 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:417 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:464 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:466 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:788 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:790 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:795 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:854 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:856 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1211 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1212 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -1329,34 +1329,42 @@ msgid "Search Transcript" msgstr "打開轉換結果" #: buzz/settings/shortcut.py:26 -msgid "Scroll to Current Text" +msgid "Go to Next Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:27 -msgid "Play/Pause Audio" +msgid "Go to Previous Transcript Search Result" msgstr "" #: buzz/settings/shortcut.py:28 -msgid "Replay Current Segment" +msgid "Scroll to Current Text" msgstr "" #: buzz/settings/shortcut.py:29 -msgid "Toggle Playback Controls" +msgid "Play/Pause Audio" +msgstr "" + +#: buzz/settings/shortcut.py:30 +msgid "Replay Current Segment" msgstr "" #: buzz/settings/shortcut.py:31 -msgid "Decrease Segment Start Time" -msgstr "" - -#: buzz/settings/shortcut.py:32 -msgid "Increase Segment Start Time" +msgid "Toggle Playback Controls" msgstr "" #: buzz/settings/shortcut.py:33 -msgid "Decrease Segment End Time" +msgid "Decrease Segment Start Time" msgstr "" #: buzz/settings/shortcut.py:34 +msgid "Increase Segment Start Time" +msgstr "" + +#: buzz/settings/shortcut.py:35 +msgid "Decrease Segment End Time" +msgstr "" + +#: buzz/settings/shortcut.py:36 msgid "Increase Segment End Time" msgstr "" diff --git a/buzz/settings/shortcut.py b/buzz/settings/shortcut.py index b5dcae1d..0816f4f6 100644 --- a/buzz/settings/shortcut.py +++ b/buzz/settings/shortcut.py @@ -23,6 +23,8 @@ class Shortcut(str, enum.Enum): VIEW_TRANSCRIPT_TRANSLATION = ("Ctrl+L", _("View Transcript Translation")) VIEW_TRANSCRIPT_TIMESTAMPS = ("Ctrl+T", _("View Transcript Timestamps")) SEARCH_TRANSCRIPT = ("Ctrl+F", _("Search Transcript")) + SEARCH_NEXT = ("Ctrl+Return", _("Go to Next Transcript Search Result")) + SEARCH_PREVIOUS = ("Shift+Return", _("Go to Previous Transcript Search Result")) SCROLL_TO_CURRENT_TEXT = ("Ctrl+G", _("Scroll to Current Text")) PLAY_PAUSE_AUDIO = ("Ctrl+P", _("Play/Pause Audio")) REPLAY_CURRENT_SEGMENT = ("Ctrl+Shift+P", _("Replay Current Segment")) diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index 71d1ae46..fb93baee 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -106,6 +106,9 @@ class TranscriptionViewerWidget(QWidget): self.search_text = "" self.current_search_index = 0 self.search_results = [] + self.search_debounce_timer = QTimer() + self.search_debounce_timer.setSingleShot(True) + self.search_debounce_timer.timeout.connect(self.perform_search) # Loop functionality self.segment_looping_enabled = self.settings.settings.value( @@ -446,7 +449,7 @@ class TranscriptionViewerWidget(QWidget): search_layout.addWidget(self.search_prev_button) self.search_next_button = QPushButton("↓") - self.search_next_button.setToolTip(_("Next match (Enter)")) + self.search_next_button.setToolTip(_("Next match (Ctrl+Enter)")) self.search_next_button.clicked.connect(self.search_next) self.search_next_button.setEnabled(False) self.search_next_button.setMaximumWidth(40) @@ -817,11 +820,12 @@ class TranscriptionViewerWidget(QWidget): """Handle search text changes""" self.search_text = text.strip() if self.search_text: - # Add a small delay to avoid searching on every keystroke for long text + # Debounce search to avoid UI jumping while typing if len(self.search_text) >= 2: - self.perform_search() + self.search_debounce_timer.start(300) # 300ms delay self.search_frame.show() else: + self.search_debounce_timer.stop() self.clear_search() # Don't hide the search frame immediately, let user clear it manually @@ -954,6 +958,16 @@ class TranscriptionViewerWidget(QWidget): self.highlight_current_match() self.update_search_results_label() + def search_next_if_results(self): + """Go to next search result only if there are results (for global shortcut)""" + if self.search_results: + self.search_next() + + def search_previous_if_results(self): + """Go to previous search result only if there are results (for global shortcut)""" + if self.search_results: + self.search_previous() + def update_search_results_label(self): """Update the search results label with current position""" if self.search_results: @@ -1005,6 +1019,15 @@ class TranscriptionViewerWidget(QWidget): self.shortcuts.get(Shortcut.SEARCH_TRANSCRIPT)), self) search_shortcut.activated.connect(self.focus_search_input) + # Search navigation shortcuts (Ctrl+Enter / Shift+Enter) + search_next_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.SEARCH_NEXT)), self) + search_next_shortcut.activated.connect(self.search_next_if_results) + + search_prev_shortcut = QShortcut(QKeySequence( + self.shortcuts.get(Shortcut.SEARCH_PREVIOUS)), self) + search_prev_shortcut.activated.connect(self.search_previous_if_results) + # Scroll to current text shortcut (Ctrl+G) scroll_to_current_shortcut = QShortcut(QKeySequence( self.shortcuts.get(Shortcut.SCROLL_TO_CURRENT_TEXT)), self) diff --git a/tests/widgets/shortcuts_editor_widget_test.py b/tests/widgets/shortcuts_editor_widget_test.py index 5beca027..d0fe1d74 100644 --- a/tests/widgets/shortcuts_editor_widget_test.py +++ b/tests/widgets/shortcuts_editor_widget_test.py @@ -40,6 +40,8 @@ class TestShortcutsEditorWidget: (_("View Transcript Translation"), "Ctrl+L"), (_("View Transcript Timestamps"), "Ctrl+T"), (_("Search Transcript"), "Ctrl+F"), + (_("Go to Next Transcript Search Result"), "Ctrl+Return"), + (_("Go to Previous Transcript Search Result"), "Shift+Return"), (_("Scroll to Current Text"), "Ctrl+G"), (_("Play/Pause Audio"), "Ctrl+P"), (_("Replay Current Segment"), "Ctrl+Shift+P"), diff --git a/tests/widgets/transcription_viewer_test.py b/tests/widgets/transcription_viewer_test.py index 883390fe..c948dede 100644 --- a/tests/widgets/transcription_viewer_test.py +++ b/tests/widgets/transcription_viewer_test.py @@ -797,8 +797,8 @@ class TestTranscriptionViewerWidget: widget.search_input.setText("Bien") qtbot.keyPress(widget.search_input, Qt.Key.Key_Return) - # Wait for search to complete - qtbot.wait(100) + # Wait for search debounce timer to complete (300ms) plus buffer + qtbot.wait(400) # Verify the format is correct (should show "1 of X matches" or similar) results_text = widget.search_results_label.text() @@ -935,7 +935,9 @@ class TestTranscriptionViewerWidget: # Set up search widget.search_input.setText("test search") qtbot.keyPress(widget.search_input, Qt.Key.Key_Return) - qtbot.wait(100) + + # Wait for search debounce timer to complete (300ms) plus buffer + qtbot.wait(400) # Verify search is active assert widget.search_input.text() == "test search" From 9d8ee2112d8b1ce3e6a50e9d7997eb5f1bffb53e Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Fri, 12 Dec 2025 20:41:44 +0200 Subject: [PATCH 20/73] Adjusting library load and versions (#1309) --- Makefile | 3 + buzz/buzz.py | 6 +- buzz/file_transcriber_queue_worker.py | 9 +- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 2 +- buzz/model_loader.py | 229 ++-- buzz/settings/settings.py | 3 + buzz/transcriber/whisper_file_transcriber.py | 64 +- buzz/widgets/main_window.py | 1 + .../widgets/model_download_progress_dialog.py | 2 +- .../transcription_tasks_table_widget.py | 45 +- .../transcription_viewer_widget.py | 9 +- hatch_build.py | 4 +- pyproject.toml | 65 +- pytest.ini | 2 +- tests/conftest.py | 2 + tests/settings/settings_test.py | 48 +- .../transcription_tasks_table_widget_test.py | 53 +- uv.lock | 1097 +++++++++-------- 18 files changed, 963 insertions(+), 681 deletions(-) diff --git a/Makefile b/Makefile index 92315dbd..9eb844a5 100644 --- a/Makefile +++ b/Makefile @@ -23,10 +23,13 @@ ifeq ($(OS), Windows_NT) -rm -rf buzz/whisper_cpp -rm -rf whisper.cpp/build -rm -rf dist/* + -rm -rf buzz/__pycache__ buzz/**/__pycache__ buzz/**/**/__pycache__ buzz/**/**/**/__pycache__ + -for /d /r buzz %%d in (__pycache__) do @if exist "%%d" rmdir /s /q "%%d" else rm -rf buzz/whisper_cpp || true rm -rf whisper.cpp/build || true rm -rf dist/* || true + find buzz -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true endif COVERAGE_THRESHOLD := 70 diff --git a/buzz/buzz.py b/buzz/buzz.py index 289304b3..09a63d9f 100644 --- a/buzz/buzz.py +++ b/buzz/buzz.py @@ -4,6 +4,7 @@ import multiprocessing import os import platform import sys +from pathlib import Path from typing import TextIO from platformdirs import user_log_dir, user_cache_dir, user_data_dir @@ -14,7 +15,10 @@ os.environ.setdefault("HF_HOME", user_cache_dir("Buzz")) from buzz.assets import APP_BASE_DIR # Check for segfaults if not running in frozen mode -if getattr(sys, "frozen", False) is False: +# Note: On Windows, faulthandler can print "Windows fatal exception" messages +# for non-fatal RPC errors (0x800706be) during multiprocessing operations. +# These are usually harmless but noisy, so we disable faulthandler on Windows. +if getattr(sys, "frozen", False) is False and platform.system() != "Windows": faulthandler.enable() # Sets stderr to no-op TextIO when None (run as Windows GUI). diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index 06ab099f..32f83591 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Optional, Tuple, List, Set from uuid import UUID -from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot +from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, Qt # Patch subprocess for demucs to prevent console windows on Windows if sys.platform == "win32": @@ -70,7 +70,9 @@ class FileTranscriberQueueWorker(QObject): self.current_transcriber = None self.speech_path = None self.is_running = False - self.trigger_run.connect(self.run) + # Use QueuedConnection to ensure run() is called in the correct thread context + # and doesn't block signal handlers + self.trigger_run.connect(self.run, Qt.ConnectionType.QueuedConnection) @pyqtSlot() def run(self): @@ -174,7 +176,8 @@ class FileTranscriberQueueWorker(QObject): def _on_task_finished(self): """Called when a task completes or errors, resets state and triggers next run""" self.is_running = False - self.run() + # Use signal to avoid blocking in signal handler context + self.trigger_run.emit() def add_task(self, task: FileTranscriptionTask): # Remove from canceled tasks if it was previously canceled (for restart functionality) diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index 6c095e93..1858a164 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -663,7 +663,7 @@ msgstr "Notīrīt" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 msgid "Playback Controls:" -msgstr "Atskaņošanas iespējas:" +msgstr "Atskaņošana:" #: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 msgid "Loop Segment" diff --git a/buzz/model_loader.py b/buzz/model_loader.py index ce12ba42..a89cfe21 100644 --- a/buzz/model_loader.py +++ b/buzz/model_loader.py @@ -59,20 +59,20 @@ class WhisperModelSize(str, enum.Enum): def __str__(self): return self.value.capitalize() -# Approximate expected file sizes for Whisper models +# Approximate expected file sizes for Whisper models (based on actual .pt file sizes) WHISPER_MODEL_SIZES = { - WhisperModelSize.TINY: 75 * 1024 * 1024, - WhisperModelSize.TINYEN: 75 * 1024 * 1024, - WhisperModelSize.BASE: 150 * 1024 * 1024, - WhisperModelSize.BASEEN: 150 * 1024 * 1024, - WhisperModelSize.SMALL: 500 * 1024 * 1024, - WhisperModelSize.SMALLEN: 500 * 1024 * 1024, - WhisperModelSize.MEDIUM: 1500 * 1024 * 1024, - WhisperModelSize.MEDIUMEN: 1500 * 1024 * 1024, - WhisperModelSize.LARGE: 3100 * 1024 * 1024, - WhisperModelSize.LARGEV2: 3100 * 1024 * 1024, - WhisperModelSize.LARGEV3: 3100 * 1024 * 1024, - WhisperModelSize.LARGEV3TURBO: 3100 * 1024 * 1024, + WhisperModelSize.TINY: 72 * 1024 * 1024, # ~73 MB actual + WhisperModelSize.TINYEN: 72 * 1024 * 1024, # ~73 MB actual + WhisperModelSize.BASE: 138 * 1024 * 1024, # ~139 MB actual + WhisperModelSize.BASEEN: 138 * 1024 * 1024, # ~139 MB actual + WhisperModelSize.SMALL: 460 * 1024 * 1024, # ~462 MB actual + WhisperModelSize.SMALLEN: 460 * 1024 * 1024, # ~462 MB actual + WhisperModelSize.MEDIUM: 1500 * 1024 * 1024, # ~1.5 GB actual + WhisperModelSize.MEDIUMEN: 1500 * 1024 * 1024, # ~1.5 GB actual + WhisperModelSize.LARGE: 2870 * 1024 * 1024, # ~2.9 GB actual + WhisperModelSize.LARGEV2: 2870 * 1024 * 1024, # ~2.9 GB actual + WhisperModelSize.LARGEV3: 2870 * 1024 * 1024, # ~2.9 GB actual + WhisperModelSize.LARGEV3TURBO: 1550 * 1024 * 1024, # ~1.6 GB actual (turbo is smaller) } def get_expected_whisper_model_size(size: WhisperModelSize) -> Optional[int]: @@ -324,18 +324,28 @@ class HuggingfaceDownloadMonitor: def monitor_file_size(self): while not self.stop_event.is_set(): - if model_root_dir is not None: - for filename in os.listdir(model_root_dir): - if filename.startswith("tmp"): - file_size = os.path.getsize( - os.path.join(model_root_dir, filename)) - self.progress.emit((file_size, self.total_file_size)) + try: + if model_root_dir is not None and os.path.isdir(model_root_dir): + for filename in os.listdir(model_root_dir): + if filename.startswith("tmp"): + try: + file_size = os.path.getsize( + os.path.join(model_root_dir, filename)) + self.progress.emit((file_size, self.total_file_size)) + except OSError: + pass # File may have been deleted - for filename in os.listdir(self.incomplete_download_root): - if filename.endswith(".incomplete"): - file_size = os.path.getsize(os.path.join( - self.incomplete_download_root, filename)) - self.progress.emit((file_size, self.total_file_size)) + if self.incomplete_download_root and os.path.isdir(self.incomplete_download_root): + for filename in os.listdir(self.incomplete_download_root): + if filename.endswith(".incomplete"): + try: + file_size = os.path.getsize(os.path.join( + self.incomplete_download_root, filename)) + self.progress.emit((file_size, self.total_file_size)) + except OSError: + pass # File may have been deleted + except OSError: + pass # Directory listing failed, ignore time.sleep(2) @@ -613,18 +623,21 @@ class ModelDownloader(QRunnable): elif file_size == expected_size: # This means file size matches - verify SHA256 to confirm it is complete try: + # Use chunked reading to avoid loading entire file into memory + sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: - model_bytes = f.read() - model_sha256 = hashlib.sha256(model_bytes).hexdigest() - if model_sha256 == expected_sha256: - logging.debug("Model already downloaded and verified") - return True - else: - warnings.warn( - f"{file_path} exists, but the SHA256 checksum does not match; re-downloading the file" - ) - # File exists but it is wrong, delete it - os.remove(file_path) + for chunk in iter(lambda: f.read(8192), b""): + sha256_hash.update(chunk) + model_sha256 = sha256_hash.hexdigest() + if model_sha256 == expected_sha256: + logging.debug("Model already downloaded and verified") + return True + else: + warnings.warn( + f"{file_path} exists, but the SHA256 checksum does not match; re-downloading the file" + ) + # File exists but it is wrong, delete it + os.remove(file_path) except Exception as e: logging.warning(f"Error checking existing file: {e}") os.remove(file_path) @@ -639,17 +652,19 @@ class ModelDownloader(QRunnable): file_mode = "ab" # Append mode to resume logging.debug(f"Resuming download from byte {resume_from}") else: - # Large file - verify SHA256 + # Large file - verify SHA256 using chunked reading try: + sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: - model_bytes = f.read() - model_sha256 = hashlib.sha256(model_bytes).hexdigest() - if model_sha256 == expected_sha256: - logging.debug("Model already downloaded and verified") - return True - else: - warnings.warn("SHA256 mismatch, re-downloading") - os.remove(file_path) + for chunk in iter(lambda: f.read(8192), b""): + sha256_hash.update(chunk) + model_sha256 = sha256_hash.hexdigest() + if model_sha256 == expected_sha256: + logging.debug("Model already downloaded and verified") + return True + else: + warnings.warn("SHA256 mismatch, re-downloading") + os.remove(file_path) except Exception as e: logging.warning(f"Error verifying file: {e}") os.remove(file_path) @@ -667,53 +682,109 @@ class ModelDownloader(QRunnable): resume_from = file_size file_mode = "ab" logging.debug(f"Resuming download from byte {resume_from}") - + # Downloads the model using the requests module instead of urllib to # use the certs from certifi when the app is running in frozen mode - headers = {} + + # Check if server supports Range requests before starting download + supports_range = False if resume_from > 0: + try: + head_resp = requests.head(url, timeout=10, allow_redirects=True) + accept_ranges = head_resp.headers.get("Accept-Ranges", "").lower() + supports_range = accept_ranges == "bytes" + if not supports_range: + logging.debug("Server doesn't support Range requests, starting from beginning") + resume_from = 0 + file_mode = "wb" + except requests.RequestException as e: + logging.debug(f"HEAD request failed, starting fresh: {e}") + resume_from = 0 + file_mode = "wb" + + headers = {} + if resume_from > 0 and supports_range: headers["Range"] = f"bytes={resume_from}-" - with requests.get(url, stream=True, timeout=15) as source, open( - file_path, file_mode - ) as output: - source.raise_for_status() + # Use a temporary file for fresh downloads to ensure atomic writes + temp_file_path = None + if resume_from == 0: + temp_file_path = file_path + ".downloading" + # Clean up any existing temp file + if os.path.exists(temp_file_path): + try: + os.remove(temp_file_path) + except OSError: + pass + download_path = temp_file_path + else: + download_path = file_path - if resume_from > 0: - if source.status_code == 206: - logging.debug( - f"Server supports resume, continuing from byte {resume_from}") - total_size = int(source.headers.get( - "Content-Range", "").split("/")[-1]) - current = resume_from - self.signals.progress.emit((current, total_size)) - elif source.status_code == 200: - logging.debug( - "Server doesn't support Range requests, starting from beginning") - # Truncate file and start over - output.close() - output = open(file_path, "wb") + try: + with requests.get(url, stream=True, timeout=30, headers=headers) as source: + source.raise_for_status() + + if resume_from > 0: + if source.status_code == 206: + logging.debug( + f"Server supports resume, continuing from byte {resume_from}") + content_range = source.headers.get("Content-Range", "") + if "/" in content_range: + total_size = int(content_range.split("/")[-1]) + else: + total_size = resume_from + int(source.headers.get("Content-Length", 0)) + current = resume_from + else: + # Server returned 200 instead of 206, need to start over + logging.debug("Server returned 200 instead of 206, starting fresh") + resume_from = 0 + file_mode = "wb" + temp_file_path = file_path + ".downloading" + download_path = temp_file_path + total_size = float(source.headers.get("Content-Length", 0)) + current = 0.0 + else: total_size = float(source.headers.get("Content-Length", 0)) current = 0.0 - resume_from = 0 - else: - source.raise_for_status() - else: - total_size = float(source.headers.get("Content-Length", 0)) - current = 0.0 - - self.signals.progress.emit((current, total_size)) - for chunk in source.iter_content(chunk_size=8192): - if self.stopped: - return False - output.write(chunk) - current += len(chunk) self.signals.progress.emit((current, total_size)) + with open(download_path, file_mode) as output: + for chunk in source.iter_content(chunk_size=8192): + if self.stopped: + return False + output.write(chunk) + current += len(chunk) + self.signals.progress.emit((current, total_size)) + + # If we used a temp file, rename it to the final path + if temp_file_path and os.path.exists(temp_file_path): + # Remove existing file if present + if os.path.exists(file_path): + os.remove(file_path) + shutil.move(temp_file_path, file_path) + + except Exception: + # Clean up temp file on error + if temp_file_path and os.path.exists(temp_file_path): + try: + os.remove(temp_file_path) + except OSError: + pass + raise + if expected_sha256 is not None: - model_bytes = open(file_path, "rb").read() - if hashlib.sha256(model_bytes).hexdigest() != expected_sha256: + # Use chunked reading to avoid loading entire file into memory + sha256_hash = hashlib.sha256() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(8192), b""): + sha256_hash.update(chunk) + if sha256_hash.hexdigest() != expected_sha256: + # Delete the corrupted file before raising the error + try: + os.remove(file_path) + except OSError as e: + logging.warning(f"Failed to delete corrupted model file: {e}") raise RuntimeError( "Model has been downloaded but the SHA256 checksum does not match. Please retry loading the " "model." diff --git a/buzz/settings/settings.py b/buzz/settings/settings.py index 288b0c76..e76868dd 100644 --- a/buzz/settings/settings.py +++ b/buzz/settings/settings.py @@ -59,6 +59,9 @@ class Settings: TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS = ( "transcription-tasks-table/column-widths" ) + TRANSCRIPTION_TASKS_TABLE_SORT_STATE = ( + "transcription-tasks-table/sort-state" + ) MAIN_WINDOW = "main-window" TRANSCRIPTION_VIEWER = "transcription-viewer" diff --git a/buzz/transcriber/whisper_file_transcriber.py b/buzz/transcriber/whisper_file_transcriber.py index 8faf016b..e329c587 100644 --- a/buzz/transcriber/whisper_file_transcriber.py +++ b/buzz/transcriber/whisper_file_transcriber.py @@ -76,8 +76,20 @@ class WhisperFileTranscriber(FileTranscriber): if self.started_process: self.current_process.join() - if self.started_process and self.current_process.exitcode != 0: - self.send_pipe.close() + # Close the send pipe after process ends to signal read_line thread to stop + # This prevents the read thread from blocking on recv() after the process is gone + try: + if self.send_pipe and not self.send_pipe.closed: + self.send_pipe.close() + except OSError: + pass + + # Close the receive pipe to unblock the read_line thread + try: + if self.recv_pipe and not self.recv_pipe.closed: + self.recv_pipe.close() + except OSError: + pass # Join read_line_thread with timeout to prevent hanging if self.read_line_thread and self.read_line_thread.is_alive(): @@ -111,6 +123,37 @@ class WhisperFileTranscriber(FileTranscriber): def transcribe_whisper( cls, stderr_conn: Connection, task: FileTranscriptionTask ) -> None: + # Patch subprocess on Windows to prevent console window flash + # This is needed because multiprocessing spawns a new process without the main process patches + if sys.platform == "win32": + import subprocess + _original_run = subprocess.run + _original_popen = subprocess.Popen + + def _patched_run(*args, **kwargs): + if 'startupinfo' not in kwargs: + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + si.wShowWindow = subprocess.SW_HIDE + kwargs['startupinfo'] = si + if 'creationflags' not in kwargs: + kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW + return _original_run(*args, **kwargs) + + class _PatchedPopen(subprocess.Popen): + def __init__(self, *args, **kwargs): + if 'startupinfo' not in kwargs: + si = subprocess.STARTUPINFO() + si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + si.wShowWindow = subprocess.SW_HIDE + kwargs['startupinfo'] = si + if 'creationflags' not in kwargs: + kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW + super().__init__(*args, **kwargs) + + subprocess.run = _patched_run + subprocess.Popen = _PatchedPopen + with pipe_stderr(stderr_conn): if task.transcription_options.model.model_type == ModelType.WHISPER_CPP: segments = cls.transcribe_whisper_cpp(task) @@ -235,7 +278,19 @@ class WhisperFileTranscriber(FileTranscriber): use_cuda = torch.cuda.is_available() and force_cpu == "false" device = "cuda" if use_cuda else "cpu" - model = whisper.load_model(task.model_path, device=device) + + # Monkeypatch torch.load to use weights_only=False for PyTorch 2.6+ + # This is required for loading Whisper models with the newer PyTorch versions + original_torch_load = torch.load + def patched_torch_load(*args, **kwargs): + kwargs.setdefault('weights_only', False) + return original_torch_load(*args, **kwargs) + + torch.load = patched_torch_load + try: + model = whisper.load_model(task.model_path, device=device) + finally: + torch.load = original_torch_load if task.transcription_options.word_level_timings: stable_whisper.modify_model(model) @@ -314,7 +369,8 @@ class WhisperFileTranscriber(FileTranscriber): # Uncomment to debug # print(f"*** DEBUG ***: {line}") - except (EOFError, BrokenPipeError, ConnectionResetError): # Connection closed or broken + except (EOFError, BrokenPipeError, ConnectionResetError, OSError): + # Connection closed, broken, or process crashed (Windows RPC errors raise OSError) break except Exception as e: logging.debug(f"Error reading from pipe: {e}") diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 88252032..384c8c71 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -113,6 +113,7 @@ class MainWindow(QMainWindow): self.table_widget.transcription_service = self.transcription_service self.table_widget.doubleClicked.connect(self.on_table_double_clicked) self.table_widget.return_clicked.connect(self.open_transcript_viewer) + self.table_widget.delete_requested.connect(self.on_clear_history_action_triggered) self.table_widget.selectionModel().selectionChanged.connect( self.on_table_selection_changed ) diff --git a/buzz/widgets/model_download_progress_dialog.py b/buzz/widgets/model_download_progress_dialog.py index 6023bd18..3c2bba80 100644 --- a/buzz/widgets/model_download_progress_dialog.py +++ b/buzz/widgets/model_download_progress_dialog.py @@ -20,7 +20,7 @@ class ModelDownloadProgressDialog(QProgressDialog): self.setMinimumWidth(350) self.cancelable = ( - model_type == ModelType.WHISPER or model_type == ModelType.WHISPER_CPP + model_type == ModelType.WHISPER ) self.start_time = datetime.now() self.setRange(0, 100) diff --git a/buzz/widgets/transcription_tasks_table_widget.py b/buzz/widgets/transcription_tasks_table_widget.py index 0d79b35b..296ea79e 100644 --- a/buzz/widgets/transcription_tasks_table_widget.py +++ b/buzz/widgets/transcription_tasks_table_widget.py @@ -230,6 +230,7 @@ class TranscriptionTasksTableHeaderView(QHeaderView): class TranscriptionTasksTableWidget(QTableView): return_clicked = pyqtSignal() + delete_requested = pyqtSignal() def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) @@ -279,12 +280,14 @@ class TranscriptionTasksTableWidget(QTableView): # Connect signals for column resize and move self.horizontalHeader().sectionResized.connect(self.on_column_resized) self.horizontalHeader().sectionMoved.connect(self.on_column_moved) + self.horizontalHeader().sortIndicatorChanged.connect(self.on_sort_indicator_changed) - # Load saved column order and widths + # Load saved column order, widths, and sort state self.load_column_order() self.load_column_widths() + self.load_sort_state() + - # Reload column visibility after all reordering is complete self.load_column_visibility() @@ -330,6 +333,10 @@ class TranscriptionTasksTableWidget(QTableView): # Refresh visibility after column move to ensure it's maintained self.load_column_visibility() + def on_sort_indicator_changed(self, logical_index: int, order: Qt.SortOrder): + """Handle sort indicator change events""" + self.save_sort_state() + def on_double_click(self, index: QModelIndex): """Handle double-click events - trigger notes edit for notes column""" if index.column() == Column.NOTES.value: @@ -369,6 +376,25 @@ class TranscriptionTasksTableWidget(QTableView): self.setColumnWidth(definition.column.value, int(saved_width)) self.settings.end_group() + def save_sort_state(self): + """Save current sort state to settings""" + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE) + header = self.horizontalHeader() + self.settings.settings.setValue("column", header.sortIndicatorSection()) + self.settings.settings.setValue("order", header.sortIndicatorOrder().value) + self.settings.end_group() + + def load_sort_state(self): + """Load saved sort state from settings""" + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE) + column = self.settings.settings.value("column") + order = self.settings.settings.value("order") + self.settings.end_group() + + if column is not None and order is not None: + sort_order = Qt.SortOrder(int(order)) + self.sortByColumn(int(column), sort_order) + def load_column_visibility(self): """Load saved column visibility from settings""" self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY) @@ -428,6 +454,9 @@ class TranscriptionTasksTableWidget(QTableView): if current_visual_index != target_visual_index: header.moveSection(current_visual_index, target_visual_index) + # Reset sort to default (TIME_QUEUED descending) + self.sortByColumn(Column.TIME_QUEUED.value, Qt.SortOrder.DescendingOrder) + # Clear saved settings self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER) self.settings.settings.remove("") @@ -437,9 +466,14 @@ class TranscriptionTasksTableWidget(QTableView): self.settings.settings.remove("") self.settings.end_group() - # Save the reset state for both visibility and widths + self.settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE) + self.settings.settings.remove("") + self.settings.end_group() + + # Save the reset state for visibility, widths, and sort self.save_column_visibility() self.save_column_widths() + self.save_sort_state() # Force a refresh of the table layout self.horizontalHeader().update() @@ -535,6 +569,11 @@ class TranscriptionTasksTableWidget(QTableView): if event.key() == Qt.Key.Key_Return: self.return_clicked.emit() + if event.key() == Qt.Key.Key_Delete: + if self.selectionModel().selectedRows(): + self.delete_requested.emit() + return + if event.matches(QKeySequence.StandardKey.Copy): self.copy_selected_fields() return diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index fb93baee..768ef365 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -721,16 +721,13 @@ class TranscriptionViewerWidget(QWidget): (updated_start, updated_end)) def on_audio_playback_state_changed(self, state): - """Handle audio playback state changes to automatically show/hide playback controls""" + """Handle audio playback state changes to automatically show playback controls""" from PyQt6.QtMultimedia import QMediaPlayer if state == QMediaPlayer.PlaybackState.PlayingState: # Show playback controls when audio starts playing if self.view_mode == ViewMode.TIMESTAMPS: self.show_loop_controls() - elif state == QMediaPlayer.PlaybackState.StoppedState: - # Hide playback controls when audio stops - self.hide_loop_controls() def initialize_speed_control(self): """Initialize the speed control with current value from audio player""" @@ -1214,10 +1211,6 @@ class TranscriptionViewerWidget(QWidget): if self.current_media_player.position_ms < start_time_ms or self.current_media_player.position_ms > end_time_ms: self.current_media_player.set_position(start_time_ms) - # Start playing if not yet playing - if self.current_media_player.media_player.playbackState() != QMediaPlayer.PlaybackState.PlayingState: - self.current_media_player.media_player.play() - if self.segment_looping_enabled: self.current_media_player.set_range((start_time_ms, end_time_ms)) diff --git a/hatch_build.py b/hatch_build.py index d95e4c66..b070d9d9 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -117,7 +117,7 @@ class CustomBuildHook(BuildHookInterface): print(f"Warning: {whisper_cpp_dir} does not exist after build", file=sys.stderr) # Force include all files in demucs directory - demucs_dir = project_root / "demucs" + demucs_dir = project_root / "demucs_repo" if demucs_dir.exists(): # Get all files in the demucs directory demucs_files = glob.glob(str(demucs_dir / "**" / "*"), recursive=True) @@ -134,7 +134,7 @@ class CustomBuildHook(BuildHookInterface): rel_path = Path(file_path).relative_to(project_root) build_data['force_include'][str(rel_path)] = str(rel_path) - print(f"Force including {len(demucs_files)} files from demucs/") + print(f"Force including {len(demucs_files)} files from demucs_repo/") else: print(f"Warning: {demucs_dir} does not exist", file=sys.stderr) diff --git a/pyproject.toml b/pyproject.toml index 8451e0f9..db82c21f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,23 +8,23 @@ requires-python = ">=3.12,<3.13" readme = "README.md" license = { text = "MIT" } dependencies = [ - "sounddevice>=0.4.5,<0.5", + "sounddevice>=0.5.3,<0.6", "humanize>=4.4.0,<5", "PyQt6==6.9.1", "PyQt6-Qt6==6.9.1", "PyQt6-sip==13.10.2", "openai>=1.14.2,<2", "keyring>=25.0.0,<26", - "platformdirs>=4.2.0,<5", + "platformdirs>=4.2.1,<5", "dataclasses-json>=0.6.4,<0.7", "numpy>=1.21.2,<2", "requests>=2.31.0,<3", - "yt-dlp>=2025.2.19,<2026", - "stable-ts>=2.18.3,<3", - "faster-whisper>=1.1.1,<2", - "openai-whisper>=20240930,<20240931", - "transformers>=4.49.0,<5", - "accelerate>=1.0.1,<2", + "yt-dlp>=2025.11.12,<2026", + "stable-ts>=2.19.1,<3", + "faster-whisper>=1.2.1,<2", + "openai-whisper==20250625", + "transformers>=4.53,<5", + "accelerate>=1.12.0,<2", "polib>=1.2.0,<2", "srt-equalizer>=0.1.10,<0.2", # For Intel macOS (x86_64) - use older versions that support Intel @@ -32,13 +32,19 @@ dependencies = [ "torchaudio==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64'", "ctranslate2==4.3.1; sys_platform == 'darwin' and platform_machine == 'x86_64'", # For ARM macOS (arm64) - use latest CPU-only versions from PyPI - "torch==2.7.1; sys_platform == 'darwin' and platform_machine == 'arm64'", - "torchaudio==2.7.1; sys_platform == 'darwin' and platform_machine == 'arm64'", - "ctranslate2>=4.6.0,<5; sys_platform == 'darwin' and platform_machine == 'arm64'", + "torch==2.8.0; sys_platform == 'darwin' and platform_machine == 'arm64'", + "torchaudio==2.8.0; sys_platform == 'darwin' and platform_machine == 'arm64'", + "ctranslate2>=4.6.2,<5; sys_platform == 'darwin' and platform_machine == 'arm64'", # For Linux/Windows - use CUDA versions from pytorch index - "torch==2.7.1; sys_platform != 'darwin'", - "torchaudio==2.7.1; sys_platform != 'darwin'", - "ctranslate2>=4.6.0,<5; sys_platform != 'darwin'", + "torch==2.8.0; sys_platform != 'darwin'", + "torchaudio==2.8.0; sys_platform != 'darwin'", + "ctranslate2>=4.6.2,<5; sys_platform != 'darwin'", + # faster whisper need cudnn 9 + "nvidia-cudnn-cu12>=9,<10; sys_platform != 'darwin'", + # CUDA runtime libraries for Windows (Linux gets them via torch dependencies) + "nvidia-cuda-runtime-cu12>=12.9,<13; sys_platform == 'win32'", + "nvidia-cublas-cu12>=12.9,<13; sys_platform == 'win32'", + "nvidia-cuda-nvrtc-cu12>=12.9,<13; sys_platform == 'win32'", "darkdetect>=0.8.0,<0.9", "dora-search>=0.1.12,<0.2", "diffq>=0.2.4,<0.3", @@ -56,19 +62,23 @@ dependencies = [ "tqdm>=4.67.1,<5", "treetable>=0.2.5,<0.3", "soundfile>=0.13.1,<0.14", - "urllib3>=2.3.0,<3", + "urllib3>=2.6.0,<3", "posthog>=3.23.0,<4", + # This version works, newer have issues on Windows "onnxruntime==1.18.1", "vulkan>=1.3.275.1,<2", "hf-xet>=1.1.5,<2", - "hatchling>=1.27.0", - "cmake>=3.31.6", - "nemo-toolkit[asr]>=2.5.3; sys_platform != 'darwin' or platform_machine != 'x86_64'", + "hatchling>=1.28.0", + "cmake>=4.2.0,<5", + # 2.5.3 is last versions with cuda 12 + "nemo-toolkit[asr]==2.5.3; sys_platform != 'darwin' or platform_machine != 'x86_64'", "nltk>=3.9.2", "uroman>=1.3.1.1", - "lhotse==1.31.1", - "coverage==7.6.1", + "lhotse==1.32.1", + "coverage==7.12.0", "demucs", + "certifi==2025.11.12", + "torchcodec>=0.9.0; sys_platform != 'darwin' or platform_machine != 'x86_64'", ] repository = "https://github.com/chidiwilliams/buzz" documentation = "https://chidiwilliams.github.io/buzz/docs" @@ -78,7 +88,7 @@ buzz = "buzz.buzz:main" [dependency-groups] dev = [ - "autopep8>=1.7.0,<2", + "autopep8>=2.3.2,<3", "pyinstaller>=6.12.0,<7", "pyinstaller-hooks-contrib~=2025.1", "six>=1.16.0,<2", @@ -94,8 +104,7 @@ dev = [ "ruff>=0.1.3,<0.2", ] build = [ - "ctypesgen>=1.1.1,<2", - "cmake>=3.26.4,<4", + "cmake>=4.2.0,<5", "polib>=1.2.0,<2", ] @@ -110,11 +119,11 @@ default-groups = [ demucs = { path = "demucs_repo", editable = true } torch = [ { index = "PyPI", marker = "sys_platform == 'darwin'" }, - { index = "pytorch-cu128", marker = "sys_platform != 'darwin'" }, + { index = "pytorch-cu129", marker = "sys_platform != 'darwin'" }, ] torchaudio = [ { index = "PyPI", marker = "sys_platform == 'darwin'" }, - { index = "pytorch-cu128", marker = "sys_platform != 'darwin'" }, + { index = "pytorch-cu129", marker = "sys_platform != 'darwin'" }, ] [[tool.uv.index]] @@ -122,8 +131,8 @@ name = "nvidia" url = "https://pypi.ngc.nvidia.com/" [[tool.uv.index]] -name = "pytorch-cu128" -url = "https://download.pytorch.org/whl/cu128" +name = "pytorch-cu129" +url = "https://download.pytorch.org/whl/cu129" [[tool.uv.index]] name = "PyPI" @@ -158,7 +167,7 @@ include = [ [tool.hatch.build.hooks.custom] [build-system] -requires = ["hatchling", "cmake>=3.26.4,<4", "polib>=1.2.0,<2", "pybind11", "setuptools>=42"] +requires = ["hatchling", "cmake>=4.2.0,<5", "polib>=1.2.0,<2", "pybind11", "setuptools>=42"] build-backend = "hatchling.build" [tool.ruff] diff --git a/pytest.ini b/pytest.ini index 36fdeb2a..0ad2fec7 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,7 +4,7 @@ log_cli_level = DEBUG qt_api=pyqt6 log_format = %(asctime)s %(levelname)s %(module)s::%(funcName)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S -addopts = -x -p no:xdist -p no:pytest_parallel +addopts = -x -s -p no:xdist -p no:pytest_parallel timeout = 600 timeout_method = thread testpaths = tests diff --git a/tests/conftest.py b/tests/conftest.py index aa1e1f3a..1ab7a4b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ import os +import platform import random import string +from pathlib import Path import pytest from PyQt6.QtSql import QSqlDatabase diff --git a/tests/settings/settings_test.py b/tests/settings/settings_test.py index 54a9439a..c1154e7f 100644 --- a/tests/settings/settings_test.py +++ b/tests/settings/settings_test.py @@ -20,28 +20,36 @@ class TestSettings: assert hasattr(Settings.Key, 'TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY') assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value == "transcription-tasks-table/column-visibility" + def test_transcription_tasks_table_sort_state_key(self): + """Test that TRANSCRIPTION_TASKS_TABLE_SORT_STATE key is defined""" + assert hasattr(Settings.Key, 'TRANSCRIPTION_TASKS_TABLE_SORT_STATE') + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value == "transcription-tasks-table/sort-state" + def test_all_transcription_tasks_table_keys_are_strings(self): """Test that all transcription tasks table keys are strings""" assert isinstance(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, str) assert isinstance(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, str) assert isinstance(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value, str) + assert isinstance(Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value, str) def test_transcription_tasks_table_keys_have_correct_prefix(self): """Test that all transcription tasks table keys have the correct prefix""" prefix = "transcription-tasks-table/" - + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value.startswith(prefix) assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value.startswith(prefix) assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value.startswith(prefix) + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value.startswith(prefix) def test_transcription_tasks_table_keys_are_unique(self): """Test that all transcription tasks table keys are unique""" keys = [ Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, - Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value ] - + assert len(keys) == len(set(keys)), "All transcription tasks table keys should be unique" def test_settings_key_enum_values(self): @@ -50,9 +58,10 @@ class TestSettings: expected_keys = { 'TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY': 'transcription-tasks-table/column-visibility', 'TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER': 'transcription-tasks-table/column-order', - 'TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS': 'transcription-tasks-table/column-widths' + 'TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS': 'transcription-tasks-table/column-widths', + 'TRANSCRIPTION_TASKS_TABLE_SORT_STATE': 'transcription-tasks-table/sort-state' } - + for key_name, expected_value in expected_keys.items(): assert hasattr(Settings.Key, key_name) assert getattr(Settings.Key, key_name).value == expected_value @@ -63,37 +72,41 @@ class TestSettings: original_visibility = Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY original_order = Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER original_widths = Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS - + original_sort_state = Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE + # Attempting to modify these should not work (they should be immutable) # If they were mutable, this test would fail assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY == original_visibility assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER == original_order assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS == original_widths + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE == original_sort_state def test_settings_key_format_consistency(self): """Test that all transcription tasks table keys follow the same format""" keys = [ Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, - Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value ] - + for key in keys: # All keys should start with the same prefix assert key.startswith("transcription-tasks-table/") # All keys should contain only lowercase letters, hyphens, and forward slashes assert all(c.islower() or c in '-/' for c in key) # All keys should end with a descriptive suffix - assert key.endswith(('visibility', 'order', 'widths')) + assert key.endswith(('visibility', 'order', 'widths', 'sort-state')) def test_settings_key_length(self): """Test that transcription tasks table keys have reasonable length""" keys = [ Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, - Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value ] - + for key in keys: # Keys should be long enough to be descriptive but not excessively long assert 20 <= len(key) <= 50, f"Key '{key}' has unexpected length: {len(key)}" @@ -103,9 +116,10 @@ class TestSettings: keys = [ Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value, Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value, - Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value + Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value, + Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value ] - + for key in keys: # Keys should use kebab-case (lowercase with hyphens) assert '-' in key, f"Key '{key}' should use kebab-case with hyphens" @@ -119,15 +133,17 @@ class TestSettings: mock_settings.begin_group = Mock() mock_settings.end_group = Mock() mock_settings.settings = Mock() - + # Test that the keys can be used with begin_group mock_settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value) mock_settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value) mock_settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value) - + mock_settings.begin_group(Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value) + # Verify that begin_group was called with the correct keys - assert mock_settings.begin_group.call_count == 3 + assert mock_settings.begin_group.call_count == 4 call_args = [call[0][0] for call in mock_settings.begin_group.call_args_list] assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY.value in call_args assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER.value in call_args assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS.value in call_args + assert Settings.Key.TRANSCRIPTION_TASKS_TABLE_SORT_STATE.value in call_args diff --git a/tests/widgets/transcription_tasks_table_widget_test.py b/tests/widgets/transcription_tasks_table_widget_test.py index 017a54f9..785866f1 100644 --- a/tests/widgets/transcription_tasks_table_widget_test.py +++ b/tests/widgets/transcription_tasks_table_widget_test.py @@ -75,7 +75,8 @@ def mock_dependencies(monkeypatch): Mock( TRANSCRIPTION_TASKS_TABLE_COLUMN_VISIBILITY="visibility", TRANSCRIPTION_TASKS_TABLE_COLUMN_ORDER="order", - TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS="widths" + TRANSCRIPTION_TASKS_TABLE_COLUMN_WIDTHS="widths", + TRANSCRIPTION_TASKS_TABLE_SORT_STATE="sort-state" ), ) @@ -475,12 +476,56 @@ class TestTranscriptionTasksTableWidget: # Mock settings to return specific values widget.settings.settings.value.side_effect = lambda key, default=None: { "file_name": "0", - "notes": "1", + "notes": "1", "status": "2" }.get(key, default) - + # Call reload method widget.reload_column_order_from_settings() - + # Verify the method completes without error assert True # If we get here, no exception was raised + + def test_sort_indicator_change_event(self, widget): + """Test sort indicator change event handling""" + with patch.object(widget, 'save_sort_state') as mock_save: + # Simulate sort indicator change + widget.on_sort_indicator_changed(0, Qt.SortOrder.AscendingOrder) + mock_save.assert_called_once() + + def test_save_sort_state(self, widget): + """Test saving sort state to settings""" + # Set a specific sort + widget.sortByColumn(Column.FILE.value, Qt.SortOrder.AscendingOrder) + widget.save_sort_state() + + # Verify settings were called + assert widget.settings.begin_group.called + assert widget.settings.settings.setValue.called + + def test_load_sort_state(self, widget): + """Test loading sort state from settings""" + # Mock settings to return specific sort state + widget.settings.settings.value.side_effect = lambda key, default=None: { + "sort-state/column": Column.FILE.value, + "sort-state/order": Qt.SortOrder.AscendingOrder.value + }.get(key, default) + + # Call load method + widget.load_sort_state() + + # Verify the method completes without error + assert True # If we get here, no exception was raised + + def test_reset_column_order_resets_sort(self, widget): + """Test that reset column order also resets sort state""" + # Change sort from default + widget.sortByColumn(Column.FILE.value, Qt.SortOrder.AscendingOrder) + + # Reset column order + widget.reset_column_order() + + # Verify sort is reset to default (TIME_QUEUED descending) + header = widget.horizontalHeader() + assert header.sortIndicatorSection() == Column.TIME_QUEUED.value + assert header.sortIndicatorOrder() == Qt.SortOrder.DescendingOrder diff --git a/uv.lock b/uv.lock index 6ca4afda..7d9a086f 100644 --- a/uv.lock +++ b/uv.lock @@ -2,12 +2,12 @@ version = 1 revision = 3 requires-python = "==3.12.*" resolution-markers = [ - "platform_machine == 'x86_64' and sys_platform == 'darwin'", - "platform_machine == 'arm64' and sys_platform == 'darwin'", - "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", "sys_platform != 'darwin' and sys_platform != 'linux'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", + "platform_machine == 'arm64' and sys_platform == 'darwin'", + "platform_machine == 'x86_64' and sys_platform == 'darwin'", + "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] [[package]] @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "accelerate" -version = "1.11.0" +version = "1.12.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "huggingface-hub" }, @@ -31,12 +31,12 @@ dependencies = [ { name = "pyyaml" }, { name = "safetensors" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/60/2757c4f03a8705dbf80b1268b03881927878dca5ed07d74f733fb6c219e0/accelerate-1.11.0.tar.gz", hash = "sha256:bb1caf2597b4cd632b917b5000c591d10730bb024a79746f1ee205bba80bd229", size = 393715, upload-time = "2025-10-20T14:42:25.025Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/8e/ac2a9566747a93f8be36ee08532eb0160558b07630a081a6056a9f89bf1d/accelerate-1.12.0.tar.gz", hash = "sha256:70988c352feb481887077d2ab845125024b2a137a5090d6d7a32b57d03a45df6", size = 398399, upload-time = "2025-11-21T11:27:46.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/85/85951bc0f9843e2c10baaa1b6657227056095de08f4d1eea7d8b423a6832/accelerate-1.11.0-py3-none-any.whl", hash = "sha256:a628fa6beb069b8e549460fc449135d5bd8d73e7a11fd09f0bc9fc4ace7f06f1", size = 375777, upload-time = "2025-10-20T14:42:23.256Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d2/c581486aa6c4fbd7394c23c47b83fa1a919d34194e16944241daf9e762dd/accelerate-1.12.0-py3-none-any.whl", hash = "sha256:3e2091cd341423207e2f084a6654b1efcd250dc326f2a37d6dde446e07cabb11", size = 380935, upload-time = "2025-11-21T11:27:44.522Z" }, ] [[package]] @@ -111,11 +111,11 @@ wheels = [ [[package]] name = "altgraph" -version = "0.17.4" +version = "0.17.5" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/de/a8/7145824cf0b9e3c28046520480f207df47e927df83aa9555fb47f8505922/altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406", size = 48418, upload-time = "2023-09-25T09:04:52.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3f/3bc3f1d83f6e4a7fcb834d3720544ca597590425be5ba9db032b2bf322a2/altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff", size = 21212, upload-time = "2023-09-25T09:04:50.691Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, ] [[package]] @@ -135,16 +135,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] [[package]] @@ -189,15 +188,14 @@ wheels = [ [[package]] name = "autopep8" -version = "1.7.0" +version = "2.3.2" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "pycodestyle" }, - { name = "toml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/5d/016888824972086a4ee164806520d85ff173e83699907b9cfe119aaefbbc/autopep8-1.7.0.tar.gz", hash = "sha256:ca9b1a83e53a7fad65d731dc7a2a2d50aa48f43850407c59f6a1a306c4201142", size = 117055, upload-time = "2022-08-09T12:55:28.934Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/d8/30873d2b7b57dee9263e53d142da044c4600a46f2d28374b3e38b023df16/autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758", size = 92210, upload-time = "2025-01-14T14:46:18.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/9b/1ed75f8c9086fafe0e9bbb379a70c43b1aa9dff6154ddcfb818f78cb0736/autopep8-1.7.0-py2.py3-none-any.whl", hash = "sha256:6f09e90a2be784317e84dc1add17ebfc7abe3924239957a37e5040e27d812087", size = 45563, upload-time = "2022-08-09T12:55:25.914Z" }, + { url = "https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128", size = 45807, upload-time = "2025-01-14T14:46:15.466Z" }, ] [[package]] @@ -230,7 +228,7 @@ version = "0.46.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/72/27/ec6ee3408e09e01ab05db07af5a97dc76db7bc18824cf5f5dbc98e1e08a4/bitsandbytes-0.46.0-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:ef38883cfd26f36a0dfff1715f620f87cee3813431f33e10e9658205160cb89b", size = 67047276, upload-time = "2025-05-27T21:25:31.299Z" }, @@ -252,10 +250,11 @@ version = "1.4.0" source = { editable = "." } dependencies = [ { name = "accelerate" }, + { name = "certifi" }, { name = "cmake" }, { name = "coverage" }, { name = "ctranslate2", version = "4.3.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "ctranslate2", version = "4.6.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' or sys_platform != 'darwin'" }, + { name = "ctranslate2", version = "4.6.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' or sys_platform != 'darwin'" }, { name = "darkdetect" }, { name = "dataclasses-json" }, { name = "demucs" }, @@ -278,6 +277,10 @@ dependencies = [ { name = "nemo-toolkit", extra = ["asr"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "nltk" }, { name = "numpy" }, + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'win32'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "sys_platform == 'win32'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "sys_platform == 'win32'" }, + { name = "nvidia-cudnn-cu12", marker = "sys_platform != 'darwin'" }, { name = "onnxruntime" }, { name = "openai" }, { name = "openai-whisper" }, @@ -296,12 +299,13 @@ dependencies = [ { name = "stable-ts" }, { name = "submitit" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchcodec", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tqdm" }, { name = "transformers" }, { name = "treetable" }, @@ -314,7 +318,6 @@ dependencies = [ [package.dev-dependencies] build = [ { name = "cmake" }, - { name = "ctypesgen" }, { name = "polib" }, ] dev = [ @@ -336,11 +339,12 @@ dev = [ [package.metadata] requires-dist = [ - { name = "accelerate", specifier = ">=1.0.1,<2" }, - { name = "cmake", specifier = ">=3.31.6" }, - { name = "coverage", specifier = "==7.6.1" }, - { name = "ctranslate2", marker = "sys_platform != 'darwin'", specifier = ">=4.6.0,<5" }, - { name = "ctranslate2", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = ">=4.6.0,<5" }, + { name = "accelerate", specifier = ">=1.12.0,<2" }, + { name = "certifi", specifier = "==2025.11.12" }, + { name = "cmake", specifier = ">=4.2.0,<5" }, + { name = "coverage", specifier = "==7.12.0" }, + { name = "ctranslate2", marker = "sys_platform != 'darwin'", specifier = ">=4.6.2,<5" }, + { name = "ctranslate2", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = ">=4.6.2,<5" }, { name = "ctranslate2", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==4.3.1" }, { name = "darkdetect", specifier = ">=0.8.0,<0.9" }, { name = "dataclasses-json", specifier = ">=0.6.4,<0.7" }, @@ -348,9 +352,9 @@ requires-dist = [ { name = "diffq", specifier = ">=0.2.4,<0.3" }, { name = "dora-search", specifier = ">=0.1.12,<0.2" }, { name = "einops", specifier = ">=0.8.1,<0.9" }, - { name = "faster-whisper", specifier = ">=1.1.1,<2" }, + { name = "faster-whisper", specifier = ">=1.2.1,<2" }, { name = "flake8", specifier = ">=7.1.2,<8" }, - { name = "hatchling", specifier = ">=1.27.0" }, + { name = "hatchling", specifier = ">=1.28.0" }, { name = "hf-xet", specifier = ">=1.1.5,<2" }, { name = "humanize", specifier = ">=4.4.0,<5" }, { name = "hydra-colorlog", specifier = ">=1.2.0,<2" }, @@ -358,17 +362,21 @@ requires-dist = [ { name = "julius", specifier = ">=0.2.7,<0.3" }, { name = "keyring", specifier = ">=25.0.0,<26" }, { name = "lameenc", specifier = ">=1.8.1,<2" }, - { name = "lhotse", specifier = "==1.31.1" }, + { name = "lhotse", specifier = "==1.32.1" }, { name = "museval", specifier = ">=0.4.1,<0.5" }, { name = "mypy", specifier = ">=1.15.0,<2" }, - { name = "nemo-toolkit", extras = ["asr"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = ">=2.5.3" }, + { name = "nemo-toolkit", extras = ["asr"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = "==2.5.3" }, { name = "nltk", specifier = ">=3.9.2" }, { name = "numpy", specifier = ">=1.21.2,<2" }, + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'win32'", specifier = ">=12.9,<13" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "sys_platform == 'win32'", specifier = ">=12.9,<13" }, + { name = "nvidia-cuda-runtime-cu12", marker = "sys_platform == 'win32'", specifier = ">=12.9,<13" }, + { name = "nvidia-cudnn-cu12", marker = "sys_platform != 'darwin'", specifier = ">=9,<10" }, { name = "onnxruntime", specifier = "==1.18.1" }, { name = "openai", specifier = ">=1.14.2,<2" }, - { name = "openai-whisper", specifier = ">=20240930,<20240931" }, + { name = "openai-whisper", specifier = "==20250625" }, { name = "openunmix", specifier = ">=1.3.0,<2" }, - { name = "platformdirs", specifier = ">=4.2.0,<5" }, + { name = "platformdirs", specifier = ">=4.2.1,<5" }, { name = "polib", specifier = ">=1.2.0,<2" }, { name = "posthog", specifier = ">=3.23.0,<4" }, { name = "pyqt6", specifier = "==6.9.1" }, @@ -376,34 +384,34 @@ requires-dist = [ { name = "pyqt6-sip", specifier = "==13.10.2" }, { name = "pyyaml", specifier = ">=6.0.2,<7" }, { name = "requests", specifier = ">=2.31.0,<3" }, - { name = "sounddevice", specifier = ">=0.4.5,<0.5" }, + { name = "sounddevice", specifier = ">=0.5.3,<0.6" }, { name = "soundfile", specifier = ">=0.13.1,<0.14" }, { name = "srt-equalizer", specifier = ">=0.1.10,<0.2" }, - { name = "stable-ts", specifier = ">=2.18.3,<3" }, + { name = "stable-ts", specifier = ">=2.19.1,<3" }, { name = "submitit", specifier = ">=1.5.2,<2" }, - { name = "torch", marker = "sys_platform != 'darwin'", specifier = "==2.7.1", index = "https://download.pytorch.org/whl/cu128" }, - { name = "torch", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = "==2.7.1", index = "https://pypi.org/simple/" }, + { name = "torch", marker = "sys_platform != 'darwin'", specifier = "==2.8.0", index = "https://download.pytorch.org/whl/cu129" }, + { name = "torch", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = "==2.8.0", index = "https://pypi.org/simple/" }, { name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==2.2.2", index = "https://pypi.org/simple/" }, - { name = "torchaudio", marker = "sys_platform != 'darwin'", specifier = "==2.7.1", index = "https://download.pytorch.org/whl/cu128" }, - { name = "torchaudio", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = "==2.7.1", index = "https://pypi.org/simple/" }, + { name = "torchaudio", marker = "sys_platform != 'darwin'", specifier = "==2.8.0", index = "https://download.pytorch.org/whl/cu129" }, + { name = "torchaudio", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = "==2.8.0", index = "https://pypi.org/simple/" }, { name = "torchaudio", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==2.2.2", index = "https://pypi.org/simple/" }, + { name = "torchcodec", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = ">=0.9.0" }, { name = "tqdm", specifier = ">=4.67.1,<5" }, - { name = "transformers", specifier = ">=4.49.0,<5" }, + { name = "transformers", specifier = ">=4.53,<5" }, { name = "treetable", specifier = ">=0.2.5,<0.3" }, - { name = "urllib3", specifier = ">=2.3.0,<3" }, + { name = "urllib3", specifier = ">=2.6.0,<3" }, { name = "uroman", specifier = ">=1.3.1.1" }, { name = "vulkan", specifier = ">=1.3.275.1,<2" }, - { name = "yt-dlp", specifier = ">=2025.2.19,<2026" }, + { name = "yt-dlp", specifier = ">=2025.11.12,<2026" }, ] [package.metadata.requires-dev] build = [ - { name = "cmake", specifier = ">=3.26.4,<4" }, - { name = "ctypesgen", specifier = ">=1.1.1,<2" }, + { name = "cmake", specifier = ">=4.2.0,<5" }, { name = "polib", specifier = ">=1.2.0,<2" }, ] dev = [ - { name = "autopep8", specifier = ">=1.7.0,<2" }, + { name = "autopep8", specifier = ">=2.3.2,<3" }, { name = "pre-commit", specifier = ">=2.20.0,<3" }, { name = "pyinstaller", specifier = ">=6.12.0,<7" }, { name = "pyinstaller-hooks-contrib", specifier = "~=2025.1" }, @@ -421,11 +429,11 @@ dev = [ [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] @@ -453,11 +461,11 @@ wheels = [ [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] @@ -499,43 +507,43 @@ wheels = [ [[package]] name = "cloudpickle" -version = "3.1.1" +version = "3.1.2" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] name = "cmake" -version = "3.31.6" +version = "4.2.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/00/95/ed1ad3763da30c963a941d3c641c9ec9f1397742407a3ab00f94263a5d9d/cmake-3.31.6.tar.gz", hash = "sha256:8edddfbf367fa1bcf4b9f3064470bc0e1022f70609c0cf69c863961897826205", size = 34370, upload-time = "2025-02-28T00:16:15.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/bb/1f5fa918267ecf3b24158efa53d71193ecacfa93d0e86264e46239ae7abb/cmake-4.2.0.tar.gz", hash = "sha256:7744c20e4a23e68dea276d819767d2bdbb45442cc342560b03ff693b755cd181", size = 37433, upload-time = "2025-11-21T15:06:31.914Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/09/516b0d709672bc430eb13278f0316acd34869269447744f5d136daeef689/cmake-3.31.6-py3-none-macosx_10_10_universal2.whl", hash = "sha256:da9d4fd9abd571fd016ddb27da0428b10277010b23bb21e3678f8b9e96e1686e", size = 47224338, upload-time = "2025-02-28T00:14:40.995Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a7/c12bc44214397a0429d08cb90adb8fdcfa643a03121daade5ee6bbfe060f/cmake-3.31.6-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:689441fc74fbb03673c67e20d4636614a231634d5e803387cd213d2cdf9675fc", size = 27569682, upload-time = "2025-02-28T00:14:47.646Z" }, - { url = "https://files.pythonhosted.org/packages/1e/52/85550dfcadca90b59809a1225461bfaadfcbbcc8fe62fa24f75edbe6e0b1/cmake-3.31.6-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2297e9591307d9c61e557efe737bcf4d7c13a30f1f860732f684a204fee24dca", size = 26820570, upload-time = "2025-02-28T00:14:52.256Z" }, - { url = "https://files.pythonhosted.org/packages/1e/97/c950850b00daf4a79c38a9f2e463dc75581a43a9575186439cff43cf4740/cmake-3.31.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42d9883b8958da285d53d5f69d40d9650c2d1bcf922d82b3ebdceb2b3a7d4521", size = 27155601, upload-time = "2025-02-28T00:14:58.201Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a4/d1fa5222f399cb6c304fc96d18d2144e61c1e5146f6fc98063dfa6b61ea2/cmake-3.31.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cefb910be81e1b4fdc3b89ef61819c3e848b3906ed56ac36d090f37cfa05666b", size = 28882832, upload-time = "2025-02-28T00:15:03.969Z" }, - { url = "https://files.pythonhosted.org/packages/82/e3/3c4057e797e2151ae57ce0cb9ca10310e5b2ff3da4e2089b713f1a680280/cmake-3.31.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4326f6c6f39867a60e2822fea8e6aedbcac09c9f59ad3f0f3386a890a2c8d89d", size = 30746172, upload-time = "2025-02-28T00:15:08.698Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ec/d1c50c2a283bd0f567da1a0a70d99e0c8056104b3d857829b5759ee5321f/cmake-3.31.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f77db820af725bb92fab60c4c9d67f64442ac0ea9b933aca4cd4586219cbd1f", size = 26923376, upload-time = "2025-02-28T00:15:14.204Z" }, - { url = "https://files.pythonhosted.org/packages/59/e8/096984b89133681533650b9078c5ed1c5c9b534e869b5487f22d4de1935c/cmake-3.31.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c8b05df0602365da91ee6a3336fe57525b137706c4ab5675498f662ae1dbcec", size = 27800904, upload-time = "2025-02-28T00:15:19.697Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f9/715a389ebbca277fb9d90e512ed5643e99139283c6f1fb211d7b62e18641/cmake-3.31.6-py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:9eed74a1f2a29a7cd92a9f071a35d64645b19802beb393ec250d6e7c09441314", size = 24978138, upload-time = "2025-02-28T00:15:24.533Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0c/75dd5349f17070858428ab4d8109581236724aa9ab8bf7702c48fb242ac8/cmake-3.31.6-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:112b36427e59bd26145b705a49d5f70b16433a655ce807cb8fdd81dd4d0e60c2", size = 27838267, upload-time = "2025-02-28T00:15:30.131Z" }, - { url = "https://files.pythonhosted.org/packages/02/8c/8c71a96e54192d81dfe696920b8cc018a1acf34029fcb18d47f21ba1d582/cmake-3.31.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:13f2e636dc27834fe096f53301d6efb913b4b501fdc0ed03f386c0a7e7ec1a21", size = 31379771, upload-time = "2025-02-28T00:15:35.566Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/37f09b8e6dcdbcbeb165c36e7def24463b3a05e9c95018f3b45ea779c975/cmake-3.31.6-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:8b67bf9613dfb59c12ce643c6be582c49c981e6eee28c4c244aeb3248b33f05e", size = 32092248, upload-time = "2025-02-28T00:15:40.51Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f8/b09c58e08ab7e9c1d1da28fde86fc8f48228dc9dbf3530ee695346b72f42/cmake-3.31.6-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:024a79ca3d2c355f75875b6cc92d907afd710d1a4ffde2f20a7da712a2f4b1c3", size = 27960713, upload-time = "2025-02-28T00:15:45.594Z" }, - { url = "https://files.pythonhosted.org/packages/1e/40/08cdebe9f4ab7e3299c4a3a10c7f209bbe8b25781c40ea1788a3aca39222/cmake-3.31.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ce5fc0299ecafe489b2614daa6176c3c2baacea6bc3b359bac9aa25b46ed43e9", size = 29486066, upload-time = "2025-02-28T00:15:50.767Z" }, - { url = "https://files.pythonhosted.org/packages/ec/23/fea759f3e09e1d42e58ce64e5acddb96c95d48bb1b0495d20d6b3ec0da88/cmake-3.31.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:547efc1d0e27a194da819a0392fe645a9b8f1485bc2c3f34ae4f1e682cfd3153", size = 32986603, upload-time = "2025-02-28T00:15:55.536Z" }, - { url = "https://files.pythonhosted.org/packages/2d/be/3c8fb670b75ff2850ba2269dd5d2b76ba12545e3c0c6ce064334ac4edf9f/cmake-3.31.6-py3-none-win32.whl", hash = "sha256:9f170e3c6933dba64f333cb456823bbb1d0ac126f94aa4a577e40855d2b1ca49", size = 33420573, upload-time = "2025-02-28T00:16:00.427Z" }, - { url = "https://files.pythonhosted.org/packages/18/58/909d6d99acb4e0886d0f660cf4e0fb26f586590e370b2e4ce7a10d06b145/cmake-3.31.6-py3-none-win_amd64.whl", hash = "sha256:bbaed969cef3c427f4f17591feb28db4ae595e3a4bbd45cb35522cee14df6a32", size = 36396448, upload-time = "2025-02-28T00:16:05.702Z" }, - { url = "https://files.pythonhosted.org/packages/c2/89/59ce2d293dfb2da1360e3c21b775559dd18b9f9d34c5cb5ed128d5a8faf5/cmake-3.31.6-py3-none-win_arm64.whl", hash = "sha256:6cb97adae7e5390ce68f8b7f38e1be1c72bf19e9f6727f31f8fa1c095b39be88", size = 35422613, upload-time = "2025-02-28T00:16:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3f/8c6419e6cde2dec09701f8542abb1b6cedabc9e5290ac9c896cec2c12dd0/cmake-4.2.0-py3-none-macosx_10_10_universal2.whl", hash = "sha256:28595ec42fb6f81128b7a9bdbdfcb7b785ad197dbfb1b785cec5727a97a521f4", size = 51571817, upload-time = "2025-11-21T15:05:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/25/35/4e23b6da5668007b2bf3c0f0ef0ae25e4fecfb3f237dbc8e8862c0ac0d18/cmake-4.2.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1a914c39a9349246b66089e6d741f1a3009c32fcd3a5110f9ddfc49adb4952c2", size = 28976273, upload-time = "2025-11-21T15:05:38.601Z" }, + { url = "https://files.pythonhosted.org/packages/dd/12/a798a9e718c4f88d9366cf234f61c85b30e2c851f3f6acf2e069dce7894f/cmake-4.2.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0940b5b00d2b65efbd409bfe83c4144a1a4f9bac5845c2c2f52b5cb71d5ca87f", size = 29843294, upload-time = "2025-11-21T15:05:41.629Z" }, + { url = "https://files.pythonhosted.org/packages/c2/18/b26ce699d30d11bd8408217eac6ee3efa6fd844798af0923f2056378aef2/cmake-4.2.0-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a94596c64c3a302ad27fd2aa23dd19829b3a64e9493adf87758b0c7ceee6e544", size = 30081472, upload-time = "2025-11-21T15:05:45.031Z" }, + { url = "https://files.pythonhosted.org/packages/50/69/517aff174e20335d703cd944a35af644e9d18d8343867b3cbdd23cca8053/cmake-4.2.0-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b537c69c4e91a29e82e2651e54f92b9794f4f7e9bb5385951065272cd11abe0", size = 27833234, upload-time = "2025-11-21T15:05:48.903Z" }, + { url = "https://files.pythonhosted.org/packages/a3/11/7debff1cae6d71e1b0c929af108ef6108488cf6e496d69f0c4116607e106/cmake-4.2.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5461ceca47ad352bdb3db2fdd5affdbc5707aaee415c5ff12773b8cc0d5f5949", size = 28897425, upload-time = "2025-11-21T15:05:51.863Z" }, + { url = "https://files.pythonhosted.org/packages/31/9e/1752084c0cf8feeca3f07c8e4b9141e9b24f0239a2d63115b5e5fd12973f/cmake-4.2.0-py3-none-manylinux_2_31_armv7l.whl", hash = "sha256:c4ea343eba9896b8ae94ffc7141902c2a40ce5ade5be1ebe5d2dc14109a4d9b4", size = 25808543, upload-time = "2025-11-21T15:05:54.879Z" }, + { url = "https://files.pythonhosted.org/packages/f8/15/59eb851836c90d9df29079ca0cffa11e9bd1e8e1a2ced790a11474568ec3/cmake-4.2.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9f34c9018425767e4ff42b66442a57dea34809341208c5de5432ec2a87bdce59", size = 26216711, upload-time = "2025-11-21T15:05:57.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/0e/874f57265c7effe2a634705434b60ea56b4dedc72ceaeaa73f370cfcd764/cmake-4.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8784c73dc24c34f6e9cadafc4848db5ff124aaf372e58b6550ed50726a81f9", size = 37768449, upload-time = "2025-11-21T15:06:00.927Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/9afcd0dc6e1f70d704b284a73af0afe442ba05d2f772d808b54fab3fe7df/cmake-4.2.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3b71cc13ba664b19eddbdf68ab30f12c27af93f987ee5ef66ce585d0b4ef5614", size = 34113664, upload-time = "2025-11-21T15:06:04.193Z" }, + { url = "https://files.pythonhosted.org/packages/80/70/804b6e5633859691b79febeab7973617d1e1ee73a55ed35ba39f9af4b528/cmake-4.2.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3dd6dcb08b5562e22f6b433d45bd07e3ef2e156284ddeefcb9da4ec68b9ba6bb", size = 40054242, upload-time = "2025-11-21T15:06:07.436Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bc/d806beaf4da94a0f32b6b1461d7a22129496c47d084a14d730075844aed9/cmake-4.2.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:1971a8ef69a31e814cb72c48f39bcbe6b45fff4afced4a3970c85dda7f4a755c", size = 39948745, upload-time = "2025-11-21T15:06:10.911Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f7e97cbdc9cd2e4a4e03bf84dd0b0d9a8aaf1ac719e2b8ff91b7fcfa06df/cmake-4.2.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:ce565817a47798d75d6b17b21b2389826dee069e2a9eeb07beefc6f055e79191", size = 34796506, upload-time = "2025-11-21T15:06:13.864Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/1d0cb699c723c2664f67cc4f7c24fd6da952d94d3e5706e7e9fb792ae0cf/cmake-4.2.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:c43baab5a829b92660d4eaf2896063da49d500a066a5088139d87793cb75b2e0", size = 36831790, upload-time = "2025-11-21T15:06:16.623Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/c9b1888f92e9ce8beb1668658a0eae45b38ba294c1a64c89faf6b7a1a40f/cmake-4.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bf11883a4cb3529f69746857df9733cae6175f07361f8016f8f050a3177e7767", size = 37703841, upload-time = "2025-11-21T15:06:19.88Z" }, + { url = "https://files.pythonhosted.org/packages/7e/4f/4a367f84e20af9f27f2c16990b3d4adc64ac84128992defe634ee54bb9e1/cmake-4.2.0-py3-none-win32.whl", hash = "sha256:a052030a9722c55d50025fac1f74b499aa2ce0cb137733aa1c6fb49689f560cb", size = 35488384, upload-time = "2025-11-21T15:06:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/448cf5de781f30d00ff59521b91998ba9bd8dc3271a0540e4cd2bd201a6b/cmake-4.2.0-py3-none-win_amd64.whl", hash = "sha256:fb33a0c0486c3f4923a133dbeef4d009b798f1d4e6768381670736665a7f8c0a", size = 38882176, upload-time = "2025-11-21T15:06:25.89Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7a/5e6dbc0e2236b521763525c6630f0e7bfffaebf0709bed8ea078f4d56e4b/cmake-4.2.0-py3-none-win_arm64.whl", hash = "sha256:5c0dbe7a37991720d89c84825a4818f19debc8b10d5e4636b56c8fc08bec7a00", size = 37609134, upload-time = "2025-11-21T15:06:29.216Z" }, ] [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://download.pytorch.org/whl/cu128" } +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ { url = "https://download.pytorch.org/whl/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, ] @@ -588,20 +596,24 @@ wheels = [ [[package]] name = "coverage" -version = "7.6.1" +version = "7.12.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, - { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, - { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, - { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, - { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, - { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, - { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, - { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, + { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, + { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, + { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, ] [[package]] @@ -666,14 +678,14 @@ wheels = [ [[package]] name = "ctranslate2" -version = "4.6.0" +version = "4.6.2" source = { registry = "https://pypi.org/simple/" } resolution-markers = [ - "platform_machine == 'arm64' and sys_platform == 'darwin'", - "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", "sys_platform != 'darwin' and sys_platform != 'linux'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", + "platform_machine == 'arm64' and sys_platform == 'darwin'", + "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -681,20 +693,11 @@ dependencies = [ { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/02/e9/3f1e35528b445b2fc928063f3ddd1ca5ac195b08c28ab10312e599c5cf28/ctranslate2-4.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff3ad05010857d450ee40fd9c28a33c10215a7180e189151e378ed2d19be8a57", size = 13310925, upload-time = "2025-04-08T19:49:47.051Z" }, - { url = "https://files.pythonhosted.org/packages/2a/72/3880c3be097596a523cb24b52dc0514f685c2ec0bab9cceaeed874aeddec/ctranslate2-4.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78a844c633b6d450b20adac296f7f60ac2a67f2c76e510a83c8916835dc13f04", size = 1297913, upload-time = "2025-04-08T19:49:48.702Z" }, - { url = "https://files.pythonhosted.org/packages/3f/b3/77af5ad0e896dd27a10db768d7a67b8807e394c8e68c2fa559c662a33547/ctranslate2-4.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44bf4b973ea985b80696093e11e9c72909aee55b35abb749428333822c70ce68", size = 17485132, upload-time = "2025-04-08T19:49:50.076Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e9/06c2bf49d6808359d71f1126ec5b8e5a5c3c9526899ed58f24666e0e1b86/ctranslate2-4.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b2ca5c2905b540dd833a0b75d912ec9acc18d33a2dc4f85f12032851659a0d", size = 38816537, upload-time = "2025-04-08T19:49:52.735Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4c/0ecd260233290bee4b2facec4d8e755e57d8781d68f276e1248433993c9f/ctranslate2-4.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:511cdf810a5bf6a2cec735799e5cd47966e63f8f7688fdee1b97fed621abda00", size = 19470040, upload-time = "2025-04-08T19:49:55.274Z" }, -] - -[[package]] -name = "ctypesgen" -version = "1.1.1" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/85/a3/048c4b4d4d31e2ff0d5266b467db01c2ea76b8276653268b7a25e7eb72fc/ctypesgen-1.1.1.tar.gz", hash = "sha256:deaa2d64a95d90196a2e8a689cf9b952be6f3366f81e835245354bf9dbac92f6", size = 143415, upload-time = "2022-10-19T07:00:54.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/92/f344ba59f3aeb3bb37d05c445229b2a8c25d7bfa61e2759cde7f14a64d9a/ctypesgen-1.1.1-py3-none-any.whl", hash = "sha256:94cc6c89ccdd93a72a4c915266cde9a82bfe693331d9d880f66fe9d82af1fc87", size = 124193, upload-time = "2022-10-19T07:00:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d9/b0f73569dda653f398c881b80b62051930f081ac87abb2150070211564b1/ctranslate2-4.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:79113452aaa839a93f7eaeed4ce6555044a863086527b9e39b580cd9f962deaf", size = 1251230, upload-time = "2025-12-05T06:40:13.959Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/8c40bc2f006587bdc7d9881f96aa1be67190c24a0722878704b25162884d/ctranslate2-4.6.2-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:e50074c781ec43071723f142ffec6d5689cf093be7c6f8372348f5ddbe5146de", size = 11912521, upload-time = "2025-12-05T06:40:15.685Z" }, + { url = "https://files.pythonhosted.org/packages/a1/50/05f7daca7442f61a0e5c9b8cfd89f661efdf97a9bd2ec10d93475cd37653/ctranslate2-4.6.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:189fc6a32e979d1e2442e05d6450214252547a58c47a763eebc0014cc234b53d", size = 17485506, upload-time = "2025-12-05T06:40:18.448Z" }, + { url = "https://files.pythonhosted.org/packages/5c/47/14f4ce74ae900d609e127a6de03f364f8b9e10bd6729d29e8b199da71b05/ctranslate2-4.6.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff3a9714aa6a1ee8be6abda58fdd0ef819bb92f4da510e4bb65ab51ed62df64c", size = 38012473, upload-time = "2025-12-05T06:40:21.96Z" }, + { url = "https://files.pythonhosted.org/packages/96/c7/9e221c6ba96b961a9318c5c179edae0567c783a9b81d14627f4f2b0cc866/ctranslate2-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:3462d5dd849ed006ca98be0237a949d1cce5c285f09405c416a6f2b80181235e", size = 18587949, upload-time = "2025-12-05T06:40:25.076Z" }, ] [[package]] @@ -708,21 +711,24 @@ wheels = [ [[package]] name = "cython" -version = "3.1.6" +version = "3.2.2" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/58/6a8321cc0791876dc2509d7a22fc75535a1a7aa770b3496772f58b0a53a4/cython-3.1.6.tar.gz", hash = "sha256:ff4ccffcf98f30ab5723fc45a39c0548a3f6ab14f01d73930c5bfaea455ff01c", size = 3192329, upload-time = "2025-10-23T12:38:20.786Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/17/55fc687ba986f2210298fa2f60fec265fa3004c3f9a1e958ea1fe2d4e061/cython-3.2.2.tar.gz", hash = "sha256:c3add3d483acc73129a61d105389344d792c17e7c1cee24863f16416bd071634", size = 3275797, upload-time = "2025-11-30T12:48:20.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/cd/6e7bb9ef074d35c1b62af91c9f92126fae992d5a8fb6b47fdd1ade67bf56/cython-3.1.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d2c32e8f6c65854e8203b381ff7ab540820763756b7c326e2c8dc18c9bbb44e", size = 3059014, upload-time = "2025-10-23T12:39:16.823Z" }, - { url = "https://files.pythonhosted.org/packages/13/04/a1b4fe2a4c72eb8fdcdf6b680908328f920f813caeb72f1b5d2cea40e45c/cython-3.1.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be24fcde7300a81712af279467ebc79baafc8483eb4dfa4daebf8ee90a826d39", size = 2966746, upload-time = "2025-10-23T12:39:18.56Z" }, - { url = "https://files.pythonhosted.org/packages/57/44/347f48b0ccfaa8233860a64b88a9df851138058ea923583e68625528710f/cython-3.1.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5012025af433bd7188fe1f7705df1c4a67e7add80c71658f6c6bc35ea876cc68", size = 3383297, upload-time = "2025-10-23T12:39:20.231Z" }, - { url = "https://files.pythonhosted.org/packages/98/80/e065d0725614ce9ff43624ae1d9f81647c5fd2d88ecffc2614dde703482d/cython-3.1.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b3520e2d4484f927c3ec00d32ffda75ec72cfd6a2ee07adac721cce339fa26f", size = 3164391, upload-time = "2025-10-23T12:39:22.036Z" }, - { url = "https://files.pythonhosted.org/packages/95/e1/3f86f321ff6bfd31310a5478f5ac56eaac3ea0743f6b76543ff5fbcb2b4e/cython-3.1.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8a01d241d775319bcd7adb4144b070e1c4b01cdf841a62032492f07fad9efdc", size = 3316085, upload-time = "2025-10-23T12:39:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/94/b5/677a2f4faa1c036cedbb715edc933b09de3e235891f1fcdaa82f8c3fdc85/cython-3.1.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fd88799fa7bb177182423e0745c9197c50938c6839ebfbe6fd01539582ed488e", size = 3176911, upload-time = "2025-10-23T12:39:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e4/21117a7768ab19fcd766f2dd81f0a61d2d24e7a3649eff306349c2ab99a8/cython-3.1.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f809bae2e00b79c01ff5daf9a260df7c1bc9fda087b9d625592fa28c1a2248a9", size = 3396231, upload-time = "2025-10-23T12:39:28.168Z" }, - { url = "https://files.pythonhosted.org/packages/b5/4e/1152e9bfa0357d2237449fad94673c273f72c011a54c7227bb1291dd4423/cython-3.1.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f657e7a4b2242d159de603f280928d8e458dfba48144714774ad76c08f5a530", size = 3327101, upload-time = "2025-10-23T12:39:30.361Z" }, - { url = "https://files.pythonhosted.org/packages/39/fe/b7f9dc5ba8ce221aa7d40587d1d7175871b2ea61917c7fa4d5e85a7c042f/cython-3.1.6-cp312-cp312-win32.whl", hash = "sha256:6502f3e58db0ab3e2c983bec2c8c9e45d602e2c7ff921a5a8515b0008d918102", size = 2483823, upload-time = "2025-10-23T12:39:31.986Z" }, - { url = "https://files.pythonhosted.org/packages/40/d5/60261f023b0bdb28f0b9e8f00690b8bdbef692995184bc57f33811f8a936/cython-3.1.6-cp312-cp312-win_amd64.whl", hash = "sha256:71d099d8d6094c5de63a32e67b29964565aed889a218e8d16a94083f4239b904", size = 2701846, upload-time = "2025-10-23T12:39:33.769Z" }, - { url = "https://files.pythonhosted.org/packages/18/d5/7a04640bf559bb890455ffb28978daf7d44f667c3f04a4d422c655c1ba92/cython-3.1.6-py3-none-any.whl", hash = "sha256:91dcf7eb9b6a089ce4e9e1140e571d84c3bca834afb77ec269be7aa9d31a8157", size = 1223550, upload-time = "2025-10-23T12:38:16.732Z" }, + { url = "https://files.pythonhosted.org/packages/57/0f/6fd78dc581373722bb9dedfc90c35b59ba88af988756315af227a877c7a2/cython-3.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:692a41c8fe06fb2dc55ca2c8d71c80c469fd16fe69486ed99f3b3cbb2d3af83f", size = 2968037, upload-time = "2025-11-30T12:48:47.279Z" }, + { url = "https://files.pythonhosted.org/packages/b0/52/50b6263c2fbad73aae8911ce54641ee1739d430a0592d3b3510591d7842b/cython-3.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:098590c1dc309f8a0406ade031963a95a87714296b425539f9920aebf924560d", size = 3223137, upload-time = "2025-11-30T12:48:48.951Z" }, + { url = "https://files.pythonhosted.org/packages/d6/44/4e34d161674c9162c6eb9ddef0cd69d41d92ae7e6dee3945fed3a6871ebe/cython-3.2.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3898c076e9c458bcb3e4936187919fda5f5365fe4c567d35d2b003444b6f3fe", size = 3390943, upload-time = "2025-11-30T12:48:51.125Z" }, + { url = "https://files.pythonhosted.org/packages/62/8a/ffc2df024c1341737008fbaf0fbea51ef983a7146b43b84a239f197cf005/cython-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:2b910b89a2a71004064c5e890b9512a251eda63fae252caa0feb9835057035f9", size = 2756403, upload-time = "2025-11-30T12:48:52.929Z" }, + { url = "https://files.pythonhosted.org/packages/f4/69/5430879d35235ec3d5ffd778862173b6419390509ae4e37a72bdd45d9e86/cython-3.2.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a6387e3ad31342443916db9a419509935fddd8d4cbac34aab9c895ae55326a56", size = 2874031, upload-time = "2025-11-30T12:49:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/51/fa/584f4b56b35b3e7a43dc16603dd722cb5528484da67c27136534b782827b/cython-3.2.2-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:436eb562d0affbc0b959f62f3f9c1ed251b9499e4f29c1d19514ae859894b6bf", size = 3210813, upload-time = "2025-11-30T12:49:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d4/063c34a34d9ef54836a5dafb100b8f4fdbdaa63942913fe93f9eb93a11a2/cython-3.2.2-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f560ff3aea5b5df93853ec7bf1a1e9623d6d511f4192f197559aca18fca43392", size = 2855611, upload-time = "2025-11-30T12:49:22.303Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/c0b8854e0bf6d444c88cc2050f550d964596daea20eaf1bc592fcfde2782/cython-3.2.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d8c93fe128b58942832b1fcac96e48f93c2c69b569eff0d38d30fb5995fecfa0", size = 2992824, upload-time = "2025-11-30T12:49:24.02Z" }, + { url = "https://files.pythonhosted.org/packages/90/6f/741186935c52de99acf4d7fad5c3dcf28d980b4c95d171d9618f9c399316/cython-3.2.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:b4fe499eed7cd70b2aa4e096b9ce2588f5e6fdf049b46d40a5e55efcde6e4904", size = 2890389, upload-time = "2025-11-30T12:49:25.783Z" }, + { url = "https://files.pythonhosted.org/packages/5c/79/3e487876addd0d69c148a529f3973c1942498ad39cede1e63565676064ed/cython-3.2.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:14432d7f207245a3c35556155873f494784169297b28978a6204f1c60d31553e", size = 3224881, upload-time = "2025-11-30T12:49:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/15/b9/d9a103feb74d04579c6bde7b0cad6d5f45c002d843ca70788a5758707b68/cython-3.2.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:820c4a99dbf6b3e6c0300be42b4040b501eff0e1feeb80cfa52c48a346fb0df2", size = 3114308, upload-time = "2025-11-30T12:49:29.292Z" }, + { url = "https://files.pythonhosted.org/packages/18/56/90445707cff62ab72136857a0134c5e50f9c73920c1a3af5218dfdae1c19/cython-3.2.2-cp39-abi3-win32.whl", hash = "sha256:826cad0ad43ab05a26e873b5d625f64d458dc739ec6fdeecab848b60a91c4252", size = 2435212, upload-time = "2025-11-30T12:49:32.179Z" }, + { url = "https://files.pythonhosted.org/packages/44/54/25a98c2731521ac9fc18e17d79a0e7d58164d4db398f09e1bd24cdd27ed1/cython-3.2.2-cp39-abi3-win_arm64.whl", hash = "sha256:5f818d40bbcf17e2089e2de7840f0de1c0ca527acf9b044aba79d5f5d8a5bdba", size = 2440536, upload-time = "2025-11-30T12:49:34.109Z" }, + { url = "https://files.pythonhosted.org/packages/76/f2/98fd8d0b514622a789fd2824b59bd6041b799aaeeba14a8d92d52f6654dd/cython-3.2.2-py3-none-any.whl", hash = "sha256:13b99ecb9482aff6a6c12d1ca6feef6940c507af909914b49f568de74fa965fb", size = 1255106, upload-time = "2025-11-30T12:48:18.454Z" }, ] [[package]] @@ -824,12 +830,12 @@ dependencies = [ { name = "openunmix" }, { name = "pyyaml" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, ] @@ -873,8 +879,8 @@ dependencies = [ { name = "cython" }, { name = "numpy" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/fd/4c58807bf855c5929ffa6da55f26dd6b9ae462a4193f5e09cc49fbbfd451/diffq-0.2.4.tar.gz", hash = "sha256:049064861e974ebf00d0badab8b324c775037371419eda3150985b9d477b5bd2", size = 157139, upload-time = "2023-05-05T12:39:43.089Z" } @@ -920,8 +926,8 @@ dependencies = [ { name = "retrying" }, { name = "submitit" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "treetable" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d5/9d/9a13947db237375486c0690f4741dd2b7e1eee20e0ffcb55dbd1b21cc600/dora_search-0.1.12.tar.gz", hash = "sha256:2956fd2c4c7e4b9a4830e83f0d4cf961be45cfba1a2f0570281e91d15ac516fb", size = 87111, upload-time = "2023-05-23T14:36:24.743Z" } @@ -965,20 +971,19 @@ wheels = [ [[package]] name = "faster-whisper" -version = "1.2.0" +version = "1.2.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "av" }, { name = "ctranslate2", version = "4.3.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "ctranslate2", version = "4.6.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "ctranslate2", version = "4.6.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "huggingface-hub" }, { name = "onnxruntime" }, { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/c2/72002e5f80e73941de05f7b4347ea183d29f76768978a04acda68401c931/faster-whisper-1.2.0.tar.gz", hash = "sha256:56b20d616a575049a79f33b04f02db0868ce38c5d057a0b816d36ca59a6d2598", size = 1124896, upload-time = "2025-08-06T00:34:10.878Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/6d/64cdc135e4195f9473c2e42aa1d2268654be4c289223828eee8e6ba4fc6d/faster_whisper-1.2.0-py3-none-any.whl", hash = "sha256:e5535628fe93b5123029b410fd8edba2d28f8cee9f8fff8119138e5a9d81afbe", size = 1118581, upload-time = "2025-08-06T00:34:09.476Z" }, + { url = "https://files.pythonhosted.org/packages/05/99/49ee85903dee060d9f08297b4a342e5e0bcfca2f027a07b4ee0a38ab13f9/faster_whisper-1.2.1-py3-none-any.whl", hash = "sha256:79a66ad50688c0b794dd501dc340a736992a6342f7f95e5811be60b5224a26a7", size = 1118909, upload-time = "2025-10-31T11:35:47.794Z" }, ] [[package]] @@ -1011,10 +1016,10 @@ wheels = [ [[package]] name = "filelock" version = "3.20.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +source = { registry = "https://download.pytorch.org/whl/cu129" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2" }, ] [[package]] @@ -1042,19 +1047,19 @@ wheels = [ [[package]] name = "fonttools" -version = "4.60.1" +version = "4.61.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f9/0e84d593c0e12244150280a630999835a64f2852276161b62a0f98318de0/fonttools-4.61.0.tar.gz", hash = "sha256:ec520a1f0c7758d7a858a00f090c1745f6cde6a7c5e76fb70ea4044a15f712e7", size = 3561884, upload-time = "2025-11-28T17:05:49.491Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706, upload-time = "2025-09-29T21:11:41.826Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716, upload-time = "2025-09-29T21:11:43.893Z" }, - { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175, upload-time = "2025-09-29T21:11:46.439Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031, upload-time = "2025-09-29T21:11:48.977Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966, upload-time = "2025-09-29T21:11:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750, upload-time = "2025-09-29T21:11:56.601Z" }, - { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026, upload-time = "2025-09-29T21:11:58.852Z" }, - { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, + { url = "https://files.pythonhosted.org/packages/00/5d/19e5939f773c7cb05480fe2e881d63870b63ee2b4bdb9a77d55b1d36c7b9/fonttools-4.61.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e24a1565c4e57111ec7f4915f8981ecbb61adf66a55f378fdc00e206059fcfef", size = 2846930, upload-time = "2025-11-28T17:04:46.639Z" }, + { url = "https://files.pythonhosted.org/packages/25/b2/0658faf66f705293bd7e739a4f038302d188d424926be9c59bdad945664b/fonttools-4.61.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2bfacb5351303cae9f072ccf3fc6ecb437a6f359c0606bae4b1ab6715201d87", size = 2383016, upload-time = "2025-11-28T17:04:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/29/a3/1fa90b95b690f0d7541f48850adc40e9019374d896c1b8148d15012b2458/fonttools-4.61.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0bdcf2e29d65c26299cc3d502f4612365e8b90a939f46cd92d037b6cb7bb544a", size = 4949425, upload-time = "2025-11-28T17:04:50.482Z" }, + { url = "https://files.pythonhosted.org/packages/af/00/acf18c00f6c501bd6e05ee930f926186f8a8e268265407065688820f1c94/fonttools-4.61.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6cd0d9051b8ddaf7385f99dd82ec2a058e2b46cf1f1961e68e1ff20fcbb61af", size = 4999632, upload-time = "2025-11-28T17:04:52.508Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e0/19a2b86e54109b1d2ee8743c96a1d297238ae03243897bc5345c0365f34d/fonttools-4.61.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e074bc07c31406f45c418e17c1722e83560f181d122c412fa9e815df0ff74810", size = 4939438, upload-time = "2025-11-28T17:04:54.437Z" }, + { url = "https://files.pythonhosted.org/packages/04/35/7b57a5f57d46286360355eff8d6b88c64ab6331107f37a273a71c803798d/fonttools-4.61.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a9b78da5d5faa17e63b2404b77feeae105c1b7e75f26020ab7a27b76e02039f", size = 5088960, upload-time = "2025-11-28T17:04:56.348Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0e/6c5023eb2e0fe5d1ababc7e221e44acd3ff668781489cc1937a6f83d620a/fonttools-4.61.0-cp312-cp312-win32.whl", hash = "sha256:9821ed77bb676736b88fa87a737c97b6af06e8109667e625a4f00158540ce044", size = 2264404, upload-time = "2025-11-28T17:04:58.149Z" }, + { url = "https://files.pythonhosted.org/packages/36/0b/63273128c7c5df19b1e4cd92e0a1e6ea5bb74a400c4905054c96ad60a675/fonttools-4.61.0-cp312-cp312-win_amd64.whl", hash = "sha256:0011d640afa61053bc6590f9a3394bd222de7cfde19346588beabac374e9d8ac", size = 2314427, upload-time = "2025-11-28T17:04:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/0c/14/634f7daea5ffe6a5f7a0322ba8e1a0e23c9257b80aa91458107896d1dfc7/fonttools-4.61.0-py3-none-any.whl", hash = "sha256:276f14c560e6f98d24ef7f5f44438e55ff5a67f78fa85236b218462c9f5d0635", size = 1144485, upload-time = "2025-11-28T17:05:47.573Z" }, ] [[package]] @@ -1085,10 +1090,10 @@ wheels = [ [[package]] name = "fsspec" version = "2024.12.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/11/de70dee31455c546fbc88301971ec03c328f3d1138cfba14263f651e9551/fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f", size = 291600, upload-time = "2024-12-19T19:57:30.333Z" } +source = { registry = "https://download.pytorch.org/whl/cu129" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/11/de70dee31455c546fbc88301971ec03c328f3d1138cfba14263f651e9551/fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/86/5486b0188d08aa643e127774a99bac51ffa6cf343e3deb0583956dca5b22/fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2", size = 183862, upload-time = "2024-12-19T19:57:28.258Z" }, + { url = "https://files.pythonhosted.org/packages/de/86/5486b0188d08aa643e127774a99bac51ffa6cf343e3deb0583956dca5b22/fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2" }, ] [package.optional-dependencies] @@ -1140,21 +1145,18 @@ wheels = [ [[package]] name = "greenlet" -version = "3.2.4" +version = "3.3.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, - { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, - { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, - { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, - { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, - { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, + { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, + { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, + { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, ] [[package]] @@ -1189,7 +1191,7 @@ wheels = [ [[package]] name = "hatchling" -version = "1.27.0" +version = "1.28.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "packaging" }, @@ -1197,24 +1199,24 @@ dependencies = [ { name = "pluggy" }, { name = "trove-classifiers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/8a/cc1debe3514da292094f1c3a700e4ca25442489731ef7c0814358816bb03/hatchling-1.27.0.tar.gz", hash = "sha256:971c296d9819abb3811112fc52c7a9751c8d381898f36533bb16f9791e941fd6", size = 54983, upload-time = "2024-12-15T17:08:11.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/8e/e480359492affde4119a131da729dd26da742c2c9b604dff74836e47eef9/hatchling-1.28.0.tar.gz", hash = "sha256:4d50b02aece6892b8cd0b3ce6c82cb218594d3ec5836dbde75bf41a21ab004c8", size = 55365, upload-time = "2025-11-27T00:31:13.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl", hash = "sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b", size = 75794, upload-time = "2024-12-15T17:08:10.364Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a5/48cb7efb8b4718b1a4c0c331e3364a3a33f614ff0d6afd2b93ee883d3c47/hatchling-1.28.0-py3-none-any.whl", hash = "sha256:dc48722b68b3f4bbfa3ff618ca07cdea6750e7d03481289ffa8be1521d18a961", size = 76075, upload-time = "2025-11-27T00:31:12.544Z" }, ] [[package]] name = "hf-xet" -version = "1.1.10" +version = "1.2.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", hash = "sha256:408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97", size = 487910, upload-time = "2025-09-12T20:10:27.12Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/a2/343e6d05de96908366bdc0081f2d8607d61200be2ac802769c4284cc65bd/hf_xet-1.1.10-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:686083aca1a6669bc85c21c0563551cbcdaa5cf7876a91f3d074a030b577231d", size = 2761466, upload-time = "2025-09-12T20:10:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/31/f9/6215f948ac8f17566ee27af6430ea72045e0418ce757260248b483f4183b/hf_xet-1.1.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71081925383b66b24eedff3013f8e6bbd41215c3338be4b94ba75fd75b21513b", size = 2623807, upload-time = "2025-09-12T20:10:21.118Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435", size = 3186960, upload-time = "2025-09-12T20:10:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/01/a7/0b2e242b918cc30e1f91980f3c4b026ff2eedaf1e2ad96933bca164b2869/hf_xet-1.1.10-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eae7c1fc8a664e54753ffc235e11427ca61f4b0477d757cc4eb9ae374b69f09c", size = 3087167, upload-time = "2025-09-12T20:10:17.255Z" }, - { url = "https://files.pythonhosted.org/packages/4a/25/3e32ab61cc7145b11eee9d745988e2f0f4fafda81b25980eebf97d8cff15/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0a0005fd08f002180f7a12d4e13b22be277725bc23ed0529f8add5c7a6309c06", size = 3248612, upload-time = "2025-09-12T20:10:24.093Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3d/ab7109e607ed321afaa690f557a9ada6d6d164ec852fd6bf9979665dc3d6/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f900481cf6e362a6c549c61ff77468bd59d6dd082f3170a36acfef2eb6a6793f", size = 3353360, upload-time = "2025-09-12T20:10:25.563Z" }, - { url = "https://files.pythonhosted.org/packages/ee/0e/471f0a21db36e71a2f1752767ad77e92d8cde24e974e03d662931b1305ec/hf_xet-1.1.10-cp37-abi3-win_amd64.whl", hash = "sha256:5f54b19cc347c13235ae7ee98b330c26dd65ef1df47e5316ffb1e87713ca7045", size = 2804691, upload-time = "2025-09-12T20:10:28.433Z" }, + { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" }, + { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" }, + { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" }, ] [[package]] @@ -1372,7 +1374,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/50/fb/396d568039d213446 [[package]] name = "ipython" -version = "9.7.0" +version = "9.8.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1386,9 +1388,9 @@ dependencies = [ { name = "stack-data", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "traitlets", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115, upload-time = "2025-11-05T12:18:54.646Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/51/a703c030f4928646d390b4971af4938a1b10c9dfce694f0d99a0bb073cb2/ipython-9.8.0.tar.gz", hash = "sha256:8e4ce129a627eb9dd221c41b1d2cdaed4ef7c9da8c17c63f6f578fe231141f83", size = 4424940, upload-time = "2025-12-03T10:18:24.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f", size = 618911, upload-time = "2025-11-05T12:18:52.484Z" }, + { url = "https://files.pythonhosted.org/packages/f1/df/8ee1c5dd1e3308b5d5b2f2dfea323bb2f3827da8d654abb6642051199049/ipython-9.8.0-py3-none-any.whl", hash = "sha256:ebe6d1d58d7d988fbf23ff8ff6d8e1622cfdb194daf4b7b73b792c4ec3b85385", size = 621374, upload-time = "2025-12-03T10:18:22.335Z" }, ] [[package]] @@ -1469,7 +1471,7 @@ wheels = [ [[package]] name = "jinja2" version = "3.1.6" -source = { registry = "https://download.pytorch.org/whl/cu128" } +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ { name = "markupsafe" }, ] @@ -1479,27 +1481,27 @@ wheels = [ [[package]] name = "jiter" -version = "0.11.1" +version = "0.12.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/68/0357982493a7b20925aece061f7fb7a2678e3b232f8d73a6edb7e5304443/jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc", size = 168385, upload-time = "2025-10-17T11:31:15.186Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/8b/318e8af2c904a9d29af91f78c1e18f0592e189bbdb8a462902d31fe20682/jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c", size = 305655, upload-time = "2025-10-17T11:29:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/f7/29/6c7de6b5d6e511d9e736312c0c9bfcee8f9b6bef68182a08b1d78767e627/jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d", size = 315645, upload-time = "2025-10-17T11:29:20.889Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5f/ef9e5675511ee0eb7f98dd8c90509e1f7743dbb7c350071acae87b0145f3/jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b", size = 348003, upload-time = "2025-10-17T11:29:22.712Z" }, - { url = "https://files.pythonhosted.org/packages/56/1b/abe8c4021010b0a320d3c62682769b700fb66f92c6db02d1a1381b3db025/jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4", size = 365122, upload-time = "2025-10-17T11:29:24.408Z" }, - { url = "https://files.pythonhosted.org/packages/2a/2d/4a18013939a4f24432f805fbd5a19893e64650b933edb057cd405275a538/jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239", size = 488360, upload-time = "2025-10-17T11:29:25.724Z" }, - { url = "https://files.pythonhosted.org/packages/f0/77/38124f5d02ac4131f0dfbcfd1a19a0fac305fa2c005bc4f9f0736914a1a4/jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711", size = 376884, upload-time = "2025-10-17T11:29:27.056Z" }, - { url = "https://files.pythonhosted.org/packages/7b/43/59fdc2f6267959b71dd23ce0bd8d4aeaf55566aa435a5d00f53d53c7eb24/jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939", size = 358827, upload-time = "2025-10-17T11:29:28.698Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d0/b3cc20ff5340775ea3bbaa0d665518eddecd4266ba7244c9cb480c0c82ec/jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54", size = 385171, upload-time = "2025-10-17T11:29:30.078Z" }, - { url = "https://files.pythonhosted.org/packages/d2/bc/94dd1f3a61f4dc236f787a097360ec061ceeebebf4ea120b924d91391b10/jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d", size = 518359, upload-time = "2025-10-17T11:29:31.464Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8c/12ee132bd67e25c75f542c227f5762491b9a316b0dad8e929c95076f773c/jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250", size = 509205, upload-time = "2025-10-17T11:29:32.895Z" }, - { url = "https://files.pythonhosted.org/packages/39/d5/9de848928ce341d463c7e7273fce90ea6d0ea4343cd761f451860fa16b59/jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e", size = 205448, upload-time = "2025-10-17T11:29:34.217Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b0/8002d78637e05009f5e3fb5288f9d57d65715c33b5d6aa20fd57670feef5/jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87", size = 204285, upload-time = "2025-10-17T11:29:35.446Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a2/bb24d5587e4dff17ff796716542f663deee337358006a80c8af43ddc11e5/jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c", size = 188712, upload-time = "2025-10-17T11:29:37.027Z" }, - { url = "https://files.pythonhosted.org/packages/a6/bc/950dd7f170c6394b6fdd73f989d9e729bd98907bcc4430ef080a72d06b77/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d", size = 302626, upload-time = "2025-10-17T11:31:09.645Z" }, - { url = "https://files.pythonhosted.org/packages/3a/65/43d7971ca82ee100b7b9b520573eeef7eabc0a45d490168ebb9a9b5bb8b2/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838", size = 297034, upload-time = "2025-10-17T11:31:10.975Z" }, - { url = "https://files.pythonhosted.org/packages/19/4c/000e1e0c0c67e96557a279f8969487ea2732d6c7311698819f977abae837/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f", size = 337328, upload-time = "2025-10-17T11:31:12.399Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697, upload-time = "2025-10-17T11:31:13.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" }, + { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" }, + { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" }, + { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" }, + { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" }, + { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" }, ] [[package]] @@ -1557,8 +1559,8 @@ version = "0.2.7" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/19/c9e1596b5572c786b93428d0904280e964c930fae7e6c9368ed9e1b63922/julius-0.2.7.tar.gz", hash = "sha256:3c0f5f5306d7d6016fcc95196b274cae6f07e2c9596eed314e4e7641554fbb08", size = 59640, upload-time = "2022-09-19T16:13:34.2Z" } @@ -1573,7 +1575,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/80/45/e3e542ffa8970ebd7 [[package]] name = "keyring" -version = "25.6.0" +version = "25.7.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "jaraco-classes" }, @@ -1583,9 +1585,9 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] [[package]] @@ -1672,7 +1674,7 @@ wheels = [ [[package]] name = "lhotse" -version = "1.31.1" +version = "1.32.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "audioread" }, @@ -1686,13 +1688,13 @@ dependencies = [ { name = "soundfile" }, { name = "tabulate" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/03/28/ef2fb33e424e29dec83d2a150d76fb1920418a5d93d5268e6ce401cc33ad/lhotse-1.31.1.tar.gz", hash = "sha256:2ebc3c103c3e09313dff0c4e8740584e28ec35d74e985412c6b37279144a9716", size = 654706, upload-time = "2025-09-18T21:43:51.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/fd/32baf46d238f029a22b2c1762fc717ebdb85515fb48bafa395d3de5da0f5/lhotse-1.32.1.tar.gz", hash = "sha256:8b0e946d1bd2c695b09df831ea612913f1a1f103b1aea36a4b43a8778be0a3d5", size = 674412, upload-time = "2025-11-24T16:42:25.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/dd/4a3698be19e7eca530312afefada5cef524b397d69f8eca5e7cd26a1e4d1/lhotse-1.31.1-py3-none-any.whl", hash = "sha256:d1a8a3d79f7b1ec8d2a9daecc871514999b721bee8ab354db6063864362cc857", size = 866472, upload-time = "2025-09-18T21:43:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/e6/90/9c8e26e4d56c93784503f53e68673d9bf6ea588a36f69aa48a20e99f94b0/lhotse-1.32.1-py3-none-any.whl", hash = "sha256:f2013832c568c146a0dbc76b922afa7776c13749a67c7e685f5988fac09472d0", size = 893069, upload-time = "2025-11-24T16:42:23.806Z" }, ] [[package]] @@ -1738,6 +1740,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/ba/c63c5786dfee4c3417094c4b00966e61e4a63efecee22cb7b4c0387dda83/librosa-0.11.0-py3-none-any.whl", hash = "sha256:0b6415c4fd68bff4c29288abe67c6d80b587e0e1e2cfb0aad23e4559504a7fa1", size = 260749, upload-time = "2025-03-11T15:09:52.982Z" }, ] +[[package]] +name = "librt" +version = "0.7.3" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" }, + { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" }, + { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" }, + { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" }, + { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" }, +] + [[package]] name = "lightning" version = "2.4.0" @@ -1748,8 +1769,8 @@ dependencies = [ { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pytorch-lightning", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "torchmetrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -1817,14 +1838,14 @@ wheels = [ [[package]] name = "macholib" -version = "1.16.3" +version = "1.16.4" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "altgraph", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/ee/af1a3842bdd5902ce133bd246eb7ffd4375c38642aeb5dc0ae3a0329dfa2/macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30", size = 59309, upload-time = "2023-09-25T09:10:16.155Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/5d/c059c180c84f7962db0aeae7c3b9303ed1d73d76f2bfbc32bc231c8be314/macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c", size = 38094, upload-time = "2023-09-25T09:10:14.188Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, ] [[package]] @@ -1999,9 +2020,10 @@ wheels = [ [[package]] name = "mpmath" version = "1.3.0" -source = { registry = "https://download.pytorch.org/whl/cu128" } +source = { registry = "https://download.pytorch.org/whl/cu129" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f" } wheels = [ - { url = "https://download.pytorch.org/whl/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c" }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c" }, ] [[package]] @@ -2099,22 +2121,23 @@ wheels = [ [[package]] name = "mypy" -version = "1.18.2" +version = "1.19.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ + { name = "librt" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, - { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, - { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, - { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, + { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, + { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, + { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, ] [[package]] @@ -2143,8 +2166,8 @@ dependencies = [ { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tensorboard", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "text-unidecode", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "wget", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "wrapt", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -2204,10 +2227,11 @@ asr = [ [[package]] name = "networkx" -version = "3.5" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "3.6.1" +source = { registry = "https://download.pytorch.org/whl/cu129" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509" } wheels = [ - { url = "https://download.pytorch.org/whl/networkx-3.5-py3-none-any.whl" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762" }, ] [[package]] @@ -2266,17 +2290,17 @@ wheels = [ [[package]] name = "numpy" version = "1.26.4" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +source = { registry = "https://download.pytorch.org/whl/cu129" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818" }, ] [[package]] @@ -2327,129 +2351,134 @@ wheels = [ [[package]] name = "nvidia-cublas-cu12" -version = "12.8.3.14" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "12.9.1.4" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cublas_cu12-12.8.3.14-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:3f0e05e7293598cf61933258b73e66a160c27d59c4422670bf0b79348c04be44" }, + { url = "https://pypi.nvidia.com/nvidia-cublas-cu12/nvidia_cublas_cu12-12.9.1.4-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:7a950dae01add3b415a5a5cdc4ec818fb5858263e9cca59004bb99fdbbd3a5d6" }, + { url = "https://pypi.nvidia.com/nvidia-cublas-cu12/nvidia_cublas_cu12-12.9.1.4-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:453611eb21a7c1f2c2156ed9f3a45b691deda0440ec550860290dc901af5b4c2" }, + { url = "https://pypi.nvidia.com/nvidia-cublas-cu12/nvidia_cublas_cu12-12.9.1.4-py3-none-win_amd64.whl", hash = "sha256:1e5fee10662e6e52bd71dec533fbbd4971bb70a5f24f3bc3793e5c2e9dc640bf" }, ] [[package]] name = "nvidia-cuda-cupti-cu12" -version = "12.8.57" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "12.9.79" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cuda_cupti_cu12-12.8.57-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e0b2eb847de260739bee4a3f66fac31378f4ff49538ff527a38a01a9a39f950" }, + { url = "https://pypi.nvidia.com/nvidia-cuda-cupti-cu12/nvidia_cuda_cupti_cu12-12.9.79-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:096bcf334f13e1984ba36685ad4c1d6347db214de03dbb6eebb237b41d9d934f" }, ] [[package]] name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.61" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "12.9.86" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cuda_nvrtc_cu12-12.8.61-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a0fa9c2a21583105550ebd871bd76e2037205d56f33f128e69f6d2a55e0af9ed" }, + { url = "https://pypi.nvidia.com/nvidia-cuda-nvrtc-cu12/nvidia_cuda_nvrtc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:210cf05005a447e29214e9ce50851e83fc5f4358df8b453155d5e1918094dcb4" }, + { url = "https://pypi.nvidia.com/nvidia-cuda-nvrtc-cu12/nvidia_cuda_nvrtc_cu12-12.9.86-py3-none-win_amd64.whl", hash = "sha256:72972ebdcf504d69462d3bcd67e7b81edd25d0fb85a2c46d3ea3517666636349" }, ] [[package]] name = "nvidia-cuda-runtime-cu12" -version = "12.8.57" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "12.9.79" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cuda_runtime_cu12-12.8.57-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75342e28567340b7428ce79a5d6bb6ca5ff9d07b69e7ce00d2c7b4dc23eff0be" }, + { url = "https://pypi.nvidia.com/nvidia-cuda-runtime-cu12/nvidia_cuda_runtime_cu12-12.9.79-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25bba2dfb01d48a9b59ca474a1ac43c6ebf7011f1b0b8cc44f54eb6ac48a96c3" }, + { url = "https://pypi.nvidia.com/nvidia-cuda-runtime-cu12/nvidia_cuda_runtime_cu12-12.9.79-py3-none-win_amd64.whl", hash = "sha256:8e018af8fa02363876860388bd10ccb89eb9ab8fb0aa749aaf58430a9f7c4891" }, ] [[package]] name = "nvidia-cudnn-cu12" -version = "9.7.1.26" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "9.10.2.21" +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, + { name = "nvidia-cublas-cu12", marker = "sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cudnn_cu12-9.7.1.26-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:6d011159a158f3cfc47bf851aea79e31bcff60d530b70ef70474c84cac484d07" }, + { url = "https://pypi.nvidia.com/nvidia-cudnn-cu12/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c9132cc3f8958447b4910a1720036d9eff5928cc3179b0a51fb6d167c6cc87d8" }, + { url = "https://pypi.nvidia.com/nvidia-cudnn-cu12/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8" }, + { url = "https://pypi.nvidia.com/nvidia-cudnn-cu12/nvidia_cudnn_cu12-9.10.2.21-py3-none-win_amd64.whl", hash = "sha256:c6288de7d63e6cf62988f0923f96dc339cea362decb1bf5b3141883392a7d65e" }, ] [[package]] name = "nvidia-cufft-cu12" -version = "11.3.3.41" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "11.4.1.4" +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cufft_cu12-11.3.3.41-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:da650080ab79fcdf7a4b06aa1b460e99860646b176a43f6208099bdc17836b6a" }, + { url = "https://pypi.nvidia.com/nvidia-cufft-cu12/nvidia_cufft_cu12-11.4.1.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c67884f2a7d276b4b80eb56a79322a95df592ae5e765cf1243693365ccab4e28" }, ] [[package]] name = "nvidia-cufile-cu12" -version = "1.13.0.11" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "1.14.1.1" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cufile_cu12-1.13.0.11-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:483f434c541806936b98366f6d33caef5440572de8ddf38d453213729da3e7d4" }, + { url = "https://pypi.nvidia.com/nvidia-cufile-cu12/nvidia_cufile_cu12-1.14.1.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9552e2231792e94b1ff17bc99e958cc0e6bbbaa4a9d91fa2dbeed97716628fe6" }, ] [[package]] name = "nvidia-curand-cu12" -version = "10.3.9.55" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "10.3.10.19" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_curand_cu12-10.3.9.55-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8387d974240c91f6a60b761b83d4b2f9b938b7e0b9617bae0f0dafe4f5c36b86" }, + { url = "https://pypi.nvidia.com/nvidia-curand-cu12/nvidia_curand_cu12-10.3.10.19-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:49b274db4780d421bd2ccd362e1415c13887c53c214f0d4b761752b8f9f6aa1e" }, ] [[package]] name = "nvidia-cusolver-cu12" -version = "11.7.2.55" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "11.7.5.82" +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cusolver_cu12-11.7.2.55-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4d1354102f1e922cee9db51920dba9e2559877cf6ff5ad03a00d853adafb191b" }, + { url = "https://pypi.nvidia.com/nvidia-cusolver-cu12/nvidia_cusolver_cu12-11.7.5.82-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:15da72d1340d29b5b3cf3fd100e3cd53421dde36002eda6ed93811af63c40d88" }, ] [[package]] name = "nvidia-cusparse-cu12" -version = "12.5.7.53" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "12.5.10.65" +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cusparse_cu12-12.5.7.53-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c1b61eb8c85257ea07e9354606b26397612627fdcd327bfd91ccf6155e7c86d" }, + { url = "https://pypi.nvidia.com/nvidia-cusparse-cu12/nvidia_cusparse_cu12-12.5.10.65-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:73060ce019ac064a057267c585bf1fd5a353734151f87472ff02b2c5c9984e78" }, ] [[package]] name = "nvidia-cusparselt-cu12" -version = "0.6.3" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "0.7.1" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46" }, - { url = "https://download.pytorch.org/whl/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46" }, + { url = "https://pypi.nvidia.com/nvidia-cusparselt-cu12/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623" }, ] [[package]] name = "nvidia-nccl-cu12" -version = "2.26.2" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "2.27.3" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6" }, + { url = "https://pypi.nvidia.com/nvidia-nccl-cu12/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039" }, ] [[package]] name = "nvidia-nvjitlink-cu12" -version = "12.8.61" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "12.9.86" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_nvjitlink_cu12-12.8.61-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:45fd79f2ae20bd67e8bc411055939049873bfd8fac70ff13bd4865e0b9bdab17" }, + { url = "https://pypi.nvidia.com/nvidia-nvjitlink-cu12/nvidia_nvjitlink_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:e3f1171dbdc83c5932a45f0f4c99180a70de9bd2718c1ab77d14104f6d7147f9" }, ] [[package]] name = "nvidia-nvtx-cu12" -version = "12.8.55" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "12.9.79" +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ - { url = "https://download.pytorch.org/whl/cu128/nvidia_nvtx_cu12-12.8.55-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dd0780f1a55c21d8e06a743de5bd95653de630decfff40621dbde78cc307102" }, + { url = "https://pypi.nvidia.com/nvidia-nvtx-cu12/nvidia_nvtx_cu12-12.9.79-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fec150986817f2b4e7eed72ed059f2dcb9ba3856b9a96134e448eac946a6952f" }, ] [[package]] @@ -2467,7 +2496,7 @@ wheels = [ [[package]] name = "onnx" -version = "1.19.1" +version = "1.20.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "ml-dtypes", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -2475,14 +2504,14 @@ dependencies = [ { name = "protobuf", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/2f/c619eb65769357e9b6de9212c9a821ab39cd484448e5d6b3fb5fb0a64c6d/onnx-1.19.1.tar.gz", hash = "sha256:737524d6eb3907d3499ea459c6f01c5a96278bb3a0f2ff8ae04786fb5d7f1ed5", size = 12033525, upload-time = "2025-10-10T04:01:34.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/bf/824b13b7ea14c2d374b48a296cfa412442e5559326fbab5441a4fcb68924/onnx-1.20.0.tar.gz", hash = "sha256:1a93ec69996b4556062d552ed1aa0671978cfd3c17a40bf4c89a1ae169c6a4ad", size = 12049527, upload-time = "2025-12-01T18:14:34.679Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/07/f6c5b2cffef8c29e739616d1415aea22f7b7ef1f19c17f02b7cff71f5498/onnx-1.19.1-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:3612193a89ddbce5c4e86150869b9258780a82fb8c4ca197723a4460178a6ce9", size = 18327840, upload-time = "2025-10-10T04:00:24.259Z" }, - { url = "https://files.pythonhosted.org/packages/93/20/0568ebd52730287ae80cac8ac893a7301c793ea1630984e2519ee92b02a9/onnx-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c2fd2f744e7a3880ad0c262efa2edf6d965d0bd02b8f327ec516ad4cb0f2f15", size = 18042539, upload-time = "2025-10-10T04:00:27.693Z" }, - { url = "https://files.pythonhosted.org/packages/14/fd/cd7a0fd10a04f8cc5ae436b63e0022e236fe51b9dbb8ee6317fd48568c72/onnx-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:485d3674d50d789e0ee72fa6f6e174ab81cb14c772d594f992141bd744729d8a", size = 18218271, upload-time = "2025-10-10T04:00:30.495Z" }, - { url = "https://files.pythonhosted.org/packages/65/68/cc8b8c05469fe08384b446304ad7e6256131ca0463bf6962366eebec98c0/onnx-1.19.1-cp312-cp312-win32.whl", hash = "sha256:638bc56ff1a5718f7441e887aeb4e450f37a81c6eac482040381b140bd9ba601", size = 16345111, upload-time = "2025-10-10T04:00:34.982Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5e/d1cb16693598a512c2cf9ffe0841d8d8fd2c83ae8e889efd554f5aa427cf/onnx-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc7e2e4e163e679721e547958b5a7db875bf822cad371b7c1304aa4401a7c7a4", size = 16465621, upload-time = "2025-10-10T04:00:39.107Z" }, - { url = "https://files.pythonhosted.org/packages/90/32/da116cc61fdef334782aa7f87a1738431dd1af1a5d1a44bd95d6d51ad260/onnx-1.19.1-cp312-cp312-win_arm64.whl", hash = "sha256:17c215b1c0f20fe93b4cbe62668247c1d2294b9bc7f6be0ca9ced28e980c07b7", size = 16437505, upload-time = "2025-10-10T04:00:42.255Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/2caa972a31014a8cb4525f715f2a75d93caef9d4b9da2809cc05d0489e43/onnx-1.20.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:31efe37d7d1d659091f34ddd6a31780334acf7c624176832db9a0a8ececa8fb5", size = 18340913, upload-time = "2025-12-01T18:14:00.477Z" }, + { url = "https://files.pythonhosted.org/packages/78/bb/b98732309f2f6beb4cdcf7b955d7bbfd75a191185370ee21233373db381e/onnx-1.20.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d75da05e743eb9a11ff155a775cae5745e71f1cd0ca26402881b8f20e8d6e449", size = 17896118, upload-time = "2025-12-01T18:14:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/84/a7/38aa564871d062c11538d65c575af9c7e057be880c09ecbd899dd1abfa83/onnx-1.20.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02e0d72ab09a983fce46686b155a5049898558d9f3bc6e8515120d6c40666318", size = 18115415, upload-time = "2025-12-01T18:14:06.261Z" }, + { url = "https://files.pythonhosted.org/packages/3b/17/a600b62cf4ad72976c66f83ce9e324205af434706ad5ec0e35129e125aef/onnx-1.20.0-cp312-abi3-win32.whl", hash = "sha256:392ca68b34b97e172d33b507e1e7bfdf2eea96603e6e7ff109895b82ff009dc7", size = 16363019, upload-time = "2025-12-01T18:14:09.16Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3b/5146ba0a89f73c026bb468c49612bab8d005aa28155ebf06cf5f2eb8d36c/onnx-1.20.0-cp312-abi3-win_amd64.whl", hash = "sha256:259b05758d41645f5545c09f887187662b350d40db8d707c33c94a4f398e1733", size = 16485934, upload-time = "2025-12-01T18:14:13.046Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bc/d251b97395e721b3034e9578d4d4d9fb33aac4197ae16ce8c7ed79a26dce/onnx-1.20.0-cp312-abi3-win_arm64.whl", hash = "sha256:2d25a9e1fde44bc69988e50e2211f62d6afcd01b0fd6dfd23429fd978a35d32f", size = 16444946, upload-time = "2025-12-01T18:14:15.801Z" }, ] [[package]] @@ -2526,7 +2555,7 @@ wheels = [ [[package]] name = "openai-whisper" -version = "20240930" +version = "20250625" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "more-itertools" }, @@ -2534,12 +2563,12 @@ dependencies = [ { name = "numpy" }, { name = "tiktoken" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "tqdm" }, { name = "triton", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or sys_platform == 'linux2'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/77/952ca71515f81919bd8a6a4a3f89a27b09e73880cebf90957eda8f2f8545/openai-whisper-20240930.tar.gz", hash = "sha256:b7178e9c1615576807a300024f4daa6353f7e1a815dac5e38c33f1ef055dd2d2", size = 800544, upload-time = "2024-09-30T18:21:22.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/8e/d36f8880bcf18ec026a55807d02fe4c7357da9f25aebd92f85178000c0dc/openai_whisper-20250625.tar.gz", hash = "sha256:37a91a3921809d9f44748ffc73c0a55c9f366c85a3ef5c2ae0cc09540432eb96", size = 803191, upload-time = "2025-06-26T01:06:13.34Z" } [[package]] name = "openunmix" @@ -2548,12 +2577,12 @@ source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "numpy" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/ef/4ad54e3ecb1e89f7f7bdb4c7b751e43754e892d3c32a8550e5d0882565df/openunmix-1.3.0.tar.gz", hash = "sha256:cc9245ce728700f5d0b72c67f01be4162777e617cdc47f9b035963afac180fc8", size = 45889, upload-time = "2024-04-16T11:10:47.121Z" } @@ -2638,11 +2667,11 @@ wheels = [ [[package]] name = "pefile" -version = "2023.2.7" +version = "2024.8.26" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/78/c5/3b3c62223f72e2360737fd2a57c30e5b2adecd85e70276879609a7403334/pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc", size = 74854, upload-time = "2023-02-07T12:23:55.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, ] [[package]] @@ -2657,8 +2686,8 @@ dependencies = [ { name = "psutil", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "safetensors", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "transformers", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] @@ -2682,20 +2711,20 @@ wheels = [ [[package]] name = "pillow" version = "12.0.0" -source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +source = { registry = "https://download.pytorch.org/whl/cu129" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, - { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, - { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad" }, ] [[package]] @@ -2709,11 +2738,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] [[package]] @@ -2833,18 +2862,16 @@ wheels = [ [[package]] name = "psutil" -version = "7.1.1" +version = "7.1.3" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/89/fc/889242351a932d6183eec5df1fc6539b6f36b6a88444f1e63f18668253aa/psutil-7.1.1.tar.gz", hash = "sha256:092b6350145007389c1cfe5716050f02030a05219d90057ea867d18fe8d372fc", size = 487067, upload-time = "2025-10-19T15:43:59.373Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/30/f97f8fb1f9ecfbeae4b5ca738dcae66ab28323b5cfbc96cb5565f3754056/psutil-7.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8fa59d7b1f01f0337f12cd10dbd76e4312a4d3c730a4fedcbdd4e5447a8b8460", size = 244221, upload-time = "2025-10-19T15:44:03.145Z" }, - { url = "https://files.pythonhosted.org/packages/7b/98/b8d1f61ebf35f4dbdbaabadf9208282d8adc820562f0257e5e6e79e67bf2/psutil-7.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:2a95104eae85d088891716db676f780c1404fc15d47fde48a46a5d61e8f5ad2c", size = 245660, upload-time = "2025-10-19T15:44:05.657Z" }, - { url = "https://files.pythonhosted.org/packages/f0/4a/b8015d7357fefdfe34bc4a3db48a107bae4bad0b94fb6eb0613f09a08ada/psutil-7.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98629cd8567acefcc45afe2f4ba1e9290f579eacf490a917967decce4b74ee9b", size = 286963, upload-time = "2025-10-19T15:44:08.877Z" }, - { url = "https://files.pythonhosted.org/packages/3d/3c/b56076bb35303d0733fc47b110a1c9cce081a05ae2e886575a3587c1ee76/psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92ebc58030fb054fa0f26c3206ef01c31c29d67aee1367e3483c16665c25c8d2", size = 290118, upload-time = "2025-10-19T15:44:11.897Z" }, - { url = "https://files.pythonhosted.org/packages/dc/af/c13d360c0adc6f6218bf9e2873480393d0f729c8dd0507d171f53061c0d3/psutil-7.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:146a704f224fb2ded2be3da5ac67fc32b9ea90c45b51676f9114a6ac45616967", size = 292587, upload-time = "2025-10-19T15:44:14.67Z" }, - { url = "https://files.pythonhosted.org/packages/90/2d/c933e7071ba60c7862813f2c7108ec4cf8304f1c79660efeefd0de982258/psutil-7.1.1-cp37-abi3-win32.whl", hash = "sha256:295c4025b5cd880f7445e4379e6826f7307e3d488947bf9834e865e7847dc5f7", size = 243772, upload-time = "2025-10-19T15:44:16.938Z" }, - { url = "https://files.pythonhosted.org/packages/be/f3/11fd213fff15427bc2853552138760c720fd65032d99edfb161910d04127/psutil-7.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:9b4f17c5f65e44f69bd3a3406071a47b79df45cf2236d1f717970afcb526bcd3", size = 246936, upload-time = "2025-10-19T15:44:18.663Z" }, - { url = "https://files.pythonhosted.org/packages/0a/8d/8a9a45c8b655851f216c1d44f68e3533dc8d2c752ccd0f61f1aa73be4893/psutil-7.1.1-cp37-abi3-win_arm64.whl", hash = "sha256:5457cf741ca13da54624126cd5d333871b454ab133999a9a103fb097a7d7d21a", size = 243944, upload-time = "2025-10-19T15:44:20.666Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, + { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, + { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, ] [[package]] @@ -2981,7 +3008,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.3" +version = "2.12.5" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "annotated-types" }, @@ -2989,38 +3016,38 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.41.5" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, - { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, - { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, - { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, - { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, - { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, - { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, - { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, - { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, - { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, - { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, - { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, ] [[package]] @@ -3052,7 +3079,7 @@ wheels = [ [[package]] name = "pyinstaller" -version = "6.16.0" +version = "6.17.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "altgraph" }, @@ -3063,32 +3090,32 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/94/1f62e95e4a28b64cfbb5b922ef3046f968b47170d37a1e1a029f56ac9cb4/pyinstaller-6.16.0.tar.gz", hash = "sha256:53559fe1e041a234f2b4dcc3288ea8bdd57f7cad8a6644e422c27bb407f3edef", size = 4008473, upload-time = "2025-09-13T20:07:01.733Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/80/9e0dad9c69a7cfd4b5aaede8c6225d762bab7247a2a6b7651e1995522001/pyinstaller-6.17.0.tar.gz", hash = "sha256:be372bd911392b88277e510940ac32a5c2a6ce4b8d00a311c78fa443f4f27313", size = 4014147, upload-time = "2025-11-24T19:43:32.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/0a/c42ce6e5d3de287f2e9432a074fb209f1fb72a86a72f3903849fdb5e4829/pyinstaller-6.16.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:7fd1c785219a87ca747c21fa92f561b0d2926a7edc06d0a0fe37f3736e00bd7a", size = 1027899, upload-time = "2025-09-13T20:05:59.2Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d0/f18fedde32835d5a758f464c75924e2154065625f09d5456c3c303527654/pyinstaller-6.16.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b756ddb9007b8141c5476b553351f9d97559b8af5d07f9460869bfae02be26b0", size = 727990, upload-time = "2025-09-13T20:06:03.583Z" }, - { url = "https://files.pythonhosted.org/packages/7a/db/c8bb47514ce857b24bf9294cf1ff74844b6a489fa0ab4ef6f923288c4e38/pyinstaller-6.16.0-py3-none-manylinux2014_i686.whl", hash = "sha256:0a48f55b85ff60f83169e10050f2759019cf1d06773ad1c4da3a411cd8751058", size = 739238, upload-time = "2025-09-13T20:06:07.69Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3e/451dc784a8fcca0fe9f9b6b802d58555364a95b60f253613a2c83fc6b023/pyinstaller-6.16.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:73ba72e04fcece92e32518bbb1e1fb5ac2892677943dfdff38e01a06e8742851", size = 737142, upload-time = "2025-09-13T20:06:11.732Z" }, - { url = "https://files.pythonhosted.org/packages/71/37/2f457479ef8fa2821cdb448acee2421dfb19fbe908bf5499d1930c164084/pyinstaller-6.16.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b1752488248f7899281b17ca3238eefb5410521291371a686a4f5830f29f52b3", size = 734133, upload-time = "2025-09-13T20:06:15.477Z" }, - { url = "https://files.pythonhosted.org/packages/63/c4/0f7daac4d062a4d1ac2571d8a8b9b5d6812094fcd914d139af591ca5e1ba/pyinstaller-6.16.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ba618a61627ee674d6d68e5de084ba17c707b59a4f2a856084b3999bdffbd3f0", size = 733817, upload-time = "2025-09-13T20:06:19.683Z" }, - { url = "https://files.pythonhosted.org/packages/11/e4/b6127265b42bef883e8873d850becadf748bc5652e5a7029b059328f3c31/pyinstaller-6.16.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:c8b7ef536711617e12fef4673806198872033fa06fa92326ad7fd1d84a9fa454", size = 732912, upload-time = "2025-09-13T20:06:23.46Z" }, - { url = "https://files.pythonhosted.org/packages/2b/00/c6663107bdf814b2916e71563beabd09f693c47712213bc228994cb2cc65/pyinstaller-6.16.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1ebf84d02c51fed19b82a8abb4df536923abd55bb684d694e1356e4ae2a0ce5", size = 732773, upload-time = "2025-09-13T20:06:27.352Z" }, - { url = "https://files.pythonhosted.org/packages/a3/14/cabe9bc5f60b95d2e70e7d045ab94b0015ff8f6c8b16e2142d3597e30749/pyinstaller-6.16.0-py3-none-win32.whl", hash = "sha256:6d5f8617f3650ff9ef893e2ab4ddbf3c0d23d0c602ef74b5df8fbef4607840c8", size = 1313878, upload-time = "2025-09-13T20:06:33.234Z" }, - { url = "https://files.pythonhosted.org/packages/aa/99/2005efbc297e7813c1d6f18484aa94a1a81ce87b6a5b497c563681f4c4ea/pyinstaller-6.16.0-py3-none-win_amd64.whl", hash = "sha256:bc10eb1a787f99fea613509f55b902fbd2d8b73ff5f51ff245ea29a481d97d41", size = 1374706, upload-time = "2025-09-13T20:06:39.95Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f4/4dfcf69b86d60fcaae05a42bbff1616d48a91e71726e5ed795d773dae9b3/pyinstaller-6.16.0-py3-none-win_arm64.whl", hash = "sha256:d0af8a401de792c233c32c44b16d065ca9ab8262ee0c906835c12bdebc992a64", size = 1315923, upload-time = "2025-09-13T20:06:45.846Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/37e419d84d5284ecab11ef8b61306a3b978fe6f0fd69a9541e16bfd72e65/pyinstaller-6.17.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4e446b8030c6e5a2f712e3f82011ecf6c7ead86008357b0d23a0ec4bcde31dac", size = 1031880, upload-time = "2025-11-24T19:42:30.862Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b6/2e184879ab9cf90a1d2867fdd34d507c4d246b3cc52ca05aad00bfc70ee7/pyinstaller-6.17.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:aa9fd87aaa28239c6f0d0210114029bd03f8cac316a90bab071a5092d7c85ad7", size = 731968, upload-time = "2025-11-24T19:42:35.421Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/f529de98f7e5cce7904c19b224990003fc2267eda2ee5fdd8452acb420a9/pyinstaller-6.17.0-py3-none-manylinux2014_i686.whl", hash = "sha256:060b122e43e7c0b23e759a4153be34bd70914135ab955bb18a67181e0dca85a2", size = 743217, upload-time = "2025-11-24T19:42:39.286Z" }, + { url = "https://files.pythonhosted.org/packages/a3/10/c02bfbb050cafc4c353cf69baf95407e211e1372bd286ab5ce5cbc13a30a/pyinstaller-6.17.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cd213d1a545c97dfe4a3c40e8213ff7c5127fc115c49229f27a3fa541503444b", size = 741119, upload-time = "2025-11-24T19:42:43.12Z" }, + { url = "https://files.pythonhosted.org/packages/11/9d/69fdacfd9335695f5900a376cfe3e4aed28f0720ffc15fee81fdb9d920bc/pyinstaller-6.17.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:89c0d18ba8b62c6607abd8cf2299ae5ffa5c36d8c47f39608ce8c3f357f6099f", size = 738111, upload-time = "2025-11-24T19:42:46.97Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/e8e36e1568f6865ac706c6e1f875c1a346ddaa9f9a8f923d66545d2240ed/pyinstaller-6.17.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2a147b83cdebb07855bd5a663600891550062373a2ca375c58eacead33741a27", size = 737795, upload-time = "2025-11-24T19:42:50.675Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/9dc0f81ccb746c27bfa6ee53164422fe47ee079c7a717d9c4791aba78797/pyinstaller-6.17.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:f8cfbbfa6708e54fb936df6dd6eafaf133e84efb0d2fe25b91cfeefa793c4ca4", size = 736891, upload-time = "2025-11-24T19:42:54.458Z" }, + { url = "https://files.pythonhosted.org/packages/97/e6/bed54821c1ebe1275c559661d3e7bfa23c406673b515252dfbf89db56c65/pyinstaller-6.17.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:97f4c1942f7b4cd73f9e38b49cc8f5f8a6fbb44922cb60dd3073a189b77ee1ae", size = 736752, upload-time = "2025-11-24T19:42:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/c7/84/897d759198676b910d69d42640b6d25d50b449f2209e18127a974cf59dbe/pyinstaller-6.17.0-py3-none-win32.whl", hash = "sha256:ce0be227a037fd4be672226db709088565484f597d6b230bceec19850fdd4c85", size = 1317851, upload-time = "2025-11-24T19:43:04.361Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f5/6a122efe024433ecc34aab6f499e0bd2bbe059c639b77b0045aa2421b0bf/pyinstaller-6.17.0-py3-none-win_amd64.whl", hash = "sha256:b019940dbf7a01489d6b26f9fb97db74b504e0a757010f7ad078675befc85a82", size = 1378685, upload-time = "2025-11-24T19:43:10.395Z" }, + { url = "https://files.pythonhosted.org/packages/c4/96/14991773c9e599707a53594429ccf372f9ee638df3b7d26b65fd1a7433f0/pyinstaller-6.17.0-py3-none-win_arm64.whl", hash = "sha256:3c92a335e338170df7e615f75279cfeea97ade89e6dd7694943c8c185460f7b7", size = 1320032, upload-time = "2025-11-24T19:43:16.388Z" }, ] [[package]] name = "pyinstaller-hooks-contrib" -version = "2025.9" +version = "2025.10" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "packaging" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/83/be0f57c0b77b66c33c2283ebd4ea341022b5a743e97c5fb3bebab82b38b9/pyinstaller_hooks_contrib-2025.9.tar.gz", hash = "sha256:56e972bdaad4e9af767ed47d132362d162112260cbe488c9da7fee01f228a5a6", size = 165189, upload-time = "2025-09-24T11:21:35.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/4f/e33132acdb8f732978e577b8a0130a412cbfe7a3414605e3fd380a975522/pyinstaller_hooks_contrib-2025.10.tar.gz", hash = "sha256:a1a737e5c0dccf1cf6f19a25e2efd109b9fec9ddd625f97f553dac16ee884881", size = 168155, upload-time = "2025-11-22T09:34:36.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/26/23b4cfc77d7f808c69f59070e1e8293a579ec281a547c61562357160b346/pyinstaller_hooks_contrib-2025.9-py3-none-any.whl", hash = "sha256:ccbfaa49399ef6b18486a165810155e5a8d4c59b41f20dc5da81af7482aaf038", size = 444283, upload-time = "2025-09-24T11:21:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/86/de/a7688eed49a1d3df337cdaa4c0d64e231309a52f269850a72051975e3c4a/pyinstaller_hooks_contrib-2025.10-py3-none-any.whl", hash = "sha256:aa7a378518772846221f63a84d6306d9827299323243db890851474dfd1231a9", size = 447760, upload-time = "2025-11-22T09:34:34.753Z" }, ] [[package]] @@ -3290,22 +3317,22 @@ wheels = [ [[package]] name = "pytorch-lightning" -version = "2.5.6" +version = "2.6.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "fsspec", extra = ["http"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "lightning-utilities", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "torchmetrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/1f/94a441d30779e1ffa5f7dc2ac5fa374c142d8b96c347a49a30226264124e/pytorch_lightning-2.5.6.tar.gz", hash = "sha256:c428faaceef74be50b870814d0d7e9f9c6ee748b8769a2afd3366bc69daf3a0f", size = 642830, upload-time = "2025-11-05T20:53:04.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/d7/e3963d9669758f93b07941f4e2e82a394eb3d0980e29baa4764f3bad6689/pytorch_lightning-2.6.0.tar.gz", hash = "sha256:25b0d4f05e1f33b72be0920c34d0465777fe5f623228f9d6252b4b0f685d7037", size = 658853, upload-time = "2025-11-28T09:34:13.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/e4/32ed2f33c1b634f7c2895369222f4f8cb345044f4642bbff718e7dd1e0b7/pytorch_lightning-2.5.6-py3-none-any.whl", hash = "sha256:037bad1e2fd94d5eb6c5144f045fd4c1070c3d38fc9c14d9f3774a3a9be54dff", size = 831555, upload-time = "2025-11-05T20:53:03.316Z" }, + { url = "https://files.pythonhosted.org/packages/77/eb/cc6dbfe70d15318dbce82674b1e8057cef2634ca9f9121a16b8a06c630db/pytorch_lightning-2.6.0-py3-none-any.whl", hash = "sha256:ee72cff4b8c983ecfaae8599382544bd5236d9eb300adc7dd305f359195f4e79", size = 849476, upload-time = "2025-11-28T09:34:11.271Z" }, ] [[package]] @@ -3388,24 +3415,24 @@ wheels = [ [[package]] name = "regex" -version = "2025.10.23" +version = "2025.11.3" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/c8/1d2160d36b11fbe0a61acb7c3c81ab032d9ec8ad888ac9e0a61b85ab99dd/regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26", size = 401266, upload-time = "2025-10-21T15:58:20.23Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669, upload-time = "2025-11-03T21:34:22.089Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/57/eeb274d83ab189d02d778851b1ac478477522a92b52edfa6e2ae9ff84679/regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc", size = 489187, upload-time = "2025-10-21T15:55:18.322Z" }, - { url = "https://files.pythonhosted.org/packages/55/5c/7dad43a9b6ea88bf77e0b8b7729a4c36978e1043165034212fd2702880c6/regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2", size = 291122, upload-time = "2025-10-21T15:55:20.2Z" }, - { url = "https://files.pythonhosted.org/packages/66/21/38b71e6f2818f0f4b281c8fba8d9d57cfca7b032a648fa59696e0a54376a/regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45", size = 288797, upload-time = "2025-10-21T15:55:21.932Z" }, - { url = "https://files.pythonhosted.org/packages/be/95/888f069c89e7729732a6d7cca37f76b44bfb53a1e35dda8a2c7b65c1b992/regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e", size = 798442, upload-time = "2025-10-21T15:55:23.747Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/4f903c608faf786627a8ee17c06e0067b5acade473678b69c8094b248705/regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43", size = 864039, upload-time = "2025-10-21T15:55:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/62/19/2df67b526bf25756c7f447dde554fc10a220fd839cc642f50857d01e4a7b/regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca", size = 912057, upload-time = "2025-10-21T15:55:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/99/14/9a39b7c9e007968411bc3c843cc14cf15437510c0a9991f080cab654fd16/regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc", size = 803374, upload-time = "2025-10-21T15:55:28.9Z" }, - { url = "https://files.pythonhosted.org/packages/d4/f7/3495151dd3ca79949599b6d069b72a61a2c5e24fc441dccc79dcaf708fe6/regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a", size = 787714, upload-time = "2025-10-21T15:55:30.628Z" }, - { url = "https://files.pythonhosted.org/packages/28/65/ee882455e051131869957ee8597faea45188c9a98c0dad724cfb302d4580/regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32", size = 858392, upload-time = "2025-10-21T15:55:32.322Z" }, - { url = "https://files.pythonhosted.org/packages/53/25/9287fef5be97529ebd3ac79d256159cb709a07eb58d4be780d1ca3885da8/regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288", size = 850484, upload-time = "2025-10-21T15:55:34.037Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b4/b49b88b4fea2f14dc73e5b5842755e782fc2e52f74423d6f4adc130d5880/regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147", size = 789634, upload-time = "2025-10-21T15:55:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3c/2f8d199d0e84e78bcd6bdc2be9b62410624f6b796e2893d1837ae738b160/regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079", size = 266060, upload-time = "2025-10-21T15:55:37.902Z" }, - { url = "https://files.pythonhosted.org/packages/d7/67/c35e80969f6ded306ad70b0698863310bdf36aca57ad792f45ddc0e2271f/regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842", size = 276931, upload-time = "2025-10-21T15:55:39.502Z" }, - { url = "https://files.pythonhosted.org/packages/f5/a1/4ed147de7d2b60174f758412c87fa51ada15cd3296a0ff047f4280aaa7ca/regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335", size = 270103, upload-time = "2025-10-21T15:55:41.24Z" }, + { url = "https://files.pythonhosted.org/packages/e8/74/18f04cb53e58e3fb107439699bd8375cf5a835eec81084e0bddbd122e4c2/regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8ab71e2e31b16e40868a40a69007bc305e1109bd4658eb6cad007e0bf67c41", size = 489312, upload-time = "2025-11-03T21:31:34.343Z" }, + { url = "https://files.pythonhosted.org/packages/78/3f/37fcdd0d2b1e78909108a876580485ea37c91e1acf66d3bb8e736348f441/regex-2025.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:22b29dda7e1f7062a52359fca6e58e548e28c6686f205e780b02ad8ef710de36", size = 291256, upload-time = "2025-11-03T21:31:35.675Z" }, + { url = "https://files.pythonhosted.org/packages/bf/26/0a575f58eb23b7ebd67a45fccbc02ac030b737b896b7e7a909ffe43ffd6a/regex-2025.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a91e4a29938bc1a082cc28fdea44be420bf2bebe2665343029723892eb073e1", size = 288921, upload-time = "2025-11-03T21:31:37.07Z" }, + { url = "https://files.pythonhosted.org/packages/ea/98/6a8dff667d1af907150432cf5abc05a17ccd32c72a3615410d5365ac167a/regex-2025.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b884f4226602ad40c5d55f52bf91a9df30f513864e0054bad40c0e9cf1afb7", size = 798568, upload-time = "2025-11-03T21:31:38.784Z" }, + { url = "https://files.pythonhosted.org/packages/64/15/92c1db4fa4e12733dd5a526c2dd2b6edcbfe13257e135fc0f6c57f34c173/regex-2025.11.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e0b11b2b2433d1c39c7c7a30e3f3d0aeeea44c2a8d0bae28f6b95f639927a69", size = 864165, upload-time = "2025-11-03T21:31:40.559Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e7/3ad7da8cdee1ce66c7cd37ab5ab05c463a86ffeb52b1a25fe7bd9293b36c/regex-2025.11.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87eb52a81ef58c7ba4d45c3ca74e12aa4b4e77816f72ca25258a85b3ea96cb48", size = 912182, upload-time = "2025-11-03T21:31:42.002Z" }, + { url = "https://files.pythonhosted.org/packages/84/bd/9ce9f629fcb714ffc2c3faf62b6766ecb7a585e1e885eb699bcf130a5209/regex-2025.11.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a12ab1f5c29b4e93db518f5e3872116b7e9b1646c9f9f426f777b50d44a09e8c", size = 803501, upload-time = "2025-11-03T21:31:43.815Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0f/8dc2e4349d8e877283e6edd6c12bdcebc20f03744e86f197ab6e4492bf08/regex-2025.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7521684c8c7c4f6e88e35ec89680ee1aa8358d3f09d27dfbdf62c446f5d4c695", size = 787842, upload-time = "2025-11-03T21:31:45.353Z" }, + { url = "https://files.pythonhosted.org/packages/f9/73/cff02702960bc185164d5619c0c62a2f598a6abff6695d391b096237d4ab/regex-2025.11.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7fe6e5440584e94cc4b3f5f4d98a25e29ca12dccf8873679a635638349831b98", size = 858519, upload-time = "2025-11-03T21:31:46.814Z" }, + { url = "https://files.pythonhosted.org/packages/61/83/0e8d1ae71e15bc1dc36231c90b46ee35f9d52fab2e226b0e039e7ea9c10a/regex-2025.11.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8e026094aa12b43f4fd74576714e987803a315c76edb6b098b9809db5de58f74", size = 850611, upload-time = "2025-11-03T21:31:48.289Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f5/70a5cdd781dcfaa12556f2955bf170cd603cb1c96a1827479f8faea2df97/regex-2025.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:435bbad13e57eb5606a68443af62bed3556de2f46deb9f7d4237bc2f1c9fb3a0", size = 789759, upload-time = "2025-11-03T21:31:49.759Z" }, + { url = "https://files.pythonhosted.org/packages/59/9b/7c29be7903c318488983e7d97abcf8ebd3830e4c956c4c540005fcfb0462/regex-2025.11.3-cp312-cp312-win32.whl", hash = "sha256:3839967cf4dc4b985e1570fd8d91078f0c519f30491c60f9ac42a8db039be204", size = 266194, upload-time = "2025-11-03T21:31:51.53Z" }, + { url = "https://files.pythonhosted.org/packages/1a/67/3b92df89f179d7c367be654ab5626ae311cb28f7d5c237b6bb976cd5fbbb/regex-2025.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:e721d1b46e25c481dc5ded6f4b3f66c897c58d2e8cfdf77bbced84339108b0b9", size = 277069, upload-time = "2025-11-03T21:31:53.151Z" }, + { url = "https://files.pythonhosted.org/packages/d7/55/85ba4c066fe5094d35b249c3ce8df0ba623cfd35afb22d6764f23a52a1c5/regex-2025.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:64350685ff08b1d3a6fff33f45a9ca183dc1d58bbfe4981604e70ec9801bbc26", size = 270330, upload-time = "2025-11-03T21:31:54.514Z" }, ] [[package]] @@ -3460,25 +3487,25 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.28.0" +version = "0.30.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/48/dc/95f074d43452b3ef5d06276696ece4b3b5d696e7c9ad7173c54b1390cd70/rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea", size = 27419, upload-time = "2025-10-22T22:24:29.327Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/5c/6c3936495003875fe7b14f90ea812841a08fca50ab26bd840e924097d9c8/rpds_py-0.28.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6b4f28583a4f247ff60cd7bdda83db8c3f5b05a7a82ff20dd4b078571747708f", size = 366439, upload-time = "2025-10-22T22:22:04.525Z" }, - { url = "https://files.pythonhosted.org/packages/56/f9/a0f1ca194c50aa29895b442771f036a25b6c41a35e4f35b1a0ea713bedae/rpds_py-0.28.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d678e91b610c29c4b3d52a2c148b641df2b4676ffe47c59f6388d58b99cdc424", size = 348170, upload-time = "2025-10-22T22:22:06.397Z" }, - { url = "https://files.pythonhosted.org/packages/18/ea/42d243d3a586beb72c77fa5def0487daf827210069a95f36328e869599ea/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e819e0e37a44a78e1383bf1970076e2ccc4dc8c2bbaa2f9bd1dc987e9afff628", size = 378838, upload-time = "2025-10-22T22:22:07.932Z" }, - { url = "https://files.pythonhosted.org/packages/e7/78/3de32e18a94791af8f33601402d9d4f39613136398658412a4e0b3047327/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5ee514e0f0523db5d3fb171f397c54875dbbd69760a414dccf9d4d7ad628b5bd", size = 393299, upload-time = "2025-10-22T22:22:09.435Z" }, - { url = "https://files.pythonhosted.org/packages/13/7e/4bdb435afb18acea2eb8a25ad56b956f28de7c59f8a1d32827effa0d4514/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3fa06d27fdcee47f07a39e02862da0100cb4982508f5ead53ec533cd5fe55e", size = 518000, upload-time = "2025-10-22T22:22:11.326Z" }, - { url = "https://files.pythonhosted.org/packages/31/d0/5f52a656875cdc60498ab035a7a0ac8f399890cc1ee73ebd567bac4e39ae/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46959ef2e64f9e4a41fc89aa20dbca2b85531f9a72c21099a3360f35d10b0d5a", size = 408746, upload-time = "2025-10-22T22:22:13.143Z" }, - { url = "https://files.pythonhosted.org/packages/3e/cd/49ce51767b879cde77e7ad9fae164ea15dce3616fe591d9ea1df51152706/rpds_py-0.28.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8455933b4bcd6e83fde3fefc987a023389c4b13f9a58c8d23e4b3f6d13f78c84", size = 386379, upload-time = "2025-10-22T22:22:14.602Z" }, - { url = "https://files.pythonhosted.org/packages/6a/99/e4e1e1ee93a98f72fc450e36c0e4d99c35370220e815288e3ecd2ec36a2a/rpds_py-0.28.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ad50614a02c8c2962feebe6012b52f9802deec4263946cddea37aaf28dd25a66", size = 401280, upload-time = "2025-10-22T22:22:16.063Z" }, - { url = "https://files.pythonhosted.org/packages/61/35/e0c6a57488392a8b319d2200d03dad2b29c0db9996f5662c3b02d0b86c02/rpds_py-0.28.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5deca01b271492553fdb6c7fd974659dce736a15bae5dad7ab8b93555bceb28", size = 412365, upload-time = "2025-10-22T22:22:17.504Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6a/841337980ea253ec797eb084665436007a1aad0faac1ba097fb906c5f69c/rpds_py-0.28.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:735f8495a13159ce6a0d533f01e8674cec0c57038c920495f87dcb20b3ddb48a", size = 559573, upload-time = "2025-10-22T22:22:19.108Z" }, - { url = "https://files.pythonhosted.org/packages/e7/5e/64826ec58afd4c489731f8b00729c5f6afdb86f1df1df60bfede55d650bb/rpds_py-0.28.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:961ca621ff10d198bbe6ba4957decca61aa2a0c56695384c1d6b79bf61436df5", size = 583973, upload-time = "2025-10-22T22:22:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ee/44d024b4843f8386a4eeaa4c171b3d31d55f7177c415545fd1a24c249b5d/rpds_py-0.28.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2374e16cc9131022e7d9a8f8d65d261d9ba55048c78f3b6e017971a4f5e6353c", size = 553800, upload-time = "2025-10-22T22:22:22.25Z" }, - { url = "https://files.pythonhosted.org/packages/7d/89/33e675dccff11a06d4d85dbb4d1865f878d5020cbb69b2c1e7b2d3f82562/rpds_py-0.28.0-cp312-cp312-win32.whl", hash = "sha256:d15431e334fba488b081d47f30f091e5d03c18527c325386091f31718952fe08", size = 216954, upload-time = "2025-10-22T22:22:24.105Z" }, - { url = "https://files.pythonhosted.org/packages/af/36/45f6ebb3210887e8ee6dbf1bc710ae8400bb417ce165aaf3024b8360d999/rpds_py-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:a410542d61fc54710f750d3764380b53bf09e8c4edbf2f9141a82aa774a04f7c", size = 227844, upload-time = "2025-10-22T22:22:25.551Z" }, - { url = "https://files.pythonhosted.org/packages/57/91/f3fb250d7e73de71080f9a221d19bd6a1c1eb0d12a1ea26513f6c1052ad6/rpds_py-0.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:1f0cfd1c69e2d14f8c892b893997fa9a60d890a0c8a603e88dca4955f26d1edd", size = 217624, upload-time = "2025-10-22T22:22:26.914Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, ] [[package]] @@ -3552,24 +3579,24 @@ wheels = [ [[package]] name = "safetensors" -version = "0.6.2" +version = "0.7.0" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9", size = 197968, upload-time = "2025-08-08T13:13:58.654Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba", size = 454797, upload-time = "2025-08-08T13:13:52.066Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b", size = 432206, upload-time = "2025-08-08T13:13:50.931Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd", size = 473261, upload-time = "2025-08-08T13:13:41.259Z" }, - { url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a", size = 485117, upload-time = "2025-08-08T13:13:43.506Z" }, - { url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1", size = 616154, upload-time = "2025-08-08T13:13:45.096Z" }, - { url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda", size = 520713, upload-time = "2025-08-08T13:13:46.25Z" }, - { url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f", size = 485835, upload-time = "2025-08-08T13:13:49.373Z" }, - { url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19", size = 521503, upload-time = "2025-08-08T13:13:47.651Z" }, - { url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce", size = 652256, upload-time = "2025-08-08T13:13:53.167Z" }, - { url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7", size = 747281, upload-time = "2025-08-08T13:13:54.656Z" }, - { url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5", size = 692286, upload-time = "2025-08-08T13:13:55.884Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac", size = 655957, upload-time = "2025-08-08T13:13:57.029Z" }, - { url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1", size = 308926, upload-time = "2025-08-08T13:14:01.095Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, + { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" }, + { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" }, + { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" }, + { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" }, + { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" }, + { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, ] [[package]] @@ -3593,36 +3620,36 @@ wheels = [ [[package]] name = "scipy" -version = "1.16.2" +version = "1.16.3" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/8d/6396e00db1282279a4ddd507c5f5e11f606812b608ee58517ce8abbf883f/scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d", size = 36646259, upload-time = "2025-09-11T17:40:39.329Z" }, - { url = "https://files.pythonhosted.org/packages/3b/93/ea9edd7e193fceb8eef149804491890bde73fb169c896b61aa3e2d1e4e77/scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371", size = 28888976, upload-time = "2025-09-11T17:40:46.82Z" }, - { url = "https://files.pythonhosted.org/packages/91/4d/281fddc3d80fd738ba86fd3aed9202331180b01e2c78eaae0642f22f7e83/scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0", size = 20879905, upload-time = "2025-09-11T17:40:52.545Z" }, - { url = "https://files.pythonhosted.org/packages/69/40/b33b74c84606fd301b2915f0062e45733c6ff5708d121dd0deaa8871e2d0/scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232", size = 23553066, upload-time = "2025-09-11T17:40:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/55/a7/22c739e2f21a42cc8f16bc76b47cff4ed54fbe0962832c589591c2abec34/scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1", size = 33336407, upload-time = "2025-09-11T17:41:06.796Z" }, - { url = "https://files.pythonhosted.org/packages/53/11/a0160990b82999b45874dc60c0c183d3a3a969a563fffc476d5a9995c407/scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f", size = 35673281, upload-time = "2025-09-11T17:41:15.055Z" }, - { url = "https://files.pythonhosted.org/packages/96/53/7ef48a4cfcf243c3d0f1643f5887c81f29fdf76911c4e49331828e19fc0a/scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef", size = 36004222, upload-time = "2025-09-11T17:41:23.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7f/71a69e0afd460049d41c65c630c919c537815277dfea214031005f474d78/scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1", size = 38664586, upload-time = "2025-09-11T17:41:31.021Z" }, - { url = "https://files.pythonhosted.org/packages/34/95/20e02ca66fb495a95fba0642fd48e0c390d0ece9b9b14c6e931a60a12dea/scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e", size = 38550641, upload-time = "2025-09-11T17:41:36.61Z" }, - { url = "https://files.pythonhosted.org/packages/92/ad/13646b9beb0a95528ca46d52b7babafbe115017814a611f2065ee4e61d20/scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851", size = 25456070, upload-time = "2025-09-11T17:41:41.3Z" }, + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" }, ] [[package]] name = "secretstorage" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "cryptography", marker = "sys_platform == 'linux'" }, { name = "jeepney", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] [[package]] @@ -3643,15 +3670,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.45.0" +version = "2.47.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "certifi", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "urllib3", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/89/1561b3dc8e28bf7978d031893297e89be266f53650c87bb14a29406a9791/sentry_sdk-2.45.0.tar.gz", hash = "sha256:e9bbfe69d5f6742f48bad22452beffb525bbc5b797d817c7f1b1f7d210cdd271", size = 373631, upload-time = "2025-11-18T13:23:22.475Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/2a/d225cbf87b6c8ecce5664db7bcecb82c317e448e3b24a2dcdaacb18ca9a7/sentry_sdk-2.47.0.tar.gz", hash = "sha256:8218891d5e41b4ea8d61d2aed62ed10c80e39d9f2959d6f939efbf056857e050", size = 381895, upload-time = "2025-12-03T14:06:36.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/c6/039121a0355bc1b5bcceef0dabf211b021fd435d0ee5c46393717bb1c09f/sentry_sdk-2.45.0-py2.py3-none-any.whl", hash = "sha256:86c8ab05dc3e8666aece77a5c747b45b25aa1d5f35f06cde250608f495d50f23", size = 404791, upload-time = "2025-11-18T13:23:20.533Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ac/d6286ea0d49e7b58847faf67b00e56bb4ba3d525281e2ac306e1f1f353da/sentry_sdk-2.47.0-py2.py3-none-any.whl", hash = "sha256:d72f8c61025b7d1d9e52510d03a6247b280094a327dd900d987717a4fce93412", size = 411088, upload-time = "2025-12-03T14:06:35.374Z" }, ] [[package]] @@ -3732,17 +3759,17 @@ wheels = [ [[package]] name = "sounddevice" -version = "0.4.7" +version = "0.5.3" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/88/5832219fa90595932d5f6d1b5125bfd8a55e95b19ad866e265c9bbb7cde4/sounddevice-0.4.7.tar.gz", hash = "sha256:69b386818d50a2d518607d4b973442e8d524760c7cd6c8b8be03d8c98fc4bce7", size = 52244, upload-time = "2024-05-27T19:27:31.663Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/4f/28e734898b870db15b6474453f19813d3c81b91c806d9e6f867bd6e4dd03/sounddevice-0.5.3.tar.gz", hash = "sha256:cbac2b60198fbab84533697e7c4904cc895ec69d5fb3973556c9eb74a4629b2c", size = 53465, upload-time = "2025-10-19T13:23:57.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/ea/e9196f01ec3c5ad537e1bb83fe08da3bacfbdfee8a872c461e491f489801/sounddevice-0.4.7-py3-none-any.whl", hash = "sha256:1c3f18bfa4d9a257f5715f2ab83f2c0eb412a09f3e6a9fa73720886ca88f6bc7", size = 32092, upload-time = "2024-05-27T19:27:26.064Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9c/d8de668a462b7a326d9f697dfa2adb6fbde07cc468cc7cdcf51cbe975d56/sounddevice-0.4.7-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d6ddfd341ad7412b14ca001f2c4dbf5fa2503bdc9eb15ad2c3105f6c260b698a", size = 108360, upload-time = "2024-05-27T19:27:27.298Z" }, - { url = "https://files.pythonhosted.org/packages/96/7f/620dda64a6e7fbdab11ca9065ae72668c78dc331058f51175a62a8fede12/sounddevice-0.4.7-py3-none-win32.whl", hash = "sha256:1ec1df094c468a210113aa22c4f390d5b4d9c7a73e41a6cb6ecfec83db59b380", size = 197641, upload-time = "2024-05-27T19:27:28.615Z" }, - { url = "https://files.pythonhosted.org/packages/d4/09/bfdd393f1bb1b90b4a6849b84972b7059c95e36818cc489922228d58cc63/sounddevice-0.4.7-py3-none-win_amd64.whl", hash = "sha256:0c8b3543da1496f282b66a7bc54b755577ba638b1af06c146d4ac7f39d86b548", size = 200096, upload-time = "2024-05-27T19:27:30.31Z" }, + { url = "https://files.pythonhosted.org/packages/73/e7/9020e9f0f3df00432728f4c4044387468a743e3d9a4f91123d77be10010e/sounddevice-0.5.3-py3-none-any.whl", hash = "sha256:ea7738baa0a9f9fef7390f649e41c9f2c8ada776180e56c2ffd217133c92a806", size = 32670, upload-time = "2025-10-19T13:23:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/2f/39/714118f8413e0e353436914f2b976665161f1be2b6483ac15a8f61484c14/sounddevice-0.5.3-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:278dc4451fff70934a176df048b77d80d7ce1623a6ec9db8b34b806f3112f9c2", size = 108306, upload-time = "2025-10-19T13:23:53.277Z" }, + { url = "https://files.pythonhosted.org/packages/f5/74/52186e3e5c833d00273f7949a9383adff93692c6e02406bf359cb4d3e921/sounddevice-0.5.3-py3-none-win32.whl", hash = "sha256:845d6927bcf14e84be5292a61ab3359cf8e6b9145819ec6f3ac2619ff089a69c", size = 312882, upload-time = "2025-10-19T13:23:54.829Z" }, + { url = "https://files.pythonhosted.org/packages/66/c7/16123d054aef6d445176c9122bfbe73c11087589b2413cab22aff5a7839a/sounddevice-0.5.3-py3-none-win_amd64.whl", hash = "sha256:f55ad20082efc2bdec06928e974fbcae07bc6c405409ae1334cefe7d377eb687", size = 364025, upload-time = "2025-10-19T13:23:56.362Z" }, ] [[package]] @@ -3837,12 +3864,12 @@ dependencies = [ { name = "numpy" }, { name = "openai-whisper" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/d9/d326f9dbbb7da6806aa8cfc080342e5f78dc33552f4339bdc8a6251d11a3/stable_ts-2.19.1.tar.gz", hash = "sha256:0ecaf1ed93e029839569618d2da9a57b883ad04db21f0680146e0650caaf4f52", size = 189132, upload-time = "2025-08-16T16:53:48.811Z" } @@ -3863,15 +3890,15 @@ wheels = [ [[package]] name = "stempeg" -version = "0.2.4" +version = "0.2.6" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "ffmpeg-python" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/ab/b06ccf942eba0a101b9c673cbfaa0fdb716b5d34782e9a3aa47a4df7820e/stempeg-0.2.4.tar.gz", hash = "sha256:e587007187f05215e50d92a693f2ca0d84aef6f45ae6212f85d5a8455f7b2bb1", size = 968027, upload-time = "2025-05-28T08:42:12.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/5f/1996e7d82df0bdda50a26e1d43b7a20c681887a5ae2959eda30ca4fe2a7f/stempeg-0.2.6.tar.gz", hash = "sha256:a71766fc4c8b0a3cb804b5026021a088b2728271ad67b2a99bc20fee10d7b81c", size = 968274, upload-time = "2025-10-31T10:22:51.547Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/07/ce07799f7cc0af533d58b5380957638cc049d66a6a18736094b78cd08d1a/stempeg-0.2.4-py3-none-any.whl", hash = "sha256:83c9e4ac73edcc61a2a807eded0ae2c9f0b99ea3110e46756b3fff153a063838", size = 963032, upload-time = "2025-05-28T08:42:11.181Z" }, + { url = "https://files.pythonhosted.org/packages/00/6e/1355edec39268e2cb5d8d5c7e84c07701d71db081169c568f821c4b6072d/stempeg-0.2.6-py3-none-any.whl", hash = "sha256:aa5d5dcdfba10abf0c76b502c00808a6dedd86a14e64b298615249294e522bee", size = 963160, upload-time = "2025-10-31T10:22:47.288Z" }, ] [[package]] @@ -3899,12 +3926,13 @@ wheels = [ [[package]] name = "sympy" version = "1.14.0" -source = { registry = "https://download.pytorch.org/whl/cu128" } +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ { name = "mpmath" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517" } wheels = [ - { url = "https://download.pytorch.org/whl/sympy-1.14.0-py3-none-any.whl" }, + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5" }, ] [[package]] @@ -4080,39 +4108,39 @@ wheels = [ [[package]] name = "torch" -version = "2.7.1" +version = "2.8.0" source = { registry = "https://pypi.org/simple/" } resolution-markers = [ "platform_machine == 'arm64' and sys_platform == 'darwin'", - "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "filelock", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "fsspec", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "jinja2", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "networkx", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "setuptools", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "sympy", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "filelock", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "fsspec", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "jinja2", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "networkx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "setuptools", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "sympy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/60/04b77281c730bb13460628e518c52721257814ac6c298acd25757f6a175c/torch-2.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:787687087412c4bd68d315e39bc1223f08aae1d16a9e9771d95eabbb04ae98fb", size = 68645146, upload-time = "2025-06-04T17:38:52.97Z" }, + { url = "https://files.pythonhosted.org/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" }, ] [[package]] name = "torch" -version = "2.7.1+cu128" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "2.8.0+cu129" +source = { registry = "https://download.pytorch.org/whl/cu129" } resolution-markers = [ "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", "sys_platform != 'darwin' and sys_platform != 'linux'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", + "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "filelock", marker = "sys_platform != 'darwin'" }, - { name = "fsspec", marker = "sys_platform != 'darwin'" }, - { name = "jinja2", marker = "sys_platform != 'darwin'" }, - { name = "networkx", marker = "sys_platform != 'darwin'" }, + { name = "filelock", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "fsspec", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "jinja2", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "networkx", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -4127,15 +4155,15 @@ dependencies = [ { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "sys_platform != 'darwin'" }, - { name = "sympy", marker = "sys_platform != 'darwin'" }, + { name = "setuptools", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "sympy", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, { name = "triton", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/torch-2.7.1%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:268e54db9f0bc2b7b9eb089852d3e592c2dea2facc3db494100c3d3b796549fa" }, - { url = "https://download.pytorch.org/whl/cu128/torch-2.7.1%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0b64f7d0a6f2a739ed052ba959f7b67c677028c9566ce51997f9f90fe573ddaa" }, - { url = "https://download.pytorch.org/whl/cu128/torch-2.7.1%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:2bb8c05d48ba815b316879a18195d53a6472a03e297d971e916753f8e1053d30" }, + { url = "https://download.pytorch.org/whl/cu129/torch-2.8.0%2Bcu129-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:692fe6e513b667f789a543fa9b1baba58e77a46d5c8629764ca0c00a56823e1f" }, + { url = "https://download.pytorch.org/whl/cu129/torch-2.8.0%2Bcu129-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:02c7258e917f3043c978b53acf6f02b818db0d0d85db0e58ae578af333b9b4e2" }, + { url = "https://download.pytorch.org/whl/cu129/torch-2.8.0%2Bcu129-cp312-cp312-win_amd64.whl", hash = "sha256:2bc729898e422b9f3da54349eed98f2f0b5dd415434508ee2ab2a13fb021815d" }, ] [[package]] @@ -4154,48 +4182,57 @@ wheels = [ [[package]] name = "torchaudio" -version = "2.7.1" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "2.8.0" +source = { registry = "https://download.pytorch.org/whl/cu129" } resolution-markers = [ "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", ] dependencies = [ - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/torchaudio-2.7.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e187fbd6fd771dadee097893785e9f62869739ca21f3509c855eeabd35c05ed3" }, + { url = "https://download.pytorch.org/whl/cu129/torchaudio-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:67d4f6cbfcb795157850db6a3735301b929cdfe2b56032dc3365105b49a4ee84" }, ] [[package]] name = "torchaudio" -version = "2.7.1" +version = "2.8.0" source = { registry = "https://pypi.org/simple/" } resolution-markers = [ "platform_machine == 'arm64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d1/eb8bc3b3502dddb1b789567b7b19668b1d32817266887b9f381494cfe463/torchaudio-2.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9306dcfc4586cebd7647a93fe9a448e791c4f83934da616b9433b75597a1f978", size = 1846897, upload-time = "2025-06-04T17:44:07.79Z" }, + { url = "https://files.pythonhosted.org/packages/ac/cc/c2e2a3eb6ee956f73c68541e439916f8146170ea9cc61e72adea5c995312/torchaudio-2.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ddef94bf181e6447cbb05f38beaca8f6c5bb8d2b9ddced1aa3452025b9fc70d3", size = 1856736, upload-time = "2025-08-06T14:58:36.3Z" }, ] [[package]] name = "torchaudio" -version = "2.7.1+cu128" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "2.8.0+cu129" +source = { registry = "https://download.pytorch.org/whl/cu129" } resolution-markers = [ - "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", "sys_platform != 'darwin' and sys_platform != 'linux'", + "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/cu128/torchaudio-2.7.1%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0c1d407f934d44f87935b139991d8872f81f88f8a6be9b7bd25918bf744e2be6" }, - { url = "https://download.pytorch.org/whl/cu128/torchaudio-2.7.1%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:4586e3106701b06a4f9377f5c1da9e1d8555e16bd58fd7d810aa3f6cf50bd713" }, + { url = "https://download.pytorch.org/whl/cu129/torchaudio-2.8.0%2Bcu129-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:40df9011972519120f284f56e5e7d131d4250ea69653499028d1d30b353f932e" }, + { url = "https://download.pytorch.org/whl/cu129/torchaudio-2.8.0%2Bcu129-cp312-cp312-win_amd64.whl", hash = "sha256:bfe0d4c6e770ef3b1f7a287a4c8d33ac276a1d983306573ee28e42de02a32fe3" }, +] + +[[package]] +name = "torchcodec" +version = "0.9.0" +source = { registry = "https://pypi.org/simple/" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/88/dc4a7928ee80823913b1ec9d6433458b32d5510288030b122c9b3d58b484/torchcodec-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:30d031eafbe287a2a54b90b35109f7e0711b393bbb263cf90487f533b8ac92d4", size = 4063923, upload-time = "2025-12-04T14:16:35.491Z" }, + { url = "https://files.pythonhosted.org/packages/42/10/742531478a71585bcb901913a9a806dc6b8c4097f39e6e82213a129cbaf1/torchcodec-0.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c4b5964e85e616097b35db6927561bbac1cbf227e1a8d4dffb0acf00a7e94725", size = 2061634, upload-time = "2025-12-04T14:16:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5b/1f712dc3cbf26e0eaab90dccf75aee468d63ce340c51beccbdfc426400c1/torchcodec-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:30cf3053ddd54d993b7b28613beab4ffa1199339160c4f489a87bd5bb3c0062b", size = 2186861, upload-time = "2025-12-04T14:16:42.743Z" }, ] [[package]] @@ -4206,8 +4243,8 @@ dependencies = [ { name = "lightning-utilities", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } wheels = [ @@ -4267,23 +4304,23 @@ wheels = [ [[package]] name = "triton" -version = "3.3.1" -source = { registry = "https://download.pytorch.org/whl/cu128" } +version = "3.4.0" +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ { name = "setuptools", marker = "sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://download.pytorch.org/whl/triton-3.3.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl" }, - { url = "https://download.pytorch.org/whl/triton-3.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" }, + { url = "https://download.pytorch.org/whl/triton-3.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/triton-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" }, ] [[package]] name = "trove-classifiers" -version = "2025.9.11.17" +version = "2025.12.1.14" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/9a/778622bc06632529817c3c524c82749a112603ae2bbcf72ee3eb33a2c4f1/trove_classifiers-2025.9.11.17.tar.gz", hash = "sha256:931ca9841a5e9c9408bc2ae67b50d28acf85bef56219b56860876dd1f2d024dd", size = 16975, upload-time = "2025-09-11T17:07:50.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/e1/000add3b3e0725ce7ee0ea6ea4543f1e1d9519742f3b2320de41eeefa7c7/trove_classifiers-2025.12.1.14.tar.gz", hash = "sha256:a74f0400524fc83620a9be74a07074b5cbe7594fd4d97fd4c2bfde625fdc1633", size = 16985, upload-time = "2025-12-01T14:47:11.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/85/a4ff8758c66f1fc32aa5e9a145908394bf9cf1c79ffd1113cfdeb77e74e4/trove_classifiers-2025.9.11.17-py3-none-any.whl", hash = "sha256:5d392f2d244deb1866556457d6f3516792124a23d1c3a463a2e8668a5d1c15dd", size = 14158, upload-time = "2025-09-11T17:07:49.886Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7e/bc19996fa86cad8801e8ffe6f1bba5836ca0160df76d0410d27432193712/trove_classifiers-2025.12.1.14-py3-none-any.whl", hash = "sha256:a8206978ede95937b9959c3aff3eb258bbf7b07dff391ddd4ea7e61f316635ab", size = 14184, upload-time = "2025-12-01T14:47:10.113Z" }, ] [[package]] @@ -4316,7 +4353,7 @@ wheels = [ [[package]] name = "typing-extensions" version = "4.15.0" -source = { registry = "https://download.pytorch.org/whl/cu128" } +source = { registry = "https://download.pytorch.org/whl/cu129" } wheels = [ { url = "https://download.pytorch.org/whl/typing_extensions-4.15.0-py3-none-any.whl" }, ] @@ -4324,7 +4361,7 @@ wheels = [ [[package]] name = "typing-inspect" version = "0.9.0" -source = { registry = "https://download.pytorch.org/whl/cu128" } +source = { registry = "https://download.pytorch.org/whl/cu129" } dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, @@ -4356,11 +4393,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.1" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/1d/0f3a93cca1ac5e8287842ed4eebbd0f7a991315089b1a0b01c7788aa7b63/urllib3-2.6.1.tar.gz", hash = "sha256:5379eb6e1aba4088bae84f8242960017ec8d8e3decf30480b3a1abdaa9671a3f", size = 432678, upload-time = "2025-12-08T15:25:26.773Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/bc/56/190ceb8cb10511b730b564fb1e0293fa468363dbad26145c34928a60cb0c/urllib3-2.6.1-py3-none-any.whl", hash = "sha256:e67d06fe947c36a7ca39f4994b08d73922d40e6cca949907be05efa6fd75110b", size = 131138, upload-time = "2025-12-08T15:25:25.51Z" }, ] [[package]] @@ -4377,16 +4414,16 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.35.3" +version = "20.35.4" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a4/d5/b0ccd381d55c8f45d46f77df6ae59fbc23d19e901e2d523395598e5f4c93/virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44", size = 6002907, upload-time = "2025-10-10T21:23:33.178Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a", size = 5981061, upload-time = "2025-10-10T21:23:30.433Z" }, + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, ] [[package]] @@ -4403,7 +4440,7 @@ wheels = [ [[package]] name = "wandb" -version = "0.23.0" +version = "0.23.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "click", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -4417,17 +4454,17 @@ dependencies = [ { name = "sentry-sdk", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/8b/db2d44395c967cd452517311fd6ede5d1e07310769f448358d4874248512/wandb-0.23.0.tar.gz", hash = "sha256:e5f98c61a8acc3ee84583ca78057f64344162ce026b9f71cb06eea44aec27c93", size = 44413921, upload-time = "2025-11-11T21:06:30.737Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/cc/770ae3aa7ae44f6792f7ecb81c14c0e38b672deb35235719bb1006519487/wandb-0.23.1.tar.gz", hash = "sha256:f6fb1e3717949b29675a69359de0eeb01e67d3360d581947d5b3f98c273567d6", size = 44298053, upload-time = "2025-12-03T02:25:10.79Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/61/a3220c7fa4cadfb2b2a5c09e3fa401787326584ade86d7c1f58bf1cd43bd/wandb-0.23.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:b682ec5e38fc97bd2e868ac7615a0ab4fc6a15220ee1159e87270a5ebb7a816d", size = 18992250, upload-time = "2025-11-11T21:06:03.412Z" }, - { url = "https://files.pythonhosted.org/packages/90/16/e69333cf3d11e7847f424afc6c8ae325e1f6061b2e5118d7a17f41b6525d/wandb-0.23.0-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:ec094eb71b778e77db8c188da19e52c4f96cb9d5b4421d7dc05028afc66fd7e7", size = 20045616, upload-time = "2025-11-11T21:06:07.109Z" }, - { url = "https://files.pythonhosted.org/packages/62/79/42dc6c7bb0b425775fe77f1a3f1a22d75d392841a06b43e150a3a7f2553a/wandb-0.23.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e43f1f04b98c34f407dcd2744cec0a590abce39bed14a61358287f817514a7b", size = 18758848, upload-time = "2025-11-11T21:06:09.832Z" }, - { url = "https://files.pythonhosted.org/packages/b8/94/d6ddb78334996ccfc1179444bfcfc0f37ffd07ee79bb98940466da6f68f8/wandb-0.23.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5847f98cbb3175caf5291932374410141f5bb3b7c25f9c5e562c1988ce0bf5", size = 20231493, upload-time = "2025-11-11T21:06:12.323Z" }, - { url = "https://files.pythonhosted.org/packages/52/4d/0ad6df0e750c19dabd24d2cecad0938964f69a072f05fbdab7281bec2b64/wandb-0.23.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6151355fd922539926e870be811474238c9614b96541773b990f1ce53368aef6", size = 18793473, upload-time = "2025-11-11T21:06:14.967Z" }, - { url = "https://files.pythonhosted.org/packages/f8/da/c2ba49c5573dff93dafc0acce691bb1c3d57361bf834b2f2c58e6193439b/wandb-0.23.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df62e426e448ebc44269140deb7240df474e743b12d4b1f53b753afde4aa06d4", size = 20332882, upload-time = "2025-11-11T21:06:17.865Z" }, - { url = "https://files.pythonhosted.org/packages/40/65/21bfb10ee5cd93fbcaf794958863c7e05bac4bbeb1cc1b652094aa3743a5/wandb-0.23.0-py3-none-win32.whl", hash = "sha256:6c21d3eadda17aef7df6febdffdddfb0b4835c7754435fc4fe27631724269f5c", size = 19433198, upload-time = "2025-11-11T21:06:21.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/33/cbe79e66c171204e32cf940c7fdfb8b5f7d2af7a00f301c632f3a38aa84b/wandb-0.23.0-py3-none-win_amd64.whl", hash = "sha256:b50635fa0e16e528bde25715bf446e9153368428634ca7a5dbd7a22c8ae4e915", size = 19433201, upload-time = "2025-11-11T21:06:24.607Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/5ecfae12d78ea036a746c071e4c13b54b28d641efbba61d2947c73b3e6f9/wandb-0.23.0-py3-none-win_arm64.whl", hash = "sha256:fa0181b02ce4d1993588f4a728d8b73ae487eb3cb341e6ce01c156be7a98ec72", size = 17678649, upload-time = "2025-11-11T21:06:27.289Z" }, + { url = "https://files.pythonhosted.org/packages/12/0b/c3d7053dfd93fd259a63c7818d9c4ac2ba0642ff8dc8db98662ea0cf9cc0/wandb-0.23.1-py3-none-macosx_12_0_arm64.whl", hash = "sha256:358e15471d19b7d73fc464e37371c19d44d39e433252ac24df107aff993a286b", size = 21527293, upload-time = "2025-12-03T02:24:48.011Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9f/059420fa0cb6c511dc5c5a50184122b6aca7b178cb2aa210139e354020da/wandb-0.23.1-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:110304407f4b38f163bdd50ed5c5225365e4df3092f13089c30171a75257b575", size = 22745926, upload-time = "2025-12-03T02:24:50.519Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fd465827c14c64d056d30b4c9fcf4dac889a6969dba64489a88fc4ffa333/wandb-0.23.1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:6cc984cf85feb2f8ee0451d76bc9fb7f39da94956bb8183e30d26284cf203b65", size = 21212973, upload-time = "2025-12-03T02:24:52.828Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ee/9a8bb9a39cc1f09c3060456cc79565110226dc4099a719af5c63432da21d/wandb-0.23.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:67431cd3168d79fdb803e503bd669c577872ffd5dadfa86de733b3274b93088e", size = 22887885, upload-time = "2025-12-03T02:24:55.281Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4d/8d9e75add529142e037b05819cb3ab1005679272950128d69d218b7e5b2e/wandb-0.23.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:07be70c0baa97ea25fadc4a9d0097f7371eef6dcacc5ceb525c82491a31e9244", size = 21250967, upload-time = "2025-12-03T02:24:57.603Z" }, + { url = "https://files.pythonhosted.org/packages/97/72/0b35cddc4e4168f03c759b96d9f671ad18aec8bdfdd84adfea7ecb3f5701/wandb-0.23.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:216c95b08e0a2ec6a6008373b056d597573d565e30b43a7a93c35a171485ee26", size = 22988382, upload-time = "2025-12-03T02:25:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6d/e78093d49d68afb26f5261a70fc7877c34c114af5c2ee0ab3b1af85f5e76/wandb-0.23.1-py3-none-win32.whl", hash = "sha256:fb5cf0f85692f758a5c36ab65fea96a1284126de64e836610f92ddbb26df5ded", size = 22150756, upload-time = "2025-12-03T02:25:02.734Z" }, + { url = "https://files.pythonhosted.org/packages/05/27/4f13454b44c9eceaac3d6e4e4efa2230b6712d613ff9bf7df010eef4fd18/wandb-0.23.1-py3-none-win_amd64.whl", hash = "sha256:21c8c56e436eb707b7d54f705652e030d48e5cfcba24cf953823eb652e30e714", size = 22150760, upload-time = "2025-12-03T02:25:05.106Z" }, + { url = "https://files.pythonhosted.org/packages/30/20/6c091d451e2a07689bfbfaeb7592d488011420e721de170884fedd68c644/wandb-0.23.1-py3-none-win_arm64.whl", hash = "sha256:8aee7f3bb573f2c0acf860f497ca9c684f9b35f2ca51011ba65af3d4592b77c1", size = 20137463, upload-time = "2025-12-03T02:25:08.317Z" }, ] [[package]] @@ -4455,14 +4492,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.3" +version = "3.1.4" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "markupsafe", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/ea/b0f8eeb287f8df9066e56e831c7824ac6bab645dd6c7a8f4b2d767944f9b/werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e", size = 864687, upload-time = "2025-11-29T02:15:22.841Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f9/9e082990c2585c744734f85bec79b5dae5df9c974ffee58fe421652c8e91/werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", size = 224960, upload-time = "2025-11-29T02:15:21.13Z" }, ] [[package]] @@ -4568,9 +4605,9 @@ wheels = [ [[package]] name = "yt-dlp" -version = "2025.10.22" +version = "2025.12.8" source = { registry = "https://pypi.org/simple/" } -sdist = { url = "https://files.pythonhosted.org/packages/08/70/cf4bd6c837ab0a709040888caa70d166aa2dfbb5018d1d5c983bf0b50254/yt_dlp-2025.10.22.tar.gz", hash = "sha256:db2d48133222b1d9508c6de757859c24b5cefb9568cf68ccad85dac20b07f77b", size = 3046863, upload-time = "2025-10-22T19:53:19.301Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/77/db924ebbd99d0b2b571c184cb08ed232cf4906c6f9b76eed763cd2c84170/yt_dlp-2025.12.8.tar.gz", hash = "sha256:b773c81bb6b71cb2c111cfb859f453c7a71cf2ef44eff234ff155877184c3e4f", size = 3088947, upload-time = "2025-12-08T00:16:01.649Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/2a/fd184bf97d570841aa86b4aeb84aee93e7957a34059dafd4982157c10bff/yt_dlp-2025.10.22-py3-none-any.whl", hash = "sha256:9c803a9598859f91d0d5bd3337f1506ecb40bbe97f6efbe93bc4461fed344fb2", size = 3248983, upload-time = "2025-10-22T19:53:16.483Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2f/98c3596ad923f8efd32c90dca62e241e8ad9efcebf20831173c357042ba0/yt_dlp-2025.12.8-py3-none-any.whl", hash = "sha256:36e2584342e409cfbfa0b5e61448a1c5189e345cf4564294456ee509e7d3e065", size = 3291464, upload-time = "2025-12-08T00:15:58.556Z" }, ] From 463121bb4b1db0d178d7867be6d2e745b40611b9 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Fri, 12 Dec 2025 23:03:45 +0200 Subject: [PATCH 21/73] Adding debug for audio issues (#1310) --- buzz/widgets/audio_player.py | 17 ++++++++++++++++- buzz/widgets/video_player.py | 14 +++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/buzz/widgets/audio_player.py b/buzz/widgets/audio_player.py index 7e1ac94b..bb8d15ae 100644 --- a/buzz/widgets/audio_player.py +++ b/buzz/widgets/audio_player.py @@ -3,7 +3,7 @@ from typing import Tuple, Optional from PyQt6 import QtGui from PyQt6.QtCore import QTime, QUrl, Qt, pyqtSignal -from PyQt6.QtMultimedia import QAudioOutput, QMediaPlayer +from PyQt6.QtMultimedia import QAudioOutput, QMediaPlayer, QMediaDevices from PyQt6.QtWidgets import QWidget, QSlider, QPushButton, QLabel, QHBoxLayout, QVBoxLayout from buzz.widgets.icon import PlayIcon, PauseIcon @@ -32,6 +32,16 @@ class AudioPlayer(QWidget): self.audio_output = QAudioOutput() self.audio_output.setVolume(100) + # Log audio device info for debugging + default_device = QMediaDevices.defaultAudioOutput() + if default_device.isNull(): + logging.warning("No default audio output device found!") + else: + logging.info(f"Audio output device: {default_device.description()}") + + audio_outputs = QMediaDevices.audioOutputs() + logging.info(f"Available audio outputs: {[d.description() for d in audio_outputs]}") + self.media_player = QMediaPlayer() self.media_player.setSource(QUrl.fromLocalFile(file_path)) self.media_player.setAudioOutput(self.audio_output) @@ -95,6 +105,7 @@ class AudioPlayer(QWidget): self.media_player.positionChanged.connect(self.on_position_changed) self.media_player.playbackStateChanged.connect(self.on_playback_state_changed) self.media_player.mediaStatusChanged.connect(self.on_media_status_changed) + self.media_player.errorOccurred.connect(self.on_error_occurred) self.on_duration_changed(self.media_player.duration()) @@ -133,12 +144,16 @@ class AudioPlayer(QWidget): self.play_button.setIcon(self.play_icon) def on_media_status_changed(self, status: QMediaPlayer.MediaStatus): + logging.debug(f"Media status changed: {status}") match status: case QMediaPlayer.MediaStatus.InvalidMedia: self.set_invalid_media(True) case QMediaPlayer.MediaStatus.LoadedMedia: self.set_invalid_media(False) + def on_error_occurred(self, error: QMediaPlayer.Error, error_string: str): + logging.error(f"Media player error: {error} - {error_string}") + def set_invalid_media(self, invalid_media: bool): self.invalid_media = invalid_media if self.invalid_media: diff --git a/buzz/widgets/video_player.py b/buzz/widgets/video_player.py index daaa6912..3c6288d4 100644 --- a/buzz/widgets/video_player.py +++ b/buzz/widgets/video_player.py @@ -1,6 +1,7 @@ +import logging from typing import Tuple, Optional from PyQt6.QtCore import Qt, QUrl, pyqtSignal, QTime -from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput +from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput, QMediaDevices from PyQt6.QtMultimediaWidgets import QVideoWidget from PyQt6.QtWidgets import QWidget, QVBoxLayout, QSlider, QPushButton, QHBoxLayout, QLabel, QSizePolicy from buzz.widgets.icon import PlayIcon, PauseIcon @@ -21,6 +22,13 @@ class VideoPlayer(QWidget): self.audio_output = QAudioOutput(self) self.audio_output.setVolume(100) + # Log audio device info for debugging + default_device = QMediaDevices.defaultAudioOutput() + if default_device.isNull(): + logging.warning("No default audio output device found!") + else: + logging.info(f"Audio output device: {default_device.description()}") + self.media_player = QMediaPlayer(self) self.media_player.setSource(QUrl.fromLocalFile(file_path)) self.media_player.setAudioOutput(self.audio_output) @@ -72,6 +80,10 @@ class VideoPlayer(QWidget): self.media_player.durationChanged.connect(self.on_duration_changed) self.media_player.playbackStateChanged.connect(self.on_playback_state_changed) self.media_player.mediaStatusChanged.connect(self.on_media_status_changed) + self.media_player.errorOccurred.connect(self.on_error_occurred) + + def on_error_occurred(self, error: QMediaPlayer.Error, error_string: str): + logging.error(f"Media player error: {error} - {error_string}") def on_media_status_changed(self, status: QMediaPlayer.MediaStatus): # Only do this once on initial load to show first frame From dc0dc6b3d2abc66971433ae606b70e7b9df5026d Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sat, 13 Dec 2025 08:05:55 +0200 Subject: [PATCH 22/73] Adding speech extraction option to CLI (#1311) --- buzz/cli.py | 6 ++++++ docs/docs/cli.md | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/buzz/cli.py b/buzz/cli.py index b054e440..6fd56df0 100644 --- a/buzz/cli.py +++ b/buzz/cli.py @@ -102,6 +102,9 @@ def parse(app: Application, parser: QCommandLineParser): word_timestamp_option = QCommandLineOption( ["w", "word-timestamps"], "Generate word-level timestamps." ) + extract_speech_option = QCommandLineOption( + ["e", "extract-speech"], "Extract speech from audio before transcribing." + ) open_ai_access_token_option = QCommandLineOption( "openai-token", f"OpenAI access token. Use only when --model-type is {CommandLineModelType.OPEN_AI_WHISPER_API.value}. Defaults to your previously saved access token, if one exists.", @@ -124,6 +127,7 @@ def parse(app: Application, parser: QCommandLineParser): language_option, initial_prompt_option, word_timestamp_option, + extract_speech_option, open_ai_access_token_option, output_directory_option, srt_option, @@ -178,6 +182,7 @@ def parse(app: Application, parser: QCommandLineParser): initial_prompt = parser.value(initial_prompt_option) word_timestamps = parser.isSet(word_timestamp_option) + extract_speech = parser.isSet(extract_speech_option) output_formats: typing.Set[OutputFormat] = set() if parser.isSet(srt_option): @@ -205,6 +210,7 @@ def parse(app: Application, parser: QCommandLineParser): language=language, initial_prompt=initial_prompt, word_level_timings=word_timestamps, + extract_speech=extract_speech, openai_access_token=openai_access_token, ) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index 751ed097..a8df135a 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -60,7 +60,8 @@ Options: (Yiddish), yo (Yoruba), zh (Chinese). Leave empty to detect language. -p, --prompt Initial prompt. - -w, --word-timestamps Generate word-level timestamps. (available since 1.2.0) + -w, --word-timestamps Generate word-level timestamps. (available since 1.2.0) + -e, --extract-speech Extract speech from audio before transcribing. (available since 1.3.0) --openai-token OpenAI access token. Use only when --model-type is openaiapi. Defaults to your previously saved access token, if one exists. From b666a6a099bc18af9a08bb18d36296d685b7ffa0 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sat, 13 Dec 2025 12:44:18 +0200 Subject: [PATCH 23/73] Minor improvements (#1312) --- buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 145 +++--- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 145 +++--- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 145 +++--- buzz/locale/en_US/LC_MESSAGES/buzz.po | 127 +++-- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 145 +++--- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 145 +++--- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 145 +++--- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 142 +++--- buzz/locale/nl/LC_MESSAGES/buzz.po | 143 +++--- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 122 +++-- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 145 +++--- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 143 +++--- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 137 +++--- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 122 +++-- buzz/model_loader.py | 39 +- buzz/settings/settings.py | 1 + buzz/store/keyring_store.py | 221 +++++++++ .../openai_whisper_api_file_transcriber.py | 5 +- buzz/transcriber/recording_transcriber.py | 8 +- buzz/widgets/main_window.py | 14 - .../general_preferences_widget.py | 15 + buzz/widgets/snap_notice.py | 29 -- snap/snapcraft.yaml | 2 + tests/store/__init__.py | 0 tests/store/keyring_store_test.py | 457 ++++++++++++++++++ tests/widgets/main_window_test.py | 10 - 26 files changed, 1692 insertions(+), 1060 deletions(-) delete mode 100644 buzz/widgets/snap_notice.py create mode 100644 tests/store/__init__.py create mode 100644 tests/store/keyring_store_test.py diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index 7f797f14..bf7db49b 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -29,7 +29,7 @@ msgstr "https://exemple.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "D’acord" @@ -37,7 +37,7 @@ msgstr "D’acord" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Cancel·lar" @@ -148,48 +148,53 @@ msgstr "Clau de l'API d'OpenAI" msgid "OpenAI base url" msgstr "URL base d'OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "Clau de l'API d'OpenAI" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Nom del fitxer d'exportació per defecte" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Activa l'exportació de transcripcions en directe" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Navega" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Exporta la carpeta" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "Mode d'enregistrament en directe" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "Utilitza només la CPU i desactiveu l'acceleració de la GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Establiu això si els models més grans no s'ajusten a la memòria de la GPU i " "Buzz es bloqueja" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "Desactiva la GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "Prova de clau OpenAI API" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -197,11 +202,11 @@ msgstr "" "La vostra clau API és vàlida. Buzz utilitzarà aquesta clau per realitzar " "transcripcions de l'API de Whisper i traduccions de la IA." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "Clau API no vàlida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -209,11 +214,11 @@ msgstr "" "L'API només admet caràcters base64 (A-Za-z0-9+/).-). Altres caràcters de la " "clau API poden causar errors." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Selecciona la carpeta d'exportació" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -308,11 +313,11 @@ msgid "Download failed" msgstr "Descàrrega fallida" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Error" @@ -439,7 +444,7 @@ msgstr "Obre una transcripció" msgid "Cancel Transcription" msgstr "Cancel·la la transcripció" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Neteja l'historial" @@ -489,7 +494,7 @@ msgid "Date Added" msgstr "Data d'addició" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -497,53 +502,53 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Cancel·la la transcripció" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 #, fuzzy msgid "Rename" msgstr "Vietnamita" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Cancel·la la transcripció" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Cancel·la la transcripció" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -694,36 +699,36 @@ msgstr "Desplaça't fins a l'actual" msgid "Scroll to the currently spoken text" msgstr "Desplaçar-se fins al text que es parla actualment" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "1 de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr " coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "No s'ha trobat cap coincidència" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr " de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "Clau API necessària" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Introduïu la clau API d'OpenAI a les preferències" @@ -834,29 +839,6 @@ msgstr "Desa el fitxer" msgid "Text files" msgstr "Fitxers de text" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Avís de permís d'ajust" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"S'han detectat permisos que manquen, comproveu que s'han concedit permisos " -"de captura" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"Per habilitar els permisos necessaris, executeu les ordres següents al " -"terminal" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Tanca" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Descarregant el model" @@ -889,7 +871,7 @@ msgstr "Ajuda" msgid "File" msgstr "Fitxer" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -897,11 +879,11 @@ msgstr "" "Esteu segur que voleu suprimir les transcripcions seleccionades? Aquesta " "acció no es pot desfer." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Selecciona un fitxer d'àudio" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "No s'ha pogut desar la clau OpenAI API a l'anell de claus" @@ -1287,7 +1269,7 @@ msgstr "Sundanès" msgid "Cantonese" msgstr "Cantonès" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "S'ha produït un error de connexió" @@ -1380,6 +1362,25 @@ msgstr "Afegeix a sobre" msgid "Append and correct" msgstr "Afegeix i corregeix" +#~ msgid "Snap permission notice" +#~ msgstr "Avís de permís d'ajust" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "S'han detectat permisos que manquen, comproveu que s'han concedit " +#~ "permisos de captura" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "Per habilitar els permisos necessaris, executeu les ordres següents al " +#~ "terminal" + +#~ msgid "Close" +#~ msgstr "Tanca" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Introduïu les instruccions per a la IA sobre com traduir..." diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index f1638540..8deb97db 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -26,7 +26,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "OK" @@ -34,7 +34,7 @@ msgstr "OK" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Afbryd" @@ -147,47 +147,52 @@ msgstr "OpenAI API-nøgle" msgid "OpenAI base url" msgstr "OpenAI base-URL" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "OpenAI API-nøgle" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Standard eksport filnavn" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Slå transkription af live optagelse eksport til" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Gennemse" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Eksportmappe" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 #, fuzzy msgid "Live recording mode" msgstr "Live optagelsesmode" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "OpenAI API Nøgle test" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -195,12 +200,12 @@ msgstr "" "Din API nøgle er gyldig. Buzz vil benytte nøglen til at anvende Whisper API " "transkription og AI oversættelser." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 #, fuzzy msgid "Invalid API key" msgstr "Ugyldig API-nøgle" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -208,11 +213,11 @@ msgstr "" "API supporterer kun base64 tegn (A-Za-z0-9+/=_-). Andre tegn i API-nøglen " "kan guve fejl. " -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Vælg eksport-mappe" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -307,11 +312,11 @@ msgid "Download failed" msgstr "Download mislykkedes" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Fejl" @@ -437,7 +442,7 @@ msgstr "Åben transkription" msgid "Cancel Transcription" msgstr "Afbryd transkription" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Ryd historik" @@ -487,7 +492,7 @@ msgid "Date Added" msgstr "Dato for tilføjelse" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -495,52 +500,52 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Afbryd transkription" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Afbryd transkription" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Afbryd transkription" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -688,36 +693,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "API-nøgle påkrævet" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Indtast venligst OpenAI API-nøgle i indstillinger" @@ -828,29 +833,6 @@ msgstr "Gem fil" msgid "Text files" msgstr "Tekst filer" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Snap tilladelse notifikationer" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Detekterede manglene tilladelser, tjek om venligst Snap tilladelserne er " -"givne" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"For at give de nødvendige tilladelser kør den følgende kommando i et " -"terminalvindue" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Luk" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Downloader model" @@ -883,7 +865,7 @@ msgstr "Hjælp" msgid "File" msgstr "Fil" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -891,11 +873,11 @@ msgstr "" "Er du sikker på at du vil slette den valgte transkription? Denne handling " "kan ikke fortrydes." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Vælg audio-fil" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "Kan ikke gemme OpenAI API-nøgle i nøgleringen" @@ -1277,7 +1259,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Der er opstået en forbindelsesfejl" @@ -1371,5 +1353,24 @@ msgstr "Tilføj herover" msgid "Append and correct" msgstr "Tilføj og ret" +#~ msgid "Snap permission notice" +#~ msgstr "Snap tilladelse notifikationer" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Detekterede manglene tilladelser, tjek om venligst Snap tilladelserne er " +#~ "givne" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "For at give de nødvendige tilladelser kør den følgende kommando i et " +#~ "terminalvindue" + +#~ msgid "Close" +#~ msgstr "Luk" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Indtast instruktioner til AI om hvordan den skal oversætte..." diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index 574689a8..0352e5f9 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -28,7 +28,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "OK" @@ -36,7 +36,7 @@ msgstr "OK" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Abbrechen" @@ -148,46 +148,51 @@ msgstr "OpenAI-API-Schlüssel" msgid "OpenAI base url" msgstr "OpenAI-Basis-URL" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "OpenAI-API-Schlüssel" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Standardname der Exportdatei" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Export von Live-Aufnahmetranskriptionen aktivieren" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Durchsuchen" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Exportordner" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "Live-Aufnahmemodus" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "OpenAI-API-Schlüssel Test" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -195,11 +200,11 @@ msgstr "" "Ihr API-Schlüssel ist gültig. Buzz verwendet diesen Schlüssel, um Whisper-" "API-Transkriptionen und KI-Übersetzungen durchzuführen." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "Ungültiger API-Schlüssel" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -207,11 +212,11 @@ msgstr "" "Die API unterstützt nur Base64-Zeichen (A-Za-z0-9+/=_-). Andere Zeichen im " "API-Schlüssel können Fehler verursachen." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Exportordner auswählen" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -307,11 +312,11 @@ msgid "Download failed" msgstr "Der Download ist fehlgeschlagen" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Fehler" @@ -437,7 +442,7 @@ msgstr "Transkript öffnen" msgid "Cancel Transcription" msgstr "Transkription abbrechen" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Verlauf löschen" @@ -487,7 +492,7 @@ msgid "Date Added" msgstr "Datum hinzugefügt" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -495,53 +500,53 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Transkription abbrechen" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 #, fuzzy msgid "Rename" msgstr "Vietnamesisch" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Transkription abbrechen" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Transkription abbrechen" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -689,36 +694,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "API-Schlüssel erforderlich" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Bitte geben Sie den OpenAI-API-Schlüssel in den Einstellungen ein" @@ -829,29 +834,6 @@ msgstr "Datei speichern" msgid "Text files" msgstr "Textdateien" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Snap-Berechtigungsmitteilung" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Es wurden fehlende Berechtigungen festgestellt. Bitte überprüfen Sie, ob " -"Snap-Berechtigungen erteilt wurden" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"Um die erforderlichen Berechtigungen zu aktivieren, führen Sie die folgenden " -"Befehle im Terminal aus" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Schließen" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Modell wird heruntergeladen" @@ -884,7 +866,7 @@ msgstr "Hilfe" msgid "File" msgstr "Datei" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -892,11 +874,11 @@ msgstr "" "Sind Sie sicher, dass Sie die ausgewählte(n) Transkription(en) löschen " "möchten? Diese Aktion kann nicht rückgängig gemacht werden." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Audiodatei auswählen" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "" "Der OpenAI-API-Schlüssel kann nicht im Schlüsselbund gespeichert werden" @@ -1279,7 +1261,7 @@ msgstr "Sundanesisch" msgid "Cantonese" msgstr "Kantonesisch" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Ein Verbindungsfehler ist aufgetreten" @@ -1373,5 +1355,24 @@ msgstr "Oben anhängen" msgid "Append and correct" msgstr "Anhängen und korrigieren" +#~ msgid "Snap permission notice" +#~ msgstr "Snap-Berechtigungsmitteilung" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Es wurden fehlende Berechtigungen festgestellt. Bitte überprüfen Sie, ob " +#~ "Snap-Berechtigungen erteilt wurden" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "Um die erforderlichen Berechtigungen zu aktivieren, führen Sie die " +#~ "folgenden Befehle im Terminal aus" + +#~ msgid "Close" +#~ msgstr "Schließen" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Geben Sie Anweisungen für die KI zum Übersetzen ein..." diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index 4bc17730..64d9ac70 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,7 +29,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "" @@ -37,7 +37,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "" @@ -148,66 +148,70 @@ msgstr "" msgid "OpenAI base url" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 -msgid "Default export file name" -msgstr "" - -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 -msgid "Enable live recording transcription export" +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +msgid "OpenAI API model" msgstr "" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +msgid "Default export file name" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 +msgid "Enable live recording transcription export" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -299,11 +303,11 @@ msgid "Download failed" msgstr "" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "" @@ -427,7 +431,7 @@ msgstr "" msgid "Cancel Transcription" msgstr "" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "" @@ -477,7 +481,7 @@ msgid "Date Added" msgstr "" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -485,49 +489,49 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 msgid "Restart Transcription" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 msgid "Rename Transcription" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 msgid "Failed to restart transcription: {}" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -673,36 +677,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -811,25 +815,6 @@ msgstr "" msgid "Text files" msgstr "" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "" @@ -862,17 +847,17 @@ msgstr "" msgid "File" msgstr "" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "" -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "" @@ -1253,7 +1238,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index a8c0b3e4..2f039908 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -29,7 +29,7 @@ msgstr "https://ejemplo.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Ok" @@ -37,7 +37,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Cancelar" @@ -149,49 +149,54 @@ msgstr "Clave API de OpenAI" msgid "OpenAI base url" msgstr "URL base de OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "Clave API de OpenAI" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Nombre de archivo de exportación predeterminado" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Habilitar la exportación de transcripción de grabación en vivo" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Navegar" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Carpeta de exportación" # automatic translation -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "Modo de grabación en directo" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "Usa solo CPU y desactiva la aceleración de GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Configure esto si los modelos más grandes no se ajustan a la memoria de su " "GPU y Buzz se bloquea" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "Desactivar GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "Prueba de la clave API de OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -199,11 +204,11 @@ msgstr "" "Tu clave API es válida. Buzz usará esta clave para realizar transcripciones " "de la API de Whisper y traducciones de IA." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "Clave API no válida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -211,11 +216,11 @@ msgstr "" "La API solo admite caracteres base64 (A-Za-z0-9+/=_-). Otros caracteres de " "la clave de API pueden causar errores." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Seleccione Exportar carpeta" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -314,11 +319,11 @@ msgid "Download failed" msgstr "Descarga fallida" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Error" @@ -463,7 +468,7 @@ msgid "Cancel Transcription" msgstr "Cancelar transcripción" # automatic translation -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Vaciar historial" @@ -516,7 +521,7 @@ msgid "Date Added" msgstr "Fecha de adición" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -525,55 +530,55 @@ msgid "Reset Column Order" msgstr "" # automatic translation -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Cancelar transcripción" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 #, fuzzy msgid "Rename" msgstr "Vietnamita" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" # automatic translation -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Cancelar transcripción" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" # automatic translation -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Cancelar transcripción" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -735,36 +740,36 @@ msgstr "Desplácese hasta Actual" msgid "Scroll to the currently spoken text" msgstr "Desplazarse hasta el texto hablado actualmente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "1 de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr " coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "No se encontraron coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr " de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "Clave de API requerida" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Ingrese la clave API de OpenAI en las preferencias" @@ -878,29 +883,6 @@ msgstr "Guardar archivo" msgid "Text files" msgstr "Archivos de texto" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Aviso de permiso Snap" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Se ha detectado que faltan permisos, compruebe que se han concedido los " -"permisos snap" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"Para habilitar los permisos necesarios ejecute los siguientes comandos en el " -"terminal" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Cerrar" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Descargando modelo" @@ -939,7 +921,7 @@ msgid "File" msgstr "Archivo" # automatic translation -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -948,11 +930,11 @@ msgstr "" "no se puede deshacer." # automatic translation -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Seleccionar archivo de audio" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "No se puede guardar la clave de la API de OpenAI en el llavero" @@ -1339,7 +1321,7 @@ msgstr "Sundanés" msgid "Cantonese" msgstr "Cantonés" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Se ha producido un error de conexión" @@ -1439,6 +1421,25 @@ msgstr "Añadir arriba" msgid "Append and correct" msgstr "Añadir y corregir" +#~ msgid "Snap permission notice" +#~ msgstr "Aviso de permiso Snap" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Se ha detectado que faltan permisos, compruebe que se han concedido los " +#~ "permisos snap" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "Para habilitar los permisos necesarios ejecute los siguientes comandos en " +#~ "el terminal" + +#~ msgid "Close" +#~ msgstr "Cerrar" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Introduzca instrucciones para la IA sobre cómo traducir..." diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index d8c13272..7fd50c46 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -28,7 +28,7 @@ msgstr "https://esempio.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Ok" @@ -36,7 +36,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Annulla" @@ -148,48 +148,53 @@ msgstr "Chiave API OpenAI" msgid "OpenAI base url" msgstr "URL di base di OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "Chiave API OpenAI" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Nome file di esportazione predefinito" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Abilita l'esportazione della trascrizione della registrazione live" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Sfoglia" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Esporta cartella" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "Modalità di registrazione in diretta" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "Utilizza solo la CPU e disattiva l'accelerazione GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Imposta questa opzione se i modelli più grandi non si adattano alla memoria " "della tua GPU e Buzz si blocca" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "Disabilita GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "Test della chiave API OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -197,11 +202,11 @@ msgstr "" "La tua chiave API è valida. Buzz utilizzerà questa chiave per eseguire le " "trascrizioni API Whisper e le traduzioni AI." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "Chiave API non valida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -209,11 +214,11 @@ msgstr "" "L'API supporta solo caratteri base64 (A-Za-z0-9+/=). Altri caratteri nella " "chiave API potrebbero causare errori." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Seleziona la cartella di esportazione" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -308,11 +313,11 @@ msgid "Download failed" msgstr "Download non riuscito" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Errore" @@ -440,7 +445,7 @@ msgstr "Apri trascrizione" msgid "Cancel Transcription" msgstr "Annulla trascrizione" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Elimina la cronologia" @@ -490,7 +495,7 @@ msgid "Date Added" msgstr "Data aggiunta" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -498,53 +503,53 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Inizio trascrizione..." -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 #, fuzzy msgid "Rename" msgstr "Vietnamita" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Annulla trascrizione" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Inizio trascrizione..." -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -697,36 +702,36 @@ msgstr "Scorri fino al Corrente" msgid "Scroll to the currently spoken text" msgstr "Scorrere fino al testo attualmente pronunciato" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "1 di 100+ corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "1 di" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "Nessuna corrispondenza trovata" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr " di oltre 100 corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr " di " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "Chiave API richiesta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Inserisci la chiave API OpenAI nelle preferenze" @@ -837,29 +842,6 @@ msgstr "Salva file" msgid "Text files" msgstr "File di testo" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Avviso di autorizzazione Snap" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Rilevate autorizzazioni mancanti, verificare che le autorizzazioni snap " -"siano state concesse" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"Per abilitare le autorizzazioni necessarie, eseguire i seguenti comandi nel " -"terminale" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Chiudi" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Download del modello" @@ -892,7 +874,7 @@ msgstr "Aiuto" msgid "File" msgstr "File" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -900,11 +882,11 @@ msgstr "" "Sei certo di voler eliminare le trascrizioni selezionate? Questa azione non " "può essere annullata." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Seleziona file audio" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "Impossibile salvare la chiave API OpenAI nel portachiavi" @@ -1289,7 +1271,7 @@ msgstr "Sundanese" msgid "Cantonese" msgstr "Cantonese" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Si è verificato un errore di connessione" @@ -1381,6 +1363,25 @@ msgstr "Aggiungere sopra" msgid "Append and correct" msgstr "Aggiungere e correggere" +#~ msgid "Snap permission notice" +#~ msgstr "Avviso di autorizzazione Snap" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Rilevate autorizzazioni mancanti, verificare che le autorizzazioni snap " +#~ "siano state concesse" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "Per abilitare le autorizzazioni necessarie, eseguire i seguenti comandi " +#~ "nel terminale" + +#~ msgid "Close" +#~ msgstr "Chiudi" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Inserisci le istruzioni per l'IA su come tradurre..." diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index 82c7ef2c..f2bb2f90 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -24,7 +24,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Ok" @@ -32,7 +32,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "キャンセル" @@ -145,47 +145,52 @@ msgstr "OpenAI APIキー" msgid "OpenAI base url" msgstr "OpenAI ベースURL" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "OpenAI APIキー" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "デフォルトの出力ファイル名" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "ライブ録音書き起こしの出力を有効にする" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "参照" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "出力フォルダ" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 #, fuzzy msgid "Live recording mode" msgstr "ライブ録音" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "OpenAI APIキー テスト" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -193,22 +198,22 @@ msgstr "" "あなたのAPIキーは有効です。Buzzはこのキーを使ってWhisper APIの書き起こしとAI" "翻訳を行います。" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 #, fuzzy msgid "Invalid API key" msgstr "OpenAI APIキー" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "出力フォルダを選択" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -303,11 +308,11 @@ msgid "Download failed" msgstr "ダウンロード失敗" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "エラー" @@ -433,7 +438,7 @@ msgstr "文字起こしを開く" msgid "Cancel Transcription" msgstr "文字起こしをキャンセルする" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "履歴を削除する" @@ -483,7 +488,7 @@ msgid "Date Added" msgstr "追加日" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -491,52 +496,52 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "文字起こしをキャンセルする" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "文字起こしをキャンセルする" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "文字起こしをキャンセルする" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -684,36 +689,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "APIキーが必要" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "設定画面でOpenAI APIキーを入力してください" @@ -825,29 +830,6 @@ msgstr "ファイルを保存" msgid "Text files" msgstr "テキストファイル" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Snap権限通知" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"不足している権限が検出されました。Snapパッケージに権限が付与されていることを" -"確認してください" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"必要なパーミッションを有効にするには、ターミナルで以下のコマンドを実行してく" -"ださい" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "閉じる" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "モデルをダウンロード中" @@ -880,17 +862,17 @@ msgstr "ヘルプ" msgid "File" msgstr "ファイル" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "本当に選択された文字起こしを削除しますか? この操作は元に戻せません。" -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "音声ファイルを選択" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "OpenAI API キーをkeyringに保存できません" @@ -1272,7 +1254,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "接続エラーが発生しました" @@ -1366,6 +1348,25 @@ msgstr "" msgid "Append and correct" msgstr "" +#~ msgid "Snap permission notice" +#~ msgstr "Snap権限通知" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "不足している権限が検出されました。Snapパッケージに権限が付与されていること" +#~ "を確認してください" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "必要なパーミッションを有効にするには、ターミナルで以下のコマンドを実行して" +#~ "ください" + +#~ msgid "Close" +#~ msgstr "閉じる" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "AIのための翻訳方法の指示を入力..." diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index 1858a164..339e8152 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" -"PO-Revision-Date: 2025-12-11 20:23+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"PO-Revision-Date: 2025-12-13 10:52+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -29,7 +29,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Labi" @@ -37,7 +37,7 @@ msgstr "Labi" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Atcelt" @@ -148,50 +148,54 @@ msgstr "OpenAI API atslēga" msgid "OpenAI base url" msgstr "OpenAI adrese" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +msgid "OpenAI API model" +msgstr "OpenAI modelis" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Eksporta fails" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Eksportēt dzīvā ieraksta transkriptus" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Izvēlēties" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Eksportēt mapē" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "" "Dzīvā ieraksta\n" "režīms" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "Izmantot tikai CPU un deaktivēt GPU paātrināšanu" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Aktivizējiet šo, ja lielāki modeļi neietilpst jūsu video kartes atmiņā un " "Buzz avarē" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "Deaktivēt GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "OpenAI API atslēgas pārbaude" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -199,11 +203,11 @@ msgstr "" "Jūsu API atslēga ir derīga. Buzz izmantos to runas atpazīšanai ar Whisper " "API un tulkošanai." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "Nederīga API atslēga" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -211,11 +215,11 @@ msgstr "" "API atbalsta tikai base64 simbolus (A-Za-z0-9+/=_-). Citi simboli API " "atslēgā var radīt kļūdas." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Izvēlieties mapi kurā eksportēt" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -310,11 +314,11 @@ msgid "Download failed" msgstr "Lejupielāde neizdevās" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Kļūda" @@ -442,7 +446,7 @@ msgstr "Atvērt transkriptu" msgid "Cancel Transcription" msgstr "Atcelt atpazīšanu" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Notīrīt vēsturi" @@ -492,7 +496,7 @@ msgid "Date Added" msgstr "Pievienots" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "Piezīmes" @@ -500,50 +504,50 @@ msgstr "Piezīmes" msgid "Reset Column Order" msgstr "Atjaunot kolonas" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 msgid "Restart Transcription" msgstr "Sāk atpazīšanu" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "Pārddēvēt" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "Rediģēt piezīmes" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 msgid "Rename Transcription" msgstr "Pārdēvēt" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "Ievadiet jauno nosaukumu šim atpazīšanas ierakstam:" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "Ievadiet noderīgas piezīmēs par šo ierakstu:" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "Neizdodas sākt" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "Atkārtoti sākt var tikai kļūdainus vai atceltus ierakstus." -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 msgid "Failed to restart transcription: {}" msgstr "Neizdevās sākt atpazīšanu: {}" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" "Neizdevās sākt atpazīšanu: modelis nav pieejams un to nevar lejupielādēt." -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "Neizdevās sākt atpazīšanu: Kļūda lietotnē, pārstartējiet." @@ -693,36 +697,36 @@ msgstr "Pāriet uz tekošo" msgid "Scroll to the currently spoken text" msgstr "Pāriet uz šobrīd atskaņojamo tesktu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "1 no 100+ " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "1 no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr " " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "Nekas nav atrasts" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr " no 100+" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr " no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "API atslēgas kļūda" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Lūdzu ievadiet OpenAI API atslēgu iestatījumos" @@ -831,27 +835,6 @@ msgstr "Saglabāt failu" msgid "Text files" msgstr "Teksta faili" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Snap atļauju piezīme" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Ne visi nepieciešamie moduļi darbojas korekti, iespējams nav piešķirtas snap " -"atļaujas" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "Lai piešķirtu nepieciešamās atļaujas izpildiet šīs komandas" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Aizvērt" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Lejupielādē modeli" @@ -884,7 +867,7 @@ msgstr "Palīdzība" msgid "File" msgstr "Fails" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -892,11 +875,11 @@ msgstr "" "Vai tiešām vēlaties dzēst izvēlētos transkriptus? Šī ir neatgriezeniska " "darbība." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Izvēlieties audio failu" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī" @@ -1282,7 +1265,7 @@ msgstr "Sundāņu" msgid "Cantonese" msgstr "Kantonas" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Notika savienojuma kļūda" @@ -1374,6 +1357,23 @@ msgstr "Jaunie teikumi augšā" msgid "Append and correct" msgstr "Papildināt un labot esošo" +#~ msgid "Snap permission notice" +#~ msgstr "Snap atļauju piezīme" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Ne visi nepieciešamie moduļi darbojas korekti, iespējams nav piešķirtas " +#~ "snap atļaujas" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "Lai piešķirtu nepieciešamās atļaujas izpildiet šīs komandas" + +#~ msgid "Close" +#~ msgstr "Aizvērt" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Ievadiet tulkošanas norādes mākslīgajam intelektam..." diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index d895e113..a7b4d2f0 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -31,7 +31,7 @@ msgstr "https://voorbeeld.nl/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Oké" @@ -39,7 +39,7 @@ msgstr "Oké" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Annuleren" @@ -151,46 +151,51 @@ msgstr "OpenAI-api-sleutel" msgid "OpenAI base url" msgstr "OpenAI-hoofd-url" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "OpenAI-api-sleutel" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Standaardnaam van export" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Transcripties van opnames onmiddelijk exporteren" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Bladeren" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Exportmap" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "Live-opnamemodus" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "OpenAI-api-sleuteltest" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -198,11 +203,11 @@ msgstr "" "De api-sleutel is geldig. Buzz zal deze sleutel gebruiken om transcripties " "en AI-vertalingen op te vragen bij Whisper." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "Ongeldige api-sleutel" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -210,11 +215,11 @@ msgstr "" "De api ondersteunt alleen base64-tekens (A–Za–z0–9+/=_-). Andere tekens " "kunnen problemen veroorzaken." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Kies een exportmap" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -309,11 +314,11 @@ msgid "Download failed" msgstr "Het downloaden is mislukt" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Foutmelding" @@ -439,7 +444,7 @@ msgstr "Transcriptie openen" msgid "Cancel Transcription" msgstr "Transcriptie wissen" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Geschiedenis wissen" @@ -489,7 +494,7 @@ msgid "Date Added" msgstr "Toegevoegd op" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -497,53 +502,53 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Transcriptie wissen" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 #, fuzzy msgid "Rename" msgstr "Vietnamees" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Transcriptie wissen" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Transcriptie wissen" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -689,36 +694,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "Api-sleutel vereist" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Voer de OpenAI-api-sleutel in in de instellingen" @@ -829,28 +834,6 @@ msgstr "Bestand opslaan" msgid "Text files" msgstr "Tekstbestanden" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Snap-rechten" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Er ontbreken toegangsrechten - controleer of ze daadwerkelijk allemaal zijn " -"toegekend" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"De rechten kunnen met behulp van deze terminalopdrachten worden verleend" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Sluiten" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Bezig met ophalen van model…" @@ -883,7 +866,7 @@ msgstr "Hulp" msgid "File" msgstr "Bestand" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -891,11 +874,11 @@ msgstr "" "Weet u zeker dat u de gekozen transcriptie(s) wilt verwijderen? Deze actie " "is onomkeerbaar." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Kies een audiobestand" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "De OpenAI-api-sleutel kan niet worden bewaard in de sleutelbos" @@ -1277,7 +1260,7 @@ msgstr "Soedanees" msgid "Cantonese" msgstr "Kantonees" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Er is een verbindingsfout opgetreden" @@ -1371,6 +1354,24 @@ msgstr "Bovenaan toevoegen" msgid "Append and correct" msgstr "Toevoegen en corrigeren" +#~ msgid "Snap permission notice" +#~ msgstr "Snap-rechten" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Er ontbreken toegangsrechten - controleer of ze daadwerkelijk allemaal " +#~ "zijn toegekend" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "De rechten kunnen met behulp van deze terminalopdrachten worden verleend" + +#~ msgid "Close" +#~ msgstr "Sluiten" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Voer vertaalinstructies in…" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index 6df447cf..a1e36157 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -29,7 +29,7 @@ msgstr "https://przyklad.pl/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "" @@ -37,7 +37,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Anuluj" @@ -149,68 +149,73 @@ msgstr "" msgid "OpenAI base url" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "Model:" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 #, fuzzy msgid "Live recording mode" msgstr "Nagrywanie na żywo" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 #, fuzzy msgid "Invalid API key" msgstr "Nieprawidłowy URL" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -310,11 +315,11 @@ msgid "Download failed" msgstr "Pobrany" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Błąd" @@ -441,7 +446,7 @@ msgstr "Otwórz transkrypt" msgid "Cancel Transcription" msgstr "Anuluj transkrypcję" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Wyczyść historię" @@ -495,7 +500,7 @@ msgid "Date Added" msgstr "" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -503,52 +508,52 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Anuluj transkrypcję" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Anuluj transkrypcję" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Anuluj transkrypcję" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -697,36 +702,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -838,25 +843,6 @@ msgstr "Zapisz plik" msgid "Text files" msgstr "Pliki tekstowe" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "" - #: buzz/widgets/model_download_progress_dialog.py:37 #, fuzzy msgid "Downloading model" @@ -892,7 +878,7 @@ msgstr "Pomoc" msgid "File" msgstr "Plik" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -900,11 +886,11 @@ msgstr "" "Czy na pewno chcesz usunąć zaznaczone transkrypcje? Tej operacji nie można " "cofnąć." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Wybierz plik audio" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "" @@ -1287,7 +1273,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 22fbb3e3..129dca1d 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -29,7 +29,7 @@ msgstr "https://exemplo.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Ok" @@ -37,7 +37,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Cancelar" @@ -148,47 +148,52 @@ msgstr "Chave API da OpenAI" msgid "OpenAI base url" msgstr "URL base da OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "Chave API da OpenAI" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Nome padrão do arquivo de exportação" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Habilitar exportação da transcrição ao vivo" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Procurar" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Pasta de exportação" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 msgid "Live recording mode" msgstr "Modo de gravação ao vivo" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "Usar somente a CPU e desabilitar aceleração por GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Marque isso se modelos maiores não couberem na memória da GPU e o Buzz travar" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "Desabilitar GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "Teste da Chave API OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -196,11 +201,11 @@ msgstr "" "Sua chave API é válida. O Buzz usará esta chave para realizar transcrições " "API Whisper e traduções de IA." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "Chave API inválida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -208,11 +213,11 @@ msgstr "" "A API suporta apenas caracteres base64 (A-Za-z0-9+/=_-). Outros caracteres " "na chave API podem causar erros." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Selecionar Pasta de Exportação" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -307,11 +312,11 @@ msgid "Download failed" msgstr "Falha ao baixar" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Erro" @@ -437,7 +442,7 @@ msgstr "Abrir Transcrição" msgid "Cancel Transcription" msgstr "Cancelar Transcrição" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Limpar Histórico" @@ -487,7 +492,7 @@ msgid "Date Added" msgstr "Data de Adição" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -495,53 +500,53 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Iniciando transcrição..." -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 #, fuzzy msgid "Rename" msgstr "Vietnamita" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Cancelar Transcrição" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Iniciando transcrição..." -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -692,36 +697,36 @@ msgstr "Rolar para o Atual" msgid "Scroll to the currently spoken text" msgstr "Role até o texto falado no momento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "1 de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr " encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "Nada encontrado" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr " de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "Chave API Necessária" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Insira a chave API OpenAI nas preferências" @@ -832,29 +837,6 @@ msgstr "Salvar Arquivo" msgid "Text files" msgstr "Arquivos de texto" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Aviso de permissão do Snap" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Permissões ausentes detectadas, verifique se as permissões do Snap foram " -"concedidas" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"Para habilitar as permissões necessárias, execute os seguintes comandos no " -"terminal" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Fechar" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Baixando modelo" @@ -887,7 +869,7 @@ msgstr "Ajuda" msgid "File" msgstr "Arquivo" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." @@ -895,11 +877,11 @@ msgstr "" "Tem certeza que deseja excluir a(s) transcrição(ões) selecionada(s)? Esta " "ação não pode ser desfeita." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Selecionar arquivo de áudio" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "Não foi possível salvar a chave da API OpenAI no cofre de chaves" @@ -1283,7 +1265,7 @@ msgstr "Sundanês" msgid "Cantonese" msgstr "Cantonês" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Ocorreu um erro de conexão" @@ -1376,6 +1358,25 @@ msgstr "Acrescentar acima" msgid "Append and correct" msgstr "Acrescentar e corrigir" +#~ msgid "Snap permission notice" +#~ msgstr "Aviso de permissão do Snap" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Permissões ausentes detectadas, verifique se as permissões do Snap foram " +#~ "concedidas" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "Para habilitar as permissões necessárias, execute os seguintes comandos " +#~ "no terminal" + +#~ msgid "Close" +#~ msgstr "Fechar" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Instrua a IA sobre como traduzir..." diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index ba783992..3472eed8 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -26,7 +26,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Гаразд" @@ -34,7 +34,7 @@ msgstr "Гаразд" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "Скасувати" @@ -147,47 +147,52 @@ msgstr "API-ключ OpenAI" msgid "OpenAI base url" msgstr "Базова адреса OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "API-ключ OpenAI" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "Типова назва файлу експорту" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "Увімкнути експорт транскрипції з живого запису" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "Огляд" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "Тека для експорту" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 #, fuzzy msgid "Live recording mode" msgstr "Живий запис" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "Тест API-ключа OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -195,22 +200,22 @@ msgstr "" "Ваш API-ключ дійсний. Buzz використає цей ключ для транскрипції з Whisper " "API та перекладу ШІ." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 #, fuzzy msgid "Invalid API key" msgstr "API-ключ OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "Виберіть теку для експорту" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -305,11 +310,11 @@ msgid "Download failed" msgstr "Невдале завантаження" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "Помилка" @@ -435,7 +440,7 @@ msgstr "Відкрити транскрипцію" msgid "Cancel Transcription" msgstr "Скасувати транскрипцію" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "Очистити історію" @@ -485,7 +490,7 @@ msgid "Date Added" msgstr "Дата додавання" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -493,52 +498,52 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "Скасувати транскрипцію" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "Скасувати транскрипцію" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "Скасувати транскрипцію" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -686,36 +691,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "Потрібен API-ключ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "Будь ласка, введіть API-ключ OpenAI в налаштуваннях" @@ -826,28 +831,6 @@ msgstr "Зберегти файл" msgid "Text files" msgstr "Текстові файли" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "Попередження щодо дозволів Snap" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" -"Виявлено нестачу повноважень. Будь ласка, перевірте, чи були надані дозволи " -"для Snap" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" -"Для активації необхідних дозволів, запустіть наступну команду в терміналі" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "Закрити" - #: buzz/widgets/model_download_progress_dialog.py:37 msgid "Downloading model" msgstr "Завантаження моделі" @@ -880,18 +863,18 @@ msgstr "Допомога" msgid "File" msgstr "Файл" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "" "Ви впевнені, що хочете видалити вибрані транскрипції? Це незворотна дія." -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "Вибрати аудіофайл" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "Не вдається додати до звʼязки ключів API-ключ OpenAI" @@ -1273,7 +1256,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "Виникла помилка зʼєднання" @@ -1367,5 +1350,23 @@ msgstr "" msgid "Append and correct" msgstr "" +#~ msgid "Snap permission notice" +#~ msgstr "Попередження щодо дозволів Snap" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "" +#~ "Виявлено нестачу повноважень. Будь ласка, перевірте, чи були надані " +#~ "дозволи для Snap" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "" +#~ "Для активації необхідних дозволів, запустіть наступну команду в терміналі" + +#~ msgid "Close" +#~ msgstr "Закрити" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "Введіть інструкції для перекладу ШІ..." diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index 0beb6668..a416096c 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -29,7 +29,7 @@ msgstr "https://example.com/audio.mp3" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "Ok" @@ -37,7 +37,7 @@ msgstr "Ok" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "取消" @@ -150,57 +150,62 @@ msgstr "OpenAI API key" msgid "OpenAI base url" msgstr "OpenAI 基于 url" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "OpenAI API key" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "默认输出文件名" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "启用实时录制转录导出" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "浏览" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "导出文件夹" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 #, fuzzy msgid "Live recording mode" msgstr "现场录制模式" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "测试OpenAI API Key" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "您的API密钥有效。Buzz将使用此密钥执行 Whisper API 识别和 AI 翻译。" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "无效的API key" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 #, fuzzy msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " @@ -208,11 +213,11 @@ msgid "" msgstr "" "API只支持 base64字符(A-Za-z0-9+/=)。其他字符在API密钥中可能导致错误。" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "选择输出文件夹" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -313,11 +318,11 @@ msgid "Download failed" msgstr "下载模型失败" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "错误" @@ -444,7 +449,7 @@ msgstr "打开识别结果" msgid "Cancel Transcription" msgstr "取消识别" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "清除历史纪录" @@ -498,7 +503,7 @@ msgid "Date Added" msgstr "添加日期" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -506,52 +511,52 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "取消识别" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "取消识别" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "取消识别" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -698,36 +703,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "需要API Key" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "请在偏好设置中输入OpenAI API Key" @@ -840,25 +845,6 @@ msgstr "保存文件" msgid "Text files" msgstr "文本文件" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "快照权限通知" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "检测到缺少权限,请检查是否已获得快照权限" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "要启用必要的权限,请在终端中运行以下命令" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "关闭" - #: buzz/widgets/model_download_progress_dialog.py:37 #, fuzzy msgid "Downloading model" @@ -894,17 +880,17 @@ msgstr "帮助" msgid "File" msgstr "文件" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "您确定要删除所选录制吗?此操作无法撤消。" -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "选择音频文件" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "无法将OpenAI API密钥保存到密钥串" @@ -1287,7 +1273,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "连接发生错误" @@ -1386,6 +1372,21 @@ msgstr "增加上方" msgid "Append and correct" msgstr "增加并纠正" +#~ msgid "Snap permission notice" +#~ msgstr "快照权限通知" + +#~ msgid "" +#~ "Detected missing permissions, please check that snap permissions have " +#~ "been granted" +#~ msgstr "检测到缺少权限,请检查是否已获得快照权限" + +#~ msgid "" +#~ "To enable necessary permissions run the following commands in the terminal" +#~ msgstr "要启用必要的权限,请在终端中运行以下命令" + +#~ msgid "Close" +#~ msgstr "关闭" + #~ msgid "Enter instructions for AI on how to translate..." #~ msgstr "输入AI如何翻译的说明..." diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index 508a94f1..a5a5d39d 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-11 20:21+0200\n" +"POT-Creation-Date: 2025-12-13 10:51+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -29,7 +29,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:69 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:251 #: buzz/widgets/transcriber/advanced_settings_dialog.py:97 -#: buzz/widgets/main_window.py:239 +#: buzz/widgets/main_window.py:226 msgid "Ok" msgstr "" @@ -37,7 +37,7 @@ msgstr "" #: buzz/widgets/preferences_dialog/preferences_dialog.py:70 #: buzz/widgets/preferences_dialog/models_preferences_widget.py:252 #: buzz/widgets/model_download_progress_dialog.py:30 -#: buzz/widgets/main_window.py:240 +#: buzz/widgets/main_window.py:227 msgid "Cancel" msgstr "取消" @@ -149,67 +149,72 @@ msgstr "" msgid "OpenAI base url" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:135 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:138 +#, fuzzy +msgid "OpenAI API model" +msgstr "模型:" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 msgid "Default export file name" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:153 msgid "Enable live recording transcription export" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:147 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:159 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47 #: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50 msgid "Browse" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:166 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:178 msgid "Export folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:177 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:189 #, fuzzy msgid "Live recording mode" msgstr "現場錄製" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:183 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:186 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:188 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:213 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:219 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 msgid "OpenAI API Key Test" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:230 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 msgid "Invalid API key" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:249 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 msgid "Select Export Folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:319 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -308,11 +313,11 @@ msgid "Download failed" msgstr "下載模型" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:275 -#: buzz/widgets/transcription_tasks_table_widget.py:665 -#: buzz/widgets/transcription_tasks_table_widget.py:735 -#: buzz/widgets/transcription_tasks_table_widget.py:766 -#: buzz/widgets/main_window.py:296 buzz/model_loader.py:539 -#: buzz/model_loader.py:553 +#: buzz/widgets/transcription_tasks_table_widget.py:704 +#: buzz/widgets/transcription_tasks_table_widget.py:774 +#: buzz/widgets/transcription_tasks_table_widget.py:805 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 +#: buzz/model_loader.py:591 msgid "Error" msgstr "" @@ -439,7 +444,7 @@ msgstr "打開轉換結果" msgid "Cancel Transcription" msgstr "取消錄製" -#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:228 +#: buzz/widgets/main_window_toolbar.py:71 buzz/widgets/main_window.py:215 #: buzz/settings/shortcut.py:38 msgid "Clear History" msgstr "清除歷史紀錄" @@ -493,7 +498,7 @@ msgid "Date Added" msgstr "" #: buzz/widgets/transcription_tasks_table_widget.py:156 -#: buzz/widgets/transcription_tasks_table_widget.py:624 +#: buzz/widgets/transcription_tasks_table_widget.py:663 msgid "Notes" msgstr "" @@ -501,52 +506,52 @@ msgstr "" msgid "Reset Column Order" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:301 +#: buzz/widgets/transcription_tasks_table_widget.py:304 #, fuzzy msgid "Restart Transcription" msgstr "取消錄製" -#: buzz/widgets/transcription_tasks_table_widget.py:305 +#: buzz/widgets/transcription_tasks_table_widget.py:308 msgid "Rename" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:308 +#: buzz/widgets/transcription_tasks_table_widget.py:311 msgid "Add/Edit Notes" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:597 +#: buzz/widgets/transcription_tasks_table_widget.py:636 #, fuzzy msgid "Rename Transcription" msgstr "取消錄製" -#: buzz/widgets/transcription_tasks_table_widget.py:598 +#: buzz/widgets/transcription_tasks_table_widget.py:637 msgid "Enter new name:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:625 +#: buzz/widgets/transcription_tasks_table_widget.py:664 msgid "Enter some relevant notes for this transcription:" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:652 +#: buzz/widgets/transcription_tasks_table_widget.py:691 msgid "Cannot Restart" msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:653 +#: buzz/widgets/transcription_tasks_table_widget.py:692 msgid "Only failed or canceled transcriptions can be restarted." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:666 +#: buzz/widgets/transcription_tasks_table_widget.py:705 #, fuzzy msgid "Failed to restart transcription: {}" msgstr "取消錄製" -#: buzz/widgets/transcription_tasks_table_widget.py:736 +#: buzz/widgets/transcription_tasks_table_widget.py:775 msgid "" "Could not restart transcription: model not available and could not be " "downloaded." msgstr "" -#: buzz/widgets/transcription_tasks_table_widget.py:767 +#: buzz/widgets/transcription_tasks_table_widget.py:806 msgid "Could not restart transcription: transcriber worker not found." msgstr "" @@ -693,36 +698,36 @@ msgstr "" msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:898 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:903 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:979 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1375 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1376 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -834,25 +839,6 @@ msgstr "檔案" msgid "Text files" msgstr "" -#: buzz/widgets/snap_notice.py:9 -msgid "Snap permission notice" -msgstr "" - -#: buzz/widgets/snap_notice.py:13 -msgid "" -"Detected missing permissions, please check that snap permissions have been " -"granted" -msgstr "" - -#: buzz/widgets/snap_notice.py:16 -msgid "" -"To enable necessary permissions run the following commands in the terminal" -msgstr "" - -#: buzz/widgets/snap_notice.py:27 -msgid "Close" -msgstr "" - #: buzz/widgets/model_download_progress_dialog.py:37 #, fuzzy msgid "Downloading model" @@ -888,17 +874,17 @@ msgstr "幫助" msgid "File" msgstr "檔案" -#: buzz/widgets/main_window.py:232 +#: buzz/widgets/main_window.py:219 msgid "" "Are you sure you want to delete the selected transcription(s)? This action " "cannot be undone." msgstr "您確定要刪除所選錄製嗎?此操作無法撤消。" -#: buzz/widgets/main_window.py:260 +#: buzz/widgets/main_window.py:247 msgid "Select audio file" msgstr "選擇聲音檔案" -#: buzz/widgets/main_window.py:296 +#: buzz/widgets/main_window.py:283 msgid "Unable to save OpenAI API key to keyring" msgstr "" @@ -1281,7 +1267,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:572 +#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 msgid "A connection error occurred" msgstr "" diff --git a/buzz/model_loader.py b/buzz/model_loader.py index a89cfe21..24a0a5dc 100644 --- a/buzz/model_loader.py +++ b/buzz/model_loader.py @@ -195,8 +195,10 @@ class TranscriptionModel: def delete_local_file(self): model_path = self.get_local_model_path() - if (self.model_type == ModelType.HUGGING_FACE - or self.model_type == ModelType.FASTER_WHISPER): + if self.model_type in (ModelType.HUGGING_FACE, + ModelType.FASTER_WHISPER): + # Go up two directories to get the huggingface cache root for this model + # Structure: models--repo--name/snapshots/xxx/files model_path = os.path.dirname(os.path.dirname(model_path)) logging.debug("Deleting model directory: %s", model_path) @@ -204,6 +206,32 @@ class TranscriptionModel: shutil.rmtree(model_path, ignore_errors=True) return + if self.model_type == ModelType.WHISPER_CPP: + if self.whisper_model_size == WhisperModelSize.CUSTOM: + # Custom models are stored as a single .bin file directly in model_root_dir + logging.debug("Deleting model file: %s", model_path) + os.remove(model_path) + else: + # Non-custom models are downloaded via huggingface_hub. + # Multiple models share the same repo directory, so we only delete + # the specific model files, not the entire directory. + logging.debug("Deleting model file: %s", model_path) + os.remove(model_path) + + # Also delete CoreML files if they exist (.mlmodelc.zip and extracted directory) + model_dir = os.path.dirname(model_path) + model_name = self.whisper_model_size.to_whisper_cpp_model_size() + coreml_zip = os.path.join(model_dir, f"ggml-{model_name}-encoder.mlmodelc.zip") + coreml_dir = os.path.join(model_dir, f"ggml-{model_name}-encoder.mlmodelc") + + if os.path.exists(coreml_zip): + logging.debug("Deleting CoreML zip: %s", coreml_zip) + os.remove(coreml_zip) + if os.path.exists(coreml_dir): + logging.debug("Deleting CoreML directory: %s", coreml_dir) + shutil.rmtree(coreml_dir, ignore_errors=True) + return + logging.debug("Deleting model file: %s", model_path) os.remove(model_path) @@ -796,10 +824,3 @@ class ModelDownloader(QRunnable): def cancel(self): self.stopped = True - - -def get_custom_api_whisper_model(base_url: str): - if "api.groq.com" in base_url: - return "whisper-large-v3" - - return "whisper-1" diff --git a/buzz/settings/settings.py b/buzz/settings/settings.py index e76868dd..cd8923df 100644 --- a/buzz/settings/settings.py +++ b/buzz/settings/settings.py @@ -39,6 +39,7 @@ class Settings: DEFAULT_EXPORT_FILE_NAME = "transcriber/default-export-file-name" CUSTOM_OPENAI_BASE_URL = "transcriber/custom-openai-base-url" + OPENAI_API_MODEL = "transcriber/openai-api-model" CUSTOM_FASTER_WHISPER_ID = "transcriber/custom-faster-whisper-id" HUGGINGFACE_MODEL_ID = "transcriber/huggingface-model-id" diff --git a/buzz/store/keyring_store.py b/buzz/store/keyring_store.py index 329a264e..0915e018 100644 --- a/buzz/store/keyring_store.py +++ b/buzz/store/keyring_store.py @@ -1,5 +1,10 @@ +import base64 import enum +import hashlib +import json import logging +import os +import sys import keyring @@ -10,7 +15,202 @@ class Key(enum.Enum): OPENAI_API_KEY = "OpenAI API key" +def _is_linux() -> bool: + return sys.platform.startswith("linux") + + +def _get_secrets_file_path() -> str: + """Get the path to the local encrypted secrets file.""" + from platformdirs import user_data_dir + + data_dir = user_data_dir(APP_NAME) + os.makedirs(data_dir, exist_ok=True) + return os.path.join(data_dir, ".secrets.json") + + +def _get_portal_secret() -> bytes | None: + """Get the application secret from XDG Desktop Portal. + + The portal provides a per-application secret that can be used + for encrypting application-specific data. This works in sandboxed + environments (Snap/Flatpak) via the desktop plug. + """ + if not _is_linux(): + return None + + try: + from jeepney import DBusAddress, new_method_call + from jeepney.io.blocking import open_dbus_connection + import socket + + # Open connection with file descriptor support enabled + conn = open_dbus_connection(bus="SESSION", enable_fds=True) + + portal = DBusAddress( + "/org/freedesktop/portal/desktop", + bus_name="org.freedesktop.portal.Desktop", + interface="org.freedesktop.portal.Secret", + ) + + # Create a socket pair for receiving the secret + sock_read, sock_write = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) + + try: + # Build the method call with file descriptor + # RetrieveSecret(fd: h, options: a{sv}) -> (handle: o) + # Pass the socket object directly - jeepney handles fd passing + msg = new_method_call(portal, "RetrieveSecret", "ha{sv}", (sock_write, {})) + + # Send message and get reply + conn.send_and_get_reply(msg, timeout=10) + + # Close the write end - portal has it now + sock_write.close() + sock_write = None + + # Read the secret from the read end + # The portal writes the secret and closes its end + sock_read.settimeout(5.0) + secret_data = b"" + while True: + try: + chunk = sock_read.recv(4096) + if not chunk: + break + secret_data += chunk + except socket.timeout: + break + + if secret_data: + logging.debug( + "Successfully retrieved portal secret (%d bytes)", len(secret_data) + ) + return secret_data + + return None + + finally: + sock_read.close() + if sock_write is not None: + sock_write.close() + + except Exception as exc: + logging.debug("XDG Portal secret not available: %s", exc) + return None + + +def _derive_key(master_secret: bytes, key_name: str) -> bytes: + """Derive a key-specific encryption key from the master secret.""" + # Use PBKDF2 to derive a key for this specific secret + return hashlib.pbkdf2_hmac( + "sha256", + master_secret, + f"{APP_NAME}:{key_name}".encode(), + 100000, + dklen=32, + ) + + +def _encrypt_value(value: str, key: bytes) -> str: + """Encrypt a value using XOR with the derived key (simple encryption).""" + # For a more secure implementation, use cryptography library with AES + # This is a simple XOR-based encryption suitable for the use case + value_bytes = value.encode("utf-8") + key_extended = (key * ((len(value_bytes) // len(key)) + 1))[: len(value_bytes)] + encrypted = bytes(a ^ b for a, b in zip(value_bytes, key_extended)) + return base64.b64encode(encrypted).decode("ascii") + + +def _decrypt_value(encrypted: str, key: bytes) -> str: + """Decrypt a value using XOR with the derived key.""" + encrypted_bytes = base64.b64decode(encrypted.encode("ascii")) + key_extended = (key * ((len(encrypted_bytes) // len(key)) + 1))[: len(encrypted_bytes)] + decrypted = bytes(a ^ b for a, b in zip(encrypted_bytes, key_extended)) + return decrypted.decode("utf-8") + + +def _load_local_secrets() -> dict: + """Load the local secrets file.""" + secrets_file = _get_secrets_file_path() + if os.path.exists(secrets_file): + try: + with open(secrets_file, "r") as f: + return json.load(f) + except (json.JSONDecodeError, IOError) as exc: + logging.debug("Failed to load secrets file: %s", exc) + return {} + + +def _save_local_secrets(secrets: dict) -> None: + """Save secrets to the local file.""" + secrets_file = _get_secrets_file_path() + try: + with open(secrets_file, "w") as f: + json.dump(secrets, f) + # Set restrictive permissions + os.chmod(secrets_file, 0o600) + except IOError as exc: + logging.warning("Failed to save secrets file: %s", exc) + + +def _get_portal_password(key: Key) -> str | None: + """Get a password using the XDG Desktop Portal Secret.""" + portal_secret = _get_portal_secret() + if portal_secret is None: + return None + + secrets = _load_local_secrets() + encrypted_value = secrets.get(key.value) + if encrypted_value is None: + return None + + try: + derived_key = _derive_key(portal_secret, key.value) + return _decrypt_value(encrypted_value, derived_key) + except Exception as exc: + logging.debug("Failed to decrypt portal secret: %s", exc) + return None + + +def _set_portal_password(key: Key, password: str) -> bool: + """Set a password using the XDG Desktop Portal Secret.""" + portal_secret = _get_portal_secret() + if portal_secret is None: + return False + + try: + derived_key = _derive_key(portal_secret, key.value) + encrypted_value = _encrypt_value(password, derived_key) + + secrets = _load_local_secrets() + secrets[key.value] = encrypted_value + _save_local_secrets(secrets) + return True + except Exception as exc: + logging.debug("Failed to set portal secret: %s", exc) + return False + + +def _delete_portal_password(key: Key) -> bool: + """Delete a password from the portal-based local storage.""" + secrets = _load_local_secrets() + if key.value in secrets: + del secrets[key.value] + _save_local_secrets(secrets) + return True + return False + + def get_password(key: Key) -> str | None: + # On Linux, try XDG Desktop Portal first (works in sandboxed environments) + if _is_linux(): + result = _get_portal_password(key) + + + if result is not None: + return result + + # Fall back to keyring (cross-platform, uses Secret Service on Linux) try: password = keyring.get_password(APP_NAME, username=key.value) if password is None: @@ -22,4 +222,25 @@ def get_password(key: Key) -> str | None: def set_password(username: Key, password: str) -> None: + # On Linux, try XDG Desktop Portal first (works in sandboxed environments) + if _is_linux(): + if _set_portal_password(username, password): + return + + # Fall back to keyring (cross-platform, uses Secret Service on Linux) keyring.set_password(APP_NAME, username.value, password) + + +def delete_password(key: Key) -> None: + """Delete a password from the secret store.""" + # On Linux, also delete from portal storage + if _is_linux(): + _delete_portal_password(key) + + # Delete from keyring + try: + keyring.delete_password(APP_NAME, key.value) + except keyring.errors.PasswordDeleteError: + pass # Password doesn't exist, ignore + except Exception as exc: + logging.warning("Unable to delete from keyring: %s", exc) diff --git a/buzz/transcriber/openai_whisper_api_file_transcriber.py b/buzz/transcriber/openai_whisper_api_file_transcriber.py index b2f02898..0413f7e4 100644 --- a/buzz/transcriber/openai_whisper_api_file_transcriber.py +++ b/buzz/transcriber/openai_whisper_api_file_transcriber.py @@ -12,7 +12,6 @@ from PyQt6.QtCore import QObject from openai import OpenAI from buzz.settings.settings import Settings -from buzz.model_loader import get_custom_api_whisper_model from buzz.transcriber.file_transcriber import FileTranscriber, app_env from buzz.transcriber.transcriber import FileTranscriptionTask, Segment, Task @@ -49,7 +48,9 @@ class OpenAIWhisperAPIFileTranscriber(FileTranscriber): base_url=custom_openai_base_url if custom_openai_base_url else None, max_retries=0 ) - self.whisper_api_model = get_custom_api_whisper_model(custom_openai_base_url) + self.whisper_api_model = settings.value( + key=Settings.Key.OPENAI_API_MODEL, default_value="whisper-1" + ) self.word_level_timings = self.transcription_task.transcription_options.word_level_timings logging.debug("Will use whisper API on %s, %s", custom_openai_base_url, self.whisper_api_model) diff --git a/buzz/transcriber/recording_transcriber.py b/buzz/transcriber/recording_transcriber.py index 7867e50e..6800f006 100644 --- a/buzz/transcriber/recording_transcriber.py +++ b/buzz/transcriber/recording_transcriber.py @@ -21,10 +21,9 @@ from PyQt6.QtCore import QObject, pyqtSignal from buzz import whisper_audio from buzz.locale import _ from buzz.assets import APP_BASE_DIR -from buzz.model_loader import ModelType, get_custom_api_whisper_model +from buzz.model_loader import ModelType from buzz.settings.settings import Settings from buzz.transcriber.transcriber import TranscriptionOptions, Task -from buzz.transcriber.file_transcriber import app_env from buzz.transformers_whisper import TransformersWhisper from buzz.settings.recording_transcriber_mode import RecordingTranscriberMode @@ -68,7 +67,9 @@ class RecordingTranscriber(QObject): self.mutex = threading.Lock() self.sounddevice = sounddevice self.openai_client = None - self.whisper_api_model = get_custom_api_whisper_model("") + self.whisper_api_model = self.settings.value( + key=Settings.Key.OPENAI_API_MODEL, default_value="whisper-1" + ) self.process = None def start(self): @@ -123,7 +124,6 @@ class RecordingTranscriber(QObject): custom_openai_base_url = self.settings.value( key=Settings.Key.CUSTOM_OPENAI_BASE_URL, default_value="" ) - self.whisper_api_model = get_custom_api_whisper_model(custom_openai_base_url) self.openai_client = OpenAI( api_key=self.transcription_options.openai_access_token, base_url=custom_openai_base_url if custom_openai_base_url else None, diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 384c8c71..4ddaf990 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -38,7 +38,6 @@ from buzz.widgets.icon import BUZZ_ICON_PATH from buzz.widgets.import_url_dialog import ImportURLDialog from buzz.widgets.main_window_toolbar import MainWindowToolbar from buzz.widgets.menu_bar import MenuBar -from buzz.widgets.snap_notice import SnapNotice from buzz.widgets.preferences_dialog.models.preferences import Preferences from buzz.widgets.transcriber.file_transcriber_widget import FileTranscriberWidget from buzz.widgets.transcription_task_folder_watcher import ( @@ -154,19 +153,6 @@ class MainWindow(QMainWindow): self.transcription_viewer_widget = None - # TODO Move this to the first user interaction with OpenAI api Key field - # that is the only place that needs access to password manager service - if os.environ.get('SNAP_NAME', '') == 'buzz': - logging.debug("Running in a snap environment") - self.check_linux_permissions() - - def check_linux_permissions(self): - try: - _ = keyring.get_password(APP_NAME, username="random") - except Exception: - snap_notice = SnapNotice(self) - snap_notice.show() - def on_preferences_changed(self, preferences: Preferences): self.preferences = preferences self.save_preferences(preferences) diff --git a/buzz/widgets/preferences_dialog/general_preferences_widget.py b/buzz/widgets/preferences_dialog/general_preferences_widget.py index b7bdfc74..8f29f5de 100644 --- a/buzz/widgets/preferences_dialog/general_preferences_widget.py +++ b/buzz/widgets/preferences_dialog/general_preferences_widget.py @@ -125,6 +125,18 @@ class GeneralPreferencesWidget(QWidget): self.custom_openai_base_url_line_edit.setPlaceholderText("https://api.openai.com/v1") layout.addRow(_("OpenAI base url"), self.custom_openai_base_url_line_edit) + self.openai_api_model = self.settings.value( + key=Settings.Key.OPENAI_API_MODEL, default_value="whisper-1" + ) + + self.openai_api_model_line_edit = LineEdit(self.openai_api_model, self) + self.openai_api_model_line_edit.textChanged.connect( + self.on_openai_api_model_changed + ) + self.openai_api_model_line_edit.setMinimumWidth(200) + self.openai_api_model_line_edit.setPlaceholderText("whisper-1") + layout.addRow(_("OpenAI API model"), self.openai_api_model_line_edit) + default_export_file_name = self.settings.get_default_export_file_template() default_export_file_name_line_edit = LineEdit(default_export_file_name, self) @@ -234,6 +246,9 @@ class GeneralPreferencesWidget(QWidget): def on_custom_openai_base_url_changed(self, text: str): self.settings.set_value(Settings.Key.CUSTOM_OPENAI_BASE_URL, text) + def on_openai_api_model_changed(self, text: str): + self.settings.set_value(Settings.Key.OPENAI_API_MODEL, text) + def on_recording_export_enable_changed(self, state: int): self.recording_export_enabled = state == 2 diff --git a/buzz/widgets/snap_notice.py b/buzz/widgets/snap_notice.py deleted file mode 100644 index ea9dc5c9..00000000 --- a/buzz/widgets/snap_notice.py +++ /dev/null @@ -1,29 +0,0 @@ -from PyQt6.QtWidgets import QDialog, QVBoxLayout, QTextEdit, QLabel, QPushButton -from buzz.locale import _ - - -class SnapNotice(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - - self.setWindowTitle(_("Snap permission notice")) - - self.layout = QVBoxLayout(self) - - self.notice_label = QLabel(_("Detected missing permissions, please check that snap permissions have been granted")) - self.layout.addWidget(self.notice_label) - - self.instruction_label = QLabel(_("To enable necessary permissions run the following commands in the terminal")) - self.layout.addWidget(self.instruction_label) - - self.text_edit = QTextEdit(self) - self.text_edit.setPlainText( - "sudo snap connect buzz:password-manager-service\n" - ) - self.text_edit.setReadOnly(True) - self.text_edit.setFixedHeight(80) - self.layout.addWidget(self.text_edit) - - self.button = QPushButton(_("Close"), self) - self.button.clicked.connect(self.close) - self.layout.addWidget(self.button) \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 47b96448..abb6a7cd 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -75,6 +75,7 @@ parts: - python3 - libgcc-s1 - libc6 + - libproxy1v5 # Audio - ffmpeg - libportaudio2 @@ -170,6 +171,7 @@ apps: - removable-media - audio-playback - audio-record + # Fallback for keyring support if secrets portal is missing, user has to connect this manually - password-manager-service layout: diff --git a/tests/store/__init__.py b/tests/store/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/store/keyring_store_test.py b/tests/store/keyring_store_test.py new file mode 100644 index 00000000..8486940f --- /dev/null +++ b/tests/store/keyring_store_test.py @@ -0,0 +1,457 @@ +import json +import os +import sys +import tempfile +from unittest.mock import Mock, patch, MagicMock + +import pytest + +from buzz.store.keyring_store import ( + Key, + _is_linux, + _derive_key, + _encrypt_value, + _decrypt_value, + _load_local_secrets, + _save_local_secrets, + _get_portal_password, + _set_portal_password, + _delete_portal_password, + get_password, + set_password, + delete_password, +) +from buzz.settings.settings import APP_NAME + + +class TestKey: + def test_openai_api_key_exists(self): + assert hasattr(Key, "OPENAI_API_KEY") + + def test_openai_api_key_value(self): + assert Key.OPENAI_API_KEY.value == "OpenAI API key" + + def test_key_is_enum(self): + assert isinstance(Key.OPENAI_API_KEY, Key) + + +class TestIsLinux: + @patch("buzz.store.keyring_store.sys.platform", "linux") + def test_returns_true_on_linux(self): + assert _is_linux() is True + + @patch("buzz.store.keyring_store.sys.platform", "linux2") + def test_returns_true_on_linux2(self): + assert _is_linux() is True + + @patch("buzz.store.keyring_store.sys.platform", "darwin") + def test_returns_false_on_macos(self): + assert _is_linux() is False + + @patch("buzz.store.keyring_store.sys.platform", "win32") + def test_returns_false_on_windows(self): + assert _is_linux() is False + + +class TestDeriveKey: + def test_derive_key_returns_32_bytes(self): + master_secret = b"test_secret" + key_name = "test_key" + derived = _derive_key(master_secret, key_name) + assert len(derived) == 32 + + def test_derive_key_is_deterministic(self): + master_secret = b"test_secret" + key_name = "test_key" + derived1 = _derive_key(master_secret, key_name) + derived2 = _derive_key(master_secret, key_name) + assert derived1 == derived2 + + def test_derive_key_different_for_different_names(self): + master_secret = b"test_secret" + derived1 = _derive_key(master_secret, "key1") + derived2 = _derive_key(master_secret, "key2") + assert derived1 != derived2 + + def test_derive_key_different_for_different_secrets(self): + key_name = "test_key" + derived1 = _derive_key(b"secret1", key_name) + derived2 = _derive_key(b"secret2", key_name) + assert derived1 != derived2 + + +class TestEncryptDecrypt: + def test_encrypt_decrypt_roundtrip(self): + key = b"0123456789abcdef0123456789abcdef" # 32 bytes + original = "test_password_123" + encrypted = _encrypt_value(original, key) + decrypted = _decrypt_value(encrypted, key) + assert decrypted == original + + def test_encrypt_decrypt_empty_string(self): + key = b"0123456789abcdef0123456789abcdef" + original = "" + encrypted = _encrypt_value(original, key) + decrypted = _decrypt_value(encrypted, key) + assert decrypted == original + + def test_encrypt_decrypt_unicode(self): + key = b"0123456789abcdef0123456789abcdef" + original = "test_password_\u4e2d\u6587_\U0001f600" + encrypted = _encrypt_value(original, key) + decrypted = _decrypt_value(encrypted, key) + assert decrypted == original + + def test_encrypt_decrypt_long_string(self): + key = b"0123456789abcdef0123456789abcdef" + original = "a" * 1000 + encrypted = _encrypt_value(original, key) + decrypted = _decrypt_value(encrypted, key) + assert decrypted == original + + def test_encrypted_is_base64(self): + key = b"0123456789abcdef0123456789abcdef" + original = "test" + encrypted = _encrypt_value(original, key) + # Should be valid base64 + import base64 + base64.b64decode(encrypted) # Should not raise + + def test_different_keys_produce_different_ciphertext(self): + key1 = b"0123456789abcdef0123456789abcdef" + key2 = b"fedcba9876543210fedcba9876543210" + original = "test_password" + encrypted1 = _encrypt_value(original, key1) + encrypted2 = _encrypt_value(original, key2) + assert encrypted1 != encrypted2 + + +class TestLocalSecrets: + def test_load_empty_file(self): + with tempfile.TemporaryDirectory() as tmpdir: + with patch( + "buzz.store.keyring_store._get_secrets_file_path", + return_value=os.path.join(tmpdir, ".secrets.json"), + ): + result = _load_local_secrets() + assert result == {} + + def test_save_and_load_secrets(self): + with tempfile.TemporaryDirectory() as tmpdir: + secrets_path = os.path.join(tmpdir, ".secrets.json") + with patch( + "buzz.store.keyring_store._get_secrets_file_path", + return_value=secrets_path, + ): + test_secrets = {"key1": "value1", "key2": "value2"} + _save_local_secrets(test_secrets) + loaded = _load_local_secrets() + assert loaded == test_secrets + + @pytest.mark.skipif(sys.platform == "win32", reason="Unix file permissions not applicable on Windows") + def test_save_sets_restrictive_permissions(self): + with tempfile.TemporaryDirectory() as tmpdir: + secrets_path = os.path.join(tmpdir, ".secrets.json") + with patch( + "buzz.store.keyring_store._get_secrets_file_path", + return_value=secrets_path, + ): + _save_local_secrets({"key": "value"}) + # Check file permissions (0o600 = owner read/write only) + mode = os.stat(secrets_path).st_mode & 0o777 + assert mode == 0o600 + + def test_load_handles_corrupted_json(self): + with tempfile.TemporaryDirectory() as tmpdir: + secrets_path = os.path.join(tmpdir, ".secrets.json") + with open(secrets_path, "w") as f: + f.write("not valid json {{{") + with patch( + "buzz.store.keyring_store._get_secrets_file_path", + return_value=secrets_path, + ): + result = _load_local_secrets() + assert result == {} + + +class TestPortalPassword: + @patch("buzz.store.keyring_store._get_portal_secret") + @patch("buzz.store.keyring_store._load_local_secrets") + def test_get_portal_password_returns_none_when_no_portal( + self, mock_load, mock_portal + ): + mock_portal.return_value = None + result = _get_portal_password(Key.OPENAI_API_KEY) + assert result is None + + @patch("buzz.store.keyring_store._get_portal_secret") + @patch("buzz.store.keyring_store._load_local_secrets") + def test_get_portal_password_returns_none_when_key_not_found( + self, mock_load, mock_portal + ): + mock_portal.return_value = b"test_secret_64_bytes_" + b"x" * 43 + mock_load.return_value = {} + result = _get_portal_password(Key.OPENAI_API_KEY) + assert result is None + + @patch("buzz.store.keyring_store._get_portal_secret") + @patch("buzz.store.keyring_store._load_local_secrets") + def test_get_portal_password_decrypts_stored_value(self, mock_load, mock_portal): + portal_secret = b"test_secret_64_bytes_" + b"x" * 43 + mock_portal.return_value = portal_secret + + # Pre-encrypt a value + derived_key = _derive_key(portal_secret, Key.OPENAI_API_KEY.value) + encrypted = _encrypt_value("my_api_key", derived_key) + + mock_load.return_value = {Key.OPENAI_API_KEY.value: encrypted} + + result = _get_portal_password(Key.OPENAI_API_KEY) + assert result == "my_api_key" + + @patch("buzz.store.keyring_store._get_portal_secret") + def test_set_portal_password_returns_false_when_no_portal(self, mock_portal): + mock_portal.return_value = None + result = _set_portal_password(Key.OPENAI_API_KEY, "test_password") + assert result is False + + @patch("buzz.store.keyring_store._get_portal_secret") + @patch("buzz.store.keyring_store._load_local_secrets") + @patch("buzz.store.keyring_store._save_local_secrets") + def test_set_portal_password_encrypts_and_saves( + self, mock_save, mock_load, mock_portal + ): + portal_secret = b"test_secret_64_bytes_" + b"x" * 43 + mock_portal.return_value = portal_secret + mock_load.return_value = {} + + result = _set_portal_password(Key.OPENAI_API_KEY, "test_password") + + assert result is True + mock_save.assert_called_once() + saved_secrets = mock_save.call_args[0][0] + assert Key.OPENAI_API_KEY.value in saved_secrets + + # Verify the saved value can be decrypted + derived_key = _derive_key(portal_secret, Key.OPENAI_API_KEY.value) + decrypted = _decrypt_value(saved_secrets[Key.OPENAI_API_KEY.value], derived_key) + assert decrypted == "test_password" + + +class TestDeletePortalPassword: + @patch("buzz.store.keyring_store._load_local_secrets") + @patch("buzz.store.keyring_store._save_local_secrets") + def test_delete_existing_key(self, mock_save, mock_load): + mock_load.return_value = {Key.OPENAI_API_KEY.value: "encrypted_value"} + + result = _delete_portal_password(Key.OPENAI_API_KEY) + + assert result is True + mock_save.assert_called_once() + saved_secrets = mock_save.call_args[0][0] + assert Key.OPENAI_API_KEY.value not in saved_secrets + + @patch("buzz.store.keyring_store._load_local_secrets") + @patch("buzz.store.keyring_store._save_local_secrets") + def test_delete_nonexistent_key(self, mock_save, mock_load): + mock_load.return_value = {} + + result = _delete_portal_password(Key.OPENAI_API_KEY) + + assert result is False + mock_save.assert_not_called() + + +class TestGetPassword: + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store._get_portal_password") + @patch("buzz.store.keyring_store.keyring") + def test_returns_portal_password_on_linux( + self, mock_keyring, mock_portal, mock_is_linux + ): + mock_is_linux.return_value = True + mock_portal.return_value = "portal_password" + + result = get_password(Key.OPENAI_API_KEY) + + assert result == "portal_password" + mock_keyring.get_password.assert_not_called() + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store._get_portal_password") + @patch("buzz.store.keyring_store.keyring") + def test_falls_back_to_keyring_when_portal_returns_none( + self, mock_keyring, mock_portal, mock_is_linux + ): + mock_is_linux.return_value = True + mock_portal.return_value = None + mock_keyring.get_password.return_value = "keyring_password" + + result = get_password(Key.OPENAI_API_KEY) + + assert result == "keyring_password" + mock_keyring.get_password.assert_called_once_with( + APP_NAME, username=Key.OPENAI_API_KEY.value + ) + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store.keyring") + def test_uses_keyring_directly_on_non_linux(self, mock_keyring, mock_is_linux): + mock_is_linux.return_value = False + mock_keyring.get_password.return_value = "keyring_password" + + result = get_password(Key.OPENAI_API_KEY) + + assert result == "keyring_password" + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store.keyring") + def test_returns_empty_string_when_keyring_returns_none( + self, mock_keyring, mock_is_linux + ): + mock_is_linux.return_value = False + mock_keyring.get_password.return_value = None + + result = get_password(Key.OPENAI_API_KEY) + + assert result == "" + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store.keyring") + def test_returns_empty_string_on_keyring_exception( + self, mock_keyring, mock_is_linux + ): + mock_is_linux.return_value = False + mock_keyring.get_password.side_effect = Exception("Keyring error") + + result = get_password(Key.OPENAI_API_KEY) + + assert result == "" + + +class TestSetPassword: + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store._set_portal_password") + @patch("buzz.store.keyring_store.keyring") + def test_uses_portal_on_linux_when_successful( + self, mock_keyring, mock_portal, mock_is_linux + ): + mock_is_linux.return_value = True + mock_portal.return_value = True + + set_password(Key.OPENAI_API_KEY, "test_password") + + mock_portal.assert_called_once_with(Key.OPENAI_API_KEY, "test_password") + mock_keyring.set_password.assert_not_called() + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store._set_portal_password") + @patch("buzz.store.keyring_store.keyring") + def test_falls_back_to_keyring_when_portal_fails( + self, mock_keyring, mock_portal, mock_is_linux + ): + mock_is_linux.return_value = True + mock_portal.return_value = False + + set_password(Key.OPENAI_API_KEY, "test_password") + + mock_keyring.set_password.assert_called_once_with( + APP_NAME, Key.OPENAI_API_KEY.value, "test_password" + ) + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store.keyring") + def test_uses_keyring_directly_on_non_linux(self, mock_keyring, mock_is_linux): + mock_is_linux.return_value = False + + set_password(Key.OPENAI_API_KEY, "test_password") + + mock_keyring.set_password.assert_called_once_with( + APP_NAME, Key.OPENAI_API_KEY.value, "test_password" + ) + + +class TestDeletePassword: + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store._delete_portal_password") + @patch("buzz.store.keyring_store.keyring") + def test_deletes_from_both_on_linux( + self, mock_keyring, mock_delete_portal, mock_is_linux + ): + mock_is_linux.return_value = True + mock_delete_portal.return_value = True + + delete_password(Key.OPENAI_API_KEY) + + mock_delete_portal.assert_called_once_with(Key.OPENAI_API_KEY) + mock_keyring.delete_password.assert_called_once_with( + APP_NAME, Key.OPENAI_API_KEY.value + ) + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store.keyring") + def test_deletes_from_keyring_only_on_non_linux(self, mock_keyring, mock_is_linux): + mock_is_linux.return_value = False + + delete_password(Key.OPENAI_API_KEY) + + mock_keyring.delete_password.assert_called_once_with( + APP_NAME, Key.OPENAI_API_KEY.value + ) + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store.keyring") + def test_ignores_password_delete_error(self, mock_keyring, mock_is_linux): + mock_is_linux.return_value = False + mock_keyring.errors.PasswordDeleteError = Exception + mock_keyring.delete_password.side_effect = ( + mock_keyring.errors.PasswordDeleteError("Not found") + ) + + # Should not raise + delete_password(Key.OPENAI_API_KEY) + + @patch("buzz.store.keyring_store._is_linux") + @patch("buzz.store.keyring_store.keyring") + def test_handles_other_keyring_exceptions(self, mock_keyring, mock_is_linux): + mock_is_linux.return_value = False + mock_keyring.errors.PasswordDeleteError = KeyError # Different exception type + mock_keyring.delete_password.side_effect = RuntimeError("Some other error") + + # Should not raise + delete_password(Key.OPENAI_API_KEY) + + +class TestIntegration: + """Integration tests that test the full flow with mocked portal.""" + + @patch("buzz.store.keyring_store._get_portal_secret") + def test_full_roundtrip_with_portal(self, mock_portal): + """Test set -> get -> delete flow with portal.""" + portal_secret = b"integration_test_secret_" + b"y" * 40 + + with tempfile.TemporaryDirectory() as tmpdir: + secrets_path = os.path.join(tmpdir, ".secrets.json") + + with patch( + "buzz.store.keyring_store._get_secrets_file_path", + return_value=secrets_path, + ): + with patch("buzz.store.keyring_store._is_linux", return_value=True): + mock_portal.return_value = portal_secret + + # Set password + result = _set_portal_password(Key.OPENAI_API_KEY, "my_secret_key") + assert result is True + + # Get password + retrieved = _get_portal_password(Key.OPENAI_API_KEY) + assert retrieved == "my_secret_key" + + # Delete password + deleted = _delete_portal_password(Key.OPENAI_API_KEY) + assert deleted is True + + # Verify it's gone + retrieved_after_delete = _get_portal_password(Key.OPENAI_API_KEY) + assert retrieved_after_delete is None diff --git a/tests/widgets/main_window_test.py b/tests/widgets/main_window_test.py index 5bbb194a..3fc7b39e 100644 --- a/tests/widgets/main_window_test.py +++ b/tests/widgets/main_window_test.py @@ -19,7 +19,6 @@ from buzz.locale import _ from buzz.db.entity.transcription import Transcription from buzz.db.service.transcription_service import TranscriptionService from buzz.widgets.main_window import MainWindow -from buzz.widgets.snap_notice import SnapNotice from buzz.widgets.transcriber.file_transcriber_widget import FileTranscriberWidget mock_transcriptions: List[Transcription] = [ @@ -342,12 +341,3 @@ class TestMainWindow: def _get_toolbar_action(window: MainWindow, text: str): toolbar: QToolBar = window.findChild(QToolBar) return [action for action in toolbar.actions() if action.text() == text][0] - - def test_snap_notice_dialog(self, qtbot: QtBot): - snap_notice = SnapNotice() - snap_notice.show() - - qtbot.wait_until(lambda: snap_notice.isVisible(), timeout=1000) - - snap_notice.close() - assert not snap_notice.isVisible() From ebcd42c8ebcf90f80c69d89be00c246efdb45836 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Tue, 16 Dec 2025 10:40:42 +0200 Subject: [PATCH 24/73] Fix for speaker identification crash (#1315) --- buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/en_US/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/nl/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 44 +-- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 44 +-- .../speaker_identification_widget.py | 353 ++++++++++++------ 15 files changed, 541 insertions(+), 428 deletions(-) diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index bf7db49b..e3c3ba24 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -760,73 +760,73 @@ msgstr "Divideix per la longitud màxima" msgid "Merge" msgstr "Fusiona" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Cancel·la la transcripció" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Desa el fitxer" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index 8deb97db..08302e50 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -754,73 +754,73 @@ msgstr "Split ved max længde" msgid "Merge" msgstr "Sammenflet" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Afbryd transkription" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Gem fil" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index 0352e5f9..eb751acc 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -755,73 +755,73 @@ msgstr "Aufgeteilt nach maximaler Länge" msgid "Merge" msgstr "Vereinigen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Transkription abbrechen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Datei speichern" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index 64d9ac70..e604ee25 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -738,72 +738,72 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 msgid "5/8 Preparing transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 msgid "Save" msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index 2f039908..69fd2c3a 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -801,75 +801,75 @@ msgstr "Dividido por la longitud máxima" msgid "Merge" msgstr "Fusión" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" # automatic translation -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Cancelar transcripción" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" # automatic translation -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Guardar archivo" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index 7fd50c46..53f6279e 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -763,73 +763,73 @@ msgstr "Diviso per lunghezza massima" msgid "Merge" msgstr "Unione" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Inizio trascrizione..." -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Salva file" diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index f2bb2f90..a683e72b 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -751,73 +751,73 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "文字起こしをキャンセルする" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "ファイルを保存" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index 339e8152..1d126d07 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2025-12-13 10:52+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -758,72 +758,72 @@ msgstr "Dalīt pie maksimālā garuma" msgid "Merge" msgstr "Apvienot" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "1/8 Apkopo transkripcijas" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "2/8 Ielādē audio" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "3/8 Ielādē identifikācijas modeli" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "4/8 Apstrādā audio" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 msgid "5/8 Preparing transcripts" msgstr "5/8 Sagatavo transkripcijas" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "6/8 Nosaka runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "0/0 Kļūda nosakot runātājus" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "7/8 Marķē runātāju teikumus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "8/8 Runātāju noteikšana pabeigta" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "0/0 Kļūda nosakot runātājus" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "1. solis: Runātāju noteikšana" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "Noteikt" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "Gatavs noteikt runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "Audio datne nav atrasta" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "2. solis: Runātāju identifikācija" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "Atskaņot paraugu" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "Apvienot secīgus runātāja teikumus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 msgid "Save" msgstr "Saglabāt" diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index a7b4d2f0..eb8dfa6f 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -755,73 +755,73 @@ msgstr "Splitsen op basis van max. lengte" msgid "Merge" msgstr "Samenvoegen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Transcriptie wissen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Bestand opslaan" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index a1e36157..fb8bb348 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -763,73 +763,73 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Anuluj transkrypcję" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Zapisz plik" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 129dca1d..30772b58 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -758,73 +758,73 @@ msgstr "Dividir por tamanho máximo" msgid "Merge" msgstr "Mesclar" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Iniciando transcrição..." -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Salvar Arquivo" diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index 3472eed8..39a56d0b 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -752,73 +752,73 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Скасувати транскрипцію" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "Зберегти файл" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index a416096c..4e71c3f0 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -765,73 +765,73 @@ msgstr "按最大长度拆分" msgid "Merge" msgstr "合并" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "取消识别" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "保存文件" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index a5a5d39d..beb2e501 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-13 10:51+0200\n" +"POT-Creation-Date: 2025-12-16 08:58+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -759,73 +759,73 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:92 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:106 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:115 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:121 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:133 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "取消錄製" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:151 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 -msgid "0/0 Error identifying speakers" -msgstr "" - -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:175 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:214 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:251 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +msgid "0/0 Error identifying speakers" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:263 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:272 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:274 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:298 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:313 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:406 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:333 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 #, fuzzy msgid "Save" msgstr "檔案" diff --git a/buzz/widgets/transcription_viewer/speaker_identification_widget.py b/buzz/widgets/transcription_viewer/speaker_identification_widget.py index 97bdce3d..3fba2c10 100644 --- a/buzz/widgets/transcription_viewer/speaker_identification_widget.py +++ b/buzz/widgets/transcription_viewer/speaker_identification_widget.py @@ -53,11 +53,17 @@ SENTENCE_END = re.compile(r'.*[.!?。!?]') class IdentificationWorker(QObject): finished = pyqtSignal(list) progress_update = pyqtSignal(str) + error = pyqtSignal(str) def __init__(self, transcription, transcription_service): super().__init__() self.transcription = transcription self.transcription_service = transcription_service + self._is_cancelled = False + + def cancel(self): + """Request cancellation of the worker.""" + self._is_cancelled = True def get_transcript(self, audio, **kwargs) -> dict: buzz_segments = self.transcription_service.get_transcription_segments( @@ -89,130 +95,189 @@ class IdentificationWorker(QObject): } def run(self): - self.progress_update.emit(_("1/8 Collecting transcripts")) - - # Step 1 - Get transcript - # TODO - Add detected language to the transcript, detect and store separately in metadata - # Will also be relevant for template parsing of transcript file names - # - See diarize.py for example on how to get this info from whisper transcript, maybe other whisper models also have it - language = self.transcription.language if self.transcription.language else "en" - - segments = self.transcription_service.get_transcription_segments( - transcription_id=self.transcription.id_as_uuid - ) - - full_transcript = "".join(segment.text for segment in segments) - - self.progress_update.emit(_("2/8 Loading audio")) - audio_waveform = faster_whisper.decode_audio(self.transcription.file) - - # Step 2 - Forced alignment - force_cpu = os.getenv("BUZZ_FORCE_CPU", "false") - use_cuda = torch.cuda.is_available() and force_cpu == "false" - device = "cuda" if use_cuda else "cpu" - torch_dtype = torch.float16 if use_cuda else torch.float32 - - self.progress_update.emit(_("3/8 Loading alignment model")) - alignment_model, alignment_tokenizer = load_alignment_model( - device, - dtype=torch_dtype, - ) - - self.progress_update.emit(_("4/8 Processing audio")) - emissions, stride = generate_emissions( - alignment_model, - torch.from_numpy(audio_waveform) - .to(alignment_model.dtype) - .to(alignment_model.device), - batch_size=8, - ) - - del alignment_model - torch.cuda.empty_cache() - - self.progress_update.emit(_("5/8 Preparing transcripts")) - tokens_starred, text_starred = preprocess_text( - full_transcript, - romanize=True, - language=langs_to_iso[language], - ) - - segments, scores, blank_token = get_alignments( - emissions, - tokens_starred, - alignment_tokenizer, - ) - - spans = get_spans(tokens_starred, segments, blank_token) - - word_timestamps = postprocess_results(text_starred, spans, stride, scores) - - # Step 3 - Diarization - self.progress_update.emit(_("6/8 Identifying speakers")) - - # Silence NeMo's verbose logging - logging.getLogger("nemo_logging").setLevel(logging.ERROR) - try: - # Also try to silence NeMo's internal logging system - from nemo.utils import logging as nemo_logging - nemo_logging.setLevel(logging.ERROR) - except (ImportError, AttributeError): - pass + diarizer_model = None + alignment_model = None try: - diarizer_model = MSDDDiarizer(device) - speaker_ts = diarizer_model.diarize(torch.from_numpy(audio_waveform).unsqueeze(0)) + logging.debug("Speaker identification worker: Starting") + self.progress_update.emit(_("1/8 Collecting transcripts")) - except Exception as e: - self.progress_update.emit(_("0/0 Error identifying speakers")) - logging.error(f"Error during diarization: {e}") - return - finally: - del diarizer_model - torch.cuda.empty_cache() + if self._is_cancelled: + logging.debug("Speaker identification worker: Cancelled at step 1") + return - # Step 4 - Reading timestamps <> Speaker Labels mapping - self.progress_update.emit(_("7/8 Mapping speakers to transcripts")) + # Step 1 - Get transcript + # TODO - Add detected language to the transcript, detect and store separately in metadata + # Will also be relevant for template parsing of transcript file names + # - See diarize.py for example on how to get this info from whisper transcript, maybe other whisper models also have it + language = self.transcription.language if self.transcription.language else "en" - wsm = get_words_speaker_mapping(word_timestamps, speaker_ts, "start") - - if language in punct_model_langs: - # restoring punctuation in the transcript to help realign the sentences - punct_model = PunctuationModel(model="kredor/punctuate-all") - - words_list = list(map(lambda x: x["word"], wsm)) - - labled_words = punct_model.predict(words_list, chunk_size=230) - - ending_puncts = ".?!。!?" - model_puncts = ".,;:!?。!?" - - # We don't want to punctuate U.S.A. with a period. Right? - is_acronym = lambda x: re.fullmatch(r"\b(?:[a-zA-Z]\.){2,}", x) - - for word_dict, labeled_tuple in zip(wsm, labled_words): - word = word_dict["word"] - if ( - word - and labeled_tuple[1] in ending_puncts - and (word[-1] not in model_puncts or is_acronym(word)) - ): - word += labeled_tuple[1] - if word.endswith(".."): - word = word.rstrip(".") - word_dict["word"] = word - - else: - logging.warning( - f"Punctuation restoration is not available for {language} language." - " Using the original punctuation." + segments = self.transcription_service.get_transcription_segments( + transcription_id=self.transcription.id_as_uuid ) - wsm = get_realigned_ws_mapping_with_punctuation(wsm) - ssm = get_sentences_speaker_mapping(wsm, speaker_ts) + full_transcript = "".join(segment.text for segment in segments) - self.progress_update.emit(_("8/8 Identification done")) - self.finished.emit(ssm) + if self._is_cancelled: + logging.debug("Speaker identification worker: Cancelled at step 2") + return + + self.progress_update.emit(_("2/8 Loading audio")) + audio_waveform = faster_whisper.decode_audio(self.transcription.file) + + # Step 2 - Forced alignment + force_cpu = os.getenv("BUZZ_FORCE_CPU", "false") + use_cuda = torch.cuda.is_available() and force_cpu == "false" + device = "cuda" if use_cuda else "cpu" + torch_dtype = torch.float16 if use_cuda else torch.float32 + + logging.debug(f"Speaker identification worker: Using device={device}") + + if self._is_cancelled: + logging.debug("Speaker identification worker: Cancelled at step 3") + return + + self.progress_update.emit(_("3/8 Loading alignment model")) + alignment_model, alignment_tokenizer = load_alignment_model( + device, + dtype=torch_dtype, + ) + + if self._is_cancelled: + logging.debug("Speaker identification worker: Cancelled at step 4") + return + + self.progress_update.emit(_("4/8 Processing audio")) + emissions, stride = generate_emissions( + alignment_model, + torch.from_numpy(audio_waveform) + .to(alignment_model.dtype) + .to(alignment_model.device), + batch_size=8, + ) + + # Clean up alignment model + del alignment_model + alignment_model = None + torch.cuda.empty_cache() + + if self._is_cancelled: + logging.debug("Speaker identification worker: Cancelled at step 5") + return + + self.progress_update.emit(_("5/8 Preparing transcripts")) + tokens_starred, text_starred = preprocess_text( + full_transcript, + romanize=True, + language=langs_to_iso[language], + ) + + segments, scores, blank_token = get_alignments( + emissions, + tokens_starred, + alignment_tokenizer, + ) + + spans = get_spans(tokens_starred, segments, blank_token) + + word_timestamps = postprocess_results(text_starred, spans, stride, scores) + + if self._is_cancelled: + logging.debug("Speaker identification worker: Cancelled at step 6") + return + + # Step 3 - Diarization + self.progress_update.emit(_("6/8 Identifying speakers")) + + # Silence NeMo's verbose logging + logging.getLogger("nemo_logging").setLevel(logging.ERROR) + try: + # Also try to silence NeMo's internal logging system + from nemo.utils import logging as nemo_logging + nemo_logging.setLevel(logging.ERROR) + except (ImportError, AttributeError): + pass + + logging.debug("Speaker identification worker: Creating diarizer model") + diarizer_model = MSDDDiarizer(device) + logging.debug("Speaker identification worker: Running diarization") + speaker_ts = diarizer_model.diarize(torch.from_numpy(audio_waveform).unsqueeze(0)) + logging.debug("Speaker identification worker: Diarization complete") + + # Clean up diarizer model immediately after use + del diarizer_model + diarizer_model = None + torch.cuda.empty_cache() + + if self._is_cancelled: + logging.debug("Speaker identification worker: Cancelled at step 7") + return + + # Step 4 - Reading timestamps <> Speaker Labels mapping + self.progress_update.emit(_("7/8 Mapping speakers to transcripts")) + + wsm = get_words_speaker_mapping(word_timestamps, speaker_ts, "start") + + if language in punct_model_langs: + # restoring punctuation in the transcript to help realign the sentences + punct_model = PunctuationModel(model="kredor/punctuate-all") + + words_list = list(map(lambda x: x["word"], wsm)) + + labled_words = punct_model.predict(words_list, chunk_size=230) + + ending_puncts = ".?!。!?" + model_puncts = ".,;:!?。!?" + + # We don't want to punctuate U.S.A. with a period. Right? + is_acronym = lambda x: re.fullmatch(r"\b(?:[a-zA-Z]\.){2,}", x) + + for word_dict, labeled_tuple in zip(wsm, labled_words): + word = word_dict["word"] + if ( + word + and labeled_tuple[1] in ending_puncts + and (word[-1] not in model_puncts or is_acronym(word)) + ): + word += labeled_tuple[1] + if word.endswith(".."): + word = word.rstrip(".") + word_dict["word"] = word + + else: + logging.warning( + f"Punctuation restoration is not available for {language} language." + " Using the original punctuation." + ) + + wsm = get_realigned_ws_mapping_with_punctuation(wsm) + ssm = get_sentences_speaker_mapping(wsm, speaker_ts) + + logging.debug("Speaker identification worker: Finished successfully") + self.progress_update.emit(_("8/8 Identification done")) + self.finished.emit(ssm) + + except Exception as e: + logging.error(f"Speaker identification worker: Error - {e}", exc_info=True) + self.progress_update.emit(_("0/0 Error identifying speakers")) + self.error.emit(str(e)) + # Emit empty list so the UI can reset properly + self.finished.emit([]) + + finally: + # Ensure cleanup happens regardless of how we exit + logging.debug("Speaker identification worker: Cleaning up resources") + if diarizer_model is not None: + try: + del diarizer_model + except Exception: + pass + if alignment_model is not None: + try: + del alignment_model + except Exception: + pass + torch.cuda.empty_cache() class SpeakerIdentificationWidget(QWidget): @@ -350,6 +415,11 @@ class SpeakerIdentificationWidget(QWidget): def on_identify_button_clicked(self): self.step_1_button.setEnabled(False) + # Clean up any existing thread before starting a new one + self._cleanup_thread() + + logging.debug("Speaker identification: Starting identification thread") + self.thread = QThread() self.worker = IdentificationWorker( self.transcription, @@ -357,14 +427,26 @@ class SpeakerIdentificationWidget(QWidget): ) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.run) - self.worker.finished.connect(self.thread.quit) - self.worker.finished.connect(self.worker.deleteLater) - self.thread.finished.connect(self.thread.deleteLater) - self.worker.finished.connect(self.on_identification_finished) + self.worker.finished.connect(self._on_thread_finished) self.worker.progress_update.connect(self.on_progress_update) + self.worker.error.connect(self.on_identification_error) self.thread.start() + def _on_thread_finished(self, result): + """Handle thread completion and cleanup.""" + logging.debug("Speaker identification: Thread finished") + if self.thread is not None: + self.thread.quit() + self.thread.wait(5000) + self.on_identification_finished(result) + + def on_identification_error(self, error_message): + """Handle identification error.""" + logging.error(f"Speaker identification error: {error_message}") + self.step_1_button.setEnabled(True) + self.progress_bar.setValue(0) + def on_progress_update(self, progress): self.progress_label.setText(progress) @@ -383,6 +465,11 @@ class SpeakerIdentificationWidget(QWidget): def on_identification_finished(self, result): self.identification_result = result + # Handle empty results (error case) + if not result: + logging.debug("Speaker identification: Empty result received") + return + unique_speakers = {entry['speaker'] for entry in result} while self.speaker_preview_row.count(): @@ -526,4 +613,30 @@ class SpeakerIdentificationWidget(QWidget): def closeEvent(self, event): self.hide() + # Stop media player + self.player.stop() + if self.player_timer: + self.player_timer.stop() + + # Clean up thread if running + self._cleanup_thread() + super().closeEvent(event) + + def _cleanup_thread(self): + """Properly clean up the worker thread.""" + if self.worker is not None: + # Request cancellation first + self.worker.cancel() + + if self.thread is not None and self.thread.isRunning(): + logging.debug("Speaker identification: Stopping running thread") + self.thread.quit() + if not self.thread.wait(10000): # Wait up to 10 seconds + logging.warning("Speaker identification: Thread did not quit, terminating") + self.thread.terminate() + if not self.thread.wait(2000): + logging.error("Speaker identification: Thread failed to terminate") + + self.thread = None + self.worker = None From 7af79b6bc32924c93a9e224da5a457c74981de10 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Wed, 17 Dec 2025 10:00:54 +0200 Subject: [PATCH 25/73] Fix for SSL errors on model downloading (#1316) --- buzz/file_transcriber_queue_worker.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index 32f83591..de059635 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -1,11 +1,24 @@ import logging import multiprocessing +import os import queue +import ssl import sys from pathlib import Path from typing import Optional, Tuple, List, Set from uuid import UUID +# Fix SSL certificate verification for bundled applications (macOS, Windows) +# This must be done before importing demucs which uses torch.hub with urllib +try: + import certifi + os.environ.setdefault('SSL_CERT_FILE', certifi.where()) + os.environ.setdefault('SSL_CERT_DIR', os.path.dirname(certifi.where())) + # Also update the default SSL context for urllib + ssl._create_default_https_context = lambda: ssl.create_default_context(cafile=certifi.where()) +except ImportError: + pass + from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, Qt # Patch subprocess for demucs to prevent console windows on Windows @@ -111,7 +124,6 @@ class FileTranscriberQueueWorker(QObject): self.task_progress.emit(self.current_task, int(progress["segment_offset"] * 100) / int(progress["audio_length"] * 100)) try: - # This will fail on Windows 10 and Mac with SSL cert error separator = demucsApi.Separator( progress=True, callback=separator_progress_callback, From 4dbde2b9486a7d3af6140a4d42cb3ca7b45db6a8 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Thu, 18 Dec 2025 20:49:39 +0200 Subject: [PATCH 26/73] 491 add mms (#1313) --- buzz/buzz.py | 4 + buzz/cuda_setup.py | 133 +++++++ buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/en_US/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 137 +++++--- buzz/locale/nl/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 128 ++++--- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 128 ++++--- buzz/model_loader.py | 74 ++++ buzz/settings/settings.py | 1 + buzz/store/keyring_store.py | 3 - buzz/transcriber/recording_transcriber.py | 36 +- buzz/transcriber/whisper_file_transcriber.py | 44 ++- buzz/transformers_whisper.py | 332 ++++++++++++++++-- buzz/widgets/application.py | 7 + .../general_preferences_widget.py | 27 +- .../hugging_face_search_line_edit.py | 3 +- .../transcriber/mms_language_line_edit.py | 48 +++ .../transcription_options_group_box.py | 53 ++- .../transcription_view_mode_tool_button.py | 1 + .../transcription_viewer_widget.py | 6 +- docs/docs/faq.md | 6 + docs/docs/installation.md | 23 +- docs/docs/preferences.md | 2 + pyproject.toml | 15 + .../io.github.chidiwilliams.Buzz.metainfo.xml | 19 + tests/conftest.py | 11 +- .../transcriber/transformers_whisper_test.py | 46 +++ .../whisper_file_transcriber_test.py | 1 - ...st.py => transformers_transcriber_test.py} | 6 +- uv.lock | 235 ++++++++----- 39 files changed, 2049 insertions(+), 888 deletions(-) create mode 100644 buzz/cuda_setup.py create mode 100644 buzz/widgets/transcriber/mms_language_line_edit.py create mode 100644 tests/transcriber/transformers_whisper_test.py rename tests/{transformers_whisper_test.py => transformers_transcriber_test.py} (71%) diff --git a/buzz/buzz.py b/buzz/buzz.py index 09a63d9f..d4a7a34b 100644 --- a/buzz/buzz.py +++ b/buzz/buzz.py @@ -7,6 +7,10 @@ import sys from pathlib import Path from typing import TextIO +# Set up CUDA library paths before any torch imports +# This must happen before platformdirs or any other imports that might indirectly load torch +import buzz.cuda_setup # noqa: F401 + from platformdirs import user_log_dir, user_cache_dir, user_data_dir # Will download all Huggingface data to the app cache directory diff --git a/buzz/cuda_setup.py b/buzz/cuda_setup.py new file mode 100644 index 00000000..09ba0731 --- /dev/null +++ b/buzz/cuda_setup.py @@ -0,0 +1,133 @@ +""" +CUDA library path setup for nvidia packages installed via pip. + +This module must be imported BEFORE any torch or CUDA-dependent libraries are imported. +It handles locating and loading CUDA libraries (cuDNN, cuBLAS, etc.) from the nvidia +pip packages. + +On Windows: Uses os.add_dll_directory() to add library paths +On Linux: Uses ctypes to preload libraries (LD_LIBRARY_PATH is read at process start) +On macOS: No action needed (CUDA not supported) +""" + +import ctypes +import logging +import os +import platform +import sys +from pathlib import Path + + +logger = logging.getLogger(__name__) + + +def _get_nvidia_package_lib_dirs() -> list[Path]: + """Find all nvidia package library directories in site-packages.""" + lib_dirs = [] + + # Find site-packages directories + site_packages_dirs = [] + for path in sys.path: + if "site-packages" in path: + site_packages_dirs.append(Path(path)) + + # Also check relative to the current module for frozen apps + if getattr(sys, "frozen", False): + # For frozen apps, check the _internal directory + frozen_lib_dir = Path(sys._MEIPASS) if hasattr(sys, "_MEIPASS") else Path(sys.executable).parent + nvidia_dir = frozen_lib_dir / "nvidia" + if nvidia_dir.exists(): + for pkg_dir in nvidia_dir.iterdir(): + if pkg_dir.is_dir(): + lib_subdir = pkg_dir / "lib" + if lib_subdir.exists(): + lib_dirs.append(lib_subdir) + # Some packages have bin directory on Windows + bin_subdir = pkg_dir / "bin" + if bin_subdir.exists(): + lib_dirs.append(bin_subdir) + + # Check each site-packages for nvidia packages + for sp_dir in site_packages_dirs: + nvidia_dir = sp_dir / "nvidia" + if nvidia_dir.exists(): + for pkg_dir in nvidia_dir.iterdir(): + if pkg_dir.is_dir(): + lib_subdir = pkg_dir / "lib" + if lib_subdir.exists(): + lib_dirs.append(lib_subdir) + # Some packages have bin directory on Windows + bin_subdir = pkg_dir / "bin" + if bin_subdir.exists(): + lib_dirs.append(bin_subdir) + + return lib_dirs + + +def _setup_windows_dll_directories(): + """Add nvidia library directories to Windows DLL search path.""" + lib_dirs = _get_nvidia_package_lib_dirs() + for lib_dir in lib_dirs: + try: + os.add_dll_directory(str(lib_dir)) + logger.debug(f"Added DLL directory: {lib_dir}") + except (OSError, AttributeError) as e: + logger.debug(f"Could not add DLL directory {lib_dir}: {e}") + + +def _preload_linux_libraries(): + """Preload CUDA libraries on Linux using ctypes. + + On Linux, LD_LIBRARY_PATH is only read at process start, so we need to + manually load the libraries using ctypes before torch tries to load them. + """ + lib_dirs = _get_nvidia_package_lib_dirs() + + # Libraries to skip - NVBLAS requires special configuration and causes issues + skip_patterns = ["libnvblas"] + + loaded_libs = set() + + for lib_dir in lib_dirs: + if not lib_dir.exists(): + continue + + # Find all .so files in the directory + for lib_file in sorted(lib_dir.glob("*.so*")): + if lib_file.name in loaded_libs: + continue + if lib_file.is_symlink() and not lib_file.exists(): + continue + + # Skip problematic libraries + if any(pattern in lib_file.name for pattern in skip_patterns): + logger.debug(f"Skipping library: {lib_file}") + continue + + try: + # Use RTLD_GLOBAL so symbols are available to other libraries + ctypes.CDLL(str(lib_file), mode=ctypes.RTLD_GLOBAL) + loaded_libs.add(lib_file.name) + logger.debug(f"Preloaded library: {lib_file}") + except OSError as e: + # Some libraries may have missing dependencies, that's ok + logger.debug(f"Could not preload {lib_file}: {e}") + + +def setup_cuda_libraries(): + """Set up CUDA library paths for the current platform. + + This function should be called as early as possible, before any torch + or CUDA-dependent libraries are imported. + """ + system = platform.system() + + if system == "Windows": + _setup_windows_dll_directories() + elif system == "Linux": + _preload_linux_libraries() + # macOS doesn't have CUDA support, so nothing to do + + +# Auto-run setup when this module is imported +setup_cuda_libraries() diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index e3c3ba24..dcb31aa1 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -176,25 +176,39 @@ msgid "Live recording mode" msgstr "Mode d'enregistrament en directe" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "Utilitza només la CPU i desactiveu l'acceleració de la GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Establiu això si els models més grans no s'ajusten a la memòria de la GPU i " "Buzz es bloqueja" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "Desactiva la GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "Prova de clau OpenAI API" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -202,11 +216,11 @@ msgstr "" "La vostra clau API és vàlida. Buzz utilitzarà aquesta clau per realitzar " "transcripcions de l'API de Whisper i traduccions de la IA." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "Clau API no vàlida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -214,11 +228,11 @@ msgstr "" "L'API només admet caràcters base64 (A-Za-z0-9+/).-). Altres caràcters de la " "clau API poden causar errors." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Selecciona la carpeta d'exportació" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -316,8 +330,8 @@ msgstr "Descàrrega fallida" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Error" @@ -335,28 +349,40 @@ msgstr "Atura" msgid "Detect Language" msgstr "Detecta l'idioma" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Executa" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Model:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" "L'ús per primera vegada d'un model pot trigar diversos minuts a carregar-se." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "Clau API:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Tasca:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Idioma:" @@ -601,13 +627,13 @@ msgid "End" msgstr "Finalitza" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Text" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -617,7 +643,7 @@ msgstr "Traducció" msgid "View" msgstr "Veure" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Marqua de temps" @@ -625,65 +651,65 @@ msgstr "Marqua de temps" msgid "Export" msgstr "Exporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Traduir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "Cerca" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/amaga la barra de cerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "Cerca:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "Introduïu el text a cercar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "Coincidència anterior (Maj+Retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 #, fuzzy msgid "Next match (Ctrl+Enter)" msgstr "Coincidència següent (retorn)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "Neteja" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "Controls de reproducció:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "Segment de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Activa/desactiva el bucle en fer clic als segments de transcripció" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "Segueix l'àudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -691,44 +717,44 @@ msgstr "" "Activa/desactiva seguint la posició d'àudio actual a la transcripció. Quan " "està activada, es desplaça automàticament al text actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "Desplaça't fins a l'actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "Desplaçar-se fins al text que es parla actualment" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "1 de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr " coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "No s'ha trobat cap coincidència" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr " de més de 100 coincidències" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "Clau API necessària" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Introduïu la clau API d'OpenAI a les preferències" @@ -888,14 +914,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "No s'ha pogut desar la clau OpenAI API a l'anell de claus" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" "El servidor Whisper no s'ha pogut iniciar. Consulteu els registres per " "obtenir més informació." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1269,15 +1295,15 @@ msgstr "Sundanès" msgid "Cantonese" msgstr "Cantonès" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "S'ha produït un error de connexió" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "Començant Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Cancel·la la transcripció" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index 08302e50..5773e1a3 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -176,23 +176,37 @@ msgid "Live recording mode" msgstr "Live optagelsesmode" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "OpenAI API Nøgle test" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -200,12 +214,12 @@ msgstr "" "Din API nøgle er gyldig. Buzz vil benytte nøglen til at anvende Whisper API " "transkription og AI oversættelser." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 #, fuzzy msgid "Invalid API key" msgstr "Ugyldig API-nøgle" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -213,11 +227,11 @@ msgstr "" "API supporterer kun base64 tegn (A-Za-z0-9+/=_-). Andre tegn i API-nøglen " "kan guve fejl. " -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Vælg eksport-mappe" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -315,8 +329,8 @@ msgstr "Download mislykkedes" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Fejl" @@ -334,27 +348,39 @@ msgstr "Stop" msgid "Detect Language" msgstr "Detekter sprog" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Kør" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Model:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "Først gang kan brug af en model tage flere minutter at indlæse." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "API-nøgle:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Opgave:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Sprog:" @@ -598,13 +624,13 @@ msgid "End" msgstr "Slut" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Tekst" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -614,7 +640,7 @@ msgstr "Oversættelse" msgid "View" msgstr "Vis" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Tidsstempler" @@ -622,107 +648,107 @@ msgstr "Tidsstempler" msgid "Export" msgstr "Eksporter" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Oversæt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Behandel størrelse" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "API-nøgle påkrævet" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Indtast venligst OpenAI API-nøgle i indstillinger" @@ -882,12 +908,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Kan ikke gemme OpenAI API-nøgle i nøgleringen" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1259,15 +1285,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Der er opstået en forbindelsesfejl" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Afbryd transkription" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index eb751acc..979badb1 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -176,23 +176,37 @@ msgid "Live recording mode" msgstr "Live-Aufnahmemodus" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "OpenAI-API-Schlüssel Test" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -200,11 +214,11 @@ msgstr "" "Ihr API-Schlüssel ist gültig. Buzz verwendet diesen Schlüssel, um Whisper-" "API-Transkriptionen und KI-Übersetzungen durchzuführen." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "Ungültiger API-Schlüssel" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -212,11 +226,11 @@ msgstr "" "Die API unterstützt nur Base64-Zeichen (A-Za-z0-9+/=_-). Andere Zeichen im " "API-Schlüssel können Fehler verursachen." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Exportordner auswählen" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -315,8 +329,8 @@ msgstr "Der Download ist fehlgeschlagen" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Fehler" @@ -334,29 +348,41 @@ msgstr "Stoppen" msgid "Detect Language" msgstr "Sprache erkennen" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Ausführen" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Modell:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" "Bei der ersten Verwendung eines Modells kann das Laden mehrere Minuten " "dauern." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "API-Schlüssel:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Aufgabe:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Sprache:" @@ -599,13 +625,13 @@ msgid "End" msgstr "Ende" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Text" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -615,7 +641,7 @@ msgstr "Übersetzung" msgid "View" msgstr "Anzeigen" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Zeitstempel" @@ -623,107 +649,107 @@ msgstr "Zeitstempel" msgid "Export" msgstr "Export" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Übersetzen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Größe ändern" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "API-Schlüssel erforderlich" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Bitte geben Sie den OpenAI-API-Schlüssel in den Einstellungen ein" @@ -884,12 +910,12 @@ msgstr "" "Der OpenAI-API-Schlüssel kann nicht im Schlüsselbund gespeichert werden" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1261,15 +1287,15 @@ msgstr "Sundanesisch" msgid "Cantonese" msgstr "Kantonesisch" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Ein Verbindungsfehler ist aufgetreten" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Transkription abbrechen" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index e604ee25..c2a09010 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -175,43 +175,57 @@ msgid "Live recording mode" msgstr "" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -306,8 +320,8 @@ msgstr "" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "" @@ -325,27 +339,39 @@ msgstr "" msgid "Detect Language" msgstr "" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "" @@ -582,13 +608,13 @@ msgid "End" msgstr "" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -598,7 +624,7 @@ msgstr "" msgid "View" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "" @@ -606,107 +632,107 @@ msgstr "" msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -862,12 +888,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1238,15 +1264,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 msgid "Starting transcription..." msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index 69fd2c3a..e7757df4 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -178,25 +178,39 @@ msgid "Live recording mode" msgstr "Modo de grabación en directo" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "Usa solo CPU y desactiva la aceleración de GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Configure esto si los modelos más grandes no se ajustan a la memoria de su " "GPU y Buzz se bloquea" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "Desactivar GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "Prueba de la clave API de OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -204,11 +218,11 @@ msgstr "" "Tu clave API es válida. Buzz usará esta clave para realizar transcripciones " "de la API de Whisper y traducciones de IA." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "Clave API no válida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -216,11 +230,11 @@ msgstr "" "La API solo admite caracteres base64 (A-Za-z0-9+/=_-). Otros caracteres de " "la clave de API pueden causar errores." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Seleccione Exportar carpeta" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -322,8 +336,8 @@ msgstr "Descarga fallida" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Error" @@ -344,32 +358,44 @@ msgstr "Detener" msgid "Detect Language" msgstr "Detectar idioma" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + # automatic translation #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Ejecutar" # automatic translation -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Modelo:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" "El uso por primera vez de un modelo puede tardar varios minutos en cargarse." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "Clave API:" # automatic translation -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Tarea:" # automatic translation -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Idioma:" @@ -638,14 +664,14 @@ msgid "End" msgstr "Fin" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Texto" # automatic translation #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -655,7 +681,7 @@ msgstr "Traducción" msgid "View" msgstr "Ver" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Marcas de tiempo" @@ -663,67 +689,67 @@ msgstr "Marcas de tiempo" msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Traducir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Cambiar el tamaño" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "Buscar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar barra de búsqueda (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "Encontrar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "Introducir texto para encontrar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "Coincidencia anterior (Mayús+Intro)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 #, fuzzy msgid "Next match (Ctrl+Enter)" msgstr "Siguiente coincidencia (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "Limpiar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "Controles de reproducción:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "Segmento de bucle" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Activar/desactivar la reproducción en bucle al hacer clic en segmentos de la " "transcripción" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "Seguir audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -732,44 +758,44 @@ msgstr "" "transcripción. Cuando está activado, se desplaza automáticamente al texto " "actual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "Desplácese hasta Actual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "Desplazarse hasta el texto hablado actualmente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "1 de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr " coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "No se encontraron coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr " de 100+ coincidencias" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "Clave de API requerida" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Ingrese la clave API de OpenAI en las preferencias" @@ -939,14 +965,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "No se puede guardar la clave de la API de OpenAI en el llavero" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" "El servidor Whisper no se pudo iniciar. Consulta los registros para obtener " "más detalles." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1321,16 +1347,16 @@ msgstr "Sundanés" msgid "Cantonese" msgstr "Cantonés" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Se ha producido un error de conexión" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "Iniciando Whisper.cpp..." # automatic translation -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Cancelar transcripción" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index 53f6279e..2bcbd210 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -176,25 +176,39 @@ msgid "Live recording mode" msgstr "Modalità di registrazione in diretta" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "Utilizza solo la CPU e disattiva l'accelerazione GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Imposta questa opzione se i modelli più grandi non si adattano alla memoria " "della tua GPU e Buzz si blocca" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "Disabilita GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "Test della chiave API OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -202,11 +216,11 @@ msgstr "" "La tua chiave API è valida. Buzz utilizzerà questa chiave per eseguire le " "trascrizioni API Whisper e le traduzioni AI." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "Chiave API non valida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -214,11 +228,11 @@ msgstr "" "L'API supporta solo caratteri base64 (A-Za-z0-9+/=). Altri caratteri nella " "chiave API potrebbero causare errori." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Seleziona la cartella di esportazione" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -316,8 +330,8 @@ msgstr "Download non riuscito" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Errore" @@ -335,29 +349,41 @@ msgstr "Arresta" msgid "Detect Language" msgstr "Rileva la lingua" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Avvia" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Modello:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" "Il caricamento di un modello al primo utilizzo potrebbe richiedere diversi " "minuti." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "Chiave API:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Compito:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Lingua:" @@ -602,13 +628,13 @@ msgid "End" msgstr "Fine" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Testo" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -618,7 +644,7 @@ msgstr "Traduzione" msgid "View" msgstr "Visualizza" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Timestamp" @@ -626,66 +652,66 @@ msgstr "Timestamp" msgid "Export" msgstr "Esporta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Tradurre" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Ridimensionare" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "Trova" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostra/Nascondi barra di ricerca (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "Trova:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "Inserisci il testo per trovare..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "Corrispondenza precedente (Maiusc+Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 #, fuzzy msgid "Next match (Ctrl+Enter)" msgstr "Prossima corrispondenza (Invio)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "Elimina" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "Controlli di riproduzione:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "Ciclo di segmento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" "Abilita/disabilita il loop quando si fa clic sui segmenti della trascrizione" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "Segui Audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -694,44 +720,44 @@ msgstr "" "trascrizione. Quando abilitato, scorre automaticamente fino al testo " "corrente." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "Scorri fino al Corrente" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "Scorrere fino al testo attualmente pronunciato" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "1 di 100+ corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "1 di" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "Nessuna corrispondenza trovata" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr " di oltre 100 corrispondenze" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr " di " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "Chiave API richiesta" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Inserisci la chiave API OpenAI nelle preferenze" @@ -891,13 +917,13 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Impossibile salvare la chiave API OpenAI nel portachiavi" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" "Impossibile avviare il server Whisper. Controllare i log per i dettagli." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1271,15 +1297,15 @@ msgstr "Sundanese" msgid "Cantonese" msgstr "Cantonese" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Si è verificato un errore di connessione" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "Avvio di Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 msgid "Starting transcription..." msgstr "Inizio trascrizione..." diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index a683e72b..83ee5f76 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -174,23 +174,37 @@ msgid "Live recording mode" msgstr "ライブ録音" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "OpenAI APIキー テスト" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -198,22 +212,22 @@ msgstr "" "あなたのAPIキーは有効です。Buzzはこのキーを使ってWhisper APIの書き起こしとAI" "翻訳を行います。" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 #, fuzzy msgid "Invalid API key" msgstr "OpenAI APIキー" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "出力フォルダを選択" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -311,8 +325,8 @@ msgstr "ダウンロード失敗" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "エラー" @@ -330,27 +344,39 @@ msgstr "停止する" msgid "Detect Language" msgstr "自動検出" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "実行" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "モデル:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "APIキー:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "タスク:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "言語:" @@ -594,13 +620,13 @@ msgid "End" msgstr "終了" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "テキスト" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -610,7 +636,7 @@ msgstr "翻訳" msgid "View" msgstr "表示" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "タイムスタンプ" @@ -618,107 +644,107 @@ msgstr "タイムスタンプ" msgid "Export" msgstr "出力" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "翻訳" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "リサイズ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "APIキーが必要" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "設定画面でOpenAI APIキーを入力してください" @@ -877,12 +903,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "OpenAI API キーをkeyringに保存できません" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1254,15 +1280,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "接続エラーが発生しました" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "文字起こしをキャンセルする" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index 1d126d07..a34f5558 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" -"PO-Revision-Date: 2025-12-13 10:52+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"PO-Revision-Date: 2025-12-14 09:03+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -177,25 +177,41 @@ msgstr "" "režīms" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "Izmantot 8bitu kvantizāciju, lai samazinātu nepieciešamo atmiņu" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" +"Izmantojams Huggingface un Faster whisper modeļiem, lai samazinātu " +"nepieciešamo atmiņas daudzumu, nedaudz zaudējot atpazīšanas kvalitāti." + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "Optimizēt GPU atmiņu" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "Izmantot tikai CPU un deaktivēt GPU paātrināšanu" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Aktivizējiet šo, ja lielāki modeļi neietilpst jūsu video kartes atmiņā un " "Buzz avarē" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "Deaktivēt GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "OpenAI API atslēgas pārbaude" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -203,11 +219,11 @@ msgstr "" "Jūsu API atslēga ir derīga. Buzz izmantos to runas atpazīšanai ar Whisper " "API un tulkošanai." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "Nederīga API atslēga" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -215,11 +231,11 @@ msgstr "" "API atbalsta tikai base64 simbolus (A-Za-z0-9+/=_-). Citi simboli API " "atslēgā var radīt kļūdas." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Izvēlieties mapi kurā eksportēt" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -275,7 +291,7 @@ msgstr "Veids" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:83 msgid "Huggingface ID of a Faster whisper model" -msgstr "Faster Whisper modeļa Huggingface ID" +msgstr "Faster whisper modeļa Huggingface ID" #: buzz/widgets/preferences_dialog/models_preferences_widget.py:95 msgid "Download" @@ -317,8 +333,8 @@ msgstr "Lejupielāde neizdevās" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Kļūda" @@ -336,27 +352,42 @@ msgstr "Apturēt" msgid "Detect Language" msgstr "Noteikt valodu" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "piem. eng, fra, deu" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" +"Ievadiet valodas ISO 639-3 kodu (3 burti).\n" +"Piemēram: eng (Angļu), fra (Franču), deu (Vācu),\n" +"spa (Spāņu), lav (Latviešu)" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Apstrādāt" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Modelis:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "Pirmā modeļa ielādes reize var aizņemt pat vairākas minūtes." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "API atslēga:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Uzdevums:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Valoda:" @@ -600,13 +631,13 @@ msgid "End" msgstr "Beigas" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Teksts" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -616,7 +647,7 @@ msgstr "Tulkojums" msgid "View" msgstr "Skats" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Laiks" @@ -624,64 +655,64 @@ msgstr "Laiks" msgid "Export" msgstr "Eksportēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Tulkot" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Mainīt garumu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "Noteikt runātājus" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "Meklēt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Rādīt/Slēpt meklēšanas joslu (Ctrl+F)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "Meklēt:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "Ievadiet meklējamo..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "Iepriekšējais rezultāts (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "Nākamais rezultāts (Ctrl+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "Notīrīt" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "Atskaņošana:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "Atkārtot segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Nosaka vai atkārtot izvēlēto segmentu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "Sekot audio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -689,44 +720,44 @@ msgstr "" "Nosaka vai atskaņojot audio iezīmētajam segmentam vajadzētu automātiski " "sekot tam kas tiek atskaņots." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "Pāriet uz tekošo" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "Pāriet uz šobrīd atskaņojamo tesktu" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "1 no 100+ " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "1 no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr " " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "Nekas nav atrasts" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr " no 100+" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr " no " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "API atslēgas kļūda" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Lūdzu ievadiet OpenAI API atslēgu iestatījumos" @@ -884,14 +915,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" "Whisper serverim neizdevās ieslēgties. Lūdzu pārbaudiet lietotnes žurnāla " "ierakstus." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1265,15 +1296,15 @@ msgstr "Sundāņu" msgid "Cantonese" msgstr "Kantonas" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Notika savienojuma kļūda" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "Palaiž Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 msgid "Starting transcription..." msgstr "Sāk atpazīšanu..." diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index eb8dfa6f..7f35c3d6 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -179,23 +179,37 @@ msgid "Live recording mode" msgstr "Live-opnamemodus" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "OpenAI-api-sleuteltest" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -203,11 +217,11 @@ msgstr "" "De api-sleutel is geldig. Buzz zal deze sleutel gebruiken om transcripties " "en AI-vertalingen op te vragen bij Whisper." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "Ongeldige api-sleutel" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -215,11 +229,11 @@ msgstr "" "De api ondersteunt alleen base64-tekens (A–Za–z0–9+/=_-). Andere tekens " "kunnen problemen veroorzaken." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Kies een exportmap" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -317,8 +331,8 @@ msgstr "Het downloaden is mislukt" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Foutmelding" @@ -336,29 +350,41 @@ msgstr "Stoppen" msgid "Detect Language" msgstr "Taal herkennen" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Uitvoeren" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Model:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" "Let op: de eerste keer kan het enkele minuten duren voordat het model " "geladen is." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "Api-sleutel:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Taak:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Taal:" @@ -599,13 +625,13 @@ msgid "End" msgstr "Einde" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Tekst" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -615,7 +641,7 @@ msgstr "Vertaling" msgid "View" msgstr "Bekijken" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Tijdstippen" @@ -623,107 +649,107 @@ msgstr "Tijdstippen" msgid "Export" msgstr "Exporteren" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Vertalen" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Grootte" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "Api-sleutel vereist" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Voer de OpenAI-api-sleutel in in de instellingen" @@ -883,12 +909,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "De OpenAI-api-sleutel kan niet worden bewaard in de sleutelbos" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1260,15 +1286,15 @@ msgstr "Soedanees" msgid "Cantonese" msgstr "Kantonees" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Er is een verbindingsfout opgetreden" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Transcriptie wissen" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index fb8bb348..f1fea38d 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -178,44 +178,58 @@ msgid "Live recording mode" msgstr "Nagrywanie na żywo" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 #, fuzzy msgid "Invalid API key" msgstr "Nieprawidłowy URL" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -318,8 +332,8 @@ msgstr "Pobrany" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Błąd" @@ -337,27 +351,39 @@ msgstr "Zatrzymaj" msgid "Detect Language" msgstr "Wykryj język" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Rozpocznij" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Model:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Zadanie:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Język:" @@ -606,13 +632,13 @@ msgid "End" msgstr "Zakończ" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Tekst" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 #, fuzzy @@ -623,7 +649,7 @@ msgstr "Nowa transkrypcja" msgid "View" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "" @@ -631,107 +657,107 @@ msgstr "" msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -895,12 +921,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1273,15 +1299,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Anuluj transkrypcję" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 30772b58..812a0280 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -176,24 +176,38 @@ msgid "Live recording mode" msgstr "Modo de gravação ao vivo" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "Usar somente a CPU e desabilitar aceleração por GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" "Marque isso se modelos maiores não couberem na memória da GPU e o Buzz travar" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "Desabilitar GPU" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "Teste da Chave API OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -201,11 +215,11 @@ msgstr "" "Sua chave API é válida. O Buzz usará esta chave para realizar transcrições " "API Whisper e traduções de IA." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "Chave API inválida" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." @@ -213,11 +227,11 @@ msgstr "" "A API suporta apenas caracteres base64 (A-Za-z0-9+/=_-). Outros caracteres " "na chave API podem causar erros." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Selecionar Pasta de Exportação" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -315,8 +329,8 @@ msgstr "Falha ao baixar" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Erro" @@ -334,27 +348,39 @@ msgstr "Parar" msgid "Detect Language" msgstr "Detectar Idioma" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Executar" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Modelo:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "O primeiro uso de um modelo pode levar vários minutos para carregar." -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "Chave API:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Tarefa:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Idioma:" @@ -599,13 +625,13 @@ msgid "End" msgstr "Fim" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Texto" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -615,7 +641,7 @@ msgstr "Tradução" msgid "View" msgstr "Visualizar" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Marcações de tempo" @@ -623,65 +649,65 @@ msgstr "Marcações de tempo" msgid "Export" msgstr "Exportar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Traduzir" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "Redimensionar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "Procurar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "Mostrar/Ocultar a Barra de Pesquisa" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "Procurar:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "Digite o texto a procurar..." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "Encontro prévio (Shift+Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 #, fuzzy msgid "Next match (Ctrl+Enter)" msgstr "Póximo encontro (Enter)" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "Limpar" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "Controles de Reprodução:" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "Segmento de Loop" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "Habilitar/desabilitar loop ao clicar em segmentos de transcrição" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "Siga o Áudio" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." @@ -689,44 +715,44 @@ msgstr "" "Ativar/desativar a opção de seguir a posição atual do áudio na transcrição. " "Quando ativado, rola automaticamente para o texto atual." -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "Rolar para o Atual" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "Role até o texto falado no momento" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "1 de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "1 de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr " encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "Nada encontrado" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr " de 100+ encontros" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr " de " -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "Chave API Necessária" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Insira a chave API OpenAI nas preferências" @@ -886,12 +912,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Não foi possível salvar a chave da API OpenAI no cofre de chaves" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "Falha ao iniciar o servidor Whisper. Verifique os logs." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1265,15 +1291,15 @@ msgstr "Sundanês" msgid "Cantonese" msgstr "Cantonês" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Ocorreu um erro de conexão" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "Iniciando Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Iniciando transcrição..." diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index 39a56d0b..ecf0eb0b 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -176,23 +176,37 @@ msgid "Live recording mode" msgstr "Живий запис" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "Тест API-ключа OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." @@ -200,22 +214,22 @@ msgstr "" "Ваш API-ключ дійсний. Buzz використає цей ключ для транскрипції з Whisper " "API та перекладу ШІ." -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 #, fuzzy msgid "Invalid API key" msgstr "API-ключ OpenAI" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "Виберіть теку для експорту" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -313,8 +327,8 @@ msgstr "Невдале завантаження" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "Помилка" @@ -332,27 +346,39 @@ msgstr "Зупинити" msgid "Detect Language" msgstr "Визначити мову" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "Запуск" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "Модель:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "API-ключ:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "Завдання:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "Мова:" @@ -596,13 +622,13 @@ msgid "End" msgstr "Кінець" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "Текст" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 msgid "Translation" @@ -612,7 +638,7 @@ msgstr "Переклад" msgid "View" msgstr "Вигляд" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "Позначки часу" @@ -620,107 +646,107 @@ msgstr "Позначки часу" msgid "Export" msgstr "Експорт" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "Перекласти" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "Потрібен API-ключ" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "Будь ласка, введіть API-ключ OpenAI в налаштуваннях" @@ -879,12 +905,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Не вдається додати до звʼязки ключів API-ключ OpenAI" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1256,15 +1282,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "Виникла помилка зʼєднання" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "Скасувати транскрипцію" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index 4e71c3f0..a6053e7c 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -179,33 +179,47 @@ msgid "Live recording mode" msgstr "现场录制模式" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "测试OpenAI API Key" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "您的API密钥有效。Buzz将使用此密钥执行 Whisper API 识别和 AI 翻译。" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "无效的API key" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 #, fuzzy msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " @@ -213,11 +227,11 @@ msgid "" msgstr "" "API只支持 base64字符(A-Za-z0-9+/=)。其他字符在API密钥中可能导致错误。" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "选择输出文件夹" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -321,8 +335,8 @@ msgstr "下载模型失败" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "错误" @@ -340,27 +354,39 @@ msgstr "停止" msgid "Detect Language" msgstr "检测语言" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "开始执行" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "模型:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "首次使用模型可能需要几分钟的时间才能加载" -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "Api Key:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "任务:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "语言:" @@ -607,13 +633,13 @@ msgid "End" msgstr "结束" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "文本" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 #, fuzzy @@ -624,7 +650,7 @@ msgstr "翻译" msgid "View" msgstr "查看" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "时间戳" @@ -632,107 +658,107 @@ msgstr "时间戳" msgid "Export" msgstr "导出" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "翻译" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "调整大小" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "需要API Key" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "请在偏好设置中输入OpenAI API Key" @@ -895,12 +921,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "无法将OpenAI API密钥保存到密钥串" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1273,15 +1299,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "连接发生错误" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "取消识别" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index beb2e501..795a4111 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 08:58+0200\n" +"POT-Creation-Date: 2025-12-17 19:51+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -178,43 +178,57 @@ msgid "Live recording mode" msgstr "現場錄製" #: buzz/widgets/preferences_dialog/general_preferences_widget.py:195 +msgid "Use 8-bit quantization to reduce memory usage" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:199 +msgid "" +"Applies to Huggingface and Faster Whisper models. Reduces GPU memory usage " +"but may slightly decrease transcription quality." +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:203 +msgid "Reduce GPU RAM" +msgstr "" + +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:209 msgid "Use only CPU and disable GPU acceleration" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:198 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:212 msgid "Set this if larger models do not fit your GPU memory and Buzz crashes" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:200 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:214 msgid "Disable GPU" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:225 -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:231 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:239 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:245 msgid "OpenAI API Key Test" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:226 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:240 msgid "" "Your API key is valid. Buzz will use this key to perform Whisper API " "transcriptions and AI translations." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:242 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:256 msgid "Invalid API key" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:243 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:257 msgid "" "API supports only base64 characters (A-Za-z0-9+/=_-). Other characters in " "API key may cause errors." msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:264 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:278 msgid "Select Export Folder" msgstr "" -#: buzz/widgets/preferences_dialog/general_preferences_widget.py:334 +#: buzz/widgets/preferences_dialog/general_preferences_widget.py:359 msgid "" "OpenAI API returned invalid response. Please check the API url or your key. " "Transcription and translation may still work if the API does not support key " @@ -316,8 +330,8 @@ msgstr "下載模型" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:577 -#: buzz/model_loader.py:591 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 +#: buzz/model_loader.py:665 msgid "Error" msgstr "" @@ -335,27 +349,39 @@ msgstr "停止" msgid "Detect Language" msgstr "檢測語言" +#: buzz/widgets/transcriber/mms_language_line_edit.py:26 +msgid "e.g., eng, fra, deu" +msgstr "" + +#: buzz/widgets/transcriber/mms_language_line_edit.py:28 +msgid "" +"Enter an ISO 639-3 language code (3 letters).\n" +"Examples: eng (English), fra (French), deu (German),\n" +"spa (Spanish), lav (Latvian)" +msgstr "" + #: buzz/widgets/transcriber/file_transcriber_widget.py:84 msgid "Run" msgstr "開始執行" -#: buzz/widgets/transcriber/transcription_options_group_box.py:93 +#: buzz/widgets/transcriber/transcription_options_group_box.py:101 msgid "Model:" msgstr "模型:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:105 +#: buzz/widgets/transcriber/transcription_options_group_box.py:113 msgid "First time use of a model may take up to several minutest to load." msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:115 +#: buzz/widgets/transcriber/transcription_options_group_box.py:123 msgid "Api Key:" msgstr "" -#: buzz/widgets/transcriber/transcription_options_group_box.py:116 +#: buzz/widgets/transcriber/transcription_options_group_box.py:124 msgid "Task:" msgstr "任務:" -#: buzz/widgets/transcriber/transcription_options_group_box.py:117 +#: buzz/widgets/transcriber/transcription_options_group_box.py:125 +#: buzz/widgets/transcriber/transcription_options_group_box.py:126 msgid "Language:" msgstr "語言:" @@ -602,13 +628,13 @@ msgid "End" msgstr "" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:278 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:43 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:44 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:32 msgid "Text" msgstr "" #: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:279 -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:49 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:50 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:33 #: buzz/widgets/transcription_viewer/export_transcription_menu.py:53 #, fuzzy @@ -619,7 +645,7 @@ msgstr "新錄製" msgid "View" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:56 +#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:57 msgid "Timestamps" msgstr "" @@ -627,107 +653,107 @@ msgstr "" msgid "Export" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:285 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:286 msgid "Translate" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:295 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:296 #: buzz/widgets/transcription_viewer/transcription_resizer_widget.py:175 msgid "Resize" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:308 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:309 msgid "Identify Speakers" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:320 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:321 msgid "Find" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:325 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:326 msgid "Show/Hide Search Bar (Ctrl+F)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:424 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:425 msgid "Find:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:430 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:431 msgid "Enter text to find..." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:443 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:444 msgid "Previous match (Shift+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:452 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:453 msgid "Next match (Ctrl+Enter)" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:461 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:462 msgid "Clear" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:489 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:490 msgid "Playback Controls:" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:494 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:495 msgid "Loop Segment" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:497 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:498 msgid "Enable/disable looping when clicking on transcript segments" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:504 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:505 msgid "Follow Audio" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:507 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:508 msgid "" "Enable/disable following the current audio position in the transcript. When " "enabled, automatically scrolls to current text." msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:556 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:557 msgid "Scroll to Current" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:559 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:560 msgid "Scroll to the currently spoken text" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:892 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:893 msgid "1 of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 msgid "1 of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:895 -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:896 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:900 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:901 msgid "No matches found" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:973 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:974 msgid " of 100+ matches" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:976 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:977 msgid " of " msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1368 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1372 msgid "API Key Required" msgstr "" -#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1369 +#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:1373 msgid "Please enter OpenAI API Key in preferences" msgstr "" @@ -889,12 +915,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:397 +#: buzz/transcriber/recording_transcriber.py:417 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:401 +#: buzz/transcriber/recording_transcriber.py:421 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1267,15 +1293,15 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:224 buzz/model_loader.py:610 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:333 +#: buzz/transcriber/recording_transcriber.py:353 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:388 +#: buzz/transcriber/recording_transcriber.py:408 #, fuzzy msgid "Starting transcription..." msgstr "取消錄製" diff --git a/buzz/model_loader.py b/buzz/model_loader.py index 24a0a5dc..87531189 100644 --- a/buzz/model_loader.py +++ b/buzz/model_loader.py @@ -131,6 +131,80 @@ HUGGING_FACE_MODEL_ALLOW_PATTERNS = [ "vocab.json", ] +# MMS models use different patterns - adapters are downloaded on-demand by transformers +MMS_MODEL_ALLOW_PATTERNS = [ + "model.safetensors", + "pytorch_model.bin", + "config.json", + "preprocessor_config.json", + "tokenizer_config.json", + "vocab.json", + "special_tokens_map.json", + "added_tokens.json", +] + +# ISO 639-1 to ISO 639-3 language code mapping for MMS models +ISO_639_1_TO_3 = { + "en": "eng", "fr": "fra", "de": "deu", "es": "spa", "it": "ita", + "pt": "por", "ru": "rus", "ja": "jpn", "ko": "kor", "zh": "cmn", + "ar": "ara", "hi": "hin", "nl": "nld", "pl": "pol", "sv": "swe", + "tr": "tur", "uk": "ukr", "vi": "vie", "cs": "ces", "da": "dan", + "fi": "fin", "el": "ell", "he": "heb", "hu": "hun", "id": "ind", + "ms": "zsm", "no": "nob", "ro": "ron", "sk": "slk", "th": "tha", + "bg": "bul", "ca": "cat", "hr": "hrv", "lt": "lit", "lv": "lav", + "sl": "slv", "et": "est", "sr": "srp", "tl": "tgl", "bn": "ben", + "ta": "tam", "te": "tel", "mr": "mar", "gu": "guj", "kn": "kan", + "ml": "mal", "pa": "pan", "ur": "urd", "fa": "pes", "sw": "swh", + "af": "afr", "az": "azj", "be": "bel", "bs": "bos", "cy": "cym", + "eo": "epo", "eu": "eus", "ga": "gle", "gl": "glg", "hy": "hye", + "is": "isl", "ka": "kat", "kk": "kaz", "km": "khm", "lo": "lao", + "mk": "mkd", "mn": "khk", "my": "mya", "ne": "npi", "si": "sin", + "sq": "sqi", "uz": "uzn", "zu": "zul", "am": "amh", "jw": "jav", + "la": "lat", "so": "som", "su": "sun", "tt": "tat", "yo": "yor", +} + + +def map_language_to_mms(language_code: str) -> str: + """Convert ISO 639-1 code to ISO 639-3 code for MMS models. + + If the code is already 3 letters, returns it as-is. + If the code is not found in the mapping, returns as-is. + """ + if not language_code: + return "eng" # Default to English for MMS + if len(language_code) == 3: + return language_code # Already ISO 639-3 + return ISO_639_1_TO_3.get(language_code, language_code) + + +def is_mms_model(model_id: str) -> bool: + """Detect if a HuggingFace model is an MMS (Massively Multilingual Speech) model. + + Detection criteria: + 1. Model ID contains "mms-" (e.g., facebook/mms-1b-all) + 2. Model config has model_type == "wav2vec2" with adapter architecture + """ + if not model_id: + return False + + # Fast check: model ID pattern + if "mms-" in model_id.lower(): + return True + + # For cached/downloaded models, check config.json + try: + import json + config_path = huggingface_hub.hf_hub_download( + model_id, "config.json", local_files_only=True, cache_dir=model_root_dir + ) + with open(config_path) as f: + config = json.load(f) + # MMS models have model_type "wav2vec2" and use adapter architecture + return (config.get("model_type") == "wav2vec2" + and config.get("adapter_attn_dim") is not None) + except Exception: + return False + @dataclass() class TranscriptionModel: diff --git a/buzz/settings/settings.py b/buzz/settings/settings.py index cd8923df..a33cead5 100644 --- a/buzz/settings/settings.py +++ b/buzz/settings/settings.py @@ -70,6 +70,7 @@ class Settings: AUDIO_PLAYBACK_RATE = "audio/playback-rate" FORCE_CPU = "force-cpu" + REDUCE_GPU_MEMORY = "reduce-gpu-memory" def get_user_identifier(self) -> str: user_id = self.value(self.Key.USER_IDENTIFIER, "") diff --git a/buzz/store/keyring_store.py b/buzz/store/keyring_store.py index 0915e018..670be19e 100644 --- a/buzz/store/keyring_store.py +++ b/buzz/store/keyring_store.py @@ -82,9 +82,6 @@ def _get_portal_secret() -> bytes | None: break if secret_data: - logging.debug( - "Successfully retrieved portal secret (%d bytes)", len(secret_data) - ) return secret_data return None diff --git a/buzz/transcriber/recording_transcriber.py b/buzz/transcriber/recording_transcriber.py index 6800f006..929c12cb 100644 --- a/buzz/transcriber/recording_transcriber.py +++ b/buzz/transcriber/recording_transcriber.py @@ -21,10 +21,10 @@ from PyQt6.QtCore import QObject, pyqtSignal from buzz import whisper_audio from buzz.locale import _ from buzz.assets import APP_BASE_DIR -from buzz.model_loader import ModelType +from buzz.model_loader import ModelType, map_language_to_mms from buzz.settings.settings import Settings from buzz.transcriber.transcriber import TranscriptionOptions, Task -from buzz.transformers_whisper import TransformersWhisper +from buzz.transformers_whisper import TransformersTranscriber from buzz.settings.recording_transcriber_mode import RecordingTranscriberMode import whisper @@ -105,10 +105,18 @@ class RecordingTranscriber(QObject): if force_cpu != "false": device = "cpu" + # Check if user wants reduced GPU memory usage (int8 quantization) + reduce_gpu_memory = os.getenv("BUZZ_REDUCE_GPU_MEMORY", "false") != "false" + compute_type = "default" + if reduce_gpu_memory: + compute_type = "int8" if device == "cpu" else "int8_float16" + logging.debug(f"Using {compute_type} compute type for reduced memory usage") + model = faster_whisper.WhisperModel( model_size_or_path=model_path, download_root=model_root_dir, device=device, + compute_type=compute_type, cpu_threads=(os.cpu_count() or 8)//2, ) @@ -132,7 +140,7 @@ class RecordingTranscriber(QObject): logging.debug("Will use whisper API on %s, %s", custom_openai_base_url, self.whisper_api_model) else: # ModelType.HUGGING_FACE - model = TransformersWhisper(model_path) + model = TransformersTranscriber(model_path) initial_prompt = self.transcription_options.initial_prompt @@ -211,13 +219,25 @@ class RecordingTranscriber(QObject): self.transcription_options.model.model_type == ModelType.HUGGING_FACE ): - assert isinstance(model, TransformersWhisper) + assert isinstance(model, TransformersTranscriber) + # Handle MMS-specific language and task + if model.is_mms_model: + language = map_language_to_mms( + self.transcription_options.language or "eng" + ) + effective_task = Task.TRANSCRIBE.value + else: + language = ( + self.transcription_options.language + if self.transcription_options.language is not None + else "en" + ) + effective_task = self.transcription_options.task.value + result = model.transcribe( audio=samples, - language=self.transcription_options.language - if self.transcription_options.language is not None - else "en", - task=self.transcription_options.task.value, + language=language, + task=effective_task, ) else: # OPEN_AI_WHISPER_API, also used for WHISPER_CPP if self.openai_client is None: diff --git a/buzz/transcriber/whisper_file_transcriber.py b/buzz/transcriber/whisper_file_transcriber.py index e329c587..a0882a7d 100644 --- a/buzz/transcriber/whisper_file_transcriber.py +++ b/buzz/transcriber/whisper_file_transcriber.py @@ -18,8 +18,8 @@ from PyQt6.QtCore import QObject from buzz import whisper_audio from buzz.conn import pipe_stderr -from buzz.model_loader import ModelType, WhisperModelSize -from buzz.transformers_whisper import TransformersWhisper +from buzz.model_loader import ModelType, WhisperModelSize, map_language_to_mms +from buzz.transformers_whisper import TransformersTranscriber from buzz.transcriber.file_transcriber import FileTranscriber from buzz.transcriber.transcriber import FileTranscriptionTask, Segment, Task from buzz.transcriber.whisper_cpp import WhisperCpp @@ -123,6 +123,10 @@ class WhisperFileTranscriber(FileTranscriber): def transcribe_whisper( cls, stderr_conn: Connection, task: FileTranscriptionTask ) -> None: + # Preload CUDA libraries in the subprocess - must be done before importing torch + # This is needed because multiprocessing creates a fresh process without the main process's preloaded libraries + from buzz import cuda_setup # noqa: F401 + # Patch subprocess on Windows to prevent console window flash # This is needed because multiprocessing spawns a new process without the main process patches if sys.platform == "win32": @@ -182,17 +186,29 @@ class WhisperFileTranscriber(FileTranscriber): @classmethod def transcribe_hugging_face(cls, task: FileTranscriptionTask) -> List[Segment]: - model = TransformersWhisper(task.model_path) - language = ( - task.transcription_options.language - if task.transcription_options.language is not None - else "en" - ) + model = TransformersTranscriber(task.model_path) + + # Handle language - MMS uses ISO 639-3 codes, Whisper uses ISO 639-1 + if model.is_mms_model: + language = map_language_to_mms(task.transcription_options.language or "eng") + # MMS only supports transcription, ignore translation task + effective_task = Task.TRANSCRIBE.value + # MMS doesn't support word-level timestamps + word_timestamps = False + else: + language = ( + task.transcription_options.language + if task.transcription_options.language is not None + else "en" + ) + effective_task = task.transcription_options.task.value + word_timestamps = task.transcription_options.word_level_timings + result = model.transcribe( audio=task.file_path, language=language, - task=task.transcription_options.task.value, - word_timestamps=task.transcription_options.word_level_timings, + task=effective_task, + word_timestamps=word_timestamps, ) return [ Segment( @@ -228,10 +244,18 @@ class WhisperFileTranscriber(FileTranscriber): if force_cpu != "false": device = "cpu" + # Check if user wants reduced GPU memory usage (int8 quantization) + reduce_gpu_memory = os.getenv("BUZZ_REDUCE_GPU_MEMORY", "false") != "false" + compute_type = "default" + if reduce_gpu_memory: + compute_type = "int8" if device == "cpu" else "int8_float16" + logging.debug(f"Using {compute_type} compute type for reduced memory usage") + model = faster_whisper.WhisperModel( model_size_or_path=model_size_or_path, download_root=model_root_dir, device=device, + compute_type=compute_type, cpu_threads=(os.cpu_count() or 8)//2, ) diff --git a/buzz/transformers_whisper.py b/buzz/transformers_whisper.py index 0164fd2f..96ee0f47 100644 --- a/buzz/transformers_whisper.py +++ b/buzz/transformers_whisper.py @@ -1,14 +1,28 @@ import os import sys +import logging +import platform import numpy as np import torch import requests -from typing import Optional, Union -from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline +from typing import Union +from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline, BitsAndBytesConfig from transformers.pipelines import AutomaticSpeechRecognitionPipeline from transformers.pipelines.audio_utils import ffmpeg_read from transformers.pipelines.automatic_speech_recognition import is_torchaudio_available +from buzz.model_loader import is_mms_model, map_language_to_mms + + +def is_intel_mac() -> bool: + """Check if running on Intel Mac (x86_64).""" + return sys.platform == 'darwin' and platform.machine() == 'x86_64' + + +def is_peft_model(model_id: str) -> bool: + """Check if model is a PEFT model based on model ID containing '-peft'.""" + return "-peft" in model_id.lower() + class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no cover # Copy of transformers `AutomaticSpeechRecognitionPipeline.chunk_iter` method with custom progress output @@ -162,11 +176,23 @@ class PipelineWithProgress(AutomaticSpeechRecognitionPipeline): # pragma: no co yield {"is_last": True, **processed, **extra} -class TransformersWhisper: - def __init__( - self, model_id: str - ): +class TransformersTranscriber: + """Unified transcriber for HuggingFace models (Whisper and MMS).""" + + def __init__(self, model_id: str): self.model_id = model_id + self._is_mms = is_mms_model(model_id) + self._is_peft = is_peft_model(model_id) + + @property + def is_mms_model(self) -> bool: + """Returns True if this is an MMS model.""" + return self._is_mms + + @property + def is_peft_model(self) -> bool: + """Returns True if this is a PEFT model.""" + return self._is_peft def transcribe( self, @@ -175,39 +201,85 @@ class TransformersWhisper: task: str, word_timestamps: bool = False, ): + """Transcribe audio using either Whisper or MMS model.""" + if self._is_mms: + return self._transcribe_mms(audio, language) + else: + return self._transcribe_whisper(audio, language, task, word_timestamps) + + def _transcribe_whisper( + self, + audio: Union[str, np.ndarray], + language: str, + task: str, + word_timestamps: bool = False, + ): + """Transcribe using Whisper model.""" force_cpu = os.getenv("BUZZ_FORCE_CPU", "false") use_cuda = torch.cuda.is_available() and force_cpu == "false" device = "cuda" if use_cuda else "cpu" torch_dtype = torch.float16 if use_cuda else torch.float32 - use_safetensors = True - if os.path.exists(self.model_id): - safetensors_files = [f for f in os.listdir(self.model_id) if f.endswith(".safetensors")] - use_safetensors = len(safetensors_files) > 0 + # Check if this is a PEFT model + if is_peft_model(self.model_id): + model, processor, use_8bit = self._load_peft_model(device, torch_dtype) + else: + use_safetensors = True + if os.path.exists(self.model_id): + safetensors_files = [f for f in os.listdir(self.model_id) if f.endswith(".safetensors")] + use_safetensors = len(safetensors_files) > 0 - model = AutoModelForSpeechSeq2Seq.from_pretrained( - self.model_id, torch_dtype=torch_dtype, low_cpu_mem_usage=True, use_safetensors=use_safetensors - ) + # Check if user wants reduced GPU memory usage (8-bit quantization) + # Skip on Intel Macs as bitsandbytes is not available there + reduce_gpu_memory = os.getenv("BUZZ_REDUCE_GPU_MEMORY", "false") != "false" + use_8bit = False + if device == "cuda" and reduce_gpu_memory and not is_intel_mac(): + try: + import bitsandbytes # noqa: F401 + use_8bit = True + print("Using 8-bit quantization for reduced GPU memory usage") + except ImportError: + print("bitsandbytes not available, using standard precision") - model.generation_config.language = language - model.to(device) + if use_8bit: + quantization_config = BitsAndBytesConfig(load_in_8bit=True) + model = AutoModelForSpeechSeq2Seq.from_pretrained( + self.model_id, + quantization_config=quantization_config, + device_map="auto", + use_safetensors=use_safetensors + ) + else: + model = AutoModelForSpeechSeq2Seq.from_pretrained( + self.model_id, torch_dtype=torch_dtype, low_cpu_mem_usage=True, use_safetensors=use_safetensors + ) + model.to(device) - processor = AutoProcessor.from_pretrained(self.model_id) + model.generation_config.language = language - pipe = pipeline( - "automatic-speech-recognition", - pipeline_class=PipelineWithProgress, - generate_kwargs={"language": language, "task": task}, - model=model, - tokenizer=processor.tokenizer, - feature_extractor=processor.feature_extractor, + processor = AutoProcessor.from_pretrained(self.model_id) + + pipeline_kwargs = { + "task": "automatic-speech-recognition", + "pipeline_class": PipelineWithProgress, + "generate_kwargs": { + "language": language, + "task": task, + "no_repeat_ngram_size": 3, + "repetition_penalty": 1.2, + }, + "model": model, + "tokenizer": processor.tokenizer, + "feature_extractor": processor.feature_extractor, # pipeline has built in chunking, works faster, but we loose progress output # needed for word level timestamps, otherwise there is huge RAM usage on longer audios - chunk_length_s=30 if word_timestamps else None, - torch_dtype=torch_dtype, - device=device, - ignore_warning=True # Ignore warning about chunk_length_s being experimental for seq2seq models - ) + "chunk_length_s": 30 if word_timestamps else None, + "torch_dtype": torch_dtype, + "ignore_warning": True, # Ignore warning about chunk_length_s being experimental for seq2seq models + } + if not use_8bit: + pipeline_kwargs["device"] = device + pipe = pipeline(**pipeline_kwargs) transcript = pipe( audio, @@ -238,3 +310,207 @@ class TransformersWhisper: "segments": segments, } + def _load_peft_model(self, device: str, torch_dtype): + """Load a PEFT (Parameter-Efficient Fine-Tuning) model. + + PEFT models require loading the base model first, then applying the adapter. + The base model path is extracted from the PEFT config. + + Returns: + Tuple of (model, processor, use_8bit) + """ + from peft import PeftModel, PeftConfig + from transformers import WhisperForConditionalGeneration, WhisperFeatureExtractor, WhisperTokenizer + + print(f"Loading PEFT model: {self.model_id}") + + # Get the PEFT model ID (handle both local paths and repo IDs) + peft_model_id = self._get_peft_repo_id() + + # Load PEFT config to get base model path + peft_config = PeftConfig.from_pretrained(peft_model_id) + base_model_path = peft_config.base_model_name_or_path + print(f"PEFT base model: {base_model_path}") + + # Load the base Whisper model + # Use 8-bit quantization on CUDA if user enabled "Reduce GPU RAM" and bitsandbytes is available + # Skip on Intel Macs as bitsandbytes is not available there + reduce_gpu_memory = os.getenv("BUZZ_REDUCE_GPU_MEMORY", "false") != "false" + use_8bit = False + if device == "cuda" and reduce_gpu_memory and not is_intel_mac(): + try: + import bitsandbytes # noqa: F401 + use_8bit = True + print("Using 8-bit quantization for reduced GPU memory usage") + except ImportError: + print("bitsandbytes not available, using standard precision for PEFT model") + + if use_8bit: + quantization_config = BitsAndBytesConfig(load_in_8bit=True) + model = WhisperForConditionalGeneration.from_pretrained( + base_model_path, + quantization_config=quantization_config, + device_map="auto" + ) + else: + model = WhisperForConditionalGeneration.from_pretrained( + base_model_path, + torch_dtype=torch_dtype, + low_cpu_mem_usage=True + ) + model.to(device) + + # Apply the PEFT adapter + model = PeftModel.from_pretrained(model, peft_model_id) + model.config.use_cache = True + + # Load feature extractor and tokenizer from base model + feature_extractor = WhisperFeatureExtractor.from_pretrained(base_model_path) + tokenizer = WhisperTokenizer.from_pretrained(base_model_path, task="transcribe") + + # Create a simple processor-like object that the pipeline expects + class PeftProcessor: + def __init__(self, feature_extractor, tokenizer): + self.feature_extractor = feature_extractor + self.tokenizer = tokenizer + + processor = PeftProcessor(feature_extractor, tokenizer) + + return model, processor, use_8bit + + def _get_peft_repo_id(self) -> str: + """Extract HuggingFace repo ID from local cache path for PEFT models.""" + model_id = self.model_id + + # If it's already a repo ID (contains / but not a file path), return as-is + if "/" in model_id and not os.path.exists(model_id): + return model_id + + # Extract repo ID from cache path + if "models--" in model_id: + parts = model_id.split("models--") + if len(parts) > 1: + repo_part = parts[1].split(os.sep + "snapshots")[0] + repo_id = repo_part.replace("--", "/", 1) + return repo_id + + # Fallback: return as-is + return model_id + + def _get_mms_repo_id(self) -> str: + """Extract HuggingFace repo ID from local cache path or return as-is if already a repo ID.""" + model_id = self.model_id + + # If it's already a repo ID (contains / but not a file path), return as-is + if "/" in model_id and not os.path.exists(model_id): + return model_id + + # Extract repo ID from cache path like: + # Linux: /home/user/.cache/Buzz/models/models--facebook--mms-1b-all/snapshots/xxx + # Windows: C:\Users\user\.cache\Buzz\models\models--facebook--mms-1b-all\snapshots\xxx + if "models--" in model_id: + # Extract the part after "models--" and before "/snapshots" or "\snapshots" + parts = model_id.split("models--") + if len(parts) > 1: + # Split on os.sep to handle both Windows and Unix paths + repo_part = parts[1].split(os.sep + "snapshots")[0] + # Convert facebook--mms-1b-all to facebook/mms-1b-all + repo_id = repo_part.replace("--", "/", 1) + return repo_id + + # Fallback: return as-is + return model_id + + def _transcribe_mms( + self, + audio: Union[str, np.ndarray], + language: str, + ): + """Transcribe using MMS (Massively Multilingual Speech) model.""" + from transformers import Wav2Vec2ForCTC, AutoProcessor as MMSAutoProcessor + from transformers.pipelines.audio_utils import ffmpeg_read as mms_ffmpeg_read + + force_cpu = os.getenv("BUZZ_FORCE_CPU", "false") + use_cuda = torch.cuda.is_available() and force_cpu == "false" + device = "cuda" if use_cuda else "cpu" + + # Map language code to ISO 639-3 for MMS + mms_language = map_language_to_mms(language) + print(f"MMS transcription with language: {mms_language} (original: {language})") + + sys.stderr.write("0%\n") + + # Use repo ID for MMS to allow adapter downloads + # Local paths don't work for adapter downloads + repo_id = self._get_mms_repo_id() + print(f"MMS using repo ID: {repo_id} (from model_id: {self.model_id})") + + # Load processor and model with target language + # This will download the language adapter if not cached + processor = MMSAutoProcessor.from_pretrained( + repo_id, + target_lang=mms_language + ) + + model = Wav2Vec2ForCTC.from_pretrained( + repo_id, + target_lang=mms_language, + ignore_mismatched_sizes=True + ) + model.to(device) + + sys.stderr.write("25%\n") + + # Load and process audio + if isinstance(audio, str): + with open(audio, "rb") as f: + audio_data = f.read() + audio_array = mms_ffmpeg_read(audio_data, processor.feature_extractor.sampling_rate) + else: + audio_array = audio + + # Ensure audio is the right sample rate + sampling_rate = processor.feature_extractor.sampling_rate + + sys.stderr.write("50%\n") + + # Process audio in chunks for progress reporting + inputs = processor( + audio_array, + sampling_rate=sampling_rate, + return_tensors="pt", + padding=True + ) + inputs = {k: v.to(device) for k, v in inputs.items()} + + sys.stderr.write("75%\n") + + # Run inference + with torch.no_grad(): + outputs = model(**inputs).logits + + # Decode + ids = torch.argmax(outputs, dim=-1)[0] + transcription = processor.decode(ids) + + sys.stderr.write("100%\n") + + # Calculate approximate duration for segment + duration = len(audio_array) / sampling_rate if isinstance(audio_array, np.ndarray) else 0 + + # Return in same format as Whisper for consistency + # MMS doesn't provide word-level timestamps, so we return a single segment + return { + "text": transcription, + "segments": [{ + "start": 0, + "end": duration, + "text": transcription.strip(), + "translation": "" + }] if transcription.strip() else [] + } + + +# Alias for backward compatibility +TransformersWhisper = TransformersTranscriber + diff --git a/buzz/widgets/application.py b/buzz/widgets/application.py index 571257a5..0448298c 100755 --- a/buzz/widgets/application.py +++ b/buzz/widgets/application.py @@ -47,6 +47,13 @@ class Application(QApplication): ) if force_cpu_enabled: os.environ["BUZZ_FORCE_CPU"] = "true" + + # Set BUZZ_REDUCE_GPU_MEMORY environment variable if Reduce GPU RAM setting is enabled + reduce_gpu_memory_enabled = self.settings.value( + key=Settings.Key.REDUCE_GPU_MEMORY, default_value=False + ) + if reduce_gpu_memory_enabled: + os.environ["BUZZ_REDUCE_GPU_MEMORY"] = "true" font_size = self.settings.value( key=Settings.Key.FONT_SIZE, default_value=self.font().pointSize() diff --git a/buzz/widgets/preferences_dialog/general_preferences_widget.py b/buzz/widgets/preferences_dialog/general_preferences_widget.py index 8f29f5de..af569091 100644 --- a/buzz/widgets/preferences_dialog/general_preferences_widget.py +++ b/buzz/widgets/preferences_dialog/general_preferences_widget.py @@ -188,6 +188,20 @@ class GeneralPreferencesWidget(QWidget): layout.addRow(_("Live recording mode"), self.recording_transcriber_mode) + self.reduce_gpu_memory_enabled = self.settings.value( + key=Settings.Key.REDUCE_GPU_MEMORY, default_value=False + ) + + self.reduce_gpu_memory_checkbox = QCheckBox(_("Use 8-bit quantization to reduce memory usage")) + self.reduce_gpu_memory_checkbox.setChecked(self.reduce_gpu_memory_enabled) + self.reduce_gpu_memory_checkbox.setObjectName("ReduceGPUMemoryCheckbox") + self.reduce_gpu_memory_checkbox.setToolTip( + _("Applies to Huggingface and Faster Whisper models. " + "Reduces GPU memory usage but may slightly decrease transcription quality.") + ) + self.reduce_gpu_memory_checkbox.stateChanged.connect(self.on_reduce_gpu_memory_changed) + layout.addRow(_("Reduce GPU RAM"), self.reduce_gpu_memory_checkbox) + self.force_cpu_enabled = self.settings.value( key=Settings.Key.FORCE_CPU, default_value=False ) @@ -295,12 +309,23 @@ class GeneralPreferencesWidget(QWidget): import os self.force_cpu_enabled = state == 2 self.settings.set_value(Settings.Key.FORCE_CPU, self.force_cpu_enabled) - + if self.force_cpu_enabled: os.environ["BUZZ_FORCE_CPU"] = "true" else: os.environ.pop("BUZZ_FORCE_CPU", None) + def on_reduce_gpu_memory_changed(self, state: int): + import os + self.reduce_gpu_memory_enabled = state == 2 + self.settings.set_value(Settings.Key.REDUCE_GPU_MEMORY, self.reduce_gpu_memory_enabled) + + if self.reduce_gpu_memory_enabled: + os.environ["BUZZ_REDUCE_GPU_MEMORY"] = "true" + else: + os.environ.pop("BUZZ_REDUCE_GPU_MEMORY", None) + + class ValidateOpenAIApiKeyJob(QRunnable): class Signals(QObject): success = pyqtSignal() diff --git a/buzz/widgets/transcriber/hugging_face_search_line_edit.py b/buzz/widgets/transcriber/hugging_face_search_line_edit.py index db9da3af..b53bbfa7 100644 --- a/buzz/widgets/transcriber/hugging_face_search_line_edit.py +++ b/buzz/widgets/transcriber/hugging_face_search_line_edit.py @@ -64,7 +64,8 @@ class HuggingFaceSearchLineEdit(LineEdit): def focusInEvent(self, event): super().focusInEvent(event) - self.clear() + # Defer selectAll to run after mouse events are processed + QTimer.singleShot(0, self.selectAll) def on_text_edited(self, text: str): self.model_selected.emit(text) diff --git a/buzz/widgets/transcriber/mms_language_line_edit.py b/buzz/widgets/transcriber/mms_language_line_edit.py new file mode 100644 index 00000000..4f101d6d --- /dev/null +++ b/buzz/widgets/transcriber/mms_language_line_edit.py @@ -0,0 +1,48 @@ +from typing import Optional + +from PyQt6.QtCore import pyqtSignal +from PyQt6.QtWidgets import QWidget, QSizePolicy + +from buzz.locale import _ +from buzz.widgets.line_edit import LineEdit + + +class MMSLanguageLineEdit(LineEdit): + """Text input for MMS language codes (ISO 639-3). + + MMS models support 1000+ languages using ISO 639-3 codes (3 letters). + Examples: eng (English), fra (French), deu (German), spa (Spanish) + """ + + languageChanged = pyqtSignal(str) + + def __init__( + self, + default_language: str = "eng", + parent: Optional[QWidget] = None + ): + super().__init__(default_language, parent) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + self.setPlaceholderText(_("e.g., eng, fra, deu")) + self.setToolTip( + _("Enter an ISO 639-3 language code (3 letters).\n" + "Examples: eng (English), fra (French), deu (German),\n" + "spa (Spanish), lav (Latvian)") + ) + self.setMaxLength(10) # Allow some flexibility for edge cases + self.setMinimumWidth(100) + + self.textChanged.connect(self._on_text_changed) + + def _on_text_changed(self, text: str): + """Emit language changed signal with cleaned text.""" + cleaned = text.strip().lower() + self.languageChanged.emit(cleaned) + + def language(self) -> str: + """Get the current language code.""" + return self.text().strip().lower() + + def setLanguage(self, language: str): + """Set the language code.""" + self.setText(language.strip().lower() if language else "eng") diff --git a/buzz/widgets/transcriber/transcription_options_group_box.py b/buzz/widgets/transcriber/transcription_options_group_box.py index 844e4df9..1524086e 100644 --- a/buzz/widgets/transcriber/transcription_options_group_box.py +++ b/buzz/widgets/transcriber/transcription_options_group_box.py @@ -10,7 +10,7 @@ from PyQt6.QtWidgets import QGroupBox, QWidget, QFormLayout, QComboBox, QLabel, from buzz.locale import _ from buzz.settings.settings import Settings from buzz.widgets.icon import INFO_ICON_PATH -from buzz.model_loader import ModelType, WhisperModelSize, get_whisper_cpp_file_path +from buzz.model_loader import ModelType, WhisperModelSize, get_whisper_cpp_file_path, is_mms_model from buzz.transcriber.transcriber import TranscriptionOptions, Task from buzz.widgets.model_type_combo_box import ModelTypeComboBox from buzz.widgets.openai_api_key_line_edit import OpenAIAPIKeyLineEdit @@ -20,6 +20,7 @@ from buzz.widgets.transcriber.hugging_face_search_line_edit import ( HuggingFaceSearchLineEdit, ) from buzz.widgets.transcriber.languages_combo_box import LanguagesComboBox +from buzz.widgets.transcriber.mms_language_line_edit import MMSLanguageLineEdit from buzz.widgets.transcriber.tasks_combo_box import TasksComboBox @@ -87,6 +88,13 @@ class TranscriptionOptionsGroupBox(QGroupBox): ) self.languages_combo_box.languageChanged.connect(self.on_language_changed) + # MMS language input (text field for ISO 639-3 codes) + self.mms_language_line_edit = MMSLanguageLineEdit( + default_language="eng", parent=self + ) + self.mms_language_line_edit.languageChanged.connect(self.on_mms_language_changed) + self.mms_language_line_edit.setVisible(False) + self.advanced_settings_button = AdvancedSettingsButton(self) self.advanced_settings_button.clicked.connect(self.open_advanced_settings) @@ -115,6 +123,7 @@ class TranscriptionOptionsGroupBox(QGroupBox): self.form_layout.addRow(_("Api Key:"), self.openai_access_token_edit) self.form_layout.addRow(_("Task:"), self.tasks_combo_box) self.form_layout.addRow(_("Language:"), self.languages_combo_box) + self.form_layout.addRow(_("Language:"), self.mms_language_line_edit) self.reset_visible_rows() @@ -133,6 +142,14 @@ class TranscriptionOptionsGroupBox(QGroupBox): self.transcription_options.language = language self.transcription_options_changed.emit(self.transcription_options) + def on_mms_language_changed(self, language: str): + """Handle MMS language code changes.""" + if language == "": + language = "eng" # Default to English for MMS + + self.transcription_options.language = language + self.transcription_options_changed.emit(self.transcription_options) + def on_task_changed(self, task: Task): self.transcription_options.task = task self.transcription_options_changed.emit(self.transcription_options) @@ -229,6 +246,9 @@ class TranscriptionOptionsGroupBox(QGroupBox): self.transcription_options.model.model_type == ModelType.WHISPER_CPP ) + # Update language widget visibility (MMS vs Whisper) + self._update_language_widget_visibility() + def on_model_type_changed(self, model_type: ModelType): self.transcription_options.model.model_type = model_type if not model_type.supports_initial_prompt: @@ -254,3 +274,34 @@ class TranscriptionOptionsGroupBox(QGroupBox): self.transcription_options_changed.emit(self.transcription_options) self.settings.save_custom_model_id(self.transcription_options.model) + + # Update language widget visibility based on whether this is an MMS model + self._update_language_widget_visibility() + + def _update_language_widget_visibility(self): + """Update language widget visibility based on whether the selected model is MMS.""" + model_type = self.transcription_options.model.model_type + model_id = self.transcription_options.model.hugging_face_model_id + + # Check if this is an MMS model + is_mms = (model_type == ModelType.HUGGING_FACE and is_mms_model(model_id)) + + # Show MMS language input for MMS models, show dropdown for others + self.form_layout.setRowVisible(self.mms_language_line_edit, is_mms) + self.form_layout.setRowVisible(self.languages_combo_box, not is_mms) + + # Sync the language value when switching between MMS and non-MMS + if is_mms: + # When switching to MMS, use the MMS language input value + mms_lang = self.mms_language_line_edit.language() + if mms_lang: + self.transcription_options.language = mms_lang + self.transcription_options_changed.emit(self.transcription_options) + else: + # When switching from MMS to a regular model, use the dropdown's current value + # This prevents invalid MMS language codes (like "eng") being used with Whisper + current_index = self.languages_combo_box.currentIndex() + dropdown_lang = self.languages_combo_box.languages[current_index][0] + if self.transcription_options.language != dropdown_lang: + self.transcription_options.language = dropdown_lang if dropdown_lang else None + self.transcription_options_changed.emit(self.transcription_options) diff --git a/buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py b/buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py index 4c9abc01..610f7d79 100644 --- a/buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py +++ b/buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py @@ -34,6 +34,7 @@ class TranscriptionViewModeToolButton(QToolButton): self.setIcon(VisibilityIcon(self)) self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) self.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup) + self.setMinimumWidth(80) translation.connect(self.on_translation_available) diff --git a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py index 768ef365..546936a6 100644 --- a/buzz/widgets/transcription_viewer/transcription_viewer_widget.py +++ b/buzz/widgets/transcription_viewer/transcription_viewer_widget.py @@ -267,6 +267,7 @@ class TranscriptionViewerWidget(QWidget): export_tool_button.setToolButtonStyle( Qt.ToolButtonStyle.ToolButtonTextBesideIcon ) + export_tool_button.setMinimumWidth(100) export_transcription_menu = ExportTranscriptionMenu( transcription, @@ -1124,6 +1125,7 @@ class TranscriptionViewerWidget(QWidget): if self.view_mode == ViewMode.TIMESTAMPS: self.text_display_box.hide() self.table_widget.show() + self.media_splitter.show() if self.current_media_player: self.current_media_player.show() # Show playback controls in timestamps mode @@ -1149,6 +1151,7 @@ class TranscriptionViewerWidget(QWidget): self.text_display_box.setPlainText(combined_text.strip()) self.text_display_box.show() self.table_widget.hide() + self.media_splitter.hide() if self.current_media_player: self.current_media_player.hide() # Hide playback controls in text mode @@ -1164,6 +1167,7 @@ class TranscriptionViewerWidget(QWidget): ) self.text_display_box.show() self.table_widget.hide() + self.media_splitter.hide() if self.current_media_player: self.current_media_player.hide() # Hide playback controls in translation mode @@ -1494,7 +1498,7 @@ class TranscriptionViewerWidget(QWidget): if self.currently_selected_segment is None: self.highlight_table_match(0) - if current_segment_index == 0 and segments[1]: + if current_segment_index == 0 and len(segments) > 1: self.highlight_table_match(1) self.highlight_table_match(current_segment_index) diff --git a/docs/docs/faq.md b/docs/docs/faq.md index ab47a824..1eb3207e 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -37,6 +37,12 @@ Model size to use will depend on your hardware and use case. Smaller models will When choosing among large models consider the following. "Large" is the first released older model, "Large-V2" is later updated model with better accuracy, for some languages considered the most robust and stable. "Large-V3" is the latest model with the best accuracy in many cases, but some times can hallucinate or invent words that were never in the audio. "Turbo" model tries to get a good balance between speed and accuracy. The only sure way to know what model best suits your needs is to test them all in your language. +In addition to choosing an appropriate model size you also can choose whisper type. +- **Whisper** is initial OpenAI implementation, it is accurate but slow and requires a lot of RAM. +- Faster **Whisper** is an optimized implementation, it is orders of magnitude faster than regular Whisper and requires less RAM. Use this option if you have an Nvidia GPU with at least 6GB of VRAM. +- **Whisper.cpp** is optimized C++ implementation, it quite fast and efficient and will use any brand of GPU. Whisper.cpp is capable of running real time transcription even on a modern laptop with integrated GPU. It can also run on CPU only. Use this option if you do not have Nvidia GPU. +- **HuggingFace** option is a `Transformers` implementation and is good in that it supports wide range of custom models that may be optimized for a particular language. This option also supports [MMS](https://ai.meta.com/blog/multilingual-model-speech-recognition/) family of models from Meta AI that support over 1000 of worlds languages as well as [PEFT](https://github.com/huggingface/peft) adjustments to Whisper models. + ### 5. How to get GPU acceleration for faster transcription? On Linux GPU acceleration is supported out of the box on Nvidia GPUs. If you still get any issues install [CUDA 12](https://developer.nvidia.com/cuda-downloads), [cuBLASS](https://developer.nvidia.com/cublas) and [cuDNN](https://developer.nvidia.com/cudnn). diff --git a/docs/docs/installation.md b/docs/docs/installation.md index d5f5c4e2..633ea9cd 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -4,26 +4,17 @@ sidebar_position: 2 --- To install Buzz, download the [latest version](https://github.com/chidiwilliams/buzz/releases/latest) for your operating -system. Buzz is available on **Mac** (Intel), **Windows**, and **Linux**. (For Apple Silicon, please see -the [App Store version](https://apps.apple.com/us/app/buzz-captions/id6446018936?mt=12&itsct=apps_box_badge&itscg=30200).) +system. Buzz is available on **Mac** (Intel), **Windows**, and **Linux**. -## macOS (Intel, macOS 11.7 and later) +### macOS -Install via [brew](https://brew.sh/): +Download the `.dmg` from the [SourceForge](https://sourceforge.net/projects/buzz-captions/files/). -```shell -brew install --cask buzz -``` +### Windows -Alternatively, download and run the `Buzz-x.y.z.dmg` file. +Get the installation files from the [SourceForge](https://sourceforge.net/projects/buzz-captions/files/). -For Mac Silicon (and for a better experience on Mac Intel), -download [Buzz Captions](https://apps.apple.com/us/app/buzz-captions/id6446018936?mt=12&itsct=apps_box_badge&itscg=30200) -on the App Store. - -## Windows (Windows 10 and later) - -Download and run the `Buzz-x.y.z.exe` file. +App is not signed, you will get a warning when you install it. Select `More info` -> `Run anyway`. ## Linux @@ -49,8 +40,6 @@ Alternatively, on Ubuntu 20.04 and later, install the dependencies: sudo apt-get install libportaudio2 ``` -Then, download and extract the `Buzz-x.y.z-unix.tar.gz` file - ## PyPI ```shell diff --git a/docs/docs/preferences.md b/docs/docs/preferences.md index 1e48e9de..b6c8bd5e 100644 --- a/docs/docs/preferences.md +++ b/docs/docs/preferences.md @@ -109,6 +109,8 @@ Defaults to [user_cache_dir](https://pypi.org/project/platformdirs/). **BUZZ_FORCE_CPU** - Will force Buzz to use CPU and not GPU, useful for setups with older GPU if that is slower than GPU or GPU has issues. Example usage `BUZZ_FORCE_CPU=true`. Available since `1.2.1` +**BUZZ_REDUCE_GPU_MEMORY** - Will use 8bit quantization for Huggingface adn Faster Whisper transcriptions to reduce required GPU memory. Example usage `BUZZ_REDUCE_GPU_MEMORY=true`. Available since `1.4.0` + **BUZZ_MERGE_REGROUP_RULE** - Custom regroup merge rule to use when combining transcripts with word-level timings. More information on available options [in stable-ts repo](https://github.com/jianfch/stable-ts?tab=readme-ov-file#regrouping-methods). Available since `1.3.0` **BUZZ_DISABLE_TELEMETRY** - Buzz collects basic OS name and architecture usage statistics to better focus development efforts. This variable lets disable collection of these statistics. Example usage `BUZZ_DISABLE_TELEMETRY=true`. Available since `1.3.0` diff --git a/pyproject.toml b/pyproject.toml index db82c21f..8d39405a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,10 @@ dependencies = [ "openai-whisper==20250625", "transformers>=4.53,<5", "accelerate>=1.12.0,<2", + "peft>=0.14.0,<1", + # Overriden in uv.tool section below to ensure CUDA 12.9 compatibility + # Skip on Intel Macs (x86_64), use 0.49.0 on ARM Macs, 0.45.0+ elsewhere + "bitsandbytes>=0.45.0; sys_platform != 'darwin' or platform_machine != 'x86_64'", "polib>=1.2.0,<2", "srt-equalizer>=0.1.10,<0.2", # For Intel macOS (x86_64) - use older versions that support Intel @@ -79,6 +83,9 @@ dependencies = [ "demucs", "certifi==2025.11.12", "torchcodec>=0.9.0; sys_platform != 'darwin' or platform_machine != 'x86_64'", + "torch>=2.2.2", + "torchaudio>=2.2.2", + "datasets>=4.4.1", ] repository = "https://github.com/chidiwilliams/buzz" documentation = "https://chidiwilliams.github.io/buzz/docs" @@ -115,6 +122,14 @@ default-groups = [ "build", ] +# Should be removed after nemo-toolkit update to 2.6.0 +# Forcing a CUDA 12.9 compatable bitsandbytes version +# ARM Macs use 0.49.0, others use 0.47.0 (Intel Macs skip entirely via marker) +override-dependencies = [ + "bitsandbytes==0.49.0; sys_platform == 'darwin' and platform_machine == 'arm64'", + "bitsandbytes==0.47.0; sys_platform != 'darwin'", +] + [tool.uv.sources] demucs = { path = "demucs_repo", editable = true } torch = [ diff --git a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml index b94e23bd..988beeab 100644 --- a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml +++ b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml @@ -64,6 +64,25 @@ + + https://github.com/chidiwilliams/buzz/releases/tag/v1.4.0 + +

    Adding speaker identification on transcriptions and video support for transcription viewer, improvements to transcription table and support for over 1000 of worlds languages via MMS models.

    +

    Release details:

    +
      +
    • Speaker identification on finished transcripts
    • +
    • Support for video in transcription viewer
    • +
    • Ability to add notes and restart transcriptions in main table
    • +
    • Adding support for more than 1000 languages via MMS model family when transcribing with Huggingface transcription type
    • +
    • Adding support for PEFT models when transcribing with Huggingface transcription type
    • +
    • Adding support for 8bit quantization for Huggingface and faster Whisper transcriptions
    • +
    • Updated libraries and dependencies to support latest GPUs
    • +
    • Support for secrets portal for snap packages on Linux
    • +
    • Ability to specify model to use when transcribing with OpenAI API
    • +
    • Ability to access application logs from About screen
    • +
    +

    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%0Ar4F8@pxSe!)fMsWlY#9z&6JJ`J#>tyWQ* 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 U!mh~1LP0+A(kfE55~iX5A2>YpwEzGB diff --git a/demucs/demucs/__init__.py b/demucs/demucs/__init__.py deleted file mode 100644 index 3bf9f708..00000000 --- a/demucs/demucs/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -__version__ = "4.1.0a3" diff --git a/demucs/demucs/__main__.py b/demucs/demucs/__main__.py deleted file mode 100644 index da0a5410..00000000 --- a/demucs/demucs/__main__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -from .separate import main - -if __name__ == '__main__': - main() diff --git a/demucs/demucs/api.py b/demucs/demucs/api.py deleted file mode 100644 index ee8a5126..00000000 --- a/demucs/demucs/api.py +++ /dev/null @@ -1,393 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -"""API methods for demucs - -Classes -------- -`demucs.api.Separator`: The base separator class - -Functions ---------- -`demucs.api.save_audio`: Save an audio -`demucs.api.list_models`: Get models list - -Examples --------- -See the end of this module (if __name__ == "__main__") -""" - -import subprocess - -from . import audio_legacy -import torch as th -import torchaudio as ta - -from dora.log import fatal -from pathlib import Path -from typing import Optional, Callable, Dict, Tuple, Union - -from .apply import apply_model, _replace_dict -from .audio import AudioFile, convert_audio, save_audio -from .pretrained import get_model, _parse_remote_files, REMOTE_ROOT -from .repo import RemoteRepo, LocalRepo, ModelOnlyRepo, BagOnlyRepo - - -class LoadAudioError(Exception): - pass - - -class LoadModelError(Exception): - pass - - -class _NotProvided: - pass - - -NotProvided = _NotProvided() - - -class Separator: - def __init__( - self, - model: str = "htdemucs", - repo: Optional[Path] = None, - device: str = "cuda" if th.cuda.is_available() else "cpu", - shifts: int = 1, - overlap: float = 0.25, - split: bool = True, - segment: Optional[int] = None, - jobs: int = 0, - progress: bool = False, - callback: Optional[Callable[[dict], None]] = None, - callback_arg: Optional[dict] = None, - ): - """ - `class Separator` - ================= - - Parameters - ---------- - model: Pretrained model name or signature. Default is htdemucs. - repo: Folder containing all pre-trained models for use. - segment: Length (in seconds) of each segment (only available if `split` is `True`). If \ - not specified, will use the command line option. - shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and \ - apply the oppositve shift to the output. This is repeated `shifts` time and all \ - predictions are averaged. This effectively makes the model time equivariant and \ - improves SDR by up to 0.2 points. If not specified, will use the command line option. - split: If True, the input will be broken down into small chunks (length set by `segment`) \ - and predictions will be performed individually on each and concatenated. Useful for \ - model with large memory footprint like Tasnet. If not specified, will use the command \ - line option. - overlap: The overlap between the splits. If not specified, will use the command line \ - option. - device (torch.device, str, or None): If provided, device on which to execute the \ - computation, otherwise `wav.device` is assumed. When `device` is different from \ - `wav.device`, only local computations will be on `device`, while the entire tracks \ - will be stored on `wav.device`. If not specified, will use the command line option. - jobs: Number of jobs. This can increase memory usage but will be much faster when \ - multiple cores are available. If not specified, will use the command line option. - callback: A function will be called when the separation of a chunk starts or finished. \ - The argument passed to the function will be a dict. For more information, please see \ - the Callback section. - callback_arg: A dict containing private parameters to be passed to callback function. For \ - more information, please see the Callback section. - progress: If true, show a progress bar. - - Callback - -------- - The function will be called with only one positional parameter whose type is `dict`. The - `callback_arg` will be combined with information of current separation progress. The - progress information will override the values in `callback_arg` if same key has been used. - To abort the separation, raise `KeyboardInterrupt`. - - Progress information contains several keys (These keys will always exist): - - `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. - - `shift_idx`: The index of shifts. Starts from 0. - - `segment_offset`: The offset of current segment. If the number is 441000, it doesn't - mean that it is at the 441000 second of the audio, but the "frame" of the tensor. - - `state`: Could be `"start"` or `"end"`. - - `audio_length`: Length of the audio (in "frame" of the tensor). - - `models`: Count of submodels in the model. - """ - self._name = model - self._repo = repo - self._load_model() - self.update_parameter(device=device, shifts=shifts, overlap=overlap, split=split, - segment=segment, jobs=jobs, progress=progress, callback=callback, - callback_arg=callback_arg) - - def update_parameter( - self, - device: Union[str, _NotProvided] = NotProvided, - shifts: Union[int, _NotProvided] = NotProvided, - overlap: Union[float, _NotProvided] = NotProvided, - split: Union[bool, _NotProvided] = NotProvided, - segment: Optional[Union[int, _NotProvided]] = NotProvided, - jobs: Union[int, _NotProvided] = NotProvided, - progress: Union[bool, _NotProvided] = NotProvided, - callback: Optional[ - Union[Callable[[dict], None], _NotProvided] - ] = NotProvided, - callback_arg: Optional[Union[dict, _NotProvided]] = NotProvided, - ): - """ - Update the parameters of separation. - - Parameters - ---------- - segment: Length (in seconds) of each segment (only available if `split` is `True`). If \ - not specified, will use the command line option. - shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and \ - apply the oppositve shift to the output. This is repeated `shifts` time and all \ - predictions are averaged. This effectively makes the model time equivariant and \ - improves SDR by up to 0.2 points. If not specified, will use the command line option. - split: If True, the input will be broken down into small chunks (length set by `segment`) \ - and predictions will be performed individually on each and concatenated. Useful for \ - model with large memory footprint like Tasnet. If not specified, will use the command \ - line option. - overlap: The overlap between the splits. If not specified, will use the command line \ - option. - device (torch.device, str, or None): If provided, device on which to execute the \ - computation, otherwise `wav.device` is assumed. When `device` is different from \ - `wav.device`, only local computations will be on `device`, while the entire tracks \ - will be stored on `wav.device`. If not specified, will use the command line option. - jobs: Number of jobs. This can increase memory usage but will be much faster when \ - multiple cores are available. If not specified, will use the command line option. - callback: A function will be called when the separation of a chunk starts or finished. \ - The argument passed to the function will be a dict. For more information, please see \ - the Callback section. - callback_arg: A dict containing private parameters to be passed to callback function. For \ - more information, please see the Callback section. - progress: If true, show a progress bar. - - Callback - -------- - The function will be called with only one positional parameter whose type is `dict`. The - `callback_arg` will be combined with information of current separation progress. The - progress information will override the values in `callback_arg` if same key has been used. - To abort the separation, raise `KeyboardInterrupt`. - - Progress information contains several keys (These keys will always exist): - - `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. - - `shift_idx`: The index of shifts. Starts from 0. - - `segment_offset`: The offset of current segment. If the number is 441000, it doesn't - mean that it is at the 441000 second of the audio, but the "frame" of the tensor. - - `state`: Could be `"start"` or `"end"`. - - `audio_length`: Length of the audio (in "frame" of the tensor). - - `models`: Count of submodels in the model. - """ - if not isinstance(device, _NotProvided): - self._device = device - if not isinstance(shifts, _NotProvided): - self._shifts = shifts - if not isinstance(overlap, _NotProvided): - self._overlap = overlap - if not isinstance(split, _NotProvided): - self._split = split - if not isinstance(segment, _NotProvided): - self._segment = segment - if not isinstance(jobs, _NotProvided): - self._jobs = jobs - if not isinstance(progress, _NotProvided): - self._progress = progress - if not isinstance(callback, _NotProvided): - self._callback = callback - if not isinstance(callback_arg, _NotProvided): - self._callback_arg = callback_arg - - def _load_model(self): - self._model = get_model(name=self._name, repo=self._repo) - if self._model is None: - raise LoadModelError("Failed to load model") - self._audio_channels = self._model.audio_channels - self._samplerate = self._model.samplerate - - def _load_audio(self, track: Path): - errors = {} - wav = None - - try: - wav = AudioFile(track).read(streams=0, samplerate=self._samplerate, - channels=self._audio_channels) - except FileNotFoundError: - errors["ffmpeg"] = "FFmpeg is not installed." - except subprocess.CalledProcessError: - errors["ffmpeg"] = "FFmpeg could not read the file." - - if wav is None: - try: - wav, sr = ta.load(str(track)) - except RuntimeError as err: - errors["torchaudio"] = err.args[0] - else: - wav = convert_audio(wav, sr, self._samplerate, self._audio_channels) - - if wav is None: - raise LoadAudioError( - "\n".join( - "When trying to load using {}, got the following error: {}".format( - backend, error - ) - for backend, error in errors.items() - ) - ) - return wav - - def separate_tensor( - self, wav: th.Tensor, sr: Optional[int] = None - ) -> Tuple[th.Tensor, Dict[str, th.Tensor]]: - """ - Separate a loaded tensor. - - Parameters - ---------- - wav: Waveform of the audio. Should have 2 dimensions, the first is each audio channel, \ - while the second is the waveform of each channel. Type should be float32. \ - e.g. `tuple(wav.shape) == (2, 884000)` means the audio has 2 channels. - sr: Sample rate of the original audio, the wave will be resampled if it doesn't match the \ - model. - - Returns - ------- - A tuple, whose first element is the original wave and second element is a dict, whose keys - are the name of stems and values are separated waves. The original wave will have already - been resampled. - - Notes - ----- - Use this function with cautiousness. This function does not provide data verifying. - """ - if sr is not None and sr != self.samplerate: - wav = convert_audio(wav, sr, self._samplerate, self._audio_channels) - ref = wav.mean(0) - wav -= ref.mean() - wav /= ref.std() + 1e-8 - out = apply_model( - self._model, - wav[None], - segment=self._segment, - shifts=self._shifts, - split=self._split, - overlap=self._overlap, - device=self._device, - num_workers=self._jobs, - callback=self._callback, - callback_arg=_replace_dict( - self._callback_arg, ("audio_length", wav.shape[1]) - ), - progress=self._progress, - ) - if out is None: - raise KeyboardInterrupt - out *= ref.std() + 1e-8 - out += ref.mean() - wav *= ref.std() + 1e-8 - wav += ref.mean() - return (wav, dict(zip(self._model.sources, out[0]))) - - def separate_audio_file(self, file: Path): - """ - Separate an audio file. The method will automatically read the file. - - Parameters - ---------- - wav: Path of the file to be separated. - - Returns - ------- - A tuple, whose first element is the original wave and second element is a dict, whose keys - are the name of stems and values are separated waves. The original wave will have already - been resampled. - """ - return self.separate_tensor(self._load_audio(file), self.samplerate) - - @property - def samplerate(self): - return self._samplerate - - @property - def audio_channels(self): - return self._audio_channels - - @property - def model(self): - return self._model - - -def list_models(repo: Optional[Path] = None) -> Dict[str, Dict[str, Union[str, Path]]]: - """ - List the available models. Please remember that not all the returned models can be - successfully loaded. - - Parameters - ---------- - repo: The repo whose models are to be listed. - - Returns - ------- - A dict with two keys ("single" for single models and "bag" for bag of models). The values are - lists whose components are strs. - """ - model_repo: ModelOnlyRepo - if repo is None: - models = _parse_remote_files(REMOTE_ROOT / 'files.txt') - model_repo = RemoteRepo(models) - bag_repo = BagOnlyRepo(REMOTE_ROOT, model_repo) - else: - if not repo.is_dir(): - fatal(f"{repo} must exist and be a directory.") - model_repo = LocalRepo(repo) - bag_repo = BagOnlyRepo(repo, model_repo) - return {"single": model_repo.list_model(), "bag": bag_repo.list_model()} - - -if __name__ == "__main__": - # Test API functions - # two-stem not supported - - from .separate import get_parser - - args = get_parser().parse_args() - separator = Separator( - model=args.name, - repo=args.repo, - device=args.device, - shifts=args.shifts, - overlap=args.overlap, - split=args.split, - segment=args.segment, - jobs=args.jobs, - callback=print - ) - out = args.out / args.name - out.mkdir(parents=True, exist_ok=True) - for file in args.tracks: - separated = separator.separate_audio_file(file)[1] - if args.mp3: - ext = "mp3" - elif args.flac: - ext = "flac" - else: - ext = "wav" - kwargs = { - "samplerate": separator.samplerate, - "bitrate": args.mp3_bitrate, - "clip": args.clip_mode, - "as_float": args.float32, - "bits_per_sample": 24 if args.int24 else 16, - } - for stem, source in separated.items(): - stem = out / args.filename.format( - track=Path(file).name.rsplit(".", 1)[0], - trackext=Path(file).name.rsplit(".", 1)[-1], - stem=stem, - ext=ext, - ) - stem.parent.mkdir(parents=True, exist_ok=True) - save_audio(source, str(stem), **kwargs) diff --git a/demucs/demucs/apply.py b/demucs/demucs/apply.py deleted file mode 100644 index c84993de..00000000 --- a/demucs/demucs/apply.py +++ /dev/null @@ -1,322 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -Code to apply a model to a mix. It will handle chunking with overlaps and -inteprolation between chunks, as well as the "shift trick". -""" -from concurrent.futures import ThreadPoolExecutor -import copy -import random -from threading import Lock -import typing as tp - -import torch as th -from torch import nn -from torch.nn import functional as F -import tqdm - -from .demucs import Demucs -from .hdemucs import HDemucs -from .htdemucs import HTDemucs -from .utils import center_trim, DummyPoolExecutor - -Model = tp.Union[Demucs, HDemucs, HTDemucs] - - -class BagOfModels(nn.Module): - def __init__(self, models: tp.List[Model], - weights: tp.Optional[tp.List[tp.List[float]]] = None, - segment: tp.Optional[float] = None): - """ - Represents a bag of models with specific weights. - You should call `apply_model` rather than calling directly the forward here for - optimal performance. - - Args: - models (list[nn.Module]): list of Demucs/HDemucs models. - weights (list[list[float]]): list of weights. If None, assumed to - be all ones, otherwise it should be a list of N list (N number of models), - each containing S floats (S number of sources). - segment (None or float): overrides the `segment` attribute of each model - (this is performed inplace, be careful is you reuse the models passed). - """ - super().__init__() - assert len(models) > 0 - first = models[0] - for other in models: - assert other.sources == first.sources - assert other.samplerate == first.samplerate - assert other.audio_channels == first.audio_channels - if segment is not None: - if not isinstance(other, HTDemucs) or segment <= other.segment: - other.segment = segment - - self.audio_channels = first.audio_channels - self.samplerate = first.samplerate - self.sources = first.sources - self.models = nn.ModuleList(models) - - if weights is None: - weights = [[1. for _ in first.sources] for _ in models] - else: - assert len(weights) == len(models) - for weight in weights: - assert len(weight) == len(first.sources) - self.weights = weights - - @property - def max_allowed_segment(self) -> float: - max_allowed_segment = float('inf') - for model in self.models: - if isinstance(model, HTDemucs): - max_allowed_segment = min(max_allowed_segment, float(model.segment)) - return max_allowed_segment - - def forward(self, x): - raise NotImplementedError("Call `apply_model` on this.") - - -class TensorChunk: - def __init__(self, tensor, offset=0, length=None): - total_length = tensor.shape[-1] - assert offset >= 0 - assert offset < total_length - - if length is None: - length = total_length - offset - else: - length = min(total_length - offset, length) - - if isinstance(tensor, TensorChunk): - self.tensor = tensor.tensor - self.offset = offset + tensor.offset - else: - self.tensor = tensor - self.offset = offset - self.length = length - self.device = tensor.device - - @property - def shape(self): - shape = list(self.tensor.shape) - shape[-1] = self.length - return shape - - def padded(self, target_length): - delta = target_length - self.length - total_length = self.tensor.shape[-1] - assert delta >= 0 - - start = self.offset - delta // 2 - end = start + target_length - - correct_start = max(0, start) - correct_end = min(total_length, end) - - pad_left = correct_start - start - pad_right = end - correct_end - - out = F.pad(self.tensor[..., correct_start:correct_end], (pad_left, pad_right)) - assert out.shape[-1] == target_length - return out - - -def tensor_chunk(tensor_or_chunk): - if isinstance(tensor_or_chunk, TensorChunk): - return tensor_or_chunk - else: - assert isinstance(tensor_or_chunk, th.Tensor) - return TensorChunk(tensor_or_chunk) - - -def _replace_dict(_dict: tp.Optional[dict], *subs: tp.Tuple[tp.Hashable, tp.Any]) -> dict: - if _dict is None: - _dict = {} - else: - _dict = copy.copy(_dict) - for key, value in subs: - _dict[key] = value - return _dict - - -def apply_model(model: tp.Union[BagOfModels, Model], - mix: tp.Union[th.Tensor, TensorChunk], - shifts: int = 1, split: bool = True, - overlap: float = 0.25, transition_power: float = 1., - progress: bool = False, device=None, - num_workers: int = 0, segment: tp.Optional[float] = None, - pool=None, lock=None, - callback: tp.Optional[tp.Callable[[dict], None]] = None, - callback_arg: tp.Optional[dict] = None) -> th.Tensor: - """ - Apply model to a given mixture. - - Args: - shifts (int): if > 0, will shift in time `mix` by a random amount between 0 and 0.5 sec - and apply the oppositve shift to the output. This is repeated `shifts` time and - all predictions are averaged. This effectively makes the model time equivariant - and improves SDR by up to 0.2 points. - split (bool): if True, the input will be broken down in 8 seconds extracts - and predictions will be performed individually on each and concatenated. - Useful for model with large memory footprint like Tasnet. - progress (bool): if True, show a progress bar (requires split=True) - device (torch.device, str, or None): if provided, device on which to - execute the computation, otherwise `mix.device` is assumed. - When `device` is different from `mix.device`, only local computations will - be on `device`, while the entire tracks will be stored on `mix.device`. - num_workers (int): if non zero, device is 'cpu', how many threads to - use in parallel. - segment (float or None): override the model segment parameter. - """ - if device is None: - device = mix.device - else: - device = th.device(device) - if pool is None: - if num_workers > 0 and device.type == 'cpu': - pool = ThreadPoolExecutor(num_workers) - else: - pool = DummyPoolExecutor() - if lock is None: - lock = Lock() - callback_arg = _replace_dict( - callback_arg, *{"model_idx_in_bag": 0, "shift_idx": 0, "segment_offset": 0}.items() - ) - kwargs: tp.Dict[str, tp.Any] = { - 'shifts': shifts, - 'split': split, - 'overlap': overlap, - 'transition_power': transition_power, - 'progress': progress, - 'device': device, - 'pool': pool, - 'segment': segment, - 'lock': lock, - } - out: tp.Union[float, th.Tensor] - res: tp.Union[float, th.Tensor] - if isinstance(model, BagOfModels): - # Special treatment for bag of model. - # We explicitely apply multiple times `apply_model` so that the random shifts - # are different for each model. - estimates: tp.Union[float, th.Tensor] = 0. - totals = [0.] * len(model.sources) - callback_arg["models"] = len(model.models) - for sub_model, model_weights in zip(model.models, model.weights): - kwargs["callback"] = (( - lambda d, i=callback_arg["model_idx_in_bag"]: callback( - _replace_dict(d, ("model_idx_in_bag", i))) if callback else None) - ) - original_model_device = next(iter(sub_model.parameters())).device - sub_model.to(device) - - res = apply_model(sub_model, mix, **kwargs, callback_arg=callback_arg) - out = res - sub_model.to(original_model_device) - for k, inst_weight in enumerate(model_weights): - out[:, k, :, :] *= inst_weight - totals[k] += inst_weight - estimates += out - del out - callback_arg["model_idx_in_bag"] += 1 - - assert isinstance(estimates, th.Tensor) - for k in range(estimates.shape[1]): - estimates[:, k, :, :] /= totals[k] - return estimates - - if "models" not in callback_arg: - callback_arg["models"] = 1 - model.to(device) - model.eval() - assert transition_power >= 1, "transition_power < 1 leads to weird behavior." - batch, channels, length = mix.shape - if shifts: - kwargs['shifts'] = 0 - max_shift = int(0.5 * model.samplerate) - mix = tensor_chunk(mix) - assert isinstance(mix, TensorChunk) - padded_mix = mix.padded(length + 2 * max_shift) - out = 0. - for shift_idx in range(shifts): - offset = random.randint(0, max_shift) - shifted = TensorChunk(padded_mix, offset, length + max_shift - offset) - kwargs["callback"] = ( - (lambda d, i=shift_idx: callback(_replace_dict(d, ("shift_idx", i))) - if callback else None) - ) - res = apply_model(model, shifted, **kwargs, callback_arg=callback_arg) - shifted_out = res - out += shifted_out[..., max_shift - offset:] - out /= shifts - assert isinstance(out, th.Tensor) - return out - elif split: - kwargs['split'] = False - out = th.zeros(batch, len(model.sources), channels, length, device=mix.device) - sum_weight = th.zeros(length, device=mix.device) - if segment is None: - segment = model.segment - assert segment is not None and segment > 0. - segment_length: int = int(model.samplerate * segment) - stride = int((1 - overlap) * segment_length) - offsets = range(0, length, stride) - scale = float(format(stride / model.samplerate, ".2f")) - # We start from a triangle shaped weight, with maximal weight in the middle - # of the segment. Then we normalize and take to the power `transition_power`. - # Large values of transition power will lead to sharper transitions. - weight = th.cat([th.arange(1, segment_length // 2 + 1, device=device), - th.arange(segment_length - segment_length // 2, 0, -1, device=device)]) - assert len(weight) == segment_length - # If the overlap < 50%, this will translate to linear transition when - # transition_power is 1. - weight = (weight / weight.max())**transition_power - futures = [] - for offset in offsets: - chunk = TensorChunk(mix, offset, segment_length) - future = pool.submit(apply_model, model, chunk, **kwargs, callback_arg=callback_arg, - callback=(lambda d, i=offset: - callback(_replace_dict(d, ("segment_offset", i))) - if callback else None)) - futures.append((future, offset)) - offset += segment_length - if progress: - futures = tqdm.tqdm(futures, unit_scale=scale, ncols=120, unit='seconds') - for future, offset in futures: - try: - chunk_out = future.result() # type: th.Tensor - except Exception: - pool.shutdown(wait=True, cancel_futures=True) - raise - chunk_length = chunk_out.shape[-1] - out[..., offset:offset + segment_length] += ( - weight[:chunk_length] * chunk_out).to(mix.device) - sum_weight[offset:offset + segment_length] += weight[:chunk_length].to(mix.device) - assert sum_weight.min() > 0 - out /= sum_weight - assert isinstance(out, th.Tensor) - return out - else: - valid_length: int - if isinstance(model, HTDemucs) and segment is not None: - valid_length = int(segment * model.samplerate) - elif hasattr(model, 'valid_length'): - valid_length = model.valid_length(length) # type: ignore - else: - valid_length = length - mix = tensor_chunk(mix) - assert isinstance(mix, TensorChunk) - padded_mix = mix.padded(valid_length).to(device) - with lock: - if callback is not None: - callback(_replace_dict(callback_arg, ("state", "start"))) # type: ignore - with th.no_grad(): - out = model(padded_mix) - with lock: - if callback is not None: - callback(_replace_dict(callback_arg, ("state", "end"))) # type: ignore - assert isinstance(out, th.Tensor) - return center_trim(out, length) diff --git a/demucs/demucs/audio.py b/demucs/demucs/audio.py deleted file mode 100644 index 600bd55b..00000000 --- a/demucs/demucs/audio.py +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -import json -import subprocess as sp -from pathlib import Path - -import lameenc -import julius -import numpy as np -from . import audio_legacy -import torch -import torchaudio as ta -import typing as tp - -from .utils import temp_filenames - - -def _read_info(path): - stdout_data = sp.check_output([ - 'ffprobe', "-loglevel", "panic", - str(path), '-print_format', 'json', '-show_format', '-show_streams' - ]) - return json.loads(stdout_data.decode('utf-8')) - - -class AudioFile: - """ - Allows to read audio from any format supported by ffmpeg, as well as resampling or - converting to mono on the fly. See :method:`read` for more details. - """ - def __init__(self, path: Path): - self.path = Path(path) - self._info = None - - def __repr__(self): - features = [("path", self.path)] - features.append(("samplerate", self.samplerate())) - features.append(("channels", self.channels())) - features.append(("streams", len(self))) - features_str = ", ".join(f"{name}={value}" for name, value in features) - return f"AudioFile({features_str})" - - @property - def info(self): - if self._info is None: - self._info = _read_info(self.path) - return self._info - - @property - def duration(self): - return float(self.info['format']['duration']) - - @property - def _audio_streams(self): - return [ - index for index, stream in enumerate(self.info["streams"]) - if stream["codec_type"] == "audio" - ] - - def __len__(self): - return len(self._audio_streams) - - def channels(self, stream=0): - return int(self.info['streams'][self._audio_streams[stream]]['channels']) - - def samplerate(self, stream=0): - return int(self.info['streams'][self._audio_streams[stream]]['sample_rate']) - - def read(self, - seek_time=None, - duration=None, - streams=slice(None), - samplerate=None, - channels=None): - """ - Slightly more efficient implementation than stempeg, - in particular, this will extract all stems at once - rather than having to loop over one file multiple times - for each stream. - - Args: - seek_time (float): seek time in seconds or None if no seeking is needed. - duration (float): duration in seconds to extract or None to extract until the end. - streams (slice, int or list): streams to extract, can be a single int, a list or - a slice. If it is a slice or list, the output will be of size [S, C, T] - with S the number of streams, C the number of channels and T the number of samples. - If it is an int, the output will be [C, T]. - samplerate (int): if provided, will resample on the fly. If None, no resampling will - be done. Original sampling rate can be obtained with :method:`samplerate`. - channels (int): if 1, will convert to mono. We do not rely on ffmpeg for that - as ffmpeg automatically scale by +3dB to conserve volume when playing on speakers. - See https://sound.stackexchange.com/a/42710. - Our definition of mono is simply the average of the two channels. Any other - value will be ignored. - """ - streams = np.array(range(len(self)))[streams] - single = not isinstance(streams, np.ndarray) - if single: - streams = [streams] - - if duration is None: - target_size = None - query_duration = None - else: - target_size = int((samplerate or self.samplerate()) * duration) - query_duration = float((target_size + 1) / (samplerate or self.samplerate())) - - with temp_filenames(len(streams)) as filenames: - command = ['ffmpeg', '-y'] - command += ['-loglevel', 'panic'] - if seek_time: - command += ['-ss', str(seek_time)] - command += ['-i', str(self.path)] - for stream, filename in zip(streams, filenames): - command += ['-map', f'0:{self._audio_streams[stream]}'] - if query_duration is not None: - command += ['-t', str(query_duration)] - command += ['-threads', '1'] - command += ['-f', 'f32le'] - if samplerate is not None: - command += ['-ar', str(samplerate)] - command += [filename] - - sp.run(command, check=True) - wavs = [] - for filename in filenames: - wav = np.fromfile(filename, dtype=np.float32) - wav = torch.from_numpy(wav) - wav = wav.view(-1, self.channels()).t() - if channels is not None: - wav = convert_audio_channels(wav, channels) - if target_size is not None: - wav = wav[..., :target_size] - wavs.append(wav) - wav = torch.stack(wavs, dim=0) - if single: - wav = wav[0] - return wav - - -def convert_audio_channels(wav, channels=2): - """Convert audio to the given number of channels.""" - *shape, src_channels, length = wav.shape - if src_channels == channels: - pass - elif channels == 1: - # Case 1: - # The caller asked 1-channel audio, but the stream have multiple - # channels, downmix all channels. - wav = wav.mean(dim=-2, keepdim=True) - elif src_channels == 1: - # Case 2: - # The caller asked for multiple channels, but the input file have - # one single channel, replicate the audio over all channels. - wav = wav.expand(*shape, channels, length) - elif src_channels >= channels: - # Case 3: - # The caller asked for multiple channels, and the input file have - # more channels than requested. In that case return the first channels. - wav = wav[..., :channels, :] - else: - # Case 4: What is a reasonable choice here? - raise ValueError('The audio file has less channels than requested but is not mono.') - return wav - - -def convert_audio(wav, from_samplerate, to_samplerate, channels) -> torch.Tensor: - """Convert audio from a given samplerate to a target one and target number of channels.""" - wav = convert_audio_channels(wav, channels) - return julius.resample_frac(wav, from_samplerate, to_samplerate) - - -def i16_pcm(wav): - """Convert audio to 16 bits integer PCM format.""" - if wav.dtype.is_floating_point: - return (wav.clamp_(-1, 1) * (2**15 - 1)).short() - else: - return wav - - -def f32_pcm(wav): - """Convert audio to float 32 bits PCM format.""" - if wav.dtype.is_floating_point: - return wav - else: - return wav.float() / (2**15 - 1) - - -def as_dtype_pcm(wav, dtype): - """Convert audio to either f32 pcm or i16 pcm depending on the given dtype.""" - if wav.dtype.is_floating_point: - return f32_pcm(wav) - else: - return i16_pcm(wav) - - -def encode_mp3(wav, path, samplerate=44100, bitrate=320, quality=2, verbose=False): - """Save given audio as mp3. This should work on all OSes.""" - C, T = wav.shape - wav = i16_pcm(wav) - encoder = lameenc.Encoder() - encoder.set_bit_rate(bitrate) - encoder.set_in_sample_rate(samplerate) - encoder.set_channels(C) - encoder.set_quality(quality) # 2-highest, 7-fastest - if not verbose: - encoder.silence() - wav = wav.data.cpu() - wav = wav.transpose(0, 1).numpy() - mp3_data = encoder.encode(wav.tobytes()) - mp3_data += encoder.flush() - with open(path, "wb") as f: - f.write(mp3_data) - - -def prevent_clip(wav, mode='rescale'): - """ - different strategies for avoiding raw clipping. - """ - if mode is None or mode == 'none': - return wav - assert wav.dtype.is_floating_point, "too late for clipping" - if mode == 'rescale': - wav = wav / max(1.01 * wav.abs().max(), 1) - elif mode == 'clamp': - wav = wav.clamp(-0.99, 0.99) - elif mode == 'tanh': - wav = torch.tanh(wav) - else: - raise ValueError(f"Invalid mode {mode}") - return wav - - -def save_audio(wav: torch.Tensor, - path: tp.Union[str, Path], - samplerate: int, - bitrate: int = 320, - clip: tp.Literal["rescale", "clamp", "tanh", "none"] = 'rescale', - bits_per_sample: tp.Literal[16, 24, 32] = 16, - as_float: bool = False, - preset: tp.Literal[2, 3, 4, 5, 6, 7] = 2): - """Save audio file, automatically preventing clipping if necessary - based on the given `clip` strategy. If the path ends in `.mp3`, this - will save as mp3 with the given `bitrate`. Use `preset` to set mp3 quality: - 2 for highest quality, 7 for fastest speed - """ - wav = prevent_clip(wav, mode=clip) - path = Path(path) - suffix = path.suffix.lower() - if suffix == ".mp3": - encode_mp3(wav, path, samplerate, bitrate, preset, verbose=True) - elif suffix == ".wav": - if as_float: - bits_per_sample = 32 - encoding = 'PCM_F' - else: - encoding = 'PCM_S' - ta.save(str(path), wav, sample_rate=samplerate, - encoding=encoding, bits_per_sample=bits_per_sample) - elif suffix == ".flac": - ta.save(str(path), wav, sample_rate=samplerate, bits_per_sample=bits_per_sample) - else: - raise ValueError(f"Invalid suffix for path: {suffix}") diff --git a/demucs/demucs/audio_legacy.py b/demucs/demucs/audio_legacy.py deleted file mode 100644 index ab6bdce4..00000000 --- a/demucs/demucs/audio_legacy.py +++ /dev/null @@ -1,17 +0,0 @@ -# This file is to extend support for torchaudio 2.1 - -import importlib -import os -import sys -import warnings - -if not "torchaudio" in sys.modules: - os.environ["TORCHAUDIO_USE_BACKEND_DISPATCHER"] = "0" -elif os.getenv("TORCHAUDIO_USE_BACKEND_DISPATCHER", default="1") == "1": - if sys.modules["torchaudio"].__version__ >= "2.1": - os.environ["TORCHAUDIO_USE_BACKEND_DISPATCHER"] = "0" - importlib.reload(sys.modules["torchaudio"]) - warnings.warn( - "TORCHAUDIO_USE_BACKEND_DISPATCHER is set to 0 and torchaudio is reloaded.", - ImportWarning, - ) diff --git a/demucs/demucs/augment.py b/demucs/demucs/augment.py deleted file mode 100644 index 6dab7f12..00000000 --- a/demucs/demucs/augment.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Data augmentations. -""" - -import random -import torch as th -from torch import nn - - -class Shift(nn.Module): - """ - Randomly shift audio in time by up to `shift` samples. - """ - def __init__(self, shift=8192, same=False): - super().__init__() - self.shift = shift - self.same = same - - def forward(self, wav): - batch, sources, channels, time = wav.size() - length = time - self.shift - if self.shift > 0: - if not self.training: - wav = wav[..., :length] - else: - srcs = 1 if self.same else sources - offsets = th.randint(self.shift, [batch, srcs, 1, 1], device=wav.device) - offsets = offsets.expand(-1, sources, channels, -1) - indexes = th.arange(length, device=wav.device) - wav = wav.gather(3, indexes + offsets) - return wav - - -class FlipChannels(nn.Module): - """ - Flip left-right channels. - """ - def forward(self, wav): - batch, sources, channels, time = wav.size() - if self.training and wav.size(2) == 2: - left = th.randint(2, (batch, sources, 1, 1), device=wav.device) - left = left.expand(-1, -1, -1, time) - right = 1 - left - wav = th.cat([wav.gather(2, left), wav.gather(2, right)], dim=2) - return wav - - -class FlipSign(nn.Module): - """ - Random sign flip. - """ - def forward(self, wav): - batch, sources, channels, time = wav.size() - if self.training: - signs = th.randint(2, (batch, sources, 1, 1), device=wav.device, dtype=th.float32) - wav = wav * (2 * signs - 1) - return wav - - -class Remix(nn.Module): - """ - Shuffle sources to make new mixes. - """ - def __init__(self, proba=1, group_size=4): - """ - Shuffle sources within one batch. - Each batch is divided into groups of size `group_size` and shuffling is done within - each group separatly. This allow to keep the same probability distribution no matter - the number of GPUs. Without this grouping, using more GPUs would lead to a higher - probability of keeping two sources from the same track together which can impact - performance. - """ - super().__init__() - self.proba = proba - self.group_size = group_size - - def forward(self, wav): - batch, streams, channels, time = wav.size() - device = wav.device - - if self.training and random.random() < self.proba: - group_size = self.group_size or batch - if batch % group_size != 0: - raise ValueError(f"Batch size {batch} must be divisible by group size {group_size}") - groups = batch // group_size - wav = wav.view(groups, group_size, streams, channels, time) - permutations = th.argsort(th.rand(groups, group_size, streams, 1, 1, device=device), - dim=1) - wav = wav.gather(1, permutations.expand(-1, -1, -1, channels, time)) - wav = wav.view(batch, streams, channels, time) - return wav - - -class Scale(nn.Module): - def __init__(self, proba=1., min=0.25, max=1.25): - super().__init__() - self.proba = proba - self.min = min - self.max = max - - def forward(self, wav): - batch, streams, channels, time = wav.size() - device = wav.device - if self.training and random.random() < self.proba: - scales = th.empty(batch, streams, 1, 1, device=device).uniform_(self.min, self.max) - wav *= scales - return wav diff --git a/demucs/demucs/demucs.py b/demucs/demucs/demucs.py deleted file mode 100644 index f6a4305c..00000000 --- a/demucs/demucs/demucs.py +++ /dev/null @@ -1,447 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -import math -import typing as tp - -import julius -import torch -from torch import nn -from torch.nn import functional as F - -from .states import capture_init -from .utils import center_trim, unfold -from .transformer import LayerScale - - -class BLSTM(nn.Module): - """ - BiLSTM with same hidden units as input dim. - If `max_steps` is not None, input will be splitting in overlapping - chunks and the LSTM applied separately on each chunk. - """ - def __init__(self, dim, layers=1, max_steps=None, skip=False): - super().__init__() - assert max_steps is None or max_steps % 4 == 0 - self.max_steps = max_steps - self.lstm = nn.LSTM(bidirectional=True, num_layers=layers, hidden_size=dim, input_size=dim) - self.linear = nn.Linear(2 * dim, dim) - self.skip = skip - - def forward(self, x): - B, C, T = x.shape - y = x - framed = False - if self.max_steps is not None and T > self.max_steps: - width = self.max_steps - stride = width // 2 - frames = unfold(x, width, stride) - nframes = frames.shape[2] - framed = True - x = frames.permute(0, 2, 1, 3).reshape(-1, C, width) - - x = x.permute(2, 0, 1) - - x = self.lstm(x)[0] - x = self.linear(x) - x = x.permute(1, 2, 0) - if framed: - out = [] - frames = x.reshape(B, -1, C, width) - limit = stride // 2 - for k in range(nframes): - if k == 0: - out.append(frames[:, k, :, :-limit]) - elif k == nframes - 1: - out.append(frames[:, k, :, limit:]) - else: - out.append(frames[:, k, :, limit:-limit]) - out = torch.cat(out, -1) - out = out[..., :T] - x = out - if self.skip: - x = x + y - return x - - -def rescale_conv(conv, reference): - """Rescale initial weight scale. It is unclear why it helps but it certainly does. - """ - std = conv.weight.std().detach() - scale = (std / reference)**0.5 - conv.weight.data /= scale - if conv.bias is not None: - conv.bias.data /= scale - - -def rescale_module(module, reference): - for sub in module.modules(): - if isinstance(sub, (nn.Conv1d, nn.ConvTranspose1d, nn.Conv2d, nn.ConvTranspose2d)): - rescale_conv(sub, reference) - - -class DConv(nn.Module): - """ - New residual branches in each encoder layer. - This alternates dilated convolutions, potentially with LSTMs and attention. - Also before entering each residual branch, dimension is projected on a smaller subspace, - e.g. of dim `channels // compress`. - """ - def __init__(self, channels: int, compress: float = 4, depth: int = 2, init: float = 1e-4, - norm=True, attn=False, heads=4, ndecay=4, lstm=False, gelu=True, - kernel=3, dilate=True): - """ - Args: - channels: input/output channels for residual branch. - compress: amount of channel compression inside the branch. - depth: number of layers in the residual branch. Each layer has its own - projection, and potentially LSTM and attention. - init: initial scale for LayerNorm. - norm: use GroupNorm. - attn: use LocalAttention. - heads: number of heads for the LocalAttention. - ndecay: number of decay controls in the LocalAttention. - lstm: use LSTM. - gelu: Use GELU activation. - kernel: kernel size for the (dilated) convolutions. - dilate: if true, use dilation, increasing with the depth. - """ - - super().__init__() - assert kernel % 2 == 1 - self.channels = channels - self.compress = compress - self.depth = abs(depth) - dilate = depth > 0 - - norm_fn: tp.Callable[[int], nn.Module] - norm_fn = lambda d: nn.Identity() # noqa - if norm: - norm_fn = lambda d: nn.GroupNorm(1, d) # noqa - - hidden = int(channels / compress) - - act: tp.Type[nn.Module] - if gelu: - act = nn.GELU - else: - act = nn.ReLU - - self.layers = nn.ModuleList([]) - for d in range(self.depth): - dilation = 2 ** d if dilate else 1 - padding = dilation * (kernel // 2) - mods = [ - nn.Conv1d(channels, hidden, kernel, dilation=dilation, padding=padding), - norm_fn(hidden), act(), - nn.Conv1d(hidden, 2 * channels, 1), - norm_fn(2 * channels), nn.GLU(1), - LayerScale(channels, init), - ] - if attn: - mods.insert(3, LocalState(hidden, heads=heads, ndecay=ndecay)) - if lstm: - mods.insert(3, BLSTM(hidden, layers=2, max_steps=200, skip=True)) - layer = nn.Sequential(*mods) - self.layers.append(layer) - - def forward(self, x): - for layer in self.layers: - x = x + layer(x) - return x - - -class LocalState(nn.Module): - """Local state allows to have attention based only on data (no positional embedding), - but while setting a constraint on the time window (e.g. decaying penalty term). - - Also a failed experiments with trying to provide some frequency based attention. - """ - def __init__(self, channels: int, heads: int = 4, nfreqs: int = 0, ndecay: int = 4): - super().__init__() - assert channels % heads == 0, (channels, heads) - self.heads = heads - self.nfreqs = nfreqs - self.ndecay = ndecay - self.content = nn.Conv1d(channels, channels, 1) - self.query = nn.Conv1d(channels, channels, 1) - self.key = nn.Conv1d(channels, channels, 1) - if nfreqs: - self.query_freqs = nn.Conv1d(channels, heads * nfreqs, 1) - if ndecay: - self.query_decay = nn.Conv1d(channels, heads * ndecay, 1) - # Initialize decay close to zero (there is a sigmoid), for maximum initial window. - self.query_decay.weight.data *= 0.01 - assert self.query_decay.bias is not None # stupid type checker - self.query_decay.bias.data[:] = -2 - self.proj = nn.Conv1d(channels + heads * nfreqs, channels, 1) - - def forward(self, x): - B, C, T = x.shape - heads = self.heads - indexes = torch.arange(T, device=x.device, dtype=x.dtype) - # left index are keys, right index are queries - delta = indexes[:, None] - indexes[None, :] - - queries = self.query(x).view(B, heads, -1, T) - keys = self.key(x).view(B, heads, -1, T) - # t are keys, s are queries - dots = torch.einsum("bhct,bhcs->bhts", keys, queries) - dots /= keys.shape[2]**0.5 - if self.nfreqs: - periods = torch.arange(1, self.nfreqs + 1, device=x.device, dtype=x.dtype) - freq_kernel = torch.cos(2 * math.pi * delta / periods.view(-1, 1, 1)) - freq_q = self.query_freqs(x).view(B, heads, -1, T) / self.nfreqs ** 0.5 - dots += torch.einsum("fts,bhfs->bhts", freq_kernel, freq_q) - if self.ndecay: - decays = torch.arange(1, self.ndecay + 1, device=x.device, dtype=x.dtype) - decay_q = self.query_decay(x).view(B, heads, -1, T) - decay_q = torch.sigmoid(decay_q) / 2 - decay_kernel = - decays.view(-1, 1, 1) * delta.abs() / self.ndecay**0.5 - dots += torch.einsum("fts,bhfs->bhts", decay_kernel, decay_q) - - # Kill self reference. - dots.masked_fill_(torch.eye(T, device=dots.device, dtype=torch.bool), -100) - weights = torch.softmax(dots, dim=2) - - content = self.content(x).view(B, heads, -1, T) - result = torch.einsum("bhts,bhct->bhcs", weights, content) - if self.nfreqs: - time_sig = torch.einsum("bhts,fts->bhfs", weights, freq_kernel) - result = torch.cat([result, time_sig], 2) - result = result.reshape(B, -1, T) - return x + self.proj(result) - - -class Demucs(nn.Module): - @capture_init - def __init__(self, - sources, - # Channels - audio_channels=2, - channels=64, - growth=2., - # Main structure - depth=6, - rewrite=True, - lstm_layers=0, - # Convolutions - kernel_size=8, - stride=4, - context=1, - # Activations - gelu=True, - glu=True, - # Normalization - norm_starts=4, - norm_groups=4, - # DConv residual branch - dconv_mode=1, - dconv_depth=2, - dconv_comp=4, - dconv_attn=4, - dconv_lstm=4, - dconv_init=1e-4, - # Pre/post processing - normalize=True, - resample=True, - # Weight init - rescale=0.1, - # Metadata - samplerate=44100, - segment=4 * 10): - """ - Args: - sources (list[str]): list of source names - audio_channels (int): stereo or mono - channels (int): first convolution channels - depth (int): number of encoder/decoder layers - growth (float): multiply (resp divide) number of channels by that - for each layer of the encoder (resp decoder) - depth (int): number of layers in the encoder and in the decoder. - rewrite (bool): add 1x1 convolution to each layer. - lstm_layers (int): number of lstm layers, 0 = no lstm. Deactivated - by default, as this is now replaced by the smaller and faster small LSTMs - in the DConv branches. - kernel_size (int): kernel size for convolutions - stride (int): stride for convolutions - context (int): kernel size of the convolution in the - decoder before the transposed convolution. If > 1, - will provide some context from neighboring time steps. - gelu: use GELU activation function. - glu (bool): use glu instead of ReLU for the 1x1 rewrite conv. - norm_starts: layer at which group norm starts being used. - decoder layers are numbered in reverse order. - norm_groups: number of groups for group norm. - dconv_mode: if 1: dconv in encoder only, 2: decoder only, 3: both. - dconv_depth: depth of residual DConv branch. - dconv_comp: compression of DConv branch. - dconv_attn: adds attention layers in DConv branch starting at this layer. - dconv_lstm: adds a LSTM layer in DConv branch starting at this layer. - dconv_init: initial scale for the DConv branch LayerScale. - normalize (bool): normalizes the input audio on the fly, and scales back - the output by the same amount. - resample (bool): upsample x2 the input and downsample /2 the output. - rescale (float): rescale initial weights of convolutions - to get their standard deviation closer to `rescale`. - samplerate (int): stored as meta information for easing - future evaluations of the model. - segment (float): duration of the chunks of audio to ideally evaluate the model on. - This is used by `demucs.apply.apply_model`. - """ - - super().__init__() - self.audio_channels = audio_channels - self.sources = sources - self.kernel_size = kernel_size - self.context = context - self.stride = stride - self.depth = depth - self.resample = resample - self.channels = channels - self.normalize = normalize - self.samplerate = samplerate - self.segment = segment - self.encoder = nn.ModuleList() - self.decoder = nn.ModuleList() - self.skip_scales = nn.ModuleList() - - if glu: - activation = nn.GLU(dim=1) - ch_scale = 2 - else: - activation = nn.ReLU() - ch_scale = 1 - if gelu: - act2 = nn.GELU - else: - act2 = nn.ReLU - - in_channels = audio_channels - padding = 0 - for index in range(depth): - norm_fn = lambda d: nn.Identity() # noqa - if index >= norm_starts: - norm_fn = lambda d: nn.GroupNorm(norm_groups, d) # noqa - - encode = [] - encode += [ - nn.Conv1d(in_channels, channels, kernel_size, stride), - norm_fn(channels), - act2(), - ] - attn = index >= dconv_attn - lstm = index >= dconv_lstm - if dconv_mode & 1: - encode += [DConv(channels, depth=dconv_depth, init=dconv_init, - compress=dconv_comp, attn=attn, lstm=lstm)] - if rewrite: - encode += [ - nn.Conv1d(channels, ch_scale * channels, 1), - norm_fn(ch_scale * channels), activation] - self.encoder.append(nn.Sequential(*encode)) - - decode = [] - if index > 0: - out_channels = in_channels - else: - out_channels = len(self.sources) * audio_channels - if rewrite: - decode += [ - nn.Conv1d(channels, ch_scale * channels, 2 * context + 1, padding=context), - norm_fn(ch_scale * channels), activation] - if dconv_mode & 2: - decode += [DConv(channels, depth=dconv_depth, init=dconv_init, - compress=dconv_comp, attn=attn, lstm=lstm)] - decode += [nn.ConvTranspose1d(channels, out_channels, - kernel_size, stride, padding=padding)] - if index > 0: - decode += [norm_fn(out_channels), act2()] - self.decoder.insert(0, nn.Sequential(*decode)) - in_channels = channels - channels = int(growth * channels) - - channels = in_channels - if lstm_layers: - self.lstm = BLSTM(channels, lstm_layers) - else: - self.lstm = None - - if rescale: - rescale_module(self, reference=rescale) - - def valid_length(self, length): - """ - Return the nearest valid length to use with the model so that - there is no time steps left over in a convolution, e.g. for all - layers, size of the input - kernel_size % stride = 0. - - Note that input are automatically padded if necessary to ensure that the output - has the same length as the input. - """ - if self.resample: - length *= 2 - - for _ in range(self.depth): - length = math.ceil((length - self.kernel_size) / self.stride) + 1 - length = max(1, length) - - for idx in range(self.depth): - length = (length - 1) * self.stride + self.kernel_size - - if self.resample: - length = math.ceil(length / 2) - return int(length) - - def forward(self, mix): - x = mix - length = x.shape[-1] - - if self.normalize: - mono = mix.mean(dim=1, keepdim=True) - mean = mono.mean(dim=-1, keepdim=True) - std = mono.std(dim=-1, keepdim=True) - x = (x - mean) / (1e-5 + std) - else: - mean = 0 - std = 1 - - delta = self.valid_length(length) - length - x = F.pad(x, (delta // 2, delta - delta // 2)) - - if self.resample: - x = julius.resample_frac(x, 1, 2) - - saved = [] - for encode in self.encoder: - x = encode(x) - saved.append(x) - - if self.lstm: - x = self.lstm(x) - - for decode in self.decoder: - skip = saved.pop(-1) - skip = center_trim(skip, x) - x = decode(x + skip) - - if self.resample: - x = julius.resample_frac(x, 2, 1) - x = x * std + mean - x = center_trim(x, length) - x = x.view(x.size(0), len(self.sources), self.audio_channels, x.size(-1)) - return x - - def load_state_dict(self, state, strict=True): - # fix a mismatch with previous generation Demucs models. - for idx in range(self.depth): - for a in ['encoder', 'decoder']: - for b in ['bias', 'weight']: - new = f'{a}.{idx}.3.{b}' - old = f'{a}.{idx}.2.{b}' - if old in state and new not in state: - state[new] = state.pop(old) - super().load_state_dict(state, strict=strict) diff --git a/demucs/demucs/distrib.py b/demucs/demucs/distrib.py deleted file mode 100644 index dc1576cb..00000000 --- a/demucs/demucs/distrib.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Distributed training utilities. -""" -import logging -import pickle - -import numpy as np -import torch -from torch.utils.data.distributed import DistributedSampler -from torch.utils.data import DataLoader, Subset -from torch.nn.parallel.distributed import DistributedDataParallel - -from dora import distrib as dora_distrib - -logger = logging.getLogger(__name__) -rank = 0 -world_size = 1 - - -def init(): - global rank, world_size - if not torch.distributed.is_initialized(): - dora_distrib.init() - rank = dora_distrib.rank() - world_size = dora_distrib.world_size() - - -def average(metrics, count=1.): - if isinstance(metrics, dict): - keys, values = zip(*sorted(metrics.items())) - values = average(values, count) - return dict(zip(keys, values)) - if world_size == 1: - return metrics - tensor = torch.tensor(list(metrics) + [1], device='cuda', dtype=torch.float32) - tensor *= count - torch.distributed.all_reduce(tensor, op=torch.distributed.ReduceOp.SUM) - return (tensor[:-1] / tensor[-1]).cpu().numpy().tolist() - - -def wrap(model): - if world_size == 1: - return model - else: - return DistributedDataParallel( - model, - # find_unused_parameters=True, - device_ids=[torch.cuda.current_device()], - output_device=torch.cuda.current_device()) - - -def barrier(): - if world_size > 1: - torch.distributed.barrier() - - -def share(obj=None, src=0): - if world_size == 1: - return obj - size = torch.empty(1, device='cuda', dtype=torch.long) - if rank == src: - dump = pickle.dumps(obj) - size[0] = len(dump) - torch.distributed.broadcast(size, src=src) - # size variable is now set to the length of pickled obj in all processes - - if rank == src: - buffer = torch.from_numpy(np.frombuffer(dump, dtype=np.uint8).copy()).cuda() - else: - buffer = torch.empty(size[0].item(), device='cuda', dtype=torch.uint8) - torch.distributed.broadcast(buffer, src=src) - # buffer variable is now set to pickled obj in all processes - - if rank != src: - obj = pickle.loads(buffer.cpu().numpy().tobytes()) - logger.debug(f"Shared object of size {len(buffer)}") - return obj - - -def loader(dataset, *args, shuffle=False, klass=DataLoader, **kwargs): - """ - Create a dataloader properly in case of distributed training. - If a gradient is going to be computed you must set `shuffle=True`. - """ - if world_size == 1: - return klass(dataset, *args, shuffle=shuffle, **kwargs) - - if shuffle: - # train means we will compute backward, we use DistributedSampler - sampler = DistributedSampler(dataset) - # We ignore shuffle, DistributedSampler already shuffles - return klass(dataset, *args, **kwargs, sampler=sampler) - else: - # We make a manual shard, as DistributedSampler otherwise replicate some examples - dataset = Subset(dataset, list(range(rank, len(dataset), world_size))) - return klass(dataset, *args, shuffle=shuffle, **kwargs) diff --git a/demucs/demucs/ema.py b/demucs/demucs/ema.py deleted file mode 100644 index 101bee02..00000000 --- a/demucs/demucs/ema.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -# Inspired from https://github.com/rwightman/pytorch-image-models -from contextlib import contextmanager - -import torch - -from .states import swap_state - - -class ModelEMA: - """ - Perform EMA on a model. You can switch to the EMA weights temporarily - with the `swap` method. - - ema = ModelEMA(model) - with ema.swap(): - # compute valid metrics with averaged model. - """ - def __init__(self, model, decay=0.9999, unbias=True, device='cpu'): - self.decay = decay - self.model = model - self.state = {} - self.count = 0 - self.device = device - self.unbias = unbias - - self._init() - - def _init(self): - for key, val in self.model.state_dict().items(): - if val.dtype != torch.float32: - continue - device = self.device or val.device - if key not in self.state: - self.state[key] = val.detach().to(device, copy=True) - - def update(self): - if self.unbias: - self.count = self.count * self.decay + 1 - w = 1 / self.count - else: - w = 1 - self.decay - for key, val in self.model.state_dict().items(): - if val.dtype != torch.float32: - continue - device = self.device or val.device - self.state[key].mul_(1 - w) - self.state[key].add_(val.detach().to(device), alpha=w) - - @contextmanager - def swap(self): - with swap_state(self.model, self.state): - yield - - def state_dict(self): - return {'state': self.state, 'count': self.count} - - def load_state_dict(self, state): - self.count = state['count'] - for k, v in state['state'].items(): - self.state[k].copy_(v) diff --git a/demucs/demucs/evaluate.py b/demucs/demucs/evaluate.py deleted file mode 100755 index fa2ff453..00000000 --- a/demucs/demucs/evaluate.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -"""Test time evaluation, either using the original SDR from [Vincent et al. 2006] -or the newest SDR definition from the MDX 2021 competition (this one will -be reported as `nsdr` for `new sdr`). -""" - -from concurrent import futures -import logging - -from dora.log import LogProgress -import numpy as np -import musdb -import museval -import torch as th - -from .apply import apply_model -from .audio import convert_audio, save_audio -from . import distrib -from .utils import DummyPoolExecutor - - -logger = logging.getLogger(__name__) - - -def new_sdr(references, estimates): - """ - Compute the SDR according to the MDX challenge definition. - Adapted from AIcrowd/music-demixing-challenge-starter-kit (MIT license) - """ - assert references.dim() == 4 - assert estimates.dim() == 4 - delta = 1e-7 # avoid numerical errors - num = th.sum(th.square(references), dim=(2, 3)) - den = th.sum(th.square(references - estimates), dim=(2, 3)) - num += delta - den += delta - scores = 10 * th.log10(num / den) - return scores - - -def eval_track(references, estimates, win, hop, compute_sdr=True): - references = references.transpose(1, 2).double() - estimates = estimates.transpose(1, 2).double() - - new_scores = new_sdr(references.cpu()[None], estimates.cpu()[None])[0] - - if not compute_sdr: - return None, new_scores - else: - references = references.numpy() - estimates = estimates.numpy() - scores = museval.metrics.bss_eval( - references, estimates, - compute_permutation=False, - window=win, - hop=hop, - framewise_filters=False, - bsseval_sources_version=False)[:-1] - return scores, new_scores - - -def evaluate(solver, compute_sdr=False): - """ - Evaluate model using museval. - compute_sdr=False means using only the MDX definition of the SDR, which - is much faster to evaluate. - """ - - args = solver.args - - output_dir = solver.folder / "results" - output_dir.mkdir(exist_ok=True, parents=True) - json_folder = solver.folder / "results/test" - json_folder.mkdir(exist_ok=True, parents=True) - - # we load tracks from the original musdb set - if args.test.nonhq is None: - test_set = musdb.DB(args.dset.musdb, subsets=["test"], is_wav=True) - else: - test_set = musdb.DB(args.test.nonhq, subsets=["test"], is_wav=False) - src_rate = args.dset.musdb_samplerate - - eval_device = 'cpu' - - model = solver.model - win = int(1. * model.samplerate) - hop = int(1. * model.samplerate) - - indexes = range(distrib.rank, len(test_set), distrib.world_size) - indexes = LogProgress(logger, indexes, updates=args.misc.num_prints, - name='Eval') - pendings = [] - - pool = futures.ProcessPoolExecutor if args.test.workers else DummyPoolExecutor - with pool(args.test.workers) as pool: - for index in indexes: - track = test_set.tracks[index] - - mix = th.from_numpy(track.audio).t().float() - if mix.dim() == 1: - mix = mix[None] - mix = mix.to(solver.device) - ref = mix.mean(dim=0) # mono mixture - mix = (mix - ref.mean()) / ref.std() - mix = convert_audio(mix, src_rate, model.samplerate, model.audio_channels) - estimates = apply_model(model, mix[None], - shifts=args.test.shifts, split=args.test.split, - overlap=args.test.overlap)[0] - estimates = estimates * ref.std() + ref.mean() - estimates = estimates.to(eval_device) - - references = th.stack( - [th.from_numpy(track.targets[name].audio).t() for name in model.sources]) - if references.dim() == 2: - references = references[:, None] - references = references.to(eval_device) - references = convert_audio(references, src_rate, - model.samplerate, model.audio_channels) - if args.test.save: - folder = solver.folder / "wav" / track.name - folder.mkdir(exist_ok=True, parents=True) - for name, estimate in zip(model.sources, estimates): - save_audio(estimate.cpu(), folder / (name + ".mp3"), model.samplerate) - - pendings.append((track.name, pool.submit( - eval_track, references, estimates, win=win, hop=hop, compute_sdr=compute_sdr))) - - pendings = LogProgress(logger, pendings, updates=args.misc.num_prints, - name='Eval (BSS)') - tracks = {} - for track_name, pending in pendings: - pending = pending.result() - scores, nsdrs = pending - tracks[track_name] = {} - for idx, target in enumerate(model.sources): - tracks[track_name][target] = {'nsdr': [float(nsdrs[idx])]} - if scores is not None: - (sdr, isr, sir, sar) = scores - for idx, target in enumerate(model.sources): - values = { - "SDR": sdr[idx].tolist(), - "SIR": sir[idx].tolist(), - "ISR": isr[idx].tolist(), - "SAR": sar[idx].tolist() - } - tracks[track_name][target].update(values) - - all_tracks = {} - for src in range(distrib.world_size): - all_tracks.update(distrib.share(tracks, src)) - - result = {} - metric_names = next(iter(all_tracks.values()))[model.sources[0]] - for metric_name in metric_names: - avg = 0 - avg_of_medians = 0 - for source in model.sources: - medians = [ - np.nanmedian(all_tracks[track][source][metric_name]) - for track in all_tracks.keys()] - mean = np.mean(medians) - median = np.median(medians) - result[metric_name.lower() + "_" + source] = mean - result[metric_name.lower() + "_med" + "_" + source] = median - avg += mean / len(model.sources) - avg_of_medians += median / len(model.sources) - result[metric_name.lower()] = avg - result[metric_name.lower() + "_med"] = avg_of_medians - return result diff --git a/demucs/demucs/grids/__init__.py b/demucs/demucs/grids/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/demucs/demucs/grids/_explorers.py b/demucs/demucs/grids/_explorers.py deleted file mode 100644 index ec3a858d..00000000 --- a/demucs/demucs/grids/_explorers.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -from dora import Explorer -import treetable as tt - - -class MyExplorer(Explorer): - test_metrics = ['nsdr', 'sdr_med'] - - def get_grid_metrics(self): - """Return the metrics that should be displayed in the tracking table. - """ - return [ - tt.group("train", [ - tt.leaf("epoch"), - tt.leaf("reco", ".3f"), - ], align=">"), - tt.group("valid", [ - tt.leaf("penalty", ".1f"), - tt.leaf("ms", ".1f"), - tt.leaf("reco", ".2%"), - tt.leaf("breco", ".2%"), - tt.leaf("b_nsdr", ".2f"), - # tt.leaf("b_nsdr_drums", ".2f"), - # tt.leaf("b_nsdr_bass", ".2f"), - # tt.leaf("b_nsdr_other", ".2f"), - # tt.leaf("b_nsdr_vocals", ".2f"), - ], align=">"), - tt.group("test", [ - tt.leaf(name, ".2f") - for name in self.test_metrics - ], align=">") - ] - - def process_history(self, history): - train = { - 'epoch': len(history), - } - valid = {} - test = {} - best_v_main = float('inf') - breco = float('inf') - for metrics in history: - train.update(metrics['train']) - valid.update(metrics['valid']) - if 'main' in metrics['valid']: - best_v_main = min(best_v_main, metrics['valid']['main']['loss']) - valid['bmain'] = best_v_main - valid['breco'] = min(breco, metrics['valid']['reco']) - breco = valid['breco'] - if (metrics['valid']['loss'] == metrics['valid']['best'] or - metrics['valid'].get('nsdr') == metrics['valid']['best']): - for k, v in metrics['valid'].items(): - if k.startswith('reco_'): - valid['b_' + k[len('reco_'):]] = v - if k.startswith('nsdr'): - valid[f'b_{k}'] = v - if 'test' in metrics: - test.update(metrics['test']) - metrics = history[-1] - return {"train": train, "valid": valid, "test": test} diff --git a/demucs/demucs/grids/mdx.py b/demucs/demucs/grids/mdx.py deleted file mode 100644 index 62d447f1..00000000 --- a/demucs/demucs/grids/mdx.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -Main training for the Track A MDX models. -""" - -from ._explorers import MyExplorer -from ..train import main - - -TRACK_A = ['0d19c1c6', '7ecf8ec1', 'c511e2ab', '7d865c68'] - - -@MyExplorer -def explorer(launcher): - launcher.slurm_( - gpus=8, - time=3 * 24 * 60, - partition='learnlab') - - # Reproduce results from MDX competition Track A - # This trains the first round of models. Once this is trained, - # you will need to schedule `mdx_refine`. - for sig in TRACK_A: - xp = main.get_xp_from_sig(sig) - parent = xp.cfg.continue_from - xp = main.get_xp_from_sig(parent) - launcher(xp.argv) - launcher(xp.argv, {'quant.diffq': 1e-4}) - launcher(xp.argv, {'quant.diffq': 3e-4}) diff --git a/demucs/demucs/grids/mdx_extra.py b/demucs/demucs/grids/mdx_extra.py deleted file mode 100644 index b99a37b0..00000000 --- a/demucs/demucs/grids/mdx_extra.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -Main training for the Track A MDX models. -""" - -from ._explorers import MyExplorer -from ..train import main - -TRACK_B = ['e51eebcc', 'a1d90b5c', '5d2d6c55', 'cfa93e08'] - - -@MyExplorer -def explorer(launcher): - launcher.slurm_( - gpus=8, - time=3 * 24 * 60, - partition='learnlab') - - # Reproduce results from MDX competition Track A - # This trains the first round of models. Once this is trained, - # you will need to schedule `mdx_refine`. - for sig in TRACK_B: - while sig is not None: - xp = main.get_xp_from_sig(sig) - sig = xp.cfg.continue_from - - for dset in ['extra44', 'extra_test']: - sub = launcher.bind(xp.argv, dset=dset) - sub() - if dset == 'extra_test': - sub({'quant.diffq': 1e-4}) - sub({'quant.diffq': 3e-4}) diff --git a/demucs/demucs/grids/mdx_refine.py b/demucs/demucs/grids/mdx_refine.py deleted file mode 100644 index f62da1de..00000000 --- a/demucs/demucs/grids/mdx_refine.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -Main training for the Track A MDX models. -""" - -from ._explorers import MyExplorer -from .mdx import TRACK_A -from ..train import main - - -@MyExplorer -def explorer(launcher): - launcher.slurm_( - gpus=8, - time=3 * 24 * 60, - partition='learnlab') - - # Reproduce results from MDX competition Track A - # WARNING: all the experiments in the `mdx` grid must have completed. - for sig in TRACK_A: - xp = main.get_xp_from_sig(sig) - launcher(xp.argv) - for diffq in [1e-4, 3e-4]: - xp_src = main.get_xp_from_sig(xp.cfg.continue_from) - q_argv = [f'quant.diffq={diffq}'] - actual_src = main.get_xp(xp_src.argv + q_argv) - actual_src.link.load() - assert len(actual_src.link.history) == actual_src.cfg.epochs - argv = xp.argv + q_argv + [f'continue_from="{actual_src.sig}"'] - launcher(argv) diff --git a/demucs/demucs/grids/mmi.py b/demucs/demucs/grids/mmi.py deleted file mode 100644 index d75aa2b6..00000000 --- a/demucs/demucs/grids/mmi.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -from ._explorers import MyExplorer -from dora import Launcher - - -@MyExplorer -def explorer(launcher: Launcher): - launcher.slurm_(gpus=8, time=3 * 24 * 60, partition="devlab,learnlab,learnfair") # 3 days - - sub = launcher.bind_( - { - "dset": "extra_mmi_goodclean", - "test.shifts": 0, - "model": "htdemucs", - "htdemucs.dconv_mode": 3, - "htdemucs.depth": 4, - "htdemucs.t_dropout": 0.02, - "htdemucs.t_layers": 5, - "max_batches": 800, - "ema.epoch": [0.9, 0.95], - "ema.batch": [0.9995, 0.9999], - "dset.segment": 10, - "batch_size": 32, - } - ) - sub({"model": "hdemucs"}) - sub({"model": "hdemucs", "dset": "extra44"}) - sub({"model": "hdemucs", "dset": "musdb44"}) - - sparse = { - 'batch_size': 3 * 8, - 'augment.remix.group_size': 3, - 'htdemucs.t_auto_sparsity': True, - 'htdemucs.t_sparse_self_attn': True, - 'htdemucs.t_sparse_cross_attn': True, - 'htdemucs.t_sparsity': 0.9, - "htdemucs.t_layers": 7 - } - - with launcher.job_array(): - for transf_layers in [5, 7]: - for bottom_channels in [0, 512]: - sub = launcher.bind({ - "htdemucs.t_layers": transf_layers, - "htdemucs.bottom_channels": bottom_channels, - }) - if bottom_channels == 0 and transf_layers == 5: - sub({"augment.remix.proba": 0.0}) - sub({ - "augment.repitch.proba": 0.0, - # when doing repitching, we trim the outut to align on the - # highest change of BPM. When removing repitching, - # we simulate it here to ensure the training context is the same. - # Another second is lost for all experiments due to the random - # shift augmentation. - "dset.segment": 10 * 0.88}) - elif bottom_channels == 512 and transf_layers == 5: - sub(dset="musdb44") - sub(dset="extra44") - # Sparse kernel XP, currently not released as kernels are still experimental. - sub(sparse, {'dset.segment': 15, "htdemucs.t_layers": 7}) - - for duration in [5, 10, 15]: - sub({"dset.segment": duration}) diff --git a/demucs/demucs/grids/mmi_ft.py b/demucs/demucs/grids/mmi_ft.py deleted file mode 100644 index 73e488b5..00000000 --- a/demucs/demucs/grids/mmi_ft.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -from ._explorers import MyExplorer -from dora import Launcher -from demucs import train - - -def get_sub(launcher, sig): - xp = train.main.get_xp_from_sig(sig) - sub = launcher.bind(xp.argv) - sub() - sub.bind_({ - 'continue_from': sig, - 'continue_best': True}) - return sub - - -@MyExplorer -def explorer(launcher: Launcher): - launcher.slurm_(gpus=4, time=3 * 24 * 60, partition="devlab,learnlab,learnfair") # 3 days - ft = { - 'optim.lr': 1e-4, - 'augment.remix.proba': 0, - 'augment.scale.proba': 0, - 'augment.shift_same': True, - 'htdemucs.t_weight_decay': 0.05, - 'batch_size': 8, - 'optim.clip_grad': 5, - 'optim.optim': 'adamw', - 'epochs': 50, - 'dset.wav2_valid': True, - 'ema.epoch': [], # let's make valid a bit faster - } - with launcher.job_array(): - for sig in ['2899e11a']: - sub = get_sub(launcher, sig) - sub.bind_(ft) - for segment in [15, 18]: - for source in range(4): - w = [0] * 4 - w[source] = 1 - sub({'weights': w, 'dset.segment': segment}) - - for sig in ['955717e8']: - sub = get_sub(launcher, sig) - sub.bind_(ft) - for segment in [10, 15]: - for source in range(4): - w = [0] * 4 - w[source] = 1 - sub({'weights': w, 'dset.segment': segment}) diff --git a/demucs/demucs/grids/repro.py b/demucs/demucs/grids/repro.py deleted file mode 100644 index 21d33fce..00000000 --- a/demucs/demucs/grids/repro.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -Easier training for reproducibility -""" - -from ._explorers import MyExplorer - - -@MyExplorer -def explorer(launcher): - launcher.slurm_( - gpus=8, - time=3 * 24 * 60, - partition='devlab,learnlab') - - launcher.bind_({'ema.epoch': [0.9, 0.95]}) - launcher.bind_({'ema.batch': [0.9995, 0.9999]}) - launcher.bind_({'epochs': 600}) - - base = {'model': 'demucs', 'demucs.dconv_mode': 0, 'demucs.gelu': False, - 'demucs.lstm_layers': 2} - newt = {'model': 'demucs', 'demucs.normalize': True} - hdem = {'model': 'hdemucs'} - svd = {'svd.penalty': 1e-5, 'svd': 'base2'} - - with launcher.job_array(): - for model in [base, newt, hdem]: - sub = launcher.bind(model) - if model is base: - # Training the v2 Demucs on MusDB HQ - sub(epochs=360) - continue - - # those two will be used in the repro_mdx_a bag of models. - sub(svd) - sub(svd, seed=43) - if model == newt: - # Ablation study - sub() - abl = sub.bind(svd) - abl({'ema.epoch': [], 'ema.batch': []}) - abl({'demucs.dconv_lstm': 10}) - abl({'demucs.dconv_attn': 10}) - abl({'demucs.dconv_attn': 10, 'demucs.dconv_lstm': 10, 'demucs.lstm_layers': 2}) - abl({'demucs.dconv_mode': 0}) - abl({'demucs.gelu': False}) diff --git a/demucs/demucs/grids/repro_ft.py b/demucs/demucs/grids/repro_ft.py deleted file mode 100644 index 7bb4ee89..00000000 --- a/demucs/demucs/grids/repro_ft.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -Fine tuning experiments -""" - -from ._explorers import MyExplorer -from ..train import main - - -@MyExplorer -def explorer(launcher): - launcher.slurm_( - gpus=8, - time=300, - partition='devlab,learnlab') - - # Mus - launcher.slurm_(constraint='volta32gb') - - grid = "repro" - folder = main.dora.dir / "grids" / grid - - for sig in folder.iterdir(): - if not sig.is_symlink(): - continue - xp = main.get_xp_from_sig(sig) - xp.link.load() - if len(xp.link.history) != xp.cfg.epochs: - continue - sub = launcher.bind(xp.argv, [f'continue_from="{xp.sig}"']) - sub.bind_({'ema.epoch': [0.9, 0.95], 'ema.batch': [0.9995, 0.9999]}) - sub.bind_({'test.every': 1, 'test.sdr': True, 'epochs': 4}) - sub.bind_({'dset.segment': 28, 'dset.shift': 2}) - sub.bind_({'batch_size': 32}) - auto = {'dset': 'auto_mus'} - auto.update({'augment.remix.proba': 0, 'augment.scale.proba': 0, - 'augment.shift_same': True}) - sub.bind_(auto) - sub.bind_({'batch_size': 16}) - sub.bind_({'optim.lr': 1e-4}) - sub.bind_({'model_segment': 44}) - sub() diff --git a/demucs/demucs/grids/sdx23.py b/demucs/demucs/grids/sdx23.py deleted file mode 100644 index 3bdb4191..00000000 --- a/demucs/demucs/grids/sdx23.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -from ._explorers import MyExplorer -from dora import Launcher - - -@MyExplorer -def explorer(launcher: Launcher): - launcher.slurm_(gpus=8, time=3 * 24 * 60, partition="speechgpt,learnfair", - mem_per_gpu=None, constraint='') - launcher.bind_({"dset.use_musdb": False}) - - with launcher.job_array(): - launcher(dset='sdx23_bleeding') - launcher(dset='sdx23_labelnoise') diff --git a/demucs/demucs/hdemucs.py b/demucs/demucs/hdemucs.py deleted file mode 100644 index 9992b60a..00000000 --- a/demucs/demucs/hdemucs.py +++ /dev/null @@ -1,796 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -This code contains the spectrogram and Hybrid version of Demucs. -""" -from copy import deepcopy -import math -import typing as tp - -from openunmix.filtering import wiener -import torch -from torch import nn -from torch.nn import functional as F - -from .demucs import DConv, rescale_module -from .states import capture_init -from .spec import spectro, ispectro - - -def pad1d(x: torch.Tensor, paddings: tp.Tuple[int, int], mode: str = 'constant', value: float = 0.): - """Tiny wrapper around F.pad, just to allow for reflect padding on small input. - If this is the case, we insert extra 0 padding to the right before the reflection happen.""" - x0 = x - length = x.shape[-1] - padding_left, padding_right = paddings - if mode == 'reflect': - max_pad = max(padding_left, padding_right) - if length <= max_pad: - extra_pad = max_pad - length + 1 - extra_pad_right = min(padding_right, extra_pad) - extra_pad_left = extra_pad - extra_pad_right - paddings = (padding_left - extra_pad_left, padding_right - extra_pad_right) - x = F.pad(x, (extra_pad_left, extra_pad_right)) - out = F.pad(x, paddings, mode, value) - assert out.shape[-1] == length + padding_left + padding_right - assert (out[..., padding_left: padding_left + length] == x0).all() - return out - - -class ScaledEmbedding(nn.Module): - """ - Boost learning rate for embeddings (with `scale`). - Also, can make embeddings continuous with `smooth`. - """ - def __init__(self, num_embeddings: int, embedding_dim: int, - scale: float = 10., smooth=False): - super().__init__() - self.embedding = nn.Embedding(num_embeddings, embedding_dim) - if smooth: - weight = torch.cumsum(self.embedding.weight.data, dim=0) - # when summing gaussian, overscale raises as sqrt(n), so we nornalize by that. - weight = weight / torch.arange(1, num_embeddings + 1).to(weight).sqrt()[:, None] - self.embedding.weight.data[:] = weight - self.embedding.weight.data /= scale - self.scale = scale - - @property - def weight(self): - return self.embedding.weight * self.scale - - def forward(self, x): - out = self.embedding(x) * self.scale - return out - - -class HEncLayer(nn.Module): - def __init__(self, chin, chout, kernel_size=8, stride=4, norm_groups=1, empty=False, - freq=True, dconv=True, norm=True, context=0, dconv_kw={}, pad=True, - rewrite=True): - """Encoder layer. This used both by the time and the frequency branch. - - Args: - chin: number of input channels. - chout: number of output channels. - norm_groups: number of groups for group norm. - empty: used to make a layer with just the first conv. this is used - before merging the time and freq. branches. - freq: this is acting on frequencies. - dconv: insert DConv residual branches. - norm: use GroupNorm. - context: context size for the 1x1 conv. - dconv_kw: list of kwargs for the DConv class. - pad: pad the input. Padding is done so that the output size is - always the input size / stride. - rewrite: add 1x1 conv at the end of the layer. - """ - super().__init__() - norm_fn = lambda d: nn.Identity() # noqa - if norm: - norm_fn = lambda d: nn.GroupNorm(norm_groups, d) # noqa - if pad: - pad = kernel_size // 4 - else: - pad = 0 - klass = nn.Conv1d - self.freq = freq - self.kernel_size = kernel_size - self.stride = stride - self.empty = empty - self.norm = norm - self.pad = pad - if freq: - kernel_size = [kernel_size, 1] - stride = [stride, 1] - pad = [pad, 0] - klass = nn.Conv2d - self.conv = klass(chin, chout, kernel_size, stride, pad) - if self.empty: - return - self.norm1 = norm_fn(chout) - self.rewrite = None - if rewrite: - self.rewrite = klass(chout, 2 * chout, 1 + 2 * context, 1, context) - self.norm2 = norm_fn(2 * chout) - - self.dconv = None - if dconv: - self.dconv = DConv(chout, **dconv_kw) - - def forward(self, x, inject=None): - """ - `inject` is used to inject the result from the time branch into the frequency branch, - when both have the same stride. - """ - if not self.freq and x.dim() == 4: - B, C, Fr, T = x.shape - x = x.view(B, -1, T) - - if not self.freq: - le = x.shape[-1] - if not le % self.stride == 0: - x = F.pad(x, (0, self.stride - (le % self.stride))) - y = self.conv(x) - if self.empty: - return y - if inject is not None: - assert inject.shape[-1] == y.shape[-1], (inject.shape, y.shape) - if inject.dim() == 3 and y.dim() == 4: - inject = inject[:, :, None] - y = y + inject - y = F.gelu(self.norm1(y)) - if self.dconv: - if self.freq: - B, C, Fr, T = y.shape - y = y.permute(0, 2, 1, 3).reshape(-1, C, T) - y = self.dconv(y) - if self.freq: - y = y.view(B, Fr, C, T).permute(0, 2, 1, 3) - if self.rewrite: - z = self.norm2(self.rewrite(y)) - z = F.glu(z, dim=1) - else: - z = y - return z - - -class MultiWrap(nn.Module): - """ - Takes one layer and replicate it N times. each replica will act - on a frequency band. All is done so that if the N replica have the same weights, - then this is exactly equivalent to applying the original module on all frequencies. - - This is a bit over-engineered to avoid edge artifacts when splitting - the frequency bands, but it is possible the naive implementation would work as well... - """ - def __init__(self, layer, split_ratios): - """ - Args: - layer: module to clone, must be either HEncLayer or HDecLayer. - split_ratios: list of float indicating which ratio to keep for each band. - """ - super().__init__() - self.split_ratios = split_ratios - self.layers = nn.ModuleList() - self.conv = isinstance(layer, HEncLayer) - assert not layer.norm - assert layer.freq - assert layer.pad - if not self.conv: - assert not layer.context_freq - for k in range(len(split_ratios) + 1): - lay = deepcopy(layer) - if self.conv: - lay.conv.padding = (0, 0) - else: - lay.pad = False - for m in lay.modules(): - if hasattr(m, 'reset_parameters'): - m.reset_parameters() - self.layers.append(lay) - - def forward(self, x, skip=None, length=None): - B, C, Fr, T = x.shape - - ratios = list(self.split_ratios) + [1] - start = 0 - outs = [] - for ratio, layer in zip(ratios, self.layers): - if self.conv: - pad = layer.kernel_size // 4 - if ratio == 1: - limit = Fr - frames = -1 - else: - limit = int(round(Fr * ratio)) - le = limit - start - if start == 0: - le += pad - frames = round((le - layer.kernel_size) / layer.stride + 1) - limit = start + (frames - 1) * layer.stride + layer.kernel_size - if start == 0: - limit -= pad - assert limit - start > 0, (limit, start) - assert limit <= Fr, (limit, Fr) - y = x[:, :, start:limit, :] - if start == 0: - y = F.pad(y, (0, 0, pad, 0)) - if ratio == 1: - y = F.pad(y, (0, 0, 0, pad)) - outs.append(layer(y)) - start = limit - layer.kernel_size + layer.stride - else: - if ratio == 1: - limit = Fr - else: - limit = int(round(Fr * ratio)) - last = layer.last - layer.last = True - - y = x[:, :, start:limit] - s = skip[:, :, start:limit] - out, _ = layer(y, s, None) - if outs: - outs[-1][:, :, -layer.stride:] += ( - out[:, :, :layer.stride] - layer.conv_tr.bias.view(1, -1, 1, 1)) - out = out[:, :, layer.stride:] - if ratio == 1: - out = out[:, :, :-layer.stride // 2, :] - if start == 0: - out = out[:, :, layer.stride // 2:, :] - outs.append(out) - layer.last = last - start = limit - out = torch.cat(outs, dim=2) - if not self.conv and not last: - out = F.gelu(out) - if self.conv: - return out - else: - return out, None - - -class HDecLayer(nn.Module): - def __init__(self, chin, chout, last=False, kernel_size=8, stride=4, norm_groups=1, empty=False, - freq=True, dconv=True, norm=True, context=1, dconv_kw={}, pad=True, - context_freq=True, rewrite=True): - """ - Same as HEncLayer but for decoder. See `HEncLayer` for documentation. - """ - super().__init__() - norm_fn = lambda d: nn.Identity() # noqa - if norm: - norm_fn = lambda d: nn.GroupNorm(norm_groups, d) # noqa - if pad: - pad = kernel_size // 4 - else: - pad = 0 - self.pad = pad - self.last = last - self.freq = freq - self.chin = chin - self.empty = empty - self.stride = stride - self.kernel_size = kernel_size - self.norm = norm - self.context_freq = context_freq - klass = nn.Conv1d - klass_tr = nn.ConvTranspose1d - if freq: - kernel_size = [kernel_size, 1] - stride = [stride, 1] - klass = nn.Conv2d - klass_tr = nn.ConvTranspose2d - self.conv_tr = klass_tr(chin, chout, kernel_size, stride) - self.norm2 = norm_fn(chout) - if self.empty: - return - self.rewrite = None - if rewrite: - if context_freq: - self.rewrite = klass(chin, 2 * chin, 1 + 2 * context, 1, context) - else: - self.rewrite = klass(chin, 2 * chin, [1, 1 + 2 * context], 1, - [0, context]) - self.norm1 = norm_fn(2 * chin) - - self.dconv = None - if dconv: - self.dconv = DConv(chin, **dconv_kw) - - def forward(self, x, skip, length): - if self.freq and x.dim() == 3: - B, C, T = x.shape - x = x.view(B, self.chin, -1, T) - - if not self.empty: - x = x + skip - - if self.rewrite: - y = F.glu(self.norm1(self.rewrite(x)), dim=1) - else: - y = x - if self.dconv: - if self.freq: - B, C, Fr, T = y.shape - y = y.permute(0, 2, 1, 3).reshape(-1, C, T) - y = self.dconv(y) - if self.freq: - y = y.view(B, Fr, C, T).permute(0, 2, 1, 3) - else: - y = x - assert skip is None - z = self.norm2(self.conv_tr(y)) - if self.freq: - if self.pad: - z = z[..., self.pad:-self.pad, :] - else: - z = z[..., self.pad:self.pad + length] - assert z.shape[-1] == length, (z.shape[-1], length) - if not self.last: - z = F.gelu(z) - return z, y - - -class HDemucs(nn.Module): - """ - Spectrogram and hybrid Demucs model. - The spectrogram model has the same structure as Demucs, except the first few layers are over the - frequency axis, until there is only 1 frequency, and then it moves to time convolutions. - Frequency layers can still access information across time steps thanks to the DConv residual. - - Hybrid model have a parallel time branch. At some layer, the time branch has the same stride - as the frequency branch and then the two are combined. The opposite happens in the decoder. - - Models can either use naive iSTFT from masking, Wiener filtering ([Ulhih et al. 2017]), - or complex as channels (CaC) [Choi et al. 2020]. Wiener filtering is based on - Open Unmix implementation [Stoter et al. 2019]. - - The loss is always on the temporal domain, by backpropagating through the above - output methods and iSTFT. This allows to define hybrid models nicely. However, this breaks - a bit Wiener filtering, as doing more iteration at test time will change the spectrogram - contribution, without changing the one from the waveform, which will lead to worse performance. - I tried using the residual option in OpenUnmix Wiener implementation, but it didn't improve. - CaC on the other hand provides similar performance for hybrid, and works naturally with - hybrid models. - - This model also uses frequency embeddings are used to improve efficiency on convolutions - over the freq. axis, following [Isik et al. 2020] (https://arxiv.org/pdf/2008.04470.pdf). - - Unlike classic Demucs, there is no resampling here, and normalization is always applied. - """ - @capture_init - def __init__(self, - sources, - # Channels - audio_channels=2, - channels=48, - channels_time=None, - growth=2, - # STFT - nfft=4096, - wiener_iters=0, - end_iters=0, - wiener_residual=False, - cac=True, - # Main structure - depth=6, - rewrite=True, - hybrid=True, - hybrid_old=False, - # Frequency branch - multi_freqs=None, - multi_freqs_depth=2, - freq_emb=0.2, - emb_scale=10, - emb_smooth=True, - # Convolutions - kernel_size=8, - time_stride=2, - stride=4, - context=1, - context_enc=0, - # Normalization - norm_starts=4, - norm_groups=4, - # DConv residual branch - dconv_mode=1, - dconv_depth=2, - dconv_comp=4, - dconv_attn=4, - dconv_lstm=4, - dconv_init=1e-4, - # Weight init - rescale=0.1, - # Metadata - samplerate=44100, - segment=4 * 10): - """ - Args: - sources (list[str]): list of source names. - audio_channels (int): input/output audio channels. - channels (int): initial number of hidden channels. - channels_time: if not None, use a different `channels` value for the time branch. - growth: increase the number of hidden channels by this factor at each layer. - nfft: number of fft bins. Note that changing this require careful computation of - various shape parameters and will not work out of the box for hybrid models. - wiener_iters: when using Wiener filtering, number of iterations at test time. - end_iters: same but at train time. For a hybrid model, must be equal to `wiener_iters`. - wiener_residual: add residual source before wiener filtering. - cac: uses complex as channels, i.e. complex numbers are 2 channels each - in input and output. no further processing is done before ISTFT. - depth (int): number of layers in the encoder and in the decoder. - rewrite (bool): add 1x1 convolution to each layer. - hybrid (bool): make a hybrid time/frequency domain, otherwise frequency only. - hybrid_old: some models trained for MDX had a padding bug. This replicates - this bug to avoid retraining them. - multi_freqs: list of frequency ratios for splitting frequency bands with `MultiWrap`. - multi_freqs_depth: how many layers to wrap with `MultiWrap`. Only the outermost - layers will be wrapped. - freq_emb: add frequency embedding after the first frequency layer if > 0, - the actual value controls the weight of the embedding. - emb_scale: equivalent to scaling the embedding learning rate - emb_smooth: initialize the embedding with a smooth one (with respect to frequencies). - kernel_size: kernel_size for encoder and decoder layers. - stride: stride for encoder and decoder layers. - time_stride: stride for the final time layer, after the merge. - context: context for 1x1 conv in the decoder. - context_enc: context for 1x1 conv in the encoder. - norm_starts: layer at which group norm starts being used. - decoder layers are numbered in reverse order. - norm_groups: number of groups for group norm. - dconv_mode: if 1: dconv in encoder only, 2: decoder only, 3: both. - dconv_depth: depth of residual DConv branch. - dconv_comp: compression of DConv branch. - dconv_attn: adds attention layers in DConv branch starting at this layer. - dconv_lstm: adds a LSTM layer in DConv branch starting at this layer. - dconv_init: initial scale for the DConv branch LayerScale. - rescale: weight recaling trick - - """ - super().__init__() - self.cac = cac - self.wiener_residual = wiener_residual - self.audio_channels = audio_channels - self.sources = sources - self.kernel_size = kernel_size - self.context = context - self.stride = stride - self.depth = depth - self.channels = channels - self.samplerate = samplerate - self.segment = segment - - self.nfft = nfft - self.hop_length = nfft // 4 - self.wiener_iters = wiener_iters - self.end_iters = end_iters - self.freq_emb = None - self.hybrid = hybrid - self.hybrid_old = hybrid_old - if hybrid_old: - assert hybrid, "hybrid_old must come with hybrid=True" - if hybrid: - assert wiener_iters == end_iters - - self.encoder = nn.ModuleList() - self.decoder = nn.ModuleList() - - if hybrid: - self.tencoder = nn.ModuleList() - self.tdecoder = nn.ModuleList() - - chin = audio_channels - chin_z = chin # number of channels for the freq branch - if self.cac: - chin_z *= 2 - chout = channels_time or channels - chout_z = channels - freqs = nfft // 2 - - for index in range(depth): - lstm = index >= dconv_lstm - attn = index >= dconv_attn - norm = index >= norm_starts - freq = freqs > 1 - stri = stride - ker = kernel_size - if not freq: - assert freqs == 1 - ker = time_stride * 2 - stri = time_stride - - pad = True - last_freq = False - if freq and freqs <= kernel_size: - ker = freqs - pad = False - last_freq = True - - kw = { - 'kernel_size': ker, - 'stride': stri, - 'freq': freq, - 'pad': pad, - 'norm': norm, - 'rewrite': rewrite, - 'norm_groups': norm_groups, - 'dconv_kw': { - 'lstm': lstm, - 'attn': attn, - 'depth': dconv_depth, - 'compress': dconv_comp, - 'init': dconv_init, - 'gelu': True, - } - } - kwt = dict(kw) - kwt['freq'] = 0 - kwt['kernel_size'] = kernel_size - kwt['stride'] = stride - kwt['pad'] = True - kw_dec = dict(kw) - multi = False - if multi_freqs and index < multi_freqs_depth: - multi = True - kw_dec['context_freq'] = False - - if last_freq: - chout_z = max(chout, chout_z) - chout = chout_z - - enc = HEncLayer(chin_z, chout_z, - dconv=dconv_mode & 1, context=context_enc, **kw) - if hybrid and freq: - tenc = HEncLayer(chin, chout, dconv=dconv_mode & 1, context=context_enc, - empty=last_freq, **kwt) - self.tencoder.append(tenc) - - if multi: - enc = MultiWrap(enc, multi_freqs) - self.encoder.append(enc) - if index == 0: - chin = self.audio_channels * len(self.sources) - chin_z = chin - if self.cac: - chin_z *= 2 - dec = HDecLayer(chout_z, chin_z, dconv=dconv_mode & 2, - last=index == 0, context=context, **kw_dec) - if multi: - dec = MultiWrap(dec, multi_freqs) - if hybrid and freq: - tdec = HDecLayer(chout, chin, dconv=dconv_mode & 2, empty=last_freq, - last=index == 0, context=context, **kwt) - self.tdecoder.insert(0, tdec) - self.decoder.insert(0, dec) - - chin = chout - chin_z = chout_z - chout = int(growth * chout) - chout_z = int(growth * chout_z) - if freq: - if freqs <= kernel_size: - freqs = 1 - else: - freqs //= stride - if index == 0 and freq_emb: - self.freq_emb = ScaledEmbedding( - freqs, chin_z, smooth=emb_smooth, scale=emb_scale) - self.freq_emb_scale = freq_emb - - if rescale: - rescale_module(self, reference=rescale) - - def _spec(self, x): - hl = self.hop_length - nfft = self.nfft - x0 = x # noqa - - if self.hybrid: - # We re-pad the signal in order to keep the property - # that the size of the output is exactly the size of the input - # divided by the stride (here hop_length), when divisible. - # This is achieved by padding by 1/4th of the kernel size (here nfft). - # which is not supported by torch.stft. - # Having all convolution operations follow this convention allow to easily - # align the time and frequency branches later on. - assert hl == nfft // 4 - le = int(math.ceil(x.shape[-1] / hl)) - pad = hl // 2 * 3 - if not self.hybrid_old: - x = pad1d(x, (pad, pad + le * hl - x.shape[-1]), mode='reflect') - else: - x = pad1d(x, (pad, pad + le * hl - x.shape[-1])) - - z = spectro(x, nfft, hl)[..., :-1, :] - if self.hybrid: - assert z.shape[-1] == le + 4, (z.shape, x.shape, le) - z = z[..., 2:2+le] - return z - - def _ispec(self, z, length=None, scale=0): - hl = self.hop_length // (4 ** scale) - z = F.pad(z, (0, 0, 0, 1)) - if self.hybrid: - z = F.pad(z, (2, 2)) - pad = hl // 2 * 3 - if not self.hybrid_old: - le = hl * int(math.ceil(length / hl)) + 2 * pad - else: - le = hl * int(math.ceil(length / hl)) - x = ispectro(z, hl, length=le) - if not self.hybrid_old: - x = x[..., pad:pad + length] - else: - x = x[..., :length] - else: - x = ispectro(z, hl, length) - return x - - def _magnitude(self, z): - # return the magnitude of the spectrogram, except when cac is True, - # in which case we just move the complex dimension to the channel one. - if self.cac: - B, C, Fr, T = z.shape - m = torch.view_as_real(z).permute(0, 1, 4, 2, 3) - m = m.reshape(B, C * 2, Fr, T) - else: - m = z.abs() - return m - - def _mask(self, z, m): - # Apply masking given the mixture spectrogram `z` and the estimated mask `m`. - # If `cac` is True, `m` is actually a full spectrogram and `z` is ignored. - niters = self.wiener_iters - if self.cac: - B, S, C, Fr, T = m.shape - out = m.view(B, S, -1, 2, Fr, T).permute(0, 1, 2, 4, 5, 3) - out = torch.view_as_complex(out.contiguous()) - return out - if self.training: - niters = self.end_iters - if niters < 0: - z = z[:, None] - return z / (1e-8 + z.abs()) * m - else: - return self._wiener(m, z, niters) - - def _wiener(self, mag_out, mix_stft, niters): - # apply wiener filtering from OpenUnmix. - init = mix_stft.dtype - wiener_win_len = 300 - residual = self.wiener_residual - - B, S, C, Fq, T = mag_out.shape - mag_out = mag_out.permute(0, 4, 3, 2, 1) - mix_stft = torch.view_as_real(mix_stft.permute(0, 3, 2, 1)) - - outs = [] - for sample in range(B): - pos = 0 - out = [] - for pos in range(0, T, wiener_win_len): - frame = slice(pos, pos + wiener_win_len) - z_out = wiener( - mag_out[sample, frame], mix_stft[sample, frame], niters, - residual=residual) - out.append(z_out.transpose(-1, -2)) - outs.append(torch.cat(out, dim=0)) - out = torch.view_as_complex(torch.stack(outs, 0)) - out = out.permute(0, 4, 3, 2, 1).contiguous() - if residual: - out = out[:, :-1] - assert list(out.shape) == [B, S, C, Fq, T] - return out.to(init) - - def forward(self, mix): - x = mix - length = x.shape[-1] - - z = self._spec(mix) - mag = self._magnitude(z).to(mix.device) - x = mag - - B, C, Fq, T = x.shape - - # unlike previous Demucs, we always normalize because it is easier. - mean = x.mean(dim=(1, 2, 3), keepdim=True) - std = x.std(dim=(1, 2, 3), keepdim=True) - x = (x - mean) / (1e-5 + std) - # x will be the freq. branch input. - - if self.hybrid: - # Prepare the time branch input. - xt = mix - meant = xt.mean(dim=(1, 2), keepdim=True) - stdt = xt.std(dim=(1, 2), keepdim=True) - xt = (xt - meant) / (1e-5 + stdt) - - # okay, this is a giant mess I know... - saved = [] # skip connections, freq. - saved_t = [] # skip connections, time. - lengths = [] # saved lengths to properly remove padding, freq branch. - lengths_t = [] # saved lengths for time branch. - for idx, encode in enumerate(self.encoder): - lengths.append(x.shape[-1]) - inject = None - if self.hybrid and idx < len(self.tencoder): - # we have not yet merged branches. - lengths_t.append(xt.shape[-1]) - tenc = self.tencoder[idx] - xt = tenc(xt) - if not tenc.empty: - # save for skip connection - saved_t.append(xt) - else: - # tenc contains just the first conv., so that now time and freq. - # branches have the same shape and can be merged. - inject = xt - x = encode(x, inject) - if idx == 0 and self.freq_emb is not None: - # add frequency embedding to allow for non equivariant convolutions - # over the frequency axis. - frs = torch.arange(x.shape[-2], device=x.device) - emb = self.freq_emb(frs).t()[None, :, :, None].expand_as(x) - x = x + self.freq_emb_scale * emb - - saved.append(x) - - x = torch.zeros_like(x) - if self.hybrid: - xt = torch.zeros_like(x) - # initialize everything to zero (signal will go through u-net skips). - - for idx, decode in enumerate(self.decoder): - skip = saved.pop(-1) - x, pre = decode(x, skip, lengths.pop(-1)) - # `pre` contains the output just before final transposed convolution, - # which is used when the freq. and time branch separate. - - if self.hybrid: - offset = self.depth - len(self.tdecoder) - if self.hybrid and idx >= offset: - tdec = self.tdecoder[idx - offset] - length_t = lengths_t.pop(-1) - if tdec.empty: - assert pre.shape[2] == 1, pre.shape - pre = pre[:, :, 0] - xt, _ = tdec(pre, None, length_t) - else: - skip = saved_t.pop(-1) - xt, _ = tdec(xt, skip, length_t) - - # Let's make sure we used all stored skip connections. - assert len(saved) == 0 - assert len(lengths_t) == 0 - assert len(saved_t) == 0 - - S = len(self.sources) - x = x.view(B, S, -1, Fq, T) - x = x * std[:, None] + mean[:, None] - - # to cpu as mps doesnt support complex numbers - # demucs issue #435 ##432 - # NOTE: in this case z already is on cpu - # TODO: remove this when mps supports complex numbers - x_is_mps_xpu = x.device.type in ["mps", "xpu"] - x_device = x.device - if x_is_mps_xpu: - x = x.cpu() - - zout = self._mask(z, x) - x = self._ispec(zout, length) - - # back to mps device - if x_is_mps_xpu: - x = x.to(x_device) - - - if self.hybrid: - xt = xt.view(B, S, -1, length) - xt = xt * stdt[:, None] + meant[:, None] - x = xt + x - return x diff --git a/demucs/demucs/htdemucs.py b/demucs/demucs/htdemucs.py deleted file mode 100644 index 56568608..00000000 --- a/demucs/demucs/htdemucs.py +++ /dev/null @@ -1,661 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -# First author is Simon Rouard. -""" -This code contains the spectrogram and Hybrid version of Demucs. -""" -import math - -from openunmix.filtering import wiener -import torch -from torch import nn -from torch.nn import functional as F -from fractions import Fraction -from einops import rearrange - -from .transformer import CrossTransformerEncoder - -from .demucs import rescale_module -from .states import capture_init -from .spec import spectro, ispectro -from .hdemucs import pad1d, ScaledEmbedding, HEncLayer, MultiWrap, HDecLayer - - -class HTDemucs(nn.Module): - """ - Spectrogram and hybrid Demucs model. - The spectrogram model has the same structure as Demucs, except the first few layers are over the - frequency axis, until there is only 1 frequency, and then it moves to time convolutions. - Frequency layers can still access information across time steps thanks to the DConv residual. - - Hybrid model have a parallel time branch. At some layer, the time branch has the same stride - as the frequency branch and then the two are combined. The opposite happens in the decoder. - - Models can either use naive iSTFT from masking, Wiener filtering ([Ulhih et al. 2017]), - or complex as channels (CaC) [Choi et al. 2020]. Wiener filtering is based on - Open Unmix implementation [Stoter et al. 2019]. - - The loss is always on the temporal domain, by backpropagating through the above - output methods and iSTFT. This allows to define hybrid models nicely. However, this breaks - a bit Wiener filtering, as doing more iteration at test time will change the spectrogram - contribution, without changing the one from the waveform, which will lead to worse performance. - I tried using the residual option in OpenUnmix Wiener implementation, but it didn't improve. - CaC on the other hand provides similar performance for hybrid, and works naturally with - hybrid models. - - This model also uses frequency embeddings are used to improve efficiency on convolutions - over the freq. axis, following [Isik et al. 2020] (https://arxiv.org/pdf/2008.04470.pdf). - - Unlike classic Demucs, there is no resampling here, and normalization is always applied. - """ - - @capture_init - def __init__( - self, - sources, - # Channels - audio_channels=2, - channels=48, - channels_time=None, - growth=2, - # STFT - nfft=4096, - wiener_iters=0, - end_iters=0, - wiener_residual=False, - cac=True, - # Main structure - depth=4, - rewrite=True, - # Frequency branch - multi_freqs=None, - multi_freqs_depth=3, - freq_emb=0.2, - emb_scale=10, - emb_smooth=True, - # Convolutions - kernel_size=8, - time_stride=2, - stride=4, - context=1, - context_enc=0, - # Normalization - norm_starts=4, - norm_groups=4, - # DConv residual branch - dconv_mode=1, - dconv_depth=2, - dconv_comp=8, - dconv_init=1e-3, - # Before the Transformer - bottom_channels=0, - # Transformer - t_layers=5, - t_emb="sin", - t_hidden_scale=4.0, - t_heads=8, - t_dropout=0.0, - t_max_positions=10000, - t_norm_in=True, - t_norm_in_group=False, - t_group_norm=False, - t_norm_first=True, - t_norm_out=True, - t_max_period=10000.0, - t_weight_decay=0.0, - t_lr=None, - t_layer_scale=True, - t_gelu=True, - t_weight_pos_embed=1.0, - t_sin_random_shift=0, - t_cape_mean_normalize=True, - t_cape_augment=True, - t_cape_glob_loc_scale=[5000.0, 1.0, 1.4], - t_sparse_self_attn=False, - t_sparse_cross_attn=False, - t_mask_type="diag", - t_mask_random_seed=42, - t_sparse_attn_window=500, - t_global_window=100, - t_sparsity=0.95, - t_auto_sparsity=False, - # ------ Particuliar parameters - t_cross_first=False, - # Weight init - rescale=0.1, - # Metadata - samplerate=44100, - segment=10, - use_train_segment=True, - ): - """ - Args: - sources (list[str]): list of source names. - audio_channels (int): input/output audio channels. - channels (int): initial number of hidden channels. - channels_time: if not None, use a different `channels` value for the time branch. - growth: increase the number of hidden channels by this factor at each layer. - nfft: number of fft bins. Note that changing this require careful computation of - various shape parameters and will not work out of the box for hybrid models. - wiener_iters: when using Wiener filtering, number of iterations at test time. - end_iters: same but at train time. For a hybrid model, must be equal to `wiener_iters`. - wiener_residual: add residual source before wiener filtering. - cac: uses complex as channels, i.e. complex numbers are 2 channels each - in input and output. no further processing is done before ISTFT. - depth (int): number of layers in the encoder and in the decoder. - rewrite (bool): add 1x1 convolution to each layer. - multi_freqs: list of frequency ratios for splitting frequency bands with `MultiWrap`. - multi_freqs_depth: how many layers to wrap with `MultiWrap`. Only the outermost - layers will be wrapped. - freq_emb: add frequency embedding after the first frequency layer if > 0, - the actual value controls the weight of the embedding. - emb_scale: equivalent to scaling the embedding learning rate - emb_smooth: initialize the embedding with a smooth one (with respect to frequencies). - kernel_size: kernel_size for encoder and decoder layers. - stride: stride for encoder and decoder layers. - time_stride: stride for the final time layer, after the merge. - context: context for 1x1 conv in the decoder. - context_enc: context for 1x1 conv in the encoder. - norm_starts: layer at which group norm starts being used. - decoder layers are numbered in reverse order. - norm_groups: number of groups for group norm. - dconv_mode: if 1: dconv in encoder only, 2: decoder only, 3: both. - dconv_depth: depth of residual DConv branch. - dconv_comp: compression of DConv branch. - dconv_attn: adds attention layers in DConv branch starting at this layer. - dconv_lstm: adds a LSTM layer in DConv branch starting at this layer. - dconv_init: initial scale for the DConv branch LayerScale. - bottom_channels: if >0 it adds a linear layer (1x1 Conv) before and after the - transformer in order to change the number of channels - t_layers: number of layers in each branch (waveform and spec) of the transformer - t_emb: "sin", "cape" or "scaled" - t_hidden_scale: the hidden scale of the Feedforward parts of the transformer - for instance if C = 384 (the number of channels in the transformer) and - t_hidden_scale = 4.0 then the intermediate layer of the FFN has dimension - 384 * 4 = 1536 - t_heads: number of heads for the transformer - t_dropout: dropout in the transformer - t_max_positions: max_positions for the "scaled" positional embedding, only - useful if t_emb="scaled" - t_norm_in: (bool) norm before addinf positional embedding and getting into the - transformer layers - t_norm_in_group: (bool) if True while t_norm_in=True, the norm is on all the - timesteps (GroupNorm with group=1) - t_group_norm: (bool) if True, the norms of the Encoder Layers are on all the - timesteps (GroupNorm with group=1) - t_norm_first: (bool) if True the norm is before the attention and before the FFN - t_norm_out: (bool) if True, there is a GroupNorm (group=1) at the end of each layer - t_max_period: (float) denominator in the sinusoidal embedding expression - t_weight_decay: (float) weight decay for the transformer - t_lr: (float) specific learning rate for the transformer - t_layer_scale: (bool) Layer Scale for the transformer - t_gelu: (bool) activations of the transformer are GeLU if True, ReLU else - t_weight_pos_embed: (float) weighting of the positional embedding - t_cape_mean_normalize: (bool) if t_emb="cape", normalisation of positional embeddings - see: https://arxiv.org/abs/2106.03143 - t_cape_augment: (bool) if t_emb="cape", must be True during training and False - during the inference, see: https://arxiv.org/abs/2106.03143 - t_cape_glob_loc_scale: (list of 3 floats) if t_emb="cape", CAPE parameters - see: https://arxiv.org/abs/2106.03143 - t_sparse_self_attn: (bool) if True, the self attentions are sparse - t_sparse_cross_attn: (bool) if True, the cross-attentions are sparse (don't use it - unless you designed really specific masks) - t_mask_type: (str) can be "diag", "jmask", "random", "global" or any combination - with '_' between: i.e. "diag_jmask_random" (note that this is permutation - invariant i.e. "diag_jmask_random" is equivalent to "jmask_random_diag") - t_mask_random_seed: (int) if "random" is in t_mask_type, controls the seed - that generated the random part of the mask - t_sparse_attn_window: (int) if "diag" is in t_mask_type, for a query (i), and - a key (j), the mask is True id |i-j|<=t_sparse_attn_window - t_global_window: (int) if "global" is in t_mask_type, mask[:t_global_window, :] - and mask[:, :t_global_window] will be True - t_sparsity: (float) if "random" is in t_mask_type, t_sparsity is the sparsity - level of the random part of the mask. - t_cross_first: (bool) if True cross attention is the first layer of the - transformer (False seems to be better) - rescale: weight rescaling trick - use_train_segment: (bool) if True, the actual size that is used during the - training is used during inference. - """ - super().__init__() - self.cac = cac - self.wiener_residual = wiener_residual - self.audio_channels = audio_channels - self.sources = sources - self.kernel_size = kernel_size - self.context = context - self.stride = stride - self.depth = depth - self.bottom_channels = bottom_channels - self.channels = channels - self.samplerate = samplerate - self.segment = segment - self.use_train_segment = use_train_segment - self.nfft = nfft - self.hop_length = nfft // 4 - self.wiener_iters = wiener_iters - self.end_iters = end_iters - self.freq_emb = None - assert wiener_iters == end_iters - - self.encoder = nn.ModuleList() - self.decoder = nn.ModuleList() - - self.tencoder = nn.ModuleList() - self.tdecoder = nn.ModuleList() - - chin = audio_channels - chin_z = chin # number of channels for the freq branch - if self.cac: - chin_z *= 2 - chout = channels_time or channels - chout_z = channels - freqs = nfft // 2 - - for index in range(depth): - norm = index >= norm_starts - freq = freqs > 1 - stri = stride - ker = kernel_size - if not freq: - assert freqs == 1 - ker = time_stride * 2 - stri = time_stride - - pad = True - last_freq = False - if freq and freqs <= kernel_size: - ker = freqs - pad = False - last_freq = True - - kw = { - "kernel_size": ker, - "stride": stri, - "freq": freq, - "pad": pad, - "norm": norm, - "rewrite": rewrite, - "norm_groups": norm_groups, - "dconv_kw": { - "depth": dconv_depth, - "compress": dconv_comp, - "init": dconv_init, - "gelu": True, - }, - } - kwt = dict(kw) - kwt["freq"] = 0 - kwt["kernel_size"] = kernel_size - kwt["stride"] = stride - kwt["pad"] = True - kw_dec = dict(kw) - multi = False - if multi_freqs and index < multi_freqs_depth: - multi = True - kw_dec["context_freq"] = False - - if last_freq: - chout_z = max(chout, chout_z) - chout = chout_z - - enc = HEncLayer( - chin_z, chout_z, dconv=dconv_mode & 1, context=context_enc, **kw - ) - if freq: - tenc = HEncLayer( - chin, - chout, - dconv=dconv_mode & 1, - context=context_enc, - empty=last_freq, - **kwt - ) - self.tencoder.append(tenc) - - if multi: - enc = MultiWrap(enc, multi_freqs) - self.encoder.append(enc) - if index == 0: - chin = self.audio_channels * len(self.sources) - chin_z = chin - if self.cac: - chin_z *= 2 - dec = HDecLayer( - chout_z, - chin_z, - dconv=dconv_mode & 2, - last=index == 0, - context=context, - **kw_dec - ) - if multi: - dec = MultiWrap(dec, multi_freqs) - if freq: - tdec = HDecLayer( - chout, - chin, - dconv=dconv_mode & 2, - empty=last_freq, - last=index == 0, - context=context, - **kwt - ) - self.tdecoder.insert(0, tdec) - self.decoder.insert(0, dec) - - chin = chout - chin_z = chout_z - chout = int(growth * chout) - chout_z = int(growth * chout_z) - if freq: - if freqs <= kernel_size: - freqs = 1 - else: - freqs //= stride - if index == 0 and freq_emb: - self.freq_emb = ScaledEmbedding( - freqs, chin_z, smooth=emb_smooth, scale=emb_scale - ) - self.freq_emb_scale = freq_emb - - if rescale: - rescale_module(self, reference=rescale) - - transformer_channels = channels * growth ** (depth - 1) - if bottom_channels: - self.channel_upsampler = nn.Conv1d(transformer_channels, bottom_channels, 1) - self.channel_downsampler = nn.Conv1d( - bottom_channels, transformer_channels, 1 - ) - self.channel_upsampler_t = nn.Conv1d( - transformer_channels, bottom_channels, 1 - ) - self.channel_downsampler_t = nn.Conv1d( - bottom_channels, transformer_channels, 1 - ) - - transformer_channels = bottom_channels - - if t_layers > 0: - self.crosstransformer = CrossTransformerEncoder( - dim=transformer_channels, - emb=t_emb, - hidden_scale=t_hidden_scale, - num_heads=t_heads, - num_layers=t_layers, - cross_first=t_cross_first, - dropout=t_dropout, - max_positions=t_max_positions, - norm_in=t_norm_in, - norm_in_group=t_norm_in_group, - group_norm=t_group_norm, - norm_first=t_norm_first, - norm_out=t_norm_out, - max_period=t_max_period, - weight_decay=t_weight_decay, - lr=t_lr, - layer_scale=t_layer_scale, - gelu=t_gelu, - sin_random_shift=t_sin_random_shift, - weight_pos_embed=t_weight_pos_embed, - cape_mean_normalize=t_cape_mean_normalize, - cape_augment=t_cape_augment, - cape_glob_loc_scale=t_cape_glob_loc_scale, - sparse_self_attn=t_sparse_self_attn, - sparse_cross_attn=t_sparse_cross_attn, - mask_type=t_mask_type, - mask_random_seed=t_mask_random_seed, - sparse_attn_window=t_sparse_attn_window, - global_window=t_global_window, - sparsity=t_sparsity, - auto_sparsity=t_auto_sparsity, - ) - else: - self.crosstransformer = None - - def _spec(self, x): - hl = self.hop_length - nfft = self.nfft - x0 = x # noqa - - # We re-pad the signal in order to keep the property - # that the size of the output is exactly the size of the input - # divided by the stride (here hop_length), when divisible. - # This is achieved by padding by 1/4th of the kernel size (here nfft). - # which is not supported by torch.stft. - # Having all convolution operations follow this convention allow to easily - # align the time and frequency branches later on. - assert hl == nfft // 4 - le = int(math.ceil(x.shape[-1] / hl)) - pad = hl // 2 * 3 - x = pad1d(x, (pad, pad + le * hl - x.shape[-1]), mode="reflect") - - z = spectro(x, nfft, hl)[..., :-1, :] - assert z.shape[-1] == le + 4, (z.shape, x.shape, le) - z = z[..., 2: 2 + le] - return z - - def _ispec(self, z, length=None, scale=0): - hl = self.hop_length // (4**scale) - z = F.pad(z, (0, 0, 0, 1)) - z = F.pad(z, (2, 2)) - pad = hl // 2 * 3 - le = hl * int(math.ceil(length / hl)) + 2 * pad - x = ispectro(z, hl, length=le) - x = x[..., pad: pad + length] - return x - - def _magnitude(self, z): - # return the magnitude of the spectrogram, except when cac is True, - # in which case we just move the complex dimension to the channel one. - if self.cac: - B, C, Fr, T = z.shape - m = torch.view_as_real(z).permute(0, 1, 4, 2, 3) - m = m.reshape(B, C * 2, Fr, T) - else: - m = z.abs() - return m - - def _mask(self, z, m): - # Apply masking given the mixture spectrogram `z` and the estimated mask `m`. - # If `cac` is True, `m` is actually a full spectrogram and `z` is ignored. - niters = self.wiener_iters - if self.cac: - B, S, C, Fr, T = m.shape - out = m.view(B, S, -1, 2, Fr, T).permute(0, 1, 2, 4, 5, 3) - out = torch.view_as_complex(out.contiguous()) - return out - if self.training: - niters = self.end_iters - if niters < 0: - z = z[:, None] - return z / (1e-8 + z.abs()) * m - else: - return self._wiener(m, z, niters) - - def _wiener(self, mag_out, mix_stft, niters): - # apply wiener filtering from OpenUnmix. - init = mix_stft.dtype - wiener_win_len = 300 - residual = self.wiener_residual - - B, S, C, Fq, T = mag_out.shape - mag_out = mag_out.permute(0, 4, 3, 2, 1) - mix_stft = torch.view_as_real(mix_stft.permute(0, 3, 2, 1)) - - outs = [] - for sample in range(B): - pos = 0 - out = [] - for pos in range(0, T, wiener_win_len): - frame = slice(pos, pos + wiener_win_len) - z_out = wiener( - mag_out[sample, frame], - mix_stft[sample, frame], - niters, - residual=residual, - ) - out.append(z_out.transpose(-1, -2)) - outs.append(torch.cat(out, dim=0)) - out = torch.view_as_complex(torch.stack(outs, 0)) - out = out.permute(0, 4, 3, 2, 1).contiguous() - if residual: - out = out[:, :-1] - assert list(out.shape) == [B, S, C, Fq, T] - return out.to(init) - - def valid_length(self, length: int): - """ - Return a length that is appropriate for evaluation. - In our case, always return the training length, unless - it is smaller than the given length, in which case this - raises an error. - """ - if not self.use_train_segment: - return length - training_length = int(self.segment * self.samplerate) - if training_length < length: - raise ValueError( - f"Given length {length} is longer than " - f"training length {training_length}") - return training_length - - def forward(self, mix): - length = mix.shape[-1] - length_pre_pad = None - if self.use_train_segment: - if self.training: - self.segment = Fraction(mix.shape[-1], self.samplerate) - else: - training_length = int(self.segment * self.samplerate) - if mix.shape[-1] < training_length: - length_pre_pad = mix.shape[-1] - mix = F.pad(mix, (0, training_length - length_pre_pad)) - z = self._spec(mix) - mag = self._magnitude(z).to(mix.device) - x = mag - - B, C, Fq, T = x.shape - - # unlike previous Demucs, we always normalize because it is easier. - mean = x.mean(dim=(1, 2, 3), keepdim=True) - std = x.std(dim=(1, 2, 3), keepdim=True) - x = (x - mean) / (1e-5 + std) - # x will be the freq. branch input. - - # Prepare the time branch input. - xt = mix - meant = xt.mean(dim=(1, 2), keepdim=True) - stdt = xt.std(dim=(1, 2), keepdim=True) - xt = (xt - meant) / (1e-5 + stdt) - - # okay, this is a giant mess I know... - saved = [] # skip connections, freq. - saved_t = [] # skip connections, time. - lengths = [] # saved lengths to properly remove padding, freq branch. - lengths_t = [] # saved lengths for time branch. - for idx, encode in enumerate(self.encoder): - lengths.append(x.shape[-1]) - inject = None - if idx < len(self.tencoder): - # we have not yet merged branches. - lengths_t.append(xt.shape[-1]) - tenc = self.tencoder[idx] - xt = tenc(xt) - if not tenc.empty: - # save for skip connection - saved_t.append(xt) - else: - # tenc contains just the first conv., so that now time and freq. - # branches have the same shape and can be merged. - inject = xt - x = encode(x, inject) - if idx == 0 and self.freq_emb is not None: - # add frequency embedding to allow for non equivariant convolutions - # over the frequency axis. - frs = torch.arange(x.shape[-2], device=x.device) - emb = self.freq_emb(frs).t()[None, :, :, None].expand_as(x) - x = x + self.freq_emb_scale * emb - - saved.append(x) - if self.crosstransformer: - if self.bottom_channels: - b, c, f, t = x.shape - x = rearrange(x, "b c f t-> b c (f t)") - x = self.channel_upsampler(x) - x = rearrange(x, "b c (f t)-> b c f t", f=f) - xt = self.channel_upsampler_t(xt) - - x, xt = self.crosstransformer(x, xt) - - if self.bottom_channels: - x = rearrange(x, "b c f t-> b c (f t)") - x = self.channel_downsampler(x) - x = rearrange(x, "b c (f t)-> b c f t", f=f) - xt = self.channel_downsampler_t(xt) - - for idx, decode in enumerate(self.decoder): - skip = saved.pop(-1) - x, pre = decode(x, skip, lengths.pop(-1)) - # `pre` contains the output just before final transposed convolution, - # which is used when the freq. and time branch separate. - - offset = self.depth - len(self.tdecoder) - if idx >= offset: - tdec = self.tdecoder[idx - offset] - length_t = lengths_t.pop(-1) - if tdec.empty: - assert pre.shape[2] == 1, pre.shape - pre = pre[:, :, 0] - xt, _ = tdec(pre, None, length_t) - else: - skip = saved_t.pop(-1) - xt, _ = tdec(xt, skip, length_t) - - # Let's make sure we used all stored skip connections. - assert len(saved) == 0 - assert len(lengths_t) == 0 - assert len(saved_t) == 0 - - S = len(self.sources) - x = x.view(B, S, -1, Fq, T) - x = x * std[:, None] + mean[:, None] - - # to cpu as mps doesnt support complex numbers - # demucs issue #435 ##432 - # NOTE: in this case z already is on cpu - # TODO: remove this when mps supports complex numbers - x_is_mps_xpu = x.device.type in ["mps", "xpu"] - x_device = x.device - if x_is_mps_xpu: - x = x.cpu() - - zout = self._mask(z, x) - if self.use_train_segment: - if self.training: - x = self._ispec(zout, length) - else: - x = self._ispec(zout, training_length) - else: - x = self._ispec(zout, length) - - # back to mps device - if x_is_mps_xpu: - x = x.to(x_device) - - if self.use_train_segment: - if self.training: - xt = xt.view(B, S, -1, length) - else: - xt = xt.view(B, S, -1, training_length) - else: - xt = xt.view(B, S, -1, length) - xt = xt * stdt[:, None] + meant[:, None] - x = xt + x - if length_pre_pad: - x = x[..., :length_pre_pad] - return x diff --git a/demucs/demucs/pretrained.py b/demucs/demucs/pretrained.py deleted file mode 100644 index 80ae49cb..00000000 --- a/demucs/demucs/pretrained.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Loading pretrained models. -""" - -import logging -from pathlib import Path -import typing as tp - -from dora.log import fatal, bold - -from .hdemucs import HDemucs -from .repo import RemoteRepo, LocalRepo, ModelOnlyRepo, BagOnlyRepo, AnyModelRepo, ModelLoadingError # noqa -from .states import _check_diffq - -logger = logging.getLogger(__name__) -ROOT_URL = "https://dl.fbaipublicfiles.com/demucs/" -REMOTE_ROOT = Path(__file__).parent / 'remote' - -SOURCES = ["drums", "bass", "other", "vocals"] -DEFAULT_MODEL = 'htdemucs' - - -def demucs_unittest(): - model = HDemucs(channels=4, sources=SOURCES) - return model - - -def add_model_flags(parser): - group = parser.add_mutually_exclusive_group(required=False) - group.add_argument("-s", "--sig", help="Locally trained XP signature.") - group.add_argument("-n", "--name", default="htdemucs", - help="Pretrained model name or signature. Default is htdemucs.") - parser.add_argument("--repo", type=Path, - help="Folder containing all pre-trained models for use with -n.") - - -def _parse_remote_files(remote_file_list) -> tp.Dict[str, str]: - root: str = '' - models: tp.Dict[str, str] = {} - for line in remote_file_list.read_text().split('\n'): - line = line.strip() - if line.startswith('#'): - continue - elif len(line) == 0: - continue - elif line.startswith('root:'): - root = line.split(':', 1)[1].strip() - else: - sig = line.split('-', 1)[0] - assert sig not in models - models[sig] = ROOT_URL + root + line - return models - - -def get_model(name: str, - repo: tp.Optional[Path] = None): - """`name` must be a bag of models name or a pretrained signature - from the remote AWS model repo or the specified local repo if `repo` is not None. - """ - if name == 'demucs_unittest': - return demucs_unittest() - model_repo: ModelOnlyRepo - if repo is None: - models = _parse_remote_files(REMOTE_ROOT / 'files.txt') - model_repo = RemoteRepo(models) - bag_repo = BagOnlyRepo(REMOTE_ROOT, model_repo) - else: - if not repo.is_dir(): - fatal(f"{repo} must exist and be a directory.") - model_repo = LocalRepo(repo) - bag_repo = BagOnlyRepo(repo, model_repo) - any_repo = AnyModelRepo(model_repo, bag_repo) - try: - model = any_repo.get_model(name) - except ImportError as exc: - if 'diffq' in exc.args[0]: - _check_diffq() - raise - - model.eval() - return model - - -def get_model_from_args(args): - """ - Load local model package or pre-trained model. - """ - if args.name is None: - args.name = DEFAULT_MODEL - print(bold("Important: the default model was recently changed to `htdemucs`"), - "the latest Hybrid Transformer Demucs model. In some cases, this model can " - "actually perform worse than previous models. To get back the old default model " - "use `-n mdx_extra_q`.") - return get_model(name=args.name, repo=args.repo) diff --git a/demucs/demucs/py.typed b/demucs/demucs/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/demucs/demucs/remote/files.txt b/demucs/demucs/remote/files.txt deleted file mode 100644 index 346eb33c..00000000 --- a/demucs/demucs/remote/files.txt +++ /dev/null @@ -1,32 +0,0 @@ -# MDX Models -root: mdx_final/ -0d19c1c6-0f06f20e.th -5d2d6c55-db83574e.th -7d865c68-3d5dd56b.th -7ecf8ec1-70f50cc9.th -a1d90b5c-ae9d2452.th -c511e2ab-fe698775.th -cfa93e08-61801ae1.th -e51eebcc-c1b80bdd.th -6b9c2ca1-3fd82607.th -b72baf4e-8778635e.th -42e558d4-196e0e1b.th -305bc58f-18378783.th -14fc6a69-a89dd0ee.th -464b36d7-e5a9386e.th -7fd6ef75-a905dd85.th -83fc094f-4a16d450.th -1ef250f1-592467ce.th -902315c2-b39ce9c9.th -9a6b4851-03af0aa6.th -fa0cb7f9-100d8bf4.th -# Hybrid Transformer models -root: hybrid_transformer/ -955717e8-8726e21a.th -f7e0c4bc-ba3fe64a.th -d12395a8-e57c48e6.th -92cfc3b6-ef3bcb9c.th -04573f0d-f3cf25b2.th -75fc33f5-1941ce65.th -# Experimental 6 sources model -5c90dfd2-34c22ccb.th diff --git a/demucs/demucs/remote/hdemucs_mmi.yaml b/demucs/demucs/remote/hdemucs_mmi.yaml deleted file mode 100644 index 0ea08913..00000000 --- a/demucs/demucs/remote/hdemucs_mmi.yaml +++ /dev/null @@ -1,2 +0,0 @@ -models: ['75fc33f5'] -segment: 44 diff --git a/demucs/demucs/remote/htdemucs.yaml b/demucs/demucs/remote/htdemucs.yaml deleted file mode 100644 index 0d5f2089..00000000 --- a/demucs/demucs/remote/htdemucs.yaml +++ /dev/null @@ -1 +0,0 @@ -models: ['955717e8'] diff --git a/demucs/demucs/remote/htdemucs_6s.yaml b/demucs/demucs/remote/htdemucs_6s.yaml deleted file mode 100644 index 651a0fa5..00000000 --- a/demucs/demucs/remote/htdemucs_6s.yaml +++ /dev/null @@ -1 +0,0 @@ -models: ['5c90dfd2'] diff --git a/demucs/demucs/remote/htdemucs_ft.yaml b/demucs/demucs/remote/htdemucs_ft.yaml deleted file mode 100644 index ba5c69c2..00000000 --- a/demucs/demucs/remote/htdemucs_ft.yaml +++ /dev/null @@ -1,7 +0,0 @@ -models: ['f7e0c4bc', 'd12395a8', '92cfc3b6', '04573f0d'] -weights: [ - [1., 0., 0., 0.], - [0., 1., 0., 0.], - [0., 0., 1., 0.], - [0., 0., 0., 1.], -] \ No newline at end of file diff --git a/demucs/demucs/remote/mdx.yaml b/demucs/demucs/remote/mdx.yaml deleted file mode 100644 index 4e81a506..00000000 --- a/demucs/demucs/remote/mdx.yaml +++ /dev/null @@ -1,8 +0,0 @@ -models: ['0d19c1c6', '7ecf8ec1', 'c511e2ab', '7d865c68'] -weights: [ - [1., 1., 0., 0.], - [0., 1., 0., 0.], - [1., 0., 1., 1.], - [1., 0., 1., 1.], -] -segment: 44 diff --git a/demucs/demucs/remote/mdx_extra.yaml b/demucs/demucs/remote/mdx_extra.yaml deleted file mode 100644 index 847bf665..00000000 --- a/demucs/demucs/remote/mdx_extra.yaml +++ /dev/null @@ -1,2 +0,0 @@ -models: ['e51eebcc', 'a1d90b5c', '5d2d6c55', 'cfa93e08'] -segment: 44 \ No newline at end of file diff --git a/demucs/demucs/remote/mdx_extra_q.yaml b/demucs/demucs/remote/mdx_extra_q.yaml deleted file mode 100644 index 87702bc8..00000000 --- a/demucs/demucs/remote/mdx_extra_q.yaml +++ /dev/null @@ -1,2 +0,0 @@ -models: ['83fc094f', '464b36d7', '14fc6a69', '7fd6ef75'] -segment: 44 diff --git a/demucs/demucs/remote/mdx_q.yaml b/demucs/demucs/remote/mdx_q.yaml deleted file mode 100644 index 827d2c66..00000000 --- a/demucs/demucs/remote/mdx_q.yaml +++ /dev/null @@ -1,8 +0,0 @@ -models: ['6b9c2ca1', 'b72baf4e', '42e558d4', '305bc58f'] -weights: [ - [1., 1., 0., 0.], - [0., 1., 0., 0.], - [1., 0., 1., 1.], - [1., 0., 1., 1.], -] -segment: 44 diff --git a/demucs/demucs/remote/repro_mdx_a.yaml b/demucs/demucs/remote/repro_mdx_a.yaml deleted file mode 100644 index 691abc2c..00000000 --- a/demucs/demucs/remote/repro_mdx_a.yaml +++ /dev/null @@ -1,2 +0,0 @@ -models: ['9a6b4851', '1ef250f1', 'fa0cb7f9', '902315c2'] -segment: 44 diff --git a/demucs/demucs/remote/repro_mdx_a_hybrid_only.yaml b/demucs/demucs/remote/repro_mdx_a_hybrid_only.yaml deleted file mode 100644 index 78eb8e0c..00000000 --- a/demucs/demucs/remote/repro_mdx_a_hybrid_only.yaml +++ /dev/null @@ -1,2 +0,0 @@ -models: ['fa0cb7f9', '902315c2', 'fa0cb7f9', '902315c2'] -segment: 44 diff --git a/demucs/demucs/remote/repro_mdx_a_time_only.yaml b/demucs/demucs/remote/repro_mdx_a_time_only.yaml deleted file mode 100644 index d5d16ea8..00000000 --- a/demucs/demucs/remote/repro_mdx_a_time_only.yaml +++ /dev/null @@ -1,2 +0,0 @@ -models: ['9a6b4851', '9a6b4851', '1ef250f1', '1ef250f1'] -segment: 44 diff --git a/demucs/demucs/repitch.py b/demucs/demucs/repitch.py deleted file mode 100644 index b69c0d25..00000000 --- a/demucs/demucs/repitch.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Utility for on the fly pitch/tempo change for data augmentation.""" - -import random -import subprocess as sp -import tempfile - -from . import audio_legacy -import torch -import torchaudio as ta - -from .audio import save_audio - - -class RepitchedWrapper: - """ - Wrap a dataset to apply online change of pitch / tempo. - """ - def __init__(self, dataset, proba=0.2, max_pitch=2, max_tempo=12, - tempo_std=5, vocals=[3], same=True): - self.dataset = dataset - self.proba = proba - self.max_pitch = max_pitch - self.max_tempo = max_tempo - self.tempo_std = tempo_std - self.same = same - self.vocals = vocals - - def __len__(self): - return len(self.dataset) - - def __getitem__(self, index): - streams = self.dataset[index] - in_length = streams.shape[-1] - out_length = int((1 - 0.01 * self.max_tempo) * in_length) - - if random.random() < self.proba: - outs = [] - for idx, stream in enumerate(streams): - if idx == 0 or not self.same: - delta_pitch = random.randint(-self.max_pitch, self.max_pitch) - delta_tempo = random.gauss(0, self.tempo_std) - delta_tempo = min(max(-self.max_tempo, delta_tempo), self.max_tempo) - stream = repitch( - stream, - delta_pitch, - delta_tempo, - voice=idx in self.vocals) - outs.append(stream[:, :out_length]) - streams = torch.stack(outs) - else: - streams = streams[..., :out_length] - return streams - - -def repitch(wav, pitch, tempo, voice=False, quick=False, samplerate=44100): - """ - tempo is a relative delta in percentage, so tempo=10 means tempo at 110%! - pitch is in semi tones. - Requires `soundstretch` to be installed, see - https://www.surina.net/soundtouch/soundstretch.html - """ - infile = tempfile.NamedTemporaryFile(suffix=".wav") - outfile = tempfile.NamedTemporaryFile(suffix=".wav") - save_audio(wav, infile.name, samplerate, clip='clamp') - command = [ - "soundstretch", - infile.name, - outfile.name, - f"-pitch={pitch}", - f"-tempo={tempo:.6f}", - ] - if quick: - command += ["-quick"] - if voice: - command += ["-speech"] - try: - sp.run(command, capture_output=True, check=True) - except sp.CalledProcessError as error: - raise RuntimeError(f"Could not change bpm because {error.stderr.decode('utf-8')}") - wav, sr = ta.load(outfile.name) - assert sr == samplerate - return wav diff --git a/demucs/demucs/repo.py b/demucs/demucs/repo.py deleted file mode 100644 index 5e20ff51..00000000 --- a/demucs/demucs/repo.py +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Represents a model repository, including pre-trained models and bags of models. -A repo can either be the main remote repository stored in AWS, or a local repository -with your own models. -""" - -from hashlib import sha256 -from pathlib import Path -import typing as tp - -import torch -import yaml - -from .apply import BagOfModels, Model -from .states import load_model - - -AnyModel = tp.Union[Model, BagOfModels] - - -class ModelLoadingError(RuntimeError): - pass - - -def check_checksum(path: Path, checksum: str): - sha = sha256() - with open(path, 'rb') as file: - while True: - buf = file.read(2**20) - if not buf: - break - sha.update(buf) - actual_checksum = sha.hexdigest()[:len(checksum)] - if actual_checksum != checksum: - raise ModelLoadingError(f'Invalid checksum for file {path}, ' - f'expected {checksum} but got {actual_checksum}') - - -class ModelOnlyRepo: - """Base class for all model only repos. - """ - def has_model(self, sig: str) -> bool: - raise NotImplementedError() - - def get_model(self, sig: str) -> Model: - raise NotImplementedError() - - def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: - raise NotImplementedError() - - -class RemoteRepo(ModelOnlyRepo): - def __init__(self, models: tp.Dict[str, str]): - self._models = models - - def has_model(self, sig: str) -> bool: - return sig in self._models - - def get_model(self, sig: str) -> Model: - try: - url = self._models[sig] - except KeyError: - raise ModelLoadingError(f'Could not find a pre-trained model with signature {sig}.') - pkg = torch.hub.load_state_dict_from_url( - url, map_location='cpu', check_hash=True) # type: ignore - return load_model(pkg) - - def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: - return self._models # type: ignore - - -class LocalRepo(ModelOnlyRepo): - def __init__(self, root: Path): - self.root = root - self.scan() - - def scan(self): - self._models = {} - self._checksums = {} - for file in self.root.iterdir(): - if file.suffix == '.th': - if '-' in file.stem: - xp_sig, checksum = file.stem.split('-') - self._checksums[xp_sig] = checksum - else: - xp_sig = file.stem - if xp_sig in self._models: - raise ModelLoadingError( - f'Duplicate pre-trained model exist for signature {xp_sig}. ' - 'Please delete all but one.') - self._models[xp_sig] = file - - def has_model(self, sig: str) -> bool: - return sig in self._models - - def get_model(self, sig: str) -> Model: - try: - file = self._models[sig] - except KeyError: - raise ModelLoadingError(f'Could not find pre-trained model with signature {sig}.') - if sig in self._checksums: - check_checksum(file, self._checksums[sig]) - return load_model(file) - - def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: - return self._models - - -class BagOnlyRepo: - """Handles only YAML files containing bag of models, leaving the actual - model loading to some Repo. - """ - def __init__(self, root: Path, model_repo: ModelOnlyRepo): - self.root = root - self.model_repo = model_repo - self.scan() - - def scan(self): - self._bags = {} - for file in self.root.iterdir(): - if file.suffix == '.yaml': - self._bags[file.stem] = file - - def has_model(self, name: str) -> bool: - return name in self._bags - - def get_model(self, name: str) -> BagOfModels: - try: - yaml_file = self._bags[name] - except KeyError: - raise ModelLoadingError(f'{name} is neither a single pre-trained model or ' - 'a bag of models.') - bag = yaml.safe_load(open(yaml_file)) - signatures = bag['models'] - models = [self.model_repo.get_model(sig) for sig in signatures] - weights = bag.get('weights') - segment = bag.get('segment') - return BagOfModels(models, weights, segment) - - def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: - return self._bags - - -class AnyModelRepo: - def __init__(self, model_repo: ModelOnlyRepo, bag_repo: BagOnlyRepo): - self.model_repo = model_repo - self.bag_repo = bag_repo - - def has_model(self, name_or_sig: str) -> bool: - return self.model_repo.has_model(name_or_sig) or self.bag_repo.has_model(name_or_sig) - - def get_model(self, name_or_sig: str) -> AnyModel: - if self.model_repo.has_model(name_or_sig): - return self.model_repo.get_model(name_or_sig) - else: - return self.bag_repo.get_model(name_or_sig) - - def list_model(self) -> tp.Dict[str, tp.Union[str, Path]]: - models = self.model_repo.list_model() - for key, value in self.bag_repo.list_model().items(): - models[key] = value - return models diff --git a/demucs/demucs/separate.py b/demucs/demucs/separate.py deleted file mode 100644 index 7de5f114..00000000 --- a/demucs/demucs/separate.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -import argparse -import sys -from pathlib import Path - -from dora.log import fatal -import torch as th - -from .api import Separator, save_audio, list_models - -from .apply import BagOfModels -from .htdemucs import HTDemucs -from .pretrained import add_model_flags, ModelLoadingError - - -def get_parser(): - parser = argparse.ArgumentParser("demucs.separate", - description="Separate the sources for the given tracks") - parser.add_argument("tracks", nargs='*', type=Path, default=[], help='Path to tracks') - add_model_flags(parser) - parser.add_argument("--list-models", action="store_true", help="List available models " - "from current repo and exit") - parser.add_argument("-v", "--verbose", action="store_true") - parser.add_argument("-o", - "--out", - type=Path, - default=Path("separated"), - help="Folder where to put extracted tracks. A subfolder " - "with the model name will be created.") - parser.add_argument("--filename", - default="{track}/{stem}.{ext}", - help="Set the name of output file. \n" - 'Use "{track}", "{trackext}", "{stem}", "{ext}" to use ' - "variables of track name without extension, track extension, " - "stem name and default output file extension. \n" - 'Default is "{track}/{stem}.{ext}".') - parser.add_argument("-d", - "--device", - default=( - "cuda" - if th.cuda.is_available() - else "mps" - if th.backends.mps.is_available() - else "cpu" - ), - help="Device to use, default is cuda if available else cpu") - parser.add_argument("--shifts", - default=1, - type=int, - help="Number of random shifts for equivariant stabilization." - "Increase separation time but improves quality for Demucs. 10 was used " - "in the original paper.") - parser.add_argument("--overlap", - default=0.25, - type=float, - help="Overlap between the splits.") - split_group = parser.add_mutually_exclusive_group() - split_group.add_argument("--no-split", - action="store_false", - dest="split", - default=True, - help="Doesn't split audio in chunks. " - "This can use large amounts of memory.") - split_group.add_argument("--segment", type=int, - help="Set split size of each chunk. " - "This can help save memory of graphic card. ") - parser.add_argument("--two-stems", - dest="stem", metavar="STEM", - help="Only separate audio into {STEM} and no_{STEM}. ") - parser.add_argument("--other-method", dest="other_method", choices=["none", "add", "minus"], - default="add", help='Decide how to get "no_{STEM}". "none" will not save ' - '"no_{STEM}". "add" will add all the other stems. "minus" will use the ' - "original track minus the selected stem.") - depth_group = parser.add_mutually_exclusive_group() - depth_group.add_argument("--int24", action="store_true", - help="Save wav output as 24 bits wav.") - depth_group.add_argument("--float32", action="store_true", - help="Save wav output as float32 (2x bigger).") - parser.add_argument("--clip-mode", default="rescale", choices=["rescale", "clamp", "none"], - help="Strategy for avoiding clipping: rescaling entire signal " - "if necessary (rescale) or hard clipping (clamp).") - format_group = parser.add_mutually_exclusive_group() - format_group.add_argument("--flac", action="store_true", - help="Convert the output wavs to flac.") - format_group.add_argument("--mp3", action="store_true", - help="Convert the output wavs to mp3.") - parser.add_argument("--mp3-bitrate", - default=320, - type=int, - help="Bitrate of converted mp3.") - parser.add_argument("--mp3-preset", choices=range(2, 8), type=int, default=2, - help="Encoder preset of MP3, 2 for highest quality, 7 for " - "fastest speed. Default is 2") - parser.add_argument("-j", "--jobs", - default=0, - type=int, - help="Number of jobs. This can increase memory usage but will " - "be much faster when multiple cores are available.") - - return parser - - -def main(opts=None): - parser = get_parser() - args = parser.parse_args(opts) - if args.list_models: - models = list_models(args.repo) - print("Bag of models:", end="\n ") - print("\n ".join(models["bag"])) - print("Single models:", end="\n ") - print("\n ".join(models["single"])) - sys.exit(0) - if len(args.tracks) == 0: - print("error: the following arguments are required: tracks", file=sys.stderr) - sys.exit(1) - - try: - separator = Separator(model=args.name, - repo=args.repo, - device=args.device, - shifts=args.shifts, - split=args.split, - overlap=args.overlap, - progress=True, - jobs=args.jobs, - segment=args.segment) - except ModelLoadingError as error: - fatal(error.args[0]) - - max_allowed_segment = float('inf') - if isinstance(separator.model, HTDemucs): - max_allowed_segment = float(separator.model.segment) - elif isinstance(separator.model, BagOfModels): - max_allowed_segment = separator.model.max_allowed_segment - if args.segment is not None and args.segment > max_allowed_segment: - fatal("Cannot use a Transformer model with a longer segment " - f"than it was trained for. Maximum segment is: {max_allowed_segment}") - - if isinstance(separator.model, BagOfModels): - print( - f"Selected model is a bag of {len(separator.model.models)} models. " - "You will see that many progress bars per track." - ) - - if args.stem is not None and args.stem not in separator.model.sources: - fatal( - 'error: stem "{stem}" is not in selected model. ' - "STEM must be one of {sources}.".format( - stem=args.stem, sources=", ".join(separator.model.sources) - ) - ) - out = args.out / args.name - out.mkdir(parents=True, exist_ok=True) - print(f"Separated tracks will be stored in {out.resolve()}") - for track in args.tracks: - if not track.exists(): - print(f"File {track} does not exist. If the path contains spaces, " - 'please try again after surrounding the entire path with quotes "".', - file=sys.stderr) - continue - print(f"Separating track {track}") - - origin, res = separator.separate_audio_file(track) - - if args.mp3: - ext = "mp3" - elif args.flac: - ext = "flac" - else: - ext = "wav" - kwargs = { - "samplerate": separator.samplerate, - "bitrate": args.mp3_bitrate, - "preset": args.mp3_preset, - "clip": args.clip_mode, - "as_float": args.float32, - "bits_per_sample": 24 if args.int24 else 16, - } - if args.stem is None: - for name, source in res.items(): - stem = out / args.filename.format( - track=track.name.rsplit(".", 1)[0], - trackext=track.name.rsplit(".", 1)[-1], - stem=name, - ext=ext, - ) - stem.parent.mkdir(parents=True, exist_ok=True) - save_audio(source, str(stem), **kwargs) - else: - stem = out / args.filename.format( - track=track.name.rsplit(".", 1)[0], - trackext=track.name.rsplit(".", 1)[-1], - stem="minus_" + args.stem, - ext=ext, - ) - if args.other_method == "minus": - stem.parent.mkdir(parents=True, exist_ok=True) - save_audio(origin - res[args.stem], str(stem), **kwargs) - stem = out / args.filename.format( - track=track.name.rsplit(".", 1)[0], - trackext=track.name.rsplit(".", 1)[-1], - stem=args.stem, - ext=ext, - ) - stem.parent.mkdir(parents=True, exist_ok=True) - save_audio(res.pop(args.stem), str(stem), **kwargs) - # Warning : after poping the stem, selected stem is no longer in the dict 'res' - if args.other_method == "add": - other_stem = th.zeros_like(next(iter(res.values()))) - for i in res.values(): - other_stem += i - stem = out / args.filename.format( - track=track.name.rsplit(".", 1)[0], - trackext=track.name.rsplit(".", 1)[-1], - stem="no_" + args.stem, - ext=ext, - ) - stem.parent.mkdir(parents=True, exist_ok=True) - save_audio(other_stem, str(stem), **kwargs) - - -if __name__ == "__main__": - main() diff --git a/demucs/demucs/solver.py b/demucs/demucs/solver.py deleted file mode 100644 index 7c80b148..00000000 --- a/demucs/demucs/solver.py +++ /dev/null @@ -1,405 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Main training loop.""" - -import logging - -from dora import get_xp -from dora.utils import write_and_rename -from dora.log import LogProgress, bold -import torch -import torch.nn.functional as F - -from . import augment, distrib, states, pretrained -from .apply import apply_model -from .ema import ModelEMA -from .evaluate import evaluate, new_sdr -from .svd import svd_penalty -from .utils import pull_metric, EMA - -logger = logging.getLogger(__name__) - - -def _summary(metrics): - return " | ".join(f"{key.capitalize()}={val}" for key, val in metrics.items()) - - -class Solver(object): - def __init__(self, loaders, model, optimizer, args): - self.args = args - self.loaders = loaders - - self.model = model - self.optimizer = optimizer - self.quantizer = states.get_quantizer(self.model, args.quant, self.optimizer) - self.dmodel = distrib.wrap(model) - self.device = next(iter(self.model.parameters())).device - - # Exponential moving average of the model, either updated every batch or epoch. - # The best model from all the EMAs and the original one is kept based on the valid - # loss for the final best model. - self.emas = {'batch': [], 'epoch': []} - for kind in self.emas.keys(): - decays = getattr(args.ema, kind) - device = self.device if kind == 'batch' else 'cpu' - if decays: - for decay in decays: - self.emas[kind].append(ModelEMA(self.model, decay, device=device)) - - # data augment - augments = [augment.Shift(shift=int(args.dset.samplerate * args.dset.shift), - same=args.augment.shift_same)] - if args.augment.flip: - augments += [augment.FlipChannels(), augment.FlipSign()] - for aug in ['scale', 'remix']: - kw = getattr(args.augment, aug) - if kw.proba: - augments.append(getattr(augment, aug.capitalize())(**kw)) - self.augment = torch.nn.Sequential(*augments) - - xp = get_xp() - self.folder = xp.folder - # Checkpoints - self.checkpoint_file = xp.folder / 'checkpoint.th' - self.best_file = xp.folder / 'best.th' - logger.debug("Checkpoint will be saved to %s", self.checkpoint_file.resolve()) - self.best_state = None - self.best_changed = False - - self.link = xp.link - self.history = self.link.history - - self._reset() - - def _serialize(self, epoch): - package = {} - package['state'] = self.model.state_dict() - package['optimizer'] = self.optimizer.state_dict() - package['history'] = self.history - package['best_state'] = self.best_state - package['args'] = self.args - for kind, emas in self.emas.items(): - for k, ema in enumerate(emas): - package[f'ema_{kind}_{k}'] = ema.state_dict() - with write_and_rename(self.checkpoint_file) as tmp: - torch.save(package, tmp) - - save_every = self.args.save_every - if save_every and (epoch + 1) % save_every == 0 and epoch + 1 != self.args.epochs: - with write_and_rename(self.folder / f'checkpoint_{epoch + 1}.th') as tmp: - torch.save(package, tmp) - - if self.best_changed: - # Saving only the latest best model. - with write_and_rename(self.best_file) as tmp: - package = states.serialize_model(self.model, self.args) - package['state'] = self.best_state - torch.save(package, tmp) - self.best_changed = False - - def _reset(self): - """Reset state of the solver, potentially using checkpoint.""" - if self.checkpoint_file.exists(): - logger.info(f'Loading checkpoint model: {self.checkpoint_file}') - package = torch.load(self.checkpoint_file, 'cpu') - self.model.load_state_dict(package['state']) - self.optimizer.load_state_dict(package['optimizer']) - self.history[:] = package['history'] - self.best_state = package['best_state'] - for kind, emas in self.emas.items(): - for k, ema in enumerate(emas): - ema.load_state_dict(package[f'ema_{kind}_{k}']) - elif self.args.continue_pretrained: - model = pretrained.get_model( - name=self.args.continue_pretrained, - repo=self.args.pretrained_repo) - self.model.load_state_dict(model.state_dict()) - elif self.args.continue_from: - name = 'checkpoint.th' - root = self.folder.parent - cf = root / str(self.args.continue_from) / name - logger.info("Loading from %s", cf) - package = torch.load(cf, 'cpu') - self.best_state = package['best_state'] - if self.args.continue_best: - self.model.load_state_dict(package['best_state'], strict=False) - else: - self.model.load_state_dict(package['state'], strict=False) - if self.args.continue_opt: - self.optimizer.load_state_dict(package['optimizer']) - - def _format_train(self, metrics: dict) -> dict: - """Formatting for train/valid metrics.""" - losses = { - 'loss': format(metrics['loss'], ".4f"), - 'reco': format(metrics['reco'], ".4f"), - } - if 'nsdr' in metrics: - losses['nsdr'] = format(metrics['nsdr'], ".3f") - if self.quantizer is not None: - losses['ms'] = format(metrics['ms'], ".2f") - if 'grad' in metrics: - losses['grad'] = format(metrics['grad'], ".4f") - if 'best' in metrics: - losses['best'] = format(metrics['best'], '.4f') - if 'bname' in metrics: - losses['bname'] = metrics['bname'] - if 'penalty' in metrics: - losses['penalty'] = format(metrics['penalty'], ".4f") - if 'hloss' in metrics: - losses['hloss'] = format(metrics['hloss'], ".4f") - return losses - - def _format_test(self, metrics: dict) -> dict: - """Formatting for test metrics.""" - losses = {} - if 'sdr' in metrics: - losses['sdr'] = format(metrics['sdr'], '.3f') - if 'nsdr' in metrics: - losses['nsdr'] = format(metrics['nsdr'], '.3f') - for source in self.model.sources: - key = f'sdr_{source}' - if key in metrics: - losses[key] = format(metrics[key], '.3f') - key = f'nsdr_{source}' - if key in metrics: - losses[key] = format(metrics[key], '.3f') - return losses - - def train(self): - # Optimizing the model - if self.history: - logger.info("Replaying metrics from previous run") - for epoch, metrics in enumerate(self.history): - formatted = self._format_train(metrics['train']) - logger.info( - bold(f'Train Summary | Epoch {epoch + 1} | {_summary(formatted)}')) - formatted = self._format_train(metrics['valid']) - logger.info( - bold(f'Valid Summary | Epoch {epoch + 1} | {_summary(formatted)}')) - if 'test' in metrics: - formatted = self._format_test(metrics['test']) - if formatted: - logger.info(bold(f"Test Summary | Epoch {epoch + 1} | {_summary(formatted)}")) - - epoch = 0 - for epoch in range(len(self.history), self.args.epochs): - # Train one epoch - self.model.train() # Turn on BatchNorm & Dropout - metrics = {} - logger.info('-' * 70) - logger.info("Training...") - metrics['train'] = self._run_one_epoch(epoch) - formatted = self._format_train(metrics['train']) - logger.info( - bold(f'Train Summary | Epoch {epoch + 1} | {_summary(formatted)}')) - - # Cross validation - logger.info('-' * 70) - logger.info('Cross validation...') - self.model.eval() # Turn off Batchnorm & Dropout - with torch.no_grad(): - valid = self._run_one_epoch(epoch, train=False) - bvalid = valid - bname = 'main' - state = states.copy_state(self.model.state_dict()) - metrics['valid'] = {} - metrics['valid']['main'] = valid - key = self.args.test.metric - for kind, emas in self.emas.items(): - for k, ema in enumerate(emas): - with ema.swap(): - valid = self._run_one_epoch(epoch, train=False) - name = f'ema_{kind}_{k}' - metrics['valid'][name] = valid - a = valid[key] - b = bvalid[key] - if key.startswith('nsdr'): - a = -a - b = -b - if a < b: - bvalid = valid - state = ema.state - bname = name - metrics['valid'].update(bvalid) - metrics['valid']['bname'] = bname - - valid_loss = metrics['valid'][key] - mets = pull_metric(self.link.history, f'valid.{key}') + [valid_loss] - if key.startswith('nsdr'): - best_loss = max(mets) - else: - best_loss = min(mets) - metrics['valid']['best'] = best_loss - if self.args.svd.penalty > 0: - kw = dict(self.args.svd) - kw.pop('penalty') - with torch.no_grad(): - penalty = svd_penalty(self.model, exact=True, **kw) - metrics['valid']['penalty'] = penalty - - formatted = self._format_train(metrics['valid']) - logger.info( - bold(f'Valid Summary | Epoch {epoch + 1} | {_summary(formatted)}')) - - # Save the best model - if valid_loss == best_loss or self.args.dset.train_valid: - logger.info(bold('New best valid loss %.4f'), valid_loss) - self.best_state = states.copy_state(state) - self.best_changed = True - - # Eval model every `test.every` epoch or on last epoch - should_eval = (epoch + 1) % self.args.test.every == 0 - is_last = epoch == self.args.epochs - 1 - # # Tries to detect divergence in a reliable way and finish job - # # not to waste compute. - # # Commented out as this was super specific to the MDX competition. - # reco = metrics['valid']['main']['reco'] - # div = epoch >= 180 and reco > 0.18 - # div = div or epoch >= 100 and reco > 0.25 - # div = div and self.args.optim.loss == 'l1' - # if div: - # logger.warning("Finishing training early because valid loss is too high.") - # is_last = True - if should_eval or is_last: - # Evaluate on the testset - logger.info('-' * 70) - logger.info('Evaluating on the test set...') - # We switch to the best known model for testing - if self.args.test.best: - state = self.best_state - else: - state = states.copy_state(self.model.state_dict()) - compute_sdr = self.args.test.sdr and is_last - with states.swap_state(self.model, state): - with torch.no_grad(): - metrics['test'] = evaluate(self, compute_sdr=compute_sdr) - formatted = self._format_test(metrics['test']) - logger.info(bold(f"Test Summary | Epoch {epoch + 1} | {_summary(formatted)}")) - self.link.push_metrics(metrics) - - if distrib.rank == 0: - # Save model each epoch - self._serialize(epoch) - logger.debug("Checkpoint saved to %s", self.checkpoint_file.resolve()) - if is_last: - break - - def _run_one_epoch(self, epoch, train=True): - args = self.args - data_loader = self.loaders['train'] if train else self.loaders['valid'] - if distrib.world_size > 1 and train: - data_loader.sampler.set_epoch(epoch) - - label = ["Valid", "Train"][train] - name = label + f" | Epoch {epoch + 1}" - total = len(data_loader) - if args.max_batches: - total = min(total, args.max_batches) - logprog = LogProgress(logger, data_loader, total=total, - updates=self.args.misc.num_prints, name=name) - averager = EMA() - - for idx, sources in enumerate(logprog): - sources = sources.to(self.device) - if train: - sources = self.augment(sources) - mix = sources.sum(dim=1) - else: - mix = sources[:, 0] - sources = sources[:, 1:] - - if not train and self.args.valid_apply: - estimate = apply_model(self.model, mix, split=self.args.test.split, overlap=0) - else: - estimate = self.dmodel(mix) - if train and hasattr(self.model, 'transform_target'): - sources = self.model.transform_target(mix, sources) - assert estimate.shape == sources.shape, (estimate.shape, sources.shape) - dims = tuple(range(2, sources.dim())) - - if args.optim.loss == 'l1': - loss = F.l1_loss(estimate, sources, reduction='none') - loss = loss.mean(dims).mean(0) - reco = loss - elif args.optim.loss == 'mse': - loss = F.mse_loss(estimate, sources, reduction='none') - loss = loss.mean(dims) - reco = loss**0.5 - reco = reco.mean(0) - else: - raise ValueError(f"Invalid loss {self.args.loss}") - weights = torch.tensor(args.weights).to(sources) - loss = (loss * weights).sum() / weights.sum() - - ms = 0 - if self.quantizer is not None: - ms = self.quantizer.model_size() - if args.quant.diffq: - loss += args.quant.diffq * ms - - losses = {} - losses['reco'] = (reco * weights).sum() / weights.sum() - losses['ms'] = ms - - if not train: - nsdrs = new_sdr(sources, estimate.detach()).mean(0) - total = 0 - for source, nsdr, w in zip(self.model.sources, nsdrs, weights): - losses[f'nsdr_{source}'] = nsdr - total += w * nsdr - losses['nsdr'] = total / weights.sum() - - if train and args.svd.penalty > 0: - kw = dict(args.svd) - kw.pop('penalty') - penalty = svd_penalty(self.model, **kw) - losses['penalty'] = penalty - loss += args.svd.penalty * penalty - - losses['loss'] = loss - - for k, source in enumerate(self.model.sources): - losses[f'reco_{source}'] = reco[k] - - # optimize model in training mode - if train: - loss.backward() - grad_norm = 0 - grads = [] - for p in self.model.parameters(): - if p.grad is not None: - grad_norm += p.grad.data.norm()**2 - grads.append(p.grad.data) - losses['grad'] = grad_norm ** 0.5 - if args.optim.clip_grad: - torch.nn.utils.clip_grad_norm_( - self.model.parameters(), - args.optim.clip_grad) - - if self.args.flag == 'uns': - for n, p in self.model.named_parameters(): - if p.grad is None: - print('no grad', n) - self.optimizer.step() - self.optimizer.zero_grad() - for ema in self.emas['batch']: - ema.update() - losses = averager(losses) - logs = self._format_train(losses) - logprog.update(**logs) - # Just in case, clear some memory - del loss, estimate, reco, ms - if args.max_batches == idx: - break - if self.args.debug and train: - break - if self.args.flag == 'debug': - break - if train: - for ema in self.emas['epoch']: - ema.update() - return distrib.average(losses, idx + 1) diff --git a/demucs/demucs/spec.py b/demucs/demucs/spec.py deleted file mode 100644 index d8f6ee5e..00000000 --- a/demucs/demucs/spec.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Conveniance wrapper to perform STFT and iSTFT""" - -import torch as th - - -def spectro(x, n_fft=512, hop_length=None, pad=0): - *other, length = x.shape - x = x.reshape(-1, length) - is_mps_xpu = x.device.type in ['mps', 'xpu'] - if is_mps_xpu: - x = x.cpu() - z = th.stft(x, - n_fft * (1 + pad), - hop_length or n_fft // 4, - window=th.hann_window(n_fft).to(x), - win_length=n_fft, - normalized=True, - center=True, - return_complex=True, - pad_mode='reflect') - _, freqs, frame = z.shape - return z.view(*other, freqs, frame) - - -def ispectro(z, hop_length=None, length=None, pad=0): - *other, freqs, frames = z.shape - n_fft = 2 * freqs - 2 - z = z.view(-1, freqs, frames) - win_length = n_fft // (1 + pad) - is_mps_xpu = z.device.type in ['mps', 'xpu'] - if is_mps_xpu: - z = z.cpu() - x = th.istft(z, - n_fft, - hop_length, - window=th.hann_window(win_length).to(z.real), - win_length=win_length, - normalized=True, - length=length, - center=True) - _, length = x.shape - return x.view(*other, length) diff --git a/demucs/demucs/states.py b/demucs/demucs/states.py deleted file mode 100644 index 361bb419..00000000 --- a/demucs/demucs/states.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -""" -Utilities to save and load models. -""" -from contextlib import contextmanager - -import functools -import hashlib -import inspect -import io -from pathlib import Path -import warnings - -from omegaconf import OmegaConf -from dora.log import fatal -import torch - - -def _check_diffq(): - try: - import diffq # noqa - except ImportError: - fatal('Trying to use DiffQ, but diffq is not installed.\n' - 'On Windows run: python.exe -m pip install diffq \n' - 'On Linux/Mac, run: python3 -m pip install diffq') - - -def get_quantizer(model, args, optimizer=None): - """Return the quantizer given the XP quantization args.""" - quantizer = None - if args.diffq: - _check_diffq() - from diffq import DiffQuantizer - quantizer = DiffQuantizer( - model, min_size=args.min_size, group_size=args.group_size) - if optimizer is not None: - quantizer.setup_optimizer(optimizer) - elif args.qat: - _check_diffq() - from diffq import UniformQuantizer - quantizer = UniformQuantizer( - model, bits=args.qat, min_size=args.min_size) - return quantizer - - -def load_model(path_or_package, strict=False): - """Load a model from the given serialized model, either given as a dict (already loaded) - or a path to a file on disk.""" - if isinstance(path_or_package, dict): - package = path_or_package - elif isinstance(path_or_package, (str, Path)): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - path = path_or_package - package = torch.load(path, 'cpu') - else: - raise ValueError(f"Invalid type for {path_or_package}.") - - klass = package["klass"] - args = package["args"] - kwargs = package["kwargs"] - - if strict: - model = klass(*args, **kwargs) - else: - sig = inspect.signature(klass) - for key in list(kwargs): - if key not in sig.parameters: - warnings.warn("Dropping inexistant parameter " + key) - del kwargs[key] - model = klass(*args, **kwargs) - - state = package["state"] - - set_state(model, state) - return model - - -def get_state(model, quantizer, half=False): - """Get the state from a model, potentially with quantization applied. - If `half` is True, model are stored as half precision, which shouldn't impact performance - but half the state size.""" - if quantizer is None: - dtype = torch.half if half else None - state = {k: p.data.to(device='cpu', dtype=dtype) for k, p in model.state_dict().items()} - else: - state = quantizer.get_quantized_state() - state['__quantized'] = True - return state - - -def set_state(model, state, quantizer=None): - """Set the state on a given model.""" - if state.get('__quantized'): - if quantizer is not None: - quantizer.restore_quantized_state(model, state['quantized']) - else: - _check_diffq() - from diffq import restore_quantized_state - restore_quantized_state(model, state) - else: - model.load_state_dict(state) - return state - - -def save_with_checksum(content, path): - """Save the given value on disk, along with a sha256 hash. - Should be used with the output of either `serialize_model` or `get_state`.""" - buf = io.BytesIO() - torch.save(content, buf) - sig = hashlib.sha256(buf.getvalue()).hexdigest()[:8] - - path = path.parent / (path.stem + "-" + sig + path.suffix) - path.write_bytes(buf.getvalue()) - - -def serialize_model(model, training_args, quantizer=None, half=True): - args, kwargs = model._init_args_kwargs - klass = model.__class__ - - state = get_state(model, quantizer, half) - return { - 'klass': klass, - 'args': args, - 'kwargs': kwargs, - 'state': state, - 'training_args': OmegaConf.to_container(training_args, resolve=True), - } - - -def copy_state(state): - return {k: v.cpu().clone() for k, v in state.items()} - - -@contextmanager -def swap_state(model, state): - """ - Context manager that swaps the state of a model, e.g: - - # model is in old state - with swap_state(model, new_state): - # model in new state - # model back to old state - """ - old_state = copy_state(model.state_dict()) - model.load_state_dict(state, strict=False) - try: - yield - finally: - model.load_state_dict(old_state) - - -def capture_init(init): - @functools.wraps(init) - def __init__(self, *args, **kwargs): - self._init_args_kwargs = (args, kwargs) - init(self, *args, **kwargs) - - return __init__ diff --git a/demucs/demucs/svd.py b/demucs/demucs/svd.py deleted file mode 100644 index 1cbaa82c..00000000 --- a/demucs/demucs/svd.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Ways to make the model stronger.""" -import random -import torch - - -def power_iteration(m, niters=1, bs=1): - """This is the power method. batch size is used to try multiple starting point in parallel.""" - assert m.dim() == 2 - assert m.shape[0] == m.shape[1] - dim = m.shape[0] - b = torch.randn(dim, bs, device=m.device, dtype=m.dtype) - - for _ in range(niters): - n = m.mm(b) - norm = n.norm(dim=0, keepdim=True) - b = n / (1e-10 + norm) - - return norm.mean() - - -# We need a shared RNG to make sure all the distributed worker will skip the penalty together, -# as otherwise we wouldn't get any speed up. -penalty_rng = random.Random(1234) - - -def svd_penalty(model, min_size=0.1, dim=1, niters=2, powm=False, convtr=True, - proba=1, conv_only=False, exact=False, bs=1): - """ - Penalty on the largest singular value for a layer. - Args: - - model: model to penalize - - min_size: minimum size in MB of a layer to penalize. - - dim: projection dimension for the svd_lowrank. Higher is better but slower. - - niters: number of iterations in the algorithm used by svd_lowrank. - - powm: use power method instead of lowrank SVD, my own experience - is that it is both slower and less stable. - - convtr: when True, differentiate between Conv and Transposed Conv. - this is kept for compatibility with older experiments. - - proba: probability to apply the penalty. - - conv_only: only apply to conv and conv transposed, not LSTM - (might not be reliable for other models than Demucs). - - exact: use exact SVD (slow but useful at validation). - - bs: batch_size for power method. - """ - total = 0 - if penalty_rng.random() > proba: - return 0. - - for m in model.modules(): - for name, p in m.named_parameters(recurse=False): - if p.numel() / 2**18 < min_size: - continue - if convtr: - if isinstance(m, (torch.nn.ConvTranspose1d, torch.nn.ConvTranspose2d)): - if p.dim() in [3, 4]: - p = p.transpose(0, 1).contiguous() - if p.dim() == 3: - p = p.view(len(p), -1) - elif p.dim() == 4: - p = p.view(len(p), -1) - elif p.dim() == 1: - continue - elif conv_only: - continue - assert p.dim() == 2, (name, p.shape) - if exact: - estimate = torch.svd(p, compute_uv=False)[1].pow(2).max() - elif powm: - a, b = p.shape - if a < b: - n = p.mm(p.t()) - else: - n = p.t().mm(p) - estimate = power_iteration(n, niters, bs) - else: - estimate = torch.svd_lowrank(p, dim, niters)[1][0].pow(2) - total += estimate - return total / proba diff --git a/demucs/demucs/train.py b/demucs/demucs/train.py deleted file mode 100644 index e045b83f..00000000 --- a/demucs/demucs/train.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Main training script entry point""" - -import logging -import os -from pathlib import Path -import sys - -from dora import hydra_main -import hydra -from hydra.core.global_hydra import GlobalHydra -from omegaconf import OmegaConf -from . import audio_legacy -import torch -from torch import nn -import torchaudio -from torch.utils.data import ConcatDataset - -from . import distrib -from .wav import get_wav_datasets, get_musdb_wav_datasets -from .demucs import Demucs -from .hdemucs import HDemucs -from .htdemucs import HTDemucs -from .repitch import RepitchedWrapper -from .solver import Solver -from .states import capture_init -from .utils import random_subset - -logger = logging.getLogger(__name__) - - -class TorchHDemucsWrapper(nn.Module): - """Wrapper around torchaudio HDemucs implementation to provide the proper metadata - for model evaluation. - See https://pytorch.org/audio/stable/tutorials/hybrid_demucs_tutorial.html""" - - @capture_init - def __init__(self, **kwargs): - super().__init__() - try: - from torchaudio.models import HDemucs as TorchHDemucs - except ImportError: - raise ImportError("Please upgrade torchaudio for using its implementation of HDemucs") - self.samplerate = kwargs.pop('samplerate') - self.segment = kwargs.pop('segment') - self.sources = kwargs['sources'] - self.torch_hdemucs = TorchHDemucs(**kwargs) - - def forward(self, mix): - return self.torch_hdemucs.forward(mix) - - -def get_model(args): - extra = { - 'sources': list(args.dset.sources), - 'audio_channels': args.dset.channels, - 'samplerate': args.dset.samplerate, - 'segment': args.model_segment or 4 * args.dset.segment, - } - klass = { - 'demucs': Demucs, - 'hdemucs': HDemucs, - 'htdemucs': HTDemucs, - 'torch_hdemucs': TorchHDemucsWrapper, - }[args.model] - kw = OmegaConf.to_container(getattr(args, args.model), resolve=True) - model = klass(**extra, **kw) - return model - - -def get_optimizer(model, args): - seen_params = set() - other_params = [] - groups = [] - for n, module in model.named_modules(): - if hasattr(module, "make_optim_group"): - group = module.make_optim_group() - params = set(group["params"]) - assert params.isdisjoint(seen_params) - seen_params |= set(params) - groups.append(group) - for param in model.parameters(): - if param not in seen_params: - other_params.append(param) - groups.insert(0, {"params": other_params}) - parameters = groups - if args.optim.optim == "adam": - return torch.optim.Adam( - parameters, - lr=args.optim.lr, - betas=(args.optim.momentum, args.optim.beta2), - weight_decay=args.optim.weight_decay, - ) - elif args.optim.optim == "adamw": - return torch.optim.AdamW( - parameters, - lr=args.optim.lr, - betas=(args.optim.momentum, args.optim.beta2), - weight_decay=args.optim.weight_decay, - ) - else: - raise ValueError("Invalid optimizer %s", args.optim.optimizer) - - -def get_datasets(args): - if args.dset.backend: - torchaudio.set_audio_backend(args.dset.backend) - if args.dset.use_musdb: - train_set, valid_set = get_musdb_wav_datasets(args.dset) - else: - train_set, valid_set = [], [] - if args.dset.wav: - extra_train_set, extra_valid_set = get_wav_datasets(args.dset) - if len(args.dset.sources) <= 4: - train_set = ConcatDataset([train_set, extra_train_set]) - valid_set = ConcatDataset([valid_set, extra_valid_set]) - else: - train_set = extra_train_set - valid_set = extra_valid_set - - if args.dset.wav2: - extra_train_set, extra_valid_set = get_wav_datasets(args.dset, "wav2") - weight = args.dset.wav2_weight - if weight is not None: - b = len(train_set) - e = len(extra_train_set) - reps = max(1, round(e / b * (1 / weight - 1))) - else: - reps = 1 - train_set = ConcatDataset([train_set] * reps + [extra_train_set]) - if args.dset.wav2_valid: - if weight is not None: - b = len(valid_set) - n_kept = int(round(weight * b / (1 - weight))) - valid_set = ConcatDataset( - [valid_set, random_subset(extra_valid_set, n_kept)] - ) - else: - valid_set = ConcatDataset([valid_set, extra_valid_set]) - if args.dset.valid_samples is not None: - valid_set = random_subset(valid_set, args.dset.valid_samples) - assert len(train_set) - assert len(valid_set) - return train_set, valid_set - - -def get_solver(args, model_only=False): - distrib.init() - - torch.manual_seed(args.seed) - model = get_model(args) - if args.misc.show: - logger.info(model) - mb = sum(p.numel() for p in model.parameters()) * 4 / 2**20 - logger.info('Size: %.1f MB', mb) - if hasattr(model, 'valid_length'): - field = model.valid_length(1) - logger.info('Field: %.1f ms', field / args.dset.samplerate * 1000) - sys.exit(0) - - # torch also initialize cuda seed if available - if torch.cuda.is_available(): - model.cuda() - - # optimizer - optimizer = get_optimizer(model, args) - - assert args.batch_size % distrib.world_size == 0 - args.batch_size //= distrib.world_size - - if model_only: - return Solver(None, model, optimizer, args) - - train_set, valid_set = get_datasets(args) - - if args.augment.repitch.proba: - vocals = [] - if 'vocals' in args.dset.sources: - vocals.append(args.dset.sources.index('vocals')) - else: - logger.warning('No vocal source found') - if args.augment.repitch.proba: - train_set = RepitchedWrapper(train_set, vocals=vocals, **args.augment.repitch) - - logger.info("train/valid set size: %d %d", len(train_set), len(valid_set)) - train_loader = distrib.loader( - train_set, batch_size=args.batch_size, shuffle=True, - num_workers=args.misc.num_workers, drop_last=True) - if args.dset.full_cv: - valid_loader = distrib.loader( - valid_set, batch_size=1, shuffle=False, - num_workers=args.misc.num_workers) - else: - valid_loader = distrib.loader( - valid_set, batch_size=args.batch_size, shuffle=False, - num_workers=args.misc.num_workers, drop_last=True) - loaders = {"train": train_loader, "valid": valid_loader} - - # Construct Solver - return Solver(loaders, model, optimizer, args) - - -def get_solver_from_sig(sig, model_only=False): - inst = GlobalHydra.instance() - hyd = None - if inst.is_initialized(): - hyd = inst.hydra - inst.clear() - xp = main.get_xp_from_sig(sig) - if hyd is not None: - inst.clear() - inst.initialize(hyd) - - with xp.enter(stack=True): - return get_solver(xp.cfg, model_only) - - -@hydra_main(config_path="../conf", config_name="config", version_base="1.1") -def main(args): - global __file__ - __file__ = hydra.utils.to_absolute_path(__file__) - for attr in ["musdb", "wav", "metadata"]: - val = getattr(args.dset, attr) - if val is not None: - setattr(args.dset, attr, hydra.utils.to_absolute_path(val)) - - os.environ["OMP_NUM_THREADS"] = "1" - os.environ["MKL_NUM_THREADS"] = "1" - - if args.misc.verbose: - logger.setLevel(logging.DEBUG) - - logger.info("For logs, checkpoints and samples check %s", os.getcwd()) - logger.debug(args) - from dora import get_xp - logger.debug(get_xp().cfg) - - solver = get_solver(args) - solver.train() - - -if '_DORA_TEST_PATH' in os.environ: - main.dora.dir = Path(os.environ['_DORA_TEST_PATH']) - - -if __name__ == "__main__": - main() diff --git a/demucs/demucs/transformer.py b/demucs/demucs/transformer.py deleted file mode 100644 index 56a465b8..00000000 --- a/demucs/demucs/transformer.py +++ /dev/null @@ -1,839 +0,0 @@ -# Copyright (c) 2019-present, Meta, Inc. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -# First author is Simon Rouard. - -import random -import typing as tp - -import torch -import torch.nn as nn -import torch.nn.functional as F -import numpy as np -import math -from einops import rearrange - - -def create_sin_embedding( - length: int, dim: int, shift: int = 0, device="cpu", max_period=10000 -): - # We aim for TBC format - assert dim % 2 == 0 - pos = shift + torch.arange(length, device=device).view(-1, 1, 1) - half_dim = dim // 2 - adim = torch.arange(dim // 2, device=device).view(1, 1, -1) - phase = pos / (max_period ** (adim / (half_dim - 1))) - return torch.cat( - [ - torch.cos(phase), - torch.sin(phase), - ], - dim=-1, - ) - - -def create_2d_sin_embedding(d_model, height, width, device="cpu", max_period=10000): - """ - :param d_model: dimension of the model - :param height: height of the positions - :param width: width of the positions - :return: d_model*height*width position matrix - """ - if d_model % 4 != 0: - raise ValueError( - "Cannot use sin/cos positional encoding with " - "odd dimension (got dim={:d})".format(d_model) - ) - pe = torch.zeros(d_model, height, width) - # Each dimension use half of d_model - d_model = int(d_model / 2) - div_term = torch.exp( - torch.arange(0.0, d_model, 2) * -(math.log(max_period) / d_model) - ) - pos_w = torch.arange(0.0, width).unsqueeze(1) - pos_h = torch.arange(0.0, height).unsqueeze(1) - pe[0:d_model:2, :, :] = ( - torch.sin(pos_w * div_term).transpose(0, 1).unsqueeze(1).repeat(1, height, 1) - ) - pe[1:d_model:2, :, :] = ( - torch.cos(pos_w * div_term).transpose(0, 1).unsqueeze(1).repeat(1, height, 1) - ) - pe[d_model::2, :, :] = ( - torch.sin(pos_h * div_term).transpose(0, 1).unsqueeze(2).repeat(1, 1, width) - ) - pe[d_model + 1:: 2, :, :] = ( - torch.cos(pos_h * div_term).transpose(0, 1).unsqueeze(2).repeat(1, 1, width) - ) - - return pe[None, :].to(device) - - -def create_sin_embedding_cape( - length: int, - dim: int, - batch_size: int, - mean_normalize: bool, - augment: bool, # True during training - max_global_shift: float = 0.0, # delta max - max_local_shift: float = 0.0, # epsilon max - max_scale: float = 1.0, - device: str = "cpu", - max_period: float = 10000.0, -): - # We aim for TBC format - assert dim % 2 == 0 - pos = 1.0 * torch.arange(length).view(-1, 1, 1) # (length, 1, 1) - pos = pos.repeat(1, batch_size, 1) # (length, batch_size, 1) - if mean_normalize: - pos -= torch.nanmean(pos, dim=0, keepdim=True) - - if augment: - delta = np.random.uniform( - -max_global_shift, +max_global_shift, size=[1, batch_size, 1] - ) - delta_local = np.random.uniform( - -max_local_shift, +max_local_shift, size=[length, batch_size, 1] - ) - log_lambdas = np.random.uniform( - -np.log(max_scale), +np.log(max_scale), size=[1, batch_size, 1] - ) - pos = (pos + delta + delta_local) * np.exp(log_lambdas) - - pos = pos.to(device) - - half_dim = dim // 2 - adim = torch.arange(dim // 2, device=device).view(1, 1, -1) - phase = pos / (max_period ** (adim / (half_dim - 1))) - return torch.cat( - [ - torch.cos(phase), - torch.sin(phase), - ], - dim=-1, - ).float() - - -def get_causal_mask(length): - pos = torch.arange(length) - return pos > pos[:, None] - - -def get_elementary_mask( - T1, - T2, - mask_type, - sparse_attn_window, - global_window, - mask_random_seed, - sparsity, - device, -): - """ - When the input of the Decoder has length T1 and the output T2 - The mask matrix has shape (T2, T1) - """ - assert mask_type in ["diag", "jmask", "random", "global"] - - if mask_type == "global": - mask = torch.zeros(T2, T1, dtype=torch.bool) - mask[:, :global_window] = True - line_window = int(global_window * T2 / T1) - mask[:line_window, :] = True - - if mask_type == "diag": - - mask = torch.zeros(T2, T1, dtype=torch.bool) - rows = torch.arange(T2)[:, None] - cols = ( - (T1 / T2 * rows + torch.arange(-sparse_attn_window, sparse_attn_window + 1)) - .long() - .clamp(0, T1 - 1) - ) - mask.scatter_(1, cols, torch.ones(1, dtype=torch.bool).expand_as(cols)) - - elif mask_type == "jmask": - mask = torch.zeros(T2 + 2, T1 + 2, dtype=torch.bool) - rows = torch.arange(T2 + 2)[:, None] - t = torch.arange(0, int((2 * T1) ** 0.5 + 1)) - t = (t * (t + 1) / 2).int() - t = torch.cat([-t.flip(0)[:-1], t]) - cols = (T1 / T2 * rows + t).long().clamp(0, T1 + 1) - mask.scatter_(1, cols, torch.ones(1, dtype=torch.bool).expand_as(cols)) - mask = mask[1:-1, 1:-1] - - elif mask_type == "random": - gene = torch.Generator(device=device) - gene.manual_seed(mask_random_seed) - mask = ( - torch.rand(T1 * T2, generator=gene, device=device).reshape(T2, T1) - > sparsity - ) - - mask = mask.to(device) - return mask - - -def get_mask( - T1, - T2, - mask_type, - sparse_attn_window, - global_window, - mask_random_seed, - sparsity, - device, -): - """ - Return a SparseCSRTensor mask that is a combination of elementary masks - mask_type can be a combination of multiple masks: for instance "diag_jmask_random" - """ - from xformers.sparse import SparseCSRTensor - # create a list - mask_types = mask_type.split("_") - - all_masks = [ - get_elementary_mask( - T1, - T2, - mask, - sparse_attn_window, - global_window, - mask_random_seed, - sparsity, - device, - ) - for mask in mask_types - ] - - final_mask = torch.stack(all_masks).sum(axis=0) > 0 - - return SparseCSRTensor.from_dense(final_mask[None]) - - -class ScaledEmbedding(nn.Module): - def __init__( - self, - num_embeddings: int, - embedding_dim: int, - scale: float = 1.0, - boost: float = 3.0, - ): - super().__init__() - self.embedding = nn.Embedding(num_embeddings, embedding_dim) - self.embedding.weight.data *= scale / boost - self.boost = boost - - @property - def weight(self): - return self.embedding.weight * self.boost - - def forward(self, x): - return self.embedding(x) * self.boost - - -class LayerScale(nn.Module): - """Layer scale from [Touvron et al 2021] (https://arxiv.org/pdf/2103.17239.pdf). - This rescales diagonaly residual outputs close to 0 initially, then learnt. - """ - - def __init__(self, channels: int, init: float = 0, channel_last=False): - """ - channel_last = False corresponds to (B, C, T) tensors - channel_last = True corresponds to (T, B, C) tensors - """ - super().__init__() - self.channel_last = channel_last - self.scale = nn.Parameter(torch.zeros(channels, requires_grad=True)) - self.scale.data[:] = init - - def forward(self, x): - if self.channel_last: - return self.scale * x - else: - return self.scale[:, None] * x - - -class MyGroupNorm(nn.GroupNorm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def forward(self, x): - """ - x: (B, T, C) - if num_groups=1: Normalisation on all T and C together for each B - """ - x = x.transpose(1, 2) - return super().forward(x).transpose(1, 2) - - -class MyTransformerEncoderLayer(nn.TransformerEncoderLayer): - def __init__( - self, - d_model, - nhead, - dim_feedforward=2048, - dropout=0.1, - activation=F.relu, - group_norm=0, - norm_first=False, - norm_out=False, - layer_norm_eps=1e-5, - layer_scale=False, - init_values=1e-4, - device=None, - dtype=None, - sparse=False, - mask_type="diag", - mask_random_seed=42, - sparse_attn_window=500, - global_window=50, - auto_sparsity=False, - sparsity=0.95, - batch_first=False, - ): - factory_kwargs = {"device": device, "dtype": dtype} - super().__init__( - d_model=d_model, - nhead=nhead, - dim_feedforward=dim_feedforward, - dropout=dropout, - activation=activation, - layer_norm_eps=layer_norm_eps, - batch_first=batch_first, - norm_first=norm_first, - device=device, - dtype=dtype, - ) - self.sparse = sparse - self.auto_sparsity = auto_sparsity - if sparse: - if not auto_sparsity: - self.mask_type = mask_type - self.sparse_attn_window = sparse_attn_window - self.global_window = global_window - self.sparsity = sparsity - if group_norm: - self.norm1 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) - self.norm2 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) - - self.norm_out = None - if self.norm_first & norm_out: - self.norm_out = MyGroupNorm(num_groups=int(norm_out), num_channels=d_model) - self.gamma_1 = ( - LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() - ) - self.gamma_2 = ( - LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() - ) - - if sparse: - self.self_attn = MultiheadAttention( - d_model, nhead, dropout=dropout, batch_first=batch_first, - auto_sparsity=sparsity if auto_sparsity else 0, - ) - self.__setattr__("src_mask", torch.zeros(1, 1)) - self.mask_random_seed = mask_random_seed - - def forward(self, src, src_mask=None, src_key_padding_mask=None): - """ - if batch_first = False, src shape is (T, B, C) - the case where batch_first=True is not covered - """ - device = src.device - x = src - T, B, C = x.shape - if self.sparse and not self.auto_sparsity: - assert src_mask is None - src_mask = self.src_mask - if src_mask.shape[-1] != T: - src_mask = get_mask( - T, - T, - self.mask_type, - self.sparse_attn_window, - self.global_window, - self.mask_random_seed, - self.sparsity, - device, - ) - self.__setattr__("src_mask", src_mask) - - if self.norm_first: - x = x + self.gamma_1( - self._sa_block(self.norm1(x), src_mask, src_key_padding_mask) - ) - x = x + self.gamma_2(self._ff_block(self.norm2(x))) - - if self.norm_out: - x = self.norm_out(x) - else: - x = self.norm1( - x + self.gamma_1(self._sa_block(x, src_mask, src_key_padding_mask)) - ) - x = self.norm2(x + self.gamma_2(self._ff_block(x))) - - return x - - -class CrossTransformerEncoderLayer(nn.Module): - def __init__( - self, - d_model: int, - nhead: int, - dim_feedforward: int = 2048, - dropout: float = 0.1, - activation=F.relu, - layer_norm_eps: float = 1e-5, - layer_scale: bool = False, - init_values: float = 1e-4, - norm_first: bool = False, - group_norm: bool = False, - norm_out: bool = False, - sparse=False, - mask_type="diag", - mask_random_seed=42, - sparse_attn_window=500, - global_window=50, - sparsity=0.95, - auto_sparsity=None, - device=None, - dtype=None, - batch_first=False, - ): - factory_kwargs = {"device": device, "dtype": dtype} - super().__init__() - - self.sparse = sparse - self.auto_sparsity = auto_sparsity - if sparse: - if not auto_sparsity: - self.mask_type = mask_type - self.sparse_attn_window = sparse_attn_window - self.global_window = global_window - self.sparsity = sparsity - - self.cross_attn: nn.Module - self.cross_attn = nn.MultiheadAttention( - d_model, nhead, dropout=dropout, batch_first=batch_first) - # Implementation of Feedforward model - self.linear1 = nn.Linear(d_model, dim_feedforward, **factory_kwargs) - self.dropout = nn.Dropout(dropout) - self.linear2 = nn.Linear(dim_feedforward, d_model, **factory_kwargs) - - self.norm_first = norm_first - self.norm1: nn.Module - self.norm2: nn.Module - self.norm3: nn.Module - if group_norm: - self.norm1 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) - self.norm2 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) - self.norm3 = MyGroupNorm(int(group_norm), d_model, eps=layer_norm_eps, **factory_kwargs) - else: - self.norm1 = nn.LayerNorm(d_model, eps=layer_norm_eps, **factory_kwargs) - self.norm2 = nn.LayerNorm(d_model, eps=layer_norm_eps, **factory_kwargs) - self.norm3 = nn.LayerNorm(d_model, eps=layer_norm_eps, **factory_kwargs) - - self.norm_out = None - if self.norm_first & norm_out: - self.norm_out = MyGroupNorm(num_groups=int(norm_out), num_channels=d_model) - - self.gamma_1 = ( - LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() - ) - self.gamma_2 = ( - LayerScale(d_model, init_values, True) if layer_scale else nn.Identity() - ) - - self.dropout1 = nn.Dropout(dropout) - self.dropout2 = nn.Dropout(dropout) - - # Legacy string support for activation function. - if isinstance(activation, str): - self.activation = self._get_activation_fn(activation) - else: - self.activation = activation - - if sparse: - self.cross_attn = MultiheadAttention( - d_model, nhead, dropout=dropout, batch_first=batch_first, - auto_sparsity=sparsity if auto_sparsity else 0) - if not auto_sparsity: - self.__setattr__("mask", torch.zeros(1, 1)) - self.mask_random_seed = mask_random_seed - - def forward(self, q, k, mask=None): - """ - Args: - q: tensor of shape (T, B, C) - k: tensor of shape (S, B, C) - mask: tensor of shape (T, S) - - """ - device = q.device - T, B, C = q.shape - S, B, C = k.shape - if self.sparse and not self.auto_sparsity: - assert mask is None - mask = self.mask - if mask.shape[-1] != S or mask.shape[-2] != T: - mask = get_mask( - S, - T, - self.mask_type, - self.sparse_attn_window, - self.global_window, - self.mask_random_seed, - self.sparsity, - device, - ) - self.__setattr__("mask", mask) - - if self.norm_first: - x = q + self.gamma_1(self._ca_block(self.norm1(q), self.norm2(k), mask)) - x = x + self.gamma_2(self._ff_block(self.norm3(x))) - if self.norm_out: - x = self.norm_out(x) - else: - x = self.norm1(q + self.gamma_1(self._ca_block(q, k, mask))) - x = self.norm2(x + self.gamma_2(self._ff_block(x))) - - return x - - # self-attention block - def _ca_block(self, q, k, attn_mask=None): - x = self.cross_attn(q, k, k, attn_mask=attn_mask, need_weights=False)[0] - return self.dropout1(x) - - # feed forward block - def _ff_block(self, x): - x = self.linear2(self.dropout(self.activation(self.linear1(x)))) - return self.dropout2(x) - - def _get_activation_fn(self, activation): - if activation == "relu": - return F.relu - elif activation == "gelu": - return F.gelu - - raise RuntimeError("activation should be relu/gelu, not {}".format(activation)) - - -# ----------------- MULTI-BLOCKS MODELS: ----------------------- - - -class CrossTransformerEncoder(nn.Module): - def __init__( - self, - dim: int, - emb: str = "sin", - hidden_scale: float = 4.0, - num_heads: int = 8, - num_layers: int = 6, - cross_first: bool = False, - dropout: float = 0.0, - max_positions: int = 1000, - norm_in: bool = True, - norm_in_group: bool = False, - group_norm: int = False, - norm_first: bool = False, - norm_out: bool = False, - max_period: float = 10000.0, - weight_decay: float = 0.0, - lr: tp.Optional[float] = None, - layer_scale: bool = False, - gelu: bool = True, - sin_random_shift: int = 0, - weight_pos_embed: float = 1.0, - cape_mean_normalize: bool = True, - cape_augment: bool = True, - cape_glob_loc_scale: list = [5000.0, 1.0, 1.4], - sparse_self_attn: bool = False, - sparse_cross_attn: bool = False, - mask_type: str = "diag", - mask_random_seed: int = 42, - sparse_attn_window: int = 500, - global_window: int = 50, - auto_sparsity: bool = False, - sparsity: float = 0.95, - ): - super().__init__() - """ - """ - assert dim % num_heads == 0 - - hidden_dim = int(dim * hidden_scale) - - self.num_layers = num_layers - # classic parity = 1 means that if idx%2 == 1 there is a - # classical encoder else there is a cross encoder - self.classic_parity = 1 if cross_first else 0 - self.emb = emb - self.max_period = max_period - self.weight_decay = weight_decay - self.weight_pos_embed = weight_pos_embed - self.sin_random_shift = sin_random_shift - if emb == "cape": - self.cape_mean_normalize = cape_mean_normalize - self.cape_augment = cape_augment - self.cape_glob_loc_scale = cape_glob_loc_scale - if emb == "scaled": - self.position_embeddings = ScaledEmbedding(max_positions, dim, scale=0.2) - - self.lr = lr - - activation: tp.Any = F.gelu if gelu else F.relu - - self.norm_in: nn.Module - self.norm_in_t: nn.Module - if norm_in: - self.norm_in = nn.LayerNorm(dim) - self.norm_in_t = nn.LayerNorm(dim) - elif norm_in_group: - self.norm_in = MyGroupNorm(int(norm_in_group), dim) - self.norm_in_t = MyGroupNorm(int(norm_in_group), dim) - else: - self.norm_in = nn.Identity() - self.norm_in_t = nn.Identity() - - # spectrogram layers - self.layers = nn.ModuleList() - # temporal layers - self.layers_t = nn.ModuleList() - - kwargs_common = { - "d_model": dim, - "nhead": num_heads, - "dim_feedforward": hidden_dim, - "dropout": dropout, - "activation": activation, - "group_norm": group_norm, - "norm_first": norm_first, - "norm_out": norm_out, - "layer_scale": layer_scale, - "mask_type": mask_type, - "mask_random_seed": mask_random_seed, - "sparse_attn_window": sparse_attn_window, - "global_window": global_window, - "sparsity": sparsity, - "auto_sparsity": auto_sparsity, - "batch_first": True, - } - - kwargs_classic_encoder = dict(kwargs_common) - kwargs_classic_encoder.update({ - "sparse": sparse_self_attn, - }) - kwargs_cross_encoder = dict(kwargs_common) - kwargs_cross_encoder.update({ - "sparse": sparse_cross_attn, - }) - - for idx in range(num_layers): - if idx % 2 == self.classic_parity: - - self.layers.append(MyTransformerEncoderLayer(**kwargs_classic_encoder)) - self.layers_t.append( - MyTransformerEncoderLayer(**kwargs_classic_encoder) - ) - - else: - self.layers.append(CrossTransformerEncoderLayer(**kwargs_cross_encoder)) - - self.layers_t.append( - CrossTransformerEncoderLayer(**kwargs_cross_encoder) - ) - - def forward(self, x, xt): - B, C, Fr, T1 = x.shape - pos_emb_2d = create_2d_sin_embedding( - C, Fr, T1, x.device, self.max_period - ) # (1, C, Fr, T1) - pos_emb_2d = rearrange(pos_emb_2d, "b c fr t1 -> b (t1 fr) c") - x = rearrange(x, "b c fr t1 -> b (t1 fr) c") - x = self.norm_in(x) - x = x + self.weight_pos_embed * pos_emb_2d - - B, C, T2 = xt.shape - xt = rearrange(xt, "b c t2 -> b t2 c") # now T2, B, C - pos_emb = self._get_pos_embedding(T2, B, C, x.device) - pos_emb = rearrange(pos_emb, "t2 b c -> b t2 c") - xt = self.norm_in_t(xt) - xt = xt + self.weight_pos_embed * pos_emb - - for idx in range(self.num_layers): - if idx % 2 == self.classic_parity: - x = self.layers[idx](x) - xt = self.layers_t[idx](xt) - else: - old_x = x - x = self.layers[idx](x, xt) - xt = self.layers_t[idx](xt, old_x) - - x = rearrange(x, "b (t1 fr) c -> b c fr t1", t1=T1) - xt = rearrange(xt, "b t2 c -> b c t2") - return x, xt - - def _get_pos_embedding(self, T, B, C, device): - if self.emb == "sin": - shift = random.randrange(self.sin_random_shift + 1) - pos_emb = create_sin_embedding( - T, C, shift=shift, device=device, max_period=self.max_period - ) - elif self.emb == "cape": - if self.training: - pos_emb = create_sin_embedding_cape( - T, - C, - B, - device=device, - max_period=self.max_period, - mean_normalize=self.cape_mean_normalize, - augment=self.cape_augment, - max_global_shift=self.cape_glob_loc_scale[0], - max_local_shift=self.cape_glob_loc_scale[1], - max_scale=self.cape_glob_loc_scale[2], - ) - else: - pos_emb = create_sin_embedding_cape( - T, - C, - B, - device=device, - max_period=self.max_period, - mean_normalize=self.cape_mean_normalize, - augment=False, - ) - - elif self.emb == "scaled": - pos = torch.arange(T, device=device) - pos_emb = self.position_embeddings(pos)[:, None] - - return pos_emb - - def make_optim_group(self): - group = {"params": list(self.parameters()), "weight_decay": self.weight_decay} - if self.lr is not None: - group["lr"] = self.lr - return group - - -# Attention Modules - - -class MultiheadAttention(nn.Module): - def __init__( - self, - embed_dim, - num_heads, - dropout=0.0, - bias=True, - add_bias_kv=False, - add_zero_attn=False, - kdim=None, - vdim=None, - batch_first=False, - auto_sparsity=None, - ): - super().__init__() - assert auto_sparsity is not None, "sanity check" - self.num_heads = num_heads - self.q = torch.nn.Linear(embed_dim, embed_dim, bias=bias) - self.k = torch.nn.Linear(embed_dim, embed_dim, bias=bias) - self.v = torch.nn.Linear(embed_dim, embed_dim, bias=bias) - self.attn_drop = torch.nn.Dropout(dropout) - self.proj = torch.nn.Linear(embed_dim, embed_dim, bias) - self.proj_drop = torch.nn.Dropout(dropout) - self.batch_first = batch_first - self.auto_sparsity = auto_sparsity - - def forward( - self, - query, - key, - value, - key_padding_mask=None, - need_weights=True, - attn_mask=None, - average_attn_weights=True, - ): - - if not self.batch_first: # N, B, C - query = query.permute(1, 0, 2) # B, N_q, C - key = key.permute(1, 0, 2) # B, N_k, C - value = value.permute(1, 0, 2) # B, N_k, C - B, N_q, C = query.shape - B, N_k, C = key.shape - - q = ( - self.q(query) - .reshape(B, N_q, self.num_heads, C // self.num_heads) - .permute(0, 2, 1, 3) - ) - q = q.flatten(0, 1) - k = ( - self.k(key) - .reshape(B, N_k, self.num_heads, C // self.num_heads) - .permute(0, 2, 1, 3) - ) - k = k.flatten(0, 1) - v = ( - self.v(value) - .reshape(B, N_k, self.num_heads, C // self.num_heads) - .permute(0, 2, 1, 3) - ) - v = v.flatten(0, 1) - - if self.auto_sparsity: - assert attn_mask is None - x = dynamic_sparse_attention(q, k, v, sparsity=self.auto_sparsity) - else: - x = scaled_dot_product_attention(q, k, v, attn_mask, dropout=self.attn_drop) - x = x.reshape(B, self.num_heads, N_q, C // self.num_heads) - - x = x.transpose(1, 2).reshape(B, N_q, C) - x = self.proj(x) - x = self.proj_drop(x) - if not self.batch_first: - x = x.permute(1, 0, 2) - return x, None - - -def scaled_query_key_softmax(q, k, att_mask): - from xformers.ops import masked_matmul - q = q / (k.size(-1)) ** 0.5 - att = masked_matmul(q, k.transpose(-2, -1), att_mask) - att = torch.nn.functional.softmax(att, -1) - return att - - -def scaled_dot_product_attention(q, k, v, att_mask, dropout): - att = scaled_query_key_softmax(q, k, att_mask=att_mask) - att = dropout(att) - y = att @ v - return y - - -def _compute_buckets(x, R): - qq = torch.einsum('btf,bfhi->bhti', x, R) - qq = torch.cat([qq, -qq], dim=-1) - buckets = qq.argmax(dim=-1) - - return buckets.permute(0, 2, 1).byte().contiguous() - - -def dynamic_sparse_attention(query, key, value, sparsity, infer_sparsity=True, attn_bias=None): - # assert False, "The code for the custom sparse kernel is not ready for release yet." - from xformers.ops import find_locations, sparse_memory_efficient_attention - n_hashes = 32 - proj_size = 4 - query, key, value = [x.contiguous() for x in [query, key, value]] - with torch.no_grad(): - R = torch.randn(1, query.shape[-1], n_hashes, proj_size // 2, device=query.device) - bucket_query = _compute_buckets(query, R) - bucket_key = _compute_buckets(key, R) - row_offsets, column_indices = find_locations( - bucket_query, bucket_key, sparsity, infer_sparsity) - return sparse_memory_efficient_attention( - query, key, value, row_offsets, column_indices, attn_bias) diff --git a/demucs/demucs/utils.py b/demucs/demucs/utils.py deleted file mode 100755 index a3f5993e..00000000 --- a/demucs/demucs/utils.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. - -from collections import defaultdict -from concurrent.futures import CancelledError -from contextlib import contextmanager -import math -import os -import tempfile -import typing as tp - -import torch -from torch.nn import functional as F -from torch.utils.data import Subset - - -def unfold(a, kernel_size, stride): - """Given input of size [*OT, T], output Tensor of size [*OT, F, K] - with K the kernel size, by extracting frames with the given stride. - - This will pad the input so that `F = ceil(T / K)`. - - see https://github.com/pytorch/pytorch/issues/60466 - """ - *shape, length = a.shape - n_frames = math.ceil(length / stride) - tgt_length = (n_frames - 1) * stride + kernel_size - a = F.pad(a, (0, tgt_length - length)) - strides = list(a.stride()) - assert strides[-1] == 1, 'data should be contiguous' - strides = strides[:-1] + [stride, 1] - return a.as_strided([*shape, n_frames, kernel_size], strides) - - -def center_trim(tensor: torch.Tensor, reference: tp.Union[torch.Tensor, int]): - """ - Center trim `tensor` with respect to `reference`, along the last dimension. - `reference` can also be a number, representing the length to trim to. - If the size difference != 0 mod 2, the extra sample is removed on the right side. - """ - ref_size: int - if isinstance(reference, torch.Tensor): - ref_size = reference.size(-1) - else: - ref_size = reference - delta = tensor.size(-1) - ref_size - if delta < 0: - raise ValueError("tensor must be larger than reference. " f"Delta is {delta}.") - if delta: - tensor = tensor[..., delta // 2:-(delta - delta // 2)] - return tensor - - -def pull_metric(history: tp.List[dict], name: str): - out = [] - for metrics in history: - metric = metrics - for part in name.split("."): - metric = metric[part] - out.append(metric) - return out - - -def EMA(beta: float = 1): - """ - Exponential Moving Average callback. - Returns a single function that can be called to repeatidly update the EMA - with a dict of metrics. The callback will return - the new averaged dict of metrics. - - Note that for `beta=1`, this is just plain averaging. - """ - fix: tp.Dict[str, float] = defaultdict(float) - total: tp.Dict[str, float] = defaultdict(float) - - def _update(metrics: dict, weight: float = 1) -> dict: - nonlocal total, fix - for key, value in metrics.items(): - total[key] = total[key] * beta + weight * float(value) - fix[key] = fix[key] * beta + weight - return {key: tot / fix[key] for key, tot in total.items()} - return _update - - -def sizeof_fmt(num: float, suffix: str = 'B'): - """ - Given `num` bytes, return human readable size. - Taken from https://stackoverflow.com/a/1094933 - """ - for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: - if abs(num) < 1024.0: - return "%3.1f%s%s" % (num, unit, suffix) - num /= 1024.0 - return "%.1f%s%s" % (num, 'Yi', suffix) - - -@contextmanager -def temp_filenames(count: int, delete=True): - names = [] - try: - for _ in range(count): - names.append(tempfile.NamedTemporaryFile(delete=False).name) - yield names - finally: - if delete: - for name in names: - os.unlink(name) - - -def random_subset(dataset, max_samples: int, seed: int = 42): - if max_samples >= len(dataset): - return dataset - - generator = torch.Generator().manual_seed(seed) - perm = torch.randperm(len(dataset), generator=generator) - return Subset(dataset, perm[:max_samples].tolist()) - - -class DummyPoolExecutor: - class DummyResult: - def __init__(self, func, _dict, *args, **kwargs): - self.func = func - self._dict = _dict - self.args = args - self.kwargs = kwargs - - def result(self): - if self._dict["run"]: - return self.func(*self.args, **self.kwargs) - else: - raise CancelledError() - - def __init__(self, workers=0): - self._dict = {"run": True} - - def submit(self, func, *args, **kwargs): - return DummyPoolExecutor.DummyResult(func, self._dict, *args, **kwargs) - - def shutdown(self, *_, **__): - self._dict["run"] = False - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, exc_tb): - return diff --git a/demucs/demucs/wav.py b/demucs/demucs/wav.py deleted file mode 100644 index ca1e23a3..00000000 --- a/demucs/demucs/wav.py +++ /dev/null @@ -1,255 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -"""Loading wav based datasets, including MusdbHQ.""" - -from collections import OrderedDict -import hashlib -import math -import json -import os -from pathlib import Path -import tqdm - -import musdb -import julius -from . import audio_legacy -import torch as th -from torch import distributed -import torchaudio as ta -from torch.nn import functional as F - -from .audio import convert_audio_channels -from . import distrib - -MIXTURE = "mixture" -EXT = ".wav" - - -def _track_metadata(track, sources, normalize=True, ext=EXT): - track_length = None - track_samplerate = None - mean = 0 - std = 1 - for source in sources + [MIXTURE]: - file = track / f"{source}{ext}" - if source == MIXTURE and not file.exists(): - audio = 0 - for sub_source in sources: - sub_file = track / f"{sub_source}{ext}" - sub_audio, sr = ta.load(sub_file) - audio += sub_audio - would_clip = audio.abs().max() >= 1 - if would_clip: - assert ta.get_audio_backend() == 'soundfile', 'use dset.backend=soundfile' - ta.save(file, audio, sr, encoding='PCM_F') - - try: - info = ta.info(str(file)) - except RuntimeError: - print(file) - raise - length = info.num_frames - if track_length is None: - track_length = length - track_samplerate = info.sample_rate - elif track_length != length: - raise ValueError( - f"Invalid length for file {file}: " - f"expecting {track_length} but got {length}.") - elif info.sample_rate != track_samplerate: - raise ValueError( - f"Invalid sample rate for file {file}: " - f"expecting {track_samplerate} but got {info.sample_rate}.") - if source == MIXTURE and normalize: - try: - wav, _ = ta.load(str(file)) - except RuntimeError: - print(file) - raise - wav = wav.mean(0) - mean = wav.mean().item() - std = wav.std().item() - - return {"length": length, "mean": mean, "std": std, "samplerate": track_samplerate} - - -def build_metadata(path, sources, normalize=True, ext=EXT): - """ - Build the metadata for `Wavset`. - - Args: - path (str or Path): path to dataset. - sources (list[str]): list of sources to look for. - normalize (bool): if True, loads full track and store normalization - values based on the mixture file. - ext (str): extension of audio files (default is .wav). - """ - - meta = {} - path = Path(path) - pendings = [] - from concurrent.futures import ThreadPoolExecutor - with ThreadPoolExecutor(8) as pool: - for root, folders, files in os.walk(path, followlinks=True): - root = Path(root) - if root.name.startswith('.') or folders or root == path: - continue - name = str(root.relative_to(path)) - pendings.append((name, pool.submit(_track_metadata, root, sources, normalize, ext))) - # meta[name] = _track_metadata(root, sources, normalize, ext) - for name, pending in tqdm.tqdm(pendings, ncols=120): - meta[name] = pending.result() - return meta - - -class Wavset: - def __init__( - self, - root, metadata, sources, - segment=None, shift=None, normalize=True, - samplerate=44100, channels=2, ext=EXT): - """ - Waveset (or mp3 set for that matter). Can be used to train - with arbitrary sources. Each track should be one folder inside of `path`. - The folder should contain files named `{source}.{ext}`. - - Args: - root (Path or str): root folder for the dataset. - metadata (dict): output from `build_metadata`. - sources (list[str]): list of source names. - segment (None or float): segment length in seconds. If `None`, returns entire tracks. - shift (None or float): stride in seconds bewteen samples. - normalize (bool): normalizes input audio, **based on the metadata content**, - i.e. the entire track is normalized, not individual extracts. - samplerate (int): target sample rate. if the file sample rate - is different, it will be resampled on the fly. - channels (int): target nb of channels. if different, will be - changed onthe fly. - ext (str): extension for audio files (default is .wav). - - samplerate and channels are converted on the fly. - """ - self.root = Path(root) - self.metadata = OrderedDict(metadata) - self.segment = segment - self.shift = shift or segment - self.normalize = normalize - self.sources = sources - self.channels = channels - self.samplerate = samplerate - self.ext = ext - self.num_examples = [] - for name, meta in self.metadata.items(): - track_duration = meta['length'] / meta['samplerate'] - if segment is None or track_duration < segment: - examples = 1 - else: - examples = int(math.ceil((track_duration - self.segment) / self.shift) + 1) - self.num_examples.append(examples) - - def __len__(self): - return sum(self.num_examples) - - def get_file(self, name, source): - return self.root / name / f"{source}{self.ext}" - - def __getitem__(self, index): - for name, examples in zip(self.metadata, self.num_examples): - if index >= examples: - index -= examples - continue - meta = self.metadata[name] - num_frames = -1 - offset = 0 - if self.segment is not None: - offset = int(meta['samplerate'] * self.shift * index) - num_frames = int(math.ceil(meta['samplerate'] * self.segment)) - wavs = [] - for source in self.sources: - file = self.get_file(name, source) - wav, _ = ta.load(str(file), frame_offset=offset, num_frames=num_frames) - wav = convert_audio_channels(wav, self.channels) - wavs.append(wav) - - example = th.stack(wavs) - example = julius.resample_frac(example, meta['samplerate'], self.samplerate) - if self.normalize: - example = (example - meta['mean']) / meta['std'] - if self.segment: - length = int(self.segment * self.samplerate) - example = example[..., :length] - example = F.pad(example, (0, length - example.shape[-1])) - return example - - -def get_wav_datasets(args, name='wav'): - """Extract the wav datasets from the XP arguments.""" - path = getattr(args, name) - sig = hashlib.sha1(str(path).encode()).hexdigest()[:8] - metadata_file = Path(args.metadata) / ('wav_' + sig + ".json") - train_path = Path(path) / "train" - valid_path = Path(path) / "valid" - if not metadata_file.is_file() and distrib.rank == 0: - metadata_file.parent.mkdir(exist_ok=True, parents=True) - train = build_metadata(train_path, args.sources) - valid = build_metadata(valid_path, args.sources) - json.dump([train, valid], open(metadata_file, "w")) - if distrib.world_size > 1: - distributed.barrier() - train, valid = json.load(open(metadata_file)) - if args.full_cv: - kw_cv = {} - else: - kw_cv = {'segment': args.segment, 'shift': args.shift} - train_set = Wavset(train_path, train, args.sources, - segment=args.segment, shift=args.shift, - samplerate=args.samplerate, channels=args.channels, - normalize=args.normalize) - valid_set = Wavset(valid_path, valid, [MIXTURE] + list(args.sources), - samplerate=args.samplerate, channels=args.channels, - normalize=args.normalize, **kw_cv) - return train_set, valid_set - - -def _get_musdb_valid(): - # Return musdb valid set. - import yaml - setup_path = Path(musdb.__path__[0]) / 'configs' / 'mus.yaml' - setup = yaml.safe_load(open(setup_path, 'r')) - return setup['validation_tracks'] - - -def get_musdb_wav_datasets(args): - """Extract the musdb dataset from the XP arguments.""" - sig = hashlib.sha1(str(args.musdb).encode()).hexdigest()[:8] - metadata_file = Path(args.metadata) / ('musdb_' + sig + ".json") - root = Path(args.musdb) / "train" - if not metadata_file.is_file() and distrib.rank == 0: - metadata_file.parent.mkdir(exist_ok=True, parents=True) - metadata = build_metadata(root, args.sources) - json.dump(metadata, open(metadata_file, "w")) - if distrib.world_size > 1: - distributed.barrier() - metadata = json.load(open(metadata_file)) - - valid_tracks = _get_musdb_valid() - if args.train_valid: - metadata_train = metadata - else: - metadata_train = {name: meta for name, meta in metadata.items() if name not in valid_tracks} - metadata_valid = {name: meta for name, meta in metadata.items() if name in valid_tracks} - if args.full_cv: - kw_cv = {} - else: - kw_cv = {'segment': args.segment, 'shift': args.shift} - train_set = Wavset(root, metadata_train, args.sources, - segment=args.segment, shift=args.shift, - samplerate=args.samplerate, channels=args.channels, - normalize=args.normalize) - valid_set = Wavset(root, metadata_valid, [MIXTURE] + list(args.sources), - samplerate=args.samplerate, channels=args.channels, - normalize=args.normalize, **kw_cv) - return train_set, valid_set diff --git a/demucs/demucs/wdemucs.py b/demucs/demucs/wdemucs.py deleted file mode 100644 index 03d6dd3b..00000000 --- a/demucs/demucs/wdemucs.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the license found in the -# LICENSE file in the root directory of this source tree. -# For compat -from .hdemucs import HDemucs - -WDemucs = HDemucs diff --git a/demucs/docs/api.md b/demucs/docs/api.md deleted file mode 100644 index dbd858a7..00000000 --- a/demucs/docs/api.md +++ /dev/null @@ -1,204 +0,0 @@ -# Demucs APIs - -## Quick start - -Notes: Type hints have been added to all API functions. It is recommended to check them before passing parameters to a function as some arguments only support limited types (e.g. parameter `repo` of method `load_model` only support type `pathlib.Path`). - -1. The first step is to import api module: - -```python -import demucs.api -``` - -2. Then initialize the `Separator`. Parameters which will be served as default values for methods can be passed. Model should be specified. - -```python -# Initialize with default parameters: -separator = demucs.api.Separator() - -# Use another model and segment: -separator = demucs.api.Separator(model="mdx_extra", segment=12) - -# You can also use other parameters defined -``` - -3. Separate it! - -```python -# Separating an audio file -origin, separated = separator.separate_audio_file("file.mp3") - -# Separating a loaded audio -origin, separated = separator.separate_tensor(audio) - -# If you encounter an error like CUDA out of memory, you can use this to change parameters like `segment`: -separator.update_parameter(segment=smaller_segment) -``` - -4. Save audio - -```python -# Remember to create the destination folder before calling `save_audio` -# Or you are likely to recieve `FileNotFoundError` -for file, sources in separated: - for stem, source in sources.items(): - demucs.api.save_audio(source, f"{stem}_{file}", samplerate=separator.samplerate) -``` - -## API References - -The types of each parameter and return value is not listed in this document. To know the exact type of them, please read the type hints in api.py (most modern code editors support inferring types based on type hints). - -### `class Separator` - -The base separator class - -##### Parameters - -model: Pretrained model name or signature. Default is htdemucs. - -repo: Folder containing all pre-trained models for use. - -segment: Length (in seconds) of each segment (only available if `split` is `True`). If not specified, will use the command line option. - -shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and apply the oppositve shift to the output. This is repeated `shifts` time and all predictions are averaged. This effectively makes the model time equivariant and improves SDR by up to 0.2 points. If not specified, will use the command line option. - -split: If True, the input will be broken down into small chunks (length set by `segment`) and predictions will be performed individually on each and concatenated. Useful for model with large memory footprint like Tasnet. If not specified, will use the command line option. - -overlap: The overlap between the splits. If not specified, will use the command line option. - -device (torch.device, str, or None): If provided, device on which to execute the computation, otherwise `wav.device` is assumed. When `device` is different from `wav.device`, only local computations will be on `device`, while the entire tracks will be stored on `wav.device`. If not specified, will use the command line option. - -jobs: Number of jobs. This can increase memory usage but will be much faster when multiple cores are available. If not specified, will use the command line option. - -callback: A function will be called when the separation of a chunk starts or finished. The argument passed to the function will be a dict. For more information, please see the Callback section. - -callback_arg: A dict containing private parameters to be passed to callback function. For more information, please see the Callback section. - -progress: If true, show a progress bar. - -##### Notes for callback - -The function will be called with only one positional parameter whose type is `dict`. The `callback_arg` will be combined with information of current separation progress. The progress information will override the values in `callback_arg` if same key has been used. To abort the separation, raise an exception in `callback` which should be handled by yourself if you want your codes continue to function. - -Progress information contains several keys (These keys will always exist): -- `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. -- `shift_idx`: The index of shifts. Starts from 0. -- `segment_offset`: The offset of current segment. If the number is 441000, it doesn't mean that it is at the 441000 second of the audio, but the "frame" of the tensor. -- `state`: Could be `"start"` or `"end"`. -- `audio_length`: Length of the audio (in "frame" of the tensor). -- `models`: Count of submodels in the model. - -#### `property samplerate` - -A read-only property saving sample rate of the model requires. Will raise a warning if the model is not loaded and return the default value. - -#### `property audio_channels` - -A read-only property saving audio channels of the model requires. Will raise a warning if the model is not loaded and return the default value. - -#### `property model` - -A read-only property saving the model. - -#### `method update_parameter()` - -Update the parameters of separation. - -##### Parameters - -segment: Length (in seconds) of each segment (only available if `split` is `True`). If not specified, will use the command line option. - -shifts: If > 0, will shift in time `wav` by a random amount between 0 and 0.5 sec and apply the oppositve shift to the output. This is repeated `shifts` time and all predictions are averaged. This effectively makes the model time equivariant and improves SDR by up to 0.2 points. If not specified, will use the command line option. - -split: If True, the input will be broken down into small chunks (length set by `segment`) and predictions will be performed individually on each and concatenated. Useful for model with large memory footprint like Tasnet. If not specified, will use the command line option. - -overlap: The overlap between the splits. If not specified, will use the command line option. - -device (torch.device, str, or None): If provided, device on which to execute the computation, otherwise `wav.device` is assumed. When `device` is different from `wav.device`, only local computations will be on `device`, while the entire tracks will be stored on `wav.device`. If not specified, will use the command line option. - -jobs: Number of jobs. This can increase memory usage but will be much faster when multiple cores are available. If not specified, will use the command line option. - -callback: A function will be called when the separation of a chunk starts or finished. The argument passed to the function will be a dict. For more information, please see the Callback section. - -callback_arg: A dict containing private parameters to be passed to callback function. For more information, please see the Callback section. - -progress: If true, show a progress bar. - -##### Notes for callback - -The function will be called with only one positional parameter whose type is `dict`. The `callback_arg` will be combined with information of current separation progress. The progress information will override the values in `callback_arg` if same key has been used. To abort the separation, raise an exception in `callback` which should be handled by yourself if you want your codes continue to function. - -Progress information contains several keys (These keys will always exist): -- `model_idx_in_bag`: The index of the submodel in `BagOfModels`. Starts from 0. -- `shift_idx`: The index of shifts. Starts from 0. -- `segment_offset`: The offset of current segment. If the number is 441000, it doesn't mean that it is at the 441000 second of the audio, but the "frame" of the tensor. -- `state`: Could be `"start"` or `"end"`. -- `audio_length`: Length of the audio (in "frame" of the tensor). -- `models`: Count of submodels in the model. - -#### `method separate_tensor()` - -Separate an audio. - -##### Parameters - -wav: Waveform of the audio. Should have 2 dimensions, the first is each audio channel, while the second is the waveform of each channel. e.g. `tuple(wav.shape) == (2, 884000)` means the audio has 2 channels. - -sr: Sample rate of the original audio, the wave will be resampled if it doesn't match the model. - -##### Returns - -A tuple, whose first element is the original wave and second element is a dict, whose keys are the name of stems and values are separated waves. The original wave will have already been resampled. - -##### Notes - -Use this function with cautiousness. This function does not provide data verifying. - -#### `method separate_audio_file()` - -Separate an audio file. The method will automatically read the file. - -##### Parameters - -wav: Path of the file to be separated. - -##### Returns - -A tuple, whose first element is the original wave and second element is a dict, whose keys are the name of stems and values are separated waves. The original wave will have already been resampled. - -### `function save_audio()` - -Save audio file. - -##### Parameters - -wav: Audio to be saved - -path: The file path to be saved. Ending must be one of `.mp3` and `.wav`. - -samplerate: File sample rate. - -bitrate: If the suffix of `path` is `.mp3`, it will be used to specify the bitrate of mp3. - -clip: Clipping preventing strategy. - -bits_per_sample: If the suffix of `path` is `.wav`, it will be used to specify the bit depth of wav. - -as_float: If it is True and the suffix of `path` is `.wav`, then `bits_per_sample` will be set to 32 and will write the wave file with float format. - -##### Returns - -None - -### `function list_models()` - -List the available models. Please remember that not all the returned models can be successfully loaded. - -##### Parameters - -repo: The repo whose models are to be listed. - -##### Returns - -A dict with two keys ("single" for single models and "bag" for bag of models). The values are lists whose components are strs. \ No newline at end of file diff --git a/demucs/docs/linux.md b/demucs/docs/linux.md deleted file mode 100644 index 31d9a695..00000000 --- a/demucs/docs/linux.md +++ /dev/null @@ -1,28 +0,0 @@ -# Linux support for Demucs - -If your distribution has at least Python 3.8, and you just wish to separate -tracks with Demucs, not train it, you can just run - -```bash -pip3 install --user -U demucs -# Then anytime you want to use demucs, just do -python3 -m demucs -d cpu PATH_TO_AUDIO_FILE_1 -# If you have added the user specific pip bin/ folder to your path, you can also do -demucs -d cpu PATH_TO_AUDIO_FILE_1 -``` - -If Python is too old, or you want to be able to train, I recommend [installing Miniconda][miniconda], with Python 3.8 or more. - -```bash -conda activate -pip3 install -U demucs -# Then anytime you want to use demucs, first do conda activate, then -demucs -d cpu PATH_TO_AUDIO_FILE_1 -``` - -Of course, you can also use a specific env for Demucs. - -**Important, torchaudio 0.12 update:** Torchaudio no longer supports decoding mp3s without ffmpeg installed. You must have ffmpeg installed, either through Anaconda (`conda install ffmpeg -c conda-forge`) or as a distribution package (e.g. `sudo apt-get install ffmpeg`). - - -[miniconda]: https://docs.conda.io/en/latest/miniconda.html#linux-installers diff --git a/demucs/docs/mac.md b/demucs/docs/mac.md deleted file mode 100644 index 62dd235e..00000000 --- a/demucs/docs/mac.md +++ /dev/null @@ -1,28 +0,0 @@ -# macOS support for Demucs - -If you have a sufficiently recent version of macOS, you can just run - -```bash -python3 -m pip install --user -U demucs -# Then anytime you want to use demucs, just do -python3 -m demucs -d cpu PATH_TO_AUDIO_FILE_1 -# If you have added the user specific pip bin/ folder to your path, you can also do -demucs -d cpu PATH_TO_AUDIO_FILE_1 -``` - -If you do not already have Anaconda installed or much experience with the terminal on macOS, here are some detailed instructions: - -1. Download [Anaconda 3.8 (or more recent) 64-bit for macOS][anaconda]: -2. Open [Anaconda Prompt in macOS][prompt] -3. Follow these commands: -```bash -conda activate -pip3 install -U demucs -# Then anytime you want to use demucs, first do conda activate, then -demucs -d cpu PATH_TO_AUDIO_FILE_1 -``` - -**Important, torchaudio 0.12 update:** Torchaudio no longer supports decoding mp3s without ffmpeg installed. You must have ffmpeg installed, either through Anaconda (`conda install ffmpeg -c conda-forge`) or with Homebrew for instance (`brew install ffmpeg`). - -[anaconda]: https://www.anaconda.com/download -[prompt]: https://docs.anaconda.com/anaconda/user-guide/getting-started/#open-nav-mac diff --git a/demucs/docs/mdx.md b/demucs/docs/mdx.md deleted file mode 100644 index 2a20f9cb..00000000 --- a/demucs/docs/mdx.md +++ /dev/null @@ -1,73 +0,0 @@ -# Music DemiXing challenge (MDX) - -If you want to use Demucs for the [MDX challenge](https://www.aicrowd.com/challenges/music-demixing-challenge-ismir-2021), -please follow the instructions hereafter - -## Installing Demucs - -Follow the instructions from the [main README](https://github.com/facebookresearch/demucs#requirements) -in order to setup Demucs using Anaconda. You will need the full setup up for training, including soundstretch. - -## Getting MusDB-HQ - -Download [MusDB-HQ](https://zenodo.org/record/3338373) to some folder and unzip it. - -## Training Demucs - -Train Demucs (you might need to change the batch size depending on the number of GPUs available). -It seems 48 channels is enough to get the best performance on MusDB-HQ, and training will faster -and less memory demanding. In any case, the 64 channels versions is timing out on the challenge. -```bash -./run.py --channels=48 --batch_size 64 --musdb=PATH_TO_MUSDB --is_wav [EXTRA_FLAGS] -``` - -### Post training - -Once the training is completed, a new model file will be exported in `models/`. - -You can look at the SDR on the MusDB dataset using `python result_table.py`. - - -### Evaluate and export a model before training is over - -If you want to export a model before training is complete, use the following command: -```bash -python -m demucs [ALL EXACT TRAINING FLAGS] --save_model -``` -You can also pass the `--half` flag, in order to save weights in half precision. This will divide the model size by 2 and won't impact SDR. - -Once this is done, you can partially evaluate a model with -```bash -./run.py --test NAME_OF_MODEL.th --musdb=PATH_TO_MUSDB --is_wav -``` - -**Note:** `NAME_OF_MODEL.th` is given relative to the models folder (given by `--models`, defaults to `models/`), so don't include it in the name. - - -### Training smaller models - -If you want to quickly test idea, I would recommend training a 16 kHz model, and testing if things work there or not, before training the full 44kHz model. You can train one of those with -```bash -./run.py --channels=32 --samplerate 16000 --samples 160000 --data_stride 16000 --depth=5 --batch_size 64 --repitch=0 --musdb=PATH_TO_MUSDB --is_wav [EXTRA_FLAGS] -``` -(repitch must be turned off, because things will break at 16kHz). - -## Submitting your model - -1. Git clone [the Music Demixing Challenge - Starter Kit - Demucs Edition](https://github.com/adefossez/music-demixing-challenge-starter-kit). -2. Inside the starter kit, create a `models/` folder and copy over the trained model from the Demucs repo (renaming -it for instance `my_model.th`) -3. Inside the `test_demuc.py` file, change the function `prediction_setup`: comment the loading -of the pre-trained model, and uncomment the code to load your own model. -4. Edit the file `aicrowd.json` with your username. -5. Install [git-lfs](https://git-lfs.github.com/). Then run - -```bash -git lfs install -git add models/ -git add -u . -git commit -m "My Demucs submission" -``` -6. Follow the [submission instructions](https://github.com/AIcrowd/music-demixing-challenge-starter-kit/blob/master/docs/SUBMISSION.md). - -Best of luck 🤞 diff --git a/demucs/docs/release.md b/demucs/docs/release.md deleted file mode 100644 index df8f122f..00000000 --- a/demucs/docs/release.md +++ /dev/null @@ -1,114 +0,0 @@ -# Release notes for Demucs - -## V4.1.0a, TBD - -Get models list - -Check segment of HTDemucs inside BagOfModels - -Added api.py to be called from another program - -Use api in separate.py - -Added `--other-method`: method to get `no_{STEM}`, add up all the other stems (add), original track substract the specific stem (minus), and discard (none) - -Added type `HTDemucs` to type alias `AnyModel`. - -Improving recent torchaudio versions support (Thanks @CarlGao4) - -## V4.0.1, 8th of September 2023 - -**From this version, Python 3.7 is no longer supported. This is not a problem since the latest PyTorch 2.0.0 no longer support it either.** - -Various improvements by @CarlGao4. Support for `segment` param inside of HTDemucs -model. - -Made diffq an optional dependency, with an error message if not installed. - -Added output format flac (Free Lossless Audio Codec) - -Will use CPU for complex numbers, when using MPS device (all other computations are performed by mps). - -Optimize codes to save memory - -Allow changing preset of MP3 - -## V4.0.0, 7th of December 2022 - -Adding hybrid transformer Demucs model. - -Added support for [Torchaudio implementation of HDemucs](https://pytorch.org/audio/main/tutorials/hybrid_demucs_tutorial.html), thanks @skim0514. - -Added experimental 6 sources model `htdemucs_6s` (`drums`, `bass`, `other`, `vocals`, `piano`, `guitar`). - -## V3.0.6, 16th of November 2022 - -Option to customize output path of stems (@CarlGao4) - -Fixed bug in pad1d leading to failure sometimes. - -## V3.0.5, 17th of August 2022 - -Added `--segment` flag to customize the segment length and use less memory (thanks @CarlGao4). - -Fix reflect padding bug on small inputs. - -Compatible with pyTorch 1.12 - -## V3.0.4, 24th of February 2022 - -Added option to split into two stems (i.e. vocals, vs. non vocals), thanks to @CarlGao4. - -Added `--float32`, `--int24` and `--clip-mode` options to customize how output stems are saved. - -## V3.0.3, 2nd of December 2021 - -Fix bug in weights used for different sources. Thanks @keunwoochoi for the report and fix. - -Improving drastically memory usage on GPU for long files. Thanks a lot @famzah for providing this. - -Adding multithread evaluation on CPU (`-j` option). - -(v3.0.2 had a bug with the CPU pool and is skipped.) - -## V3.0.1, 12th of November 2021 - -Release of Demucs v3, featuring hybrid domain separation and much more. -This drops support for Conv-Tasnet and training on the non HQ MusDB dataset. -There is no version 3.0.0 because I messed up. - -## V2.0.2, 26th of May 2021 - -- Fix in Tasnet (PR #178) -- Use ffmpeg in priority when available instead of torchaudio to avoid small shift in MP3 data. -- other minor fixes - -## v2.0.1, 11th of May 2021 - -MusDB HQ support added. Custom wav dataset support added. -Minor changes: issue with padding of mp3 and torchaudio reading, in order to limit that, -Demucs now uses ffmpeg in priority and fallback to torchaudio. -Replaced pre-trained demucs model with one trained on more recent codebase. - -## v2.0.0, 28th of April 2021 - -This is a big release, with at lof of breaking changes. You will likely -need to install Demucs from scratch. - - - -- Demucs now supports on the fly resampling by a factor of 2. -This improves SDR almost 0.3 points. -- Random scaling of each source added (From Uhlich et al. 2017). -- Random pitch and tempo augmentation addded, from [Cohen-Hadria et al. 2019]. -- With extra augmentation, the best performing Demucs model now has only 64 channels -instead of 100, so model size goes from 2.4GB to 1GB. Also SDR is up from 5.6 SDR to 6.3 when trained only on MusDB. -- Quantized model using [DiffQ](https://github.com/facebookresearch/diffq) has been added. Model size is 150MB, no loss in quality as far as I, or the metrics, -can say. -- Pretrained models are now using the TorchHub interface. -- Overlap mode for separation, to limit inconsitencies at - frame boundaries, with linear transition over the overlap. Overlap is currently - at 25%. Not that this is only done for separation, not training, because - I added that quite late to the code. For Conv-TasNet this can improve - SDR quite a bit (+0.3 points, to 6.0). -- PyPI hosting, for separation, not training! diff --git a/demucs/docs/sdx23.md b/demucs/docs/sdx23.md deleted file mode 100644 index 65c5df9a..00000000 --- a/demucs/docs/sdx23.md +++ /dev/null @@ -1,61 +0,0 @@ -# SDX 23 challenge - -Checkout [the challenge page](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023) -for more information. This page is specifically on training models for the [MDX'23 sub-challenge](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023/problems/music-demixing-track-mdx-23). -There are two tracks: one trained on a dataset with bleeding, and the other with label mixups. - -This gives instructions on training an Hybrid Demucs model on those datasets. -I haven't tried the HT Demucs model, as it typically requires quite a bit of training data but the same could be done with it. - -You will need to work from an up to date clone of this repo. See the [generic training instructions](./training.md) for more information. - -## Getting the data - -Register on the challenge, then checkout the [Resources page](https://www.aicrowd.com/challenges/sound-demixing-challenge-2023/problems/music-demixing-track-mdx-23/dataset_files) and download the dataset you are -interested in. - -Update the `conf/dset/sdx23_bleeding.yaml` and `conf/dset/sdx23_labelnoise.yaml` files to point to the right path. - -**Make sure soundfile** is installed (`conda install -c conda-forge libsndfile; pip install soundfile`). - -### Create proper train / valid structure - -Demucs requires a valid set to work properly. Go to the folder where you extracted the tracks then do: - -```shell -mkdir train -mv * train # there will be a warning saying cannot move train to itself but that's fine the other tracks should have. -mkdir valid -cd train -mv 5640831d-7853-4d06-8166-988e2844b652 bc964128-da16-4e4c-af95-4d1211e78c70 \ - cc7f7675-d3c8-4a49-a2d7-a8959b694004 f40ffd10-4e8b-41e6-bd8a-971929ca9138 \ - bc1f2967-f834-43bd-aadc-95afc897cfe7 cc3e4991-6cce-40fe-a917-81a4fbb92ea6 \ - ed90a89a-bf22-444d-af3d-d9ac3896ebd2 f4b735de-14b1-4091-a9ba-c8b30c0740a7 ../valid -``` - -## Training - -See `dora grid sdx23` for a starting point. You can do `dora grid sdx23 --init --dry_run` then `dora run -f SIG -d` with `SIG` one of the signature -to train on a machine with GPUs if you do not have a SLURM cluster. - -Keep in mind that the valid tracks and train tracks are corrupted in different ways for those tasks, so do not expect -the valid loss to go down as smoothly as with normal training on the clean MusDB. - -I only trained Hybrid Demucs baselines as Hybrid Transformer typically requires more data. - - -## Exporting models - -Run -``` -python -m tools.export SIG -``` - -This will export the trained model into the `release_models` folder. - -## Submitting a model - -Clone the [Demucs Starter Kit for SDX23](https://github.com/adefossez/sdx23). Follow the instructions there. - -You will to copy the models under `release_models` in the `sdx23/models/` folder before you can use them. -Make sure you have git-lfs properly installed and setup before adding those files to your fork of `sdx23`. diff --git a/demucs/docs/training.md b/demucs/docs/training.md deleted file mode 100644 index fa046070..00000000 --- a/demucs/docs/training.md +++ /dev/null @@ -1,290 +0,0 @@ -# Training (Hybrid) Demucs - -## Install all the dependencies - -You should install all the dependencies either with either Anaconda (using the env file `environment-cuda.yml` ) -or `pip`, with `requirements.txt`. - -## Datasets - -### MusDB HQ - -Note that we do not support MusDB non HQ training anymore. -Get the [Musdb HQ](https://zenodo.org/record/3338373) dataset, and update the path to it in two places: -- The `dset.musdb` key inside `conf/config.yaml`. -- The variable `MUSDB_PATH` inside `tools/automix.py`. - -### Create the fine tuning datasets - -**This is only for the MDX 2021 competition models** - -I use a fine tuning on a dataset crafted by remixing songs in a musically plausible way. -The automix script will make sure that BPM, first beat and pitches are aligned. -In the file `tools/automix.py`, edit `OUTPATH` to suit your setup, as well as the `MUSDB_PATH` -to point to your copy of MusDB HQ. Then run - -```bash -export NUMBA_NUM_THREADS=1; python3 -m tools.automix -``` - -**Important:** the script will show many errors, those are normals. They just indicate when two stems - do not batch due to BPM or music scale difference. - -Finally, edit the file `conf/dset/auto_mus.yaml` and replace `dset.wav` to the value of `OUTPATH`. - -If you have a custom dataset, you can also uncomment the lines `dset2 = ...` and -`dset3 = ...` to add your custom wav data and the test set of MusDB for Track B models. -You can then replace the paths in `conf/dset/auto_extra.yaml`, `conf/dset/auto_extra_test.yaml` -and `conf/dset/aetl.yaml` (this last one was using 10 mixes instead of 6 for each song). - -### Dataset metadata cache - -Datasets are scanned the first time they are used to determine the files and their durations. -If you change a dataset and need a rescan, just delete the `metadata` folder. - -## A short intro to Dora - -I use [Dora][dora] for all the of experiments (XPs) management. You should have a look at the Dora README -to learn about the tool. Here is a quick summary of what to know: - -- An XP is a unique set of hyper-parameters with a given signature. The signature is a hash of - those hyper-parameters. I will always refer to an XP with its signature, e.g. `9357e12e`. - We will see after that you can retrieve the hyper-params and re-rerun it in a single command. -- In fact, the hash is defined as a delta between the base config and the one obtained with - the config overrides you passed from the command line. - **This means you must never change the `conf/**.yaml` files directly.**, - except for editing things like paths. Changing the default values in the config files means - the XP signature won't reflect that change, and wrong checkpoints might be reused. - I know, this is annoying, but the reason is that otherwise, any change to the config file would - mean that all XPs ran so far would see their signature change. - -### Dora commands - -Run `tar xvf outputs.tar.gz`. This will initialize the Dora XP repository, so that Dora knows -which hyper-params match the signature like `9357e12e`. Once you have done that, you should be able -to run the following: - -```bash -dora info -f 81de367c # this will show the hyper-parameter used by a specific XP. - # Be careful some overrides might present twice, and the right most one - # will give you the right value for it. -dora run -d -f 81de367c # run an XP with the hyper-parameters from XP 81de367c. - # `-d` is for distributed, it will use all available GPUs. -dora run -d -f 81de367c hdemucs.channels=32 # start from the config of XP 81de367c but change some hyper-params. - # This will give you a new XP with a new signature (here 3fe9c332). -``` - -An XP runs from a specific folder based on its signature, by default under the `outputs/` folder. -You can safely interrupt a training and resume it, it will reuse any existing checkpoint, as it will -reuse the same folder. -If you made some change to the code and need to ignore a previous checkpoint you can use `dora run --clear [RUN ARGS]`. - -If you have a Slurm cluster, you can also use the `dora grid` command, e.g. `dora grid mdx`. -Please refer to the [Dora documentation][dora] for more information. - -## Hyper parameters - -Have a look at [conf/config.yaml](../conf/config.yaml) for a list of all the hyper-parameters you can override. -If you are not familiar with [Hydra](https://github.com/facebookresearch/hydra), go checkout their page -to be familiar with how to provide overrides for your trainings. - - -## Model architecture - -A number of architectures are supported. You can select one with `model=NAME`, and have a look -in [conf/config.yaml'(../conf/config.yaml) for each architecture specific hyperparams. -Those specific params will be always prefixed with the architecture name when passing the override -from the command line or in grid files. Here is the list of models: - -- demucs: original time-only Demucs. -- hdemucs: Hybrid Demucs (v3). -- torch_hdemucs: Same as Hybrid Demucs, but using [torchaudio official implementation](https://pytorch.org/audio/stable/tutorials/hybrid_demucs_tutorial.html). -- htdemucs: Hybrid Transformer Demucs (v4). - -### Storing config in files - -As mentioned earlier, you should never change the base config files. However, you can use Hydra config groups -in order to store variants you often use. If you want to create a new variant combining multiple hyper-params, -copy the file `conf/variant/example.yaml` to `conf/variant/my_variant.yaml`, and then you can use it with - -```bash -dora run -d variant=my_variant -``` - -Once you have created this file, you should not edit it once you have started training models with it. - - -## Fine tuning - -If a first model is trained, you can fine tune it with other settings (e.g. automix dataset) with - -```bash -dora run -d -f 81de367c continue_from=81de367c dset=auto_mus variant=finetune -```` - -Note that you need both `-f 81de367c` and `continue_from=81de367c`. The first one indicates -that the hyper-params of `81de367c` should be used as a starting point for the config. -The second indicates that the weights from `81de367c` should be used as a starting point for the solver. - - -## Model evaluation - -Your model will be evaluated automatically with the new SDR definition from MDX every 20 epochs. -Old style SDR (which is quite slow) will only happen at the end of training. - -## Model Export - - -In order to use your models with other commands (such as the `demucs` command for separation) you must -export it. For that run - -```bash -python3 -m tools.export 9357e12e [OTHER SIGS ...] # replace with the appropriate signatures. -``` - -The models will be stored under `release_models/`. You can use them with the `demucs` separation command with the following flags: -```bash -demucs --repo ./release_models -n 9357e12e my_track.mp3 -``` - -### Bag of models - -If you want to combine multiple models, potentially with different weights for each source, you can copy -`demucs/remote/mdx.yaml` to `./release_models/my_bag.yaml`. You can then edit the list of models (all models used should have been exported first) and the weights per source and model (list of list, outer list is over models, inner list is over sources). You can then use your bag of model as - -```bash -demucs --repo ./release_models -n my_bag my_track.mp3 -``` - -## Model evaluation - -You can evaluate any pre-trained model or bag of models using the following command: -```bash -python3 -m tools.test_pretrained -n NAME_OF_MODEL [EXTRA ARGS] -``` -where `NAME_OF_MODEL` is either the name of the bag (e.g. `mdx`, `repro_mdx_a`), -or a single Dora signature of one of the model of the bags. You can pass `EXTRA ARGS` to customize -the test options, like the number of random shifts (e.g. `test.shifts=2`). This will compute the old-style -SDR and can take quite bit of time. - -For custom models that were trained locally, you will need to indicate that you wish -to use the local model repositories, with the `--repo ./release_models` flag, e.g., -```bash -python3 -m tools.test_pretrained --repo ./release_models -n my_bag -``` - - -## API to retrieve the model - -You can retrieve officially released models in Python using the following API: -```python -from demucs import pretrained -from demucs.apply import apply_model -bag = pretrained.get_model('htdemucs') # for a bag of models or a named model - # (which is just a bag with 1 model). -model = pretrained.get_model('955717e8') # using the signature for single models. - -bag.models # list of individual models -stems = apply_model(model, mix) # apply the model to the given mix. -``` - -## Model Zoo - -### Hybrid Transformer Demucs - -The configuration for the Hybrid Transformer models are available in: - -```shell -dora grid mmi --dry_run --init -dora grid mmi_ft --dry_run --init # fined tuned on each sources. -``` - -We release in particular `955717e8`, Hybrid Transformer Demucs using 5 layers, 512 channels, 10 seconds training segment length. We also release its fine tuned version, with one model -for each source `f7e0c4bc`, `d12395a8`, `92cfc3b6`, `04573f0d` (drums, bass, other, vocals). -The model `955717e8` is also named `htdemucs`, while the bag of models is provided -as `htdemucs_ft`. - -We also release `75fc33f5`, a regular Hybrid Demucs trained on the same dataset, -available as `hdemucs_mmi`. - - - -### Models from the MDX Competition 2021 - - -Here is a short descriptions of the models used for the MDX submission, either Track A (MusDB HQ only) -or Track B (extra training data allowed). Training happen in two stage, with the second stage -being the fine tunining on the automix generated dataset. -All the fine tuned models are available on our AWS repository -(you can retrieve it with `demucs.pretrained.get_model(SIG)`). The bag of models are available -by doing `demucs.pretrained.get_model(NAME)` with `NAME` begin either `mdx` (for Track A) or `mdx_extra` -(for Track B). - -#### Track A - -The 4 models are: - -- `0d19c1c6`: fine-tuned on automix dataset from `9357e12e` -- `7ecf8ec1`: fine-tuned on automix dataset from `e312f349` -- `c511e2ab`: fine-tuned on automix dataset from `81de367c` -- `7d865c68`: fine-tuned on automix dataset from `80a68df8` - -The 4 initial models (before fine tuning are): - -- `9357e12e`: 64ch time domain only improved Demucs, with new residual branches, group norm, - and singular value penalty. -- `e312f349`: 64ch time domain only improved, with new residual branches, group norm, - and singular value penalty, trained with a loss that focus only on drums and bass. -- `81de367c`: 48ch hybrid model , with residual branches, group norm, - singular value penalty penalty and amplitude spectrogram. -- `80a68df8`: same as b5559babb but using CaC and different - random seed, as well different weigths per frequency bands in outermost layers. - -The hybrid models are combined with equal weights for all sources except for the bass. -`0d19c1c6` (time domain) is used for both drums and bass. `7ecf8ec1` is used only for the bass. - -You can see all the hyper parameters at once with (one common line for all common hyper params, and then only shows -the hyper parameters that differs), along with the DiffQ variants that are used for the `mdx_q` models: -``` -dora grid mdx --dry_run --init -dora grid mdx --dry_run --init -``` - -#### Track B - -- `e51eebcc` -- `a1d90b5c` -- `5d2d6c55` -- `cfa93e08` - -All the models are 48ch hybrid demucs with different random seeds. Two of them -are using CaC, and two are using amplitude spectrograms with masking. -All the models are combined with equal weights for all sources. - -Things are a bit messy for Track B, there was a lot of fine tuning -over different datasets. I won't describe the entire genealogy of models here, -but all the information can be accessed with the `dora info -f SIG` command. - -Similarly you can do (those will contain a few extra lines, for training without the MusDB test set as training, and extra DiffQ XPs): -``` -dora grid mdx_extra --dry_run --init -``` - -### Reproducibility and Ablation - -I updated the paper to report numbers with a more homogeneous setup than the one used for the competition. -On MusDB HQ, I still need to use a combination of time only and hybrid models to achieve the best performance. -The experiments are provided in the grids [repro.py](../demucs/grids/repro.py) and -[repro_ft._py](../demucs/grids/repro_ft.py) for the fine tuning on the realistic mix datasets. - -The new bag of models reaches an SDR of 7.64 (vs. 7.68 for the original track A model). It uses -2 time only models trained with residual branches, local attention and the SVD penalty, -along with 2 hybrid models, with the same features, and using CaC representation. -We average the performance of all the models with the same weight over all sources, unlike -what was done for the original track A model. We trained for 600 epochs, against 360 before. - -The new bag of model is available as part of the pretrained model as `repro_mdx_a`. -The time only bag is named `repro_mdx_a_time_only`, and the hybrid only `repro_mdx_a_hybrid_only`. -Checkout the paper for more information on the training. - -[dora]: https://github.com/facebookresearch/dora diff --git a/demucs/docs/windows.md b/demucs/docs/windows.md deleted file mode 100644 index b259b765..00000000 --- a/demucs/docs/windows.md +++ /dev/null @@ -1,67 +0,0 @@ -# Windows support for Demucs - -## Installation and usage - -If you don't have much experience with Anaconda, python or the shell, here are more detailed instructions. Note that **Demucs is not supported on 32bits systems** (as Pytorch is not available there). - -- First install Anaconda with **Python 3.8** or more recent, which you can find [here][install]. -- Start the [Anaconda prompt][prompt]. - -Then, all commands that follow must be run from this prompt. - -

    +
    https://github.com/chidiwilliams/buzz/releases/tag/v1.3.3 diff --git a/tests/conftest.py b/tests/conftest.py index 1ab7a4b5..a551d91e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,19 @@ +import multiprocessing import os import platform import random import string -from pathlib import Path import pytest + +# Set multiprocessing to use 'spawn' instead of 'fork' on Linux +# This is required because Qt creates threads early, and forking a multi-threaded +# process can lead to deadlocks. The main application sets this in buzz/buzz.py. +if platform.system() != "Windows": + try: + multiprocessing.set_start_method("spawn", force=True) + except RuntimeError: + pass # Already set from PyQt6.QtSql import QSqlDatabase from _pytest.fixtures import SubRequest diff --git a/tests/transcriber/transformers_whisper_test.py b/tests/transcriber/transformers_whisper_test.py new file mode 100644 index 00000000..69c72705 --- /dev/null +++ b/tests/transcriber/transformers_whisper_test.py @@ -0,0 +1,46 @@ +import os +from unittest.mock import patch + +import pytest + +from buzz.transformers_whisper import TransformersTranscriber + + +class TestGetMmsRepoId: + """Tests for TransformersTranscriber._get_mms_repo_id method.""" + + def test_repo_id_returned_as_is(self): + """Test that a HuggingFace repo ID is returned unchanged.""" + transcriber = TransformersTranscriber("facebook/mms-1b-all") + with patch("os.path.exists", return_value=False): + assert transcriber._get_mms_repo_id() == "facebook/mms-1b-all" + + def test_linux_cache_path(self): + """Test extraction from Linux-style cache path.""" + linux_path = "/home/user/.cache/Buzz/models/models--facebook--mms-1b-all/snapshots/abc123" + transcriber = TransformersTranscriber(linux_path) + with patch("os.path.exists", return_value=True), \ + patch("buzz.transformers_whisper.os.sep", "/"): + assert transcriber._get_mms_repo_id() == "facebook/mms-1b-all" + + def test_windows_cache_path(self): + """Test extraction from Windows-style cache path.""" + windows_path = r"C:\Users\user\.cache\Buzz\models\models--facebook--mms-1b-all\snapshots\abc123" + transcriber = TransformersTranscriber(windows_path) + with patch("os.path.exists", return_value=True), \ + patch("buzz.transformers_whisper.os.sep", "\\"): + assert transcriber._get_mms_repo_id() == "facebook/mms-1b-all" + + def test_fallback_returns_model_id(self): + """Test that model_id is returned as fallback when pattern not matched.""" + transcriber = TransformersTranscriber("some-local-model") + with patch("os.path.exists", return_value=True): + assert transcriber._get_mms_repo_id() == "some-local-model" + + def test_nested_org_name(self): + """Test extraction with different org/model names.""" + linux_path = "/home/user/.cache/Buzz/models/models--openai--whisper-large-v3/snapshots/xyz" + transcriber = TransformersTranscriber(linux_path) + with patch("os.path.exists", return_value=True), \ + patch("buzz.transformers_whisper.os.sep", "/"): + assert transcriber._get_mms_repo_id() == "openai/whisper-large-v3" diff --git a/tests/transcriber/whisper_file_transcriber_test.py b/tests/transcriber/whisper_file_transcriber_test.py index 94466c38..e4041edf 100644 --- a/tests/transcriber/whisper_file_transcriber_test.py +++ b/tests/transcriber/whisper_file_transcriber_test.py @@ -3,7 +3,6 @@ import logging import os import platform import shutil -import sys import tempfile import time from typing import List diff --git a/tests/transformers_whisper_test.py b/tests/transformers_transcriber_test.py similarity index 71% rename from tests/transformers_whisper_test.py rename to tests/transformers_transcriber_test.py index 235984d3..dabf1714 100644 --- a/tests/transformers_whisper_test.py +++ b/tests/transformers_transcriber_test.py @@ -1,17 +1,17 @@ import platform import pytest -from buzz.transformers_whisper import TransformersWhisper +from buzz.transformers_whisper import TransformersTranscriber from tests.audio import test_audio_path -class TestTransformersWhisper: +class TestTransformersTranscriber: @pytest.mark.skipif( platform.system() == "Darwin", reason="Not supported on Darwin", ) def test_should_transcribe(self): - model = TransformersWhisper("openai/whisper-tiny") + model = TransformersTranscriber("openai/whisper-tiny") result = model.transcribe( audio=test_audio_path, language="fr", task="transcribe" ) diff --git a/uv.lock b/uv.lock index 7d9a086f..07a5205a 100644 --- a/uv.lock +++ b/uv.lock @@ -10,6 +10,12 @@ resolution-markers = [ "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] +[manifest] +overrides = [ + { name = "bitsandbytes", marker = "sys_platform != 'darwin'", specifier = "==0.47.0" }, + { name = "bitsandbytes", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = "==0.49.0" }, +] + [[package]] name = "absl-py" version = "2.3.1" @@ -31,8 +37,8 @@ dependencies = [ { name = "pyyaml" }, { name = "safetensors" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4a/8e/ac2a9566747a93f8be36ee08532eb0160558b07630a081a6056a9f89bf1d/accelerate-1.12.0.tar.gz", hash = "sha256:70988c352feb481887077d2ab845125024b2a137a5090d6d7a32b57d03a45df6", size = 398399, upload-time = "2025-11-21T11:27:46.973Z" } wheels = [ @@ -53,13 +59,13 @@ name = "aiohttp" version = "3.13.2" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "aiohappyeyeballs", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "aiosignal", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "attrs", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "frozenlist", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "multidict", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "propcache", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "yarl", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } wheels = [ @@ -87,8 +93,8 @@ name = "aiosignal" version = "1.4.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "frozenlist", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "frozenlist" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } wheels = [ @@ -224,15 +230,37 @@ wheels = [ [[package]] name = "bitsandbytes" -version = "0.46.0" +version = "0.47.0" source = { registry = "https://pypi.org/simple/" } +resolution-markers = [ + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", + "sys_platform != 'darwin' and sys_platform != 'linux'", + "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", +] dependencies = [ - { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "numpy", marker = "sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/72/27/ec6ee3408e09e01ab05db07af5a97dc76db7bc18824cf5f5dbc98e1e08a4/bitsandbytes-0.46.0-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:ef38883cfd26f36a0dfff1715f620f87cee3813431f33e10e9658205160cb89b", size = 67047276, upload-time = "2025-05-27T21:25:31.299Z" }, - { url = "https://files.pythonhosted.org/packages/f3/06/2ef5f6b28d8fa442c670b5acc1eb09dd57d4edb00b435b35529c3f09936c/bitsandbytes-0.46.0-py3-none-win_amd64.whl", hash = "sha256:121820a6df80ae3b7e361f7ef193279c3204c361a7e21eb43b5ffa7293403979", size = 66452401, upload-time = "2025-05-27T21:25:35.552Z" }, + { url = "https://files.pythonhosted.org/packages/aa/eb/477d6b5602f469c7305fd43eec71d890c39909f615c1d7138f6e7d226eff/bitsandbytes-0.47.0-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:2f805b76891a596025e9e13318b675d08481b9ee650d65e5d2f9d844084c6521", size = 30004641, upload-time = "2025-08-11T18:51:20.524Z" }, + { url = "https://files.pythonhosted.org/packages/9c/40/91f1a5a694f434bc13cba160045fdc4e867032e627b001bf411048fefd9c/bitsandbytes-0.47.0-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:68f3fffd494a47ed1fd7593bfc5dd2ac69b68260599b71b4c4b3a32f90f3b184", size = 61284639, upload-time = "2025-08-11T18:51:23.581Z" }, + { url = "https://files.pythonhosted.org/packages/18/a9/e07a227f1cd6562844cea2f05ee576b0991a9a91f45965c06034178ba0f6/bitsandbytes-0.47.0-py3-none-win_amd64.whl", hash = "sha256:4880a6d42ca9628b5a571c8cc3093dc3f5f52511e5a9e47d52d569807975531a", size = 60725121, upload-time = "2025-08-11T18:51:27.543Z" }, +] + +[[package]] +name = "bitsandbytes" +version = "0.49.0" +source = { registry = "https://pypi.org/simple/" } +resolution-markers = [ + "platform_machine == 'arm64' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "packaging", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/96/2b825cb874477a26478df0ce8ce3550abe81af1c7bcbc47871f0619b120c/bitsandbytes-0.49.0-py3-none-macosx_14_0_arm64.whl", hash = "sha256:17d5b57e6d51b78bcfc07da0e93db061181b25bffabfafe101dd9b75c2710872", size = 129838, upload-time = "2025-12-11T20:50:39.645Z" }, ] [[package]] @@ -250,6 +278,8 @@ version = "1.4.0" source = { editable = "." } dependencies = [ { name = "accelerate" }, + { name = "bitsandbytes", version = "0.47.0", source = { registry = "https://pypi.org/simple/" }, marker = "sys_platform != 'darwin'" }, + { name = "bitsandbytes", version = "0.49.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, { name = "certifi" }, { name = "cmake" }, { name = "coverage" }, @@ -257,6 +287,7 @@ dependencies = [ { name = "ctranslate2", version = "4.6.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' or sys_platform != 'darwin'" }, { name = "darkdetect" }, { name = "dataclasses-json" }, + { name = "datasets" }, { name = "demucs" }, { name = "diffq" }, { name = "dora-search" }, @@ -285,6 +316,7 @@ dependencies = [ { name = "openai" }, { name = "openai-whisper" }, { name = "openunmix" }, + { name = "peft" }, { name = "platformdirs" }, { name = "polib" }, { name = "posthog" }, @@ -299,11 +331,11 @@ dependencies = [ { name = "stable-ts" }, { name = "submitit" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "torchcodec", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tqdm" }, @@ -340,6 +372,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "accelerate", specifier = ">=1.12.0,<2" }, + { name = "bitsandbytes", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = ">=0.45.0" }, { name = "certifi", specifier = "==2025.11.12" }, { name = "cmake", specifier = ">=4.2.0,<5" }, { name = "coverage", specifier = "==7.12.0" }, @@ -348,6 +381,7 @@ requires-dist = [ { name = "ctranslate2", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==4.3.1" }, { name = "darkdetect", specifier = ">=0.8.0,<0.9" }, { name = "dataclasses-json", specifier = ">=0.6.4,<0.7" }, + { name = "datasets", specifier = ">=4.4.1" }, { name = "demucs", editable = "demucs_repo" }, { name = "diffq", specifier = ">=0.2.4,<0.3" }, { name = "dora-search", specifier = ">=0.1.12,<0.2" }, @@ -376,6 +410,7 @@ requires-dist = [ { name = "openai", specifier = ">=1.14.2,<2" }, { name = "openai-whisper", specifier = "==20250625" }, { name = "openunmix", specifier = ">=1.3.0,<2" }, + { name = "peft", specifier = ">=0.14.0,<1" }, { name = "platformdirs", specifier = ">=4.2.1,<5" }, { name = "polib", specifier = ">=1.2.0,<2" }, { name = "posthog", specifier = ">=3.23.0,<4" }, @@ -390,9 +425,13 @@ requires-dist = [ { name = "stable-ts", specifier = ">=2.19.1,<3" }, { name = "submitit", specifier = ">=1.5.2,<2" }, { name = "torch", marker = "sys_platform != 'darwin'", specifier = "==2.8.0", index = "https://download.pytorch.org/whl/cu129" }, + { name = "torch", marker = "sys_platform != 'darwin'", specifier = ">=2.2.2", index = "https://download.pytorch.org/whl/cu129" }, + { name = "torch", marker = "sys_platform == 'darwin'", specifier = ">=2.2.2", index = "https://pypi.org/simple/" }, { name = "torch", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = "==2.8.0", index = "https://pypi.org/simple/" }, { name = "torch", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==2.2.2", index = "https://pypi.org/simple/" }, { name = "torchaudio", marker = "sys_platform != 'darwin'", specifier = "==2.8.0", index = "https://download.pytorch.org/whl/cu129" }, + { name = "torchaudio", marker = "sys_platform != 'darwin'", specifier = ">=2.2.2", index = "https://download.pytorch.org/whl/cu129" }, + { name = "torchaudio", marker = "sys_platform == 'darwin'", specifier = ">=2.2.2", index = "https://pypi.org/simple/" }, { name = "torchaudio", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'", specifier = "==2.8.0", index = "https://pypi.org/simple/" }, { name = "torchaudio", marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'", specifier = "==2.2.2", index = "https://pypi.org/simple/" }, { name = "torchcodec", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'", specifier = ">=0.9.0" }, @@ -789,20 +828,20 @@ name = "datasets" version = "4.4.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "dill", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "filelock", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "fsspec", extra = ["http"], marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "httpx", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "huggingface-hub", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "multiprocess", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "pandas", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "pyarrow", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "requests", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "xxhash", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/bf/0dae295d6d1ba0b1a200a9dd216838464b5bbd05da01407cb1330b377445/datasets-4.4.1.tar.gz", hash = "sha256:80322699aa8c0bbbdb7caa87906da689c3c2e29523cff698775c67f28fdab1fc", size = 585341, upload-time = "2025-11-05T16:00:38.162Z" } wheels = [ @@ -830,12 +869,12 @@ dependencies = [ { name = "openunmix" }, { name = "pyyaml" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, ] @@ -879,8 +918,8 @@ dependencies = [ { name = "cython" }, { name = "numpy" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/fd/4c58807bf855c5929ffa6da55f26dd6b9ae462a4193f5e09cc49fbbfd451/diffq-0.2.4.tar.gz", hash = "sha256:049064861e974ebf00d0badab8b324c775037371419eda3150985b9d477b5bd2", size = 157139, upload-time = "2023-05-05T12:39:43.089Z" } @@ -926,8 +965,8 @@ dependencies = [ { name = "retrying" }, { name = "submitit" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "treetable" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d5/9d/9a13947db237375486c0690f4741dd2b7e1eee20e0ffcb55dbd1b21cc600/dora_search-0.1.12.tar.gz", hash = "sha256:2956fd2c4c7e4b9a4830e83f0d4cf961be45cfba1a2f0570281e91d15ac516fb", size = 87111, upload-time = "2023-05-23T14:36:24.743Z" } @@ -1098,7 +1137,7 @@ wheels = [ [package.optional-dependencies] http = [ - { name = "aiohttp", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "aiohttp" }, ] [[package]] @@ -1559,8 +1598,8 @@ version = "0.2.7" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/19/c9e1596b5572c786b93428d0904280e964c930fae7e6c9368ed9e1b63922/julius-0.2.7.tar.gz", hash = "sha256:3c0f5f5306d7d6016fcc95196b274cae6f07e2c9596eed314e4e7641554fbb08", size = 59640, upload-time = "2022-09-19T16:13:34.2Z" } @@ -1688,8 +1727,8 @@ dependencies = [ { name = "soundfile" }, { name = "tabulate" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/fd/32baf46d238f029a22b2c1762fc717ebdb85515fb48bafa395d3de5da0f5/lhotse-1.32.1.tar.gz", hash = "sha256:8b0e946d1bd2c695b09df831ea612913f1a1f103b1aea36a4b43a8778be0a3d5", size = 674412, upload-time = "2025-11-24T16:42:25.511Z" } @@ -1769,8 +1808,8 @@ dependencies = [ { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pytorch-lightning", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "torchmetrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -2075,7 +2114,7 @@ name = "multiprocess" version = "0.70.18" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "dill", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "dill" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/fd/2ae3826f5be24c6ed87266bc4e59c46ea5b059a103f3d7e7eb76a52aeecb/multiprocess-0.70.18.tar.gz", hash = "sha256:f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d", size = 1798503, upload-time = "2025-04-17T03:11:27.742Z" } wheels = [ @@ -2166,8 +2205,8 @@ dependencies = [ { name = "setuptools", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tensorboard", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "text-unidecode", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "wget", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "wrapt", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -2179,7 +2218,8 @@ wheels = [ [package.optional-dependencies] asr = [ - { name = "bitsandbytes", marker = "platform_machine == 'x86_64' and sys_platform != 'darwin'" }, + { name = "bitsandbytes", version = "0.47.0", source = { registry = "https://pypi.org/simple/" }, marker = "sys_platform != 'darwin'" }, + { name = "bitsandbytes", version = "0.49.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, { name = "braceexpand", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "cloudpickle", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "ctc-segmentation", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -2563,8 +2603,8 @@ dependencies = [ { name = "numpy" }, { name = "tiktoken" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "tqdm" }, { name = "triton", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or sys_platform == 'linux2'" }, ] @@ -2577,12 +2617,12 @@ source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "numpy" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/ef/4ad54e3ecb1e89f7f7bdb4c7b751e43754e892d3c32a8550e5d0882565df/openunmix-1.3.0.tar.gz", hash = "sha256:cc9245ce728700f5d0b72c67f01be4162777e617cdc47f9b035963afac180fc8", size = 45889, upload-time = "2024-04-16T11:10:47.121Z" } @@ -2679,17 +2719,18 @@ name = "peft" version = "0.18.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "accelerate", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "huggingface-hub", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "psutil", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "safetensors", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, - { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "transformers", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "accelerate" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, + { name = "tqdm" }, + { name = "transformers" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/0c/f2938db546ac7fc961ab5917cd50fcf5d0d70b406de93e3faccaa504e152/peft-0.18.0.tar.gz", hash = "sha256:c81c80b2056ab40c23d58ef25f74daab417ac653970718589a11a8af28218588", size = 634141, upload-time = "2025-11-13T11:13:06.603Z" } wheels = [ @@ -3324,8 +3365,8 @@ dependencies = [ { name = "lightning-utilities", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "pyyaml", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "torchmetrics", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "tqdm", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "typing-extensions", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, @@ -3864,12 +3905,12 @@ dependencies = [ { name = "numpy" }, { name = "openai-whisper" }, { name = "torch", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, { name = "torchaudio", version = "2.2.2", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'x86_64' and sys_platform == 'darwin'" }, { name = "torchaudio", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, - { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchaudio", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/d9/d326f9dbbb7da6806aa8cfc080342e5f78dc33552f4339bdc8a6251d11a3/stable_ts-2.19.1.tar.gz", hash = "sha256:0ecaf1ed93e029839569618d2da9a57b883ad04db21f0680146e0650caaf4f52", size = 189132, upload-time = "2025-08-16T16:53:48.811Z" } @@ -4112,15 +4153,16 @@ version = "2.8.0" source = { registry = "https://pypi.org/simple/" } resolution-markers = [ "platform_machine == 'arm64' and sys_platform == 'darwin'", + "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "filelock", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "fsspec", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "jinja2", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "networkx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "setuptools", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "sympy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "filelock", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "fsspec", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "jinja2", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "networkx", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "setuptools", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "sympy", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" }, @@ -4134,13 +4176,12 @@ resolution-markers = [ "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", "sys_platform != 'darwin' and sys_platform != 'linux'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", - "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "filelock", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, - { name = "fsspec", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, - { name = "jinja2", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, - { name = "networkx", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "filelock", marker = "sys_platform != 'darwin'" }, + { name = "fsspec", marker = "sys_platform != 'darwin'" }, + { name = "jinja2", marker = "sys_platform != 'darwin'" }, + { name = "networkx", marker = "sys_platform != 'darwin'" }, { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, @@ -4155,10 +4196,10 @@ dependencies = [ { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, - { name = "sympy", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "setuptools", marker = "sys_platform != 'darwin'" }, + { name = "sympy", marker = "sys_platform != 'darwin'" }, { name = "triton", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions", marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu129/torch-2.8.0%2Bcu129-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:692fe6e513b667f789a543fa9b1baba58e77a46d5c8629764ca0c00a56823e1f" }, @@ -4200,9 +4241,10 @@ version = "2.8.0" source = { registry = "https://pypi.org/simple/" } resolution-markers = [ "platform_machine == 'arm64' and sys_platform == 'darwin'", + "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/ac/cc/c2e2a3eb6ee956f73c68541e439916f8146170ea9cc61e72adea5c995312/torchaudio-2.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ddef94bf181e6447cbb05f38beaca8f6c5bb8d2b9ddced1aa3452025b9fc70d3", size = 1856736, upload-time = "2025-08-06T14:58:36.3Z" }, @@ -4215,10 +4257,9 @@ source = { registry = "https://download.pytorch.org/whl/cu129" } resolution-markers = [ "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", "sys_platform != 'darwin' and sys_platform != 'linux'", - "platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin'", ] dependencies = [ - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu129/torchaudio-2.8.0%2Bcu129-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:40df9011972519120f284f56e5e7d131d4250ea69653499028d1d30b353f932e" }, @@ -4243,8 +4284,8 @@ dependencies = [ { name = "lightning-utilities", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "numpy", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, { name = "packaging", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'arm64' and platform_machine != 'x86_64') or sys_platform != 'darwin'" }, + { name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple/" }, marker = "platform_machine != 'x86_64' and sys_platform == 'darwin'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } wheels = [ @@ -4578,9 +4619,9 @@ name = "yarl" version = "1.22.0" source = { registry = "https://pypi.org/simple/" } dependencies = [ - { name = "idna", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "multidict", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, - { name = "propcache", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin'" }, + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, ] sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } wheels = [ From de2a5b88ee8d52e9be7633e2881bb7cca450444b Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Fri, 19 Dec 2025 14:49:26 +0200 Subject: [PATCH 27/73] Fix for GPU setting on macs (#1318) --- buzz/model_loader.py | 38 ++++++++++++++++++++++++++++++--- buzz/transcriber/whisper_cpp.py | 8 +++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/buzz/model_loader.py b/buzz/model_loader.py index 87531189..5d4061e9 100644 --- a/buzz/model_loader.py +++ b/buzz/model_loader.py @@ -624,9 +624,41 @@ class ModelDownloader(QRunnable): ) if self.is_coreml_supported: - with zipfile.ZipFile( - os.path.join(model_path, f"ggml-{model_name}-encoder.mlmodelc.zip"), 'r') as zip_ref: - zip_ref.extractall(model_path) + import tempfile + + target_dir = os.path.join(model_path, f"ggml-{model_name}-encoder.mlmodelc") + zip_path = os.path.join(model_path, f"ggml-{model_name}-encoder.mlmodelc.zip") + + # Remove target directory if it exists + if os.path.exists(target_dir): + shutil.rmtree(target_dir) + + # Extract to a temporary directory first + with tempfile.TemporaryDirectory() as temp_dir: + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(temp_dir) + + # Remove __MACOSX metadata folders if present + macosx_path = os.path.join(temp_dir, "__MACOSX") + if os.path.exists(macosx_path): + shutil.rmtree(macosx_path) + + # Check if there's a single top-level directory + temp_contents = os.listdir(temp_dir) + if len(temp_contents) == 1 and os.path.isdir(os.path.join(temp_dir, temp_contents[0])): + # Single directory - move its contents to target + nested_dir = os.path.join(temp_dir, temp_contents[0]) + shutil.move(nested_dir, target_dir) + else: + # Multiple items or files - copy everything to target + os.makedirs(target_dir, exist_ok=True) + for item in temp_contents: + src = os.path.join(temp_dir, item) + dst = os.path.join(target_dir, item) + if os.path.isdir(src): + shutil.copytree(src, dst) + else: + shutil.copy2(src, dst) self.signals.finished.emit(os.path.join( model_path, f"ggml-{model_name}.bin")) diff --git a/buzz/transcriber/whisper_cpp.py b/buzz/transcriber/whisper_cpp.py index 7bea69fb..8b2195ee 100644 --- a/buzz/transcriber/whisper_cpp.py +++ b/buzz/transcriber/whisper_cpp.py @@ -100,7 +100,12 @@ class WhisperCpp: "-l", language, "--print-progress", "--suppress-nst", + # Protections against hallucinated repetition. Seems to be problem on macOS + # https://github.com/ggml-org/whisper.cpp/issues/1507 + "--max-context", "64", + "--entropy-thold", "2.8", "--output-json-full", + "-t", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8) // 2)), "-f", file_to_process, ] @@ -110,9 +115,8 @@ class WhisperCpp: # Force CPU if specified force_cpu = os.getenv("BUZZ_FORCE_CPU", "false") - if force_cpu != "false" or not IS_VULKAN_SUPPORTED: + if force_cpu != "false" or (not IS_VULKAN_SUPPORTED and platform.system() != "Darwin"): cmd.extend(["--no-gpu"]) - cmd.extend(["-t", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8) // 2))]) print(f"Running Whisper CLI: {' '.join(cmd)}") From c93d8c9d0302bbe74e404970899bd1c933279aad Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sun, 21 Dec 2025 16:38:02 +0200 Subject: [PATCH 28/73] Fic for HF downloads on Windows (#1319) --- buzz/model_loader.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/buzz/model_loader.py b/buzz/model_loader.py index 5d4061e9..4cf4d0d0 100644 --- a/buzz/model_loader.py +++ b/buzz/model_loader.py @@ -22,6 +22,50 @@ from huggingface_hub.errors import LocalEntryNotFoundError from buzz.locale import _ +# On Windows, creating symlinks requires special privileges (Developer Mode or +# SeCreateSymbolicLinkPrivilege). Monkey-patch huggingface_hub to use file +# copying instead of symlinks to avoid [WinError 1314] errors. +if sys.platform == "win32": + try: + from huggingface_hub import file_download + from pathlib import Path + + _original_create_symlink = file_download._create_symlink + + def _windows_create_symlink(src: Path, dst: Path, new_blob: bool = False) -> None: + """Windows-compatible replacement that copies instead of symlinking.""" + src = Path(src) + dst = Path(dst) + + # If dst already exists and is correct, skip + if dst.exists(): + if dst.is_symlink(): + # Existing symlink - leave it + return + if dst.is_file(): + # Check if it's the same file + if dst.stat().st_size == src.stat().st_size: + return + + dst.parent.mkdir(parents=True, exist_ok=True) + + # Try symlink first (works if Developer Mode is enabled) + try: + dst.unlink(missing_ok=True) + os.symlink(src, dst) + return + except OSError: + pass + + # Fallback: copy the file instead + dst.unlink(missing_ok=True) + shutil.copy2(src, dst) + + file_download._create_symlink = _windows_create_symlink + logging.debug("Patched huggingface_hub to use file copying on Windows") + except Exception as e: + logging.warning(f"Failed to patch huggingface_hub for Windows: {e}") + model_root_dir = user_cache_dir("Buzz") model_root_dir = os.path.join(model_root_dir, "models") From 734bd99d17c53b97245ca3b9e0bf37e8bf75926d Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Sun, 21 Dec 2025 20:02:39 +0200 Subject: [PATCH 29/73] 978 add youtube title (#1321) --- buzz/db/dao/transcription_dao.py | 16 ++++++++++ buzz/db/service/transcription_service.py | 3 ++ buzz/transcriber/file_transcriber.py | 29 +++++++++++++++++-- buzz/widgets/main_window.py | 9 ++++++ .../transcription_tasks_table_widget.py | 4 +-- .../transcription_tasks_table_widget_test.py | 14 ++++----- 6 files changed, 63 insertions(+), 12 deletions(-) diff --git a/buzz/db/dao/transcription_dao.py b/buzz/db/dao/transcription_dao.py index 928099c3..db5107b4 100644 --- a/buzz/db/dao/transcription_dao.py +++ b/buzz/db/dao/transcription_dao.py @@ -250,6 +250,22 @@ class TranscriptionDAO(DAO[Transcription]): if not query.exec(): raise Exception(query.lastError().text()) + def update_transcription_file_and_name(self, id: UUID, file_path: str, name: str | None = None): + query = self._create_query() + query.prepare( + """ + UPDATE transcription + SET file = :file, name = COALESCE(:name, name) + WHERE id = :id + """ + ) + + query.bindValue(":id", str(id)) + query.bindValue(":file", file_path) + query.bindValue(":name", name) + if not query.exec(): + raise Exception(query.lastError().text()) + def update_transcription_name(self, id: UUID, name: str): query = self._create_query() query.prepare( diff --git a/buzz/db/service/transcription_service.py b/buzz/db/service/transcription_service.py index 4c500800..8a15a24e 100644 --- a/buzz/db/service/transcription_service.py +++ b/buzz/db/service/transcription_service.py @@ -47,6 +47,9 @@ class TranscriptionService: ) ) + def update_transcription_file_and_name(self, id: UUID, file_path: str, name: str | None = None): + self.transcription_dao.update_transcription_file_and_name(id, file_path, name) + def update_transcription_name(self, id: UUID, name: str): self.transcription_dao.update_transcription_name(id, name) diff --git a/buzz/transcriber/file_transcriber.py b/buzz/transcriber/file_transcriber.py index 5943c8a0..250c27b6 100755 --- a/buzz/transcriber/file_transcriber.py +++ b/buzz/transcriber/file_transcriber.py @@ -38,12 +38,35 @@ class FileTranscriber(QObject): @pyqtSlot() def run(self): if self.transcription_task.source == FileTranscriptionTask.Source.URL_IMPORT: - temp_output_path = tempfile.mktemp() + cookiefile = os.getenv("BUZZ_DOWNLOAD_COOKIEFILE") + + # First extract info to get the video title + extract_options = { + "logger": logging.getLogger(), + } + if cookiefile: + extract_options["cookiefile"] = cookiefile + + try: + with YoutubeDL(extract_options) as ydl_info: + info = ydl_info.extract_info(self.transcription_task.url, download=False) + video_title = info.get("title", "audio") + except Exception as exc: + logging.debug(f"Error extracting video info: {exc}") + video_title = "audio" + + # Sanitize title for use as filename + video_title = YoutubeDL.sanitize_info({"title": video_title})["title"] + # Remove characters that are problematic in filenames + for char in ['/', '\\', ':', '*', '?', '"', '<', '>', '|']: + video_title = video_title.replace(char, '_') + + # Create temp directory and use video title as filename + temp_dir = tempfile.mkdtemp() + temp_output_path = os.path.join(temp_dir, video_title) wav_file = temp_output_path + ".wav" wav_file = str(Path(wav_file).resolve()) - cookiefile = os.getenv("BUZZ_DOWNLOAD_COOKIEFILE") - options = { "format": "bestaudio/best", "progress_hooks": [self.on_download_progress], diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 4ddaf990..91f408ae 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -385,6 +385,15 @@ class MainWindow(QMainWindow): pass def on_task_completed(self, task: FileTranscriptionTask, segments: List[Segment]): + # Update file path in database (important for URL imports where file is downloaded) + if task.file_path: + logging.debug(f"Updating transcription file path: {task.file_path}") + # For URL imports, use the file basename (video title) as the display name + name = None + if task.source == FileTranscriptionTask.Source.URL_IMPORT: + basename = os.path.basename(task.file_path) + name = os.path.splitext(basename)[0] # Remove .wav extension + self.transcription_service.update_transcription_file_and_name(task.uid, task.file_path, name) self.transcription_service.update_transcription_as_completed(task.uid, segments) self.table_widget.refresh_row(task.uid) diff --git a/buzz/widgets/transcription_tasks_table_widget.py b/buzz/widgets/transcription_tasks_table_widget.py index 296ea79e..3fe0db7b 100644 --- a/buzz/widgets/transcription_tasks_table_widget.py +++ b/buzz/widgets/transcription_tasks_table_widget.py @@ -95,8 +95,8 @@ column_definitions = [ width=400, delegate=RecordDelegate( text_getter=lambda record: record.value("name") or ( - record.value("url") if record.value("url") != "" - else os.path.basename(record.value("file")) + os.path.basename(record.value("file")) if record.value("file") + else record.value("url") or "" ) ), hidden_toggleable=False, diff --git a/tests/widgets/transcription_tasks_table_widget_test.py b/tests/widgets/transcription_tasks_table_widget_test.py index 785866f1..77bb0514 100644 --- a/tests/widgets/transcription_tasks_table_widget_test.py +++ b/tests/widgets/transcription_tasks_table_widget_test.py @@ -286,16 +286,16 @@ class TestTranscriptionTasksTableWidget: text = file_column_def.delegate.callback(record_with_name) assert text == "Custom Name" - # Test fallback to URL when no name - record_url_fallback = mock_record({"name": None, "url": "http://example.com/audio.mp3", "file": "/path/file.mp3"}) + # Test fallback to file basename when no name (file takes priority over URL) + record_file_fallback = mock_record({"name": None, "url": "http://example.com/audio.mp3", "file": "/path/file.mp3"}) + text = file_column_def.delegate.callback(record_file_fallback) + assert text == "file.mp3" + + # Test fallback to URL when no name and no file + record_url_fallback = mock_record({"name": None, "url": "http://example.com/audio.mp3", "file": ""}) text = file_column_def.delegate.callback(record_url_fallback) assert text == "http://example.com/audio.mp3" - # Test fallback to filename when no name or URL - record_file_fallback = mock_record({"name": None, "url": "", "file": "/path/to/audio.mp3"}) - text = file_column_def.delegate.callback(record_file_fallback) - assert text == "audio.mp3" - def test_notes_column_text_getter(self, widget): """Test that notes column displays notes or empty string""" notes_column_def = next((col for col in column_definitions if col.column == Column.NOTES), None) From 665d21b391370c15b551f2c5efe2ff5513337930 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Mon, 22 Dec 2025 10:21:33 +0200 Subject: [PATCH 30/73] 1314 add download retry (#1322) --- buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/en_US/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 58 +++++++++++-------- buzz/locale/nl/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 54 ++++++++++------- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 54 ++++++++++------- .../speaker_identification_widget.py | 44 ++++++++++++-- 15 files changed, 491 insertions(+), 313 deletions(-) diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index dcb31aa1..49e0a048 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -330,8 +330,8 @@ msgstr "Descàrrega fallida" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Error" @@ -786,73 +786,83 @@ msgstr "Divideix per la longitud màxima" msgid "Merge" msgstr "Fusiona" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Cancel·la la transcripció" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Desa el fitxer" @@ -1295,7 +1305,7 @@ msgstr "Sundanès" msgid "Cantonese" msgstr "Cantonès" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "S'ha produït un error de connexió" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index 5773e1a3..642e76dd 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -329,8 +329,8 @@ msgstr "Download mislykkedes" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Fejl" @@ -780,73 +780,83 @@ msgstr "Split ved max længde" msgid "Merge" msgstr "Sammenflet" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Afbryd transkription" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Gem fil" @@ -1285,7 +1295,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Der er opstået en forbindelsesfejl" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index 979badb1..306dcad4 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -329,8 +329,8 @@ msgstr "Der Download ist fehlgeschlagen" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Fehler" @@ -781,73 +781,83 @@ msgstr "Aufgeteilt nach maximaler Länge" msgid "Merge" msgstr "Vereinigen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Transkription abbrechen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Datei speichern" @@ -1287,7 +1297,7 @@ msgstr "Sundanesisch" msgid "Cantonese" msgstr "Kantonesisch" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Ein Verbindungsfehler ist aufgetreten" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index c2a09010..7d4f46d1 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -320,8 +320,8 @@ msgstr "" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "" @@ -764,72 +764,82 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 msgid "5/8 Preparing transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 msgid "Save" msgstr "" @@ -1264,7 +1274,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index e7757df4..26767a62 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -336,8 +336,8 @@ msgstr "Descarga fallida" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Error" @@ -827,75 +827,85 @@ msgstr "Dividido por la longitud máxima" msgid "Merge" msgstr "Fusión" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" # automatic translation -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Cancelar transcripción" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" # automatic translation -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Guardar archivo" @@ -1347,7 +1357,7 @@ msgstr "Sundanés" msgid "Cantonese" msgstr "Cantonés" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Se ha producido un error de conexión" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index 2bcbd210..40ca5aa2 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -330,8 +330,8 @@ msgstr "Download non riuscito" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Errore" @@ -789,73 +789,83 @@ msgstr "Diviso per lunghezza massima" msgid "Merge" msgstr "Unione" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Inizio trascrizione..." -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Salva file" @@ -1297,7 +1307,7 @@ msgstr "Sundanese" msgid "Cantonese" msgstr "Cantonese" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Si è verificato un errore di connessione" diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index 83ee5f76..05fa40d3 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -325,8 +325,8 @@ msgstr "ダウンロード失敗" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "エラー" @@ -777,73 +777,83 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "文字起こしをキャンセルする" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "ファイルを保存" @@ -1280,7 +1290,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "接続エラーが発生しました" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index a34f5558..2fa839da 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" -"PO-Revision-Date: 2025-12-14 09:03+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"PO-Revision-Date: 2025-12-22 09:26+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -333,8 +333,8 @@ msgstr "Lejupielāde neizdevās" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Kļūda" @@ -789,72 +789,84 @@ msgstr "Dalīt pie maksimālā garuma" msgid "Merge" msgstr "Apvienot" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "1/8 Apkopo transkripcijas" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "2/8 Ielādē audio" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "3/8 Ielādē identifikācijas modeli" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "3/8 Ielādē identifikācijas modeli (atkārto...)" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" +"Neizdevās ielādēt modeli. Lūdzu pārbaidiet savu interneta savienojumu un " +"mēģiniet vēlreiz." + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "4/8 Apstrādā audio" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 msgid "5/8 Preparing transcripts" msgstr "5/8 Sagatavo transkripcijas" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "6/8 Nosaka runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "7/8 Marķē runātāju teikumus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "8/8 Runātāju noteikšana pabeigta" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "0/0 Kļūda nosakot runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "1. solis: Runātāju noteikšana" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "Noteikt" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "Gatavs noteikt runātājus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "Audio datne nav atrasta" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "2. solis: Runātāju identifikācija" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "Atskaņot paraugu" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "Apvienot secīgus runātāja teikumus" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 msgid "Save" msgstr "Saglabāt" @@ -1296,7 +1308,7 @@ msgstr "Sundāņu" msgid "Cantonese" msgstr "Kantonas" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Notika savienojuma kļūda" diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index 7f35c3d6..93eafe1a 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -331,8 +331,8 @@ msgstr "Het downloaden is mislukt" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Foutmelding" @@ -781,73 +781,83 @@ msgstr "Splitsen op basis van max. lengte" msgid "Merge" msgstr "Samenvoegen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Transcriptie wissen" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Bestand opslaan" @@ -1286,7 +1296,7 @@ msgstr "Soedanees" msgid "Cantonese" msgstr "Kantonees" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Er is een verbindingsfout opgetreden" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index f1fea38d..25d05062 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -332,8 +332,8 @@ msgstr "Pobrany" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Błąd" @@ -789,73 +789,83 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Anuluj transkrypcję" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Zapisz plik" @@ -1299,7 +1309,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index 812a0280..a17cc980 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -329,8 +329,8 @@ msgstr "Falha ao baixar" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Erro" @@ -784,73 +784,83 @@ msgstr "Dividir por tamanho máximo" msgid "Merge" msgstr "Mesclar" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Iniciando transcrição..." -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Salvar Arquivo" @@ -1291,7 +1301,7 @@ msgstr "Sundanês" msgid "Cantonese" msgstr "Cantonês" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Ocorreu um erro de conexão" diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index ecf0eb0b..04a43926 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -327,8 +327,8 @@ msgstr "Невдале завантаження" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "Помилка" @@ -778,73 +778,83 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "Скасувати транскрипцію" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "Зберегти файл" @@ -1282,7 +1292,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "Виникла помилка зʼєднання" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index a6053e7c..825b23d4 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -335,8 +335,8 @@ msgstr "下载模型失败" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "错误" @@ -791,73 +791,83 @@ msgstr "按最大长度拆分" msgid "Merge" msgstr "合并" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "取消识别" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "保存文件" @@ -1299,7 +1309,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "连接发生错误" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index 795a4111..eb4e33af 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-17 19:51+0200\n" +"POT-Creation-Date: 2025-12-22 09:24+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -330,8 +330,8 @@ msgstr "下載模型" #: buzz/widgets/transcription_tasks_table_widget.py:704 #: buzz/widgets/transcription_tasks_table_widget.py:774 #: buzz/widgets/transcription_tasks_table_widget.py:805 -#: buzz/widgets/main_window.py:283 buzz/model_loader.py:651 -#: buzz/model_loader.py:665 +#: buzz/widgets/main_window.py:283 buzz/model_loader.py:727 +#: buzz/model_loader.py:741 msgid "Error" msgstr "" @@ -785,73 +785,83 @@ msgstr "" msgid "Merge" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:103 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:104 msgid "1/8 Collecting transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:125 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:126 msgid "2/8 Loading audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:140 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:141 msgid "3/8 Loading alignment model" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:150 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:167 +msgid "3/8 Loading alignment model (retrying with cache...)" +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:172 +msgid "" +"Failed to load alignment model. Please check your internet connection and " +"try again." +msgstr "" + +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:180 msgid "4/8 Processing audio" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:168 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:198 #, fuzzy msgid "5/8 Preparing transcripts" msgstr "取消錄製" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:190 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:220 msgid "6/8 Identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:217 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:247 msgid "7/8 Mapping speakers to transcripts" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:257 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:287 msgid "8/8 Identification done" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:262 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:292 msgid "0/0 Error identifying speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:316 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:352 msgid "Step 1: Identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:328 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:364 msgid "Identify" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:337 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:373 msgid "Ready to identify speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:339 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:375 msgid "Audio file not found" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:363 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:399 msgid "Step 2: Name speakers" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:378 -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:493 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:414 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:529 msgid "Play sample" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:393 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:429 msgid "Merge speaker sentences" msgstr "" -#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:398 +#: buzz/widgets/transcription_viewer/speaker_identification_widget.py:434 #, fuzzy msgid "Save" msgstr "檔案" @@ -1293,7 +1303,7 @@ msgstr "" msgid "Cantonese" msgstr "" -#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:684 +#: buzz/transcriber/recording_transcriber.py:244 buzz/model_loader.py:760 msgid "A connection error occurred" msgstr "" diff --git a/buzz/widgets/transcription_viewer/speaker_identification_widget.py b/buzz/widgets/transcription_viewer/speaker_identification_widget.py index 3fba2c10..e1848bf6 100644 --- a/buzz/widgets/transcription_viewer/speaker_identification_widget.py +++ b/buzz/widgets/transcription_viewer/speaker_identification_widget.py @@ -1,6 +1,7 @@ import re import os import logging +import time import faster_whisper import torch import random @@ -138,10 +139,39 @@ class IdentificationWorker(QObject): return self.progress_update.emit(_("3/8 Loading alignment model")) - alignment_model, alignment_tokenizer = load_alignment_model( - device, - dtype=torch_dtype, - ) + alignment_model = None + alignment_tokenizer = None + for attempt in range(3): + try: + alignment_model, alignment_tokenizer = load_alignment_model( + device, + dtype=torch_dtype, + ) + break + except Exception as e: + if attempt < 2: + logging.warning( + f"Speaker identification: Failed to load alignment model " + f"(attempt {attempt + 1}/3), retrying: {e}" + ) + # On retry, try using cached models only (offline mode) + # Set at runtime by modifying the library constants directly + # (env vars are only read at import time) + try: + import huggingface_hub.constants + huggingface_hub.constants.HF_HUB_OFFLINE = True + logging.debug("Speaker identification: Enabled HF offline mode") + except Exception as offline_err: + logging.warning(f"Failed to set offline mode: {offline_err}") + self.progress_update.emit( + _("3/8 Loading alignment model (retrying with cache...)") + ) + time.sleep(2 ** attempt) # 1s, 2s backoff + else: + raise RuntimeError( + _("Failed to load alignment model. " + "Please check your internet connection and try again.") + ) from e if self._is_cancelled: logging.debug("Speaker identification worker: Cancelled at step 4") @@ -278,6 +308,12 @@ class IdentificationWorker(QObject): except Exception: pass torch.cuda.empty_cache() + # Reset offline mode so it doesn't affect other operations + try: + import huggingface_hub.constants + huggingface_hub.constants.HF_HUB_OFFLINE = False + except Exception: + pass class SpeakerIdentificationWidget(QWidget): From 47ddc1461c6bc4118d2c105b5d318a7a8a5863f9 Mon Sep 17 00:00:00 2001 From: Raivis Dejus Date: Tue, 23 Dec 2025 14:11:47 +0200 Subject: [PATCH 31/73] Fix for file being missing for speaker identification (#1325) --- buzz/widgets/main_window.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 91f408ae..de790267 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -385,14 +385,12 @@ class MainWindow(QMainWindow): pass def on_task_completed(self, task: FileTranscriptionTask, segments: List[Segment]): - # Update file path in database (important for URL imports where file is downloaded) - if task.file_path: + # Update file path in database only for URL imports where file is downloaded + if task.source == FileTranscriptionTask.Source.URL_IMPORT and task.file_path: logging.debug(f"Updating transcription file path: {task.file_path}") - # For URL imports, use the file basename (video title) as the display name - name = None - if task.source == FileTranscriptionTask.Source.URL_IMPORT: - basename = os.path.basename(task.file_path) - name = os.path.splitext(basename)[0] # Remove .wav extension + # Use the file basename (video title) as the display name + basename = os.path.basename(task.file_path) + name = os.path.splitext(basename)[0] # Remove .wav extension self.transcription_service.update_transcription_file_and_name(task.uid, task.file_path, name) self.transcription_service.update_transcription_as_completed(task.uid, segments) self.table_widget.refresh_row(task.uid) From 6e54b5cb0285db5a0c95dca689cd11b0b2adf19d Mon Sep 17 00:00:00 2001 From: David Olowomeye <100958002+greatdaveo@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:29:34 +0000 Subject: [PATCH 32/73] Implemented presentation window for live transcripts #1306 (#1323) Co-authored-by: Raivis Dejus --- buzz/assets/icons/color-background.svg | 6 + buzz/assets/icons/fullscreen.svg | 5 + buzz/assets/icons/gui-text-color.svg | 2 + buzz/assets/icons/new-window.svg | 7 + buzz/locale/ca_ES/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/da_DK/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/de_DE/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/en_US/LC_MESSAGES/buzz.po | 70 ++- buzz/locale/es_ES/LC_MESSAGES/buzz.po | 77 ++- buzz/locale/it_IT/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/ja_JP/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/lv_LV/LC_MESSAGES/buzz.po | 72 ++- buzz/locale/nl/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/pl_PL/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/pt_BR/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/uk_UA/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/zh_CN/LC_MESSAGES/buzz.po | 75 ++- buzz/locale/zh_TW/LC_MESSAGES/buzz.po | 73 ++- buzz/settings/settings.py | 5 + buzz/transcriber/recording_transcriber.py | 12 +- buzz/transcriber/whisper_cpp.py | 6 +- buzz/widgets/icon.py | 19 + buzz/widgets/icon_presentation.py | 60 +++ buzz/widgets/presentation_window.py | 189 ++++++++ buzz/widgets/recording_transcriber_widget.py | 213 ++++++++- pytest.ini | 2 +- .../io.github.chidiwilliams.Buzz.metainfo.xml | 5 +- tests/widgets/presentation_window_test.py | 324 +++++++++++++ .../recording_transcriber_widget_test.py | 440 +++++++++++++++++- 29 files changed, 2171 insertions(+), 166 deletions(-) create mode 100644 buzz/assets/icons/color-background.svg create mode 100644 buzz/assets/icons/fullscreen.svg create mode 100644 buzz/assets/icons/gui-text-color.svg create mode 100644 buzz/assets/icons/new-window.svg create mode 100644 buzz/widgets/icon_presentation.py create mode 100644 buzz/widgets/presentation_window.py create mode 100644 tests/widgets/presentation_window_test.py diff --git a/buzz/assets/icons/color-background.svg b/buzz/assets/icons/color-background.svg new file mode 100644 index 00000000..c62912ed --- /dev/null +++ b/buzz/assets/icons/color-background.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/buzz/assets/icons/fullscreen.svg b/buzz/assets/icons/fullscreen.svg new file mode 100644 index 00000000..e17e748d --- /dev/null +++ b/buzz/assets/icons/fullscreen.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/buzz/assets/icons/gui-text-color.svg b/buzz/assets/icons/gui-text-color.svg new file mode 100644 index 00000000..929d172c --- /dev/null +++ b/buzz/assets/icons/gui-text-color.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/buzz/assets/icons/new-window.svg b/buzz/assets/icons/new-window.svg new file mode 100644 index 00000000..cfb59177 --- /dev/null +++ b/buzz/assets/icons/new-window.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po index 49e0a048..27186e5c 100644 --- a/buzz/locale/ca_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/ca_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2025-10-17 07:59+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: Catalan \n" @@ -53,6 +53,11 @@ msgstr "URL no vàlida" msgid "The URL you entered is invalid." msgstr "L'URL que heu introduït no és vàlid." +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Veure la traducció de transcripció" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Restableix als valors predeterminats" @@ -578,27 +583,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Enregistrament en directe" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Feu clic a Enregistra per a començar..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "Esperant la traducció de la IA..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Micròfon:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Fitxers de text" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Fitxers de text" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Selecciona la carpeta d'exportació" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Selecciona un fitxer d'àudio" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "S'ha produït un error en iniciar un enregistrament nou:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -924,14 +977,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "No s'ha pogut desar la clau OpenAI API a l'anell de claus" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" "El servidor Whisper no s'ha pogut iniciar. Consulteu els registres per " "obtenir més informació." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1309,11 +1362,11 @@ msgstr "Cantonès" msgid "A connection error occurred" msgstr "S'ha produït un error de connexió" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "Començant Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Cancel·la la transcripció" diff --git a/buzz/locale/da_DK/LC_MESSAGES/buzz.po b/buzz/locale/da_DK/LC_MESSAGES/buzz.po index 642e76dd..b04893ac 100644 --- a/buzz/locale/da_DK/LC_MESSAGES/buzz.po +++ b/buzz/locale/da_DK/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: \n" "Last-Translator: Ole Guldberg2 \n" "Language-Team: \n" @@ -50,6 +50,11 @@ msgstr "Ugyldig URL" msgid "The URL you entered is invalid." msgstr "Den URL du har angivet er ikke gyldig." +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Vis transkriberede oversættelse" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Gendan standard-indstillinger" @@ -575,27 +580,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Live optagelse" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Klik Optage for at begynde..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "Venter på AI oversættelse..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Mikrofon:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Tekst filer" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Tekst filer" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Vælg eksport-mappe" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Vælg audio-fil" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Der skete en fejl ved opstart af en ny optagelse:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -918,12 +971,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Kan ikke gemme OpenAI API-nøgle i nøgleringen" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1299,11 +1352,11 @@ msgstr "" msgid "A connection error occurred" msgstr "Der er opstået en forbindelsesfejl" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Afbryd transkription" diff --git a/buzz/locale/de_DE/LC_MESSAGES/buzz.po b/buzz/locale/de_DE/LC_MESSAGES/buzz.po index 306dcad4..339f7279 100644 --- a/buzz/locale/de_DE/LC_MESSAGES/buzz.po +++ b/buzz/locale/de_DE/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2025-03-05 14:41+0100\n" "Last-Translator: \n" "Language-Team: \n" @@ -52,6 +52,11 @@ msgstr "Ungültige URL" msgid "The URL you entered is invalid." msgstr "Die von Ihnen eingegebene URL ist ungültig." +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Übersetzung des Transkripts anzeigen" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Auf Standardeinstellungen zurücksetzen" @@ -576,27 +581,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Live-Aufnahme" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Auf „Aufnehmen“ klicken um zu beginnen …" -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "Warten auf KI-Übersetzung..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Mikrofon:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Textdateien" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Textdateien" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Exportordner auswählen" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Audiodatei auswählen" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Beim Starten einer neuen Aufnahme ist ein Fehler aufgetreten:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -920,12 +973,12 @@ msgstr "" "Der OpenAI-API-Schlüssel kann nicht im Schlüsselbund gespeichert werden" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1301,11 +1354,11 @@ msgstr "Kantonesisch" msgid "A connection error occurred" msgstr "Ein Verbindungsfehler ist aufgetreten" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Transkription abbrechen" diff --git a/buzz/locale/en_US/LC_MESSAGES/buzz.po b/buzz/locale/en_US/LC_MESSAGES/buzz.po index 7d4f46d1..803dbab9 100644 --- a/buzz/locale/en_US/LC_MESSAGES/buzz.po +++ b/buzz/locale/en_US/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -53,6 +53,10 @@ msgstr "" msgid "The URL you entered is invalid." msgstr "" +#: buzz/widgets/presentation_window.py:23 +msgid "Live Transcript Presentation" +msgstr "" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "" @@ -561,27 +565,71 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +msgid "Text Size:" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +msgid "Text Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +msgid "Select Text Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:373 +msgid "Select Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -898,12 +946,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1278,11 +1326,11 @@ msgstr "" msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 msgid "Starting transcription..." msgstr "" diff --git a/buzz/locale/es_ES/LC_MESSAGES/buzz.po b/buzz/locale/es_ES/LC_MESSAGES/buzz.po index 26767a62..e272d88c 100644 --- a/buzz/locale/es_ES/LC_MESSAGES/buzz.po +++ b/buzz/locale/es_ES/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2025-09-08 12:43+0200\n" "Last-Translator: Éric Duarte \n" "Language-Team: \n" @@ -53,6 +53,12 @@ msgstr "URL inválido" msgid "The URL you entered is invalid." msgstr "La URL que has introducido no es válida." +# automatic translation +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Ver la traducción de la transcripción" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Reestablecer los Valores por Defecto" @@ -609,31 +615,80 @@ msgid "Could not restart transcription: transcriber worker not found." msgstr "" # automatic translation -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Grabación en vivo" # automatic translation -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Pulse en Grabar para comenzar..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "A la espera de la traducción de la IA..." # automatic translation -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Micrófono:" +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Archivos de texto" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Archivos de texto" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Seleccione Exportar carpeta" + # automatic translation -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Seleccionar archivo de audio" + +# automatic translation +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Se produjo un error al iniciar una grabación nueva:" # automatic translation -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -975,14 +1030,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "No se puede guardar la clave de la API de OpenAI en el llavero" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" "El servidor Whisper no se pudo iniciar. Consulta los registros para obtener " "más detalles." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1361,12 +1416,12 @@ msgstr "Cantonés" msgid "A connection error occurred" msgstr "Se ha producido un error de conexión" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "Iniciando Whisper.cpp..." # automatic translation -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Cancelar transcripción" diff --git a/buzz/locale/it_IT/LC_MESSAGES/buzz.po b/buzz/locale/it_IT/LC_MESSAGES/buzz.po index 40ca5aa2..5c4d08ea 100644 --- a/buzz/locale/it_IT/LC_MESSAGES/buzz.po +++ b/buzz/locale/it_IT/LC_MESSAGES/buzz.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2025-11-09 20:22+0200\n" "Language-Team: (Italiano) Albano Battistella \n" "Language: it_IT\n" @@ -52,6 +52,11 @@ msgstr "URL non valido" msgid "The URL you entered is invalid." msgstr "L'URL inserito non è valido." +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Visualizza la trascrizione della traduzione" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Ripristina impostazioni predefinite" @@ -579,27 +584,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Registrazione in diretta" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Fai clic su Registra per iniziare..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "In attesa della traduzione AI..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Microfono:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "File di testo" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "File di testo" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Seleziona la cartella di esportazione" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Seleziona file audio" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Si è verificato un errore durante l'avvio della nuova registrazione:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -927,13 +980,13 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Impossibile salvare la chiave API OpenAI nel portachiavi" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" "Impossibile avviare il server Whisper. Controllare i log per i dettagli." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1311,11 +1364,11 @@ msgstr "Cantonese" msgid "A connection error occurred" msgstr "Si è verificato un errore di connessione" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "Avvio di Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 msgid "Starting transcription..." msgstr "Inizio trascrizione..." diff --git a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po index 05fa40d3..6116f6e1 100644 --- a/buzz/locale/ja_JP/LC_MESSAGES/buzz.po +++ b/buzz/locale/ja_JP/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: \n" "Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n" "Language-Team: \n" @@ -48,6 +48,11 @@ msgstr "無効なURL" msgid "The URL you entered is invalid." msgstr "入力されたURLは無効です。" +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "文字起こしの翻訳を表示する" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "デフォルトに戻す" @@ -571,27 +576,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "ライブ録音" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "クリックで録音を開始..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "AI翻訳を待っています..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "マイク:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "テキストファイル" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "テキストファイル" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "出力フォルダを選択" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "音声ファイルを選択" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "新規録音開始時にエラーが発生しました:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -913,12 +966,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "OpenAI API キーをkeyringに保存できません" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1294,11 +1347,11 @@ msgstr "" msgid "A connection error occurred" msgstr "接続エラーが発生しました" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "文字起こしをキャンセルする" diff --git a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po index 2fa839da..83df9b2a 100644 --- a/buzz/locale/lv_LV/LC_MESSAGES/buzz.po +++ b/buzz/locale/lv_LV/LC_MESSAGES/buzz.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" -"PO-Revision-Date: 2025-12-22 09:26+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" +"PO-Revision-Date: 2025-12-23 19:24+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: lv_LV\n" @@ -53,6 +53,10 @@ msgstr "Adrese nav derīga" msgid "The URL you entered is invalid." msgstr "Jūsu ievadītā URL adrese nav derīga." +#: buzz/widgets/presentation_window.py:23 +msgid "Live Transcript Presentation" +msgstr "Dzīvais ieraksts" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Atjaunot noklusētos" @@ -582,27 +586,71 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "Neizdevās sākt atpazīšanu: Kļūda lietotnē, pārstartējiet." -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Dzīvā ierakstīšana" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Klikšķiniet Ierakstīt, lai sāktu..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "Gaida MI tulkojumu..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Mikrofons:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "Rādīt jaunā logā" + +#: buzz/widgets/recording_transcriber_widget.py:231 +msgid "Text Size:" +msgstr "Teksta izmērs:" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "Stils" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "Gaišais" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "Tumšais" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "Pielāgots" + +#: buzz/widgets/recording_transcriber_widget.py:265 +msgid "Text Color" +msgstr "Teksta krāsa" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "Fona krāsa" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "Pilnekrāns" + +#: buzz/widgets/recording_transcriber_widget.py:355 +msgid "Select Text Color" +msgstr "Izvēlieties teksta krāsu" + +#: buzz/widgets/recording_transcriber_widget.py:373 +msgid "Select Background Color" +msgstr "Izvēlieties fona krāsu" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Sākot jaunu ierakstu notikusi kļūda:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -927,14 +975,14 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" "Whisper serverim neizdevās ieslēgties. Lūdzu pārbaudiet lietotnes žurnāla " "ierakstus." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1312,11 +1360,11 @@ msgstr "Kantonas" msgid "A connection error occurred" msgstr "Notika savienojuma kļūda" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "Palaiž Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 msgid "Starting transcription..." msgstr "Sāk atpazīšanu..." diff --git a/buzz/locale/nl/LC_MESSAGES/buzz.po b/buzz/locale/nl/LC_MESSAGES/buzz.po index 93eafe1a..32ceab84 100644 --- a/buzz/locale/nl/LC_MESSAGES/buzz.po +++ b/buzz/locale/nl/LC_MESSAGES/buzz.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2025-03-20 18:30+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: none\n" @@ -55,6 +55,11 @@ msgstr "Ongeldige url" msgid "The URL you entered is invalid." msgstr "De ingevoerde url is ongeldig." +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Getranscribeerde vertaling bekijken" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Standaardwaarden" @@ -578,27 +583,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Live-opname" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Klik op de opnameknop om te beginnen…" -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "Bezig met wachten op AI-vertaling…" -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Microfoon:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Tekstbestanden" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Tekstbestanden" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Kies een exportmap" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Kies een audiobestand" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Er is een fout opgetreden tijdens het starten van de opname:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -919,12 +972,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "De OpenAI-api-sleutel kan niet worden bewaard in de sleutelbos" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1300,11 +1353,11 @@ msgstr "Kantonees" msgid "A connection error occurred" msgstr "Er is een verbindingsfout opgetreden" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Transcriptie wissen" diff --git a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po index 25d05062..f4e08020 100644 --- a/buzz/locale/pl_PL/LC_MESSAGES/buzz.po +++ b/buzz/locale/pl_PL/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2024-03-17 20:50+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -53,6 +53,11 @@ msgstr "Nieprawidłowy URL" msgid "The URL you entered is invalid." msgstr "Wprowadzony URL nie jest prawidłowy" +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Nowa transkrypcja" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "" @@ -583,27 +588,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Nagrywanie na żywo" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Naciśnij Nagraj, aby zacząć..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Mikrofon:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Pliki tekstowe" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Pliki tekstowe" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Wybierz plik audio" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Wybierz plik audio" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Wystąpił błąd podczas rozpoczęcia nowego nagrania:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -931,12 +984,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1313,11 +1366,11 @@ msgstr "" msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Anuluj transkrypcję" diff --git a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po index a17cc980..e22b0210 100644 --- a/buzz/locale/pt_BR/LC_MESSAGES/buzz.po +++ b/buzz/locale/pt_BR/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Buzz\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2025-11-01 17:43-0300\n" "Last-Translator: Paulo Schopf \n" "Language-Team: none\n" @@ -53,6 +53,11 @@ msgstr "URL inválida" msgid "The URL you entered is invalid." msgstr "A URL inserida é inválida." +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Ver Tradução da Transcrição" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Redefinir para o Padrão" @@ -576,27 +581,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Gravação ao Vivo" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Clique em Gravar para começar..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "Aguardando tradução da IA..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Microfone:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Arquivos de texto" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Arquivos de texto" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Selecionar Pasta de Exportação" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Selecionar arquivo de áudio" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "Ocorreu um erro ao iniciar uma nova gravação:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -922,12 +975,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Não foi possível salvar a chave da API OpenAI no cofre de chaves" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "Falha ao iniciar o servidor Whisper. Verifique os logs." #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1305,11 +1358,11 @@ msgstr "Cantonês" msgid "A connection error occurred" msgstr "Ocorreu um erro de conexão" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "Iniciando Whisper.cpp..." -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Iniciando transcrição..." diff --git a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po index 04a43926..6a0b21ab 100644 --- a/buzz/locale/uk_UA/LC_MESSAGES/buzz.po +++ b/buzz/locale/uk_UA/LC_MESSAGES/buzz.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: \n" "Last-Translator: Yevhen Popok \n" "Language-Team: \n" @@ -50,6 +50,11 @@ msgstr "Недійсна адреса" msgid "The URL you entered is invalid." msgstr "Адреса, яку ви ввели, є недійсною" +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "Переглянути переклад транскрипції" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "Типові значення" @@ -573,27 +578,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "Живий запис" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "Натисніть на Запис, щоб розпочати..." -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "Очікування перекладу від ШІ..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "Мікрофон:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "Текстові файли" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "Текстові файли" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "Виберіть теку для експорту" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "Вибрати аудіофайл" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "При старті нового запису виникла помилка:" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -915,12 +968,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "Не вдається додати до звʼязки ключів API-ключ OpenAI" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1296,11 +1349,11 @@ msgstr "" msgid "A connection error occurred" msgstr "Виникла помилка зʼєднання" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "Скасувати транскрипцію" diff --git a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po index 825b23d4..352cec0b 100644 --- a/buzz/locale/zh_CN/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_CN/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: lamb \n" @@ -53,6 +53,11 @@ msgstr "无效的网址" msgid "The URL you entered is invalid." msgstr "输入的网址无效" +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "查看识别的翻译" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "恢复默认" @@ -586,27 +591,75 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "实时录制" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "点击开始录制" -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "等待AI翻译..." -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "麦克风:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +#, fuzzy +msgid "Text Size:" +msgstr "文本文件" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +#, fuzzy +msgid "Text Color" +msgstr "文本文件" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "选择输出文件夹" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "选择音频文件" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "开始新录制时出错" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -931,12 +984,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "无法将OpenAI API密钥保存到密钥串" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1313,11 +1366,11 @@ msgstr "" msgid "A connection error occurred" msgstr "连接发生错误" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "取消识别" diff --git a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po index eb4e33af..f8db1a27 100644 --- a/buzz/locale/zh_TW/LC_MESSAGES/buzz.po +++ b/buzz/locale/zh_TW/LC_MESSAGES/buzz.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-22 09:24+0200\n" +"POT-Creation-Date: 2025-12-23 19:21+0200\n" "PO-Revision-Date: 2023-05-01 15:45+0800\n" "Last-Translator: \n" "Language-Team: Lamb\n" @@ -53,6 +53,11 @@ msgstr "" msgid "The URL you entered is invalid." msgstr "" +#: buzz/widgets/presentation_window.py:23 +#, fuzzy +msgid "Live Transcript Presentation" +msgstr "新錄製" + #: buzz/widgets/preferences_dialog/shortcuts_editor_preferences_widget.py:29 msgid "Reset to Defaults" msgstr "" @@ -581,27 +586,73 @@ msgstr "" msgid "Could not restart transcription: transcriber worker not found." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:83 +#: buzz/widgets/recording_transcriber_widget.py:97 msgid "Live Recording" msgstr "現場錄製" -#: buzz/widgets/recording_transcriber_widget.py:149 +#: buzz/widgets/recording_transcriber_widget.py:163 msgid "Click Record to begin..." msgstr "點擊開始錄製" -#: buzz/widgets/recording_transcriber_widget.py:152 +#: buzz/widgets/recording_transcriber_widget.py:166 msgid "Waiting for AI translation..." msgstr "" -#: buzz/widgets/recording_transcriber_widget.py:164 +#: buzz/widgets/recording_transcriber_widget.py:178 msgid "Microphone:" msgstr "麥克風:" -#: buzz/widgets/recording_transcriber_widget.py:579 +#: buzz/widgets/recording_transcriber_widget.py:225 +msgid "Show in new window" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:231 +msgid "Text Size:" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:247 +msgid "Theme" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Light" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Dark" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:251 +msgid "Custom" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:265 +msgid "Text Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:275 +msgid "Background Color" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:284 +msgid "Fullscreen" +msgstr "" + +#: buzz/widgets/recording_transcriber_widget.py:355 +#, fuzzy +msgid "Select Text Color" +msgstr "選擇聲音檔案" + +#: buzz/widgets/recording_transcriber_widget.py:373 +#, fuzzy +msgid "Select Background Color" +msgstr "選擇聲音檔案" + +#: buzz/widgets/recording_transcriber_widget.py:788 msgid "An error occurred while starting a new recording:" msgstr "開始新錄製出錯" -#: buzz/widgets/recording_transcriber_widget.py:583 +#: buzz/widgets/recording_transcriber_widget.py:792 msgid "" "Please check your audio devices or check the application logs for more " "information." @@ -925,12 +976,12 @@ msgid "Unable to save OpenAI API key to keyring" msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:57 -#: buzz/transcriber/recording_transcriber.py:417 +#: buzz/transcriber/recording_transcriber.py:427 msgid "Whisper server failed to start. Check logs for details." msgstr "" #: buzz/transcriber/local_whisper_cpp_server_transcriber.py:60 -#: buzz/transcriber/recording_transcriber.py:421 +#: buzz/transcriber/recording_transcriber.py:431 msgid "" "Whisper server failed to start due to insufficient memory. Please try again " "with a smaller model. To force CPU mode use BUZZ_FORCE_CPU=TRUE environment " @@ -1307,11 +1358,11 @@ msgstr "" msgid "A connection error occurred" msgstr "" -#: buzz/transcriber/recording_transcriber.py:353 +#: buzz/transcriber/recording_transcriber.py:358 msgid "Starting Whisper.cpp..." msgstr "" -#: buzz/transcriber/recording_transcriber.py:408 +#: buzz/transcriber/recording_transcriber.py:418 #, fuzzy msgid "Starting transcription..." msgstr "取消錄製" diff --git a/buzz/settings/settings.py b/buzz/settings/settings.py index a33cead5..7ec768fd 100644 --- a/buzz/settings/settings.py +++ b/buzz/settings/settings.py @@ -26,6 +26,11 @@ class Settings: RECORDING_TRANSCRIBER_EXPORT_FOLDER = "recording-transcriber/export-folder" RECORDING_TRANSCRIBER_MODE = "recording-transcriber/mode" + PRESENTATION_WINDOW_TEXT_COLOR = "presentation-window/text-color" + PRESENTATION_WINDOW_BACKGROUND_COLOR = "presentation-window/background-color" + PRESENTATION_WINDOW_TEXT_SIZE = "presentation-window/text-size" + PRESENTATION_WINDOW_THEME = "presentation-window/theme" + FILE_TRANSCRIBER_TASK = "file-transcriber/task" FILE_TRANSCRIBER_MODEL = "file-transcriber/model" FILE_TRANSCRIBER_LANGUAGE = "file-transcriber/language" diff --git a/buzz/transcriber/recording_transcriber.py b/buzz/transcriber/recording_transcriber.py index 929c12cb..e4f5a850 100644 --- a/buzz/transcriber/recording_transcriber.py +++ b/buzz/transcriber/recording_transcriber.py @@ -350,6 +350,11 @@ class RecordingTranscriber(QObject): self.process.wait(5000) def start_local_whisper_server(self): + # Reduce verbose HTTP client logging from OpenAI/httpx + logging.getLogger("httpx").setLevel(logging.WARNING) + logging.getLogger("httpcore").setLevel(logging.WARNING) + logging.getLogger("openai").setLevel(logging.WARNING) + self.transcription.emit(_("Starting Whisper.cpp...")) self.process = None @@ -368,7 +373,12 @@ class RecordingTranscriber(QObject): "--threads", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8) // 2)), "--model", self.model_path, "--no-timestamps", - "--no-context", # on Windows context causes duplications of last message + # on Windows context causes duplications of last message + "--no-context", + # Protections against hallucinated repetition. Seems to be problem on macOS + # https://github.com/ggml-org/whisper.cpp/issues/1507 + "--max-context", "64", + "--entropy-thold", "2.8", "--suppress-nst" ] diff --git a/buzz/transcriber/whisper_cpp.py b/buzz/transcriber/whisper_cpp.py index 8b2195ee..3b609e6d 100644 --- a/buzz/transcriber/whisper_cpp.py +++ b/buzz/transcriber/whisper_cpp.py @@ -96,8 +96,8 @@ class WhisperCpp: # Build the command cmd = [ whisper_cli_path, - "-m", task.model_path, - "-l", language, + "--model", task.model_path, + "--language", language, "--print-progress", "--suppress-nst", # Protections against hallucinated repetition. Seems to be problem on macOS @@ -105,7 +105,7 @@ class WhisperCpp: "--max-context", "64", "--entropy-thold", "2.8", "--output-json-full", - "-t", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8) // 2)), + "--threads", str(os.getenv("BUZZ_WHISPERCPP_N_THREADS", (os.cpu_count() or 8) // 2)), "-f", file_to_process, ] diff --git a/buzz/widgets/icon.py b/buzz/widgets/icon.py index 1efca875..298232a1 100644 --- a/buzz/widgets/icon.py +++ b/buzz/widgets/icon.py @@ -99,6 +99,25 @@ class ScrollToCurrentIcon(Icon): get_path("assets/visibility_FILL0_wght700_GRAD0_opsz48.svg"), parent ) +class NewWindowIcon(Icon): + def __init__(self, parent: QWidget): + super().__init__(get_path("assets/icons/new-window.svg"), parent) + + +class FullscreenIcon(Icon): + def __init__(self, parent: QWidget): + super().__init__(get_path("assets/icons/fullscreen.svg"), parent) + + +class ColorBackgroundIcon(Icon): + def __init__(self, parent: QWidget): + super().__init__(get_path("assets/icons/color-background.svg"), parent) + + +class TextColorIcon(Icon): + def __init__(self, parent: QWidget): + super().__init__(get_path("assets/icons/gui-text-color.svg"), parent) + BUZZ_ICON_PATH = get_path("assets/buzz.ico") BUZZ_LARGE_ICON_PATH = get_path("assets/buzz-icon-1024.png") diff --git a/buzz/widgets/icon_presentation.py b/buzz/widgets/icon_presentation.py new file mode 100644 index 00000000..6f230971 --- /dev/null +++ b/buzz/widgets/icon_presentation.py @@ -0,0 +1,60 @@ +from PyQt6.QtGui import QIcon, QPixmap, QPainter, QPalette +from PyQt6.QtCore import QSize +from PyQt6.QtSvg import QSvgRenderer +import os +from buzz.assets import APP_BASE_DIR + +class PresentationIcon: + "Icons for presentation window controls" + def __init__(self, parent, svg_path: str, color: str = None): + self.parent = parent + self.svg_path = svg_path + self.color = color or self.get_default_color() + + + def get_default_color(self) -> str: + """Get default icon color based on theme""" + palette = self.parent.palette() + is_dark = palette.window().color().black() > 127 + + return "#EEE" if is_dark else "#555" + + def get_icon(self) -> QIcon: + """Load SVG icon and return as QIcon""" + #Load from asset first + full_path = os.path.join(APP_BASE_DIR, "assets", "icons", os.path.basename(self.svg_path)) + + if not os.path.exists(full_path): + pixmap = QPixmap(24, 24) + pixmap.fill(self.color) + + return QIcon(pixmap) + + #Load SVG + renderer = QSvgRenderer(full_path) + pixmap = QPixmap(24, 24) + pixmap.fill(Qt.GlobalColor.transparent) + painter = QPainter(pixmap) + renderer.render(painter) + painter.end() + + return QIcon(pixmap) + + + + + + + + + + + + + + + + + + + diff --git a/buzz/widgets/presentation_window.py b/buzz/widgets/presentation_window.py new file mode 100644 index 00000000..8aad5ee4 --- /dev/null +++ b/buzz/widgets/presentation_window.py @@ -0,0 +1,189 @@ +import logging +from typing import Optional +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QTextCursor +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTextBrowser +from platformdirs import user_cache_dir + +from buzz.locale import _ +from buzz.settings.settings import Settings + +import os + +class PresentationWindow(QWidget): + """Window for displaying live transcripts in presentation mode""" + + def __init__(self, parent: Optional[QWidget] = None): + super().__init__(parent) + + self.settings = Settings() + self._current_transcript = "" + self._current_translation = "" + self.window_style = "" + self.setWindowTitle(_("Live Transcript Presentation")) + self.setWindowFlag(Qt.WindowType.Window) + + # Window size + self.resize(800, 600) + + # Create layout + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + # Text display widget + self.transcript_display = QTextBrowser(self) + self.transcript_display.setReadOnly(True) + + # Translation display (hidden first) + self.translation_display = QTextBrowser(self) + self.translation_display.setReadOnly(True) + self.translation_display.hide() + + # Add to layout + layout.addWidget(self.transcript_display) + layout.addWidget(self.translation_display) + + self.load_settings() + + def load_settings(self): + """Load and apply saved presentation settings""" + theme = self.settings.value( + Settings.Key.PRESENTATION_WINDOW_THEME, + "light" + ) + + # Load text size + text_size = self.settings.value( + Settings.Key.PRESENTATION_WINDOW_TEXT_SIZE, + 24, + int + ) + + # Load colors based on theme + if theme == "light": + text_color = "#000000" + bg_color = "#FFFFFF" + elif theme == "dark": + text_color = "#FFFFFF" + bg_color = "#000000" + else: + text_color = self.settings.value( + Settings.Key.PRESENTATION_WINDOW_TEXT_COLOR, + "#000000" + ) + + bg_color = self.settings.value( + Settings.Key.PRESENTATION_WINDOW_BACKGROUND_COLOR, + "#FFFFFF" + ) + + self.apply_styling(text_color, bg_color, text_size) + + # Refresh content with new styling + if self._current_transcript: + self.update_transcripts(self._current_transcript) + if self._current_translation: + self.update_translations(self._current_translation) + + def apply_styling(self, text_color: str, bg_color: str, text_size: int): + """Apply text color, background color and font size""" + + # Load custom CSS if it exists + css_file_path = self.get_css_file_path() + + if os.path.exists(css_file_path): + try: + with open(css_file_path, "r", encoding="utf-8") as f: + self.window_style = f.read() + except Exception as e: + logging.warning(f"Failed to load custom CSS: {e}") + else: + self.window_style = f""" + body {{ + color: {text_color}; + background-color: {bg_color}; + font-size: {text_size}pt; + font-family: Arial, sans-serif; + padding: 0; + margin: 20px; + }} + """ + + def update_transcripts(self, text: str): + """Update the transcript display with new text""" + if not text: + return + + self._current_transcript = text + escaped_text = text.replace("&", "&").replace("<", "<").replace(">", ">") + html_text = escaped_text.replace("\n", "
    ") + + html_content = f""" + + + + + + {html_text} + + + """ + + self.transcript_display.setHtml(html_content) + self.transcript_display.moveCursor(QTextCursor.MoveOperation.End) + + def update_translations(self, text: str): + """Update the translation display with new text""" + if not text: + return + + self._current_translation = text + self.translation_display.show() + + escaped_text = text.replace("&", "&").replace("<", "<").replace(">", ">") + html_text = escaped_text.replace("\n", "
    ") + + html_content = f""" + + + + + + {html_text} + + + """ + + self.translation_display.setHtml(html_content) + self.translation_display.moveCursor(QTextCursor.MoveOperation.End) + + def toggle_fullscreen(self): + """Toggle fullscreen mode""" + if self.isFullScreen(): + self.showNormal() + else: + self.showFullScreen() + + def keyPressEvent(self, event): + """Handle keyboard events""" + # ESC Key exits fullscreen + if event.key() == Qt.Key.Key_Escape and self.isFullScreen(): + self.showNormal() + event.accept() + else: + super().keyPressEvent(event) + + + def get_css_file_path(self) -> str: + """Get path to custom CSS file""" + cache_dir = user_cache_dir("Buzz") + os.makedirs(cache_dir, exist_ok=True) + + return os.path.join(cache_dir, "presentation_window_style.css") + + diff --git a/buzz/widgets/recording_transcriber_widget.py b/buzz/widgets/recording_transcriber_widget.py index b036fa03..7a35a06c 100644 --- a/buzz/widgets/recording_transcriber_widget.py +++ b/buzz/widgets/recording_transcriber_widget.py @@ -8,9 +8,20 @@ import sounddevice from enum import auto from typing import Optional, Tuple, Any -from PyQt6.QtCore import QThread, Qt, QThreadPool -from PyQt6.QtGui import QTextCursor, QCloseEvent -from PyQt6.QtWidgets import QWidget, QVBoxLayout, QFormLayout, QHBoxLayout, QMessageBox +from PyQt6.QtCore import QThread, Qt, QThreadPool, QTimer +from PyQt6.QtGui import QTextCursor, QCloseEvent, QColor +from PyQt6.QtWidgets import ( + QWidget, + QVBoxLayout, + QFormLayout, + QHBoxLayout, + QMessageBox, + QPushButton, + QComboBox, + QLabel, + QSpinBox, + QColorDialog +) from buzz.dialogs import show_model_download_error_dialog from buzz.locale import _ @@ -39,6 +50,8 @@ from buzz.widgets.text_display_box import TextDisplayBox from buzz.widgets.transcriber.transcription_options_group_box import ( TranscriptionOptionsGroupBox, ) +from buzz.widgets.presentation_window import PresentationWindow +from buzz.widgets.icon import NewWindowIcon, FullscreenIcon, ColorBackgroundIcon, TextColorIcon REAL_CHARS_REGEX = re.compile(r'\w') NO_SPACE_BETWEEN_SENTENCES = re.compile(r'([.!?。!?])([A-Z])') @@ -190,6 +203,180 @@ class RecordingTranscriberWidget(QWidget): default_value=False, ) + #Presentation window + self.presentation_window: Optional[PresentationWindow] = None + + self.presentation_options_bar = self.create_presentation_options_bar() + layout.insertWidget(3, self.presentation_options_bar) + self.presentation_options_bar.hide() + + def create_presentation_options_bar(self) -> QWidget: + """Crete the presentation options bar widget""" + + bar = QWidget(self) + layout = QHBoxLayout(bar) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(10) + + self.show_presentation_button = QPushButton(bar) + self.show_presentation_button.setIcon(NewWindowIcon(bar)) + self.show_presentation_button.setToolTip(_("Show in new window")) + self.show_presentation_button.clicked.connect(self.on_show_presentation_clicked) + layout.addWidget(self.show_presentation_button) + + layout.addStretch() #Push other controls to the right + + text_size_label = QLabel(_("Text Size:"), bar) + layout.addWidget(text_size_label) + + self.text_size_spinbox = QSpinBox(bar) + self.text_size_spinbox.setRange(12, 72) #12pt to 72pt + + saved_text_size = self.settings.value( + Settings.Key.PRESENTATION_WINDOW_TEXT_SIZE, + 24, + int + ) + self.text_size_spinbox.setValue(saved_text_size) + self.text_size_spinbox.valueChanged.connect(self.on_text_size_changed) + layout.addWidget(self.text_size_spinbox) + + #Theme selector + theme_label = QLabel(_("Theme"), bar) + layout.addWidget(theme_label) + + self.theme_combo = QComboBox(bar) + self.theme_combo.addItems([_("Light"), _("Dark"), _("Custom")]) + #Load saved theme + saved_theme = self.settings.value( + Settings.Key.PRESENTATION_WINDOW_THEME, + "light" + ) + theme_index = {"light": 0, "dark": 1, "custom": 2}.get(saved_theme, 0) + self.theme_combo.setCurrentIndex(theme_index) + self.theme_combo.currentIndexChanged.connect(self.on_theme_changed) + layout.addWidget(self.theme_combo) + + #Color buttons hidden first, show when custom is selected + self.text_color_button = QPushButton(bar) + self.text_color_button.setIcon(TextColorIcon(bar)) + self.text_color_button.setToolTip(_("Text Color")) + self.text_color_button.clicked.connect(self.on_text_color_clicked) + self.text_color_button.hide() + + if saved_theme == "custom": + self.text_color_button.show() + layout.addWidget(self.text_color_button) + + self.bg_color_button = QPushButton(bar) + self.bg_color_button.setIcon(ColorBackgroundIcon(bar)) + self.bg_color_button.setToolTip(_("Background Color")) + self.bg_color_button.clicked.connect(self.on_bg_color_clicked) + self.bg_color_button.hide() + if saved_theme == "custom": + self.bg_color_button.show() + layout.addWidget(self.bg_color_button) + + self.fullscreen_button = QPushButton(bar) + self.fullscreen_button.setIcon(FullscreenIcon(bar)) + self.fullscreen_button.setToolTip(_("Fullscreen")) + self.fullscreen_button.clicked.connect(self.on_fullscreen_clicked) + self.fullscreen_button.setEnabled(False) + layout.addWidget(self.fullscreen_button) + + return bar + + def on_show_presentation_clicked(self): + """Handle click on 'Show in new window' button""" + if self.presentation_window is None or not self.presentation_window.isVisible(): + #Create new presentation window + self.presentation_window = PresentationWindow(self) + self.presentation_window.show() + + #Enable fullscreen button + self.fullscreen_button.setEnabled(True) + + #Sync current content to presentation window + transcript_text = self.transcription_text_box.toPlainText() + if transcript_text: + self.presentation_window.update_transcripts(transcript_text) + + if self.transcription_options.enable_llm_translation: + translation_text = self.translation_text_box.toPlainText() + if translation_text: + self.presentation_window.update_translations(translation_text) + else: + #Window already open, bring to front + self.presentation_window.raise_() + self.presentation_window.activateWindow() + + def on_text_size_changed(self, value: int): + """Handle text size change""" + def save_settings(): + self.settings.set_value(Settings.Key.PRESENTATION_WINDOW_TEXT_SIZE, value) + if self.presentation_window: + # reload setting to apply new size + self.presentation_window.load_settings() + #Incase user drags slider, Debounce by waiting 100ms before saving + QTimer.singleShot(100, save_settings) + + def on_theme_changed(self, index: int): + """Handle theme selection change""" + theme = ["light", "dark", "custom"] + selected_theme = theme[index] + self.settings.set_value(Settings.Key.PRESENTATION_WINDOW_THEME, selected_theme) + + #Show/hide color buttons based on selection + if selected_theme == "custom": + self.text_color_button.show() + self.bg_color_button.show() + else: + self.text_color_button.hide() + self.bg_color_button.hide() + + # Apply theme to presentation window + if self.presentation_window: + self.presentation_window.load_settings() + + def on_text_color_clicked(self): + """Handle text color button click""" + + current_color = QColor( + self.settings.value( + Settings.Key.PRESENTATION_WINDOW_TEXT_COLOR, + "#000000" + ) + ) + + color = QColorDialog.getColor(current_color, self, _("Select Text Color")) + if color.isValid(): + color_hex = color.name() + self.settings.set_value(Settings.Key.PRESENTATION_WINDOW_TEXT_COLOR, color_hex) + if self.presentation_window: + self.presentation_window.load_settings() + + def on_bg_color_clicked(self): + """Handle background color button click""" + + current_color = QColor( + self.settings.value( + Settings.Key.PRESENTATION_WINDOW_BACKGROUND_COLOR, + "#FFFFFF" + ) + ) + + color = QColorDialog.getColor(current_color, self, _("Select Background Color")) + if color.isValid(): + color_hex = color.name() + self.settings.set_value(Settings.Key.PRESENTATION_WINDOW_BACKGROUND_COLOR, color_hex) + if self.presentation_window: + self.presentation_window.load_settings() + + def on_fullscreen_clicked(self): + """Handle fullscreen button click""" + if self.presentation_window: + self.presentation_window.toggle_fullscreen() + def setup_for_export(self): export_folder = self.settings.value( key=Settings.Key.RECORDING_TRANSCRIBER_EXPORT_FOLDER, @@ -276,9 +463,11 @@ class RecordingTranscriberWidget(QWidget): self.record_button.set_recording() self.transcription_options_group_box.setEnabled(False) self.audio_devices_combo_box.setEnabled(False) + self.presentation_options_bar.show() else: # RecordingStatus.RECORDING self.stop_recording() self.set_recording_status_stopped() + self.presentation_options_bar.hide() def start_recording(self): self.record_button.setDisabled(True) @@ -384,6 +573,7 @@ class RecordingTranscriberWidget(QWidget): self.current_status = self.RecordingStatus.STOPPED self.transcription_options_group_box.setEnabled(True) self.audio_devices_combo_box.setEnabled(True) + self.presentation_options_bar.hide() def on_download_model_error(self, error: str): self.reset_model_download() @@ -500,6 +690,12 @@ class RecordingTranscriberWidget(QWidget): elif self.transcriber_mode == RecordingTranscriberMode.APPEND_AND_CORRECT: self.process_transcription_merge(text, self.transcripts, self.transcription_text_box, self.transcript_export_file) + #Update presentation window if it is open + if self.presentation_window and self.presentation_window.isVisible(): + #Get current merged text from the translation box + current_text = self.transcription_text_box.toPlainText() + self.presentation_window.update_transcripts(current_text) + # Upload to server if self.upload_url: try: @@ -545,6 +741,10 @@ class RecordingTranscriberWidget(QWidget): elif self.transcriber_mode == RecordingTranscriberMode.APPEND_AND_CORRECT: self.process_transcription_merge(text, self.translations, self.translation_text_box, self.translation_export_file) + if self.presentation_window and self.presentation_window.isVisible(): + current_translation = self.translation_text_box.toPlainText() + self.presentation_window.update_translations(current_translation) + # Upload to server if self.upload_url: try: @@ -612,6 +812,13 @@ class RecordingTranscriberWidget(QWidget): self.audio_meter_widget.update_amplitude(amplitude) def closeEvent(self, event: QCloseEvent) -> None: + #Close presentation window if open + if self.presentation_window: + self.presentation_window.close() + self.presentation_window = None + + self.fullscreen_button.setEnabled(False) + if self.model_loader is not None: self.model_loader.cancel() diff --git a/pytest.ini b/pytest.ini index 0ad2fec7..701a9fff 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,7 +5,7 @@ qt_api=pyqt6 log_format = %(asctime)s %(levelname)s %(module)s::%(funcName)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S addopts = -x -s -p no:xdist -p no:pytest_parallel -timeout = 600 +timeout = 900 timeout_method = thread testpaths = tests markers = diff --git a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml index 988beeab..0491541d 100644 --- a/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml +++ b/share/metainfo/io.github.chidiwilliams.Buzz.metainfo.xml @@ -64,14 +64,15 @@ - + https://github.com/chidiwilliams/buzz/releases/tag/v1.4.0 -

    Adding speaker identification on transcriptions and video support for transcription viewer, improvements to transcription table and support for over 1000 of worlds languages via MMS models.

    +

    Adding speaker identification on transcriptions and video support for transcription viewer, improvements to transcription table and support for over 1000 of worlds languages via MMS models as well as separate window to show live transcripts on a projector.

    Release details:

    • Speaker identification on finished transcripts
    • Support for video in transcription viewer
    • +
    • Presentation (projector) window for live transcripts
    • Ability to add notes and restart transcriptions in main table
    • Adding support for more than 1000 languages via MMS model family when transcribing with Huggingface transcription type
    • Adding support for PEFT models when transcribing with Huggingface transcription type
    • diff --git a/tests/widgets/presentation_window_test.py b/tests/widgets/presentation_window_test.py new file mode 100644 index 00000000..2e224272 --- /dev/null +++ b/tests/widgets/presentation_window_test.py @@ -0,0 +1,324 @@ +import os +import pytest +import tempfile + +from unittest.mock import patch, MagicMock +from pytestqt.qtbot import QtBot +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QKeyEvent + +from buzz.widgets.presentation_window import PresentationWindow +from buzz.settings.settings import Settings +from buzz.locale import _ + +class TestPresentationWindow: + def test_should_set_window_title(self, qtbot: QtBot): + """Test that the window title is set correctly""" + window = PresentationWindow() + qtbot.add_widget(window) + + assert _("Live Transcript Presentation") in window.windowTitle() + window.close() + + def test_should_have_window_flag(self, qtbot: QtBot): + """Test that window has the Window flag set""" + window = PresentationWindow() + qtbot.add_widget(window) + + assert window.windowFlags() & Qt.WindowType.Window + window.close() + + def test_should_have_transcript_display(self, qtbot: QtBot): + """Test that the transcript display is created""" + window = PresentationWindow() + qtbot.add_widget(window) + + assert window.transcript_display is not None + assert window.transcript_display.isReadOnly() + window.close() + + def test_should_have_translation_display_hidden(self, qtbot: QtBot): + """Test that the translation display is created but hidden initially""" + window = PresentationWindow() + qtbot.add_widget(window) + + assert window.translation_display is not None + assert window.translation_display.isReadOnly() + assert not window.translation_display.isVisible() + window.close() + + def test_should_have_default_size(self, qtbot: QtBot): + """Test that the window has default size""" + window = PresentationWindow() + qtbot.add_widget(window) + + assert window.width() == 800 + assert window.height() == 600 + window.close() + + +class TestPresentationWindowUpdateTranscripts: + def test_update_transcripts_with_text(self, qtbot: QtBot): + """Test updating transcripts with text""" + window = PresentationWindow() + qtbot.add_widget(window) + + window.update_transcripts("Hello world") + + assert window._current_transcript == "Hello world" + assert "Hello world" in window.transcript_display.toHtml() + window.close() + + def test_update_transcripts_with_empty_text(self, qtbot: QtBot): + """Test that empty text does not update the display""" + window = PresentationWindow() + qtbot.add_widget(window) + + window.update_transcripts("") + + assert window._current_transcript == "" + window.close() + + def test_update_transcripts_escapes_html(self, qtbot: QtBot): + """Test that special HTML characters are escaped""" + window = PresentationWindow() + qtbot.add_widget(window) + + window.update_transcripts("") + + html = window.transcript_display.toHtml() + assert "