change sentence cut method
This commit is contained in:
@@ -31,6 +31,14 @@ class TranscriptionSegment:
|
||||
end_seconds: float
|
||||
text: str
|
||||
speaker: Optional[str] = None
|
||||
words: Optional[List["WordTimestamp"]] = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WordTimestamp:
|
||||
start_seconds: float
|
||||
end_seconds: float
|
||||
text: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -114,12 +122,23 @@ class MossTranscriber:
|
||||
if not chunk_segments:
|
||||
chunk_segments = _parse_compact_segments(str(payload.get("text") or "").strip())
|
||||
for segment in chunk_segments:
|
||||
words = None
|
||||
if segment.words:
|
||||
words = [
|
||||
WordTimestamp(
|
||||
start_seconds=word.start_seconds + start_seconds,
|
||||
end_seconds=word.end_seconds + start_seconds,
|
||||
text=word.text,
|
||||
)
|
||||
for word in segment.words
|
||||
]
|
||||
segments.append(
|
||||
TranscriptionSegment(
|
||||
start_seconds=segment.start_seconds + start_seconds,
|
||||
end_seconds=segment.end_seconds + start_seconds,
|
||||
text=segment.text,
|
||||
speaker=segment.speaker,
|
||||
words=words,
|
||||
)
|
||||
)
|
||||
segments.sort(key=lambda item: (item.start_seconds, item.end_seconds))
|
||||
@@ -144,17 +163,27 @@ class MossTranscriber:
|
||||
data["language"] = language
|
||||
|
||||
try:
|
||||
response = httpx.post(
|
||||
self.endpoint,
|
||||
data=data,
|
||||
files={"file": (filename, audio_file, content_type)},
|
||||
timeout=httpx.Timeout(self.timeout_seconds, connect=30),
|
||||
word_payload = dict(data)
|
||||
word_payload["timestamp_granularities[]"] = "word"
|
||||
response = self._post_once(
|
||||
word_payload, audio_file, filename, content_type
|
||||
)
|
||||
if response.status_code in (400, 422):
|
||||
audio_file.seek(0)
|
||||
response = self._post_once(data, audio_file, filename, content_type)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except (httpx.HTTPError, ValueError) as exc:
|
||||
raise RuntimeError(f"MOSS transcription request failed: {exc}") from exc
|
||||
|
||||
def _post_once(self, data: dict, audio_file, filename: str, content_type: str):
|
||||
return httpx.post(
|
||||
self.endpoint,
|
||||
data=data,
|
||||
files={"file": (filename, audio_file, content_type)},
|
||||
timeout=httpx.Timeout(self.timeout_seconds, connect=30),
|
||||
)
|
||||
|
||||
|
||||
def _parse_json_segments(
|
||||
raw_segments, compression_limit: Optional[float] = None
|
||||
@@ -186,11 +215,126 @@ def _parse_json_segments(
|
||||
end_seconds=end,
|
||||
text=text,
|
||||
speaker=str(speaker) if speaker is not None else None,
|
||||
words=_parse_word_timestamps(item.get("words")),
|
||||
)
|
||||
)
|
||||
return segments
|
||||
|
||||
|
||||
def _parse_word_timestamps(raw_words) -> Optional[List[WordTimestamp]]:
|
||||
if not isinstance(raw_words, list):
|
||||
return None
|
||||
words = []
|
||||
for item in raw_words:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
try:
|
||||
start = float(item["start"])
|
||||
end = float(item["end"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
continue
|
||||
text = str(item.get("word") or "").strip()
|
||||
if text and end > start:
|
||||
words.append(WordTimestamp(start_seconds=start, end_seconds=end, text=text))
|
||||
return words or None
|
||||
|
||||
|
||||
_PERIOD_BOUNDARY = re.compile(r"\.+")
|
||||
|
||||
|
||||
def split_segment_by_periods(
|
||||
segment: TranscriptionSegment,
|
||||
) -> List[TranscriptionSegment]:
|
||||
"""Split one whisper segment into sentences at periods (".")."""
|
||||
text = segment.text.strip()
|
||||
if not text:
|
||||
return []
|
||||
pieces: List[str] = []
|
||||
cursor = 0
|
||||
for match in _PERIOD_BOUNDARY.finditer(text):
|
||||
piece = text[cursor:match.end()].strip()
|
||||
if piece.strip(".").strip():
|
||||
pieces.append(piece)
|
||||
cursor = match.end()
|
||||
tail = text[cursor:].strip()
|
||||
if tail:
|
||||
pieces.append(tail)
|
||||
if len(pieces) <= 1:
|
||||
return [segment] if pieces else []
|
||||
ends = _sentence_end_seconds(segment, pieces)
|
||||
sub_segments: List[TranscriptionSegment] = []
|
||||
start = segment.start_seconds
|
||||
for piece, end in zip(pieces, ends):
|
||||
if end > start:
|
||||
sub_segments.append(
|
||||
TranscriptionSegment(
|
||||
start_seconds=start,
|
||||
end_seconds=end,
|
||||
text=piece,
|
||||
speaker=segment.speaker,
|
||||
)
|
||||
)
|
||||
start = end
|
||||
return sub_segments
|
||||
|
||||
|
||||
def _sentence_end_seconds(
|
||||
segment: TranscriptionSegment, pieces: List[str]
|
||||
) -> List[float]:
|
||||
lengths = [len(piece) for piece in pieces]
|
||||
total = sum(lengths)
|
||||
if segment.words and len(segment.words) >= len(pieces):
|
||||
word_ends = _word_boundary_ends(segment, lengths)
|
||||
if _valid_boundaries(segment, word_ends):
|
||||
return word_ends + [segment.end_seconds]
|
||||
return _proportional_ends(segment, lengths) + [segment.end_seconds]
|
||||
|
||||
|
||||
def _word_boundary_ends(
|
||||
segment: TranscriptionSegment, lengths: List[int]
|
||||
) -> List[float]:
|
||||
targets = [sum(lengths[:count]) for count in range(1, len(lengths))]
|
||||
buffer = ""
|
||||
word_indexes: List[int] = []
|
||||
target_index = 0
|
||||
for word_index, word in enumerate(segment.words):
|
||||
buffer += (" " if buffer else "") + word.text
|
||||
while target_index < len(targets) and len(buffer) >= targets[target_index]:
|
||||
word_indexes.append(word_index)
|
||||
target_index += 1
|
||||
if len(word_indexes) != len(targets):
|
||||
return []
|
||||
return [segment.words[index].end_seconds for index in word_indexes]
|
||||
|
||||
|
||||
def _valid_boundaries(
|
||||
segment: TranscriptionSegment, ends: List[float]
|
||||
) -> bool:
|
||||
previous = segment.start_seconds
|
||||
for end in ends:
|
||||
if not segment.start_seconds < end < segment.end_seconds:
|
||||
return False
|
||||
if end <= previous:
|
||||
return False
|
||||
previous = end
|
||||
return True
|
||||
|
||||
|
||||
def _proportional_ends(
|
||||
segment: TranscriptionSegment, lengths: List[int]
|
||||
) -> List[float]:
|
||||
total = sum(lengths)
|
||||
ends = []
|
||||
cumulative = 0
|
||||
for length in lengths[:-1]:
|
||||
cumulative += length
|
||||
ends.append(
|
||||
segment.start_seconds
|
||||
+ (cumulative / total) * (segment.end_seconds - segment.start_seconds)
|
||||
)
|
||||
return ends
|
||||
|
||||
|
||||
def _wav_duration_seconds(path: Path) -> Optional[float]:
|
||||
try:
|
||||
with wave.open(str(path), "rb") as wav:
|
||||
|
||||
Reference in New Issue
Block a user