1use crate::media::cache;
2use anyhow::{Result, anyhow};
3use audio_codec::BoxedResampler;
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 =
149 BoxedResampler::new(48000, target_sample_rate as usize).map_err(anyhow::Error::from)?;
150 all_samples = resampler.resample(&all_samples);
151 }
152
153 Ok(all_samples)
154}
155
156pub fn decode_wav(file: File, target_sample_rate: u32) -> Result<Vec<i16>> {
157 let reader = BufReader::new(file);
158 let mut wav_reader = WavReader::new(reader)?;
159 let spec = wav_reader.spec();
160 let sample_rate = spec.sample_rate;
161 let is_stereo = spec.channels == 2;
162
163 info!(
164 "WAV file detected with sample rate: {} Hz, channels: {}, bits: {}",
165 sample_rate, spec.channels, spec.bits_per_sample
166 );
167
168 let mut all_samples = Vec::new();
169
170 match spec.sample_format {
172 hound::SampleFormat::Int => match spec.bits_per_sample {
173 16 => {
174 for sample in wav_reader.samples::<i16>() {
175 if let Ok(s) = sample {
176 all_samples.push(s);
177 } else {
178 break;
179 }
180 }
181 }
182 8 => {
183 for sample in wav_reader.samples::<i8>() {
184 if let Ok(s) = sample {
185 all_samples.push((s as i16) * 256); } else {
187 break;
188 }
189 }
190 }
191 24 | 32 => {
192 for sample in wav_reader.samples::<i32>() {
193 if let Ok(s) = sample {
194 all_samples.push((s >> 16) as i16); } else {
196 break;
197 }
198 }
199 }
200 _ => {
201 return Err(anyhow!(
202 "Unsupported bits per sample: {}",
203 spec.bits_per_sample
204 ));
205 }
206 },
207 hound::SampleFormat::Float => {
208 for sample in wav_reader.samples::<f32>() {
209 if let Ok(s) = sample {
210 all_samples.push((s * 32767.0) as i16); } else {
212 break;
213 }
214 }
215 }
216 }
217
218 if is_stereo {
220 let mono_samples = all_samples
221 .chunks(2)
222 .map(|chunk| {
223 if chunk.len() == 2 {
224 ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16
225 } else {
226 chunk[0]
227 }
228 })
229 .collect();
230 all_samples = mono_samples;
231 }
232
233 if sample_rate != target_sample_rate && sample_rate > 0 {
234 let mut resampler =
235 BoxedResampler::new(sample_rate as usize, target_sample_rate as usize)
236 .map_err(anyhow::Error::from)?;
237 all_samples = resampler.resample(&all_samples);
238 }
239
240 Ok(all_samples)
241}
242
243pub fn decode_audio(
244 mut file: File,
245 extension: &str,
246 mime_type: Option<&str>,
247 target_sample_rate: u32,
248) -> Result<Vec<i16>> {
249 if matches!(extension, "wav")
250 || matches!(
251 mime_type,
252 Some("audio/wav") | Some("audio/wave") | Some("audio/x-wav")
253 )
254 {
255 return decode_wav(file, target_sample_rate);
256 }
257
258 if is_ogg(extension, mime_type) {
259 match detect_ogg_codec(&mut file)? {
260 OggCodec::Opus { channels } => {
261 return decode_opus_ogg(file, channels, target_sample_rate);
262 }
263 OggCodec::Other => {} }
265 }
266
267 let mss = MediaSourceStream::new(Box::new(file), Default::default());
268 let mut hint = Hint::new();
269 if !extension.is_empty() {
270 hint.with_extension(extension);
271 }
272 if let Some(mime) = mime_type {
273 hint.mime_type(mime);
274 }
275
276 let mut format = get_probe().probe(
277 &hint,
278 mss,
279 FormatOptions::default(),
280 MetadataOptions::default(),
281 )?;
282 let (track_id, audio_params) = {
283 let track = format
284 .default_track(TrackType::Audio)
285 .ok_or_else(|| anyhow!("loader: no default audio track found"))?;
286 let codec_params = track.codec_params
287 .as_ref()
288 .ok_or_else(|| anyhow!("loader: no codec parameters"))?;
289 let params = match codec_params {
290 CodecParameters::Audio(params) => params.clone(),
291 _ => return Err(anyhow!("loader: expected audio codec")),
292 };
293 (track.id, params)
294 };
295
296 let mut decoder = get_codecs().make_audio_decoder(&audio_params, &AudioDecoderOptions::default())?;
297 let mut all_samples = Vec::new();
298 let mut sample_rate = audio_params.sample_rate.unwrap_or(0);
299
300 loop {
301 let packet = match format.next_packet() {
302 Ok(Some(packet)) => packet,
303 Ok(None) => break,
304 Err(SymphoniaError::IoError(_)) => break,
305 Err(SymphoniaError::ResetRequired) => continue,
306 Err(e) => return Err(anyhow!("loader: failed reading audio packet: {e}")),
307 };
308
309 if packet.track_id != track_id {
310 continue;
311 }
312
313 match decoder.decode(&packet) {
314 Ok(decoded) => {
315 if sample_rate == 0 {
316 sample_rate = decoded.spec().rate();
317 info!(
318 "loader: detected {:?} with sample rate: {} Hz, channels: {}",
319 audio_params.codec,
320 sample_rate,
321 decoded.spec().channels().count()
322 );
323 }
324 let channels = decoded.spec().channels().count();
325
326 let mut interleaved = Vec::new();
327 decoded.copy_to_vec_interleaved(&mut interleaved);
328
329 if channels <= 1 {
330 all_samples.extend_from_slice(&interleaved);
331 } else {
332 for frame in interleaved.chunks(channels) {
333 if frame.is_empty() {
334 continue;
335 }
336 let sum: i32 = frame.iter().map(|s| *s as i32).sum();
337 all_samples.push((sum / frame.len() as i32) as i16);
338 }
339 }
340 }
341 Err(SymphoniaError::DecodeError(_)) => continue,
342 Err(SymphoniaError::IoError(_)) => break,
343 Err(SymphoniaError::ResetRequired) => continue,
344 Err(e) => return Err(anyhow!("loader: failed decoding audio packet: {e}")),
345 }
346 }
347
348 if all_samples.is_empty() {
349 return Err(anyhow!("loader: no decodable audio samples found"));
350 }
351
352 if sample_rate != target_sample_rate && sample_rate > 0 {
353 let mut resampler =
354 BoxedResampler::new(sample_rate as usize, target_sample_rate as usize)
355 .map_err(anyhow::Error::from)?;
356 all_samples = resampler.resample(&all_samples);
357 }
358
359 Ok(all_samples)
360}
361
362pub async fn load_audio_as_pcm_cached(
370 path: &str,
371 target_sample_rate: u32,
372 use_cache: bool,
373 offset_ms: u32,
374) -> Result<Vec<i16>> {
375 let cache_key = cache::generate_cache_key(path, target_sample_rate, None, None);
376 let offset_samples = (offset_ms as usize * target_sample_rate as usize) / 1000;
377
378 if use_cache && cache::is_cached(&cache_key).await? {
379 match cache::retrieve_pcm_from_cache_at(&cache_key, offset_samples).await {
380 Ok(samples) => {
381 info!(
382 "loader: loaded {} decoded samples from pcm cache for {} (offset {} ms)",
383 samples.len(),
384 path,
385 offset_ms
386 );
387 return Ok(samples);
388 }
389 Err(e) => warn!("loader: failed to read pcm cache for {}: {}", path, e),
390 }
391 }
392
393 let is_url = path.starts_with("http://") || path.starts_with("https://");
394
395 let (file, content_type) = if is_url {
398 download_from_url(path, false).await?
399 } else {
400 (
401 File::open(path).map_err(|e| anyhow!("loader: {} {}", path, e))?,
402 None,
403 )
404 };
405
406 let extension = if is_url {
407 path.parse::<Url>()?
408 .path()
409 .split('.')
410 .last()
411 .unwrap_or("")
412 .to_string()
413 } else {
414 path.split('.').last().unwrap_or("").to_string()
415 };
416
417 let mut samples = tokio::task::spawn_blocking(move || {
419 decode_audio(file, &extension, content_type.as_deref(), target_sample_rate)
420 })
421 .await??;
422
423 if use_cache {
425 if let Err(e) = cache::store_pcm_in_cache(&cache_key, &samples).await {
426 warn!("loader: failed to store pcm cache for {}: {}", path, e);
427 }
428 }
429
430 let skip = offset_samples.min(samples.len());
431 samples.drain(..skip);
432
433 Ok(samples)
434}
435
436pub async fn load_audio_as_pcm(
437 path: &str,
438 target_sample_rate: u32,
439 use_cache: bool,
440) -> Result<Vec<i16>> {
441 let is_url = path.starts_with("http://") || path.starts_with("https://");
442
443 let (file, content_type) = if is_url {
444 download_from_url(path, use_cache).await?
445 } else {
446 (File::open(path).map_err(|e| anyhow!("loader: {} {}", path, e))?, None)
447 };
448
449 let extension = if is_url {
450 path.parse::<Url>()?.path().split('.').last().unwrap_or("").to_string()
451 } else {
452 path.split('.').last().unwrap_or("").to_string()
453 };
454
455 decode_audio(
456 file,
457 &extension,
458 content_type.as_deref(),
459 target_sample_rate,
460 )
461}