admin can split a sentence into 2 sentences

This commit is contained in:
2026-08-30 09:25:34 +08:00
parent abc052e42d
commit a99c0cfd17
8 changed files with 577 additions and 13 deletions

View File

@@ -188,3 +188,74 @@ def test_processor_marks_ready_only_after_oss_upload(tmp_path, monkeypatch):
assert uploads[0]["stored_filename"] == f"{VIDEO_HASH}.mp4"
assert video["status"] == "ready"
assert video["remote_url"] == "https://media.example.com/lesson.mp4"
def test_admin_can_split_sentence_and_reindexes_following_sentences(tmp_path):
settings = replace(Settings.from_env(), data_dir=tmp_path / "data")
settings.ensure_directories()
repository = VideoRepository(settings.database_path)
repository.upsert_upload(
video_hash=VIDEO_HASH,
title="Manual split lesson",
filename="lesson.mp4",
stored_filename=f"{VIDEO_HASH}.mp4",
content_type="video/mp4",
size_bytes=5,
language="en",
)
repository.save_processing_result(
SentenceBoundaryDocument(
video_hash=VIDEO_HASH,
duration_ms=9000,
algorithm_version="test",
sentences=[
SentenceBoundary(
index=0,
start_ms=0,
end_ms=4000,
text="First part second part",
language="en",
reference_speech_duration_ms=3600,
),
SentenceBoundary(
index=1,
start_ms=4000,
end_ms=9000,
text="Next sentence.",
language="en",
reference_speech_duration_ms=4600,
),
],
),
None,
)
class UnavailableTranscriber:
available = False
client = TestClient(
create_app(
BoundaryStore(tmp_path / "boundaries.json"),
settings=settings,
repository=repository,
transcriber=UnavailableTranscriber(),
)
)
response = client.put(
f"/api/v1/admin/videos/{VIDEO_HASH}/sentences/0/split",
json={"text": "First part\nsecond part", "language": "en"},
)
assert response.status_code == 200
replacements = response.json()
assert [item["text"] for item in replacements] == ["First part", "second part"]
assert replacements[0]["start_ms"] == 0
assert replacements[0]["end_ms"] == replacements[1]["start_ms"]
assert replacements[1]["end_ms"] == 4000
document = repository.get_document(VIDEO_HASH)
assert [item.text for item in document.sentences] == [
"First part",
"second part",
"Next sentence.",
]
assert [item.index for item in document.sentences] == [0, 1, 2]

View File

@@ -1,6 +1,11 @@
import numpy as np
from sentence_api.processing import MOSS_ALGORITHM_VERSION, document_from_transcript
from sentence_api.processing import (
MOSS_ALGORITHM_VERSION,
document_from_transcript,
split_sentence_text,
)
from sentence_api.models import SentenceBoundary
from sentence_api.transcription import (
Transcript,
TranscriptionSegment,
@@ -13,6 +18,18 @@ SAMPLE_RATE = 16000
VIDEO_HASH = "a" * 64
class WordTimestampTranscriber:
def __init__(self, transcript):
self.transcript = transcript
@property
def available(self):
return True
def transcribe(self, audio_path, language=None):
return self.transcript
def _speech(seconds: float) -> np.ndarray:
t = np.arange(int(seconds * SAMPLE_RATE)) / SAMPLE_RATE
return 0.25 * np.sin(2 * np.pi * 220 * t)
@@ -99,3 +116,45 @@ def test_document_from_transcript_splits_sentences_at_punctuation(tmp_path):
assert document.sentences[1].start_ms == 9500
assert document.sentences[1].end_ms == 24000
assert all(sentence.reference_speech_duration_ms > 0 for sentence in document.sentences)
def test_split_sentence_text_uses_whisper_word_timestamps(tmp_path):
transcript = Transcript(
text="Alpha beta. Gamma delta.",
segments=[
TranscriptionSegment(
start_seconds=0.0,
end_seconds=1.0,
text="Alpha beta. Gamma delta.",
words=[
WordTimestamp(0.0, 0.25, "Alpha"),
WordTimestamp(0.25, 0.5, "beta."),
WordTimestamp(0.5, 0.75, "Gamma"),
WordTimestamp(0.75, 1.0, "delta."),
],
)
],
)
audio_path = tmp_path / "sentence.wav"
audio_path.write_bytes(_wav_bytes(_speech(1.0), SAMPLE_RATE))
source = SentenceBoundary(
index=2,
start_ms=1000,
end_ms=5000,
text="Alpha beta Gamma delta",
language="en",
reference_speech_duration_ms=3800,
)
replacements = split_sentence_text(
transcriber=WordTimestampTranscriber(transcript),
media_path=audio_path,
sentence=source,
text="Alpha beta\nGamma delta",
language="en",
)
assert [(item.text, item.start_ms, item.end_ms) for item in replacements] == [
("Alpha beta", 1000, 3000),
("Gamma delta", 3000, 5000),
]