optimized PC player

This commit is contained in:
2026-09-11 12:15:51 +08:00
parent 07261d092b
commit 4b71c6c615
5 changed files with 317 additions and 84 deletions

View File

@@ -246,6 +246,59 @@ _ABBREVIATION_RE = re.compile(
r"e\.g\.|i\.e\.|no\.|approx\.|fig\.|inc\.|ltd\.|jr\.|sr\.|u\.s\.|u\.k\.)"
r"(?![A-Za-z0-9])"
)
_SPEAKER_TAG_RE = re.compile(r"\[(S\d+)\]")
def _expand_speaker_segments(
segment: TranscriptionSegment,
) -> List[TranscriptionSegment]:
"""Split a segment at inline ``[S01]`` speaker tags and strip the tags.
MOSS sometimes embeds speaker tags directly in the text (rather than in
the ``speaker`` JSON field). Each tag marks the start of a new speaker
turn, so the text is split there and the tags are removed from the
visible transcription.
"""
text = segment.text
matches = list(_SPEAKER_TAG_RE.finditer(text))
if not matches:
return [segment]
sub_segments: List[TranscriptionSegment] = []
total = len(matches)
duration = segment.end_seconds - segment.start_seconds
for index, match in enumerate(matches):
start_pos = match.end()
if index + 1 < total:
end_pos = matches[index + 1].start()
else:
end_pos = len(text)
piece = text[start_pos:end_pos].strip()
if not piece:
continue
# Allocate time proportionally across sub-segments.
frac_start = index / total
frac_end = (index + 1) / total
sub_start = segment.start_seconds + duration * frac_start
sub_end = segment.start_seconds + duration * frac_end
words = None
if segment.words:
sub_words = [
w
for w in segment.words
if w.start_seconds >= sub_start and w.end_seconds <= sub_end
]
words = sub_words or None
sub_segments.append(
TranscriptionSegment(
start_seconds=sub_start,
end_seconds=sub_end,
text=piece,
speaker=match.group(1),
words=words,
)
)
return sub_segments or [segment]
def split_sentences_at_punctuation(
@@ -257,14 +310,18 @@ def split_sentences_at_punctuation(
across segments and a sentence is closed at periods/question marks. Commas
are also eligible boundaries, but only when the text before the comma has
enough words; otherwise the comma stays with the following text.
Inline ``[S01]`` speaker tags are stripped and each speaker turn always
starts a new sentence, even when no sentence-ending punctuation exists.
"""
sentences: List[TranscriptionSegment] = []
pieces: List[str] = []
start_seconds: Optional[float] = None
last_segment_end: Optional[float] = None
current_speaker: Optional[str] = None
def flush(end_seconds: float) -> None:
nonlocal pieces, start_seconds
nonlocal pieces, start_seconds, current_speaker
text = " ".join(piece for piece in pieces if piece).strip()
if text and start_seconds is not None and end_seconds > start_seconds:
sentences.append(
@@ -272,19 +329,31 @@ def split_sentences_at_punctuation(
start_seconds=start_seconds,
end_seconds=end_seconds,
text=text,
speaker=current_speaker,
)
)
pieces = []
start_seconds = None
current_speaker = None
expanded: List[TranscriptionSegment] = []
for seg in segments:
expanded.extend(_expand_speaker_segments(seg))
for segment in sorted(
segments, key=lambda item: (item.start_seconds, item.end_seconds)
expanded, key=lambda item: (item.start_seconds, item.end_seconds)
):
text = segment.text.strip()
if not text:
continue
# Each speaker-tagged sub-segment always starts a new sentence,
# regardless of whether the speaker actually changed — MOSS emits
# a tag per phrase/line even for the same speaker.
if pieces and segment.speaker is not None:
flush(segment.start_seconds)
if not pieces:
start_seconds = segment.start_seconds
current_speaker = segment.speaker
seg_pieces: List[Tuple[str, Optional[str]]] = []
cursor = 0
for match in _PUNCTUATION_BOUNDARY.finditer(text):
@@ -322,6 +391,7 @@ def split_sentences_at_punctuation(
end_seconds = ends[index] if index < len(ends) else segment.end_seconds
flush(end_seconds)
start_seconds = end_seconds
current_speaker = segment.speaker
if pieces:
flush(last_segment_end if last_segment_end is not None else start_seconds)
return sentences