diff --git a/buzz/file_transcriber_queue_worker.py b/buzz/file_transcriber_queue_worker.py index 8e5cf1a8..16935f9a 100644 --- a/buzz/file_transcriber_queue_worker.py +++ b/buzz/file_transcriber_queue_worker.py @@ -59,6 +59,7 @@ from buzz.transcriber.file_transcriber import FileTranscriber from buzz.transcriber.openai_whisper_api_file_transcriber import ( OpenAIWhisperAPIFileTranscriber, ) +from buzz.transcriber.speechmatics_file_transcriber import SpeechmaticsFileTranscriber from buzz.transcriber.transcriber import FileTranscriptionTask, Segment from buzz.transcriber.whisper_file_transcriber import WhisperFileTranscriber @@ -173,6 +174,10 @@ class FileTranscriberQueueWorker(QObject): self.current_transcriber = OpenAIWhisperAPIFileTranscriber( task=self.current_task ) + elif model_type == ModelType.SPEECHMATICS: + self.current_transcriber = SpeechmaticsFileTranscriber( + task=self.current_task + ) elif ( model_type == ModelType.WHISPER_CPP or model_type == ModelType.HUGGING_FACE diff --git a/buzz/model_loader.py b/buzz/model_loader.py index 224bd6d3..aa358544 100644 --- a/buzz/model_loader.py +++ b/buzz/model_loader.py @@ -162,6 +162,7 @@ class ModelType(enum.Enum): HUGGING_FACE = "Hugging Face" FASTER_WHISPER = "Faster Whisper" OPEN_AI_WHISPER_API = "OpenAI Whisper API" + SPEECHMATICS = "Speechmatics" @property def supports_initial_prompt(self): @@ -170,7 +171,7 @@ class ModelType(enum.Enum): ModelType.WHISPER_CPP, ModelType.OPEN_AI_WHISPER_API, ModelType.FASTER_WHISPER, - ) + ) # SPEECHMATICS does not use an initial prompt def is_available(self): if ( @@ -187,7 +188,7 @@ class ModelType(enum.Enum): ModelType.WHISPER, ModelType.WHISPER_CPP, ModelType.FASTER_WHISPER, - ) + ) # SPEECHMATICS is cloud-only, no local model HUGGING_FACE_MODEL_ALLOW_PATTERNS = [ @@ -307,6 +308,8 @@ class TranscriptionModel: return f"Faster Whisper ({self.whisper_model_size})" case ModelType.OPEN_AI_WHISPER_API: return "OpenAI Whisper API" + case ModelType.SPEECHMATICS: + return "Speechmatics" case _: raise Exception("Unknown model type") @@ -424,6 +427,9 @@ class TranscriptionModel: if self.model_type == ModelType.OPEN_AI_WHISPER_API: return "" + if self.model_type == ModelType.SPEECHMATICS: + return "" # Cloud-only; no local model path needed + if self.model_type == ModelType.HUGGING_FACE: try: return huggingface_hub.snapshot_download( @@ -780,6 +786,10 @@ class ModelDownloader(QRunnable): self.signals.finished.emit("") return + if self.model.model_type == ModelType.SPEECHMATICS: + self.signals.finished.emit("") # Cloud-only; no local model to download + return + raise Exception("Invalid model type: " + self.model.model_type.value) def download_model_to_path( diff --git a/buzz/settings/settings.py b/buzz/settings/settings.py index ac4c3603..8a417f15 100644 --- a/buzz/settings/settings.py +++ b/buzz/settings/settings.py @@ -49,6 +49,8 @@ class Settings: OPENAI_API_MODEL = "transcriber/openai-api-model" CUSTOM_FASTER_WHISPER_ID = "transcriber/custom-faster-whisper-id" HUGGINGFACE_MODEL_ID = "transcriber/huggingface-model-id" + CUSTOM_SPEECHMATICS_URL = "transcriber/custom-speechmatics-url" + SPEECHMATICS_API_KEY = "transcriber/speechmatics-api-key" SHORTCUTS = "shortcuts" diff --git a/buzz/transcriber/speechmatics_file_transcriber.py b/buzz/transcriber/speechmatics_file_transcriber.py new file mode 100644 index 00000000..d8847e05 --- /dev/null +++ b/buzz/transcriber/speechmatics_file_transcriber.py @@ -0,0 +1,199 @@ +import asyncio +import logging +import time +from typing import Optional, List + +from PyQt6.QtCore import QObject +from speechmatics.batch._models import Transcript + +from buzz.settings.settings import Settings +from buzz.transcriber.file_transcriber import FileTranscriber +from buzz.transcriber.transcriber import FileTranscriptionTask, Segment, Stopped + + +class SpeechmaticsFileTranscriber(FileTranscriber): + """Batch transcription via the Speechmatics cloud API (speechmatics-batch package).""" + + def __init__(self, task: FileTranscriptionTask, parent: Optional["QObject"] = None): + super().__init__(task=task, parent=parent) + self._stopped = False + settings = Settings() + self.custom_speechmatics_url = settings.value( + key=Settings.Key.CUSTOM_SPEECHMATICS_URL, default_value="" + ) + logging.debug("Will use speechmatics API on %s", self.custom_speechmatics_url) + + def transcribe(self) -> List[Segment]: + api_key = self.transcription_task.transcription_options.speechmatics_access_token + if not api_key: + raise Exception( + "Speechmatics API key is not set. Please enter your API key in the settings." + ) + + language = self.transcription_task.transcription_options.language or "auto" + file_path = self.transcription_task.file_path + identify_speaker = self.transcription_task.transcription_options.identify_speaker + + logging.debug( + "Starting Speechmatics batch transcription: file=%s, language=%s", + file_path, + language, + ) + + self.progress.emit((0, 100)) + + return asyncio.run(self._run_transcription(api_key, language, file_path, identify_speaker)) + + async def _run_transcription( + self, api_key: str, language: str, file_path: str, identify_speaker: bool + ) -> List[Segment]: + from speechmatics.batch import ( + AsyncClient, + JobConfig, + JobType, + JobStatus, + TranscriptionConfig, + FormatType, + ) + + async with AsyncClient( + api_key=api_key, + url=self.custom_speechmatics_url if self.custom_speechmatics_url else None, + ) as client: + config = JobConfig( + type=JobType.TRANSCRIPTION, + transcription_config=TranscriptionConfig(language=language), + ) + + logging.debug("Submitting Speechmatics job for file: %s", file_path) + job = await client.submit_job(file_path, config=config) + logging.debug("Speechmatics job submitted: id=%s", job.id) + + self.progress.emit((10, 100)) + + # Poll until complete or stopped + poll_interval = 3.0 + while True: + if self._stopped: + logging.debug("Speechmatics transcription stopped by user") + raise Stopped() + + job_info = await client.get_job_info(job.id) + status = job_info.status + logging.debug("Speechmatics job %s status: %s", job.id, status) + + if status == JobStatus.DONE: + break + elif status in (JobStatus.REJECTED, JobStatus.DELETED): + raise Exception( + f"Speechmatics job failed with status: {status.value}" + ) + + # Emit a rough progress update while waiting + self.progress.emit((20, 100)) + await asyncio.sleep(poll_interval) + + # Retrieve JSON transcript + logging.debug("Retrieving Speechmatics transcript for job: %s", job.id) + self.progress.emit((90, 100)) + result = await client.get_transcript(job.id, format_type=FormatType.JSON) + + segments = self._parse_segments(result, identify_speaker) + self.progress.emit((100, 100)) + logging.debug( + "Speechmatics transcription complete: %d segments", len(segments) + ) + return segments + + def _parse_segments(self, transcript: Transcript, identify_speaker: bool) -> List[Segment]: + """Parse Speechmatics JSON result into Segment objects. + + The JSON-v2 result has a `results` list where each item has: + - start_time (float, seconds) + - end_time (float, seconds) + - alternatives: list of dicts with 'content' key + - type: 'word' | 'punctuation' + We group consecutive words/punctuation into sentence-level segments + by collapsing them all into a single segment per result item. + """ + if not transcript.results: + return [] + + # Get language pack info for word delimiter + word_delimiter = " " # Default + if transcript.metadata and transcript.metadata.language_pack_info and "word_delimiter" in transcript.metadata.language_pack_info: + word_delimiter = transcript.metadata.language_pack_info["word_delimiter"] + + # Group results by speaker and process + segments = [] + current_speaker = None + current_start = None + current_end = None + current_group: list[str] = [] + prev_eos = "word" + + for result in transcript.results: + if not result.alternatives: + continue + + alternative = result.alternatives[0] + content = alternative.content + speaker = alternative.speaker + + # Handle speaker changes, end of sentence or if segment is too long + if speaker != current_speaker or prev_eos or (current_end - current_start > 60): + # Process accumulated group for previous speaker + if current_group: + text = transcript._join_content_items(current_group, word_delimiter) + if current_speaker: + text = f"{current_speaker}: {text}" if identify_speaker else text + segments.append( + Segment( + start=int(current_start * 1000), + end=int(current_end * 1000), + text=text, + ) + ) + else: + segments.append(Segment( + start=int(current_start * 1000), + end=int(current_end * 1000), + text=text, + )) + current_group = [] + current_start = None + + current_speaker = speaker + + # Add content to current group + if content: + current_group.append(content) + prev_eos = result.type == "punctuation" and content in ".?!" + + if current_start is None: + current_start = result.start_time + current_end = result.end_time + + # Process final group + if current_group: + text = transcript._join_content_items(current_group, word_delimiter) + if current_speaker: + text = f"{current_speaker}: {text}" if identify_speaker else text + segments.append( + Segment( + start=int(current_start * 1000), + end=int(current_end * 1000), + text=text, + ) + ) + else: + segments.append(Segment( + start=int(current_start * 1000), + end=int(current_end * 1000), + text=text, + )) + + return segments + + def stop(self): + self._stopped = True diff --git a/buzz/transcriber/transcriber.py b/buzz/transcriber/transcriber.py index 4ba82786..e5b1a2ab 100644 --- a/buzz/transcriber/transcriber.py +++ b/buzz/transcriber/transcriber.py @@ -150,6 +150,10 @@ class TranscriptionOptions: openai_access_token: str = field( default="", metadata=config(exclude=Exclude.ALWAYS) ) + speechmatics_access_token: str = field( + default="", metadata=config(exclude=Exclude.ALWAYS) + ) + identify_speaker: bool = False enable_llm_translation: bool = False llm_prompt: str = "" llm_model: str = "" diff --git a/buzz/widgets/preferences_dialog/general_preferences_widget.py b/buzz/widgets/preferences_dialog/general_preferences_widget.py index af569091..8d633c2a 100644 --- a/buzz/widgets/preferences_dialog/general_preferences_widget.py +++ b/buzz/widgets/preferences_dialog/general_preferences_widget.py @@ -137,6 +137,18 @@ class GeneralPreferencesWidget(QWidget): self.openai_api_model_line_edit.setPlaceholderText("whisper-1") layout.addRow(_("OpenAI API model"), self.openai_api_model_line_edit) + self.custom_speechmatics_url = self.settings.value( + key=Settings.Key.CUSTOM_SPEECHMATICS_URL, default_value="" + ) + + self.custom_speechmatics_url_line_edit = LineEdit(self.custom_speechmatics_url, self) + self.custom_speechmatics_url_line_edit.textChanged.connect( + self.on_custom_speechmatics_url_changed + ) + self.custom_speechmatics_url_line_edit.setMinimumWidth(200) + self.custom_speechmatics_url_line_edit.setPlaceholderText("https://asr.api.speechmatics.com/v2") + layout.addRow(_("Speechmatics base url"), self.custom_speechmatics_url_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) @@ -263,6 +275,9 @@ class GeneralPreferencesWidget(QWidget): def on_openai_api_model_changed(self, text: str): self.settings.set_value(Settings.Key.OPENAI_API_MODEL, text) + def on_custom_speechmatics_url_changed(self, text: str): + self.settings.set_value(Settings.Key.CUSTOM_SPEECHMATICS_URL, text) + def on_recording_export_enable_changed(self, state: int): self.recording_export_enabled = state == 2 diff --git a/buzz/widgets/transcriber/transcription_options_group_box.py b/buzz/widgets/transcriber/transcription_options_group_box.py index f3c124d8..ef6cf550 100644 --- a/buzz/widgets/transcriber/transcription_options_group_box.py +++ b/buzz/widgets/transcriber/transcription_options_group_box.py @@ -3,9 +3,9 @@ import logging import platform from typing import Optional, List -from PyQt6.QtCore import pyqtSignal, QLocale +from PyQt6.QtCore import pyqtSignal, Qt, QLocale from PyQt6.QtGui import QIcon -from PyQt6.QtWidgets import QGroupBox, QWidget, QFormLayout, QComboBox, QLabel, QHBoxLayout +from PyQt6.QtWidgets import QGroupBox, QWidget, QFormLayout, QComboBox, QLabel, QHBoxLayout, QLineEdit, QCheckBox from buzz.locale import _ from buzz.settings.settings import Settings @@ -98,6 +98,31 @@ class TranscriptionOptionsGroupBox(QGroupBox): self.mms_language_line_edit.languageChanged.connect(self.on_mms_language_changed) self.mms_language_line_edit.setVisible(False) + # Speechmatics API key field + speechmatics_api_key = self.settings.value( + Settings.Key.SPEECHMATICS_API_KEY, default_value="" + ) + # Pre-populate from settings if present, or from passed-in options + if speechmatics_api_key: + self.transcription_options.speechmatics_access_token = speechmatics_api_key + self.speechmatics_api_key_edit = QLineEdit(self) + self.speechmatics_api_key_edit.setPlaceholderText(_("Enter Speechmatics API key")) + self.speechmatics_api_key_edit.setEchoMode(QLineEdit.EchoMode.Password) + self.speechmatics_api_key_edit.setText( + self.transcription_options.speechmatics_access_token + ) + self.speechmatics_api_key_edit.textChanged.connect( + self.on_speechmatics_api_key_changed + ) + + self.identify_speaker_checkbox = QCheckBox( + "Identify Speaker", parent=self + ) + self.identify_speaker_checkbox.setChecked(self.transcription_options.identify_speaker) + self.identify_speaker_checkbox.stateChanged.connect( + self.on_identify_speaker_changed + ) + self.advanced_settings_button = AdvancedSettingsButton(self) self.advanced_settings_button.clicked.connect(self.open_advanced_settings) @@ -127,6 +152,8 @@ class TranscriptionOptionsGroupBox(QGroupBox): 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.form_layout.addRow(_("Api Key:"), self.speechmatics_api_key_edit) + self.form_layout.addRow("", self.identify_speaker_checkbox) self.reset_visible_rows() @@ -138,6 +165,15 @@ class TranscriptionOptionsGroupBox(QGroupBox): self.transcription_options.openai_access_token = access_token self.transcription_options_changed.emit(self.transcription_options) + def on_speechmatics_api_key_changed(self, api_key: str): + self.transcription_options.speechmatics_access_token = api_key + self.settings.set_value(Settings.Key.SPEECHMATICS_API_KEY, api_key) + self.transcription_options_changed.emit(self.transcription_options) + + def on_identify_speaker_changed(self, state: int): + self.transcription_options.identify_speaker = state == Qt.CheckState.Checked.value + self.transcription_options_changed.emit(self.transcription_options) + def on_language_changed(self, language: str): if language == "": language = None @@ -243,6 +279,20 @@ class TranscriptionOptionsGroupBox(QGroupBox): self.openai_access_token_edit, model_type == ModelType.OPEN_AI_WHISPER_API ) + self.form_layout.setRowVisible( + self.speechmatics_api_key_edit, model_type == ModelType.SPEECHMATICS + ) + + self.form_layout.setRowVisible( + self.identify_speaker_checkbox, model_type == ModelType.SPEECHMATICS + ) + + # Hide task/model-size controls for Speechmatics (cloud handles it) + self.form_layout.setRowVisible( + self.tasks_combo_box, + model_type != ModelType.SPEECHMATICS, + ) + # Note on Apple Silicon Macs if self.load_note_tooltip_icon is not None: self.load_note_tooltip_icon.setVisible( diff --git a/pyproject.toml b/pyproject.toml index 789d4818..a031cfd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ dependencies = [ "posthog>=3.23.0,<4", # This version works, newer have issues on Windows "onnxruntime==1.18.1", - "onnx>=1.20.0", # Required for nemo-toolkit, ensures ml-dtypes is installed + "onnx>=1.20.0", # Required for nemo-toolkit, ensures ml-dtypes is installed "vulkan>=1.3.275.1,<2", "hf-xet>=1.1.5,<2", "hatchling>=1.28.0", @@ -85,6 +85,7 @@ dependencies = [ "torch>=2.2.2", "torchaudio>=2.2.2", "datasets>=4.4.1", + "speechmatics-batch>=0.4.5", ] repository = "https://github.com/chidiwilliams/buzz" documentation = "https://chidiwilliams.github.io/buzz/docs" diff --git a/uv.lock b/uv.lock index 88630000..39ee0343 100644 --- a/uv.lock +++ b/uv.lock @@ -1,8 +1,9 @@ version = 1 -revision = 3 +revision = 2 requires-python = "==3.12.*" resolution-markers = [ - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", + "platform_machine != 'aarch64' and sys_platform == 'linux'", + "platform_machine == 'aarch64' and 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'", @@ -45,6 +46,15 @@ wheels = [ { 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]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -233,7 +243,8 @@ name = "bitsandbytes" 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')", + "platform_machine != 'aarch64' and sys_platform == 'linux'", + "platform_machine == 'aarch64' and 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'", ] @@ -324,6 +335,7 @@ dependencies = [ { name = "requests" }, { name = "sounddevice" }, { name = "soundfile" }, + { name = "speechmatics-batch" }, { name = "srt-equalizer" }, { name = "stable-ts" }, { name = "submitit" }, @@ -331,9 +343,9 @@ dependencies = [ { 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://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' 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 = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' 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" }, @@ -415,6 +427,7 @@ requires-dist = [ { name = "requests", specifier = ">=2.31.0,<3" }, { name = "sounddevice", specifier = ">=0.5.3,<0.6" }, { name = "soundfile", specifier = ">=0.13.1,<0.14" }, + { name = "speechmatics-batch", specifier = ">=0.4.5" }, { name = "srt-equalizer", specifier = ">=0.1.10,<0.2" }, { name = "stable-ts", specifier = ">=2.19.1,<3" }, { name = "submitit", specifier = ">=1.5.2,<2" }, @@ -714,7 +727,8 @@ name = "ctranslate2" version = "4.6.2" source = { registry = "https://pypi.org/simple/" } resolution-markers = [ - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux')", + "platform_machine != 'aarch64' and sys_platform == 'linux'", + "platform_machine == 'aarch64' and 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'", @@ -1132,6 +1146,7 @@ wheels = [ { 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" }, @@ -2375,7 +2390,7 @@ name = "nvidia-cufft-cu12" 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')" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ { 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" }, @@ -2402,9 +2417,9 @@ name = "nvidia-cusolver-cu12" 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')" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ { 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" }, @@ -2415,7 +2430,7 @@ name = "nvidia-cusparse-cu12" 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')" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ { 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" }, @@ -2552,9 +2567,9 @@ dependencies = [ { 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://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' 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 = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' 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" } @@ -3790,6 +3805,19 @@ wheels = [ { 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 = "speechmatics-batch" +version = "0.4.5" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiohttp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/f5/68239c57174972ed0b717b141e256881d9fad50c2cb230a0140650f38bd6/speechmatics_batch-0.4.5.tar.gz", hash = "sha256:02328a567eceaecb6791f0b80b912c7b6f95a0a403aeab2bc3b7ff9933ed47a6", size = 22224, upload-time = "2026-02-02T15:34:41.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/f2/951d12504d371909804995c57ab7c96ad3c0b3a2f3148bef2f69991303a2/speechmatics_batch-0.4.5-py3-none-any.whl", hash = "sha256:0303d86cf3e17186fef8ca38018f17099063e318b9965e81ee90ccbd7fdef4c8", size = 22868, upload-time = "2026-02-02T15:34:39.303Z" }, +] + [[package]] name = "sqlalchemy" version = "2.0.44" @@ -3840,9 +3868,9 @@ dependencies = [ { 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://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' 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 = "torchaudio", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' 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" } @@ -4105,7 +4133,8 @@ name = "torch" 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')", + "platform_machine != 'aarch64' and sys_platform == 'linux'", + "platform_machine == 'aarch64' and 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'", ] @@ -4158,10 +4187,11 @@ name = "torchaudio" 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'", "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", ] dependencies = [ - { 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'" }, + { name = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://download.pytorch.org/whl/cu129/torchaudio-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:67d4f6cbfcb795157850db6a3735301b929cdfe2b56032dc3365105b49a4ee84" }, @@ -4187,11 +4217,11 @@ name = "torchaudio" 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')", + "platform_machine != 'aarch64' and sys_platform == 'linux'", "sys_platform != 'darwin' and sys_platform != 'linux'", ] dependencies = [ - { 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 = "torch", version = "2.8.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "(platform_machine != 'aarch64' 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" },