max / audiofiles
8 files changed,
+465 insertions,
-242 deletions
| @@ -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]] |
| @@ -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 |