На Windows реплика в 190 секунд роняет энкодер GigaAM в свёртке, хотя на macOS та же самая проходит: пределы у сборок onnxruntime разные и заранее неизвестны. Вместо подбора константы кусок при ошибке делится пополам и пробуется снова, до пяти уровней. Базовый размер куска снижен со 150 до 60 секунд - с запасом под любую сборку. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""Тесты склейки сегментов диаризации в реплики и нарезки длинных кусков."""
|
|
from app.turns import Segment, chunk_ranges, merge_turns, speech_stats
|
|
|
|
|
|
def seg(start, end, speaker):
|
|
return Segment(start=start, end=end, speaker=speaker)
|
|
|
|
|
|
class TestMergeTurns:
|
|
def test_merges_adjacent_segments_of_same_speaker(self):
|
|
turns = merge_turns([seg(0, 2, 0), seg(2.3, 4, 0)], gap=0.8)
|
|
assert len(turns) == 1
|
|
assert turns[0].start == 0 and turns[0].end == 4
|
|
|
|
def test_keeps_separate_when_gap_is_large(self):
|
|
turns = merge_turns([seg(0, 2, 0), seg(5, 6, 0)], gap=0.8)
|
|
assert len(turns) == 2
|
|
|
|
def test_never_merges_different_speakers(self):
|
|
turns = merge_turns([seg(0, 2, 0), seg(2.1, 4, 1)], gap=0.8)
|
|
assert len(turns) == 2
|
|
assert [t.speaker for t in turns] == [0, 1]
|
|
|
|
def test_empty_input(self):
|
|
assert merge_turns([], gap=0.8) == []
|
|
|
|
def test_preserves_order(self):
|
|
turns = merge_turns([seg(0, 1, 0), seg(2, 3, 1), seg(4, 5, 0)], gap=0.5)
|
|
assert [t.start for t in turns] == [0, 2, 4]
|
|
|
|
|
|
class TestChunkRanges:
|
|
def test_short_turn_is_single_chunk(self):
|
|
assert chunk_ranges(10.0, 20.0, max_len=150) == [(10.0, 20.0)]
|
|
|
|
def test_long_turn_is_split(self):
|
|
chunks = chunk_ranges(0.0, 400.0, max_len=150)
|
|
assert len(chunks) == 3
|
|
assert chunks[0] == (0.0, 150.0)
|
|
assert chunks[-1][1] == 400.0
|
|
|
|
def test_chunks_are_contiguous(self):
|
|
chunks = chunk_ranges(5.0, 500.0, max_len=150)
|
|
for a, b in zip(chunks, chunks[1:]):
|
|
assert a[1] == b[0]
|
|
|
|
def test_no_chunk_exceeds_limit(self):
|
|
"""Главное требование: GigaAM падает на кусках длиннее 200 секунд."""
|
|
for a, b in chunk_ranges(0.0, 1000.0, max_len=150):
|
|
assert b - a <= 150
|
|
|
|
def test_exact_boundary_is_not_split(self):
|
|
assert chunk_ranges(0.0, 150.0, max_len=150) == [(0.0, 150.0)]
|
|
|
|
|
|
class TestSpeechStats:
|
|
def test_counts_share_per_speaker(self):
|
|
stats = speech_stats([seg(0, 30, 0), seg(30, 10 + 60, 1)])
|
|
assert stats["speakers"] == 2
|
|
|
|
def test_total_speech_time(self):
|
|
stats = speech_stats([seg(0, 10, 0), seg(20, 30, 1)])
|
|
assert stats["speech_sec"] == 20
|
|
|
|
def test_share_sums_to_one_hundred(self):
|
|
stats = speech_stats([seg(0, 25, 0), seg(25, 100, 1)])
|
|
total = sum(s["share_pct"] for s in stats["by_speaker"])
|
|
assert round(total) == 100
|
|
|
|
def test_empty_segments(self):
|
|
stats = speech_stats([])
|
|
assert stats["speakers"] == 0
|
|
assert stats["speech_sec"] == 0
|
|
|
|
|
|
class TestChunkLimit:
|
|
def test_default_chunk_is_conservative(self):
|
|
"""Пределы у разных сборок onnxruntime разные, берём с запасом."""
|
|
from app.pipeline import MAX_CHUNK_SEC
|
|
|
|
assert MAX_CHUNK_SEC <= 60
|
|
|
|
def test_long_turn_split_below_limit(self):
|
|
from app.pipeline import MAX_CHUNK_SEC
|
|
|
|
for a, b in chunk_ranges(0.0, 600.0, max_len=MAX_CHUNK_SEC):
|
|
assert b - a <= MAX_CHUNK_SEC
|