Files
audio-scribe/tests/test_formats.py
T
JMR-devandClaude Sonnet 5 b23995f218 refactor: rename the package from ccn-transcribe to audio-scribe
The distribution, console script and import package are now audio-scribe /
audio_scribe (src/audio_scribe). Everything named for the old project follows:

- CcnError -> AudioScribeError, and its code "ccn_error" -> "audio_scribe_error"
  (the code is never persisted, so existing job state still loads)
- CCN_LIVE -> AUDIO_SCRIBE_LIVE for the live-GPU tests
- OpenVINO kernel cache moves to <cache>/audio-scribe/ov_cache; the first run
  after upgrading recompiles kernels, and the old directory is left in place
- README, build-binary.sh, hatch/coverage config and uv.lock updated to match

Breaking: the command is now `audio-scribe`; reinstall any tool install of the
old name with `uv tool uninstall ccn-transcribe && uv tool install .`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 15:52:42 -05:00

143 lines
4.7 KiB
Python

from __future__ import annotations
import json
import pytest
from audio_scribe import formats
from audio_scribe.transcript import Segment, TranscriptResult
SEGMENTS = (Segment(0.0, 1.5, "Hello there."), Segment(1.5, 3.25, "General Kenobi."))
RESULT = TranscriptResult(
segments=SEGMENTS,
language="en",
backend="openvino",
device="GPU",
model_id="OpenVINO/whisper-large-v3-turbo-int8-ov",
model_revision="b568445d",
audio_s=3.25,
wall_s=1.0,
)
class TestTimestamps:
@pytest.mark.parametrize(
("seconds", "expected"),
[
(0.0, "00:00:00,000"),
(1.5, "00:00:01,500"),
(61.25, "00:01:01,250"),
(3600.0, "01:00:00,000"),
(7322.5, "02:02:02,500"),
],
)
def test_srt_timestamp(self, seconds: float, expected: str) -> None:
assert formats.srt_timestamp(seconds) == expected
def test_vtt_uses_a_dot_separator(self) -> None:
assert formats.vtt_timestamp(61.25) == "00:01:01.250"
def test_rounding_never_produces_sixty_seconds(self) -> None:
# Naive f"{s:06.3f}" renders this as 00:59:60.000, which is invalid and
# which some players silently mis-seek on.
assert formats.srt_timestamp(3599.9996) == "01:00:00,000"
assert formats.vtt_timestamp(3599.9996) == "01:00:00.000"
def test_negative_times_clamp_to_zero(self) -> None:
assert formats.srt_timestamp(-5.0) == "00:00:00,000"
def test_hours_beyond_two_digits_still_render(self) -> None:
assert formats.srt_timestamp(360000.0).startswith("100:00:00")
class TestTxt:
def test_joins_segment_text(self) -> None:
assert formats.to_txt(RESULT) == "Hello there. General Kenobi."
def test_ends_with_a_newline(self) -> None:
assert formats.to_txt(RESULT).endswith("\n") is False # body only; writer adds newline
class TestSrt:
def test_structure(self) -> None:
assert formats.to_srt(SEGMENTS) == (
"1\n"
"00:00:00,000 --> 00:00:01,500\n"
"Hello there.\n"
"\n"
"2\n"
"00:00:01,500 --> 00:00:03,250\n"
"General Kenobi.\n"
)
def test_empty_segments_give_empty_output(self) -> None:
assert formats.to_srt(()) == ""
class TestVtt:
def test_has_the_required_header(self) -> None:
assert formats.to_vtt(SEGMENTS).startswith("WEBVTT\n\n")
def test_uses_dot_separated_timestamps_and_no_cue_numbers(self) -> None:
body = formats.to_vtt(SEGMENTS)
assert "00:00:00.000 --> 00:00:01.500" in body
assert "," not in body.split("Hello", maxsplit=1)[0]
def test_empty_segments_still_emit_the_header(self) -> None:
assert formats.to_vtt(()) == "WEBVTT\n"
class TestJson:
def test_records_run_metadata(self) -> None:
doc = json.loads(formats.to_json(RESULT))
assert doc["backend"] == "openvino"
assert doc["device"] == "GPU"
assert doc["model"]["id"] == "OpenVINO/whisper-large-v3-turbo-int8-ov"
assert doc["model"]["revision"] == "b568445d"
assert doc["language"] == "en"
assert doc["audio_s"] == 3.25
assert doc["wall_s"] == 1.0
assert doc["rtf"] == pytest.approx(3.25)
def test_records_segments_with_times(self) -> None:
doc = json.loads(formats.to_json(RESULT))
assert doc["segments"] == [
{"start": 0.0, "end": 1.5, "text": "Hello there."},
{"start": 1.5, "end": 3.25, "text": "General Kenobi."},
]
def test_includes_full_text(self) -> None:
assert json.loads(formats.to_json(RESULT))["text"] == "Hello there. General Kenobi."
def test_merges_source_metadata_when_given(self) -> None:
doc = json.loads(formats.to_json(RESULT, source={"id": "abc", "title": "T"}))
assert doc["source"] == {"id": "abc", "title": "T"}
def test_source_key_absent_when_not_given(self) -> None:
assert "source" not in json.loads(formats.to_json(RESULT))
def test_is_utf8_readable_not_escaped(self) -> None:
result = TranscriptResult(
segments=(Segment(0.0, 1.0, "naïve café"),),
language="fr",
backend="openvino",
device="CPU",
model_id="m",
model_revision=None,
audio_s=1.0,
wall_s=1.0,
)
assert "naïve café" in formats.to_json(result)
def test_render_returns_every_requested_format() -> None:
out = formats.render(RESULT, ("txt", "srt", "vtt", "json"))
assert set(out) == {"txt", "srt", "vtt", "json"}
assert out["srt"].startswith("1\n")
def test_render_rejects_an_unknown_format() -> None:
with pytest.raises(ValueError, match="unknown"):
formats.render(RESULT, ("txt", "docx"))