Files
transcribe/tests/fake_provider.py
T
ddidderr 3042b7743b feat!: use whole-recording Pyannote speaker diarization
Sampled Gemini voice matching could leave one person under separate IDs
across transcription chunks. Use hosted Precision-2 on the complete
recording and align its exclusive speaker timeline with MAI word timing.
Preserve transcription text, order, and timestamps; leave ambiguous word
assignments unknown instead of merging identities to satisfy a count.

Upload lossless FLAC, bound polling, retry transient status reads against
the same job, and validate results before emitting stdout. Keep optional
speaker-count and hosted model overrides. Disable MAI diarization and use
30-minute transcription uploads in both modes. Respect environment keys,
keep authentication out of process arguments, and save optional diagnostics
without audio, credentials, or signed upload URLs.

Test Plan:
- python3 -B -m unittest -v: all 101 regression tests passed, including
  mocked provider upload, polling, alignment, failure, and cleanup paths.
- bash -n transcribe and shellcheck transcribe: passed.
- git diff --cached --check: passed.
- Prepared the 74-minute input locally and verified identical decoded PCM
  hashes for the normalized WAV and full-recording lossless FLAC.
- Live API testing was explicitly deferred by the user; mocked results do
  not establish speaker accuracy on the recording.

BREAKING CHANGE: Default diarization requires PYANNOTE_API_KEY or its
1Password reference. --speaker-model accepts hosted precision-2 or
community-1 instead of OpenRouter audio-chat model IDs.
2026-09-12 11:18:30 +02:00

271 lines
13 KiB
Python

#!/usr/bin/env python3
"""Test-only replacements for op/curl; no network or real credentials."""
import base64
import json
import os
from pathlib import Path
import subprocess
import sys
import time
log_path = Path(os.environ["TRANSCRIBE_TEST_LOG"])
if Path(sys.argv[0]).name == "op":
with log_path.with_suffix(".credentials.jsonl").open("a") as log:
log.write(json.dumps({"args": sys.argv[1:]}) + "\n")
if os.environ.get("TRANSCRIBE_TEST_OP_FAIL"):
sys.exit(1)
print("test-op-api-key")
sys.exit(0)
args = sys.argv[1:]
output = Path(args[args.index("-o") + 1])
url = next(value for value in args if value.startswith("https://"))
headers = {}
for position, value in enumerate(args):
if value in ("-H", "--header"):
value = args[position + 1]
header_lines = Path(value[1:]).read_text().splitlines() if value.startswith("@") else [value]
for header_line in header_lines:
key, _, header_value = header_line.partition(":")
headers[key.lower()] = header_value.strip()
request = None
request_path = None
if "--data-binary" in args:
request_path = Path(args[args.index("--data-binary") + 1][1:])
request = json.loads(request_path.read_text())
mode = os.environ.get("TRANSCRIBE_TEST_MODE", "ok")
events_path = log_path.with_suffix(".events.jsonl")
is_pyannote = "openrouter.ai" not in url
index = -1 if is_pyannote else int(output.stem.split("-")[1])
staging = next(parent for parent in (output.parent, *output.parents)
if (parent / "chunks.json").is_file())
def event(kind, **extra):
record = dict(event=kind, index=index, pid=os.getpid(), group=os.getpgrp(),
time=time.monotonic(), staging=str(staging), **extra)
with events_path.open("a") as log:
log.write(json.dumps(record) + "\n")
def reply(value, status=0, http_status=200):
if "--write-out" in args:
print(str(http_status), end="")
output.write_text(json.dumps(value))
event("finish")
sys.exit(status)
event("start")
if is_pyannote:
pyannote_log = log_path.with_suffix(".pyannote.jsonl")
previous = [json.loads(line) for line in pyannote_log.read_text().splitlines()] if pyannote_log.exists() else []
record = {"url": url, "request": request, "headers": headers, "staging": str(staging)}
if url == "https://api.pyannote.ai/v1/media/input":
stage = "media"
assert request["url"].startswith("media://") and request["url"].endswith(".flac")
response = {"url": "https://storage.test.invalid/upload?signature=fixture-signed-url"}
elif url.startswith("https://storage.test.invalid/"):
stage = "upload"
assert not headers.get("authorization"), "API bearer leaked to signed storage URL"
upload = Path(args[args.index("--upload-file") + 1])
info = json.loads(subprocess.check_output([
"ffprobe", "-v", "error", "-show_entries",
"format=duration:stream=codec_name,sample_rate,channels", "-of", "json", str(upload),
], text=True))
assert info["streams"][0] == {"codec_name": "flac", "sample_rate": "16000", "channels": 1}
record["upload_duration"] = float(info["format"]["duration"])
record["stream"] = info["streams"][0]
response = {}
elif url == "https://api.pyannote.ai/v1/diarize":
stage = "submit"
assert request["model"] in ("precision-2", "community-1")
assert request["url"] == next(item["request"]["url"] for item in previous if item["stage"] == "media")
assert request["exclusive"] is True
assert request["transcription"] is False
response = {"jobId": "test-job-42"}
elif url == "https://api.pyannote.ai/v1/jobs/test-job-42":
stage = "poll"
duration = next(item["upload_duration"] for item in previous if item["stage"] == "upload")
manifest = json.loads((staging / "chunks.json").read_text())
if mode == "silence":
turns = []
elif mode.startswith("reconcile"):
late_start = round(manifest[0]["end"]) if len(manifest) > 1 else 60
turns = [(0, min(duration, 10), "voice_a"), (10, min(duration, 16), "voice_b"),
(late_start, min(duration, late_start + 3), "voice_a"),
(late_start + 4, duration, "voice_b")]
elif mode in ("speakers", "partial-speakers"):
boundary = 1.45 if len(manifest) == 1 else 57.3
turns = [(0, boundary, "voice_a"), (boundary, duration, "voice_b")]
else:
turns = [(0, duration, "voice_a")]
timeline = [{"start": start, "end": end, "speaker": speaker}
for start, end, speaker in turns if start < end]
# Standard diarization intentionally disagrees: clients must select the
# exclusive timeline requested for word-level speaker attribution.
response = {"jobId": "test-job-42", "status": "succeeded", "output": {
"exclusiveDiarization": timeline,
"diarization": [{"start": 0, "end": duration, "speaker": "wrong_timeline"}],
}}
if mode == "reconcile-diagnostics-metadata":
response["request"] = {"url": "media://fixture-recording.flac", "key": "test-pyannote-key"}
response["output"]["debug"] = "https://storage.test.invalid/?signature=fixture-signed-url"
response["output"]["exclusiveDiarization"][0]["request"] = "test-openrouter-key"
response["output"]["exclusiveDiarization"][0]["confidence"] = {"voice_a": 99.0}
response["output"]["diarization"][0]["debug"] = "fixture-signed-url"
poll_count = sum(item["stage"] == "poll" for item in previous)
if mode == "pyannote-lifecycle" and poll_count < 2:
response = {"jobId": "test-job-42", "status": ("created", "running")[poll_count]}
elif mode in ("pyannote-failed", "pyannote-canceled"):
response = {"jobId": "test-job-42", "status": mode.removeprefix("pyannote-"),
"error": "simulated diarization failure"}
elif mode == "pyannote-missing-timeline":
response["output"].pop("exclusiveDiarization")
elif mode == "pyannote-malformed-timeline":
response["output"]["exclusiveDiarization"][0]["end"] = "not a timestamp"
elif mode == "pyannote-empty-timeline":
response["output"]["exclusiveDiarization"] = []
elif mode == "pyannote-wrong-job":
response["jobId"] = "different-job"
elif mode == "pyannote-output-error":
response["output"]["error"] = "simulated incomplete diarization output"
elif mode == "pyannote-partial-timeline":
response["output"]["exclusiveDiarization"] = [{"start": 0, "end": 2, "speaker": "voice_a"}]
else:
raise AssertionError(f"Unexpected curl URL: {url}")
assert args[args.index("--request") + 1] == {
"media": "POST", "upload": "PUT", "submit": "POST", "poll": "GET",
}[stage]
if stage != "upload":
assert headers.get("authorization") in ("Bearer test-pyannote-key", "Bearer test-op-api-key")
record["stage"] = stage
record["response"] = response
with pyannote_log.open("a") as log:
log.write(json.dumps(record) + "\n")
if mode == f"pyannote-{stage}-http-error":
reply({"error": f"simulated {stage} failure"}, http_status=401)
if stage == "poll" and mode == "pyannote-transient-poll" and poll_count == 0:
reply({"error": "simulated transient poll failure"}, status=22)
if stage == "upload" and mode == "pyannote-transport-error":
reply({"error": "simulated transport failure"}, status=22)
if stage == "poll" and mode == "pyannote-bad-json":
if "--write-out" in args:
print("200", end="")
output.write_text("not json")
event("finish")
sys.exit(0)
if stage == "poll" and mode == "pyannote-blocked":
child = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(30)"])
event("child", child_pid=child.pid)
time.sleep(30)
reply(response)
assert url == "https://openrouter.ai/api/v1/audio/transcriptions"
assert headers.get("authorization") in ("Bearer test-openrouter-key", "Bearer test-op-api-key")
manifest = json.loads((output.parent / "chunks.json").read_text())
chunk = manifest[index]
if mode in ("parallel-error", "blocked") and index == 0:
child = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(30)"])
event("child", child_pid=child.pid)
assert request["model"] == "microsoft/mai-transcribe-2"
assert request["input_audio"]["format"] == "mp3"
if len(manifest) > 1:
assert request["response_format"] == "verbose_json"
assert request["timestamp_granularities"] == ["segment", "word"]
audio = output.parent / f"uploaded-{index}.mp3"
audio.write_bytes(base64.b64decode(request["input_audio"]["data"], validate=True))
info = json.loads(subprocess.check_output([
"ffprobe", "-v", "error", "-show_entries",
"format=duration:stream=codec_name,sample_rate,channels", "-of", "json", str(audio),
], text=True))
assert info["streams"][0] == {"codec_name": "mp3", "sample_rate": "16000", "channels": 1}
assert abs(float(info["format"]["duration"]) - (chunk["end"] - chunk["start"])) < 0.1
with open(os.environ["TRANSCRIBE_TEST_LOG"], "a") as log:
log.write(json.dumps({
"index": index, "chunk": chunk, "staging": str(output.parent),
"work": str(request_path.parent),
"upload_duration": float(info["format"]["duration"]),
"provider": request.get("provider"),
"response_format": request.get("response_format"),
"timestamp_granularities": request.get("timestamp_granularities"),
"authorization": headers.get("authorization"),
}) + "\n")
if mode in ("parallel", "parallel-error"):
deadline = time.monotonic() + 5
barrier = int(os.environ.get("TRANSCRIBE_TEST_BARRIER", "1"))
while True:
events = [json.loads(line) for line in events_path.read_text().splitlines()]
enough = sum(e["event"] == "start" for e in events) >= barrier
child_ready = mode != "parallel-error" or any(e["event"] == "child" for e in events)
if enough and child_ready:
break
if time.monotonic() >= deadline:
raise RuntimeError("Worker concurrency barrier timed out")
time.sleep(0.01)
if mode == "parallel-error" and index == 1:
output.write_text('{"error":{"message":"simulated concurrent failure"}}')
event("finish")
sys.exit(22)
time.sleep(30 if mode == "parallel-error" else (0.8 if index == 0 else 0.05))
elif mode == "blocked":
time.sleep(30)
if index == 1:
if mode == "http-error":
output.write_text('{"error":{"message":"simulated timeout"}}')
sys.exit(22)
if mode == "bad-json":
output.write_text("not json")
sys.exit(0)
if mode == "api-error":
output.write_text('{"error":{"message":"simulated API error"},"text":""}')
sys.exit(0)
if mode == "missing-words":
output.write_text('{"text":"speech without timestamps"}')
sys.exit(0)
if "response_format" not in request:
response = {"text": "Provider's original formatting.\nNext line."}
else:
words = [
{"word": text, "start": start - chunk["start"], "end": end - chunk["start"]}
for text, start, end in [
("Hello.", 1, 1.4), ("boundary", 56.7, 57.1),
("continues.", 57.4, 57.8), ("Last.", 65, 65.4),
] if start >= chunk["start"] and end <= chunk["end"]
]
if mode in ("speakers", "partial-speakers"):
if len(manifest) == 1:
words = [
{"word": "Hello", "start": 1, "end": 1.4},
{"word": "there", "start": 1.5, "end": 1.9},
]
for speaker, word in enumerate(words):
if mode == "speakers" or speaker == 0:
word["speaker"] = (speaker if len(manifest) == 1 else
int(word["start"] + chunk["start"] >= 57.3))
if mode == "silence":
words = []
if mode == "parallel":
middle = (chunk["end"] - chunk["start"]) / 2
words = [{"word": f"Part{index}.", "start": middle, "end": middle + 0.4}]
if mode.startswith("reconcile"):
# Two labels already describe voice A inside the first request; later
# chunks reuse the same local numbers for different request identities.
late_start = round(manifest[0]["end"]) if len(manifest) > 1 else 60
words = [
{"word": text, "start": start - chunk["start"], "end": start + 2 - chunk["start"],
"speaker": speaker}
for text, start, speaker in [
("Alpha.", 1, 7), ("Again.", 6, 8), ("Beta.", 12, 9),
("AlphaLate.", late_start, 9), ("BetaLate.", late_start + 5, 7),
] if start >= chunk["start"] and start + 2 <= chunk["end"]
]
response = {"text": " ".join(w["word"] for w in words), "words": words}
output.write_text(json.dumps(response))
event("finish")