rlx_whisper/transcript.rs
1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! Structured transcription output with segment and word timings.
17
18use serde::{Deserialize, Serialize};
19
20/// One word with start/end times in seconds (absolute, from file start).
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct WordTiming {
23 pub word: String,
24 pub start: f32,
25 pub end: f32,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub prob: Option<f32>,
28}
29
30/// A contiguous speech segment with optional word-level breakdown.
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct TranscriptSegment {
33 pub id: u32,
34 pub start: f32,
35 pub end: f32,
36 pub text: String,
37 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub words: Vec<WordTiming>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub speaker: Option<String>,
41}
42
43/// Full-file transcription result (JSON-serializable).
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct WhisperTranscript {
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub language: Option<String>,
48 /// Audio duration in seconds.
49 pub duration: f32,
50 pub segments: Vec<TranscriptSegment>,
51}
52
53impl WhisperTranscript {
54 /// Concatenate segment text with spaces (no timestamps).
55 pub fn plain_text(&self) -> String {
56 self.segments
57 .iter()
58 .map(|s| s.text.trim())
59 .filter(|t| !t.is_empty())
60 .collect::<Vec<_>>()
61 .join(" ")
62 }
63}
64
65/// Word-level alignment mode for [`crate::pipeline::WhisperPipeline`].
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
67pub enum WordAlignMode {
68 #[default]
69 Off,
70 /// Cross-attention + DTW (whisper.cpp / faster-whisper style).
71 Dtw,
72 /// Wav2Vec2 CTC forced alignment (WhisperX style).
73 Wav2Vec2,
74}
75
76impl WordAlignMode {
77 /// Parse CLI / config strings: `off`, `dtw`, `wav2vec2`, `w2v`.
78 pub fn parse(s: &str) -> Option<Self> {
79 match s.to_ascii_lowercase().as_str() {
80 "off" | "none" => Some(Self::Off),
81 "dtw" => Some(Self::Dtw),
82 "wav2vec2" | "w2v" => Some(Self::Wav2Vec2),
83 _ => None,
84 }
85 }
86}
87
88/// Speaker turn from diarization (absolute seconds).
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90pub struct SpeakerTurn {
91 pub speaker_id: usize,
92 pub start: f32,
93 pub end: f32,
94}