1use std::{
2 any::Any,
3 env, fmt, mem, ops,
4 path::{Path, PathBuf},
5 sync::Arc,
6 time::Duration,
7};
8
9use zng_app::view_process::ViewAudioHandle;
10use zng_task::channel::{IpcBytes, IpcBytesCast};
11use zng_txt::Txt;
12use zng_unit::{ByteLength, ByteUnits};
13use zng_var::{Var, VarEq, impl_from_and_into_var};
14use zng_view_api::audio::{AudioDecoded, AudioMetadata};
15
16pub use zng_app::view_process::AudioOutputId;
17pub use zng_view_api::audio::{AudioDataFormat, AudioFormat, AudioFormatCapability, AudioTracksMode};
18
19pub trait AudiosExtension: Send + Sync + Any {
25 fn audio(&mut self, limits: &AudioLimits, source: &mut AudioSource, options: &mut AudioOptions) {
35 let _ = (limits, source, options);
36 }
37
38 #[allow(clippy::too_many_arguments)]
51 fn audio_data(
52 &mut self,
53 max_decoded_len: ByteLength,
54 key: &AudioHash,
55 data: &IpcBytes,
56 format: &AudioDataFormat,
57 options: &AudioOptions,
58 ) -> Option<AudioVar> {
59 let _ = (max_decoded_len, key, data, format, options);
60 None
61 }
62
63 fn remove(&mut self, key: &mut AudioHash, purge: &mut bool) -> bool {
70 let _ = (key, purge);
71 true
72 }
73
74 fn clear(&mut self, purge: bool) {
81 let _ = purge;
82 }
83
84 fn available_formats(&self, formats: &mut Vec<AudioFormat>) {
91 let _ = formats;
92 }
93}
94
95pub type AudioVar = Var<AudioTrack>;
101
102#[derive(Debug, Clone)]
106pub struct AudioTrack {
107 pub(crate) cache_key: Option<AudioHash>,
108
109 pub(crate) handle: ViewAudioHandle,
110 pub(crate) meta: AudioMetadata,
111 pub(crate) data: AudioDecoded,
112 tracks: Vec<VarEq<AudioTrack>>,
113
114 error: Txt,
115}
116impl PartialEq for AudioTrack {
117 fn eq(&self, other: &Self) -> bool {
118 self.handle == other.handle
119 && self.cache_key == other.cache_key
120 && self.error == other.error
121 && self.meta == other.meta
122 && self.data == other.data
123 && self.tracks == other.tracks
124 }
125}
126impl AudioTrack {
127 pub fn new_loading() -> Self {
133 Self::new_error(Txt::from_static(""))
134 }
135
136 pub fn new_error(error: Txt) -> Self {
140 let mut s = Self::new(None, ViewAudioHandle::dummy(), AudioMetadata::default(), AudioDecoded::default());
141 s.error = error;
142 s
143 }
144
145 pub(crate) fn new(cache_key: Option<AudioHash>, handle: ViewAudioHandle, meta: AudioMetadata, data: AudioDecoded) -> Self {
146 Self {
147 cache_key,
148 handle,
149 meta,
150 data,
151 tracks: vec![],
152 error: Txt::from_static(""),
153 }
154 }
155
156 pub fn is_loading(&self) -> bool {
158 self.error.is_empty() && !self.data.is_full
159 }
160
161 pub fn is_loaded(&self) -> bool {
165 !self.is_loading()
166 }
167
168 pub fn can_cue(&self) -> bool {
172 !self.view_handle().is_dummy() && !self.is_error()
173 }
174
175 pub fn is_error(&self) -> bool {
177 !self.error.is_empty()
178 }
179
180 pub fn error(&self) -> Option<Txt> {
182 if self.error.is_empty() { None } else { Some(self.error.clone()) }
183 }
184
185 pub fn total_duration(&self) -> Option<Duration> {
191 self.meta.total_duration
192 }
193
194 pub fn channel_count(&self) -> u16 {
196 self.meta.channel_count
197 }
198
199 pub fn sample_rate(&self) -> u32 {
206 self.meta.sample_rate
207 }
208
209 pub fn chunk(&self) -> IpcBytesCast<f32> {
216 self.data.chunk.clone()
217 }
218
219 pub fn chunk_offset(&self) -> usize {
223 self.data.offset
224 }
225
226 pub fn has_tracks(&self) -> bool {
230 !self.tracks.is_empty()
231 }
232
233 pub fn tracks(&self) -> Vec<AudioVar> {
235 self.tracks.iter().map(|e| e.read_only()).collect()
236 }
237
238 pub fn flat_tracks(&self) -> Var<Vec<(VarEq<AudioTrack>, usize)>> {
246 let update_signal = zng_var::var(());
252
253 let mut out = vec![];
255 let mut update_handles = vec![];
256 self.flat_tracks_init(&mut out, update_signal.clone(), &mut update_handles);
257 let out = zng_var::var(out);
258
259 let self_ = self.clone();
261 let signal_weak = update_signal.downgrade();
262 update_signal
263 .bind_modify(&out, move |_, out| {
264 out.clear();
265 update_handles.clear();
266 self_.flat_tracks_init(&mut *out, signal_weak.upgrade().unwrap(), &mut update_handles);
267 })
268 .perm();
269 out.hold(update_signal).perm();
270 out.read_only()
271 }
272 fn flat_tracks_init(&self, out: &mut Vec<(VarEq<AudioTrack>, usize)>, update_signal: Var<()>, handles: &mut Vec<zng_var::VarHandle>) {
273 for track in self.tracks.iter() {
274 Self::flat_tracks_recursive_init(track.clone(), out, update_signal.clone(), handles);
275 }
276 }
277 fn flat_tracks_recursive_init(
278 aud: VarEq<AudioTrack>,
279 out: &mut Vec<(VarEq<AudioTrack>, usize)>,
280 signal: Var<()>,
281 handles: &mut Vec<zng_var::VarHandle>,
282 ) {
283 handles.push(aud.hook(zng_clone_move::clmv!(signal, |_| {
284 signal.update();
285 true
286 })));
287 let i = out.len();
288 out.push((aud.clone(), 0));
289 aud.with(move |aud| {
290 for track in aud.tracks.iter() {
291 Self::flat_tracks_recursive_init(track.clone(), out, signal.clone(), handles);
292 }
293 let len = out.len() - i;
294 out[i].1 = len;
295 });
296 }
297
298 pub fn track_index(&self) -> usize {
300 match &self.meta.parent {
301 Some(p) => p.index,
302 None => 0,
303 }
304 }
305
306 pub fn view_handle(&self) -> &ViewAudioHandle {
308 &self.handle
309 }
310
311 pub fn insert_track(&mut self, track: AudioVar) {
315 let i = track.with(|i| i.track_index());
316 let i = self
317 .tracks
318 .iter()
319 .position(|v| {
320 let track_i = v.with(|i| i.track_index());
321 track_i > i
322 })
323 .unwrap_or(self.tracks.len());
324 self.tracks.insert(i, VarEq(track));
325 }
326}
327
328#[derive(Clone, Copy)]
336pub struct AudioHash([u8; 32]);
337impl AudioHash {
338 pub fn compute(data: &[u8]) -> Self {
340 let mut h = Self::hasher();
341 h.update(data);
342 h.finish()
343 }
344
345 pub fn hasher() -> AudioHasher {
347 AudioHasher::default()
348 }
349}
350impl fmt::Debug for AudioHash {
351 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352 if f.alternate() {
353 f.debug_tuple("AudioHash").field(&self.0).finish()
354 } else {
355 use base64::*;
356 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
357 }
358 }
359}
360impl fmt::Display for AudioHash {
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 write!(f, "{self:?}")
363 }
364}
365impl std::hash::Hash for AudioHash {
366 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
367 let h64 = [
368 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
369 ];
370 state.write_u64(u64::from_ne_bytes(h64))
371 }
372}
373impl PartialEq for AudioHash {
374 fn eq(&self, other: &Self) -> bool {
375 self.0 == other.0
376 }
377}
378impl Eq for AudioHash {}
379
380pub struct AudioHasher(sha2::Sha512_256);
382impl Default for AudioHasher {
383 fn default() -> Self {
384 use sha2::Digest;
385 Self(sha2::Sha512_256::new())
386 }
387}
388impl AudioHasher {
389 pub fn new() -> Self {
391 Self::default()
392 }
393
394 pub fn update(&mut self, data: &[u8]) {
396 use sha2::Digest;
397
398 const NUM_SAMPLES: usize = 1000;
401 const SAMPLE_CHUNK_SIZE: usize = 1024;
402
403 let total_size = data.len();
404 if total_size == 0 {
405 return;
406 }
407 if total_size < 1000 * 1000 * 4 {
408 return self.0.update(data);
409 }
410
411 let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
412 for n in 0..NUM_SAMPLES {
413 let start_index = n * step_size;
414 if start_index >= total_size {
415 break;
416 }
417 let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
418 let s = &data[start_index..end_index];
419 self.0.update(s);
420 }
421 }
422
423 pub fn finish(self) -> AudioHash {
425 use sha2::Digest;
426 AudioHash(self.0.finalize().as_slice().try_into().unwrap())
430 }
431}
432impl std::hash::Hasher for AudioHasher {
433 fn finish(&self) -> u64 {
434 tracing::warn!("Hasher::finish called for AudioHasher");
435
436 use sha2::Digest;
437 let hash = self.0.clone().finalize();
438 u64::from_le_bytes(hash[..8].try_into().unwrap())
439 }
440
441 fn write(&mut self, bytes: &[u8]) {
442 self.update(bytes);
443 }
444}
445
446#[derive(Clone)]
448#[non_exhaustive]
449pub enum AudioSource {
450 Read(PathBuf),
454 #[cfg(feature = "http")]
460 Download(zng_task::http::Uri, Option<Txt>),
461 Data(AudioHash, IpcBytes, AudioDataFormat),
469
470 Audio(AudioVar),
474}
475impl AudioSource {
476 pub fn from_data(data: IpcBytes, format: AudioDataFormat) -> Self {
478 let mut hasher = AudioHasher::default();
479 hasher.update(&data[..]);
480 let hash = hasher.finish();
481 Self::Data(hash, data, format)
482 }
483
484 pub fn hash128(&self, options: &AudioOptions) -> Option<AudioHash> {
486 match self {
487 AudioSource::Read(p) => Some(Self::hash128_read(p, options)),
488 #[cfg(feature = "http")]
489 AudioSource::Download(u, a) => Some(Self::hash128_download(u, a, options)),
490 AudioSource::Data(h, _, _) => Some(Self::hash128_data(*h, options)),
491 AudioSource::Audio(_) => None,
492 }
493 }
494
495 pub fn hash128_data(data_hash: AudioHash, options: &AudioOptions) -> AudioHash {
499 if !options.tracks.is_empty() {
500 use std::hash::Hash;
501 let mut h = AudioHash::hasher();
502 data_hash.0.hash(&mut h);
503 options.tracks.hash(&mut h);
504 h.finish()
505 } else {
506 data_hash
507 }
508 }
509
510 pub fn hash128_read(path: &Path, options: &AudioOptions) -> AudioHash {
514 use std::hash::Hash;
515 let mut h = AudioHash::hasher();
516 0u8.hash(&mut h);
517 path.hash(&mut h);
518 options.tracks.hash(&mut h);
519 h.finish()
520 }
521
522 #[cfg(feature = "http")]
526 pub fn hash128_download(uri: &zng_task::http::Uri, accept: &Option<Txt>, options: &AudioOptions) -> AudioHash {
527 use std::hash::Hash;
528 let mut h = AudioHash::hasher();
529 1u8.hash(&mut h);
530 uri.hash(&mut h);
531 accept.hash(&mut h);
532 options.tracks.hash(&mut h);
533 h.finish()
534 }
535}
536
537impl PartialEq for AudioSource {
538 fn eq(&self, other: &Self) -> bool {
539 match (self, other) {
540 (Self::Read(l), Self::Read(r)) => l == r,
541 #[cfg(feature = "http")]
542 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
543 (Self::Audio(l), Self::Audio(r)) => l.var_eq(r),
544 (l, r) => {
545 let l_hash = match l {
546 AudioSource::Data(h, _, _) => h,
547 _ => return false,
548 };
549 let r_hash = match r {
550 AudioSource::Data(h, _, _) => h,
551 _ => return false,
552 };
553
554 l_hash == r_hash
555 }
556 }
557 }
558}
559impl fmt::Debug for AudioSource {
560 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561 if f.alternate() {
562 write!(f, "AudioSource::")?;
563 }
564 match self {
565 AudioSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
566 #[cfg(feature = "http")]
567 AudioSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
568 AudioSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
569
570 AudioSource::Audio(_) => write!(f, "Audio(_)"),
571 }
572 }
573}
574
575#[cfg(feature = "http")]
576impl_from_and_into_var! {
577 fn from(uri: zng_task::http::Uri) -> AudioSource {
578 AudioSource::Download(uri, None)
579 }
580 fn from((uri, accept): (zng_task::http::Uri, &'static str)) -> AudioSource {
582 AudioSource::Download(uri, Some(accept.into()))
583 }
584
585 fn from(s: &str) -> AudioSource {
591 use zng_task::http::*;
592 if let Ok(uri) = Uri::try_from(s)
593 && let Some(scheme) = uri.scheme()
594 {
595 if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
596 return AudioSource::Download(uri, None);
597 } else if scheme.as_str() == "file" {
598 return PathBuf::from(uri.path()).into();
599 }
600 }
601 PathBuf::from(s).into()
602 }
603}
604
605#[cfg(not(feature = "http"))]
606impl_from_and_into_var! {
607 fn from(s: &str) -> AudioSource {
611 PathBuf::from(s).into()
612 }
613}
614
615impl_from_and_into_var! {
616 fn from(audio: AudioVar) -> AudioSource {
617 AudioSource::Audio(audio)
618 }
619 fn from(path: PathBuf) -> AudioSource {
620 AudioSource::Read(path)
621 }
622 fn from(path: &Path) -> AudioSource {
623 path.to_owned().into()
624 }
625
626 fn from(s: String) -> AudioSource {
628 s.as_str().into()
629 }
630 fn from(s: Txt) -> AudioSource {
632 s.as_str().into()
633 }
634 fn from(data: &[u8]) -> AudioSource {
638 AudioSource::Data(
639 AudioHash::compute(data),
640 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
641 AudioDataFormat::Unknown,
642 )
643 }
644 fn from<const N: usize>(data: &[u8; N]) -> AudioSource {
648 (&data[..]).into()
649 }
650 fn from(data: IpcBytes) -> AudioSource {
654 AudioSource::Data(AudioHash::compute(&data[..]), data, AudioDataFormat::Unknown)
655 }
656 fn from(data: Vec<u8>) -> AudioSource {
660 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
661 }
662 fn from<F: Into<AudioDataFormat>>((data, format): (&[u8], F)) -> AudioSource {
664 AudioSource::Data(
665 AudioHash::compute(data),
666 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
667 format.into(),
668 )
669 }
670 fn from<F: Into<AudioDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> AudioSource {
672 (&data[..], format).into()
673 }
674 fn from<F: Into<AudioDataFormat>>((data, format): (Vec<u8>, F)) -> AudioSource {
676 (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
677 }
678 fn from<F: Into<AudioDataFormat>>((data, format): (IpcBytes, F)) -> AudioSource {
680 AudioSource::Data(AudioHash::compute(&data[..]), data, format.into())
681 }
682}
683
684#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
688pub enum AudioCacheMode {
689 Ignore,
691 Cache,
693 Retry,
695 Reload,
699}
700impl fmt::Debug for AudioCacheMode {
701 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
702 if f.alternate() {
703 write!(f, "CacheMode::")?;
704 }
705 match self {
706 Self::Ignore => write!(f, "Ignore"),
707 Self::Cache => write!(f, "Cache"),
708 Self::Retry => write!(f, "Retry"),
709 Self::Reload => write!(f, "Reload"),
710 }
711 }
712}
713impl_from_and_into_var! {
714 fn from(cache: bool) -> AudioCacheMode {
715 if cache { AudioCacheMode::Cache } else { AudioCacheMode::Ignore }
716 }
717}
718
719#[derive(Clone)]
721pub enum AudioSourceFilter<U> {
722 BlockAll,
724 AllowAll,
726 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
728}
729impl<U> AudioSourceFilter<U> {
730 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
734 Self::Custom(Arc::new(allow))
735 }
736
737 pub fn and(self, other: Self) -> Self
746 where
747 U: 'static,
748 {
749 use AudioSourceFilter::*;
750 match (self, other) {
751 (BlockAll, _) | (_, BlockAll) => BlockAll,
752 (AllowAll, _) | (_, AllowAll) => AllowAll,
753 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
754 }
755 }
756
757 pub fn or(self, other: Self) -> Self
766 where
767 U: 'static,
768 {
769 use AudioSourceFilter::*;
770 match (self, other) {
771 (AllowAll, _) | (_, AllowAll) => AllowAll,
772 (BlockAll, _) | (_, BlockAll) => BlockAll,
773 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
774 }
775 }
776
777 pub fn allows(&self, item: &U) -> bool {
779 match self {
780 AudioSourceFilter::BlockAll => false,
781 AudioSourceFilter::AllowAll => true,
782 AudioSourceFilter::Custom(f) => f(item),
783 }
784 }
785}
786impl<U> fmt::Debug for AudioSourceFilter<U> {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 match self {
789 Self::BlockAll => write!(f, "BlockAll"),
790 Self::AllowAll => write!(f, "AllowAll"),
791 Self::Custom(_) => write!(f, "Custom(_)"),
792 }
793 }
794}
795impl<U: 'static> ops::BitAnd for AudioSourceFilter<U> {
796 type Output = Self;
797
798 fn bitand(self, rhs: Self) -> Self::Output {
799 self.and(rhs)
800 }
801}
802impl<U: 'static> ops::BitOr for AudioSourceFilter<U> {
803 type Output = Self;
804
805 fn bitor(self, rhs: Self) -> Self::Output {
806 self.or(rhs)
807 }
808}
809impl<U: 'static> ops::BitAndAssign for AudioSourceFilter<U> {
810 fn bitand_assign(&mut self, rhs: Self) {
811 *self = mem::replace(self, Self::BlockAll).and(rhs);
812 }
813}
814impl<U: 'static> ops::BitOrAssign for AudioSourceFilter<U> {
815 fn bitor_assign(&mut self, rhs: Self) {
816 *self = mem::replace(self, Self::BlockAll).or(rhs);
817 }
818}
819impl<U> PartialEq for AudioSourceFilter<U> {
820 fn eq(&self, other: &Self) -> bool {
821 match (self, other) {
822 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
823 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
824 }
825 }
826}
827
828pub type PathFilter = AudioSourceFilter<PathBuf>;
838impl PathFilter {
839 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
841 let dir = absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
842 PathFilter::custom(move |r| r.starts_with(&dir))
843 }
844
845 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
847 let ext = ext.into();
848 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
849 }
850
851 pub fn allow_current_dir() -> Self {
859 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
860 }
861
862 pub fn allow_exe_dir() -> Self {
864 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
865 && p.pop()
866 {
867 return Self::allow_dir(p);
868 }
869
870 Self::custom(|_| false)
872 }
873
874 pub fn allow_res() -> Self {
878 Self::allow_dir(zng_env::res(""))
879 }
880}
881
882#[cfg(feature = "http")]
886pub type UriFilter = AudioSourceFilter<zng_task::http::Uri>;
887#[cfg(feature = "http")]
888impl UriFilter {
889 pub fn allow_host(host: impl Into<Txt>) -> Self {
891 let host = host.into();
892 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
893 }
894}
895
896impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
897 fn from(custom: F) -> Self {
898 PathFilter::custom(custom)
899 }
900}
901
902#[cfg(feature = "http")]
903impl<F: Fn(&zng_task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
904 fn from(custom: F) -> Self {
905 UriFilter::custom(custom)
906 }
907}
908
909#[derive(Clone, Debug, PartialEq)]
911#[non_exhaustive]
912pub struct AudioLimits {
913 pub max_encoded_len: ByteLength,
921 pub max_decoded_len: ByteLength,
925
926 pub allow_path: PathFilter,
928
929 #[cfg(feature = "http")]
931 pub allow_uri: UriFilter,
932}
933impl AudioLimits {
934 pub fn none() -> Self {
936 AudioLimits {
937 max_encoded_len: ByteLength::MAX,
938 max_decoded_len: ByteLength::MAX,
939 allow_path: PathFilter::AllowAll,
940 #[cfg(feature = "http")]
941 allow_uri: UriFilter::AllowAll,
942 }
943 }
944
945 pub fn with_max_encoded_len(mut self, max_encoded_len: impl Into<ByteLength>) -> Self {
949 self.max_encoded_len = max_encoded_len.into();
950 self
951 }
952
953 pub fn with_max_decoded_len(mut self, max_decoded_len: impl Into<ByteLength>) -> Self {
957 self.max_decoded_len = max_decoded_len.into();
958 self
959 }
960
961 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
965 self.allow_path = allow_path.into();
966 self
967 }
968
969 #[cfg(feature = "http")]
973 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
974 self.allow_uri = allow_url.into();
975 self
976 }
977}
978impl Default for AudioLimits {
979 fn default() -> Self {
983 Self {
984 max_encoded_len: 100.megabytes(),
985 max_decoded_len: 4096.megabytes(),
986 allow_path: PathFilter::allow_res(),
987 #[cfg(feature = "http")]
988 allow_uri: UriFilter::BlockAll,
989 }
990 }
991}
992impl_from_and_into_var! {
993 fn from(some: AudioLimits) -> Option<AudioLimits>;
994}
995
996#[derive(Debug, Clone, PartialEq)]
1000#[non_exhaustive]
1001pub struct AudioOptions {
1002 pub cache_mode: AudioCacheMode,
1004 pub tracks: AudioTracksMode,
1006}
1007
1008impl AudioOptions {
1009 pub fn new(cache_mode: AudioCacheMode, tracks: AudioTracksMode) -> Self {
1011 Self { cache_mode, tracks }
1012 }
1013
1014 pub fn cache() -> Self {
1016 Self::new(AudioCacheMode::Cache, AudioTracksMode::empty())
1017 }
1018
1019 pub fn none() -> Self {
1021 Self::new(AudioCacheMode::Ignore, AudioTracksMode::empty())
1022 }
1023}
1024
1025fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1026 if path.is_absolute() {
1027 normalize_path(path)
1028 } else {
1029 let mut dir = base();
1030 if allow_escape {
1031 dir.push(path);
1032 normalize_path(&dir)
1033 } else {
1034 dir.push(normalize_path(path));
1035 dir
1036 }
1037 }
1038}
1039fn normalize_path(path: &Path) -> PathBuf {
1043 use std::path::Component;
1044
1045 let mut components = path.components().peekable();
1046 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1047 components.next();
1048 PathBuf::from(c.as_os_str())
1049 } else {
1050 PathBuf::new()
1051 };
1052
1053 for component in components {
1054 match component {
1055 Component::Prefix(..) => unreachable!(),
1056 Component::RootDir => {
1057 ret.push(component.as_os_str());
1058 }
1059 Component::CurDir => {}
1060 Component::ParentDir => {
1061 ret.pop();
1062 }
1063 Component::Normal(c) => {
1064 ret.push(c);
1065 }
1066 }
1067 }
1068 ret
1069}