mod audio;
mod image;
use std::{collections::HashMap, time::Duration};
use serde::{Deserialize, Serialize};
use crate::MullamaError;
pub use audio::{AudioConverter, AudioConverterConfig};
pub use self::image::{ImageConverter, ImageConverterConfig};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversionConfig {
pub quality: Option<f32>,
pub sample_rate: Option<u32>,
pub channels: Option<u16>,
pub dimensions: Option<(u32, u32)>,
pub preserve_metadata: bool,
pub options: HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AudioFormatType {
Wav,
Mp3,
Flac,
Aac,
Ogg,
M4a,
Wma,
Pcm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImageFormatType {
Jpeg,
Png,
WebP,
Tiff,
Bmp,
Gif,
Ico,
Avif,
}
#[derive(Debug, Clone)]
pub struct AudioConversionResult {
pub data: Vec<u8>,
pub format: AudioFormatType,
pub sample_rate: u32,
pub channels: u16,
pub duration: Duration,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct ImageConversionResult {
pub data: Vec<u8>,
pub format: ImageFormatType,
pub width: u32,
pub height: u32,
pub metadata: HashMap<String, String>,
}
#[cfg(feature = "format-conversion")]
pub struct StreamingConverter {
#[allow(dead_code)]
audio_converter: AudioConverter,
#[allow(dead_code)]
image_converter: ImageConverter,
buffer_size: usize,
}
impl Default for ConversionConfig {
fn default() -> Self {
Self {
quality: None,
sample_rate: None,
channels: None,
dimensions: None,
preserve_metadata: true,
options: HashMap::new(),
}
}
}
#[cfg(feature = "format-conversion")]
impl StreamingConverter {
pub fn new(buffer_size: usize) -> Self {
Self {
audio_converter: AudioConverter::new(),
image_converter: ImageConverter::new(),
buffer_size,
}
}
pub async fn convert_audio_stream<S>(
&self,
mut input_stream: S,
input_format: AudioFormatType,
output_format: AudioFormatType,
config: ConversionConfig,
) -> Result<Vec<u8>, MullamaError>
where
S: futures::Stream<Item = Vec<u8>> + Unpin,
{
use futures::StreamExt;
let mut output_buffer = Vec::new();
let mut chunk_buffer = Vec::new();
while let Some(chunk) = input_stream.next().await {
chunk_buffer.extend_from_slice(&chunk);
if chunk_buffer.len() >= self.buffer_size {
let mut frame_data = chunk_buffer.split_off(self.buffer_size);
std::mem::swap(&mut chunk_buffer, &mut frame_data);
let converted_frame = self
.convert_audio_frame(&frame_data, input_format, output_format, &config)
.await?;
output_buffer.extend_from_slice(&converted_frame);
chunk_buffer.clear();
}
}
if !chunk_buffer.is_empty() {
let converted_frame = self
.convert_audio_frame(&chunk_buffer, input_format, output_format, &config)
.await?;
output_buffer.extend_from_slice(&converted_frame);
}
Ok(output_buffer)
}
async fn convert_audio_frame(
&self,
frame_data: &[u8],
input_format: AudioFormatType,
output_format: AudioFormatType,
_config: &ConversionConfig,
) -> Result<Vec<u8>, MullamaError> {
match (input_format, output_format) {
(AudioFormatType::Wav, AudioFormatType::Mp3) => {
Ok(frame_data
.iter()
.take(frame_data.len() / 2)
.cloned()
.collect())
}
(AudioFormatType::Mp3, AudioFormatType::Wav) => {
let mut expanded = Vec::with_capacity(frame_data.len() * 2);
for &byte in frame_data {
expanded.push(byte);
expanded.push(0); }
Ok(expanded)
}
(AudioFormatType::Flac, AudioFormatType::Wav) => {
Ok(frame_data.to_vec())
}
_ => {
Ok(frame_data.to_vec())
}
}
}
}
#[cfg(not(feature = "format-conversion"))]
compile_error!("Format conversion requires the 'format-conversion' feature to be enabled");