#[cfg(feature = "backend-ctranslate2")]
use super::ctranslate2::CT2Backend;
use super::traits::TranscriptionError;
#[cfg(feature = "backend-whisper-cpp")]
use super::whisper_cpp::WhisperCppBackend;
use super::{BackendConfig, BackendType, TranscriptionBackend};
use std::path::Path;
#[cfg(any(
not(feature = "backend-ctranslate2"),
not(feature = "backend-whisper-cpp"),
not(feature = "backend-moonshine"),
not(feature = "backend-parakeet"),
not(feature = "backend-nemotron")
))]
fn disabled_backend_error(feature: &str, backend: BackendType) -> TranscriptionError {
TranscriptionError::BackendNotImplemented(format!(
"{backend} backend is disabled; enable speechcore feature `{feature}`"
))
}
pub async fn create_backend(
backend_type: BackendType,
model_path: impl AsRef<Path>,
backend_config: &BackendConfig,
) -> Result<TranscriptionBackend, TranscriptionError> {
let model_path = model_path.as_ref();
let _ = backend_config;
#[cfg(all(
not(feature = "backend-ctranslate2"),
not(feature = "backend-whisper-cpp"),
not(feature = "backend-moonshine"),
not(feature = "backend-parakeet")
))]
let _ = model_path;
match backend_type {
BackendType::CTranslate2 => {
#[cfg(feature = "backend-ctranslate2")]
{
let ct2_backend = CT2Backend::new(model_path, backend_config)?;
Ok(TranscriptionBackend::CTranslate2(Box::new(ct2_backend)))
}
#[cfg(not(feature = "backend-ctranslate2"))]
{
Err(disabled_backend_error("backend-ctranslate2", backend_type))
}
}
BackendType::WhisperCpp => {
#[cfg(feature = "backend-whisper-cpp")]
{
let model_path_ref = model_path;
if !model_path_ref.exists() {
let model_name = extract_whisper_cpp_model_name(model_path_ref)?;
tracing::info!("WhisperCpp model not found, downloading: {}", model_name);
crate::download::download_whisper_cpp_model(
&model_name,
&backend_config.quantization_level,
)
.await
.map_err(|e| {
TranscriptionError::ModelNotAvailable(format!(
"Failed to download whisper.cpp model: {}",
e
))
})?;
}
let whisper_cpp_backend = WhisperCppBackend::new(model_path, backend_config)?;
Ok(TranscriptionBackend::WhisperCpp(Box::new(
whisper_cpp_backend,
)))
}
#[cfg(not(feature = "backend-whisper-cpp"))]
{
Err(disabled_backend_error("backend-whisper-cpp", backend_type))
}
}
BackendType::Parakeet => {
#[cfg(feature = "backend-parakeet")]
{
let parakeet_backend =
super::parakeet::ParakeetBackend::new(model_path, backend_config)?;
Ok(TranscriptionBackend::Parakeet(Box::new(parakeet_backend)))
}
#[cfg(not(feature = "backend-parakeet"))]
{
Err(disabled_backend_error("backend-parakeet", backend_type))
}
}
BackendType::Moonshine => {
#[cfg(feature = "backend-moonshine")]
{
let moonshine_backend =
super::moonshine::MoonshineBackend::new(model_path, backend_config)?;
Ok(TranscriptionBackend::Moonshine(Box::new(moonshine_backend)))
}
#[cfg(not(feature = "backend-moonshine"))]
{
Err(disabled_backend_error("backend-moonshine", backend_type))
}
}
BackendType::Nemotron => {
#[cfg(feature = "backend-nemotron")]
{
let nemotron_backend =
super::nemotron::NemotronBackend::new(model_path, backend_config)?;
Ok(TranscriptionBackend::Nemotron(Box::new(nemotron_backend)))
}
#[cfg(not(feature = "backend-nemotron"))]
{
Err(disabled_backend_error("backend-nemotron", backend_type))
}
}
}
}
#[cfg(feature = "backend-whisper-cpp")]
fn extract_whisper_cpp_model_name(path: &Path) -> Result<String, TranscriptionError> {
let filename = path.file_stem().and_then(|s| s.to_str()).ok_or_else(|| {
TranscriptionError::ConfigurationError(format!("Invalid model path: {}", path.display()))
})?;
let name = filename.strip_prefix("ggml-").unwrap_or(filename);
let name = if let Some(pos) = name.rfind("-q") {
let after_q = &name[pos + 2..];
if after_q.chars().next().is_some_and(|c| c.is_ascii_digit()) {
&name[..pos]
} else {
name
}
} else {
name
};
Ok(name.to_string())
}
pub fn validate_model_path(
backend_type: BackendType,
model_path: impl AsRef<Path>,
) -> Result<(), TranscriptionError> {
let path = model_path.as_ref();
if !path.exists() {
return Err(TranscriptionError::IoError(format!(
"Model path does not exist: {}",
path.display()
)));
}
match backend_type {
BackendType::CTranslate2 => {
#[cfg(not(feature = "backend-ctranslate2"))]
{
Err(disabled_backend_error("backend-ctranslate2", backend_type))
}
#[cfg(feature = "backend-ctranslate2")]
{
if !path.is_dir() {
return Err(TranscriptionError::ConfigurationError(
"CTranslate2 models must be directories containing model files".to_string(),
));
}
let model_bin = path.join("model.bin");
if !model_bin.exists() {
return Err(TranscriptionError::ConfigurationError(
"CTranslate2 model directory missing model.bin file".to_string(),
));
}
Ok(())
}
}
BackendType::WhisperCpp => {
#[cfg(not(feature = "backend-whisper-cpp"))]
{
Err(disabled_backend_error("backend-whisper-cpp", backend_type))
}
#[cfg(feature = "backend-whisper-cpp")]
{
if !path.is_file() {
return Err(TranscriptionError::ConfigurationError(
"whisper.cpp models must be .bin files in GGML format".to_string(),
));
}
if path.extension().and_then(|s| s.to_str()) != Some("bin") {
return Err(TranscriptionError::ConfigurationError(
"whisper.cpp models must have .bin extension".to_string(),
));
}
Ok(())
}
}
BackendType::Parakeet => {
#[cfg(not(feature = "backend-parakeet"))]
{
Err(disabled_backend_error("backend-parakeet", backend_type))
}
#[cfg(feature = "backend-parakeet")]
{
if !path.is_dir() {
return Err(TranscriptionError::ConfigurationError(
"Parakeet models must be directories".to_string(),
));
}
super::parakeet::model::ParakeetModel::validate_model_dir(path).map_err(|e| {
TranscriptionError::ConfigurationError(format!(
"Parakeet model directory invalid: {}",
e
))
})
}
}
BackendType::Moonshine => {
#[cfg(not(feature = "backend-moonshine"))]
{
Err(disabled_backend_error("backend-moonshine", backend_type))
}
#[cfg(feature = "backend-moonshine")]
{
if !path.is_dir() {
return Err(TranscriptionError::ConfigurationError(
"Moonshine models must be directories".to_string(),
));
}
super::moonshine::model::MoonshineModel::validate_model_dir(path).map_err(|e| {
TranscriptionError::ConfigurationError(format!(
"Moonshine model directory invalid: {}",
e
))
})
}
}
BackendType::Nemotron => {
#[cfg(not(feature = "backend-nemotron"))]
{
Err(disabled_backend_error("backend-nemotron", backend_type))
}
#[cfg(feature = "backend-nemotron")]
{
if !path.is_dir() {
return Err(TranscriptionError::ConfigurationError(
"Nemotron models must be directories".to_string(),
));
}
super::nemotron::model::NemotronModel::validate_model_dir(path).map_err(|e| {
TranscriptionError::ConfigurationError(format!(
"Nemotron model directory invalid: {}",
e
))
})
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backend_type_display() {
assert_eq!(BackendType::CTranslate2.to_string(), "ctranslate2");
assert_eq!(BackendType::WhisperCpp.to_string(), "whisper_cpp");
assert_eq!(BackendType::Parakeet.to_string(), "parakeet");
}
}