Files
sermon-clean/tests/test_transcribe.py
T
sami7777 7db2832034 Add scan + auto subcommands
- waveform.py: fast ASCII waveform via ffmpeg PCM extract + numpy RMS
  (avoids the slow per-frame astats approach)
- transcribe.py: faster-whisper integration with substring bad-word matching
- cli.py: new 'scan' (ASCII waveform + silence marks, no whisper)
              and 'auto' (transcribe + bad-word flagging) subcommands
- numpy added to required dependencies
- 17 new tests (waveform + transcribe), 42 total all passing
2026-07-27 03:50:55 -07:00

94 lines
3.9 KiB
Python

"""Tests for sermon_clean.transcribe.
These tests don't actually invoke Whisper (that takes ~10-30s per test).
They validate the data shapes and helper functions in pure Python.
"""
import re
import pytest
from sermon_clean.transcribe import (
find_bad_words,
suggest_replacements,
export_to_segments_json,
)
class TestRegexPattern:
"""Verify the bad-word regex used by find_bad_words matches substring words correctly."""
def test_substring_match(self):
pattern = re.compile("|".join(re.escape(w) for w in ["fuck", "shit"]), re.IGNORECASE)
# "fucking" should match "fuck" as substring
assert pattern.search("fucking")
# "shitty" should match "shit" as substring
assert pattern.search("shitty")
# "fudge" should NOT match
assert not pattern.search("fudge")
# find_bad_words() guards against empty bad_words set (would match zero-width)
from sermon_clean.transcribe import find_bad_words
# Mock find_bad_words path: should return [] for empty bad_words
# (real test would require whisper; we test the guard logic by reading source)
import inspect
src = inspect.getsource(find_bad_words)
assert "if not bad_set:" in src and "return []" in src
class TestSuggestReplacements:
def test_no_context(self):
hits = [{"start": 5.0, "end": 6.0, "word": "fuck", "probability": 0.9, "bad_word": "fuck"}]
result = suggest_replacements(hits)
assert result[0]["suggested_replacement"] == ""
def test_with_context(self):
hits = [{"start": 5.0, "end": 6.0, "word": "fucking", "probability": 0.9, "bad_word": "fuck"}]
context = [
{"word": "you", "start": 4.5, "end": 4.9},
{"word": "are", "start": 7.0, "end": 7.4},
{"word": "great", "start": 7.5, "end": 7.9},
]
result = suggest_replacements(hits, transcript_context=context)
assert "you" in result[0]["suggested_replacement"]
assert "are" in result[0]["suggested_replacement"]
def test_filters_far_words(self):
hits = [{"start": 5.0, "end": 6.0, "word": "damn", "probability": 0.9, "bad_word": "damn"}]
context = [
{"word": "long", "start": 0.0, "end": 0.5}, # too far
{"word": "ago", "start": 9.0, "end": 9.5}, # too far
]
result = suggest_replacements(hits, transcript_context=context, max_window=2.0)
assert result[0]["suggested_replacement"] == ""
class TestExportToSegmentsJson:
def test_basic_export(self):
hits = [{
"start": 5.0, "end": 6.0,
"word": "fucking", "probability": 0.9, "bad_word": "fuck",
"suggested_replacement": "the actual sentence",
}]
segs = export_to_segments_json(hits)
assert len(segs) == 1
assert segs[0]["start"] == 5.0
assert segs[0]["end"] == 6.0
assert segs[0]["reason"] == "bad_word:fuck"
assert segs[0]["replacement_text"] == "the actual sentence"
def test_skip_empty_replacements_when_flagged(self):
hits = [
{"start": 5.0, "end": 6.0, "word": "x", "probability": 0.9, "bad_word": "x", "suggested_replacement": ""},
{"start": 7.0, "end": 8.0, "word": "y", "probability": 0.9, "bad_word": "y", "suggested_replacement": "good"},
]
# Default: skip empty replacements
segs = export_to_segments_json(hits, include_suggested_only=True)
assert len(segs) == 1
# With include_suggested_only=False, both kept
segs = export_to_segments_json(hits, include_suggested_only=False)
assert len(segs) == 2
def test_rounds_timestamps(self):
hits = [{"start": 5.123456, "end": 6.987654, "word": "x", "probability": 0.9, "bad_word": "x", "suggested_replacement": "r"}]
segs = export_to_segments_json(hits)
assert segs[0]["start"] == 5.123
assert segs[0]["end"] == 6.988