Skip to main content

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}