1use crate::media::cache;
2use anyhow::{Result, anyhow};
3use audio_codec::Resampler;
4use audio_codec::opus::OpusDecoder;
5use hound::WavReader;
6use ogg::reading::PacketReader;
7use reqwest::Client;
8use std::fs::File;
9use std::io::{BufReader, Seek, SeekFrom, Write};
10use std::time::Instant;
11use symphonia::core::codecs::audio::AudioDecoderOptions;
12use symphonia::core::codecs::CodecParameters;
13use symphonia::core::errors::Error as SymphoniaError;
14use symphonia::core::formats::probe::Hint;
15use symphonia::core::formats::FormatOptions;
16use symphonia::core::formats::TrackType;
17use symphonia::core::io::MediaSourceStream;
18use symphonia::core::meta::MetadataOptions;
19use symphonia::default::{get_codecs, get_probe};
20use tracing::{info, warn};
21use url::Url;
22
23pub async fn download_from_url(url: &str, use_cache: bool) -> Result<(File, Option<String>)> {
24 let cache_key = cache::generate_cache_key(url, 0, None, None);
25 if use_cache && cache::is_cached(&cache_key).await? {
26 match cache::get_cache_path(&cache_key) {
27 Ok(path) => return Ok((File::open(&path).map_err(|e| anyhow!(e))?, None)),
28 Err(e) => {
29 warn!("loader: Error getting cache path: {}", e);
30 return Err(e);
31 }
32 }
33 }
34
35 let start_time = Instant::now();
36 let client = Client::new();
37 let response = client.get(url).send().await?;
38 let content_type = response
39 .headers()
40 .get(reqwest::header::CONTENT_TYPE)
41 .and_then(|v| v.to_str().ok())
42 .map(|s| s.split(';').next().unwrap_or(s).trim().to_string());
43 let bytes = response.bytes().await?;
44 let data = bytes.to_vec();
45 let duration = start_time.elapsed();
46
47 info!(
48 "loader: Downloaded {} bytes in {:?} for {} (content-type: {:?})",
49 data.len(),
50 duration,
51 url,
52 content_type,
53 );
54
55 if use_cache {
56 cache::store_in_cache(&cache_key, &data).await?;
57 match cache::get_cache_path(&cache_key) {
58 Ok(path) => return Ok((File::open(path).map_err(|e| anyhow!(e))?, content_type)),
59 Err(e) => {
60 warn!("loader: Error getting cache path: {}", e);
61 return Err(e);
62 }
63 }
64 }
65
66 let mut temp_file = tempfile::tempfile()?;
67 temp_file.write_all(&data)?;
68 temp_file.seek(SeekFrom::Start(0))?;
69 Ok((temp_file, content_type))
70}
71
72fn is_ogg(extension: &str, mime_type: Option<&str>) -> bool {
73 matches!(extension, "ogg" | "opus")
74 || matches!(
75 mime_type,
76 Some("audio/ogg") | Some("audio/opus") | Some("application/ogg")
77 )
78}
79
80enum OggCodec {
81 Opus { channels: u16 },
82 Other,
83}
84
85fn detect_ogg_codec(file: &mut File) -> Result<OggCodec> {
86 let mut reader = PacketReader::new(BufReader::new(&mut *file));
87 let head = reader
88 .read_packet_expected()
89 .map_err(|e| anyhow!("loader: failed reading OGG header: {e}"))?;
90 let codec = if head.data.starts_with(b"OpusHead") {
91 let channels = if head.data.len() > 9 {
92 head.data[9] as u16
93 } else {
94 2
95 };
96 OggCodec::Opus { channels }
97 } else {
98 OggCodec::Other
99 };
100 file.seek(SeekFrom::Start(0))?;
101 Ok(codec)
102}
103
104fn decode_opus_ogg(file: File, channels: u16, target_sample_rate: u32) -> Result<Vec<i16>> {
105 let mut reader = PacketReader::new(BufReader::new(file));
106
107 let head = reader
109 .read_packet_expected()
110 .map_err(|e| anyhow!("loader: failed reading OGG header: {e}"))?;
111 let channels = if head.data.len() > 9 {
112 head.data[9] as u16
113 } else {
114 channels
115 };
116
117 reader
119 .read_packet_expected()
120 .map_err(|e| anyhow!("loader: failed reading OpusTags: {e}"))?;
121
122 let mut decoder = OpusDecoder::new(48000, channels);
124 let mut all_samples: Vec<i16> = Vec::new();
125
126 loop {
127 let packet = match reader.read_packet() {
128 Ok(Some(p)) => p,
129 Ok(None) => break,
130 Err(e) => return Err(anyhow!("loader: failed reading OGG packet: {e}")),
131 };
132 let samples = audio_codec::Decoder::decode(&mut decoder, &packet.data);
133 all_samples.extend_from_slice(&samples);
134 }
135
136 if all_samples.is_empty() {
137 return Err(anyhow!(
138 "loader: no decodable audio samples found in Opus stream"
139 ));
140 }
141
142 info!(
143 "loader: decoded Opus stream at 48000 Hz, {} channel(s)",
144 channels
145 );
146
147 if target_sample_rate != 48000 {
148 let mut resampler = Resampler::new(48000, target_sample_rate as usize);
149 all_samples = resampler.resample(&all_samples);
150 }
151
152 Ok(all_samples)
153}
154
155pub fn decode_wav(file: File, target_sample_rate: u32) -> Result<Vec<i16>> {
156 let reader = BufReader::new(file);
157 let mut wav_reader = WavReader::new(reader)?;
158 let spec = wav_reader.spec();
159 let sample_rate = spec.sample_rate;
160 let is_stereo = spec.channels == 2;
161
162 info!(
163 "WAV file detected with sample rate: {} Hz, channels: {}, bits: {}",
164 sample_rate, spec.channels, spec.bits_per_sample
165 );
166
167 let mut all_samples = Vec::new();
168
169 match spec.sample_format {
171 hound::SampleFormat::Int => match spec.bits_per_sample {
172 16 => {
173 for sample in wav_reader.samples::<i16>() {
174 if let Ok(s) = sample {
175 all_samples.push(s);
176 } else {
177 break;
178 }
179 }
180 }
181 8 => {
182 for sample in wav_reader.samples::<i8>() {
183 if let Ok(s) = sample {
184 all_samples.push((s as i16) * 256); } else {
186 break;
187 }
188 }
189 }
190 24 | 32 => {
191 for sample in wav_reader.samples::<i32>() {
192 if let Ok(s) = sample {
193 all_samples.push((s >> 16) as i16); } else {
195 break;
196 }
197 }
198 }
199 _ => {
200 return Err(anyhow!(
201 "Unsupported bits per sample: {}",
202 spec.bits_per_sample
203 ));
204 }
205 },
206 hound::SampleFormat::Float => {
207 for sample in wav_reader.samples::<f32>() {
208 if let Ok(s) = sample {
209 all_samples.push((s * 32767.0) as i16); } else {
211 break;
212 }
213 }
214 }
215 }
216
217 if is_stereo {
219 let mono_samples = all_samples
220 .chunks(2)
221 .map(|chunk| {
222 if chunk.len() == 2 {
223 ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16
224 } else {
225 chunk[0]
226 }
227 })
228 .collect();
229 all_samples = mono_samples;
230 }
231
232 if sample_rate != target_sample_rate && sample_rate > 0 {
233 let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
234 all_samples = resampler.resample(&all_samples);
235 }
236
237 Ok(all_samples)
238}
239
240pub fn decode_audio(
241 mut file: File,
242 extension: &str,
243 mime_type: Option<&str>,
244 target_sample_rate: u32,
245) -> Result<Vec<i16>> {
246 if matches!(extension, "wav")
247 || matches!(
248 mime_type,
249 Some("audio/wav") | Some("audio/wave") | Some("audio/x-wav")
250 )
251 {
252 return decode_wav(file, target_sample_rate);
253 }
254
255 if is_ogg(extension, mime_type) {
256 match detect_ogg_codec(&mut file)? {
257 OggCodec::Opus { channels } => {
258 return decode_opus_ogg(file, channels, target_sample_rate);
259 }
260 OggCodec::Other => {} }
262 }
263
264 let mss = MediaSourceStream::new(Box::new(file), Default::default());
265 let mut hint = Hint::new();
266 if !extension.is_empty() {
267 hint.with_extension(extension);
268 }
269 if let Some(mime) = mime_type {
270 hint.mime_type(mime);
271 }
272
273 let mut format = get_probe().probe(
274 &hint,
275 mss,
276 FormatOptions::default(),
277 MetadataOptions::default(),
278 )?;
279 let (track_id, audio_params) = {
280 let track = format
281 .default_track(TrackType::Audio)
282 .ok_or_else(|| anyhow!("loader: no default audio track found"))?;
283 let codec_params = track.codec_params
284 .as_ref()
285 .ok_or_else(|| anyhow!("loader: no codec parameters"))?;
286 let params = match codec_params {
287 CodecParameters::Audio(params) => params.clone(),
288 _ => return Err(anyhow!("loader: expected audio codec")),
289 };
290 (track.id, params)
291 };
292
293 let mut decoder = get_codecs().make_audio_decoder(&audio_params, &AudioDecoderOptions::default())?;
294 let mut all_samples = Vec::new();
295 let mut sample_rate = audio_params.sample_rate.unwrap_or(0);
296
297 loop {
298 let packet = match format.next_packet() {
299 Ok(Some(packet)) => packet,
300 Ok(None) => break,
301 Err(SymphoniaError::IoError(_)) => break,
302 Err(SymphoniaError::ResetRequired) => continue,
303 Err(e) => return Err(anyhow!("loader: failed reading audio packet: {e}")),
304 };
305
306 if packet.track_id != track_id {
307 continue;
308 }
309
310 match decoder.decode(&packet) {
311 Ok(decoded) => {
312 if sample_rate == 0 {
313 sample_rate = decoded.spec().rate();
314 info!(
315 "loader: detected {:?} with sample rate: {} Hz, channels: {}",
316 audio_params.codec,
317 sample_rate,
318 decoded.spec().channels().count()
319 );
320 }
321 let channels = decoded.spec().channels().count();
322
323 let mut interleaved = Vec::new();
324 decoded.copy_to_vec_interleaved(&mut interleaved);
325
326 if channels <= 1 {
327 all_samples.extend_from_slice(&interleaved);
328 } else {
329 for frame in interleaved.chunks(channels) {
330 if frame.is_empty() {
331 continue;
332 }
333 let sum: i32 = frame.iter().map(|s| *s as i32).sum();
334 all_samples.push((sum / frame.len() as i32) as i16);
335 }
336 }
337 }
338 Err(SymphoniaError::DecodeError(_)) => continue,
339 Err(SymphoniaError::IoError(_)) => break,
340 Err(SymphoniaError::ResetRequired) => continue,
341 Err(e) => return Err(anyhow!("loader: failed decoding audio packet: {e}")),
342 }
343 }
344
345 if all_samples.is_empty() {
346 return Err(anyhow!("loader: no decodable audio samples found"));
347 }
348
349 if sample_rate != target_sample_rate && sample_rate > 0 {
350 let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
351 all_samples = resampler.resample(&all_samples);
352 }
353
354 Ok(all_samples)
355}
356
357pub async fn load_audio_as_pcm_cached(
365 path: &str,
366 target_sample_rate: u32,
367 use_cache: bool,
368 offset_ms: u32,
369) -> Result<Vec<i16>> {
370 let cache_key = cache::generate_cache_key(path, target_sample_rate, None, None);
371 let offset_samples = (offset_ms as usize * target_sample_rate as usize) / 1000;
372
373 if use_cache && cache::is_cached(&cache_key).await? {
374 match cache::retrieve_pcm_from_cache_at(&cache_key, offset_samples).await {
375 Ok(samples) => {
376 info!(
377 "loader: loaded {} decoded samples from pcm cache for {} (offset {} ms)",
378 samples.len(),
379 path,
380 offset_ms
381 );
382 return Ok(samples);
383 }
384 Err(e) => warn!("loader: failed to read pcm cache for {}: {}", path, e),
385 }
386 }
387
388 let is_url = path.starts_with("http://") || path.starts_with("https://");
389
390 let (file, content_type) = if is_url {
393 download_from_url(path, false).await?
394 } else {
395 (
396 File::open(path).map_err(|e| anyhow!("loader: {} {}", path, e))?,
397 None,
398 )
399 };
400
401 let extension = if is_url {
402 path.parse::<Url>()?
403 .path()
404 .split('.')
405 .last()
406 .unwrap_or("")
407 .to_string()
408 } else {
409 path.split('.').last().unwrap_or("").to_string()
410 };
411
412 let mut samples = tokio::task::spawn_blocking(move || {
414 decode_audio(file, &extension, content_type.as_deref(), target_sample_rate)
415 })
416 .await??;
417
418 if use_cache {
420 if let Err(e) = cache::store_pcm_in_cache(&cache_key, &samples).await {
421 warn!("loader: failed to store pcm cache for {}: {}", path, e);
422 }
423 }
424
425 let skip = offset_samples.min(samples.len());
426 samples.drain(..skip);
427
428 Ok(samples)
429}
430
431pub async fn load_audio_as_pcm(
432 path: &str,
433 target_sample_rate: u32,
434 use_cache: bool,
435) -> Result<Vec<i16>> {
436 let is_url = path.starts_with("http://") || path.starts_with("https://");
437
438 let (file, content_type) = if is_url {
439 download_from_url(path, use_cache).await?
440 } else {
441 (File::open(path).map_err(|e| anyhow!("loader: {} {}", path, e))?, None)
442 };
443
444 let extension = if is_url {
445 path.parse::<Url>()?.path().split('.').last().unwrap_or("").to_string()
446 } else {
447 path.split('.').last().unwrap_or("").to_string()
448 };
449
450 decode_audio(
451 file,
452 &extension,
453 content_type.as_deref(),
454 target_sample_rate,
455 )
456}