use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf, time::Duration};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum VoirsError {
#[error("Voice '{voice}' not found. Available voices: {available:?}")]
VoiceNotFound {
voice: String,
available: Vec<String>,
suggestions: Vec<String>,
},
#[error("Voice '{voice}' is unavailable: {reason}")]
VoiceUnavailable { voice: String, reason: String },
#[error("Voice '{voice}' has invalid configuration: {issue}")]
VoiceConfigurationInvalid { voice: String, issue: String },
#[error("Synthesis failed for text: '{text}' (length: {text_length})")]
SynthesisFailed {
text: String,
text_length: usize,
stage: SynthesisStage,
#[source]
cause: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Synthesis quality degraded: {reason}")]
SynthesisQualityDegraded {
reason: String,
metrics: QualityMetrics,
},
#[error("Synthesis was interrupted at stage '{stage}'")]
SynthesisInterrupted {
stage: SynthesisStage,
reason: String,
},
#[error("Device error ({device}): {message}")]
DeviceError {
device: String,
message: String,
recovery_hint: Option<String>,
},
#[error("Device '{device}' is not available")]
DeviceNotAvailable {
device: String,
alternatives: Vec<String>,
},
#[error("Device '{device}' is not supported")]
UnsupportedDevice { device: String },
#[error(
"GPU out of memory on device '{device}': {used_mb}MB used, {available_mb}MB available"
)]
GpuOutOfMemory {
device: String,
used_mb: u32,
available_mb: u32,
},
#[error("I/O error during '{operation}' at path '{path}'")]
IoError {
path: PathBuf,
operation: IoOperation,
#[source]
source: std::io::Error,
},
#[error("Unsupported file format '{format}' for path '{path}'")]
UnsupportedFileFormat { path: PathBuf, format: String },
#[error("File is corrupted or invalid: '{path}'")]
FileCorrupted { path: PathBuf, reason: String },
#[error("Configuration error in field '{field}': {message}")]
ConfigError { field: String, message: String },
#[error("Invalid configuration for field '{field}': {reason}")]
InvalidConfiguration {
field: String,
value: String,
reason: String,
valid_values: Option<Vec<String>>,
},
#[error("Configuration migration failed from version '{from}' to '{to}': {reason}")]
ConfigMigrationFailed {
from: String,
to: String,
reason: String,
},
#[error("Model error for '{model_type}': {message}")]
ModelError {
model_type: ModelType,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Model '{model_name}' not found at path '{path}'")]
ModelNotFound { model_name: String, path: PathBuf },
#[error("Model version mismatch: expected {expected}, found {found}")]
ModelVersionMismatch { expected: String, found: String },
#[error("Model '{model_name}' is incompatible with device '{device}'")]
ModelDeviceIncompatible { model_name: String, device: String },
#[error("Audio processing error: {message}")]
AudioError {
message: String,
buffer_info: Option<AudioBufferInfo>,
},
#[error("Audio format conversion failed from '{from}' to '{to}': {reason}")]
AudioFormatConversionFailed {
from: String,
to: String,
reason: String,
},
#[error("Audio buffer {error_type}: {details}")]
AudioBufferError {
error_type: AudioBufferErrorType,
details: String,
},
#[error("G2P conversion failed for text '{text}': {message}")]
G2pError {
text: String,
message: String,
language: Option<String>,
},
#[error("Text preprocessing failed: {message}")]
TextPreprocessingError {
message: String,
text_sample: String,
},
#[error("Language '{language}' is not supported")]
LanguageNotSupported {
language: String,
supported: Vec<String>,
},
#[error("Network error: {message}")]
NetworkError {
message: String,
retry_count: u32,
max_retries: u32,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Download failed for '{url}': {reason}")]
DownloadFailed {
url: String,
reason: String,
bytes_downloaded: u64,
total_bytes: Option<u64>,
},
#[error("Authentication failed for service '{service}': {reason}")]
AuthenticationFailed { service: String, reason: String },
#[error("Pipeline is not ready for operation")]
PipelineNotReady,
#[error("Invalid state transition from '{from}' to '{to}': {reason}")]
InvalidStateTransition {
from: String,
to: String,
reason: String,
},
#[error("Component '{component}' synchronization failed: {reason}")]
ComponentSynchronizationFailed { component: String, reason: String },
#[error("Operation '{operation}' timed out after {duration:?}")]
TimeoutError {
operation: String,
duration: Duration,
expected_duration: Option<Duration>,
},
#[error("Performance degradation detected: {metric} = {value} (threshold: {threshold})")]
PerformanceDegradation {
metric: String,
value: f64,
threshold: f64,
},
#[error("Real-time constraint violated: {constraint}")]
RealTimeConstraintViolation { constraint: String },
#[error("Out of memory: {message}")]
OutOfMemory { message: String, requested_mb: u32 },
#[error("Resource '{resource}' exhausted: {details}")]
ResourceExhausted { resource: String, details: String },
#[error("Memory leak detected in component '{component}': {leaked_mb}MB")]
MemoryLeak { component: String, leaked_mb: u32 },
#[error("Serialization error ({format}): {message}")]
SerializationError { format: String, message: String },
#[error("Data validation failed for '{data_type}': {reason}")]
DataValidationFailed { data_type: String, reason: String },
#[error("Schema mismatch: expected version {expected}, found {found}")]
SchemaMismatch { expected: String, found: String },
#[error("Internal error in '{component}': {message}")]
InternalError { component: String, message: String },
#[error("Feature '{feature}' is not implemented")]
NotImplemented { feature: String },
#[error("Feature '{feature}' is not available: {reason}")]
FeatureUnavailable { feature: String, reason: String },
#[error("Deprecated functionality '{feature}' used. Use '{replacement}' instead")]
DeprecatedFunctionality {
feature: String,
replacement: String,
},
#[error("Emotion control error: {message}")]
EmotionError {
message: String,
emotion_type: Option<String>,
intensity: Option<f32>,
},
#[error("Emotion '{emotion}' is not supported. Available emotions: {available:?}")]
EmotionNotSupported {
emotion: String,
available: Vec<String>,
},
#[error("Emotion intensity {intensity} is out of range (0.0-1.0)")]
EmotionIntensityOutOfRange { intensity: f32 },
#[error("Emotion configuration invalid: {reason}")]
EmotionConfigurationInvalid { reason: String },
#[error("Voice cloning error: {message}")]
VoiceCloningError {
message: String,
cloning_method: Option<String>,
},
#[error("Voice cloning source is invalid: {reason}")]
VoiceCloningSourceInvalid {
reason: String,
audio_duration: Option<f32>,
required_duration: Option<f32>,
},
#[error("Voice cloning quality {quality} is below threshold {threshold}")]
VoiceCloningQualityTooLow { quality: f32, threshold: f32 },
#[error("Voice cloning method '{method}' is not supported")]
VoiceCloningMethodNotSupported {
method: String,
supported: Vec<String>,
},
#[error("Voice conversion error: {message}")]
VoiceConversionError {
message: String,
conversion_type: Option<String>,
},
#[error("Voice conversion target is invalid: {reason}")]
VoiceConversionTargetInvalid { reason: String, target: String },
#[error("Voice conversion from '{from}' to '{to}' is not supported")]
VoiceConversionNotSupported { from: String, to: String },
#[error("Real-time conversion buffer overflow: {details}")]
RealTimeConversionBufferOverflow { details: String, buffer_size: usize },
#[error("Singing synthesis error: {message}")]
SingingError {
message: String,
voice_type: Option<String>,
},
#[error("Musical score parsing error: {message}")]
MusicalScoreParsingError {
message: String,
line: Option<usize>,
column: Option<usize>,
},
#[error("Musical note '{note}' is invalid: {reason}")]
MusicalNoteInvalid { note: String, reason: String },
#[error("Singing technique '{technique}' is not supported")]
SingingTechniqueNotSupported {
technique: String,
supported: Vec<String>,
},
#[error("Tempo {tempo} BPM is out of range ({min_tempo}-{max_tempo})")]
TempoOutOfRange {
tempo: f32,
min_tempo: f32,
max_tempo: f32,
},
#[error("3D spatial audio error: {message}")]
SpatialAudioError {
message: String,
position: Option<String>,
},
#[error("HRTF processing error: {message}")]
HrtfProcessingError {
message: String,
sample_rate: Option<u32>,
},
#[error("3D position ({x}, {y}, {z}) is invalid: {reason}")]
Position3DInvalid {
x: f32,
y: f32,
z: f32,
reason: String,
},
#[error("Room acoustics configuration invalid: {reason}")]
RoomAcousticsConfigurationInvalid {
reason: String,
room_size: Option<String>,
},
#[error("Binaural rendering error: {message}")]
BinauralRenderingError {
message: String,
channel_count: Option<u32>,
},
#[error("Model load error: {0}")]
ModelLoadError(String),
#[error("Inference error: {0}")]
InferenceError(String),
}
impl Clone for VoirsError {
fn clone(&self) -> Self {
match self {
Self::VoiceNotFound {
voice,
available,
suggestions,
} => Self::VoiceNotFound {
voice: voice.clone(),
available: available.clone(),
suggestions: suggestions.clone(),
},
Self::VoiceUnavailable { voice, reason } => Self::VoiceUnavailable {
voice: voice.clone(),
reason: reason.clone(),
},
Self::VoiceConfigurationInvalid { voice, issue } => Self::VoiceConfigurationInvalid {
voice: voice.clone(),
issue: issue.clone(),
},
Self::SynthesisFailed {
text,
text_length,
stage,
cause: _,
} => Self::SynthesisFailed {
text: text.clone(),
text_length: *text_length,
stage: *stage,
cause: format!("Original error (cannot clone): {}", "error").into(),
},
Self::SynthesisQualityDegraded { reason, metrics } => Self::SynthesisQualityDegraded {
reason: reason.clone(),
metrics: metrics.clone(),
},
Self::SynthesisInterrupted { stage, reason } => Self::SynthesisInterrupted {
stage: *stage,
reason: reason.clone(),
},
Self::DeviceError {
device,
message,
recovery_hint,
} => Self::DeviceError {
device: device.clone(),
message: message.clone(),
recovery_hint: recovery_hint.clone(),
},
Self::DeviceNotAvailable {
device,
alternatives,
} => Self::DeviceNotAvailable {
device: device.clone(),
alternatives: alternatives.clone(),
},
Self::UnsupportedDevice { device } => Self::UnsupportedDevice {
device: device.clone(),
},
Self::GpuOutOfMemory {
device,
used_mb,
available_mb,
} => Self::GpuOutOfMemory {
device: device.clone(),
used_mb: *used_mb,
available_mb: *available_mb,
},
Self::IoError {
path,
operation,
source: _,
} => Self::IoError {
path: path.clone(),
operation: *operation,
source: std::io::Error::other("Cloned error"),
},
Self::UnsupportedFileFormat { path, format } => Self::UnsupportedFileFormat {
path: path.clone(),
format: format.clone(),
},
Self::FileCorrupted { path, reason } => Self::FileCorrupted {
path: path.clone(),
reason: reason.clone(),
},
Self::ConfigError { field, message } => Self::ConfigError {
field: field.clone(),
message: message.clone(),
},
Self::InvalidConfiguration {
field,
value,
reason,
valid_values,
} => Self::InvalidConfiguration {
field: field.clone(),
value: value.clone(),
reason: reason.clone(),
valid_values: valid_values.clone(),
},
Self::ConfigMigrationFailed { from, to, reason } => Self::ConfigMigrationFailed {
from: from.clone(),
to: to.clone(),
reason: reason.clone(),
},
Self::ModelError {
model_type,
message,
source: _,
} => Self::ModelError {
model_type: *model_type,
message: message.clone(),
source: None,
},
Self::ModelNotFound { model_name, path } => Self::ModelNotFound {
model_name: model_name.clone(),
path: path.clone(),
},
Self::ModelVersionMismatch { expected, found } => Self::ModelVersionMismatch {
expected: expected.clone(),
found: found.clone(),
},
Self::ModelDeviceIncompatible { model_name, device } => Self::ModelDeviceIncompatible {
model_name: model_name.clone(),
device: device.clone(),
},
Self::AudioError {
message,
buffer_info,
} => Self::AudioError {
message: message.clone(),
buffer_info: buffer_info.clone(),
},
Self::AudioFormatConversionFailed { from, to, reason } => {
Self::AudioFormatConversionFailed {
from: from.clone(),
to: to.clone(),
reason: reason.clone(),
}
}
Self::AudioBufferError {
error_type,
details,
} => Self::AudioBufferError {
error_type: *error_type,
details: details.clone(),
},
Self::G2pError {
text,
message,
language,
} => Self::G2pError {
text: text.clone(),
message: message.clone(),
language: language.clone(),
},
Self::TextPreprocessingError {
message,
text_sample,
} => Self::TextPreprocessingError {
message: message.clone(),
text_sample: text_sample.clone(),
},
Self::LanguageNotSupported {
language,
supported,
} => Self::LanguageNotSupported {
language: language.clone(),
supported: supported.clone(),
},
Self::NetworkError {
message,
retry_count,
max_retries,
source: _,
} => Self::NetworkError {
message: message.clone(),
retry_count: *retry_count,
max_retries: *max_retries,
source: None,
},
Self::DownloadFailed {
url,
reason,
bytes_downloaded,
total_bytes,
} => Self::DownloadFailed {
url: url.clone(),
reason: reason.clone(),
bytes_downloaded: *bytes_downloaded,
total_bytes: *total_bytes,
},
Self::AuthenticationFailed { service, reason } => Self::AuthenticationFailed {
service: service.clone(),
reason: reason.clone(),
},
Self::PipelineNotReady => Self::PipelineNotReady,
Self::InvalidStateTransition { from, to, reason } => Self::InvalidStateTransition {
from: from.clone(),
to: to.clone(),
reason: reason.clone(),
},
Self::ComponentSynchronizationFailed { component, reason } => {
Self::ComponentSynchronizationFailed {
component: component.clone(),
reason: reason.clone(),
}
}
Self::TimeoutError {
operation,
duration,
expected_duration,
} => Self::TimeoutError {
operation: operation.clone(),
duration: *duration,
expected_duration: *expected_duration,
},
Self::PerformanceDegradation {
metric,
value,
threshold,
} => Self::PerformanceDegradation {
metric: metric.clone(),
value: *value,
threshold: *threshold,
},
Self::RealTimeConstraintViolation { constraint } => Self::RealTimeConstraintViolation {
constraint: constraint.clone(),
},
Self::OutOfMemory {
message,
requested_mb,
} => Self::OutOfMemory {
message: message.clone(),
requested_mb: *requested_mb,
},
Self::ResourceExhausted { resource, details } => Self::ResourceExhausted {
resource: resource.clone(),
details: details.clone(),
},
Self::MemoryLeak {
component,
leaked_mb,
} => Self::MemoryLeak {
component: component.clone(),
leaked_mb: *leaked_mb,
},
Self::SerializationError { format, message } => Self::SerializationError {
format: format.clone(),
message: message.clone(),
},
Self::DataValidationFailed { data_type, reason } => Self::DataValidationFailed {
data_type: data_type.clone(),
reason: reason.clone(),
},
Self::SchemaMismatch { expected, found } => Self::SchemaMismatch {
expected: expected.clone(),
found: found.clone(),
},
Self::InternalError { component, message } => Self::InternalError {
component: component.clone(),
message: message.clone(),
},
Self::NotImplemented { feature } => Self::NotImplemented {
feature: feature.clone(),
},
Self::DeprecatedFunctionality {
feature,
replacement,
} => Self::DeprecatedFunctionality {
feature: feature.clone(),
replacement: replacement.clone(),
},
Self::EmotionError {
message,
emotion_type,
intensity,
} => Self::EmotionError {
message: message.clone(),
emotion_type: emotion_type.clone(),
intensity: *intensity,
},
Self::EmotionNotSupported { emotion, available } => Self::EmotionNotSupported {
emotion: emotion.clone(),
available: available.clone(),
},
Self::EmotionIntensityOutOfRange { intensity } => Self::EmotionIntensityOutOfRange {
intensity: *intensity,
},
Self::EmotionConfigurationInvalid { reason } => Self::EmotionConfigurationInvalid {
reason: reason.clone(),
},
Self::VoiceCloningError {
message,
cloning_method,
} => Self::VoiceCloningError {
message: message.clone(),
cloning_method: cloning_method.clone(),
},
Self::VoiceCloningSourceInvalid {
reason,
audio_duration,
required_duration,
} => Self::VoiceCloningSourceInvalid {
reason: reason.clone(),
audio_duration: *audio_duration,
required_duration: *required_duration,
},
Self::VoiceCloningQualityTooLow { quality, threshold } => {
Self::VoiceCloningQualityTooLow {
quality: *quality,
threshold: *threshold,
}
}
Self::VoiceCloningMethodNotSupported { method, supported } => {
Self::VoiceCloningMethodNotSupported {
method: method.clone(),
supported: supported.clone(),
}
}
Self::VoiceConversionError {
message,
conversion_type,
} => Self::VoiceConversionError {
message: message.clone(),
conversion_type: conversion_type.clone(),
},
Self::VoiceConversionTargetInvalid { reason, target } => {
Self::VoiceConversionTargetInvalid {
reason: reason.clone(),
target: target.clone(),
}
}
Self::VoiceConversionNotSupported { from, to } => Self::VoiceConversionNotSupported {
from: from.clone(),
to: to.clone(),
},
Self::RealTimeConversionBufferOverflow {
details,
buffer_size,
} => Self::RealTimeConversionBufferOverflow {
details: details.clone(),
buffer_size: *buffer_size,
},
Self::SingingError {
message,
voice_type,
} => Self::SingingError {
message: message.clone(),
voice_type: voice_type.clone(),
},
Self::MusicalScoreParsingError {
message,
line,
column,
} => Self::MusicalScoreParsingError {
message: message.clone(),
line: *line,
column: *column,
},
Self::MusicalNoteInvalid { note, reason } => Self::MusicalNoteInvalid {
note: note.clone(),
reason: reason.clone(),
},
Self::SingingTechniqueNotSupported {
technique,
supported,
} => Self::SingingTechniqueNotSupported {
technique: technique.clone(),
supported: supported.clone(),
},
Self::TempoOutOfRange {
tempo,
min_tempo,
max_tempo,
} => Self::TempoOutOfRange {
tempo: *tempo,
min_tempo: *min_tempo,
max_tempo: *max_tempo,
},
Self::SpatialAudioError { message, position } => Self::SpatialAudioError {
message: message.clone(),
position: position.clone(),
},
Self::HrtfProcessingError {
message,
sample_rate,
} => Self::HrtfProcessingError {
message: message.clone(),
sample_rate: *sample_rate,
},
Self::Position3DInvalid { x, y, z, reason } => Self::Position3DInvalid {
x: *x,
y: *y,
z: *z,
reason: reason.clone(),
},
Self::RoomAcousticsConfigurationInvalid { reason, room_size } => {
Self::RoomAcousticsConfigurationInvalid {
reason: reason.clone(),
room_size: room_size.clone(),
}
}
Self::BinauralRenderingError {
message,
channel_count,
} => Self::BinauralRenderingError {
message: message.clone(),
channel_count: *channel_count,
},
Self::FeatureUnavailable { feature, reason } => Self::FeatureUnavailable {
feature: feature.clone(),
reason: reason.clone(),
},
Self::ModelLoadError(msg) => Self::ModelLoadError(msg.clone()),
Self::InferenceError(msg) => Self::InferenceError(msg.clone()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SynthesisStage {
TextPreprocessing,
G2pConversion,
AcousticModeling,
Vocoding,
PostProcessing,
AudioFinalization,
}
impl std::fmt::Display for SynthesisStage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TextPreprocessing => write!(f, "text preprocessing"),
Self::G2pConversion => write!(f, "G2P conversion"),
Self::AcousticModeling => write!(f, "acoustic modeling"),
Self::Vocoding => write!(f, "vocoding"),
Self::PostProcessing => write!(f, "post-processing"),
Self::AudioFinalization => write!(f, "audio finalization"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ModelType {
G2p,
Acoustic,
Vocoder,
Preprocessor,
Postprocessor,
ASR,
}
impl std::fmt::Display for ModelType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::G2p => write!(f, "G2P"),
Self::Acoustic => write!(f, "acoustic"),
Self::Vocoder => write!(f, "vocoder"),
Self::Preprocessor => write!(f, "preprocessor"),
Self::Postprocessor => write!(f, "postprocessor"),
Self::ASR => write!(f, "ASR"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum IoOperation {
Read,
Write,
Create,
Delete,
Copy,
Move,
Metadata,
}
impl std::fmt::Display for IoOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Read => write!(f, "read"),
Self::Write => write!(f, "write"),
Self::Create => write!(f, "create"),
Self::Delete => write!(f, "delete"),
Self::Copy => write!(f, "copy"),
Self::Move => write!(f, "move"),
Self::Metadata => write!(f, "metadata"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AudioBufferErrorType {
Overflow,
Underflow,
InvalidFormat,
SizeExceeded,
Corruption,
}
impl std::fmt::Display for AudioBufferErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Overflow => write!(f, "overflow"),
Self::Underflow => write!(f, "underflow"),
Self::InvalidFormat => write!(f, "invalid format"),
Self::SizeExceeded => write!(f, "size exceeded"),
Self::Corruption => write!(f, "corruption"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityMetrics {
pub real_time_factor: f32,
pub mean_opinion_score: Option<f32>,
pub signal_to_noise_ratio: Option<f32>,
pub perceptual_quality: Option<f32>,
}
impl Default for QualityMetrics {
fn default() -> Self {
Self {
real_time_factor: 1.0,
mean_opinion_score: None,
signal_to_noise_ratio: None,
perceptual_quality: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioBufferInfo {
pub sample_rate: u32,
pub channels: u32,
pub buffer_size: usize,
pub duration: f32,
pub format: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum ErrorSeverity {
Info,
Warning,
Error,
Critical,
Fatal,
}
impl std::fmt::Display for ErrorSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Info => write!(f, "INFO"),
Self::Warning => write!(f, "WARN"),
Self::Error => write!(f, "ERROR"),
Self::Critical => write!(f, "CRITICAL"),
Self::Fatal => write!(f, "FATAL"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorContext {
pub severity: ErrorSeverity,
pub component: String,
pub operation: String,
pub context: HashMap<String, String>,
pub timestamp: std::time::SystemTime,
pub stack_trace: Option<String>,
}
impl Default for ErrorContext {
fn default() -> Self {
Self {
severity: ErrorSeverity::Error,
component: "unknown".to_string(),
operation: "unknown".to_string(),
context: HashMap::new(),
timestamp: std::time::SystemTime::now(),
stack_trace: None,
}
}
}
impl VoirsError {
pub fn severity(&self) -> ErrorSeverity {
match self {
Self::OutOfMemory { .. } | Self::GpuOutOfMemory { .. } => ErrorSeverity::Fatal,
Self::ModelNotFound { .. }
| Self::DeviceNotAvailable { .. }
| Self::UnsupportedDevice { .. } => ErrorSeverity::Critical,
Self::SynthesisFailed { .. }
| Self::VoiceNotFound { .. }
| Self::IoError { .. }
| Self::ConfigError { .. }
| Self::ModelError { .. }
| Self::AudioError { .. }
| Self::G2pError { .. }
| Self::NetworkError { .. }
| Self::TimeoutError { .. }
| Self::InvalidStateTransition { .. }
| Self::EmotionError { .. }
| Self::EmotionNotSupported { .. }
| Self::EmotionConfigurationInvalid { .. }
| Self::VoiceCloningError { .. }
| Self::VoiceCloningSourceInvalid { .. }
| Self::VoiceCloningMethodNotSupported { .. }
| Self::VoiceConversionError { .. }
| Self::VoiceConversionTargetInvalid { .. }
| Self::VoiceConversionNotSupported { .. }
| Self::RealTimeConversionBufferOverflow { .. }
| Self::SingingError { .. }
| Self::MusicalScoreParsingError { .. }
| Self::MusicalNoteInvalid { .. }
| Self::SingingTechniqueNotSupported { .. }
| Self::SpatialAudioError { .. }
| Self::HrtfProcessingError { .. }
| Self::Position3DInvalid { .. }
| Self::RoomAcousticsConfigurationInvalid { .. }
| Self::BinauralRenderingError { .. } => ErrorSeverity::Error,
Self::SynthesisQualityDegraded { .. }
| Self::PerformanceDegradation { .. }
| Self::VoiceUnavailable { .. }
| Self::DeprecatedFunctionality { .. }
| Self::EmotionIntensityOutOfRange { .. }
| Self::VoiceCloningQualityTooLow { .. }
| Self::TempoOutOfRange { .. } => ErrorSeverity::Warning,
Self::NotImplemented { .. } => ErrorSeverity::Info,
_ => ErrorSeverity::Error,
}
}
pub fn component(&self) -> &str {
match self {
Self::VoiceNotFound { .. }
| Self::VoiceUnavailable { .. }
| Self::VoiceConfigurationInvalid { .. } => "voice",
Self::SynthesisFailed { .. }
| Self::SynthesisQualityDegraded { .. }
| Self::SynthesisInterrupted { .. } => "synthesis",
Self::DeviceError { .. }
| Self::DeviceNotAvailable { .. }
| Self::UnsupportedDevice { .. }
| Self::GpuOutOfMemory { .. } => "device",
Self::IoError { .. }
| Self::UnsupportedFileFormat { .. }
| Self::FileCorrupted { .. } => "io",
Self::ConfigError { .. }
| Self::InvalidConfiguration { .. }
| Self::ConfigMigrationFailed { .. } => "config",
Self::ModelError { .. }
| Self::ModelNotFound { .. }
| Self::ModelVersionMismatch { .. }
| Self::ModelDeviceIncompatible { .. } => "model",
Self::AudioError { .. }
| Self::AudioFormatConversionFailed { .. }
| Self::AudioBufferError { .. } => "audio",
Self::G2pError { .. }
| Self::TextPreprocessingError { .. }
| Self::LanguageNotSupported { .. } => "language",
Self::NetworkError { .. }
| Self::DownloadFailed { .. }
| Self::AuthenticationFailed { .. } => "network",
Self::PipelineNotReady
| Self::InvalidStateTransition { .. }
| Self::ComponentSynchronizationFailed { .. } => "pipeline",
Self::TimeoutError { .. }
| Self::PerformanceDegradation { .. }
| Self::RealTimeConstraintViolation { .. } => "performance",
Self::OutOfMemory { .. } | Self::ResourceExhausted { .. } | Self::MemoryLeak { .. } => {
"memory"
}
Self::SerializationError { .. }
| Self::DataValidationFailed { .. }
| Self::SchemaMismatch { .. } => "data",
Self::InternalError { component, .. } => component,
Self::NotImplemented { .. } | Self::DeprecatedFunctionality { .. } => "system",
Self::EmotionError { .. }
| Self::EmotionNotSupported { .. }
| Self::EmotionIntensityOutOfRange { .. }
| Self::EmotionConfigurationInvalid { .. } => "emotion",
Self::VoiceCloningError { .. }
| Self::VoiceCloningSourceInvalid { .. }
| Self::VoiceCloningQualityTooLow { .. }
| Self::VoiceCloningMethodNotSupported { .. } => "voice_cloning",
Self::VoiceConversionError { .. }
| Self::VoiceConversionTargetInvalid { .. }
| Self::VoiceConversionNotSupported { .. }
| Self::RealTimeConversionBufferOverflow { .. } => "voice_conversion",
Self::SingingError { .. }
| Self::MusicalScoreParsingError { .. }
| Self::MusicalNoteInvalid { .. }
| Self::SingingTechniqueNotSupported { .. }
| Self::TempoOutOfRange { .. } => "singing",
Self::SpatialAudioError { .. }
| Self::HrtfProcessingError { .. }
| Self::Position3DInvalid { .. }
| Self::RoomAcousticsConfigurationInvalid { .. }
| Self::BinauralRenderingError { .. } => "spatial_audio",
Self::FeatureUnavailable { .. } => "feature",
Self::ModelLoadError(_) => "model",
Self::InferenceError(_) => "model",
}
}
pub fn is_recoverable(&self) -> bool {
match self {
Self::OutOfMemory { .. }
| Self::GpuOutOfMemory { .. }
| Self::UnsupportedDevice { .. }
| Self::ModelNotFound { .. }
| Self::FileCorrupted { .. }
| Self::LanguageNotSupported { .. }
| Self::PipelineNotReady => false,
Self::NetworkError { .. }
| Self::DownloadFailed { .. }
| Self::TimeoutError { .. }
| Self::DeviceNotAvailable { .. }
| Self::ComponentSynchronizationFailed { .. } => true,
Self::ConfigError { .. }
| Self::InvalidConfiguration { .. }
| Self::VoiceConfigurationInvalid { .. } => true,
_ => true,
}
}
pub fn recovery_suggestions(&self) -> Vec<String> {
match self {
Self::VoiceNotFound { suggestions, .. } => suggestions.clone(),
Self::DeviceNotAvailable { alternatives, .. } => {
let mut suggestions = vec!["Check device availability".to_string()];
for alt in alternatives {
suggestions.push(format!("Try using device: {alt}"));
}
suggestions
}
Self::NetworkError {
retry_count,
max_retries,
..
} => {
if retry_count < max_retries {
vec![
"Retry the operation".to_string(),
"Check network connection".to_string(),
]
} else {
vec![
"Check network connection".to_string(),
"Try again later".to_string(),
]
}
}
Self::TimeoutError { .. } => vec![
"Increase timeout duration".to_string(),
"Check system performance".to_string(),
"Retry with smaller batch size".to_string(),
],
Self::OutOfMemory { .. } => vec![
"Reduce batch size".to_string(),
"Close other applications".to_string(),
"Use CPU instead of GPU".to_string(),
],
Self::ConfigError { .. } => vec![
"Check configuration file syntax".to_string(),
"Validate configuration values".to_string(),
"Reset to default configuration".to_string(),
],
Self::ModelError { .. } => vec![
"Re-download the model".to_string(),
"Check model compatibility".to_string(),
"Verify model integrity".to_string(),
],
_ => vec!["Retry the operation".to_string()],
}
}
pub fn with_context(
self,
component: impl Into<String>,
operation: impl Into<String>,
) -> ErrorWithContext {
let mut context = ErrorContext {
severity: self.severity(),
component: component.into(),
operation: operation.into(),
context: HashMap::new(),
timestamp: std::time::SystemTime::now(),
stack_trace: if cfg!(debug_assertions) {
Some(std::backtrace::Backtrace::force_capture().to_string())
} else {
None
},
};
match &self {
Self::VoiceNotFound { voice, .. } => {
context.context.insert("voice".to_string(), voice.clone());
}
Self::SynthesisFailed { text_length, .. } => {
context
.context
.insert("text_length".to_string(), text_length.to_string());
}
Self::DeviceError { device, .. } => {
context.context.insert("device".to_string(), device.clone());
}
Self::IoError {
path, operation, ..
} => {
context
.context
.insert("path".to_string(), path.display().to_string());
context
.context
.insert("io_operation".to_string(), operation.to_string());
}
_ => {}
}
ErrorWithContext {
error: self,
context,
}
}
}
#[derive(Debug)]
pub struct ErrorWithContext {
pub error: VoirsError,
pub context: ErrorContext,
}
impl std::fmt::Display for ErrorWithContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{}] {} in {}: {}",
self.context.severity, self.context.component, self.context.operation, self.error
)
}
}
impl std::error::Error for ErrorWithContext {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.error)
}
}
pub type Result<T> = std::result::Result<T, VoirsError>;
impl From<ErrorWithContext> for VoirsError {
fn from(val: ErrorWithContext) -> Self {
val.error
}
}
pub type ContextResult<T> = std::result::Result<T, ErrorWithContext>;
impl VoirsError {
pub fn emotion_error(message: impl Into<String>) -> Self {
Self::EmotionError {
message: message.into(),
emotion_type: None,
intensity: None,
}
}
pub fn emotion_not_supported(emotion: impl Into<String>, available: Vec<String>) -> Self {
Self::EmotionNotSupported {
emotion: emotion.into(),
available,
}
}
pub fn emotion_intensity_out_of_range(intensity: f32) -> Self {
Self::EmotionIntensityOutOfRange { intensity }
}
pub fn voice_cloning_error(message: impl Into<String>) -> Self {
Self::VoiceCloningError {
message: message.into(),
cloning_method: None,
}
}
pub fn voice_cloning_source_invalid(reason: impl Into<String>) -> Self {
Self::VoiceCloningSourceInvalid {
reason: reason.into(),
audio_duration: None,
required_duration: None,
}
}
pub fn voice_cloning_quality_too_low(quality: f32, threshold: f32) -> Self {
Self::VoiceCloningQualityTooLow { quality, threshold }
}
pub fn voice_conversion_error(message: impl Into<String>) -> Self {
Self::VoiceConversionError {
message: message.into(),
conversion_type: None,
}
}
pub fn voice_conversion_target_invalid(
reason: impl Into<String>,
target: impl Into<String>,
) -> Self {
Self::VoiceConversionTargetInvalid {
reason: reason.into(),
target: target.into(),
}
}
pub fn voice_conversion_not_supported(from: impl Into<String>, to: impl Into<String>) -> Self {
Self::VoiceConversionNotSupported {
from: from.into(),
to: to.into(),
}
}
pub fn singing_error(message: impl Into<String>) -> Self {
Self::SingingError {
message: message.into(),
voice_type: None,
}
}
pub fn musical_score_parsing_error(message: impl Into<String>) -> Self {
Self::MusicalScoreParsingError {
message: message.into(),
line: None,
column: None,
}
}
pub fn musical_note_invalid(note: impl Into<String>, reason: impl Into<String>) -> Self {
Self::MusicalNoteInvalid {
note: note.into(),
reason: reason.into(),
}
}
pub fn tempo_out_of_range(tempo: f32, min_tempo: f32, max_tempo: f32) -> Self {
Self::TempoOutOfRange {
tempo,
min_tempo,
max_tempo,
}
}
pub fn spatial_audio_error(message: impl Into<String>) -> Self {
Self::SpatialAudioError {
message: message.into(),
position: None,
}
}
pub fn hrtf_processing_error(message: impl Into<String>) -> Self {
Self::HrtfProcessingError {
message: message.into(),
sample_rate: None,
}
}
pub fn position_3d_invalid(x: f32, y: f32, z: f32, reason: impl Into<String>) -> Self {
Self::Position3DInvalid {
x,
y,
z,
reason: reason.into(),
}
}
pub fn room_acoustics_configuration_invalid(reason: impl Into<String>) -> Self {
Self::RoomAcousticsConfigurationInvalid {
reason: reason.into(),
room_size: None,
}
}
pub fn binaural_rendering_error(message: impl Into<String>) -> Self {
Self::BinauralRenderingError {
message: message.into(),
channel_count: None,
}
}
}