Skip to main content

transcribe_cpp/
result.rs

1//! Owned result types.
2//!
3//! Every result is fully materialized into these types — all text is copied at
4//! the FFI boundary (`CStr -> String`), so no borrowed session pointer ever
5//! escapes to user code (the "copy at the FFI boundary" rule). Holding a
6//! [`Transcript`] across the next `run`/`stream` call is therefore always safe.
7
8use std::ffi::CStr;
9use std::os::raw::c_char;
10
11use transcribe_cpp_sys as sys;
12
13use crate::types::TimestampKind;
14
15/// A fully-materialized transcription result.
16#[derive(Debug, Clone, Default, PartialEq)]
17pub struct Transcript {
18    /// The full transcript text.
19    pub text: String,
20    /// The model's decoded output before family post-processing (diarization
21    /// markers, timestamp/special tokens, tag filtering, whitespace trims).
22    /// Equal to `text` modulo whitespace for families that emit clean text.
23    pub raw_text: String,
24    /// The model-detected language (ISO code), if it predicted one. `None`
25    /// when a language hint was given, the model lacks LID, or none applies.
26    pub language: Option<String>,
27    /// The finest timestamp granularity actually populated below.
28    pub timestamp_kind: TimestampKind,
29    /// Transcript segments. Speaker-attributed models may return untimed rows
30    /// even when timestamps are disabled.
31    pub segments: Vec<Segment>,
32    /// Diarized speaker turns (empty unless diarization was enabled and found).
33    pub speaker_segments: Vec<SpeakerSegment>,
34    /// Word rows (empty unless word-or-finer timestamps were produced).
35    pub words: Vec<Word>,
36    /// Token rows (empty unless token timestamps were produced).
37    pub tokens: Vec<Token>,
38    /// Stage timings for the run.
39    pub timings: Timings,
40}
41
42/// One segment row. Times are milliseconds relative to the original audio.
43#[derive(Debug, Clone, Default, PartialEq)]
44pub struct Segment {
45    pub t0_ms: i64,
46    pub t1_ms: i64,
47    pub first_word: i32,
48    pub n_words: i32,
49    pub first_token: i32,
50    pub n_tokens: i32,
51    pub text: String,
52    /// 1-based speaker id; zero means no attribution.
53    pub speaker_id: i32,
54}
55
56/// One diarized speaker turn. Zero times mean the model attributed text but
57/// did not provide turn timing; `p` is NaN when unavailable.
58#[derive(Debug, Clone, Default, PartialEq)]
59pub struct SpeakerSegment {
60    pub t0_ms: i64,
61    pub t1_ms: i64,
62    pub speaker_id: i32,
63    pub p: f32,
64}
65
66/// One word row.
67#[derive(Debug, Clone, Default, PartialEq)]
68pub struct Word {
69    pub t0_ms: i64,
70    pub t1_ms: i64,
71    pub seg_index: i32,
72    pub first_token: i32,
73    pub n_tokens: i32,
74    pub text: String,
75}
76
77/// One token row. `p` is a per-token confidence hint (NaN when the family
78/// produces none); its exact meaning is family-specific.
79#[derive(Debug, Clone, Default, PartialEq)]
80pub struct Token {
81    pub id: i32,
82    pub p: f32,
83    pub t0_ms: i64,
84    pub t1_ms: i64,
85    pub seg_index: i32,
86    pub word_index: i32,
87    pub text: String,
88}
89
90/// Per-run stage timings, in milliseconds. Zero means "unknown / not measured".
91#[derive(Debug, Clone, Copy, Default, PartialEq)]
92pub struct Timings {
93    pub load_ms: f32,
94    pub mel_ms: f32,
95    pub encode_ms: f32,
96    pub decode_ms: f32,
97}
98
99/// Copy a borrowed C string into an owned `String` (empty for NULL).
100pub(crate) fn owned_str(ptr: *const c_char) -> String {
101    if ptr.is_null() {
102        return String::new();
103    }
104    unsafe { CStr::from_ptr(ptr) }
105        .to_string_lossy()
106        .into_owned()
107}
108
109/// Copy a borrowed C string into `Some(String)`, or `None` for NULL/empty.
110pub(crate) fn owned_opt_str(ptr: *const c_char) -> Option<String> {
111    if ptr.is_null() {
112        return None;
113    }
114    let s = unsafe { CStr::from_ptr(ptr) }
115        .to_string_lossy()
116        .into_owned();
117    if s.is_empty() {
118        None
119    } else {
120        Some(s)
121    }
122}
123
124impl Segment {
125    pub(crate) fn from_raw(raw: &sys::transcribe_segment) -> Self {
126        Segment {
127            t0_ms: raw.t0_ms,
128            t1_ms: raw.t1_ms,
129            first_word: raw.first_word,
130            n_words: raw.n_words,
131            first_token: raw.first_token,
132            n_tokens: raw.n_tokens,
133            text: owned_str(raw.text),
134            speaker_id: raw.speaker_id,
135        }
136    }
137}
138
139impl SpeakerSegment {
140    pub(crate) fn from_raw(raw: &sys::transcribe_speaker_segment) -> Self {
141        SpeakerSegment {
142            t0_ms: raw.t0_ms,
143            t1_ms: raw.t1_ms,
144            speaker_id: raw.speaker_id,
145            p: raw.p,
146        }
147    }
148}
149
150impl Word {
151    pub(crate) fn from_raw(raw: &sys::transcribe_word) -> Self {
152        Word {
153            t0_ms: raw.t0_ms,
154            t1_ms: raw.t1_ms,
155            seg_index: raw.seg_index,
156            first_token: raw.first_token,
157            n_tokens: raw.n_tokens,
158            text: owned_str(raw.text),
159        }
160    }
161}
162
163impl Token {
164    pub(crate) fn from_raw(raw: &sys::transcribe_token) -> Self {
165        Token {
166            id: raw.id,
167            p: raw.p,
168            t0_ms: raw.t0_ms,
169            t1_ms: raw.t1_ms,
170            seg_index: raw.seg_index,
171            word_index: raw.word_index,
172            text: owned_str(raw.text),
173        }
174    }
175}
176
177impl Timings {
178    pub(crate) fn from_raw(raw: &sys::transcribe_timings) -> Self {
179        Timings {
180            load_ms: raw.load_ms,
181            mel_ms: raw.mel_ms,
182            encode_ms: raw.encode_ms,
183            decode_ms: raw.decode_ms,
184        }
185    }
186}