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::audio::SampleBuffer;
12use symphonia::core::codecs::DecoderOptions;
13use symphonia::core::errors::Error as SymphoniaError;
14use symphonia::core::formats::FormatOptions;
15use symphonia::core::io::MediaSourceStream;
16use symphonia::core::meta::MetadataOptions;
17use symphonia::core::probe::Hint;
18use symphonia::default::{get_codecs, get_probe};
19use tracing::{info, warn};
20use url::Url;
21
22pub async fn download_from_url(url: &str, use_cache: bool) -> Result<(File, Option<String>)> {
23 let cache_key = cache::generate_cache_key(url, 0, None, None);
24 if use_cache && cache::is_cached(&cache_key).await? {
25 match cache::get_cache_path(&cache_key) {
26 Ok(path) => return Ok((File::open(&path).map_err(|e| anyhow!(e))?, None)),
27 Err(e) => {
28 warn!("loader: Error getting cache path: {}", e);
29 return Err(e);
30 }
31 }
32 }
33
34 let start_time = Instant::now();
35 let client = Client::new();
36 let response = client.get(url).send().await?;
37 let content_type = response
38 .headers()
39 .get(reqwest::header::CONTENT_TYPE)
40 .and_then(|v| v.to_str().ok())
41 .map(|s| s.split(';').next().unwrap_or(s).trim().to_string());
42 let bytes = response.bytes().await?;
43 let data = bytes.to_vec();
44 let duration = start_time.elapsed();
45
46 info!(
47 "loader: Downloaded {} bytes in {:?} for {} (content-type: {:?})",
48 data.len(),
49 duration,
50 url,
51 content_type,
52 );
53
54 if use_cache {
55 cache::store_in_cache(&cache_key, &data).await?;
56 match cache::get_cache_path(&cache_key) {
57 Ok(path) => return Ok((File::open(path).map_err(|e| anyhow!(e))?, content_type)),
58 Err(e) => {
59 warn!("loader: Error getting cache path: {}", e);
60 return Err(e);
61 }
62 }
63 }
64
65 let mut temp_file = tempfile::tempfile()?;
66 temp_file.write_all(&data)?;
67 temp_file.seek(SeekFrom::Start(0))?;
68 Ok((temp_file, content_type))
69}
70
71fn is_ogg(extension: &str, mime_type: Option<&str>) -> bool {
72 matches!(extension, "ogg" | "opus")
73 || matches!(
74 mime_type,
75 Some("audio/ogg") | Some("audio/opus") | Some("application/ogg")
76 )
77}
78
79enum OggCodec {
80 Opus { channels: u16 },
81 Other,
82}
83
84fn detect_ogg_codec(file: &mut File) -> Result<OggCodec> {
85 let mut reader = PacketReader::new(BufReader::new(&mut *file));
86 let head = reader
87 .read_packet_expected()
88 .map_err(|e| anyhow!("loader: failed reading OGG header: {e}"))?;
89 let codec = if head.data.starts_with(b"OpusHead") {
90 let channels = if head.data.len() > 9 {
91 head.data[9] as u16
92 } else {
93 2
94 };
95 OggCodec::Opus { channels }
96 } else {
97 OggCodec::Other
98 };
99 file.seek(SeekFrom::Start(0))?;
100 Ok(codec)
101}
102
103fn decode_opus_ogg(file: File, channels: u16, target_sample_rate: u32) -> Result<Vec<i16>> {
104 let mut reader = PacketReader::new(BufReader::new(file));
105
106 let head = reader
108 .read_packet_expected()
109 .map_err(|e| anyhow!("loader: failed reading OGG header: {e}"))?;
110 let channels = if head.data.len() > 9 {
111 head.data[9] as u16
112 } else {
113 channels
114 };
115
116 reader
118 .read_packet_expected()
119 .map_err(|e| anyhow!("loader: failed reading OpusTags: {e}"))?;
120
121 let mut decoder = OpusDecoder::new(48000, channels);
123 let mut all_samples: Vec<i16> = Vec::new();
124
125 loop {
126 let packet = match reader.read_packet() {
127 Ok(Some(p)) => p,
128 Ok(None) => break,
129 Err(e) => return Err(anyhow!("loader: failed reading OGG packet: {e}")),
130 };
131 let samples = audio_codec::Decoder::decode(&mut decoder, &packet.data);
132 all_samples.extend_from_slice(&samples);
133 }
134
135 if all_samples.is_empty() {
136 return Err(anyhow!(
137 "loader: no decodable audio samples found in Opus stream"
138 ));
139 }
140
141 info!(
142 "loader: decoded Opus stream at 48000 Hz, {} channel(s)",
143 channels
144 );
145
146 if target_sample_rate != 48000 {
147 let mut resampler = Resampler::new(48000, target_sample_rate as usize);
148 all_samples = resampler.resample(&all_samples);
149 }
150
151 Ok(all_samples)
152}
153
154pub fn decode_wav(file: File, target_sample_rate: u32) -> Result<Vec<i16>> {
155 let reader = BufReader::new(file);
156 let mut wav_reader = WavReader::new(reader)?;
157 let spec = wav_reader.spec();
158 let sample_rate = spec.sample_rate;
159 let is_stereo = spec.channels == 2;
160
161 info!(
162 "WAV file detected with sample rate: {} Hz, channels: {}, bits: {}",
163 sample_rate, spec.channels, spec.bits_per_sample
164 );
165
166 let mut all_samples = Vec::new();
167
168 match spec.sample_format {
170 hound::SampleFormat::Int => match spec.bits_per_sample {
171 16 => {
172 for sample in wav_reader.samples::<i16>() {
173 if let Ok(s) = sample {
174 all_samples.push(s);
175 } else {
176 break;
177 }
178 }
179 }
180 8 => {
181 for sample in wav_reader.samples::<i8>() {
182 if let Ok(s) = sample {
183 all_samples.push((s as i16) * 256); } else {
185 break;
186 }
187 }
188 }
189 24 | 32 => {
190 for sample in wav_reader.samples::<i32>() {
191 if let Ok(s) = sample {
192 all_samples.push((s >> 16) as i16); } else {
194 break;
195 }
196 }
197 }
198 _ => {
199 return Err(anyhow!(
200 "Unsupported bits per sample: {}",
201 spec.bits_per_sample
202 ));
203 }
204 },
205 hound::SampleFormat::Float => {
206 for sample in wav_reader.samples::<f32>() {
207 if let Ok(s) = sample {
208 all_samples.push((s * 32767.0) as i16); } else {
210 break;
211 }
212 }
213 }
214 }
215
216 if is_stereo {
218 let mono_samples = all_samples
219 .chunks(2)
220 .map(|chunk| {
221 if chunk.len() == 2 {
222 ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16
223 } else {
224 chunk[0]
225 }
226 })
227 .collect();
228 all_samples = mono_samples;
229 }
230
231 if sample_rate != target_sample_rate && sample_rate > 0 {
232 let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
233 all_samples = resampler.resample(&all_samples);
234 }
235
236 Ok(all_samples)
237}
238
239pub fn decode_audio(
240 mut file: File,
241 extension: &str,
242 mime_type: Option<&str>,
243 target_sample_rate: u32,
244) -> Result<Vec<i16>> {
245 if matches!(extension, "wav")
246 || matches!(
247 mime_type,
248 Some("audio/wav") | Some("audio/wave") | Some("audio/x-wav")
249 )
250 {
251 return decode_wav(file, target_sample_rate);
252 }
253
254 if is_ogg(extension, mime_type) {
255 match detect_ogg_codec(&mut file)? {
256 OggCodec::Opus { channels } => {
257 return decode_opus_ogg(file, channels, target_sample_rate);
258 }
259 OggCodec::Other => {} }
261 }
262
263 let mss = MediaSourceStream::new(Box::new(file), Default::default());
264 let mut hint = Hint::new();
265 if !extension.is_empty() {
266 hint.with_extension(extension);
267 }
268 if let Some(mime) = mime_type {
269 hint.mime_type(mime);
270 }
271
272 let probed = get_probe().format(
273 &hint,
274 mss,
275 &FormatOptions::default(),
276 &MetadataOptions::default(),
277 )?;
278
279 let mut format = probed.format;
280 let (track_id, codec_params) = {
281 let track = format
282 .default_track()
283 .ok_or_else(|| anyhow!("loader: no default audio track found"))?;
284 (track.id, track.codec_params.clone())
285 };
286
287 let mut decoder = get_codecs().make(&codec_params, &DecoderOptions::default())?;
288 let mut all_samples = Vec::new();
289 let mut sample_rate = codec_params.sample_rate.unwrap_or(0);
290
291 loop {
292 let packet = match format.next_packet() {
293 Ok(packet) => packet,
294 Err(SymphoniaError::IoError(_)) => break,
295 Err(SymphoniaError::ResetRequired) => continue,
296 Err(e) => return Err(anyhow!("loader: failed reading audio packet: {e}")),
297 };
298
299 if packet.track_id() != track_id {
300 continue;
301 }
302
303 match decoder.decode(&packet) {
304 Ok(decoded) => {
305 if sample_rate == 0 {
306 sample_rate = decoded.spec().rate;
307 info!(
308 "loader: detected {:?} with sample rate: {} Hz, channels: {}",
309 codec_params.codec,
310 sample_rate,
311 decoded.spec().channels.count()
312 );
313 }
314 let spec = *decoded.spec();
315 let channels = spec.channels.count();
316
317 let mut sample_buffer = SampleBuffer::<i16>::new(decoded.capacity() as u64, spec);
318 sample_buffer.copy_interleaved_ref(decoded);
319 let interleaved = sample_buffer.samples();
320
321 if channels <= 1 {
322 all_samples.extend_from_slice(interleaved);
323 } else {
324 for frame in interleaved.chunks(channels) {
325 if frame.is_empty() {
326 continue;
327 }
328 let sum: i32 = frame.iter().map(|s| *s as i32).sum();
329 all_samples.push((sum / frame.len() as i32) as i16);
330 }
331 }
332 }
333 Err(SymphoniaError::DecodeError(_)) => continue,
334 Err(SymphoniaError::IoError(_)) => break,
335 Err(SymphoniaError::ResetRequired) => continue,
336 Err(e) => return Err(anyhow!("loader: failed decoding audio packet: {e}")),
337 }
338 }
339
340 if all_samples.is_empty() {
341 return Err(anyhow!("loader: no decodable audio samples found"));
342 }
343
344 if sample_rate != target_sample_rate && sample_rate > 0 {
345 let mut resampler = Resampler::new(sample_rate as usize, target_sample_rate as usize);
346 all_samples = resampler.resample(&all_samples);
347 }
348
349 Ok(all_samples)
350}
351
352pub async fn load_audio_as_pcm(
353 path: &str,
354 target_sample_rate: u32,
355 use_cache: bool,
356) -> Result<Vec<i16>> {
357 let is_url = path.starts_with("http://") || path.starts_with("https://");
358
359 let (file, content_type) = if is_url {
360 download_from_url(path, use_cache).await?
361 } else {
362 (File::open(path).map_err(|e| anyhow!("loader: {} {}", path, e))?, None)
363 };
364
365 let extension = if is_url {
366 path.parse::<Url>()?.path().split('.').last().unwrap_or("").to_string()
367 } else {
368 path.split('.').last().unwrap_or("").to_string()
369 };
370
371 decode_audio(
372 file,
373 &extension,
374 content_type.as_deref(),
375 target_sample_rate,
376 )
377}