Skip to main content

max / audiofiles

Upgrade symphonia to 0.6 0.6 reworks the API rather than just bumping it: - probe().format() becomes probe().probe(), returning the FormatReader directly instead of a ProbeResult wrapper, and taking options by value. - Hint moved to formats::probe; default_track now takes a TrackType. - Codec parameters split by media type: the audio ones come from codec_params.audio(), and decoders are built with make_audio_decoder. - Timebase, frame count, and duration moved from codec_params onto Track, and calc_time now takes a Timestamp and returns Option<Time>. - SampleBuffer is gone; decoded packets copy out through copy_to_slice_interleaved into a reused Vec<f32>. - next_packet() signals end of stream with Ok(None). The AIFF round-trip tests looped on 'while let Ok(packet)', which under 0.6 would spin forever, so both now share one decode helper that matches Ok(Some). Adds a stereo-WAV decode test: the existing coverage only asserted that malformed input errors cleanly, so nothing pinned the decode contract this upgrade moves.
Author: Max Johnson <me@maxj.phd> · 2026-07-22 03:53 UTC
Commit: 2f92bed6c880b6d862a24e0173b59265a34df8b9
Parent: 5cf592e
8 files changed, +465 insertions, -242 deletions
M Cargo.lock +216 -37
@@ -406,7 +406,7 @@ dependencies = [
406 406 "rusqlite",
407 407 "serde",
408 408 "serde_json",
409 - "symphonia",
409 + "symphonia 0.6.0",
410 410 "tempfile",
411 411 "thiserror 2.0.19",
412 412 "toml",
@@ -431,7 +431,7 @@ dependencies = [
431 431 "serde_json",
432 432 "sha2 0.11.0",
433 433 "stratum-dsp",
434 - "symphonia",
434 + "symphonia 0.6.0",
435 435 "tagtree",
436 436 "tempfile",
437 437 "thiserror 2.0.19",
@@ -4202,6 +4202,12 @@ dependencies = [
4202 4202 ]
4203 4203
4204 4204 [[package]]
4205 + name = "regex-lite"
4206 + version = "0.1.9"
4207 + source = "registry+https://github.com/rust-lang/crates.io-index"
4208 + checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
4209 +
4210 + [[package]]
4205 4211 name = "regex-syntax"
4206 4212 version = "0.8.11"
4207 4213 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4862,7 +4868,7 @@ dependencies = [
4862 4868 "rustfft",
4863 4869 "serde",
4864 4870 "serde_json",
4865 - "symphonia",
4871 + "symphonia 0.5.5",
4866 4872 ]
4867 4873
4868 4874 [[package]]
@@ -4884,20 +4890,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4884 4890 checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039"
4885 4891 dependencies = [
4886 4892 "lazy_static",
4887 - "symphonia-bundle-flac",
4888 - "symphonia-bundle-mp3",
4889 - "symphonia-codec-aac",
4893 + "symphonia-bundle-flac 0.5.5",
4894 + "symphonia-bundle-mp3 0.5.5",
4895 + "symphonia-codec-aac 0.5.5",
4890 4896 "symphonia-codec-adpcm",
4891 - "symphonia-codec-alac",
4892 - "symphonia-codec-pcm",
4893 - "symphonia-codec-vorbis",
4894 - "symphonia-core",
4895 - "symphonia-format-caf",
4896 - "symphonia-format-isomp4",
4897 + "symphonia-codec-alac 0.5.5",
4898 + "symphonia-codec-pcm 0.5.5",
4899 + "symphonia-codec-vorbis 0.5.5",
4900 + "symphonia-core 0.5.5",
4901 + "symphonia-format-caf 0.5.5",
4902 + "symphonia-format-isomp4 0.5.5",
4897 4903 "symphonia-format-mkv",
4898 - "symphonia-format-ogg",
4899 - "symphonia-format-riff",
4900 - "symphonia-metadata",
4904 + "symphonia-format-ogg 0.5.5",
4905 + "symphonia-format-riff 0.5.5",
4906 + "symphonia-metadata 0.5.5",
4907 + ]
4908 +
4909 + [[package]]
4910 + name = "symphonia"
4911 + version = "0.6.0"
4912 + source = "registry+https://github.com/rust-lang/crates.io-index"
4913 + checksum = "1758d6c853020a7244de03cc3e0185eaea3f58715122422dd3cc7452e6d4c16a"
4914 + dependencies = [
4915 + "lazy_static",
4916 + "symphonia-bundle-flac 0.6.0",
4917 + "symphonia-bundle-mp3 0.6.0",
4918 + "symphonia-codec-aac 0.6.0",
4919 + "symphonia-codec-alac 0.6.0",
4920 + "symphonia-codec-pcm 0.6.0",
4921 + "symphonia-codec-vorbis 0.6.0",
4922 + "symphonia-core 0.6.0",
4923 + "symphonia-format-caf 0.6.0",
4924 + "symphonia-format-isomp4 0.6.0",
4925 + "symphonia-format-ogg 0.6.0",
4926 + "symphonia-format-riff 0.6.0",
4927 + "symphonia-metadata 0.6.0",
4901 4928 ]
4902 4929
4903 4930 [[package]]
@@ -4907,12 +4934,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4907 4934 checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976"
4908 4935 dependencies = [
4909 4936 "log",
4910 - "symphonia-core",
4911 - "symphonia-metadata",
4937 + "symphonia-core 0.5.5",
4938 + "symphonia-metadata 0.5.5",
4912 4939 "symphonia-utils-xiph",
4913 4940 ]
4914 4941
4915 4942 [[package]]
4943 + name = "symphonia-bundle-flac"
4944 + version = "0.6.0"
4945 + source = "registry+https://github.com/rust-lang/crates.io-index"
4946 + checksum = "ee69ad01236a67260b82fd1ff9790dd75ead29f2f46af145e63b7e72273e0e03"
4947 + dependencies = [
4948 + "log",
4949 + "symphonia-common",
4950 + "symphonia-core 0.6.0",
4951 + "symphonia-metadata 0.6.0",
4952 + ]
4953 +
4954 + [[package]]
4916 4955 name = "symphonia-bundle-mp3"
4917 4956 version = "0.5.5"
4918 4957 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4920,8 +4959,19 @@ checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed"
4920 4959 dependencies = [
4921 4960 "lazy_static",
4922 4961 "log",
4923 - "symphonia-core",
4924 - "symphonia-metadata",
4962 + "symphonia-core 0.5.5",
4963 + "symphonia-metadata 0.5.5",
4964 + ]
4965 +
4966 + [[package]]
4967 + name = "symphonia-bundle-mp3"
4968 + version = "0.6.0"
4969 + source = "registry+https://github.com/rust-lang/crates.io-index"
4970 + checksum = "350f1f2f2e19ad4dd315db94304d1eb361b29af070681f94e51b8fdaad769546"
4971 + dependencies = [
4972 + "lazy_static",
4973 + "log",
4974 + "symphonia-core 0.6.0",
4925 4975 ]
4926 4976
4927 4977 [[package]]
@@ -4932,7 +4982,19 @@ checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790"
4932 4982 dependencies = [
4933 4983 "lazy_static",
4934 4984 "log",
4935 - "symphonia-core",
4985 + "symphonia-core 0.5.5",
4986 + ]
4987 +
4988 + [[package]]
4989 + name = "symphonia-codec-aac"
4990 + version = "0.6.0"
4991 + source = "registry+https://github.com/rust-lang/crates.io-index"
4992 + checksum = "f1979c515a76371b186aad2feff5f23e21cbec775bf95de08bf1e3af92a2ad76"
4993 + dependencies = [
4994 + "lazy_static",
4995 + "log",
4996 + "symphonia-common",
4997 + "symphonia-core 0.6.0",
4936 4998 ]
4937 4999
4938 5000 [[package]]
@@ -4942,7 +5004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4942 5004 checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f"
4943 5005 dependencies = [
4944 5006 "log",
4945 - "symphonia-core",
5007 + "symphonia-core 0.5.5",
4946 5008 ]
4947 5009
4948 5010 [[package]]
@@ -4952,7 +5014,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4952 5014 checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab"
4953 5015 dependencies = [
4954 5016 "log",
4955 - "symphonia-core",
5017 + "symphonia-core 0.5.5",
5018 + ]
5019 +
5020 + [[package]]
5021 + name = "symphonia-codec-alac"
5022 + version = "0.6.0"
5023 + source = "registry+https://github.com/rust-lang/crates.io-index"
5024 + checksum = "3a149cbfc7fb5c405d123a273227d31de17138419552112bf1aa7b73e65827b8"
5025 + dependencies = [
5026 + "log",
5027 + "symphonia-common",
5028 + "symphonia-core 0.6.0",
4956 5029 ]
4957 5030
4958 5031 [[package]]
@@ -4962,7 +5035,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4962 5035 checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95"
4963 5036 dependencies = [
4964 5037 "log",
4965 - "symphonia-core",
5038 + "symphonia-core 0.5.5",
5039 + ]
5040 +
5041 + [[package]]
5042 + name = "symphonia-codec-pcm"
5043 + version = "0.6.0"
5044 + source = "registry+https://github.com/rust-lang/crates.io-index"
5045 + checksum = "50baee168f0e9dcf6ba7fc06e8b57eb62072a4490cc7cf13af77e72baae5d328"
5046 + dependencies = [
5047 + "log",
5048 + "symphonia-core 0.6.0",
4966 5049 ]
4967 5050
4968 5051 [[package]]
@@ -4972,11 +5055,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4972 5055 checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73"
4973 5056 dependencies = [
4974 5057 "log",
4975 - "symphonia-core",
5058 + "symphonia-core 0.5.5",
4976 5059 "symphonia-utils-xiph",
4977 5060 ]
4978 5061
4979 5062 [[package]]
5063 + name = "symphonia-codec-vorbis"
5064 + version = "0.6.0"
5065 + source = "registry+https://github.com/rust-lang/crates.io-index"
5066 + checksum = "45b07b4423cd8e0fc472575909a5554b12c2f58e3c190b38c24f042e732fd8de"
5067 + dependencies = [
5068 + "log",
5069 + "symphonia-common",
5070 + "symphonia-core 0.6.0",
5071 + ]
5072 +
5073 + [[package]]
5074 + name = "symphonia-common"
5075 + version = "0.6.0"
5076 + source = "registry+https://github.com/rust-lang/crates.io-index"
5077 + checksum = "8257891ffa7f05e02b58f4761e2abf7e5278c8744fd59e981559e050f86eef55"
5078 + dependencies = [
5079 + "log",
5080 + "symphonia-core 0.6.0",
5081 + "symphonia-metadata 0.6.0",
5082 + ]
5083 +
5084 + [[package]]
4980 5085 name = "symphonia-core"
4981 5086 version = "0.5.5"
4982 5087 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4990,14 +5095,39 @@ dependencies = [
4990 5095 ]
4991 5096
4992 5097 [[package]]
5098 + name = "symphonia-core"
5099 + version = "0.6.0"
5100 + source = "registry+https://github.com/rust-lang/crates.io-index"
5101 + checksum = "95ec293b5f288383b72a7bffcade6b2860b642cf66f28b3bd5967349a49938b1"
5102 + dependencies = [
5103 + "bitflags 2.13.1",
5104 + "bytemuck",
5105 + "lazy_static",
5106 + "log",
5107 + "num-complex",
5108 + "smallvec",
5109 + ]
5110 +
5111 + [[package]]
4993 5112 name = "symphonia-format-caf"
4994 5113 version = "0.5.5"
4995 5114 source = "registry+https://github.com/rust-lang/crates.io-index"
4996 5115 checksum = "b8faf379316b6b6e6bbc274d00e7a592e0d63ff1a7e182ce8ba25e24edd3d096"
4997 5116 dependencies = [
4998 5117 "log",
4999 - "symphonia-core",
5000 - "symphonia-metadata",
5118 + "symphonia-core 0.5.5",
5119 + "symphonia-metadata 0.5.5",
5120 + ]
5121 +
5122 + [[package]]
5123 + name = "symphonia-format-caf"
5124 + version = "0.6.0"
5125 + source = "registry+https://github.com/rust-lang/crates.io-index"
5126 + checksum = "cde3ca76633d3400ab57195456c09f8a58d775ff5452329f3f212b6efc8622f5"
5127 + dependencies = [
5128 + "log",
5129 + "symphonia-common",
5130 + "symphonia-core 0.6.0",
5001 5131 ]
5002 5132
5003 5133 [[package]]
@@ -5008,12 +5138,24 @@ checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5"
5008 5138 dependencies = [
5009 5139 "encoding_rs",
5010 5140 "log",
5011 - "symphonia-core",
5012 - "symphonia-metadata",
5141 + "symphonia-core 0.5.5",
5142 + "symphonia-metadata 0.5.5",
5013 5143 "symphonia-utils-xiph",
5014 5144 ]
5015 5145
5016 5146 [[package]]
5147 + name = "symphonia-format-isomp4"
5148 + version = "0.6.0"
5149 + source = "registry+https://github.com/rust-lang/crates.io-index"
5150 + checksum = "2d179a01305b3505940135a9f0180d6ef4b487912748fe97554756f120fbd05e"
5151 + dependencies = [
5152 + "log",
5153 + "symphonia-common",
5154 + "symphonia-core 0.6.0",
5155 + "symphonia-metadata 0.6.0",
5156 + ]
5157 +
5158 + [[package]]
5017 5159 name = "symphonia-format-mkv"
5018 5160 version = "0.5.5"
5019 5161 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5021,8 +5163,8 @@ checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0"
5021 5163 dependencies = [
5022 5164 "lazy_static",
5023 5165 "log",
5024 - "symphonia-core",
5025 - "symphonia-metadata",
5166 + "symphonia-core 0.5.5",
5167 + "symphonia-metadata 0.5.5",
5026 5168 "symphonia-utils-xiph",
5027 5169 ]
5028 5170
@@ -5033,12 +5175,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
5033 5175 checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb"
5034 5176 dependencies = [
5035 5177 "log",
5036 - "symphonia-core",
5037 - "symphonia-metadata",
5178 + "symphonia-core 0.5.5",
5179 + "symphonia-metadata 0.5.5",
5038 5180 "symphonia-utils-xiph",
5039 5181 ]
5040 5182
5041 5183 [[package]]
5184 + name = "symphonia-format-ogg"
5185 + version = "0.6.0"
5186 + source = "registry+https://github.com/rust-lang/crates.io-index"
5187 + checksum = "b05a67e02b1e4fca1a261ba4fe06910a9357489ad8c36aafdd2960e9c6559433"
5188 + dependencies = [
5189 + "log",
5190 + "symphonia-common",
5191 + "symphonia-core 0.6.0",
5192 + "symphonia-metadata 0.6.0",
5193 + ]
5194 +
5195 + [[package]]
5042 5196 name = "symphonia-format-riff"
5043 5197 version = "0.5.5"
5044 5198 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5046,8 +5200,20 @@ checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f"
5046 5200 dependencies = [
5047 5201 "extended",
5048 5202 "log",
5049 - "symphonia-core",
5050 - "symphonia-metadata",
5203 + "symphonia-core 0.5.5",
5204 + "symphonia-metadata 0.5.5",
5205 + ]
5206 +
5207 + [[package]]
5208 + name = "symphonia-format-riff"
5209 + version = "0.6.0"
5210 + source = "registry+https://github.com/rust-lang/crates.io-index"
5211 + checksum = "17424452a777666d3eaf09a5c651029b15b6a333812fcc5b5474f2a3f0cff3f0"
5212 + dependencies = [
5213 + "extended",
5214 + "log",
5215 + "symphonia-core 0.6.0",
5216 + "symphonia-metadata 0.6.0",
5051 5217 ]
5052 5218
5053 5219 [[package]]
@@ -5059,7 +5225,20 @@ dependencies = [
5059 5225 "encoding_rs",
5060 5226 "lazy_static",
5061 5227 "log",
5062 - "symphonia-core",
5228 + "symphonia-core 0.5.5",
5229 + ]
5230 +
5231 + [[package]]
5232 + name = "symphonia-metadata"
5233 + version = "0.6.0"
5234 + source = "registry+https://github.com/rust-lang/crates.io-index"
5235 + checksum = "a31acf5cd623398a6208e2225d18f4b20f761c55098a796a5247ad516a4a8681"
5236 + dependencies = [
5237 + "lazy_static",
5238 + "log",
5239 + "regex-lite",
5240 + "smallvec",
5241 + "symphonia-core 0.6.0",
5063 5242 ]
5064 5243
5065 5244 [[package]]
@@ -5068,8 +5247,8 @@ version = "0.5.5"
5068 5247 source = "registry+https://github.com/rust-lang/crates.io-index"
5069 5248 checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16"
5070 5249 dependencies = [
5071 - "symphonia-core",
5072 - "symphonia-metadata",
5250 + "symphonia-core 0.5.5",
5251 + "symphonia-metadata 0.5.5",
5073 5252 ]
5074 5253
5075 5254 [[package]]
M Cargo.toml +1 -1
@@ -20,7 +20,7 @@ rusqlite = { version = "0.39", features = ["bundled", "functions"] }
20 20 thiserror = "2.0.18"
21 21 sha2 = "0.11.0"
22 22 hex = "0.4.3"
23 - symphonia = { version = "0.5.5", default-features = false, features = ["wav", "aiff", "mp3", "flac", "ogg", "vorbis", "pcm", "aac", "alac", "isomp4", "caf"] }
23 + symphonia = { version = "0.6.0", default-features = false, features = ["wav", "aiff", "mp3", "flac", "ogg", "vorbis", "pcm", "aac", "alac", "isomp4", "caf"] }
24 24 parking_lot = "0.12.5"
25 25 dirs = "6.0.0"
26 26 stratum-dsp = "=1.0.0"
@@ -96,18 +96,23 @@ pub(super) fn probe_bit_depth(path: &std::path::Path) -> Option<u16> {
96 96 // Try symphonia for AIFF/other formats
97 97 let file = std::fs::File::open(path).ok()?;
98 98 let mss = symphonia::core::io::MediaSourceStream::new(Box::new(file), Default::default());
99 - let mut hint = symphonia::core::probe::Hint::new();
99 + let mut hint = symphonia::core::formats::probe::Hint::new();
100 100 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
101 101 hint.with_extension(ext);
102 102 }
103 - let probed = symphonia::default::get_probe()
104 - .format(
103 + let format = symphonia::default::get_probe()
104 + .probe(
105 105 &hint,
106 106 mss,
107 - &symphonia::core::formats::FormatOptions::default(),
108 - &symphonia::core::meta::MetadataOptions::default(),
107 + symphonia::core::formats::FormatOptions::default(),
108 + symphonia::core::meta::MetadataOptions::default(),
109 109 )
110 110 .ok()?;
111 - let track = probed.format.default_track()?;
112 - track.codec_params.bits_per_sample.map(|b| b as u16)
111 + let track = format.default_track(symphonia::core::formats::TrackType::Audio)?;
112 + track
113 + .codec_params
114 + .as_ref()?
115 + .audio()?
116 + .bits_per_sample
117 + .map(|b| b as u16)
113 118 }
@@ -8,12 +8,11 @@ use std::sync::atomic::Ordering;
8 8
9 9 use tracing::{instrument, warn};
10 10
11 - use symphonia::core::audio::SampleBuffer;
12 - use symphonia::core::codecs::DecoderOptions;
13 - use symphonia::core::formats::FormatOptions;
11 + use symphonia::core::codecs::audio::AudioDecoderOptions;
12 + use symphonia::core::formats::probe::Hint;
13 + use symphonia::core::formats::{FormatOptions, TrackType};
14 14 use symphonia::core::io::MediaSourceStream;
15 15 use symphonia::core::meta::MetadataOptions;
16 - use symphonia::core::probe::Hint;
17 16
18 17 use crate::error::PreviewError;
19 18
@@ -87,11 +86,11 @@ fn decode_to_f32(path: &Path) -> Result<PreviewBuffer, PreviewError> {
87 86 hint.with_extension(ext);
88 87 }
89 88
90 - let probed = match symphonia::default::get_probe().format(
89 + let mut format = match symphonia::default::get_probe().probe(
91 90 &hint,
92 91 mss,
93 - &FormatOptions::default(),
94 - &MetadataOptions::default(),
92 + FormatOptions::default(),
93 + MetadataOptions::default(),
95 94 ) {
96 95 Ok(p) => p,
97 96 Err(e) => {
@@ -110,15 +109,21 @@ fn decode_to_f32(path: &Path) -> Result<PreviewBuffer, PreviewError> {
110 109 }
111 110 };
112 111
113 - let mut format = probed.format;
114 -
115 - let track = format.default_track().ok_or(PreviewError::NoTrack)?;
112 + let track = format
113 + .default_track(TrackType::Audio)
114 + .ok_or(PreviewError::NoTrack)?;
116 115
117 116 let track_id = track.id;
118 - let source_sample_rate = track.codec_params.sample_rate.unwrap_or(44100);
117 + let audio_params = track
118 + .codec_params
119 + .as_ref()
120 + .and_then(|p| p.audio())
121 + .ok_or(PreviewError::NoTrack)?
122 + .clone();
123 + let source_sample_rate = audio_params.sample_rate.unwrap_or(44100);
119 124
120 125 let mut decoder = symphonia::default::get_codecs()
121 - .make(&track.codec_params, &DecoderOptions::default())
126 + .make_audio_decoder(&audio_params, &AudioDecoderOptions::default())
122 127 .map_err(|e| PreviewError::Decoder(e.to_string()))?;
123 128
124 129 let mut all_samples: Vec<f32> = Vec::new();
@@ -127,12 +132,13 @@ fn decode_to_f32(path: &Path) -> Result<PreviewBuffer, PreviewError> {
127 132 // that bypass the streaming threshold.
128 133 const MAX_SAMPLES: usize = 10 * 60 * 48_000 * 2;
129 134
130 - // Reuse SampleBuffer across packets to avoid per-packet allocation.
131 - let mut sample_buf: Option<SampleBuffer<f32>> = None;
135 + // Reused across packets to avoid per-packet allocation.
136 + let mut sample_buf: Vec<f32> = Vec::new();
132 137
133 138 loop {
134 139 let packet = match format.next_packet() {
135 - Ok(p) => p,
140 + Ok(Some(p)) => p,
141 + Ok(None) => break,
136 142 Err(symphonia::core::errors::Error::IoError(ref e))
137 143 if e.kind() == std::io::ErrorKind::UnexpectedEof =>
138 144 {
@@ -141,7 +147,7 @@ fn decode_to_f32(path: &Path) -> Result<PreviewBuffer, PreviewError> {
141 147 Err(e) => return Err(PreviewError::Packet(e.to_string())),
142 148 };
143 149
144 - if packet.track_id() != track_id {
150 + if packet.track_id != track_id {
145 151 continue;
146 152 }
147 153
@@ -151,17 +157,13 @@ fn decode_to_f32(path: &Path) -> Result<PreviewBuffer, PreviewError> {
151 157 Err(e) => return Err(PreviewError::Decode(e.to_string())),
152 158 };
153 159
154 - let spec = *decoded.spec();
155 160 let num_frames = decoded.frames();
156 - let num_channels = spec.channels.count();
161 + let num_channels = decoded.spec().channels().count();
157 162
158 - // Reallocate only when the buffer can't fit this packet.
159 - let buf = match &mut sample_buf {
160 - Some(buf) if buf.capacity() >= num_frames => buf,
161 - _ => sample_buf.insert(SampleBuffer::<f32>::new(num_frames as u64, spec)),
162 - };
163 - buf.copy_interleaved_ref(decoded);
164 - let samples = buf.samples();
163 + // Grows only when a packet needs more room than the last one did.
164 + sample_buf.resize(decoded.samples_interleaved(), 0.0);
165 + decoded.copy_to_slice_interleaved(&mut sample_buf[..]);
166 + let samples = &sample_buf[..];
165 167
166 168 // Convert to interleaved stereo
167 169 interleaved_to_stereo(samples, num_channels, num_frames, &mut all_samples);
@@ -195,16 +197,16 @@ pub fn estimate_duration(path: &Path) -> Option<f64> {
195 197 hint.with_extension(ext);
196 198 }
197 199
198 - if let Ok(probed) = symphonia::default::get_probe().format(
200 + if let Ok(format) = symphonia::default::get_probe().probe(
199 201 &hint,
200 202 mss,
201 - &FormatOptions::default(),
202 - &MetadataOptions::default(),
203 + FormatOptions::default(),
204 + MetadataOptions::default(),
203 205 ) {
204 - let track = probed.format.default_track()?;
205 - let params = &track.codec_params;
206 - let n_frames = params.n_frames?;
207 - let sample_rate = params.sample_rate?;
206 + let track = format.default_track(TrackType::Audio)?;
207 + // symphonia 0.6 keeps the frame count on the track, the rate on the codec.
208 + let n_frames = track.num_frames?;
209 + let sample_rate = track.codec_params.as_ref()?.audio()?.sample_rate?;
208 210 if sample_rate == 0 {
209 211 return None;
210 212 }
@@ -251,11 +253,11 @@ pub fn start_streaming_decode(
251 253 hint.with_extension(ext);
252 254 }
253 255
254 - let probed = match symphonia::default::get_probe().format(
256 + let mut format = match symphonia::default::get_probe().probe(
255 257 &hint,
256 258 mss,
257 - &FormatOptions::default(),
258 - &MetadataOptions::default(),
259 + FormatOptions::default(),
260 + MetadataOptions::default(),
259 261 ) {
260 262 Ok(p) => p,
261 263 Err(e) => {
@@ -324,16 +326,22 @@ pub fn start_streaming_decode(
324 326 }
325 327 };
326 328
327 - let mut format = probed.format;
328 - let track = format.default_track().ok_or(PreviewError::NoTrack)?;
329 + let track = format
330 + .default_track(TrackType::Audio)
331 + .ok_or(PreviewError::NoTrack)?;
329 332 let track_id = track.id;
330 - let codec_params = track.codec_params.clone();
331 - let source_sample_rate = codec_params.sample_rate.unwrap_or(44100);
332 - let n_frames_estimate = codec_params.n_frames.map(|n| n as usize);
333 + let n_frames_estimate = track.num_frames.map(|n| n as usize);
334 + let audio_params = track
335 + .codec_params
336 + .as_ref()
337 + .and_then(|p| p.audio())
338 + .ok_or(PreviewError::NoTrack)?
339 + .clone();
340 + let source_sample_rate = audio_params.sample_rate.unwrap_or(44100);
333 341 let prefill_frames = (PREFILL_SECS * source_sample_rate as f64) as usize;
334 342
335 343 let mut decoder = symphonia::default::get_codecs()
336 - .make(&codec_params, &DecoderOptions::default())
344 + .make_audio_decoder(&audio_params, &AudioDecoderOptions::default())
337 345 .map_err(|e| PreviewError::Decoder(e.to_string()))?;
338 346
339 347 // Increment generation to cancel any previous streaming decode thread
@@ -372,8 +380,8 @@ pub fn start_streaming_decode(
372 380 };
373 381 let mut total_frames = 0usize;
374 382 let mut started = false;
375 - // Reuse SampleBuffer across packets to avoid per-packet allocation.
376 - let mut sample_buf: Option<SampleBuffer<f32>> = None;
383 + // Reused across packets to avoid per-packet allocation.
384 + let mut sample_buf: Vec<f32> = Vec::new();
377 385
378 386 loop {
379 387 // Check if a newer decode has started — if so, this thread exits.
@@ -382,7 +390,8 @@ pub fn start_streaming_decode(
382 390 }
383 391
384 392 let packet = match format.next_packet() {
385 - Ok(p) => p,
393 + Ok(Some(p)) => p,
394 + Ok(None) => break,
386 395 Err(symphonia::core::errors::Error::IoError(ref e))
387 396 if e.kind() == std::io::ErrorKind::UnexpectedEof =>
388 397 {
@@ -391,7 +400,7 @@ pub fn start_streaming_decode(
391 400 Err(_) => break,
392 401 };
393 402
394 - if packet.track_id() != track_id {
403 + if packet.track_id != track_id {
395 404 continue;
396 405 }
397 406
@@ -401,16 +410,12 @@ pub fn start_streaming_decode(
401 410 Err(_) => break,
402 411 };
403 412
404 - let spec = *decoded.spec();
405 413 let num_frames = decoded.frames();
406 - let num_channels = spec.channels.count();
414 + let num_channels = decoded.spec().channels().count();
407 415
408 - let buf = match &mut sample_buf {
409 - Some(buf) if buf.capacity() >= num_frames => buf,
410 - _ => sample_buf.insert(SampleBuffer::<f32>::new(num_frames as u64, spec)),
411 - };
412 - buf.copy_interleaved_ref(decoded);
413 - let samples = buf.samples();
416 + sample_buf.resize(decoded.samples_interleaved(), 0.0);
417 + decoded.copy_to_slice_interleaved(&mut sample_buf[..]);
418 + let samples = &sample_buf[..];
414 419
415 420 // Convert to interleaved stereo in a local batch
416 421 let mut batch = Vec::with_capacity(num_frames * 2);
@@ -6,12 +6,11 @@
6 6
7 7 use std::path::Path;
8 8
9 - use symphonia::core::audio::SampleBuffer;
10 - use symphonia::core::codecs::DecoderOptions;
11 - use symphonia::core::formats::FormatOptions;
9 + use symphonia::core::codecs::audio::AudioDecoderOptions;
10 + use symphonia::core::formats::probe::Hint;
11 + use symphonia::core::formats::{FormatOptions, TrackType};
12 12 use symphonia::core::io::MediaSourceStream;
13 13 use symphonia::core::meta::MetadataOptions;
14 - use symphonia::core::probe::Hint;
15 14 use tracing::{instrument, warn};
16 15
17 16 use crate::error::{AnalysisError, CoreError, io_err};
@@ -59,11 +58,11 @@ pub fn decode_to_mono(path: &Path) -> Result<DecodedAudio, CoreError> {
59 58 hint.with_extension(ext);
60 59 }
61 60
62 - let probed = match symphonia::default::get_probe().format(
61 + let mut format = match symphonia::default::get_probe().probe(
63 62 &hint,
64 63 mss,
65 - &FormatOptions::default(),
66 - &MetadataOptions::default(),
64 + FormatOptions::default(),
65 + MetadataOptions::default(),
67 66 ) {
68 67 Ok(p) => p,
69 68 Err(e) => {
@@ -82,35 +81,45 @@ pub fn decode_to_mono(path: &Path) -> Result<DecodedAudio, CoreError> {
82 81 }
83 82 };
84 83
85 - let mut format = probed.format;
86 -
87 - let track = format.default_track().ok_or(AnalysisError::NoAudioTrack)?;
84 + let track = format
85 + .default_track(TrackType::Audio)
86 + .ok_or(AnalysisError::NoAudioTrack)?;
88 87
89 88 let track_id = track.id;
90 - let mut source_sample_rate =
91 - track
92 - .codec_params
93 - .sample_rate
94 - .ok_or(AnalysisError::ProbeFailed(
95 - "missing sample rate".to_string(),
96 - ))?;
97 - let source_channels = track
89 + // Cloned so the immutable borrow of `format` ends here; the decode loop below
90 + // needs `format` mutably.
91 + let audio_params = track
98 92 .codec_params
93 + .as_ref()
94 + .and_then(|p| p.audio())
95 + .ok_or(AnalysisError::NoAudioTrack)?
96 + .clone();
97 + let mut source_sample_rate = audio_params.sample_rate.ok_or(AnalysisError::ProbeFailed(
98 + "missing sample rate".to_string(),
99 + ))?;
100 + let source_channels = audio_params
99 101 .channels
102 + .as_ref()
100 103 .map(|c| c.count() as u16)
101 104 .ok_or(AnalysisError::ProbeFailed(
102 105 "missing channel count".to_string(),
103 106 ))?;
104 107
105 108 let mut decoder = symphonia::default::get_codecs()
106 - .make(&track.codec_params, &DecoderOptions::default())
109 + .make_audio_decoder(&audio_params, &AudioDecoderOptions::default())
107 110 .map_err(|e| AnalysisError::DecoderFailed(e.to_string()))?;
108 111
109 112 let mut mono_samples: Vec<f32> = Vec::new();
113 + // Reused across packets so the interleaved copy does not reallocate per packet.
114 + let mut sample_buf: Vec<f32> = Vec::new();
110 115
111 116 loop {
112 117 let packet = match format.next_packet() {
113 - Ok(p) => p,
118 + // symphonia 0.6 signals end-of-stream with `Ok(None)` rather than an
119 + // UnexpectedEof io error, but a truncated file still surfaces as the
120 + // latter, so both end the loop cleanly.
121 + Ok(Some(p)) => p,
122 + Ok(None) => break,
114 123 Err(symphonia::core::errors::Error::IoError(ref e))
115 124 if e.kind() == std::io::ErrorKind::UnexpectedEof =>
116 125 {
@@ -119,7 +128,7 @@ pub fn decode_to_mono(path: &Path) -> Result<DecodedAudio, CoreError> {
119 128 Err(e) => return Err(AnalysisError::PacketError(e.to_string()).into()),
120 129 };
121 130
122 - if packet.track_id() != track_id {
131 + if packet.track_id != track_id {
123 132 continue;
124 133 }
125 134
@@ -129,9 +138,9 @@ pub fn decode_to_mono(path: &Path) -> Result<DecodedAudio, CoreError> {
129 138 Err(e) => return Err(AnalysisError::DecodeError(e.to_string()).into()),
130 139 };
131 140
132 - let spec = *decoded.spec();
141 + let spec = decoded.spec();
133 142 let num_frames = decoded.frames();
134 - let num_channels = spec.channels.count();
143 + let num_channels = spec.channels().count();
135 144
136 145 if num_channels == 0 {
137 146 continue;
@@ -141,18 +150,18 @@ pub fn decode_to_mono(path: &Path) -> Result<DecodedAudio, CoreError> {
141 150 // agree; if a container's header lies, the decoded spec is the truth, and
142 151 // duration/BPM/loop math must not trust the wrong declared rate. The specs
143 152 // are identical across packets, so this fires at most once.
144 - if spec.rate != 0 && spec.rate != source_sample_rate {
153 + if spec.rate() != 0 && spec.rate() != source_sample_rate {
145 154 tracing::warn!(
146 155 declared = source_sample_rate,
147 - decoded = spec.rate,
156 + decoded = spec.rate(),
148 157 "decoded sample rate disagrees with declared header; using decoded rate"
149 158 );
150 - source_sample_rate = spec.rate;
159 + source_sample_rate = spec.rate();
151 160 }
152 161
153 - let mut sample_buf = SampleBuffer::<f32>::new(num_frames as u64, *decoded.spec());
154 - sample_buf.copy_interleaved_ref(decoded);
155 - let samples = sample_buf.samples();
162 + sample_buf.resize(decoded.samples_interleaved(), 0.0);
163 + decoded.copy_to_slice_interleaved(&mut sample_buf[..]);
164 + let samples = &sample_buf[..];
156 165
157 166 // Mix down to mono by averaging all channels
158 167 for frame in 0..num_frames {
@@ -332,6 +341,48 @@ mod tests {
332 341 b
333 342 }
334 343
344 + /// A well-formed stereo WAV must go through the Symphonia path (not the hound
345 + /// fallback) and come back mixed to mono with the header's rate and channel
346 + /// count intact. The negative tests below only prove the error paths don't
347 + /// panic; this one pins the actual decode contract, which is what a Symphonia
348 + /// major-version upgrade puts at risk.
349 + #[test]
350 + fn stereo_wav_decodes_to_mono_via_symphonia() {
351 + let dir = tempfile::tempdir().unwrap();
352 + let path = dir.path().join("stereo.wav");
353 +
354 + // Four frames, hard-panned: left rail then right rail. The mono mixdown
355 + // averages the pair, so every frame lands at +/- half scale.
356 + let frames: [(i16, i16); 4] = [
357 + (i16::MAX, 0),
358 + (0, i16::MAX),
359 + (i16::MIN + 1, 0),
360 + (0, i16::MIN + 1),
361 + ];
362 + let mut data = Vec::new();
363 + for (l, r) in frames {
364 + data.extend_from_slice(&l.to_le_bytes());
365 + data.extend_from_slice(&r.to_le_bytes());
366 + }
367 + std::fs::write(&path, raw_wav(2, 16, &data)).unwrap();
368 +
369 + let decoded = decode_to_mono(&path).expect("stereo WAV must decode");
370 +
371 + assert_eq!(decoded.sample_rate, 44100);
372 + assert_eq!(decoded.channels, 2);
373 + assert_eq!(decoded.samples.len(), 4);
374 + assert!((decoded.duration - 4.0 / 44100.0).abs() < 1e-9);
375 +
376 + let expected = [0.5f32, 0.5, -0.5, -0.5];
377 + for (i, &want) in expected.iter().enumerate() {
378 + assert!(
379 + (decoded.samples[i] - want).abs() < 1e-3,
380 + "frame {i}: expected ~{want}, got {}",
381 + decoded.samples[i]
382 + );
383 + }
384 + }
385 +
335 386 #[test]
336 387 fn mix_to_mono_zero_channels_does_not_panic() {
337 388 // chunks_exact(0) would panic; the guard must return empty instead.
@@ -5,12 +5,11 @@
5 5
6 6 use std::path::Path;
7 7
8 - use symphonia::core::audio::SampleBuffer;
9 - use symphonia::core::codecs::{Decoder, DecoderOptions};
10 - use symphonia::core::formats::{FormatOptions, FormatReader};
8 + use symphonia::core::codecs::audio::{AudioDecoder, AudioDecoderOptions};
9 + use symphonia::core::formats::probe::Hint;
10 + use symphonia::core::formats::{FormatOptions, FormatReader, TrackType};
11 11 use symphonia::core::io::MediaSourceStream;
12 12 use symphonia::core::meta::MetadataOptions;
13 - use symphonia::core::probe::Hint;
14 13 use tracing::instrument;
15 14
16 15 use crate::error::{AnalysisError, CoreError, io_err};
@@ -31,7 +30,7 @@ pub struct DecodedMultichannel {
31 30 /// streaming export path needs it to size the resampler output exactly.
32 31 pub(crate) struct OpenedDecoder {
33 32 pub format: Box<dyn FormatReader>,
34 - pub decoder: Box<dyn Decoder>,
33 + pub decoder: Box<dyn AudioDecoder>,
35 34 pub track_id: u32,
36 35 pub sample_rate: u32,
37 36 pub channels: u16,
@@ -49,35 +48,41 @@ pub(crate) fn open_decoder(path: &Path) -> Result<OpenedDecoder, CoreError> {
49 48 hint.with_extension(ext);
50 49 }
51 50
52 - let probed = symphonia::default::get_probe()
53 - .format(
51 + let format = symphonia::default::get_probe()
52 + .probe(
54 53 &hint,
55 54 mss,
56 - &FormatOptions::default(),
57 - &MetadataOptions::default(),
55 + FormatOptions::default(),
56 + MetadataOptions::default(),
58 57 )
59 58 .map_err(|e| AnalysisError::ProbeFailed(e.to_string()))?;
60 59
61 - let format = probed.format;
62 - let track = format.default_track().ok_or(AnalysisError::NoAudioTrack)?;
60 + let track = format
61 + .default_track(TrackType::Audio)
62 + .ok_or(AnalysisError::NoAudioTrack)?;
63 63 let track_id = track.id;
64 - let sample_rate = track
65 - .codec_params
66 - .sample_rate
67 - .ok_or(AnalysisError::ProbeFailed(
68 - "missing sample rate".to_string(),
69 - ))?;
70 - let channels = track
64 + // symphonia 0.6 moved the frame count onto the track; the audio-specific codec
65 + // parameters are cloned so the borrow of `format` ends before it is returned.
66 + let n_frames = track.num_frames;
67 + let audio_params = track
71 68 .codec_params
69 + .as_ref()
70 + .and_then(|p| p.audio())
71 + .ok_or(AnalysisError::NoAudioTrack)?
72 + .clone();
73 + let sample_rate = audio_params.sample_rate.ok_or(AnalysisError::ProbeFailed(
74 + "missing sample rate".to_string(),
75 + ))?;
76 + let channels = audio_params
72 77 .channels
78 + .as_ref()
73 79 .map(|c| c.count() as u16)
74 80 .ok_or(AnalysisError::ProbeFailed(
75 81 "missing channel count".to_string(),
76 82 ))?;
77 - let n_frames = track.codec_params.n_frames;
78 83
79 84 let decoder = symphonia::default::get_codecs()
80 - .make(&track.codec_params, &DecoderOptions::default())
85 + .make_audio_decoder(&audio_params, &AudioDecoderOptions::default())
81 86 .map_err(|e| AnalysisError::DecoderFailed(e.to_string()))?;
82 87
83 88 Ok(OpenedDecoder {
@@ -95,13 +100,14 @@ pub(crate) fn open_decoder(path: &Path) -> Result<OpenedDecoder, CoreError> {
95 100 /// loop used by the buffered and streaming paths.
96 101 pub(crate) fn decode_next_into(
97 102 format: &mut dyn FormatReader,
98 - decoder: &mut dyn Decoder,
103 + decoder: &mut dyn AudioDecoder,
99 104 track_id: u32,
100 105 out: &mut Vec<f32>,
101 106 ) -> Result<bool, CoreError> {
102 107 loop {
103 108 let packet = match format.next_packet() {
104 - Ok(p) => p,
109 + Ok(Some(p)) => p,
110 + Ok(None) => return Ok(false),
105 111 Err(symphonia::core::errors::Error::IoError(ref e))
106 112 if e.kind() == std::io::ErrorKind::UnexpectedEof =>
107 113 {
@@ -109,7 +115,7 @@ pub(crate) fn decode_next_into(
109 115 }
110 116 Err(e) => return Err(AnalysisError::PacketError(e.to_string()).into()),
111 117 };
112 - if packet.track_id() != track_id {
118 + if packet.track_id != track_id {
113 119 continue;
114 120 }
115 121 let decoded = match decoder.decode(&packet) {
@@ -117,10 +123,9 @@ pub(crate) fn decode_next_into(
117 123 Err(symphonia::core::errors::Error::DecodeError(_)) => continue,
118 124 Err(e) => return Err(AnalysisError::DecodeError(e.to_string()).into()),
119 125 };
120 - let num_frames = decoded.frames();
121 - let mut sample_buf = SampleBuffer::<f32>::new(num_frames as u64, *decoded.spec());
122 - sample_buf.copy_interleaved_ref(decoded);
123 - out.extend_from_slice(sample_buf.samples());
126 + let start = out.len();
127 + out.resize(start + decoded.samples_interleaved(), 0.0);
128 + decoded.copy_to_slice_interleaved(&mut out[start..]);
124 129 return Ok(true);
125 130 }
126 131 }
@@ -198,6 +198,55 @@ mod tests {
198 198 }
199 199 }
200 200
201 + /// Decode an encoded AIFF back through symphonia, returning the interleaved
202 + /// samples and the sample rate the container reports. Used by the round-trip
203 + /// tests to check what a real decoder sees in what we wrote.
204 + fn decode_aiff(path: &std::path::Path) -> (Vec<f32>, Option<u32>) {
205 + use symphonia::core::codecs::audio::AudioDecoderOptions;
206 + use symphonia::core::formats::probe::Hint;
207 + use symphonia::core::formats::{FormatOptions, TrackType};
208 + use symphonia::core::io::MediaSourceStream;
209 + use symphonia::core::meta::MetadataOptions;
210 +
211 + let file = std::fs::File::open(path).unwrap();
212 + let mss = MediaSourceStream::new(Box::new(file), Default::default());
213 + let mut hint = Hint::new();
214 + hint.with_extension("aiff");
215 +
216 + let mut format = symphonia::default::get_probe()
217 + .probe(
218 + &hint,
219 + mss,
220 + FormatOptions::default(),
221 + MetadataOptions::default(),
222 + )
223 + .unwrap();
224 +
225 + let track = format.default_track(TrackType::Audio).unwrap();
226 + let audio_params = track
227 + .codec_params
228 + .as_ref()
229 + .and_then(|p| p.audio())
230 + .unwrap()
231 + .clone();
232 + let sample_rate = audio_params.sample_rate;
233 +
234 + let mut decoder = symphonia::default::get_codecs()
235 + .make_audio_decoder(&audio_params, &AudioDecoderOptions::default())
236 + .unwrap();
237 +
238 + let mut all_samples: Vec<f32> = Vec::new();
239 + while let Ok(Some(packet)) = format.next_packet() {
240 + if let Ok(decoded) = decoder.decode(&packet) {
241 + let start = all_samples.len();
242 + all_samples.resize(start + decoded.samples_interleaved(), 0.0);
243 + decoded.copy_to_slice_interleaved(&mut all_samples[start..]);
244 + }
245 + }
246 +
247 + (all_samples, sample_rate)
248 + }
249 +
201 250 #[test]
202 251 fn extended_float_44100() {
203 252 let bytes = sample_rate_to_extended(44100);
@@ -233,45 +282,8 @@ mod tests {
233 282 assert_eq!(&data[12..16], b"COMM");
234 283
235 284 // Decode with symphonia to verify round-trip
236 - use symphonia::core::audio::SampleBuffer;
237 - use symphonia::core::codecs::DecoderOptions;
238 - use symphonia::core::formats::FormatOptions;
239 - use symphonia::core::io::MediaSourceStream;
240 - use symphonia::core::meta::MetadataOptions;
241 - use symphonia::core::probe::Hint;
242 -
243 - let file = std::fs::File::open(&path).unwrap();
244 - let mss = MediaSourceStream::new(Box::new(file), Default::default());
245 - let mut hint = Hint::new();
246 - hint.with_extension("aiff");
247 -
248 - let probed = symphonia::default::get_probe()
249 - .format(
250 - &hint,
251 - mss,
252 - &FormatOptions::default(),
253 - &MetadataOptions::default(),
254 - )
255 - .unwrap();
256 -
257 - let mut format = probed.format;
258 - let track = format.default_track().unwrap();
259 - let codec_params = track.codec_params.clone();
260 - assert_eq!(codec_params.sample_rate, Some(44100));
261 -
262 - let mut decoder = symphonia::default::get_codecs()
263 - .make(&codec_params, &DecoderOptions::default())
264 - .unwrap();
265 -
266 - let mut all_samples = Vec::new();
267 - while let Ok(packet) = format.next_packet() {
268 - if let Ok(decoded) = decoder.decode(&packet) {
269 - let num_frames = decoded.frames();
270 - let mut sample_buf = SampleBuffer::<f32>::new(num_frames as u64, *decoded.spec());
271 - sample_buf.copy_interleaved_ref(decoded);
272 - all_samples.extend_from_slice(sample_buf.samples());
273 - }
274 - }
285 + let (all_samples, sample_rate) = decode_aiff(&path);
286 + assert_eq!(sample_rate, Some(44100));
275 287
276 288 assert_eq!(all_samples.len(), 4);
277 289 // Check values within quantization error (16-bit + dither)
@@ -300,45 +312,8 @@ mod tests {
300 312 assert_eq!(&data[8..12], b"AIFF");
301 313
302 314 // Decode with symphonia
303 - use symphonia::core::audio::SampleBuffer;
304 - use symphonia::core::codecs::DecoderOptions;
305 - use symphonia::core::formats::FormatOptions;
306 - use symphonia::core::io::MediaSourceStream;
307 - use symphonia::core::meta::MetadataOptions;
308 - use symphonia::core::probe::Hint;
309 -
310 - let file = std::fs::File::open(&path).unwrap();
311 - let mss = MediaSourceStream::new(Box::new(file), Default::default());
312 - let mut hint = Hint::new();
313 - hint.with_extension("aiff");
314 -
315 - let probed = symphonia::default::get_probe()
316 - .format(
317 - &hint,
318 - mss,
319 - &FormatOptions::default(),
320 - &MetadataOptions::default(),
321 - )
322 - .unwrap();
323 -
324 - let mut format = probed.format;
325 - let track = format.default_track().unwrap();
326 - let codec_params = track.codec_params.clone();
327 - assert_eq!(codec_params.sample_rate, Some(48000));
328 -
329 - let mut decoder = symphonia::default::get_codecs()
330 - .make(&codec_params, &DecoderOptions::default())
331 - .unwrap();
332 -
333 - let mut all_samples = Vec::new();
334 - while let Ok(packet) = format.next_packet() {
335 - if let Ok(decoded) = decoder.decode(&packet) {
336 - let num_frames = decoded.frames();
337 - let mut sample_buf = SampleBuffer::<f32>::new(num_frames as u64, *decoded.spec());
338 - sample_buf.copy_interleaved_ref(decoded);
339 - all_samples.extend_from_slice(sample_buf.samples());
340 - }
341 - }
315 + let (all_samples, sample_rate) = decode_aiff(&path);
316 + assert_eq!(sample_rate, Some(48000));
342 317
343 318 assert_eq!(all_samples.len(), 6);
344 319 let scale = 8_388_607.0f32;
@@ -14,10 +14,11 @@ use std::io::{Read, Write};
14 14 use std::path::{Path, PathBuf};
15 15
16 16 use sha2::{Digest, Sha256};
17 - use symphonia::core::formats::FormatOptions;
17 + use symphonia::core::formats::probe::Hint;
18 + use symphonia::core::formats::{FormatOptions, TrackType};
18 19 use symphonia::core::io::MediaSourceStream;
19 20 use symphonia::core::meta::MetadataOptions;
20 - use symphonia::core::probe::Hint;
21 + use symphonia::core::units::Timestamp;
21 22
22 23 use crate::db::Database;
23 24 use crate::error::{CoreError, Result, io_err, unix_now};
@@ -35,17 +36,19 @@ fn probe_duration(path: &Path) -> Option<f64> {
35 36 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
36 37 hint.with_extension(ext);
37 38 }
38 - if let Ok(probed) = symphonia::default::get_probe().format(
39 + if let Ok(format) = symphonia::default::get_probe().probe(
39 40 &hint,
40 41 mss,
41 - &FormatOptions::default(),
42 - &MetadataOptions::default(),
42 + FormatOptions::default(),
43 + MetadataOptions::default(),
43 44 ) {
44 - let track = probed.format.default_track()?;
45 - let time_base = track.codec_params.time_base?;
46 - let n_frames = track.codec_params.n_frames?;
47 - let duration = time_base.calc_time(n_frames);
48 - return Some(duration.seconds as f64 + duration.frac);
45 + // symphonia 0.6 moved the timebase and frame count off codec_params onto
46 + // the track itself.
47 + let track = format.default_track(TrackType::Audio)?;
48 + let time_base = track.time_base?;
49 + let n_frames = track.num_frames?;
50 + let duration = time_base.calc_time(Timestamp::new(n_frames.try_into().ok()?))?;
51 + return Some(duration.as_secs_f64());
49 52 }
50 53 // Fallback for WAV files Symphonia rejects (non-standard fmt chunk sizes)
51 54 let is_wav = path