optimized PC player
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user