1use std::path::PathBuf;
4
5use audio_analysis_transcription::TranscriptionPipelineResponse;
6use serde::{Deserialize, Serialize};
7
8use super::{
9 AutomaticWorkflowSelection, AutomaticWorkflowSelectionResource, ConfigSelection,
10 ModelResourceSource, OutputFormat,
11};
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct NativeWhisperxReport {
16 pub response: TranscriptionPipelineResponse,
17 #[serde(default)]
18 pub output_files: Vec<OutputFile>,
19 #[serde(
20 default,
21 skip_serializing_if = "NativeWorkflowSelectionReport::is_empty"
22 )]
23 pub workflow_selection: NativeWorkflowSelectionReport,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct OutputFile {
29 pub format: OutputFormat,
30 pub path: PathBuf,
31}
32
33#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct NativeWorkflowSelectionReport {
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub selected_vad: Option<SelectedVadReport>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub selected_diarization_model: Option<SelectedDiarizationModelReport>,
40}
41
42impl NativeWorkflowSelectionReport {
43 pub fn from_selection(selection: &AutomaticWorkflowSelection) -> Self {
44 let selected_vad = selection
45 .decisions
46 .iter()
47 .find(|decision| decision.target == AutomaticWorkflowSelectionResource::Vad)
48 .map(|decision| SelectedVadReport {
49 method: selection.config.vad.method.as_whisperx_arg().to_string(),
50 selection: decision.selection,
51 resource_source: decision.source,
52 });
53 let selected_diarization_model = selection
54 .config
55 .diarization
56 .enabled
57 .then(|| {
58 selection
59 .decisions
60 .iter()
61 .find(|decision| {
62 decision.target == AutomaticWorkflowSelectionResource::Diarization
63 })
64 .map(|decision| SelectedDiarizationModelReport {
65 model_id: selection.config.diarization.model_id.clone(),
66 selection: decision.selection,
67 resource_source: decision.source,
68 })
69 })
70 .flatten();
71
72 Self {
73 selected_vad,
74 selected_diarization_model,
75 }
76 }
77
78 pub fn is_empty(&self) -> bool {
79 self.selected_vad.is_none() && self.selected_diarization_model.is_none()
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct SelectedVadReport {
86 pub method: String,
87 pub selection: ConfigSelection,
88 pub resource_source: ModelResourceSource,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct SelectedDiarizationModelReport {
94 pub model_id: String,
95 pub selection: ConfigSelection,
96 pub resource_source: ModelResourceSource,
97}
98
99#[cfg(test)]
100mod tests {
101 use std::path::PathBuf;
102
103 use super::*;
104 use crate::config::{
105 AlignmentConfig, AsrConfig, DiarizationConfig, InputSource, NativeWhisperxConfig,
106 OutputConfig, TranslationConfig, VadConfig, VadMethod,
107 };
108
109 #[test]
110 fn automatic_selection_report_summarizes_automatic_choices_without_paths_or_tokens() {
111 let secret = "hf_secret_token";
112 let selection = AutomaticWorkflowSelection {
113 config: NativeWhisperxConfig {
114 input: InputSource::Path {
115 path: PathBuf::from("sample.wav"),
116 },
117 asr: AsrConfig::default(),
118 translation: TranslationConfig::default(),
119 vad: VadConfig {
120 method: VadMethod::Pyannote,
121 ..VadConfig::default()
122 },
123 alignment: AlignmentConfig::default(),
124 diarization: DiarizationConfig {
125 enabled: true,
126 model_id: "pyannote/speaker-diarization-community-1".to_string(),
127 hf_token: Some(secret.to_string()),
128 ..DiarizationConfig::default()
129 },
130 output: OutputConfig::default(),
131 },
132 decisions: vec![
133 automatic_decision(
134 AutomaticWorkflowSelectionResource::Vad,
135 Some("pyannote/segmentation-3.0"),
136 ),
137 automatic_decision(
138 AutomaticWorkflowSelectionResource::Diarization,
139 Some("pyannote/speaker-diarization-community-1"),
140 ),
141 ],
142 };
143
144 let report = NativeWorkflowSelectionReport::from_selection(&selection);
145
146 assert_eq!(
147 report.selected_vad,
148 Some(SelectedVadReport {
149 method: "pyannote".to_string(),
150 selection: ConfigSelection::Automatic,
151 resource_source: ModelResourceSource::ModelDir,
152 })
153 );
154 assert_eq!(
155 report.selected_diarization_model,
156 Some(SelectedDiarizationModelReport {
157 model_id: "pyannote/speaker-diarization-community-1".to_string(),
158 selection: ConfigSelection::Automatic,
159 resource_source: ModelResourceSource::ModelDir,
160 })
161 );
162 let json = serde_json::to_string(&report).expect("selection report json");
163 assert!(!json.contains("/models"));
164 assert!(!json.contains(secret));
165 }
166
167 #[test]
168 fn automatic_selection_report_summarizes_explicit_choices() {
169 let selection = AutomaticWorkflowSelection {
170 config: NativeWhisperxConfig {
171 input: InputSource::Path {
172 path: PathBuf::from("sample.wav"),
173 },
174 asr: AsrConfig::default(),
175 translation: TranslationConfig::default(),
176 vad: VadConfig {
177 selection: ConfigSelection::Explicit,
178 method: VadMethod::Energy,
179 ..VadConfig::default()
180 },
181 alignment: AlignmentConfig::default(),
182 diarization: DiarizationConfig {
183 enabled: true,
184 model_selection: ConfigSelection::Explicit,
185 model_id: "native-spectral-speaker-baseline".to_string(),
186 ..DiarizationConfig::default()
187 },
188 output: OutputConfig::default(),
189 },
190 decisions: vec![
191 explicit_decision(AutomaticWorkflowSelectionResource::Vad, Some("energy")),
192 explicit_decision(
193 AutomaticWorkflowSelectionResource::Diarization,
194 Some("native-spectral-speaker-baseline"),
195 ),
196 ],
197 };
198
199 let report = NativeWorkflowSelectionReport::from_selection(&selection);
200
201 assert_eq!(
202 report.selected_vad,
203 Some(SelectedVadReport {
204 method: "energy".to_string(),
205 selection: ConfigSelection::Explicit,
206 resource_source: ModelResourceSource::ExplicitConfig,
207 })
208 );
209 assert_eq!(
210 report.selected_diarization_model,
211 Some(SelectedDiarizationModelReport {
212 model_id: "native-spectral-speaker-baseline".to_string(),
213 selection: ConfigSelection::Explicit,
214 resource_source: ModelResourceSource::ExplicitConfig,
215 })
216 );
217 }
218
219 fn automatic_decision(
220 target: AutomaticWorkflowSelectionResource,
221 model_id: Option<&str>,
222 ) -> super::super::AutomaticWorkflowSelectionDecision {
223 super::super::AutomaticWorkflowSelectionDecision {
224 target,
225 selection: ConfigSelection::Automatic,
226 model_id: model_id.map(str::to_string),
227 source: ModelResourceSource::ModelDir,
228 path: Some(PathBuf::from("/models/secret")),
229 }
230 }
231
232 fn explicit_decision(
233 target: AutomaticWorkflowSelectionResource,
234 model_id: Option<&str>,
235 ) -> super::super::AutomaticWorkflowSelectionDecision {
236 super::super::AutomaticWorkflowSelectionDecision {
237 target,
238 selection: ConfigSelection::Explicit,
239 model_id: model_id.map(str::to_string),
240 source: ModelResourceSource::ExplicitConfig,
241 path: Some(PathBuf::from("/models/secret")),
242 }
243 }
244}