Add Speechmatics API as transcriber

This commit is contained in:
Nihczsche 2026-02-27 21:21:15 +08:00
commit 296fe41201
9 changed files with 340 additions and 24 deletions

View file

@ -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

View file

@ -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(

View file

@ -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"

View file

@ -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

View file

@ -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 = ""

View file

@ -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

View file

@ -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(

View file

@ -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"

68
uv.lock generated
View file

@ -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" },