Files
sermon-clean/tests/test_engine.py
T
sami7777 cea6005bf4 Initial commit: sermon-clean v0.1.0
Find, cut, paste replacements in sermon audio.
- engine.py: SermonClean core (BadSegment, trim, concat, splice verify)
- cli.py: find/cut/paste/pipe subcommands
- elevenlabs.py: optional ElevenLabs rendering hook
- 25 tests passing (timestamp parsing, BadSegment validation, end-to-end trim on real audio)
- pyproject.toml: pip-installable; dep only on ffmpeg/ffprobe system-wide
- README + LICENSE (MIT) + examples/segs.json

Validated against the krystie audio cache (OGG/Opus 48kHz mono).
2026-07-27 02:40:18 -07:00

100 lines
3.1 KiB
Python

"""Tests for sermon-clean.engine."""
from pathlib import Path
import pytest
from sermon_clean.engine import (
SermonClean,
parse_timestamp,
format_timestamp,
)
class TestTimestampParsing:
@pytest.mark.parametrize("s,expected", [
("30", 30.0),
("90", 90.0),
("1:30", 90.0),
("01:30", 90.0),
("1:30.5", 90.5),
("1:00:30", 3630.0),
("02:01:30.5", 7290.5),
])
def test_valid(self, s, expected):
assert parse_timestamp(s) == expected
@pytest.mark.parametrize("s", ["", "abc", "1:60", "60:60", "1.2.3"])
def test_invalid(self, s):
with pytest.raises(ValueError):
parse_timestamp(s)
class TestFormatTimestamp:
@pytest.mark.parametrize("seconds,expected", [
(0.0, "00:00.000"),
(59.999, "00:59.999"),
(60.0, "01:00.000"),
(90.5, "01:30.500"),
(3600.0, "01:00:00.000"),
(3661.5, "01:01:01.500"),
(7290.5, "02:01:30.500"),
])
def test_round_trip(self, seconds, expected):
assert format_timestamp(seconds) == expected
def test_roundtrip_parse(self):
for s in [0.0, 5.0, 59.999, 60.0, 3630.0, 7290.5]:
assert parse_timestamp(format_timestamp(s)) == pytest.approx(s)
class TestBadSegment:
def test_valid(self):
from sermon_clean.engine import BadSegment
s = BadSegment(10.0, 12.0, reason="misspoke")
assert s.duration == 2.0
assert s.start == 10.0
assert s.end == 12.0
def test_zero_length_rejected(self):
from sermon_clean.engine import BadSegment
with pytest.raises(ValueError):
BadSegment(10.0, 10.0)
def test_negative_rejected(self):
from sermon_clean.engine import BadSegment
with pytest.raises(ValueError):
BadSegment(15.0, 10.0)
def test_long_window_rejected(self):
from sermon_clean.engine import BadSegment
# 90s window by default > 60s cap
with pytest.raises(ValueError, match="suspiciously long"):
BadSegment(0.0, 90.0)
def test_sermon_clean_basic(tmp_path):
"""Smoke test: load a real audio file from the krystie profile cache."""
# Find any audio file in the krystie cache
cache = Path("/root/.hermes/profiles/krystie/cache/audio")
if not cache.exists():
pytest.skip("krystie audio cache not present")
audio_files = sorted(cache.glob("*.ogg")) + sorted(cache.glob("*.mp3"))
if not audio_files:
pytest.skip("no audio files in krystie cache")
audio = audio_files[0]
sc = SermonClean(audio, workdir=tmp_path)
assert sc.duration > 0
assert sc.codec["codec_name"]
assert sc.codec["sample_rate"]
seg = sc.add_segment(0.5, 1.0, reason="test")
assert seg.start == 0.5
assert len(sc.segments) == 1
segs = sc.trim_segments()
assert len(segs) == 2 # before + after the 0.5s window
assert all(p.exists() for p in segs)
# Verify the trimmed segments
from sermon_clean.engine import probe_duration
total = sum(probe_duration(p) for p in segs)
assert total == pytest.approx(sc.duration - 0.5, abs=0.1)