voxhelm 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- config/__init__.py +1 -0
- config/asgi.py +7 -0
- config/settings.py +425 -0
- config/urls.py +19 -0
- config/wsgi.py +7 -0
- jobs/__init__.py +1 -0
- jobs/apps.py +6 -0
- jobs/artifacts.py +233 -0
- jobs/media.py +190 -0
- jobs/migrations/0001_initial.py +67 -0
- jobs/migrations/0002_alter_job_job_type_alter_jobartifact_kind.py +23 -0
- jobs/migrations/0003_job_dispatch_mode_job_operator_and_more.py +35 -0
- jobs/migrations/0004_stagedmedia.py +48 -0
- jobs/migrations/0005_rename_jobs_staged_producer_76b546_idx_jobs_staged_produce_cbe898_idx_and_more.py +23 -0
- jobs/migrations/0006_alter_jobartifact_kind.py +18 -0
- jobs/migrations/0007_remote_workers.py +102 -0
- jobs/migrations/__init__.py +0 -0
- jobs/models.py +176 -0
- jobs/remote_worker_cli.py +985 -0
- jobs/remote_workers.py +1293 -0
- jobs/services.py +1510 -0
- jobs/staging.py +162 -0
- jobs/tasks.py +15 -0
- jobs/urls.py +25 -0
- jobs/views.py +184 -0
- lane_scheduler.py +294 -0
- operators/__init__.py +1 -0
- operators/apps.py +6 -0
- operators/forms.py +65 -0
- operators/management/__init__.py +1 -0
- operators/management/commands/__init__.py +1 -0
- operators/management/commands/bootstrap_operator.py +56 -0
- operators/templates/operators/root.html +302 -0
- operators/views.py +224 -0
- synthesis/__init__.py +1 -0
- synthesis/service.py +324 -0
- synthesis/views.py +139 -0
- transcriptions/__init__.py +1 -0
- transcriptions/apps.py +6 -0
- transcriptions/diarization.py +366 -0
- transcriptions/errors.py +15 -0
- transcriptions/formats.py +153 -0
- transcriptions/input_media.py +112 -0
- transcriptions/known_speaker.py +666 -0
- transcriptions/observability.py +101 -0
- transcriptions/service.py +663 -0
- transcriptions/views.py +214 -0
- transcriptions/wyoming.py +424 -0
- voxhelm-0.1.0.dist-info/METADATA +425 -0
- voxhelm-0.1.0.dist-info/RECORD +52 -0
- voxhelm-0.1.0.dist-info/WHEEL +4 -0
- voxhelm-0.1.0.dist-info/entry_points.txt +3 -0
config/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
config/asgi.py
ADDED
config/settings.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
8
|
+
VOXHELM_OPERATOR_PRODUCER_LABEL = "__operator_ui__"
|
|
9
|
+
VOXHELM_RESERVED_BEARER_TOKEN_LABELS = {VOXHELM_OPERATOR_PRODUCER_LABEL}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def env_list(name: str, *, default: str = "") -> list[str]:
|
|
13
|
+
raw = os.getenv(name, default)
|
|
14
|
+
return [item.strip() for item in raw.replace("\n", ",").split(",") if item.strip()]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def env_bool(name: str, *, default: bool = False) -> bool:
|
|
18
|
+
raw = os.getenv(name)
|
|
19
|
+
if raw is None:
|
|
20
|
+
return default
|
|
21
|
+
return raw.lower() in {"1", "true", "yes", "on"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def env_map(name: str) -> dict[str, str]:
|
|
25
|
+
raw = os.getenv(name, "").strip()
|
|
26
|
+
if not raw:
|
|
27
|
+
return {}
|
|
28
|
+
entries = (
|
|
29
|
+
entry.split("=", 1) for entry in raw.replace("\n", ",").split(",") if entry.strip()
|
|
30
|
+
)
|
|
31
|
+
return {
|
|
32
|
+
key.strip(): value.strip()
|
|
33
|
+
for key, value in entries
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def env_tokens(name: str) -> dict[str, str]:
|
|
38
|
+
raw = os.getenv(name, "").strip()
|
|
39
|
+
if not raw:
|
|
40
|
+
return {}
|
|
41
|
+
if raw.startswith("{"):
|
|
42
|
+
parsed = json.loads(raw)
|
|
43
|
+
if not isinstance(parsed, dict):
|
|
44
|
+
raise ValueError(f"{name} must be a JSON object when JSON syntax is used.")
|
|
45
|
+
return validate_bearer_token_labels(
|
|
46
|
+
name,
|
|
47
|
+
{str(key).strip(): str(value).strip() for key, value in parsed.items()},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
tokens: dict[str, str] = {}
|
|
51
|
+
for entry in raw.replace("\n", ",").split(","):
|
|
52
|
+
normalized = entry.strip()
|
|
53
|
+
if not normalized:
|
|
54
|
+
continue
|
|
55
|
+
if "=" not in normalized:
|
|
56
|
+
raise ValueError(f"Invalid {name} entry '{normalized}'. Use label=token pairs.")
|
|
57
|
+
label, token = normalized.split("=", 1)
|
|
58
|
+
tokens[label.strip()] = token.strip()
|
|
59
|
+
return validate_bearer_token_labels(name, tokens)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def validate_unique_token_values(name: str, tokens: dict[str, str]) -> dict[str, str]:
|
|
63
|
+
seen: dict[str, str] = {}
|
|
64
|
+
duplicates: list[str] = []
|
|
65
|
+
for label, token in tokens.items():
|
|
66
|
+
if token in seen:
|
|
67
|
+
duplicates.append(f"{seen[token]} and {label}")
|
|
68
|
+
seen[token] = label
|
|
69
|
+
if duplicates:
|
|
70
|
+
joined = ", ".join(duplicates)
|
|
71
|
+
raise ValueError(f"{name} maps one bearer token to multiple labels: {joined}.")
|
|
72
|
+
return tokens
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def validate_disjoint_token_values(
|
|
76
|
+
left_name: str,
|
|
77
|
+
left_tokens: dict[str, str],
|
|
78
|
+
right_name: str,
|
|
79
|
+
right_tokens: dict[str, str],
|
|
80
|
+
) -> dict[str, str]:
|
|
81
|
+
left_labels_by_token = {token: label for label, token in left_tokens.items()}
|
|
82
|
+
overlaps = [
|
|
83
|
+
f"{left_labels_by_token[token]} and {label}"
|
|
84
|
+
for label, token in right_tokens.items()
|
|
85
|
+
if token in left_labels_by_token
|
|
86
|
+
]
|
|
87
|
+
if overlaps:
|
|
88
|
+
joined = ", ".join(overlaps)
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"{left_name} and {right_name} must not share bearer token values: {joined}."
|
|
91
|
+
)
|
|
92
|
+
return right_tokens
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def validate_bearer_token_labels(name: str, tokens: dict[str, str]) -> dict[str, str]:
|
|
96
|
+
empty_labels = [label for label in tokens if not label]
|
|
97
|
+
if empty_labels:
|
|
98
|
+
raise ValueError(f"{name} contains an empty label.")
|
|
99
|
+
empty_token_labels = sorted(label for label, token in tokens.items() if not token)
|
|
100
|
+
if empty_token_labels:
|
|
101
|
+
labels = ", ".join(empty_token_labels)
|
|
102
|
+
raise ValueError(f"{name} contains empty token value(s) for label(s): {labels}.")
|
|
103
|
+
reserved = sorted(VOXHELM_RESERVED_BEARER_TOKEN_LABELS.intersection(tokens))
|
|
104
|
+
if reserved:
|
|
105
|
+
labels = ", ".join(reserved)
|
|
106
|
+
raise ValueError(f"{name} contains reserved label(s): {labels}.")
|
|
107
|
+
return tokens
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def validate_positive_int(name: str, value: int) -> int:
|
|
111
|
+
if value <= 0:
|
|
112
|
+
raise ValueError(f"{name} must be a positive integer.")
|
|
113
|
+
return value
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
REMOTE_PULL_SHARED_ARTIFACT_BACKENDS = {"s3"}
|
|
117
|
+
TRANSCRIPTION_EXECUTION_MODES = {"django_tasks", "remote_pull"}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def validate_transcription_execution_mode(mode: str) -> str:
|
|
121
|
+
if mode not in TRANSCRIPTION_EXECUTION_MODES:
|
|
122
|
+
accepted = ", ".join(sorted(TRANSCRIPTION_EXECUTION_MODES))
|
|
123
|
+
raise ValueError(f"VOXHELM_TRANSCRIPTION_EXECUTION_MODE must be one of: {accepted}.")
|
|
124
|
+
return mode
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_remote_pull_worker_tokens(
|
|
128
|
+
execution_mode: str,
|
|
129
|
+
worker_tokens: dict[str, str],
|
|
130
|
+
) -> None:
|
|
131
|
+
if execution_mode == "remote_pull" and not worker_tokens:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
"VOXHELM_TRANSCRIPTION_EXECUTION_MODE=remote_pull requires "
|
|
134
|
+
"VOXHELM_WORKER_TOKENS to configure at least one worker token."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def validate_remote_pull_artifact_backend(execution_mode: str, artifact_backend: str) -> None:
|
|
139
|
+
if execution_mode != "remote_pull":
|
|
140
|
+
return
|
|
141
|
+
if artifact_backend in REMOTE_PULL_SHARED_ARTIFACT_BACKENDS:
|
|
142
|
+
return
|
|
143
|
+
accepted = ", ".join(sorted(REMOTE_PULL_SHARED_ARTIFACT_BACKENDS))
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"VOXHELM_TRANSCRIPTION_EXECUTION_MODE=remote_pull requires "
|
|
146
|
+
f"VOXHELM_ARTIFACT_BACKEND to be one of: {accepted}."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def validate_remote_pull_s3_configuration(
|
|
151
|
+
execution_mode: str,
|
|
152
|
+
artifact_backend: str,
|
|
153
|
+
values: dict[str, str],
|
|
154
|
+
) -> None:
|
|
155
|
+
if execution_mode != "remote_pull" or artifact_backend != "s3":
|
|
156
|
+
return
|
|
157
|
+
missing = sorted(name for name, value in values.items() if not value)
|
|
158
|
+
if missing:
|
|
159
|
+
joined = ", ".join(missing)
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"VOXHELM_TRANSCRIPTION_EXECUTION_MODE=remote_pull requires complete "
|
|
162
|
+
f"S3 artifact configuration: {joined}."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_accepted_stt_models() -> set[str]:
|
|
167
|
+
from django.conf import settings as django_settings
|
|
168
|
+
|
|
169
|
+
models = {
|
|
170
|
+
"gpt-4o-mini-transcribe",
|
|
171
|
+
"whisper-1",
|
|
172
|
+
django_settings.VOXHELM_MLX_MODEL,
|
|
173
|
+
django_settings.VOXHELM_WHISPERCPP_MODEL,
|
|
174
|
+
}
|
|
175
|
+
if django_settings.VOXHELM_WHISPERKIT_ENABLED:
|
|
176
|
+
models.update({"whisperkit", django_settings.VOXHELM_WHISPERKIT_MODEL})
|
|
177
|
+
return models
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def get_batch_accepted_stt_models() -> set[str]:
|
|
181
|
+
return {"auto", *get_accepted_stt_models()}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "dev-only-secret-key")
|
|
185
|
+
DEBUG = os.getenv("DJANGO_DEBUG", "").lower() in {"1", "true", "yes", "on"}
|
|
186
|
+
ALLOWED_HOSTS = env_list("VOXHELM_ALLOWED_HOSTS", default="localhost,127.0.0.1")
|
|
187
|
+
CSRF_TRUSTED_ORIGINS = env_list("VOXHELM_CSRF_TRUSTED_ORIGINS")
|
|
188
|
+
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
189
|
+
ROOT_URLCONF = "config.urls"
|
|
190
|
+
WSGI_APPLICATION = "config.wsgi.application"
|
|
191
|
+
ASGI_APPLICATION = "config.asgi.application"
|
|
192
|
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
193
|
+
|
|
194
|
+
INSTALLED_APPS = [
|
|
195
|
+
"django.contrib.auth",
|
|
196
|
+
"django.contrib.contenttypes",
|
|
197
|
+
"django.contrib.sessions",
|
|
198
|
+
"django_tasks",
|
|
199
|
+
"django_tasks_db",
|
|
200
|
+
"operators",
|
|
201
|
+
"transcriptions",
|
|
202
|
+
"jobs",
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
MIDDLEWARE = [
|
|
206
|
+
"django.middleware.security.SecurityMiddleware",
|
|
207
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
208
|
+
"django.middleware.common.CommonMiddleware",
|
|
209
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
210
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
TEMPLATES = [
|
|
214
|
+
{
|
|
215
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
216
|
+
"DIRS": [],
|
|
217
|
+
"APP_DIRS": True,
|
|
218
|
+
"OPTIONS": {
|
|
219
|
+
"context_processors": [
|
|
220
|
+
"django.template.context_processors.request",
|
|
221
|
+
"django.contrib.auth.context_processors.auth",
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
DATABASES = {
|
|
228
|
+
"default": {
|
|
229
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
230
|
+
"NAME": BASE_DIR / "db.sqlite3",
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
LANGUAGE_CODE = "en-us"
|
|
235
|
+
TIME_ZONE = "UTC"
|
|
236
|
+
USE_I18N = True
|
|
237
|
+
USE_TZ = True
|
|
238
|
+
|
|
239
|
+
LOGIN_URL = "/"
|
|
240
|
+
LOGIN_REDIRECT_URL = "/"
|
|
241
|
+
LOGOUT_REDIRECT_URL = "/"
|
|
242
|
+
|
|
243
|
+
VOXHELM_BEARER_TOKENS = env_tokens("VOXHELM_BEARER_TOKENS")
|
|
244
|
+
VOXHELM_WORKER_TOKENS = validate_disjoint_token_values(
|
|
245
|
+
"VOXHELM_BEARER_TOKENS",
|
|
246
|
+
VOXHELM_BEARER_TOKENS,
|
|
247
|
+
"VOXHELM_WORKER_TOKENS",
|
|
248
|
+
validate_unique_token_values(
|
|
249
|
+
"VOXHELM_WORKER_TOKENS",
|
|
250
|
+
env_tokens("VOXHELM_WORKER_TOKENS"),
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
VOXHELM_STT_BACKEND = os.getenv("VOXHELM_STT_BACKEND", "whispercpp").strip()
|
|
254
|
+
VOXHELM_STT_FALLBACK_BACKEND = os.getenv("VOXHELM_STT_FALLBACK_BACKEND", "mlx").strip()
|
|
255
|
+
VOXHELM_MLX_MODEL = os.getenv("VOXHELM_MLX_MODEL", "mlx-community/whisper-large-v3-mlx")
|
|
256
|
+
# Conditioning the decoder on previously generated text is Whisper's main cause of
|
|
257
|
+
# runaway repetition loops on long audio (a hallucinated phrase feeds itself across
|
|
258
|
+
# 30s windows). Default off; flip to true to restore upstream Whisper behaviour.
|
|
259
|
+
VOXHELM_MLX_CONDITION_ON_PREVIOUS_TEXT = env_bool(
|
|
260
|
+
"VOXHELM_MLX_CONDITION_ON_PREVIOUS_TEXT", default=False
|
|
261
|
+
)
|
|
262
|
+
VOXHELM_WHISPERCPP_MODEL = os.getenv("VOXHELM_WHISPERCPP_MODEL", "ggml-large-v3.bin").strip()
|
|
263
|
+
VOXHELM_WHISPERCPP_BIN = os.getenv("VOXHELM_WHISPERCPP_BIN", "/opt/homebrew/bin/whisper-cli")
|
|
264
|
+
VOXHELM_WHISPERCPP_PROCESSORS = int(os.getenv("VOXHELM_WHISPERCPP_PROCESSORS", "4"))
|
|
265
|
+
# whisper.cpp anti-hallucination controls. max-context 0 disables conditioning on
|
|
266
|
+
# previous text (the whisper.cpp equivalent of condition_on_previous_text=False);
|
|
267
|
+
# -1 restores the upstream unbounded default. suppress-nst drops non-speech tokens,
|
|
268
|
+
# which curbs hallucinated text over music/silence (e.g. outro-music loops).
|
|
269
|
+
VOXHELM_WHISPERCPP_MAX_CONTEXT = int(os.getenv("VOXHELM_WHISPERCPP_MAX_CONTEXT", "0"))
|
|
270
|
+
VOXHELM_WHISPERCPP_SUPPRESS_NST = env_bool("VOXHELM_WHISPERCPP_SUPPRESS_NST", default=True)
|
|
271
|
+
VOXHELM_WHISPERKIT_ENABLED = env_bool("VOXHELM_WHISPERKIT_ENABLED", default=False)
|
|
272
|
+
VOXHELM_WHISPERKIT_HOST = os.getenv("VOXHELM_WHISPERKIT_HOST", "127.0.0.1").strip()
|
|
273
|
+
VOXHELM_WHISPERKIT_PORT = int(os.getenv("VOXHELM_WHISPERKIT_PORT", "50060"))
|
|
274
|
+
VOXHELM_WHISPERKIT_BASE_URL = os.getenv(
|
|
275
|
+
"VOXHELM_WHISPERKIT_BASE_URL",
|
|
276
|
+
f"http://127.0.0.1:{VOXHELM_WHISPERKIT_PORT}/v1",
|
|
277
|
+
).strip()
|
|
278
|
+
VOXHELM_WHISPERKIT_MODEL = os.getenv("VOXHELM_WHISPERKIT_MODEL", "large-v3-v20240930").strip()
|
|
279
|
+
VOXHELM_WHISPERKIT_AUDIO_ENCODER_COMPUTE_UNITS = os.getenv(
|
|
280
|
+
"VOXHELM_WHISPERKIT_AUDIO_ENCODER_COMPUTE_UNITS",
|
|
281
|
+
"cpuAndGPU",
|
|
282
|
+
).strip()
|
|
283
|
+
VOXHELM_WHISPERKIT_TEXT_DECODER_COMPUTE_UNITS = os.getenv(
|
|
284
|
+
"VOXHELM_WHISPERKIT_TEXT_DECODER_COMPUTE_UNITS",
|
|
285
|
+
"cpuAndGPU",
|
|
286
|
+
).strip()
|
|
287
|
+
VOXHELM_WHISPERKIT_CONCURRENT_WORKER_COUNT = int(
|
|
288
|
+
os.getenv("VOXHELM_WHISPERKIT_CONCURRENT_WORKER_COUNT", "8")
|
|
289
|
+
)
|
|
290
|
+
VOXHELM_WHISPERKIT_CHUNKING_STRATEGY = os.getenv(
|
|
291
|
+
"VOXHELM_WHISPERKIT_CHUNKING_STRATEGY",
|
|
292
|
+
"vad",
|
|
293
|
+
).strip()
|
|
294
|
+
VOXHELM_WHISPERKIT_TIMEOUT_SECONDS = int(
|
|
295
|
+
os.getenv("VOXHELM_WHISPERKIT_TIMEOUT_SECONDS", "900")
|
|
296
|
+
)
|
|
297
|
+
VOXHELM_STT_DEBUG_LOGGING = env_bool("VOXHELM_STT_DEBUG_LOGGING", default=False)
|
|
298
|
+
VOXHELM_DIARIZATION_BACKEND = os.getenv("VOXHELM_DIARIZATION_BACKEND", "none").strip()
|
|
299
|
+
VOXHELM_PYANNOTE_MODEL = os.getenv(
|
|
300
|
+
"VOXHELM_PYANNOTE_MODEL",
|
|
301
|
+
"pyannote/speaker-diarization-3.1",
|
|
302
|
+
).strip()
|
|
303
|
+
VOXHELM_PYANNOTE_DEVICE = os.getenv("VOXHELM_PYANNOTE_DEVICE", "auto").strip()
|
|
304
|
+
VOXHELM_HUGGINGFACE_TOKEN = os.getenv(
|
|
305
|
+
"VOXHELM_HUGGINGFACE_TOKEN",
|
|
306
|
+
os.getenv("HF_TOKEN", ""),
|
|
307
|
+
).strip()
|
|
308
|
+
VOXHELM_MODEL_CACHE_DIR = Path(
|
|
309
|
+
os.getenv("VOXHELM_MODEL_CACHE_DIR", str(BASE_DIR / "var" / "models"))
|
|
310
|
+
)
|
|
311
|
+
VOXHELM_WYOMING_STT_HOST = os.getenv("VOXHELM_WYOMING_STT_HOST", "0.0.0.0").strip()
|
|
312
|
+
VOXHELM_WYOMING_STT_PORT = int(os.getenv("VOXHELM_WYOMING_STT_PORT", "10300"))
|
|
313
|
+
VOXHELM_WYOMING_STT_BACKEND = os.getenv("VOXHELM_WYOMING_STT_BACKEND", "").strip()
|
|
314
|
+
VOXHELM_WYOMING_STT_MODEL = os.getenv("VOXHELM_WYOMING_STT_MODEL", "").strip()
|
|
315
|
+
VOXHELM_WYOMING_STT_LANGUAGE = os.getenv("VOXHELM_WYOMING_STT_LANGUAGE", "").strip()
|
|
316
|
+
VOXHELM_WYOMING_STT_LANGUAGES = env_list("VOXHELM_WYOMING_STT_LANGUAGES")
|
|
317
|
+
VOXHELM_WYOMING_STT_PROMPT = os.getenv("VOXHELM_WYOMING_STT_PROMPT", "").strip()
|
|
318
|
+
VOXHELM_WYOMING_STT_NORMALIZE_TRANSCRIPT = env_bool(
|
|
319
|
+
"VOXHELM_WYOMING_STT_NORMALIZE_TRANSCRIPT",
|
|
320
|
+
default=True,
|
|
321
|
+
)
|
|
322
|
+
VOXHELM_WYOMING_SAMPLES_PER_CHUNK = int(
|
|
323
|
+
os.getenv("VOXHELM_WYOMING_SAMPLES_PER_CHUNK", "1024")
|
|
324
|
+
)
|
|
325
|
+
VOXHELM_LANE_SCHEDULER_ENABLED = env_bool("VOXHELM_LANE_SCHEDULER_ENABLED", default=False)
|
|
326
|
+
VOXHELM_LANE_SCHEDULER_DIR = Path(
|
|
327
|
+
os.getenv("VOXHELM_LANE_SCHEDULER_DIR", str(BASE_DIR / "var" / "lane-scheduler"))
|
|
328
|
+
)
|
|
329
|
+
VOXHELM_LANE_SCHEDULER_STALE_SECONDS = int(
|
|
330
|
+
os.getenv("VOXHELM_LANE_SCHEDULER_STALE_SECONDS", "1800")
|
|
331
|
+
)
|
|
332
|
+
VOXHELM_MAX_UPLOAD_BYTES = int(os.getenv("VOXHELM_MAX_UPLOAD_BYTES", str(25 * 1024 * 1024)))
|
|
333
|
+
VOXHELM_MAX_UPLOAD_MIB = VOXHELM_MAX_UPLOAD_BYTES // (1024 * 1024)
|
|
334
|
+
VOXHELM_MAX_URL_DOWNLOAD_BYTES = int(
|
|
335
|
+
os.getenv("VOXHELM_MAX_URL_DOWNLOAD_BYTES", str(128 * 1024 * 1024))
|
|
336
|
+
)
|
|
337
|
+
VOXHELM_URL_FETCH_TIMEOUT_SECONDS = int(os.getenv("VOXHELM_URL_FETCH_TIMEOUT_SECONDS", "60"))
|
|
338
|
+
VOXHELM_BATCH_MAX_DOWNLOAD_BYTES = int(
|
|
339
|
+
os.getenv("VOXHELM_BATCH_MAX_DOWNLOAD_BYTES", str(512 * 1024 * 1024))
|
|
340
|
+
)
|
|
341
|
+
VOXHELM_BATCH_MAX_STAGED_UPLOAD_BYTES = int(
|
|
342
|
+
os.getenv("VOXHELM_BATCH_MAX_STAGED_UPLOAD_BYTES", str(512 * 1024 * 1024))
|
|
343
|
+
)
|
|
344
|
+
VOXHELM_BATCH_MAX_STAGED_UPLOAD_MIB = VOXHELM_BATCH_MAX_STAGED_UPLOAD_BYTES // (1024 * 1024)
|
|
345
|
+
VOXHELM_STAGED_INPUT_RETENTION_SECONDS = int(
|
|
346
|
+
os.getenv("VOXHELM_STAGED_INPUT_RETENTION_SECONDS", str(24 * 60 * 60))
|
|
347
|
+
)
|
|
348
|
+
VOXHELM_ALLOWED_URL_HOSTS = set(env_list("VOXHELM_ALLOWED_URL_HOSTS"))
|
|
349
|
+
VOXHELM_TRUSTED_HTTP_HOSTS = set(env_list("VOXHELM_TRUSTED_HTTP_HOSTS"))
|
|
350
|
+
VOXHELM_ACCEPTED_MODELS = get_accepted_stt_models()
|
|
351
|
+
VOXHELM_BATCH_ACCEPTED_MODELS = get_batch_accepted_stt_models()
|
|
352
|
+
VOXHELM_TTS_BACKEND = os.getenv("VOXHELM_TTS_BACKEND", "piper").strip()
|
|
353
|
+
VOXHELM_PIPER_VOICE_DIR = Path(
|
|
354
|
+
os.getenv("VOXHELM_PIPER_VOICE_DIR", str(BASE_DIR / "var" / "piper"))
|
|
355
|
+
)
|
|
356
|
+
VOXHELM_PIPER_VOICES = env_list("VOXHELM_PIPER_VOICES")
|
|
357
|
+
VOXHELM_PIPER_DEFAULT_VOICE = os.getenv("VOXHELM_PIPER_DEFAULT_VOICE", "").strip()
|
|
358
|
+
VOXHELM_PIPER_LANGUAGE_VOICES = env_map("VOXHELM_PIPER_LANGUAGE_VOICES")
|
|
359
|
+
VOXHELM_TTS_MAX_INPUT_CHARS = int(os.getenv("VOXHELM_TTS_MAX_INPUT_CHARS", "5000"))
|
|
360
|
+
VOXHELM_ACCEPTED_SPEECH_MODELS = {
|
|
361
|
+
"auto",
|
|
362
|
+
"piper",
|
|
363
|
+
"tts-1",
|
|
364
|
+
"tts-1-hd",
|
|
365
|
+
}
|
|
366
|
+
VOXHELM_TASK_QUEUE = os.getenv("VOXHELM_TASK_QUEUE", "default")
|
|
367
|
+
VOXHELM_FFMPEG_BIN = os.getenv("VOXHELM_FFMPEG_BIN", "ffmpeg")
|
|
368
|
+
VOXHELM_ARTIFACT_BACKEND = os.getenv("VOXHELM_ARTIFACT_BACKEND", "filesystem").strip()
|
|
369
|
+
VOXHELM_ARTIFACT_ROOT = Path(
|
|
370
|
+
os.getenv("VOXHELM_ARTIFACT_ROOT", str(BASE_DIR / "var" / "artifacts"))
|
|
371
|
+
)
|
|
372
|
+
VOXHELM_ARTIFACT_BUCKET = os.getenv("VOXHELM_ARTIFACT_BUCKET", "voxhelm")
|
|
373
|
+
VOXHELM_ARTIFACT_PREFIX = os.getenv("VOXHELM_ARTIFACT_PREFIX", "voxhelm")
|
|
374
|
+
VOXHELM_ARTIFACT_S3_ENDPOINT_URL = os.getenv("VOXHELM_ARTIFACT_S3_ENDPOINT_URL", "").strip()
|
|
375
|
+
VOXHELM_ARTIFACT_S3_REGION = os.getenv("VOXHELM_ARTIFACT_S3_REGION", "us-east-1")
|
|
376
|
+
VOXHELM_ARTIFACT_S3_ACCESS_KEY_ID = os.getenv("VOXHELM_ARTIFACT_S3_ACCESS_KEY_ID", "").strip()
|
|
377
|
+
VOXHELM_ARTIFACT_S3_SECRET_ACCESS_KEY = os.getenv(
|
|
378
|
+
"VOXHELM_ARTIFACT_S3_SECRET_ACCESS_KEY", ""
|
|
379
|
+
).strip()
|
|
380
|
+
VOXHELM_ARTIFACT_S3_FORCE_PATH_STYLE = env_bool(
|
|
381
|
+
"VOXHELM_ARTIFACT_S3_FORCE_PATH_STYLE",
|
|
382
|
+
default=True,
|
|
383
|
+
)
|
|
384
|
+
VOXHELM_TRANSCRIPTION_EXECUTION_MODE = validate_transcription_execution_mode(
|
|
385
|
+
os.getenv(
|
|
386
|
+
"VOXHELM_TRANSCRIPTION_EXECUTION_MODE",
|
|
387
|
+
"django_tasks",
|
|
388
|
+
).strip()
|
|
389
|
+
)
|
|
390
|
+
VOXHELM_REMOTE_WORKER_LEASE_SECONDS = validate_positive_int(
|
|
391
|
+
"VOXHELM_REMOTE_WORKER_LEASE_SECONDS",
|
|
392
|
+
int(os.getenv("VOXHELM_REMOTE_WORKER_LEASE_SECONDS", str(30 * 60))),
|
|
393
|
+
)
|
|
394
|
+
VOXHELM_REMOTE_WORKER_POLL_SECONDS = validate_positive_int(
|
|
395
|
+
"VOXHELM_REMOTE_WORKER_POLL_SECONDS",
|
|
396
|
+
int(os.getenv("VOXHELM_REMOTE_WORKER_POLL_SECONDS", "5")),
|
|
397
|
+
)
|
|
398
|
+
VOXHELM_REMOTE_WORKER_MAX_ATTEMPTS = validate_positive_int(
|
|
399
|
+
"VOXHELM_REMOTE_WORKER_MAX_ATTEMPTS",
|
|
400
|
+
int(os.getenv("VOXHELM_REMOTE_WORKER_MAX_ATTEMPTS", "3")),
|
|
401
|
+
)
|
|
402
|
+
validate_remote_pull_worker_tokens(
|
|
403
|
+
VOXHELM_TRANSCRIPTION_EXECUTION_MODE,
|
|
404
|
+
VOXHELM_WORKER_TOKENS,
|
|
405
|
+
)
|
|
406
|
+
validate_remote_pull_artifact_backend(
|
|
407
|
+
VOXHELM_TRANSCRIPTION_EXECUTION_MODE,
|
|
408
|
+
VOXHELM_ARTIFACT_BACKEND,
|
|
409
|
+
)
|
|
410
|
+
validate_remote_pull_s3_configuration(
|
|
411
|
+
VOXHELM_TRANSCRIPTION_EXECUTION_MODE,
|
|
412
|
+
VOXHELM_ARTIFACT_BACKEND,
|
|
413
|
+
{
|
|
414
|
+
"VOXHELM_ARTIFACT_S3_ENDPOINT_URL": VOXHELM_ARTIFACT_S3_ENDPOINT_URL,
|
|
415
|
+
"VOXHELM_ARTIFACT_S3_ACCESS_KEY_ID": VOXHELM_ARTIFACT_S3_ACCESS_KEY_ID,
|
|
416
|
+
"VOXHELM_ARTIFACT_S3_SECRET_ACCESS_KEY": VOXHELM_ARTIFACT_S3_SECRET_ACCESS_KEY,
|
|
417
|
+
"VOXHELM_ARTIFACT_BUCKET": VOXHELM_ARTIFACT_BUCKET,
|
|
418
|
+
},
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
TASKS = {
|
|
422
|
+
"default": {
|
|
423
|
+
"BACKEND": os.getenv("VOXHELM_TASKS_BACKEND", "django_tasks_db.backend.DatabaseBackend")
|
|
424
|
+
}
|
|
425
|
+
}
|
config/urls.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from django.urls import include, path
|
|
2
|
+
|
|
3
|
+
from operators.views import logout_view, operator_artifact, root
|
|
4
|
+
from synthesis.views import audio_speech
|
|
5
|
+
from transcriptions.views import audio_transcriptions, health
|
|
6
|
+
|
|
7
|
+
urlpatterns = [
|
|
8
|
+
path("", root, name="root"),
|
|
9
|
+
path("logout", logout_view, name="logout"),
|
|
10
|
+
path(
|
|
11
|
+
"transcripts/<uuid:job_id>/artifacts/<str:format_name>",
|
|
12
|
+
operator_artifact,
|
|
13
|
+
name="operator-artifact",
|
|
14
|
+
),
|
|
15
|
+
path("v1/health", health, name="health"),
|
|
16
|
+
path("v1/audio/speech", audio_speech, name="audio-speech"),
|
|
17
|
+
path("v1/audio/transcriptions", audio_transcriptions, name="audio-transcriptions"),
|
|
18
|
+
path("v1/", include("jobs.urls")),
|
|
19
|
+
]
|
config/wsgi.py
ADDED
jobs/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|