agent: lumotia-rebrand — drop MagnotiaError prefix, now lumotia_core::Error
Phase 3 of the rebrand cascade per locked decision D4. MagnotiaError -> Error in crates/core/src/error.rs (the crate name already qualifies it). 92 usages across 14 .rs files renamed via word-boundary sed. One collision required disambiguation: lumotia_storage already had its own local Error type (introduced by the slop-pass Area A residuals work). crates/storage/src/error.rs aliases the imported core error as CoreError on import; the From<Error> for CoreError boundary impl and the CoreError::Storage construction site use the alias. cargo build --workspace passes. cargo test --workspace: 330 pass, 0 fail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ use regex::Regex;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
|
|
||||||
const AUDIO_CHANNEL_CAPACITY: usize = 32;
|
const AUDIO_CHANNEL_CAPACITY: usize = 32;
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ impl MicrophoneCapture {
|
|||||||
|
|
||||||
let devices = host
|
let devices = host
|
||||||
.input_devices()
|
.input_devices()
|
||||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
.map_err(|e| Error::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
||||||
|
|
||||||
// Load ALSA card descriptions once per enumeration. These are the
|
// Load ALSA card descriptions once per enumeration. These are the
|
||||||
// "real" product names (e.g. "Blue Microphones") that cpal's
|
// "real" product names (e.g. "Blue Microphones") that cpal's
|
||||||
@@ -144,7 +144,7 @@ impl MicrophoneCapture {
|
|||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let devices = host
|
let devices = host
|
||||||
.input_devices()
|
.input_devices()
|
||||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
.map_err(|e| Error::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
||||||
|
|
||||||
for device in devices {
|
for device in devices {
|
||||||
let name = device_display_name(&device).unwrap_or_default();
|
let name = device_display_name(&device).unwrap_or_default();
|
||||||
@@ -154,7 +154,7 @@ impl MicrophoneCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(MagnotiaError::AudioCaptureFailed(format!(
|
Err(Error::AudioCaptureFailed(format!(
|
||||||
"Selected device '{device_name}' not found in current host enumeration. \
|
"Selected device '{device_name}' not found in current host enumeration. \
|
||||||
It may have been disconnected. Open Settings → Audio to pick another."
|
It may have been disconnected. Open Settings → Audio to pick another."
|
||||||
)))
|
)))
|
||||||
@@ -178,7 +178,7 @@ impl MicrophoneCapture {
|
|||||||
|
|
||||||
let mut all_devices: Vec<cpal::Device> = host
|
let mut all_devices: Vec<cpal::Device> = host
|
||||||
.input_devices()
|
.input_devices()
|
||||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?
|
.map_err(|e| Error::AudioCaptureFailed(format!("input_devices: {e}")))?
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort: default first, then non-monitor, then monitor-as-last-resort.
|
// Sort: default first, then non-monitor, then monitor-as-last-resort.
|
||||||
@@ -237,7 +237,7 @@ impl MicrophoneCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(MagnotiaError::AudioCaptureFailed(
|
Err(Error::AudioCaptureFailed(
|
||||||
"No working microphone found. Check that an input device is connected, \
|
"No working microphone found. Check that an input device is connected, \
|
||||||
that PulseAudio/PipeWire is running, and that the app has microphone permission. \
|
that PulseAudio/PipeWire is running, and that the app has microphone permission. \
|
||||||
Then open Settings → Audio to pick a device explicitly."
|
Then open Settings → Audio to pick a device explicitly."
|
||||||
@@ -361,7 +361,7 @@ fn open_and_validate(
|
|||||||
) -> Result<(MicrophoneCapture, mpsc::Receiver<AudioChunk>)> {
|
) -> Result<(MicrophoneCapture, mpsc::Receiver<AudioChunk>)> {
|
||||||
let config = device
|
let config = device
|
||||||
.default_input_config()
|
.default_input_config()
|
||||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("default_input_config: {e}")))?;
|
.map_err(|e| Error::AudioCaptureFailed(format!("default_input_config: {e}")))?;
|
||||||
let sample_rate = config.sample_rate();
|
let sample_rate = config.sample_rate();
|
||||||
let channels = config.channels();
|
let channels = config.channels();
|
||||||
let format = config.sample_format();
|
let format = config.sample_format();
|
||||||
@@ -422,16 +422,16 @@ fn open_and_validate(
|
|||||||
name.to_string(),
|
name.to_string(),
|
||||||
),
|
),
|
||||||
other => {
|
other => {
|
||||||
return Err(MagnotiaError::AudioCaptureFailed(format!(
|
return Err(Error::AudioCaptureFailed(format!(
|
||||||
"unsupported sample format {other:?}"
|
"unsupported sample format {other:?}"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("build_input_stream: {e}")))?;
|
.map_err(|e| Error::AudioCaptureFailed(format!("build_input_stream: {e}")))?;
|
||||||
|
|
||||||
stream
|
stream
|
||||||
.play()
|
.play()
|
||||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("stream.play: {e}")))?;
|
.map_err(|e| Error::AudioCaptureFailed(format!("stream.play: {e}")))?;
|
||||||
|
|
||||||
// Validation window: collect chunks for DEVICE_VALIDATION_MS, compute RMS.
|
// Validation window: collect chunks for DEVICE_VALIDATION_MS, compute RMS.
|
||||||
let deadline =
|
let deadline =
|
||||||
@@ -460,7 +460,7 @@ fn open_and_validate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if total_samples == 0 {
|
if total_samples == 0 {
|
||||||
return Err(MagnotiaError::AudioCaptureFailed(
|
return Err(Error::AudioCaptureFailed(
|
||||||
"device delivered zero samples in validation window".into(),
|
"device delivered zero samples in validation window".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -475,7 +475,7 @@ fn open_and_validate(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if require_audio && rms < SILENCE_RMS_FLOOR {
|
if require_audio && rms < SILENCE_RMS_FLOOR {
|
||||||
return Err(MagnotiaError::AudioCaptureFailed(format!(
|
return Err(Error::AudioCaptureFailed(format!(
|
||||||
"device produced silence (rms={rms:.6} below floor {SILENCE_RMS_FLOOR:.6})"
|
"device produced silence (rms={rms:.6} below floor {SILENCE_RMS_FLOOR:.6})"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -485,7 +485,7 @@ fn open_and_validate(
|
|||||||
// long stream of f32 zeros from a borked device — that is worse than
|
// long stream of f32 zeros from a borked device — that is worse than
|
||||||
// failing fast.
|
// failing fast.
|
||||||
if rms < DEAD_SILENCE_FLOOR {
|
if rms < DEAD_SILENCE_FLOOR {
|
||||||
return Err(MagnotiaError::AudioCaptureFailed(format!(
|
return Err(Error::AudioCaptureFailed(format!(
|
||||||
"device produced dead silence (rms={rms:.6e} below absolute floor {DEAD_SILENCE_FLOOR:.6e})"
|
"device produced dead silence (rms={rms:.6e} below absolute floor {DEAD_SILENCE_FLOOR:.6e})"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ pub async fn decode_and_resample(path: &Path) -> Result<AudioSamples> {
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
lumotia_core::error::MagnotiaError::AudioDecodeFailed(format!("Task join error: {e}"))
|
lumotia_core::error::Error::AudioDecodeFailed(format!("Task join error: {e}"))
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ use symphonia::core::io::MediaSourceStream;
|
|||||||
use symphonia::core::meta::MetadataOptions;
|
use symphonia::core::meta::MetadataOptions;
|
||||||
use symphonia::core::probe::Hint;
|
use symphonia::core::probe::Hint;
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::types::AudioSamples;
|
use lumotia_core::types::AudioSamples;
|
||||||
|
|
||||||
/// Decode an audio file to mono f32 PCM samples.
|
/// Decode an audio file to mono f32 PCM samples.
|
||||||
/// Supports all formats symphonia handles: mp3, aac, flac, wav, ogg, etc.
|
/// Supports all formats symphonia handles: mp3, aac, flac, wav, ogg, etc.
|
||||||
///
|
///
|
||||||
/// Any read- or decode-side error is propagated as `MagnotiaError::AudioDecodeFailed`.
|
/// Any read- or decode-side error is propagated as `Error::AudioDecodeFailed`.
|
||||||
/// A previous implementation `break`ed out of the packet loop on any read
|
/// A previous implementation `break`ed out of the packet loop on any read
|
||||||
/// error and skipped per-packet decode errors, so a truncated or corrupt
|
/// error and skipped per-packet decode errors, so a truncated or corrupt
|
||||||
/// input silently returned `Ok` with whatever had decoded before the
|
/// input silently returned `Ok` with whatever had decoded before the
|
||||||
@@ -29,7 +29,7 @@ pub fn decode_audio_file_limited(
|
|||||||
max_duration_secs: Option<f64>,
|
max_duration_secs: Option<f64>,
|
||||||
) -> Result<AudioSamples> {
|
) -> Result<AudioSamples> {
|
||||||
let file = File::open(path)
|
let file = File::open(path)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
||||||
let mss = MediaSourceStream::new(Box::new(file), Default::default());
|
let mss = MediaSourceStream::new(Box::new(file), Default::default());
|
||||||
|
|
||||||
let mut hint = Hint::new();
|
let mut hint = Hint::new();
|
||||||
@@ -42,7 +42,7 @@ pub fn decode_audio_file_limited(
|
|||||||
|
|
||||||
pub fn probe_audio_duration_secs(path: &Path) -> Result<Option<f64>> {
|
pub fn probe_audio_duration_secs(path: &Path) -> Result<Option<f64>> {
|
||||||
let file = File::open(path)
|
let file = File::open(path)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
||||||
let mss = MediaSourceStream::new(Box::new(file), Default::default());
|
let mss = MediaSourceStream::new(Box::new(file), Default::default());
|
||||||
let mut hint = Hint::new();
|
let mut hint = Hint::new();
|
||||||
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
||||||
@@ -56,15 +56,15 @@ pub fn probe_audio_duration_secs(path: &Path) -> Result<Option<f64>> {
|
|||||||
&FormatOptions::default(),
|
&FormatOptions::default(),
|
||||||
&MetadataOptions::default(),
|
&MetadataOptions::default(),
|
||||||
)
|
)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
||||||
let track = probed
|
let track = probed
|
||||||
.format
|
.format
|
||||||
.default_track()
|
.default_track()
|
||||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("No audio track found".into()))?;
|
.ok_or_else(|| Error::AudioDecodeFailed("No audio track found".into()))?;
|
||||||
let sample_rate = track
|
let sample_rate = track
|
||||||
.codec_params
|
.codec_params
|
||||||
.sample_rate
|
.sample_rate
|
||||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("Unknown sample rate".into()))?;
|
.ok_or_else(|| Error::AudioDecodeFailed("Unknown sample rate".into()))?;
|
||||||
Ok(track
|
Ok(track
|
||||||
.codec_params
|
.codec_params
|
||||||
.n_frames
|
.n_frames
|
||||||
@@ -86,20 +86,20 @@ fn decode_media_stream(
|
|||||||
&FormatOptions::default(),
|
&FormatOptions::default(),
|
||||||
&MetadataOptions::default(),
|
&MetadataOptions::default(),
|
||||||
)
|
)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
||||||
|
|
||||||
let mut format = probed.format;
|
let mut format = probed.format;
|
||||||
|
|
||||||
let track = format
|
let track = format
|
||||||
.default_track()
|
.default_track()
|
||||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("No audio track found".into()))?;
|
.ok_or_else(|| Error::AudioDecodeFailed("No audio track found".into()))?;
|
||||||
let sample_rate = track
|
let sample_rate = track
|
||||||
.codec_params
|
.codec_params
|
||||||
.sample_rate
|
.sample_rate
|
||||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("Unknown sample rate".into()))?;
|
.ok_or_else(|| Error::AudioDecodeFailed("Unknown sample rate".into()))?;
|
||||||
|
|
||||||
if sample_rate == 0 {
|
if sample_rate == 0 {
|
||||||
return Err(MagnotiaError::AudioDecodeFailed(
|
return Err(Error::AudioDecodeFailed(
|
||||||
"Invalid sample rate: 0".into(),
|
"Invalid sample rate: 0".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ fn decode_media_stream(
|
|||||||
|
|
||||||
let mut decoder = symphonia::default::get_codecs()
|
let mut decoder = symphonia::default::get_codecs()
|
||||||
.make(&track.codec_params, &DecoderOptions::default())
|
.make(&track.codec_params, &DecoderOptions::default())
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Codec error: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("Codec error: {e}")))?;
|
||||||
|
|
||||||
let mut samples: Vec<f32> = Vec::new();
|
let mut samples: Vec<f32> = Vec::new();
|
||||||
|
|
||||||
@@ -123,12 +123,12 @@ fn decode_media_stream(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(SymphoniaError::ResetRequired) => {
|
Err(SymphoniaError::ResetRequired) => {
|
||||||
return Err(MagnotiaError::AudioDecodeFailed(
|
return Err(Error::AudioDecodeFailed(
|
||||||
"decoder reset required mid-stream — input contains a discontinuity".into(),
|
"decoder reset required mid-stream — input contains a discontinuity".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(MagnotiaError::AudioDecodeFailed(format!(
|
return Err(Error::AudioDecodeFailed(format!(
|
||||||
"packet read failed: {e}"
|
"packet read failed: {e}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ fn decode_media_stream(
|
|||||||
|
|
||||||
let decoded = decoder
|
let decoded = decoder
|
||||||
.decode(&packet)
|
.decode(&packet)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("packet decode failed: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("packet decode failed: {e}")))?;
|
||||||
|
|
||||||
let spec = *decoded.spec();
|
let spec = *decoded.spec();
|
||||||
let channels = spec.channels.count();
|
let channels = spec.channels.count();
|
||||||
@@ -160,7 +160,7 @@ fn decode_media_stream(
|
|||||||
.map(|limit| samples.len() > limit)
|
.map(|limit| samples.len() > limit)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return Err(MagnotiaError::AudioDecodeFailed(format!(
|
return Err(Error::AudioDecodeFailed(format!(
|
||||||
"Audio is longer than the {:.0} minute import limit",
|
"Audio is longer than the {:.0} minute import limit",
|
||||||
max_duration_secs.unwrap_or(0.0) / 60.0
|
max_duration_secs.unwrap_or(0.0) / 60.0
|
||||||
)));
|
)));
|
||||||
@@ -168,7 +168,7 @@ fn decode_media_stream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if samples.is_empty() {
|
if samples.is_empty() {
|
||||||
return Err(MagnotiaError::AudioDecodeFailed(
|
return Err(Error::AudioDecodeFailed(
|
||||||
"No audio data decoded".into(),
|
"No audio data decoded".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use rubato::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use lumotia_core::constants::WHISPER_SAMPLE_RATE;
|
use lumotia_core::constants::WHISPER_SAMPLE_RATE;
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::types::AudioSamples;
|
use lumotia_core::types::AudioSamples;
|
||||||
|
|
||||||
/// Resample audio to 16kHz mono using sinc interpolation (rubato).
|
/// Resample audio to 16kHz mono using sinc interpolation (rubato).
|
||||||
@@ -17,7 +17,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result<AudioSamples> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if from_rate == 0 {
|
if from_rate == 0 {
|
||||||
return Err(MagnotiaError::AudioDecodeFailed(
|
return Err(Error::AudioDecodeFailed(
|
||||||
"Cannot resample: source rate is 0".into(),
|
"Cannot resample: source rate is 0".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result<AudioSamples> {
|
|||||||
let mut resampler = SincFixedIn::<f32>::new(
|
let mut resampler = SincFixedIn::<f32>::new(
|
||||||
ratio, 1.1, params, chunk_size, 1, // mono
|
ratio, 1.1, params, chunk_size, 1, // mono
|
||||||
)
|
)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Resampler init failed: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("Resampler init failed: {e}")))?;
|
||||||
|
|
||||||
let samples = audio.samples();
|
let samples = audio.samples();
|
||||||
let mut output_samples: Vec<f32> = Vec::new();
|
let mut output_samples: Vec<f32> = Vec::new();
|
||||||
@@ -53,7 +53,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result<AudioSamples> {
|
|||||||
let input = vec![chunk];
|
let input = vec![chunk];
|
||||||
let result = resampler
|
let result = resampler
|
||||||
.process(&input, None)
|
.process(&input, None)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Resample failed: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("Resample failed: {e}")))?;
|
||||||
|
|
||||||
if !result.is_empty() && !result[0].is_empty() {
|
if !result.is_empty() && !result[0].is_empty() {
|
||||||
output_samples.extend_from_slice(&result[0]);
|
output_samples.extend_from_slice(&result[0]);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use rubato::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use lumotia_core::constants::WHISPER_SAMPLE_RATE;
|
use lumotia_core::constants::WHISPER_SAMPLE_RATE;
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
|
|
||||||
/// Number of input samples the rubato resampler consumes per `process()`
|
/// Number of input samples the rubato resampler consumes per `process()`
|
||||||
/// call. Matches the chunk size used in `resample::resample_to_16khz`.
|
/// call. Matches the chunk size used in `resample::resample_to_16khz`.
|
||||||
@@ -51,7 +51,7 @@ impl StreamingResampler {
|
|||||||
/// rubato rejects the requested ratio.
|
/// rubato rejects the requested ratio.
|
||||||
pub fn new(from_rate: u32) -> Result<Self> {
|
pub fn new(from_rate: u32) -> Result<Self> {
|
||||||
if from_rate == 0 {
|
if from_rate == 0 {
|
||||||
return Err(MagnotiaError::AudioDecodeFailed(
|
return Err(Error::AudioDecodeFailed(
|
||||||
"StreamingResampler: input sample rate is 0".into(),
|
"StreamingResampler: input sample rate is 0".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ impl StreamingResampler {
|
|||||||
1, // mono
|
1, // mono
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
MagnotiaError::AudioDecodeFailed(format!("StreamingResampler init failed: {e}"))
|
Error::AudioDecodeFailed(format!("StreamingResampler init failed: {e}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Self::Sinc {
|
Ok(Self::Sinc {
|
||||||
@@ -110,7 +110,7 @@ impl StreamingResampler {
|
|||||||
let chunk: Vec<f32> = residual.drain(..INPUT_CHUNK).collect();
|
let chunk: Vec<f32> = residual.drain(..INPUT_CHUNK).collect();
|
||||||
let input = vec![chunk];
|
let input = vec![chunk];
|
||||||
let result = resampler.process(&input, None).map_err(|e| {
|
let result = resampler.process(&input, None).map_err(|e| {
|
||||||
MagnotiaError::AudioDecodeFailed(format!(
|
Error::AudioDecodeFailed(format!(
|
||||||
"StreamingResampler process failed: {e}"
|
"StreamingResampler process failed: {e}"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
@@ -144,7 +144,7 @@ impl StreamingResampler {
|
|||||||
|
|
||||||
let input = vec![chunk];
|
let input = vec![chunk];
|
||||||
let result = resampler.process(&input, None).map_err(|e| {
|
let result = resampler.process(&input, None).map_err(|e| {
|
||||||
MagnotiaError::AudioDecodeFailed(format!(
|
Error::AudioDecodeFailed(format!(
|
||||||
"StreamingResampler flush failed: {e}"
|
"StreamingResampler flush failed: {e}"
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::types::AudioSamples;
|
use lumotia_core::types::AudioSamples;
|
||||||
|
|
||||||
/// Append-friendly WAV writer for long-running captures.
|
/// Append-friendly WAV writer for long-running captures.
|
||||||
@@ -40,10 +40,10 @@ impl WavWriter {
|
|||||||
bits_per_sample: 16,
|
bits_per_sample: 16,
|
||||||
sample_format: hound::SampleFormat::Int,
|
sample_format: hound::SampleFormat::Int,
|
||||||
};
|
};
|
||||||
let file = std::fs::File::create(path).map_err(MagnotiaError::from)?;
|
let file = std::fs::File::create(path).map_err(Error::from)?;
|
||||||
let buffered = BufWriter::new(file);
|
let buffered = BufWriter::new(file);
|
||||||
let inner = hound::WavWriter::new(buffered, spec).map_err(|e| {
|
let inner = hound::WavWriter::new(buffered, spec).map_err(|e| {
|
||||||
MagnotiaError::from(std::io::Error::other(format!("WAV create failed: {e}")))
|
Error::from(std::io::Error::other(format!("WAV create failed: {e}")))
|
||||||
})?;
|
})?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner,
|
inner,
|
||||||
@@ -61,7 +61,7 @@ impl WavWriter {
|
|||||||
let clamped = sample.clamp(-1.0, 1.0);
|
let clamped = sample.clamp(-1.0, 1.0);
|
||||||
let int_sample = (clamped * i16::MAX as f32) as i16;
|
let int_sample = (clamped * i16::MAX as f32) as i16;
|
||||||
self.inner.write_sample(int_sample).map_err(|e| {
|
self.inner.write_sample(int_sample).map_err(|e| {
|
||||||
MagnotiaError::from(std::io::Error::other(format!("WAV write failed: {e}")))
|
Error::from(std::io::Error::other(format!("WAV write failed: {e}")))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
self.samples_since_flush += samples.len();
|
self.samples_since_flush += samples.len();
|
||||||
@@ -78,7 +78,7 @@ impl WavWriter {
|
|||||||
/// boundaries (end-of-utterance, UI events) for tighter recovery.
|
/// boundaries (end-of-utterance, UI events) for tighter recovery.
|
||||||
pub fn flush(&mut self) -> Result<()> {
|
pub fn flush(&mut self) -> Result<()> {
|
||||||
self.inner.flush().map_err(|e| {
|
self.inner.flush().map_err(|e| {
|
||||||
MagnotiaError::from(std::io::Error::other(format!("WAV flush failed: {e}")))
|
Error::from(std::io::Error::other(format!("WAV flush failed: {e}")))
|
||||||
})?;
|
})?;
|
||||||
self.samples_since_flush = 0;
|
self.samples_since_flush = 0;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -90,7 +90,7 @@ impl WavWriter {
|
|||||||
/// that care about the unflushed tail should always finalise.
|
/// that care about the unflushed tail should always finalise.
|
||||||
pub fn finalize(self) -> Result<()> {
|
pub fn finalize(self) -> Result<()> {
|
||||||
self.inner.finalize().map_err(|e| {
|
self.inner.finalize().map_err(|e| {
|
||||||
MagnotiaError::from(std::io::Error::other(format!("WAV finalize failed: {e}")))
|
Error::from(std::io::Error::other(format!("WAV finalize failed: {e}")))
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -106,19 +106,19 @@ pub fn write_wav(path: &Path, audio: &AudioSamples) -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut writer = hound::WavWriter::create(path, spec).map_err(|e| {
|
let mut writer = hound::WavWriter::create(path, spec).map_err(|e| {
|
||||||
MagnotiaError::from(std::io::Error::other(format!("WAV create failed: {e}")))
|
Error::from(std::io::Error::other(format!("WAV create failed: {e}")))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for &sample in audio.samples() {
|
for &sample in audio.samples() {
|
||||||
let clamped = sample.clamp(-1.0, 1.0);
|
let clamped = sample.clamp(-1.0, 1.0);
|
||||||
let int_sample = (clamped * i16::MAX as f32) as i16;
|
let int_sample = (clamped * i16::MAX as f32) as i16;
|
||||||
writer.write_sample(int_sample).map_err(|e| {
|
writer.write_sample(int_sample).map_err(|e| {
|
||||||
MagnotiaError::from(std::io::Error::other(format!("WAV write failed: {e}")))
|
Error::from(std::io::Error::other(format!("WAV write failed: {e}")))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.finalize().map_err(|e| {
|
writer.finalize().map_err(|e| {
|
||||||
MagnotiaError::from(std::io::Error::other(format!("WAV finalize failed: {e}")))
|
Error::from(std::io::Error::other(format!("WAV finalize failed: {e}")))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -126,14 +126,14 @@ pub fn write_wav(path: &Path, audio: &AudioSamples) -> Result<()> {
|
|||||||
|
|
||||||
/// Read a WAV file to f32 PCM `AudioSamples`.
|
/// Read a WAV file to f32 PCM `AudioSamples`.
|
||||||
///
|
///
|
||||||
/// Any per-sample decode error is surfaced as `MagnotiaError::AudioDecodeFailed`
|
/// Any per-sample decode error is surfaced as `Error::AudioDecodeFailed`
|
||||||
/// rather than silently dropped. A previous implementation used
|
/// rather than silently dropped. A previous implementation used
|
||||||
/// `filter_map(|s| s.ok())`, so a truncated or corrupt payload returned
|
/// `filter_map(|s| s.ok())`, so a truncated or corrupt payload returned
|
||||||
/// a short, silently-partial `AudioSamples` — callers got `Ok` while
|
/// a short, silently-partial `AudioSamples` — callers got `Ok` while
|
||||||
/// losing audio (flagged by the 2026-04-22 review).
|
/// losing audio (flagged by the 2026-04-22 review).
|
||||||
pub fn read_wav(path: &Path) -> Result<AudioSamples> {
|
pub fn read_wav(path: &Path) -> Result<AudioSamples> {
|
||||||
let reader = hound::WavReader::open(path)
|
let reader = hound::WavReader::open(path)
|
||||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("WAV open failed: {e}")))?;
|
.map_err(|e| Error::AudioDecodeFailed(format!("WAV open failed: {e}")))?;
|
||||||
|
|
||||||
let spec = reader.spec();
|
let spec = reader.spec();
|
||||||
let sample_rate = spec.sample_rate;
|
let sample_rate = spec.sample_rate;
|
||||||
@@ -147,7 +147,7 @@ pub fn read_wav(path: &Path) -> Result<AudioSamples> {
|
|||||||
sample
|
sample
|
||||||
.map(|s| s as f32 / (1 << (bits_per_sample - 1)) as f32)
|
.map(|s| s as f32 / (1 << (bits_per_sample - 1)) as f32)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
MagnotiaError::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
Error::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<f32>>>()?,
|
.collect::<Result<Vec<f32>>>()?,
|
||||||
@@ -155,7 +155,7 @@ pub fn read_wav(path: &Path) -> Result<AudioSamples> {
|
|||||||
.into_samples::<f32>()
|
.into_samples::<f32>()
|
||||||
.map(|sample| {
|
.map(|sample| {
|
||||||
sample.map_err(|e| {
|
sample.map_err(|e| {
|
||||||
MagnotiaError::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
Error::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<f32>>>()?,
|
.collect::<Result<Vec<f32>>>()?,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::types::ModelId;
|
|||||||
/// Implements `Serialize` so errors can be sent to the frontend as
|
/// Implements `Serialize` so errors can be sent to the frontend as
|
||||||
/// structured JSON rather than opaque strings.
|
/// structured JSON rather than opaque strings.
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum MagnotiaError {
|
pub enum Error {
|
||||||
#[error("model not found: {0}")]
|
#[error("model not found: {0}")]
|
||||||
ModelNotFound(ModelId),
|
ModelNotFound(ModelId),
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ pub enum MagnotiaError {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Coarse discriminator for `MagnotiaError::Storage`. The storage crate maps
|
/// Coarse discriminator for `Error::Storage`. The storage crate maps
|
||||||
/// its full typed error onto one of these kinds at the boundary. Backend code
|
/// its full typed error onto one of these kinds at the boundary. Backend code
|
||||||
/// that wants finer-grained branching pattern-matches on
|
/// that wants finer-grained branching pattern-matches on
|
||||||
/// `lumotia_storage::Error` directly.
|
/// `lumotia_storage::Error` directly.
|
||||||
@@ -70,7 +70,7 @@ pub enum StorageKind {
|
|||||||
Filesystem,
|
Filesystem,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for MagnotiaError {
|
impl From<std::io::Error> for Error {
|
||||||
fn from(err: std::io::Error) -> Self {
|
fn from(err: std::io::Error) -> Self {
|
||||||
Self::Io {
|
Self::Io {
|
||||||
kind: format!("{:?}", err.kind()),
|
kind: format!("{:?}", err.kind()),
|
||||||
@@ -80,4 +80,4 @@ impl From<std::io::Error> for MagnotiaError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, MagnotiaError>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub mod recommendation;
|
|||||||
pub mod tuning;
|
pub mod tuning;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use error::{MagnotiaError, Result};
|
pub use error::{Error, Result};
|
||||||
pub use types::{
|
pub use types::{
|
||||||
AudioSamples, DownloadProgress, EngineName, Megabytes, ModelId, Segment, Transcript,
|
AudioSamples, DownloadProgress, EngineName, Megabytes, ModelId, Segment, Transcript,
|
||||||
TranscriptionOptions,
|
TranscriptionOptions,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
//! Storage-local typed error.
|
//! Storage-local typed error.
|
||||||
//!
|
//!
|
||||||
//! Backend code that wants to branch on storage failure modes pattern-matches
|
//! Backend code that wants to branch on storage failure modes pattern-matches
|
||||||
//! on [`Error`] directly. The crate boundary into [`lumotia_core::error::MagnotiaError`]
|
//! on [`Error`] directly. The crate boundary into [`lumotia_core::error::Error`]
|
||||||
//! flattens this into a [`MagnotiaError::Storage`] variant carrying a serde-friendly
|
//! flattens this into a [`Error::Storage`] variant carrying a serde-friendly
|
||||||
//! `kind`, an `operation` label, and the Display output as `detail` — so the
|
//! `kind`, an `operation` label, and the Display output as `detail` — so the
|
||||||
//! frontend (which still receives stringified errors from Tauri commands today)
|
//! frontend (which still receives stringified errors from Tauri commands today)
|
||||||
//! keeps working, and Area E can later expose the typed `kind` to the FE without
|
//! keeps working, and Area E can later expose the typed `kind` to the FE without
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, StorageKind};
|
use lumotia_core::error::{Error as CoreError, StorageKind};
|
||||||
|
|
||||||
/// Kinds of database-open operation that can fail before the pool is ready.
|
/// Kinds of database-open operation that can fail before the pool is ready.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -145,7 +145,7 @@ pub enum Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
/// Discriminator for the boundary conversion into [`MagnotiaError`].
|
/// Discriminator for the boundary conversion into [`Error`].
|
||||||
pub fn kind(&self) -> StorageKind {
|
pub fn kind(&self) -> StorageKind {
|
||||||
match self {
|
match self {
|
||||||
Error::DatabaseOpen { .. } => StorageKind::DatabaseOpen,
|
Error::DatabaseOpen { .. } => StorageKind::DatabaseOpen,
|
||||||
@@ -157,7 +157,7 @@ impl Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Operation label that flows into the [`MagnotiaError::Storage`] boundary
|
/// Operation label that flows into the [`Error::Storage`] boundary
|
||||||
/// shape. For per-query failures this is the caller-provided label;
|
/// shape. For per-query failures this is the caller-provided label;
|
||||||
/// for the other variants it's the variant's intrinsic label.
|
/// for the other variants it's the variant's intrinsic label.
|
||||||
pub fn operation_label(&self) -> Cow<'static, str> {
|
pub fn operation_label(&self) -> Cow<'static, str> {
|
||||||
@@ -175,14 +175,14 @@ impl Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Boundary conversion — flattens the typed error into the wire-friendly
|
/// Boundary conversion — flattens the typed error into the wire-friendly
|
||||||
/// [`MagnotiaError::Storage`] variant. Lives in the storage crate (not in core)
|
/// [`CoreError::Storage`] variant. Lives in the storage crate (not in core)
|
||||||
/// to avoid a `core -> storage` dependency cycle.
|
/// to avoid a `core -> storage` dependency cycle.
|
||||||
impl From<Error> for MagnotiaError {
|
impl From<Error> for CoreError {
|
||||||
fn from(error: Error) -> Self {
|
fn from(error: Error) -> Self {
|
||||||
let kind = error.kind();
|
let kind = error.kind();
|
||||||
let operation = error.operation_label().into_owned();
|
let operation = error.operation_label().into_owned();
|
||||||
let detail = error.to_string();
|
let detail = error.to_string();
|
||||||
MagnotiaError::Storage {
|
CoreError::Storage {
|
||||||
kind,
|
kind,
|
||||||
operation,
|
operation,
|
||||||
detail,
|
detail,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::types::{AudioSamples, TranscriptionOptions};
|
use lumotia_core::types::{AudioSamples, TranscriptionOptions};
|
||||||
|
|
||||||
use crate::local_engine::{LocalEngine, TimedTranscript};
|
use crate::local_engine::{LocalEngine, TimedTranscript};
|
||||||
@@ -14,5 +14,5 @@ pub async fn run_inference(
|
|||||||
) -> Result<TimedTranscript> {
|
) -> Result<TimedTranscript> {
|
||||||
tokio::task::spawn_blocking(move || engine.transcribe_sync(&audio, &options))
|
tokio::task::spawn_blocking(move || engine.transcribe_sync(&audio, &options))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| MagnotiaError::TranscriptionFailed(format!("Task join error: {e}")))?
|
.map_err(|e| Error::TranscriptionFailed(format!("Task join error: {e}")))?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::time::Instant;
|
|||||||
|
|
||||||
use transcribe_rs::{SpeechModel, TranscribeOptions, TranscriptionResult};
|
use transcribe_rs::{SpeechModel, TranscribeOptions, TranscriptionResult};
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::types::{
|
use lumotia_core::types::{
|
||||||
AudioSamples, EngineName, ModelId, Segment, Transcript, TranscriptionOptions,
|
AudioSamples, EngineName, ModelId, Segment, Transcript, TranscriptionOptions,
|
||||||
};
|
};
|
||||||
@@ -48,7 +48,7 @@ impl Transcriber for SpeechModelAdapter {
|
|||||||
let result: TranscriptionResult = self
|
let result: TranscriptionResult = self
|
||||||
.0
|
.0
|
||||||
.transcribe(samples, &opts)
|
.transcribe(samples, &opts)
|
||||||
.map_err(|e| MagnotiaError::TranscriptionFailed(e.to_string()))?;
|
.map_err(|e| Error::TranscriptionFailed(e.to_string()))?;
|
||||||
Ok(result
|
Ok(result
|
||||||
.segments
|
.segments
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@@ -140,7 +140,7 @@ impl LocalEngine {
|
|||||||
options: &TranscriptionOptions,
|
options: &TranscriptionOptions,
|
||||||
) -> Result<TimedTranscript> {
|
) -> Result<TimedTranscript> {
|
||||||
let mut guard = self.engine.lock().unwrap_or_else(|e| e.into_inner());
|
let mut guard = self.engine.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let backend = guard.as_mut().ok_or(MagnotiaError::EngineNotLoaded)?;
|
let backend = guard.as_mut().ok_or(Error::EngineNotLoaded)?;
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let segments = backend.transcribe_sync(audio.samples(), options)?;
|
let segments = backend.transcribe_sync(audio.samples(), options)?;
|
||||||
@@ -197,7 +197,7 @@ impl transcribe_rs::SpeechModel for ParakeetWordGranularity {
|
|||||||
pub fn load_parakeet(model_dir: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
pub fn load_parakeet(model_dir: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
||||||
use transcribe_rs::onnx::Quantization;
|
use transcribe_rs::onnx::Quantization;
|
||||||
let model = transcribe_rs::onnx::parakeet::ParakeetModel::load(model_dir, &Quantization::Int8)
|
let model = transcribe_rs::onnx::parakeet::ParakeetModel::load(model_dir, &Quantization::Int8)
|
||||||
.map_err(|e| MagnotiaError::TranscriptionFailed(format!("Failed to load Parakeet: {e}")))?;
|
.map_err(|e| Error::TranscriptionFailed(format!("Failed to load Parakeet: {e}")))?;
|
||||||
Ok(Box::new(SpeechModelAdapter(Box::new(
|
Ok(Box::new(SpeechModelAdapter(Box::new(
|
||||||
ParakeetWordGranularity(model),
|
ParakeetWordGranularity(model),
|
||||||
))))
|
))))
|
||||||
@@ -207,7 +207,7 @@ pub fn load_parakeet(model_dir: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
|||||||
#[cfg(feature = "whisper")]
|
#[cfg(feature = "whisper")]
|
||||||
pub fn load_whisper(model_path: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
pub fn load_whisper(model_path: &Path) -> Result<Box<dyn Transcriber + Send>> {
|
||||||
let backend = WhisperRsBackend::load(model_path)
|
let backend = WhisperRsBackend::load(model_path)
|
||||||
.map_err(|e| MagnotiaError::TranscriptionFailed(format!("Failed to load Whisper: {e}")))?;
|
.map_err(|e| Error::TranscriptionFailed(format!("Failed to load Whisper: {e}")))?;
|
||||||
Ok(Box::new(backend))
|
Ok(Box::new(backend))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{LazyLock, Mutex};
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::model_registry::{find_model, ModelFile};
|
use lumotia_core::model_registry::{find_model, ModelFile};
|
||||||
use lumotia_core::types::{DownloadProgress, ModelId};
|
use lumotia_core::types::{DownloadProgress, ModelId};
|
||||||
|
|
||||||
@@ -18,9 +18,9 @@ impl DownloadReservation {
|
|||||||
let id = id.as_str().to_string();
|
let id = id.as_str().to_string();
|
||||||
let mut active = ACTIVE_DOWNLOADS
|
let mut active = ACTIVE_DOWNLOADS
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|_| MagnotiaError::DownloadFailed("download lock poisoned".into()))?;
|
.map_err(|_| Error::DownloadFailed("download lock poisoned".into()))?;
|
||||||
if !active.insert(id.clone()) {
|
if !active.insert(id.clone()) {
|
||||||
return Err(MagnotiaError::DownloadFailed(format!(
|
return Err(Error::DownloadFailed(format!(
|
||||||
"download already in progress for {id}"
|
"download already in progress for {id}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ pub async fn download(
|
|||||||
progress: impl Fn(DownloadProgress) + Send + 'static,
|
progress: impl Fn(DownloadProgress) + Send + 'static,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let _reservation = DownloadReservation::acquire(id)?;
|
let _reservation = DownloadReservation::acquire(id)?;
|
||||||
let entry = find_model(id).ok_or_else(|| MagnotiaError::ModelNotFound(id.clone()))?;
|
let entry = find_model(id).ok_or_else(|| Error::ModelNotFound(id.clone()))?;
|
||||||
|
|
||||||
let dir = model_dir(id);
|
let dir = model_dir(id);
|
||||||
std::fs::create_dir_all(&dir)?;
|
std::fs::create_dir_all(&dir)?;
|
||||||
@@ -98,7 +98,7 @@ pub async fn download(
|
|||||||
let _ = std::fs::remove_file(&dest);
|
let _ = std::fs::remove_file(&dest);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(MagnotiaError::DownloadFailed(format!(
|
return Err(Error::DownloadFailed(format!(
|
||||||
"failed to verify existing {}: {e}",
|
"failed to verify existing {}: {e}",
|
||||||
file.filename
|
file.filename
|
||||||
)));
|
)));
|
||||||
@@ -196,7 +196,7 @@ async fn download_file(
|
|||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
.connect_timeout(std::time::Duration::from_secs(30))
|
.connect_timeout(std::time::Duration::from_secs(30))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?;
|
.map_err(|e| Error::DownloadFailed(e.to_string()))?;
|
||||||
|
|
||||||
// Check for existing partial download (resume support)
|
// Check for existing partial download (resume support)
|
||||||
let existing_bytes = if part_path.exists() {
|
let existing_bytes = if part_path.exists() {
|
||||||
@@ -215,7 +215,7 @@ async fn download_file(
|
|||||||
let response = request
|
let response = request
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?;
|
.map_err(|e| Error::DownloadFailed(e.to_string()))?;
|
||||||
|
|
||||||
// If we requested Range but the server returned 200 (full file), the
|
// If we requested Range but the server returned 200 (full file), the
|
||||||
// server does not support resume. Rather than blindly appending a
|
// server does not support resume. Rather than blindly appending a
|
||||||
@@ -237,14 +237,14 @@ async fn download_file(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(MagnotiaError::DownloadFailed(format!(
|
return Err(Error::DownloadFailed(format!(
|
||||||
"resume request returned unexpected status {other}"
|
"resume request returned unexpected status {other}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
return Err(MagnotiaError::DownloadFailed(format!(
|
return Err(Error::DownloadFailed(format!(
|
||||||
"download returned HTTP {} for {}",
|
"download returned HTTP {} for {}",
|
||||||
response.status(),
|
response.status(),
|
||||||
file.filename
|
file.filename
|
||||||
@@ -291,7 +291,7 @@ async fn download_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
while let Some(chunk) = stream.next().await {
|
while let Some(chunk) = stream.next().await {
|
||||||
let chunk = chunk.map_err(|e| MagnotiaError::DownloadFailed(e.to_string()))?;
|
let chunk = chunk.map_err(|e| Error::DownloadFailed(e.to_string()))?;
|
||||||
std::io::Write::write_all(&mut out, &chunk)?;
|
std::io::Write::write_all(&mut out, &chunk)?;
|
||||||
hasher.update(&chunk);
|
hasher.update(&chunk);
|
||||||
downloaded += chunk.len() as u64;
|
downloaded += chunk.len() as u64;
|
||||||
@@ -319,7 +319,7 @@ async fn download_file(
|
|||||||
let actual = format!("{:x}", hasher.finalize());
|
let actual = format!("{:x}", hasher.finalize());
|
||||||
if actual != file.sha256 {
|
if actual != file.sha256 {
|
||||||
let _ = std::fs::remove_file(&part_path);
|
let _ = std::fs::remove_file(&part_path);
|
||||||
return Err(MagnotiaError::DownloadFailed(format!(
|
return Err(Error::DownloadFailed(format!(
|
||||||
"SHA256 mismatch for {}: expected {}, got {}",
|
"SHA256 mismatch for {}: expected {}, got {}",
|
||||||
file.filename, file.sha256, actual
|
file.filename, file.sha256, actual
|
||||||
)));
|
)));
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use lumotia_cloud_providers::{
|
|||||||
CostClass, EngineProfile, ProviderCapabilities, ProviderId, ProviderKind, ProviderTranscript,
|
CostClass, EngineProfile, ProviderCapabilities, ProviderId, ProviderKind, ProviderTranscript,
|
||||||
TranscriptionProvider,
|
TranscriptionProvider,
|
||||||
};
|
};
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::types::{AudioSamples, TranscriptionOptions};
|
use lumotia_core::types::{AudioSamples, TranscriptionOptions};
|
||||||
|
|
||||||
use crate::local_engine::LocalEngine;
|
use crate::local_engine::LocalEngine;
|
||||||
@@ -81,7 +81,7 @@ impl TranscriptionProvider for LocalProviderAdapter {
|
|||||||
if self.engine.is_loaded() {
|
if self.engine.is_loaded() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(MagnotiaError::EngineNotLoaded)
|
Err(Error::EngineNotLoaded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ impl TranscriptionProvider for LocalProviderAdapter {
|
|||||||
let engine = self.engine.clone();
|
let engine = self.engine.clone();
|
||||||
let timed = tokio::task::spawn_blocking(move || engine.transcribe_sync(&audio, &options))
|
let timed = tokio::task::spawn_blocking(move || engine.transcribe_sync(&audio, &options))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| MagnotiaError::TranscriptionFailed(format!("Task join error: {e}")))??;
|
.map_err(|e| Error::TranscriptionFailed(format!("Task join error: {e}")))??;
|
||||||
Ok(ProviderTranscript {
|
Ok(ProviderTranscript {
|
||||||
transcript: timed.transcript,
|
transcript: timed.transcript,
|
||||||
inference_ms: timed.inference_ms,
|
inference_ms: timed.inference_ms,
|
||||||
@@ -124,7 +124,7 @@ impl Orchestrator {
|
|||||||
pub fn resolve(&self, profile: &EngineProfile) -> Result<Arc<dyn TranscriptionProvider>> {
|
pub fn resolve(&self, profile: &EngineProfile) -> Result<Arc<dyn TranscriptionProvider>> {
|
||||||
self.registry
|
self.registry
|
||||||
.get(&profile.engine_id)
|
.get(&profile.engine_id)
|
||||||
.ok_or_else(|| MagnotiaError::ProviderNotRegistered(profile.engine_id.to_string()))
|
.ok_or_else(|| Error::ProviderNotRegistered(profile.engine_id.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transcribe audio using the provider named in the profile. The
|
/// Transcribe audio using the provider named in the profile. The
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::path::Path;
|
|||||||
|
|
||||||
use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
|
use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};
|
||||||
|
|
||||||
use lumotia_core::error::{MagnotiaError, Result};
|
use lumotia_core::error::{Error, Result};
|
||||||
use lumotia_core::hardware::vulkan_loader_available;
|
use lumotia_core::hardware::vulkan_loader_available;
|
||||||
use lumotia_core::tuning::{inference_thread_count, Workload};
|
use lumotia_core::tuning::{inference_thread_count, Workload};
|
||||||
use lumotia_core::types::{Segment, TranscriptionOptions};
|
use lumotia_core::types::{Segment, TranscriptionOptions};
|
||||||
@@ -65,7 +65,7 @@ impl Transcriber for WhisperRsBackend {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut state = self.ctx.create_state().map_err(|e| {
|
let mut state = self.ctx.create_state().map_err(|e| {
|
||||||
MagnotiaError::TranscriptionFailed(
|
Error::TranscriptionFailed(
|
||||||
WhisperBackendError::State(e.to_string()).to_string(),
|
WhisperBackendError::State(e.to_string()).to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@@ -88,7 +88,7 @@ impl Transcriber for WhisperRsBackend {
|
|||||||
params.set_print_realtime(false);
|
params.set_print_realtime(false);
|
||||||
|
|
||||||
state.full(params, samples).map_err(|e| {
|
state.full(params, samples).map_err(|e| {
|
||||||
MagnotiaError::TranscriptionFailed(
|
Error::TranscriptionFailed(
|
||||||
WhisperBackendError::Transcribe(e.to_string()).to_string(),
|
WhisperBackendError::Transcribe(e.to_string()).to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@@ -103,7 +103,7 @@ impl Transcriber for WhisperRsBackend {
|
|||||||
let text = seg
|
let text = seg
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
MagnotiaError::TranscriptionFailed(
|
Error::TranscriptionFailed(
|
||||||
WhisperBackendError::Transcribe(e.to_string()).to_string(),
|
WhisperBackendError::Transcribe(e.to_string()).to_string(),
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
|
|||||||
Reference in New Issue
Block a user