pile-audio refactor
This commit is contained in:
216
Cargo.lock
generated
216
Cargo.lock
generated
@@ -78,9 +78,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.100"
|
version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
@@ -122,9 +122,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitpacking"
|
name = "bitpacking"
|
||||||
@@ -235,9 +235,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.53"
|
version = "4.5.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -245,9 +245,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.53"
|
version = "4.5.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -257,9 +257,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.49"
|
version = "4.5.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -269,9 +269,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.7.6"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
@@ -294,9 +294,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e"
|
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
@@ -374,9 +374,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.2.0-rc.9"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809"
|
checksum = "211f05e03c7d03754740fd9e585de910a095d6b99f8bcfffdef8319fa02a8331"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hybrid-array",
|
"hybrid-array",
|
||||||
]
|
]
|
||||||
@@ -437,13 +437,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.11.0-rc.5"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9"
|
checksum = "f8bf3682cdec91817be507e4aa104314898b95b84d74f3d43882210101a545b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer 0.11.0",
|
"block-buffer 0.11.0",
|
||||||
"const-oid",
|
"const-oid",
|
||||||
"crypto-common 0.2.0-rc.9",
|
"crypto-common 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -477,7 +477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -633,9 +633,9 @@ checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hybrid-array"
|
name = "hybrid-array"
|
||||||
version = "0.4.5"
|
version = "0.4.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0"
|
checksum = "e1b229d73f5803b562cc26e4da0396c8610a4ee209f4fac8fa4f8d709166dc45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
@@ -687,9 +687,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.12.1"
|
version = "2.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
@@ -699,9 +699,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indicatif"
|
name = "indicatif"
|
||||||
version = "0.18.3"
|
version = "0.18.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88"
|
checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
@@ -729,9 +729,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.16"
|
version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010"
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
@@ -786,9 +786,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.178"
|
version = "0.2.182"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
@@ -852,9 +852,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
@@ -910,7 +910,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1048,21 +1048,6 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pile-audio"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"itertools",
|
|
||||||
"mime",
|
|
||||||
"paste",
|
|
||||||
"rand 0.9.2",
|
|
||||||
"sha2 0.11.0-rc.3",
|
|
||||||
"smartstring",
|
|
||||||
"strum",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pile-config"
|
name = "pile-config"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
@@ -1081,8 +1066,8 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"itertools",
|
"itertools",
|
||||||
"jsonpath-rust",
|
"jsonpath-rust",
|
||||||
"pile-audio",
|
|
||||||
"pile-config",
|
"pile-config",
|
||||||
|
"pile-flac",
|
||||||
"pile-toolbox",
|
"pile-toolbox",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tantivy",
|
"tantivy",
|
||||||
@@ -1092,6 +1077,20 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pile-flac"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"itertools",
|
||||||
|
"mime",
|
||||||
|
"paste",
|
||||||
|
"sha2 0.11.0-rc.5",
|
||||||
|
"smartstring",
|
||||||
|
"strum",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pile-toolbox"
|
name = "pile-toolbox"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
@@ -1114,9 +1113,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.12.0"
|
version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd"
|
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
@@ -1145,18 +1144,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.103"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.42"
|
version = "1.0.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -1174,18 +1173,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha",
|
||||||
"rand_core 0.6.4",
|
"rand_core",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.9.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
|
||||||
dependencies = [
|
|
||||||
"rand_chacha 0.9.0",
|
|
||||||
"rand_core 0.9.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1195,17 +1184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core 0.6.4",
|
"rand_core",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_chacha"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
|
||||||
dependencies = [
|
|
||||||
"ppv-lite86",
|
|
||||||
"rand_core 0.9.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1217,15 +1196,6 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.3.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_distr"
|
name = "rand_distr"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -1233,7 +1203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand 0.8.5",
|
"rand",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1320,7 +1290,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1382,9 +1352,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.147"
|
version = "1.0.149"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4"
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1415,13 +1385,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.11.0-rc.3"
|
version = "0.11.0-rc.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927"
|
checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"digest 0.11.0-rc.5",
|
"digest 0.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1441,9 +1411,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.18"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
checksum = "3b57709da74f9ff9f4a27dce9526eec25ca8407c45a7887243b031a58935fb8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
@@ -1493,9 +1463,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
@@ -1542,9 +1512,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.111"
|
version = "2.0.117"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1704,26 +1674,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.4.1",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.17"
|
version = "2.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.17"
|
version = "2.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1772,9 +1742,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.48.0"
|
version = "1.49.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1800,9 +1770,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.10+spec-1.1.0"
|
version = "1.0.3+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48"
|
checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -1815,18 +1785,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.7.5+spec-1.1.0"
|
version = "1.0.0+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.6+spec-1.1.0"
|
version = "1.0.9+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
@@ -1937,9 +1907,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
@@ -2167,7 +2137,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2493,18 +2463,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.31"
|
version = "0.8.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
|
checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.31"
|
version = "0.8.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2513,9 +2483,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "0.1.9"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0095ecd462946aa3927d9297b63ef82fb9a5316d7a37d134eeb36e58228615a"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
|
|||||||
32
Cargo.toml
32
Cargo.toml
@@ -65,37 +65,37 @@ type_complexity = "allow"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
pile-toolbox = { path = "crates/pile-toolbox" }
|
pile-toolbox = { path = "crates/pile-toolbox" }
|
||||||
pile-config = { path = "crates/pile-config" }
|
pile-config = { path = "crates/pile-config" }
|
||||||
pile-audio = { path = "crates/pile-audio" }
|
pile-flac = { path = "crates/pile-flac" }
|
||||||
pile-dataset = { path = "crates/pile-dataset" }
|
pile-dataset = { path = "crates/pile-dataset" }
|
||||||
|
|
||||||
# Clients
|
# Clients
|
||||||
tantivy = "0.25.0"
|
tantivy = "0.25.0"
|
||||||
|
|
||||||
# Async & Parallelism
|
# Async & Parallelism
|
||||||
tokio = { version = "1.44.1", features = ["full"] }
|
tokio = { version = "1.49.0", features = ["full"] }
|
||||||
|
|
||||||
# CLI & logging
|
# CLI & logging
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] }
|
||||||
indicatif = { version = "0.18.0", features = ["improved_unicode"] }
|
indicatif = { version = "0.18.4", features = ["improved_unicode"] }
|
||||||
tracing-indicatif = "0.3.13"
|
tracing-indicatif = "0.3.14"
|
||||||
anstyle = "1.0.10"
|
anstyle = "1.0.13"
|
||||||
clap = { version = "4.5.37", features = ["derive"] }
|
clap = { version = "4.5.60", features = ["derive"] }
|
||||||
|
|
||||||
# Serialization & formats
|
# Serialization & formats
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.149"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
toml = "0.9.8"
|
toml = "1.0.3"
|
||||||
jsonpath-rust = "1.0.4"
|
jsonpath-rust = "1.0.4"
|
||||||
sha2 = "0.11.0-rc.3"
|
sha2 = "0.11.0-rc.5"
|
||||||
|
|
||||||
# Misc helpers
|
# Misc helpers
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.18"
|
||||||
anyhow = "1.0.97"
|
anyhow = "1.0.102"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
signal-hook = "0.3.18"
|
signal-hook = "0.4.3"
|
||||||
rand = "0.9.2"
|
rand = "0.10.0"
|
||||||
strum = { version = "0.27.2", features = ["derive"] }
|
strum = { version = "0.27.2", features = ["derive"] }
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
//! Components shared between many different formats
|
|
||||||
|
|
||||||
pub mod picturetype;
|
|
||||||
pub mod tagtype;
|
|
||||||
pub mod vorbiscomment;
|
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
//! Decode and write Vorbis comment blocks
|
|
||||||
|
|
||||||
use base64::Engine;
|
|
||||||
use smartstring::{LazyCompact, SmartString};
|
|
||||||
use std::{
|
|
||||||
fmt::Display,
|
|
||||||
io::{Cursor, Read, Write},
|
|
||||||
string::FromUtf8Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::flac::blocks::{FlacMetablockDecode, FlacMetablockEncode, FlacPictureBlock};
|
|
||||||
|
|
||||||
use super::tagtype::TagType;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[expect(missing_docs)]
|
|
||||||
pub enum VorbisCommentDecodeError {
|
|
||||||
/// We encountered an IoError while processing a block
|
|
||||||
IoError(std::io::Error),
|
|
||||||
|
|
||||||
/// We tried to decode a string, but got invalid data
|
|
||||||
FailedStringDecode(FromUtf8Error),
|
|
||||||
|
|
||||||
/// The given comment string isn't within spec
|
|
||||||
MalformedCommentString(String),
|
|
||||||
|
|
||||||
/// The comment we're reading is invalid
|
|
||||||
MalformedData,
|
|
||||||
|
|
||||||
/// We tried to decode picture data, but it was malformed.
|
|
||||||
MalformedPicture,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for VorbisCommentDecodeError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::IoError(_) => write!(f, "io error while reading vorbis comments"),
|
|
||||||
Self::FailedStringDecode(_) => {
|
|
||||||
write!(f, "string decode error while reading vorbis comments")
|
|
||||||
}
|
|
||||||
Self::MalformedCommentString(x) => {
|
|
||||||
write!(f, "malformed comment string `{x}`")
|
|
||||||
}
|
|
||||||
Self::MalformedData => {
|
|
||||||
write!(f, "malformed comment data")
|
|
||||||
}
|
|
||||||
Self::MalformedPicture => {
|
|
||||||
write!(f, "malformed picture data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for VorbisCommentDecodeError {
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
Self::IoError(x) => Some(x),
|
|
||||||
Self::FailedStringDecode(x) => Some(x),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for VorbisCommentDecodeError {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IoError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FromUtf8Error> for VorbisCommentDecodeError {
|
|
||||||
fn from(value: FromUtf8Error) -> Self {
|
|
||||||
Self::FailedStringDecode(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[expect(missing_docs)]
|
|
||||||
pub enum VorbisCommentEncodeError {
|
|
||||||
/// We encountered an IoError while processing a block
|
|
||||||
IoError(std::io::Error),
|
|
||||||
|
|
||||||
/// We could not encode picture data
|
|
||||||
PictureEncodeError,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for VorbisCommentEncodeError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::IoError(_) => write!(f, "io error while reading vorbis comments"),
|
|
||||||
Self::PictureEncodeError => {
|
|
||||||
write!(f, "could not encode picture")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for VorbisCommentEncodeError {
|
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
|
||||||
match self {
|
|
||||||
Self::IoError(x) => Some(x),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for VorbisCommentEncodeError {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IoError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A decoded vorbis comment block
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct VorbisComment {
|
|
||||||
/// This comment's vendor string
|
|
||||||
pub vendor: SmartString<LazyCompact>,
|
|
||||||
|
|
||||||
/// List of (tag, value)
|
|
||||||
/// Repeated tags are allowed!
|
|
||||||
pub comments: Vec<(TagType, SmartString<LazyCompact>)>,
|
|
||||||
|
|
||||||
/// A list of pictures found in this comment
|
|
||||||
pub pictures: Vec<FlacPictureBlock>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VorbisComment {
|
|
||||||
/// Try to decode the given data as a vorbis comment block
|
|
||||||
pub fn decode(data: &[u8]) -> Result<Self, VorbisCommentDecodeError> {
|
|
||||||
let mut d = Cursor::new(data);
|
|
||||||
|
|
||||||
// This is re-used whenever we need to read four bytes
|
|
||||||
let mut block = [0u8; 4];
|
|
||||||
|
|
||||||
let vendor = {
|
|
||||||
d.read_exact(&mut block)
|
|
||||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
|
||||||
|
|
||||||
let length = u32::from_le_bytes(block);
|
|
||||||
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
let mut text = vec![
|
|
||||||
0u8;
|
|
||||||
length
|
|
||||||
.try_into()
|
|
||||||
.expect("vendor length does not fit into usize")
|
|
||||||
];
|
|
||||||
|
|
||||||
d.read_exact(&mut text)
|
|
||||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
|
||||||
|
|
||||||
String::from_utf8(text)?
|
|
||||||
};
|
|
||||||
|
|
||||||
d.read_exact(&mut block)
|
|
||||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
|
||||||
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
let n_comments: usize = u32::from_le_bytes(block)
|
|
||||||
.try_into()
|
|
||||||
.expect("comment count does not fit into usize");
|
|
||||||
|
|
||||||
let mut comments = Vec::new();
|
|
||||||
let mut pictures = Vec::new();
|
|
||||||
for _ in 0..n_comments {
|
|
||||||
let comment = {
|
|
||||||
d.read_exact(&mut block)
|
|
||||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
|
||||||
|
|
||||||
let length = u32::from_le_bytes(block);
|
|
||||||
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
let mut text = vec![
|
|
||||||
0u8;
|
|
||||||
length
|
|
||||||
.try_into()
|
|
||||||
.expect("comment length does not fit into usize")
|
|
||||||
];
|
|
||||||
|
|
||||||
d.read_exact(&mut text)
|
|
||||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
|
||||||
|
|
||||||
String::from_utf8(text)?
|
|
||||||
};
|
|
||||||
let (var, val) =
|
|
||||||
comment
|
|
||||||
.split_once('=')
|
|
||||||
.ok_or(VorbisCommentDecodeError::MalformedCommentString(
|
|
||||||
comment.clone(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
if !val.is_empty() {
|
|
||||||
if var.to_uppercase() == "METADATA_BLOCK_PICTURE" {
|
|
||||||
#[expect(clippy::map_err_ignore)]
|
|
||||||
pictures.push(
|
|
||||||
FlacPictureBlock::decode(
|
|
||||||
&base64::prelude::BASE64_STANDARD
|
|
||||||
.decode(val)
|
|
||||||
.map_err(|_| VorbisCommentDecodeError::MalformedPicture)?,
|
|
||||||
)
|
|
||||||
.map_err(|_| VorbisCommentDecodeError::MalformedPicture)?,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Make sure empty strings are saved as "None"
|
|
||||||
comments.push((
|
|
||||||
match &var.to_uppercase()[..] {
|
|
||||||
"TITLE" => TagType::TrackTitle,
|
|
||||||
"ALBUM" => TagType::Album,
|
|
||||||
"TRACKNUMBER" => TagType::TrackNumber,
|
|
||||||
"ARTIST" => TagType::TrackArtist,
|
|
||||||
"ALBUMARTIST" => TagType::AlbumArtist,
|
|
||||||
"GENRE" => TagType::Genre,
|
|
||||||
"ISRC" => TagType::Isrc,
|
|
||||||
"DATE" => TagType::ReleaseDate,
|
|
||||||
"TOTALTRACKS" => TagType::TrackTotal,
|
|
||||||
"LYRICS" => TagType::Lyrics,
|
|
||||||
x => TagType::Other(x.into()),
|
|
||||||
},
|
|
||||||
val.into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
vendor: vendor.into(),
|
|
||||||
comments,
|
|
||||||
pictures,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VorbisComment {
|
|
||||||
/// Get the number of bytes that `encode()` will write.
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
pub fn get_len(&self) -> u32 {
|
|
||||||
let mut sum: u32 = 0;
|
|
||||||
sum += u32::try_from(self.vendor.len()).expect("vendor length does not fit into u32") + 4;
|
|
||||||
sum += 4;
|
|
||||||
|
|
||||||
for (tagtype, value) in &self.comments {
|
|
||||||
let tagtype_str = match tagtype {
|
|
||||||
TagType::TrackTitle => "TITLE",
|
|
||||||
TagType::Album => "ALBUM",
|
|
||||||
TagType::TrackNumber => "TRACKNUMBER",
|
|
||||||
TagType::TrackArtist => "ARTIST",
|
|
||||||
TagType::AlbumArtist => "ALBUMARTIST",
|
|
||||||
TagType::Genre => "GENRE",
|
|
||||||
TagType::Isrc => "ISRC",
|
|
||||||
TagType::ReleaseDate => "DATE",
|
|
||||||
TagType::TrackTotal => "TOTALTRACKS",
|
|
||||||
TagType::Lyrics => "LYRICS",
|
|
||||||
TagType::Comment => "COMMENT",
|
|
||||||
TagType::DiskNumber => "DISKNUMBER",
|
|
||||||
TagType::DiskTotal => "DISKTOTAL",
|
|
||||||
TagType::Year => "YEAR",
|
|
||||||
TagType::Other(x) => x,
|
|
||||||
}
|
|
||||||
.to_uppercase();
|
|
||||||
|
|
||||||
let str = format!("{tagtype_str}={value}");
|
|
||||||
sum +=
|
|
||||||
4 + u32::try_from(str.len()).expect("comment string length does not fit into u32");
|
|
||||||
}
|
|
||||||
|
|
||||||
for p in &self.pictures {
|
|
||||||
// Compute b64 len
|
|
||||||
let mut x = p.get_len();
|
|
||||||
if x % 3 != 0 {
|
|
||||||
x -= x % 3;
|
|
||||||
x += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::integer_division)]
|
|
||||||
{
|
|
||||||
sum += 4 * (x / 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add "METADATA_BLOCK_PICTURE="
|
|
||||||
sum += 23;
|
|
||||||
|
|
||||||
// Add length bytes
|
|
||||||
sum += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to encode this vorbis comment
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
pub fn encode(&self, target: &mut impl Write) -> Result<(), VorbisCommentEncodeError> {
|
|
||||||
target.write_all(
|
|
||||||
&u32::try_from(self.vendor.len())
|
|
||||||
.expect("vendor length does not fit into u32")
|
|
||||||
.to_le_bytes(),
|
|
||||||
)?;
|
|
||||||
target.write_all(self.vendor.as_bytes())?;
|
|
||||||
|
|
||||||
target.write_all(
|
|
||||||
&u32::try_from(self.comments.len() + self.pictures.len())
|
|
||||||
.expect("total comment count does not fit into u32")
|
|
||||||
.to_le_bytes(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for (tagtype, value) in &self.comments {
|
|
||||||
let tagtype_str = match tagtype {
|
|
||||||
TagType::TrackTitle => "TITLE",
|
|
||||||
TagType::Album => "ALBUM",
|
|
||||||
TagType::TrackNumber => "TRACKNUMBER",
|
|
||||||
TagType::TrackArtist => "ARTIST",
|
|
||||||
TagType::AlbumArtist => "ALBUMARTIST",
|
|
||||||
TagType::Genre => "GENRE",
|
|
||||||
TagType::Isrc => "ISRC",
|
|
||||||
TagType::ReleaseDate => "DATE",
|
|
||||||
TagType::TrackTotal => "TOTALTRACKS",
|
|
||||||
TagType::Lyrics => "LYRICS",
|
|
||||||
TagType::Comment => "COMMENT",
|
|
||||||
TagType::DiskNumber => "DISKNUMBER",
|
|
||||||
TagType::DiskTotal => "DISKTOTAL",
|
|
||||||
TagType::Year => "YEAR",
|
|
||||||
TagType::Other(x) => x,
|
|
||||||
}
|
|
||||||
.to_uppercase();
|
|
||||||
|
|
||||||
let str = format!("{tagtype_str}={value}");
|
|
||||||
target.write_all(
|
|
||||||
&u32::try_from(str.len())
|
|
||||||
.expect("comment string length does not fit into u32")
|
|
||||||
.to_le_bytes(),
|
|
||||||
)?;
|
|
||||||
target.write_all(str.as_bytes())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for p in &self.pictures {
|
|
||||||
let mut pic_data = Vec::new();
|
|
||||||
|
|
||||||
#[expect(clippy::map_err_ignore)]
|
|
||||||
p.encode(false, false, &mut pic_data)
|
|
||||||
.map_err(|_| VorbisCommentEncodeError::PictureEncodeError)?;
|
|
||||||
|
|
||||||
let pic_string = format!(
|
|
||||||
"METADATA_BLOCK_PICTURE={}",
|
|
||||||
&base64::prelude::BASE64_STANDARD.encode(&pic_data)
|
|
||||||
);
|
|
||||||
|
|
||||||
target.write_all(
|
|
||||||
&u32::try_from(pic_string.len())
|
|
||||||
.expect("picture string length does not fit into u32")
|
|
||||||
.to_le_bytes(),
|
|
||||||
)?;
|
|
||||||
target.write_all(pic_string.as_bytes())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,867 +0,0 @@
|
|||||||
//! Strip metadata from a FLAC file without loading the whole thing into memory.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
io::{Cursor, Read, Seek, Write},
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
blocks::{
|
|
||||||
FlacAudioFrame, FlacCommentBlock, FlacMetablockDecode, FlacMetablockEncode,
|
|
||||||
FlacMetablockHeader, FlacMetablockType,
|
|
||||||
},
|
|
||||||
errors::{FlacDecodeError, FlacEncodeError},
|
|
||||||
};
|
|
||||||
use crate::flac::blocks::{
|
|
||||||
FlacApplicationBlock, FlacCuesheetBlock, FlacPaddingBlock, FlacPictureBlock,
|
|
||||||
FlacSeektableBlock, FlacStreaminfoBlock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MIN_AUDIO_FRAME_LEN: usize = 5000;
|
|
||||||
|
|
||||||
/// Select which blocks we want to keep.
|
|
||||||
/// All values are `false` by default.
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
|
||||||
pub struct FlacBlockSelector {
|
|
||||||
/// Select `FlacMetablockType::Streaminfo` blocks.
|
|
||||||
pub pick_streaminfo: bool,
|
|
||||||
|
|
||||||
/// Select `FlacMetablockType::Padding` blocks.
|
|
||||||
pub pick_padding: bool,
|
|
||||||
|
|
||||||
/// Select `FlacMetablockType::Application` blocks.
|
|
||||||
pub pick_application: bool,
|
|
||||||
|
|
||||||
/// Select `FlacMetablockType::SeekTable` blocks.
|
|
||||||
pub pick_seektable: bool,
|
|
||||||
|
|
||||||
/// Select `FlacMetablockType::VorbisComment` blocks.
|
|
||||||
pub pick_vorbiscomment: bool,
|
|
||||||
|
|
||||||
/// Select `FlacMetablockType::CueSheet` blocks.
|
|
||||||
pub pick_cuesheet: bool,
|
|
||||||
|
|
||||||
/// Select `FlacMetablockType::Picture` blocks.
|
|
||||||
pub pick_picture: bool,
|
|
||||||
|
|
||||||
/// Select audio frames.
|
|
||||||
pub pick_audio: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlacBlockSelector {
|
|
||||||
/// Make a new [`FlacBlockSelector`]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_pick_meta(&self, block_type: FlacMetablockType) -> bool {
|
|
||||||
match block_type {
|
|
||||||
FlacMetablockType::Streaminfo => self.pick_streaminfo,
|
|
||||||
FlacMetablockType::Padding => self.pick_padding,
|
|
||||||
FlacMetablockType::Application => self.pick_application,
|
|
||||||
FlacMetablockType::Seektable => self.pick_seektable,
|
|
||||||
FlacMetablockType::VorbisComment => self.pick_vorbiscomment,
|
|
||||||
FlacMetablockType::Cuesheet => self.pick_cuesheet,
|
|
||||||
FlacMetablockType::Picture => self.pick_picture,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FlacBlockType {
|
|
||||||
MagicBits {
|
|
||||||
data: [u8; 4],
|
|
||||||
left_to_read: usize,
|
|
||||||
},
|
|
||||||
MetablockHeader {
|
|
||||||
is_first: bool,
|
|
||||||
data: [u8; 4],
|
|
||||||
left_to_read: usize,
|
|
||||||
},
|
|
||||||
MetaBlock {
|
|
||||||
header: FlacMetablockHeader,
|
|
||||||
data: Vec<u8>,
|
|
||||||
},
|
|
||||||
AudioData {
|
|
||||||
data: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[expect(missing_docs)]
|
|
||||||
pub enum FlacBlock {
|
|
||||||
Streaminfo(FlacStreaminfoBlock),
|
|
||||||
Picture(FlacPictureBlock),
|
|
||||||
Padding(FlacPaddingBlock),
|
|
||||||
Application(FlacApplicationBlock),
|
|
||||||
SeekTable(FlacSeektableBlock),
|
|
||||||
VorbisComment(FlacCommentBlock),
|
|
||||||
CueSheet(FlacCuesheetBlock),
|
|
||||||
AudioFrame(FlacAudioFrame),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlacBlock {
|
|
||||||
/// Encode this block
|
|
||||||
pub fn encode(
|
|
||||||
&self,
|
|
||||||
is_last: bool,
|
|
||||||
with_header: bool,
|
|
||||||
target: &mut impl Write,
|
|
||||||
) -> Result<(), FlacEncodeError> {
|
|
||||||
match self {
|
|
||||||
Self::Streaminfo(b) => b.encode(is_last, with_header, target),
|
|
||||||
Self::SeekTable(b) => b.encode(is_last, with_header, target),
|
|
||||||
Self::Picture(b) => b.encode(is_last, with_header, target),
|
|
||||||
Self::Padding(b) => b.encode(is_last, with_header, target),
|
|
||||||
Self::Application(b) => b.encode(is_last, with_header, target),
|
|
||||||
Self::VorbisComment(b) => b.encode(is_last, with_header, target),
|
|
||||||
Self::CueSheet(b) => b.encode(is_last, with_header, target),
|
|
||||||
Self::AudioFrame(b) => b.encode(target),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to decode the given data as a block
|
|
||||||
pub fn decode(block_type: FlacMetablockType, data: &[u8]) -> Result<Self, FlacDecodeError> {
|
|
||||||
Ok(match block_type {
|
|
||||||
FlacMetablockType::Streaminfo => {
|
|
||||||
FlacBlock::Streaminfo(FlacStreaminfoBlock::decode(data)?)
|
|
||||||
}
|
|
||||||
FlacMetablockType::Application => {
|
|
||||||
FlacBlock::Application(FlacApplicationBlock::decode(data)?)
|
|
||||||
}
|
|
||||||
FlacMetablockType::Cuesheet => FlacBlock::CueSheet(FlacCuesheetBlock::decode(data)?),
|
|
||||||
FlacMetablockType::Padding => FlacBlock::Padding(FlacPaddingBlock::decode(data)?),
|
|
||||||
FlacMetablockType::Picture => FlacBlock::Picture(FlacPictureBlock::decode(data)?),
|
|
||||||
FlacMetablockType::Seektable => FlacBlock::SeekTable(FlacSeektableBlock::decode(data)?),
|
|
||||||
FlacMetablockType::VorbisComment => {
|
|
||||||
FlacBlock::VorbisComment(FlacCommentBlock::decode(data)?)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error produced by a [`FlacBlockReader`]
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum FlacBlockReaderError {
|
|
||||||
/// Could not decode flac data
|
|
||||||
#[error("decode error while reading flac blocks")]
|
|
||||||
DecodeError(#[from] FlacDecodeError),
|
|
||||||
|
|
||||||
/// Tried to finish or push data to a finished reader.
|
|
||||||
#[error("flac block reader is already finished")]
|
|
||||||
AlreadyFinished,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A buffered flac block reader.
|
|
||||||
/// Use `push_data` to add flac data into this struct,
|
|
||||||
/// use `pop_block` to read flac blocks.
|
|
||||||
///
|
|
||||||
/// This is the foundation of all other flac processors
|
|
||||||
/// we offer in this crate.
|
|
||||||
pub struct FlacBlockReader {
|
|
||||||
// Which blocks should we return?
|
|
||||||
selector: FlacBlockSelector,
|
|
||||||
|
|
||||||
// The block we're currently reading.
|
|
||||||
// If this is `None`, we've called `finish()`.
|
|
||||||
current_block: Option<FlacBlockType>,
|
|
||||||
|
|
||||||
// Blocks we pick go here
|
|
||||||
output_blocks: VecDeque<FlacBlock>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlacBlockReader {
|
|
||||||
/// Pop the next block we've read, if any.
|
|
||||||
pub fn pop_block(&mut self) -> Option<FlacBlock> {
|
|
||||||
self.output_blocks.pop_front()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If true, this reader has received all the data it needs.
|
|
||||||
pub fn is_done(&self) -> bool {
|
|
||||||
self.current_block.is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If true, this reader has at least one block ready to pop.
|
|
||||||
/// Calling `pop_block` will return `Some(_)` if this is true.
|
|
||||||
pub fn has_block(&self) -> bool {
|
|
||||||
!self.output_blocks.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make a new [`FlacBlockReader`].
|
|
||||||
pub fn new(selector: FlacBlockSelector) -> Self {
|
|
||||||
Self {
|
|
||||||
selector,
|
|
||||||
current_block: Some(FlacBlockType::MagicBits {
|
|
||||||
data: [0; 4],
|
|
||||||
left_to_read: 4,
|
|
||||||
}),
|
|
||||||
|
|
||||||
output_blocks: VecDeque::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pass the given data through this block extractor.
|
|
||||||
/// Output data is stored in an internal buffer, and should be accessed
|
|
||||||
/// through `Read`.
|
|
||||||
pub fn push_data(&mut self, buf: &[u8]) -> Result<(), FlacBlockReaderError> {
|
|
||||||
let mut buf = Cursor::new(buf);
|
|
||||||
let mut last_read_size = 1;
|
|
||||||
|
|
||||||
if self.current_block.is_none() {
|
|
||||||
return Err(FlacBlockReaderError::AlreadyFinished);
|
|
||||||
}
|
|
||||||
|
|
||||||
'outer: while last_read_size != 0 {
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
match self
|
|
||||||
.current_block
|
|
||||||
.as_mut()
|
|
||||||
.expect("current_block is Some, checked above")
|
|
||||||
{
|
|
||||||
FlacBlockType::MagicBits { data, left_to_read } => {
|
|
||||||
last_read_size = buf
|
|
||||||
.read(&mut data[4 - *left_to_read..4])
|
|
||||||
.map_err(FlacDecodeError::from)?;
|
|
||||||
*left_to_read -= last_read_size;
|
|
||||||
|
|
||||||
if *left_to_read == 0 {
|
|
||||||
if *data != [0x66, 0x4C, 0x61, 0x43] {
|
|
||||||
return Err(FlacDecodeError::BadMagicBytes.into());
|
|
||||||
};
|
|
||||||
|
|
||||||
self.current_block = Some(FlacBlockType::MetablockHeader {
|
|
||||||
is_first: true,
|
|
||||||
data: [0; 4],
|
|
||||||
left_to_read: 4,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FlacBlockType::MetablockHeader {
|
|
||||||
is_first,
|
|
||||||
data,
|
|
||||||
left_to_read,
|
|
||||||
} => {
|
|
||||||
last_read_size = buf
|
|
||||||
.read(&mut data[4 - *left_to_read..4])
|
|
||||||
.map_err(FlacDecodeError::from)?;
|
|
||||||
*left_to_read -= last_read_size;
|
|
||||||
|
|
||||||
if *left_to_read == 0 {
|
|
||||||
let header = FlacMetablockHeader::decode(data)?;
|
|
||||||
if *is_first && !matches!(header.block_type, FlacMetablockType::Streaminfo)
|
|
||||||
{
|
|
||||||
return Err(FlacDecodeError::BadFirstBlock.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_block = Some(FlacBlockType::MetaBlock {
|
|
||||||
header,
|
|
||||||
data: Vec::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FlacBlockType::MetaBlock { header, data } => {
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
{
|
|
||||||
last_read_size = buf
|
|
||||||
.by_ref()
|
|
||||||
.take(
|
|
||||||
u64::from(header.length)
|
|
||||||
- u64::try_from(data.len())
|
|
||||||
.expect("data length does not fit into u64"),
|
|
||||||
)
|
|
||||||
.read_to_end(data)
|
|
||||||
.map_err(FlacDecodeError::from)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
if data.len()
|
|
||||||
== usize::try_from(header.length)
|
|
||||||
.expect("header length does not fit into usize")
|
|
||||||
{
|
|
||||||
// If we picked this block type, add it to the queue
|
|
||||||
if self.selector.should_pick_meta(header.block_type) {
|
|
||||||
let b = FlacBlock::decode(header.block_type, data)?;
|
|
||||||
self.output_blocks.push_back(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start next block
|
|
||||||
if header.is_last {
|
|
||||||
self.current_block = Some(FlacBlockType::AudioData { data: Vec::new() })
|
|
||||||
} else {
|
|
||||||
self.current_block = Some(FlacBlockType::MetablockHeader {
|
|
||||||
is_first: false,
|
|
||||||
data: [0; 4],
|
|
||||||
left_to_read: 4,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FlacBlockType::AudioData { data } => {
|
|
||||||
// Limit the number of bytes we read at once, so we don't re-clone
|
|
||||||
// large amounts of data if `buf` contains multiple sync sequences.
|
|
||||||
// 5kb is a pretty reasonable frame size.
|
|
||||||
last_read_size = buf
|
|
||||||
.by_ref()
|
|
||||||
.take(5_000)
|
|
||||||
.read_to_end(data)
|
|
||||||
.map_err(FlacDecodeError::from)?;
|
|
||||||
if last_read_size == 0 {
|
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't run checks if we don't have enough data.
|
|
||||||
if data.len() <= 2 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check frame sync header
|
|
||||||
// (`if` makes sure we only do this once)
|
|
||||||
if data.len() - last_read_size <= 2
|
|
||||||
&& !(data[0] == 0b1111_1111 && data[1] & 0b1111_1100 == 0b1111_1000)
|
|
||||||
{
|
|
||||||
return Err(FlacDecodeError::BadSyncBytes.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.len() >= MIN_AUDIO_FRAME_LEN {
|
|
||||||
// Look for a frame sync header in the data we read
|
|
||||||
//
|
|
||||||
// This isn't the *correct* way to split audio frames (false sync bytes can occur in audio data),
|
|
||||||
// but it's good enough for now---we don't decode audio data anyway.
|
|
||||||
//
|
|
||||||
// We could split on every sequence of sync bytes, but that's not any less wrong than the approach here.
|
|
||||||
// Also, it's slower---we'd rather have few large frames than many small ones.
|
|
||||||
|
|
||||||
let first_byte = if data.len() - last_read_size < MIN_AUDIO_FRAME_LEN {
|
|
||||||
MIN_AUDIO_FRAME_LEN + 1
|
|
||||||
} else {
|
|
||||||
data.len() - last_read_size + MIN_AUDIO_FRAME_LEN + 1
|
|
||||||
};
|
|
||||||
|
|
||||||
// `i` is the index of the first byte *after* the sync sequence.
|
|
||||||
//
|
|
||||||
// This may seem odd, but it makes the odd edge case easier to handle:
|
|
||||||
// If we instead have `i` be the index of the first byte *of* the frame sequence,
|
|
||||||
// dealing with the case where `data` contained half the sync sequence before
|
|
||||||
// reading is tricky.
|
|
||||||
for i in first_byte..data.len() {
|
|
||||||
if data[i - 2] == 0b1111_1111
|
|
||||||
&& data[i - 1] & 0b1111_1100 == 0b1111_1000
|
|
||||||
{
|
|
||||||
// We found another frame sync header. Split at this index.
|
|
||||||
if self.selector.pick_audio {
|
|
||||||
self.output_blocks.push_back(FlacBlock::AudioFrame(
|
|
||||||
FlacAudioFrame::decode(&data[0..i - 2])?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backtrack to the first bit AFTER this new sync sequence
|
|
||||||
buf.seek(std::io::SeekFrom::Current(
|
|
||||||
-i64::try_from(data.len() - i)
|
|
||||||
.expect("seek offset does not fit into i64"),
|
|
||||||
))
|
|
||||||
.map_err(FlacDecodeError::from)?;
|
|
||||||
|
|
||||||
self.current_block = Some(FlacBlockType::AudioData {
|
|
||||||
data: {
|
|
||||||
let mut v = Vec::with_capacity(MIN_AUDIO_FRAME_LEN);
|
|
||||||
v.extend(&data[i - 2..i]);
|
|
||||||
v
|
|
||||||
},
|
|
||||||
});
|
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish reading data.
|
|
||||||
/// This tells the reader that it has received the entire stream.
|
|
||||||
///
|
|
||||||
/// `finish()` should be called exactly once once we have finished each stream.
|
|
||||||
/// Finishing twice or pushing data to a finished reader results in a panic.
|
|
||||||
pub fn finish(&mut self) -> Result<(), FlacBlockReaderError> {
|
|
||||||
match self.current_block.take() {
|
|
||||||
None => return Err(FlacBlockReaderError::AlreadyFinished),
|
|
||||||
|
|
||||||
Some(FlacBlockType::AudioData { data }) => {
|
|
||||||
// We can't run checks if we don't have enough data.
|
|
||||||
if data.len() <= 2 {
|
|
||||||
return Err(FlacDecodeError::MalformedBlock.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(data[0] == 0b1111_1111 && data[1] & 0b1111_1100 == 0b1111_1000) {
|
|
||||||
return Err(FlacDecodeError::BadSyncBytes.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.selector.pick_audio {
|
|
||||||
self.output_blocks
|
|
||||||
.push_back(FlacBlock::AudioFrame(FlacAudioFrame::decode(&data)?));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_block = None;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other blocks have a known length and
|
|
||||||
// are finished automatically.
|
|
||||||
_ => return Err(FlacDecodeError::MalformedBlock.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use itertools::Itertools;
|
|
||||||
use paste::paste;
|
|
||||||
use rand::Rng;
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::{io::Write, ops::Range, str::FromStr};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::{
|
|
||||||
common::tagtype::TagType,
|
|
||||||
flac::tests::{FlacBlockOutput, FlacTestCase, VorbisCommentTestValue, manifest},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[expect(clippy::unwrap_used)]
|
|
||||||
fn read_file(
|
|
||||||
test_case: &FlacTestCase,
|
|
||||||
fragment_size_range: Option<Range<usize>>,
|
|
||||||
selector: FlacBlockSelector,
|
|
||||||
) -> Result<Vec<FlacBlock>, FlacBlockReaderError> {
|
|
||||||
let file_data = std::fs::read(test_case.get_path()).unwrap();
|
|
||||||
|
|
||||||
// Make sure input file is correct
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&file_data);
|
|
||||||
assert_eq!(
|
|
||||||
test_case.get_in_hash(),
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut reader = FlacBlockReader::new(selector);
|
|
||||||
let mut out_blocks = Vec::new();
|
|
||||||
|
|
||||||
// Push file data to the reader, in parts or as a whole.
|
|
||||||
if let Some(fragment_size_range) = fragment_size_range {
|
|
||||||
let mut head = 0;
|
|
||||||
while head < file_data.len() {
|
|
||||||
let mut frag_size = rand::rng().random_range(fragment_size_range.clone());
|
|
||||||
if head + frag_size > file_data.len() {
|
|
||||||
frag_size = file_data.len() - head;
|
|
||||||
}
|
|
||||||
reader.push_data(&file_data[head..head + frag_size])?;
|
|
||||||
head += frag_size;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reader.push_data(&file_data)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.finish()?;
|
|
||||||
while let Some(b) = reader.pop_block() {
|
|
||||||
out_blocks.push(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(out_blocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::unwrap_used)]
|
|
||||||
fn test_identical(
|
|
||||||
test_case: &FlacTestCase,
|
|
||||||
fragment_size_range: Option<Range<usize>>,
|
|
||||||
) -> Result<(), FlacBlockReaderError> {
|
|
||||||
let out_blocks = read_file(
|
|
||||||
test_case,
|
|
||||||
fragment_size_range,
|
|
||||||
FlacBlockSelector {
|
|
||||||
pick_streaminfo: true,
|
|
||||||
pick_padding: true,
|
|
||||||
pick_application: true,
|
|
||||||
pick_seektable: true,
|
|
||||||
pick_vorbiscomment: true,
|
|
||||||
pick_cuesheet: true,
|
|
||||||
pick_picture: true,
|
|
||||||
pick_audio: true,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
out.write_all(&[0x66, 0x4C, 0x61, 0x43]).unwrap();
|
|
||||||
|
|
||||||
for i in 0..out_blocks.len() {
|
|
||||||
let b = &out_blocks[i];
|
|
||||||
let is_last = if i == out_blocks.len() - 1 {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
!matches!(b, FlacBlock::AudioFrame(_))
|
|
||||||
&& matches!(&out_blocks[i + 1], FlacBlock::AudioFrame(_))
|
|
||||||
};
|
|
||||||
|
|
||||||
b.encode(is_last, true, &mut out).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&out);
|
|
||||||
let result = hasher.finalize().map(|x| format!("{x:02x}")).join("");
|
|
||||||
assert_eq!(result, test_case.get_in_hash(), "Output hash doesn't match");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_blockread(
|
|
||||||
test_case: &FlacTestCase,
|
|
||||||
fragment_size_range: Option<Range<usize>>,
|
|
||||||
) -> Result<(), FlacBlockReaderError> {
|
|
||||||
let out_blocks = read_file(
|
|
||||||
test_case,
|
|
||||||
fragment_size_range,
|
|
||||||
FlacBlockSelector {
|
|
||||||
pick_streaminfo: true,
|
|
||||||
pick_padding: true,
|
|
||||||
pick_application: true,
|
|
||||||
pick_seektable: true,
|
|
||||||
pick_vorbiscomment: true,
|
|
||||||
pick_cuesheet: true,
|
|
||||||
pick_picture: true,
|
|
||||||
pick_audio: true,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
test_case.get_blocks().unwrap().len(),
|
|
||||||
out_blocks
|
|
||||||
.iter()
|
|
||||||
.filter(|x| !matches!(*x, FlacBlock::AudioFrame(_)))
|
|
||||||
.count(),
|
|
||||||
"Number of blocks didn't match"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut audio_data_hasher = Sha256::new();
|
|
||||||
let mut result_i = 0;
|
|
||||||
|
|
||||||
for b in out_blocks {
|
|
||||||
match b {
|
|
||||||
FlacBlock::Streaminfo(s) => match &test_case.get_blocks().unwrap()[result_i] {
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size,
|
|
||||||
max_block_size,
|
|
||||||
min_frame_size,
|
|
||||||
max_frame_size,
|
|
||||||
sample_rate,
|
|
||||||
channels,
|
|
||||||
bits_per_sample,
|
|
||||||
total_samples,
|
|
||||||
md5_signature,
|
|
||||||
} => {
|
|
||||||
assert_eq!(*min_block_size, s.min_block_size,);
|
|
||||||
assert_eq!(*max_block_size, s.max_block_size);
|
|
||||||
assert_eq!(*min_frame_size, s.min_frame_size);
|
|
||||||
assert_eq!(*max_frame_size, s.max_frame_size);
|
|
||||||
assert_eq!(*sample_rate, s.sample_rate);
|
|
||||||
assert_eq!(*channels, s.channels);
|
|
||||||
assert_eq!(*bits_per_sample, s.bits_per_sample);
|
|
||||||
assert_eq!(*total_samples, s.total_samples);
|
|
||||||
assert_eq!(
|
|
||||||
*md5_signature,
|
|
||||||
s.md5_signature.iter().map(|x| format!("{x:02x}")).join("")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => panic!("Unexpected block type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacBlock::Application(a) => match &test_case.get_blocks().unwrap()[result_i] {
|
|
||||||
FlacBlockOutput::Application {
|
|
||||||
application_id,
|
|
||||||
hash,
|
|
||||||
} => {
|
|
||||||
assert_eq!(
|
|
||||||
*application_id, a.application_id,
|
|
||||||
"Application id doesn't match"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
*hash,
|
|
||||||
{
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
|
|
||||||
hasher.update(&a.data);
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
},
|
|
||||||
"Application content hash doesn't match"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => panic!("Unexpected block type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacBlock::CueSheet(c) => match &test_case.get_blocks().unwrap()[result_i] {
|
|
||||||
FlacBlockOutput::CueSheet { hash } => {
|
|
||||||
assert_eq!(*hash, {
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
|
|
||||||
hasher.update(&c.data);
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => panic!("Unexpected block type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacBlock::Padding(p) => match &test_case.get_blocks().unwrap()[result_i] {
|
|
||||||
FlacBlockOutput::Padding { size } => {
|
|
||||||
assert_eq!(p.size, *size);
|
|
||||||
}
|
|
||||||
_ => panic!("Unexpected block type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacBlock::SeekTable(t) => match &test_case.get_blocks().unwrap()[result_i] {
|
|
||||||
FlacBlockOutput::Seektable { hash } => {
|
|
||||||
assert_eq!(*hash, {
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
|
|
||||||
hasher.update(&t.data);
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => panic!("Unexpected block type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacBlock::Picture(p) => match &test_case.get_blocks().unwrap()[result_i] {
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type,
|
|
||||||
mime,
|
|
||||||
description,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bit_depth,
|
|
||||||
color_count,
|
|
||||||
img_data,
|
|
||||||
} => {
|
|
||||||
assert_eq!(*picture_type, p.picture_type, "{}", test_case.get_name());
|
|
||||||
assert_eq!(*mime, p.mime, "{}", test_case.get_name());
|
|
||||||
assert_eq!(*description, p.description, "{}", test_case.get_name());
|
|
||||||
assert_eq!(*width, p.width, "{}", test_case.get_name());
|
|
||||||
assert_eq!(*height, p.height, "{}", test_case.get_name());
|
|
||||||
assert_eq!(*bit_depth, p.bit_depth, "{}", test_case.get_name());
|
|
||||||
assert_eq!(*color_count, p.color_count, "{}", test_case.get_name());
|
|
||||||
assert_eq!(
|
|
||||||
*img_data,
|
|
||||||
{
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
|
|
||||||
hasher.update(&p.img_data);
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
},
|
|
||||||
"{}",
|
|
||||||
test_case.get_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => panic!("Unexpected block type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacBlock::VorbisComment(v) => match &test_case.get_blocks().unwrap()[result_i] {
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor,
|
|
||||||
comments,
|
|
||||||
pictures,
|
|
||||||
} => {
|
|
||||||
assert_eq!(*vendor, v.comment.vendor, "Comment vendor doesn't match");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
v.comment.pictures.len(),
|
|
||||||
pictures.len(),
|
|
||||||
"Number of pictures doesn't match"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (p, e) in v.comment.pictures.iter().zip(*pictures) {
|
|
||||||
match e {
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type,
|
|
||||||
mime,
|
|
||||||
description,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bit_depth,
|
|
||||||
color_count,
|
|
||||||
img_data,
|
|
||||||
} => {
|
|
||||||
assert_eq!(*picture_type, p.picture_type);
|
|
||||||
assert_eq!(*mime, p.mime);
|
|
||||||
assert_eq!(*description, p.description);
|
|
||||||
assert_eq!(*width, p.width);
|
|
||||||
assert_eq!(*height, p.height);
|
|
||||||
assert_eq!(*bit_depth, p.bit_depth);
|
|
||||||
assert_eq!(*color_count, p.color_count);
|
|
||||||
assert_eq!(*img_data, {
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&p.img_data);
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => panic!("Bad test data: expected only Picture blocks."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match comments {
|
|
||||||
VorbisCommentTestValue::Raw { tags } => {
|
|
||||||
assert_eq!(
|
|
||||||
v.comment.comments.len(),
|
|
||||||
tags.len(),
|
|
||||||
"Number of comments doesn't match"
|
|
||||||
);
|
|
||||||
|
|
||||||
for ((got_tag, got_val), (exp_tag, exp_val)) in
|
|
||||||
v.comment.comments.iter().zip(*tags)
|
|
||||||
{
|
|
||||||
assert_eq!(
|
|
||||||
*got_tag,
|
|
||||||
TagType::from_str(exp_tag).unwrap(),
|
|
||||||
"Tag key doesn't match"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
got_val, exp_val,
|
|
||||||
"Tag value of {exp_tag} doesn't match"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VorbisCommentTestValue::Hash { n_comments, hash } => {
|
|
||||||
assert_eq!(
|
|
||||||
v.comment.comments.len(),
|
|
||||||
*n_comments,
|
|
||||||
"Number of comments doesn't match"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
|
|
||||||
for (got_tag, got_val) in v.comment.comments {
|
|
||||||
hasher.update(format!("{got_tag}={got_val};").as_bytes());
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
&hasher.finalize().map(|x| format!("{x:02x}")).join(""),
|
|
||||||
hash,
|
|
||||||
"Comment hash doesn't match"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("Unexpected block type"),
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacBlock::AudioFrame(data) => {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
data.encode(&mut vec).unwrap();
|
|
||||||
audio_data_hasher.update(&vec);
|
|
||||||
|
|
||||||
if result_i != test_case.get_blocks().unwrap().len() {
|
|
||||||
panic!("There are metadata blocks between audio frames!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't increment result_i
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result_i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check audio data hash
|
|
||||||
assert_eq!(
|
|
||||||
test_case.get_audio_hash().unwrap(),
|
|
||||||
audio_data_hasher
|
|
||||||
.finalize()
|
|
||||||
.map(|x| format!("{x:02x}"))
|
|
||||||
.join("")
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper macros to generate tests
|
|
||||||
macro_rules! gen_tests {
|
|
||||||
( $test_name:ident ) => {
|
|
||||||
paste! {
|
|
||||||
#[test]
|
|
||||||
pub fn [<blockread_small_ $test_name>]() {
|
|
||||||
let manifest = manifest();
|
|
||||||
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
|
||||||
|
|
||||||
match test_case {
|
|
||||||
FlacTestCase::Success { .. } => {
|
|
||||||
for _ in 0..5 {
|
|
||||||
test_blockread(
|
|
||||||
test_case,
|
|
||||||
Some(1..256),
|
|
||||||
).unwrap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacTestCase::Error { check_error, .. } => {
|
|
||||||
let e = test_blockread(test_case, Some(1..256)).unwrap_err();
|
|
||||||
match e {
|
|
||||||
FlacBlockReaderError::DecodeError(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
|
||||||
_ => panic!("Unexpected error {e:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn [<identical_small_ $test_name>]() {
|
|
||||||
let manifest = manifest();
|
|
||||||
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
|
||||||
|
|
||||||
match test_case {
|
|
||||||
FlacTestCase::Success { .. } => {
|
|
||||||
for _ in 0..5 {
|
|
||||||
test_identical(
|
|
||||||
test_case,
|
|
||||||
Some(1..256),
|
|
||||||
).unwrap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacTestCase::Error { check_error, .. } => {
|
|
||||||
let e = test_identical(test_case, Some(1..256)).unwrap_err();
|
|
||||||
match e {
|
|
||||||
FlacBlockReaderError::DecodeError(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
|
||||||
_ => panic!("Unexpected error {e:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
gen_tests!(custom_01);
|
|
||||||
gen_tests!(custom_02);
|
|
||||||
gen_tests!(custom_03);
|
|
||||||
|
|
||||||
gen_tests!(uncommon_10);
|
|
||||||
|
|
||||||
gen_tests!(faulty_06);
|
|
||||||
gen_tests!(faulty_07);
|
|
||||||
gen_tests!(faulty_10);
|
|
||||||
gen_tests!(faulty_11);
|
|
||||||
|
|
||||||
gen_tests!(subset_45);
|
|
||||||
gen_tests!(subset_46);
|
|
||||||
gen_tests!(subset_47);
|
|
||||||
gen_tests!(subset_48);
|
|
||||||
gen_tests!(subset_49);
|
|
||||||
gen_tests!(subset_50);
|
|
||||||
gen_tests!(subset_51);
|
|
||||||
gen_tests!(subset_52);
|
|
||||||
gen_tests!(subset_53);
|
|
||||||
gen_tests!(subset_54);
|
|
||||||
gen_tests!(subset_55);
|
|
||||||
gen_tests!(subset_56);
|
|
||||||
gen_tests!(subset_57);
|
|
||||||
gen_tests!(subset_58);
|
|
||||||
gen_tests!(subset_59);
|
|
||||||
}
|
|
||||||
@@ -1,943 +0,0 @@
|
|||||||
//! Parse FLAC metadata.
|
|
||||||
|
|
||||||
pub mod blockread;
|
|
||||||
pub mod blocks;
|
|
||||||
pub mod errors;
|
|
||||||
pub mod proc;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
use mime::Mime;
|
|
||||||
|
|
||||||
use super::errors::FlacDecodeError;
|
|
||||||
use crate::common::{picturetype::PictureType, vorbiscomment::VorbisCommentDecodeError};
|
|
||||||
|
|
||||||
/// The value of a vorbis comment.
|
|
||||||
///
|
|
||||||
/// Some files have VERY large comments, and providing them
|
|
||||||
/// explicitly here doesn't make sense.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum VorbisCommentTestValue {
|
|
||||||
/// The comments, in order
|
|
||||||
Raw {
|
|
||||||
tags: &'static [(&'static str, &'static str)],
|
|
||||||
},
|
|
||||||
/// The hash of all comments concatenated together,
|
|
||||||
/// stringified as `{key}={value};`
|
|
||||||
Hash {
|
|
||||||
n_comments: usize,
|
|
||||||
hash: &'static str,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum FlacBlockOutput {
|
|
||||||
Application {
|
|
||||||
application_id: u32,
|
|
||||||
hash: &'static str,
|
|
||||||
},
|
|
||||||
Streaminfo {
|
|
||||||
min_block_size: u32,
|
|
||||||
max_block_size: u32,
|
|
||||||
min_frame_size: u32,
|
|
||||||
max_frame_size: u32,
|
|
||||||
sample_rate: u32,
|
|
||||||
channels: u8,
|
|
||||||
bits_per_sample: u8,
|
|
||||||
total_samples: u128,
|
|
||||||
md5_signature: &'static str,
|
|
||||||
},
|
|
||||||
CueSheet {
|
|
||||||
// Hash of this block's data, without the header.
|
|
||||||
// This is easy to get with
|
|
||||||
//
|
|
||||||
// ```notrust
|
|
||||||
// metaflac \
|
|
||||||
// --list \
|
|
||||||
// --block-number=<n> \
|
|
||||||
// --data-format=binary-headerless \
|
|
||||||
// <file> \
|
|
||||||
// | sha256sum
|
|
||||||
//```
|
|
||||||
hash: &'static str,
|
|
||||||
},
|
|
||||||
Seektable {
|
|
||||||
hash: &'static str,
|
|
||||||
},
|
|
||||||
Padding {
|
|
||||||
size: u32,
|
|
||||||
},
|
|
||||||
Picture {
|
|
||||||
picture_type: PictureType,
|
|
||||||
mime: Mime,
|
|
||||||
description: &'static str,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
bit_depth: u32,
|
|
||||||
color_count: u32,
|
|
||||||
img_data: &'static str,
|
|
||||||
},
|
|
||||||
VorbisComment {
|
|
||||||
vendor: &'static str,
|
|
||||||
comments: VorbisCommentTestValue,
|
|
||||||
pictures: &'static [FlacBlockOutput],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum FlacTestCase {
|
|
||||||
Success {
|
|
||||||
/// This test's name
|
|
||||||
test_name: &'static str,
|
|
||||||
|
|
||||||
/// The file to use for this test
|
|
||||||
file_path: &'static str,
|
|
||||||
|
|
||||||
/// The hash of the input files
|
|
||||||
in_hash: &'static str,
|
|
||||||
|
|
||||||
/// The flac metablocks we expect to find in this file, in order.
|
|
||||||
blocks: Vec<FlacBlockOutput>,
|
|
||||||
|
|
||||||
/// The hash of the audio frames in this file
|
|
||||||
///
|
|
||||||
/// Get this hash by running `metaflac --remove-all --dont-use-padding`,
|
|
||||||
/// then by manually deleting remaining headers in a hex editor
|
|
||||||
/// (Remember that the sync sequence is 0xFF 0xF8)
|
|
||||||
audio_hash: &'static str,
|
|
||||||
|
|
||||||
/// The hash we should get when we strip this file's tags.
|
|
||||||
///
|
|
||||||
/// A stripped flac file has unmodified STREAMINFO, SEEKTABLE,
|
|
||||||
/// CUESHEET, and audio data blocks; and nothing else (not even padding).
|
|
||||||
///
|
|
||||||
/// Reference implementation:
|
|
||||||
/// ```notrust
|
|
||||||
/// metaflac \
|
|
||||||
/// --remove \
|
|
||||||
/// --block-type=PADDING,APPLICATION,VORBIS_COMMENT,PICTURE \
|
|
||||||
/// --dont-use-padding \
|
|
||||||
/// <file>
|
|
||||||
/// ```
|
|
||||||
stripped_hash: &'static str,
|
|
||||||
},
|
|
||||||
Error {
|
|
||||||
/// This test's name
|
|
||||||
test_name: &'static str,
|
|
||||||
|
|
||||||
/// The file to use for this test
|
|
||||||
file_path: &'static str,
|
|
||||||
|
|
||||||
/// The hash of the input files
|
|
||||||
in_hash: &'static str,
|
|
||||||
|
|
||||||
/// The error we should encounter while reading this file
|
|
||||||
check_error: &'static dyn Fn(&FlacDecodeError) -> bool,
|
|
||||||
|
|
||||||
/// If some, stripping this file's metadata should produce the given hash.
|
|
||||||
/// If none, trying to strip metadata should produce `check_error`
|
|
||||||
stripped_hash: Option<&'static str>,
|
|
||||||
|
|
||||||
/// If some, the following images should be extracted from this file
|
|
||||||
/// If none, trying to strip images should produce `check_error`
|
|
||||||
pictures: Option<Vec<FlacBlockOutput>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlacTestCase {
|
|
||||||
pub fn get_name(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::Error { test_name, .. } | Self::Success { test_name, .. } => test_name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_path(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::Success { file_path, .. } | Self::Error { file_path, .. } => file_path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_in_hash(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::Success { in_hash, .. } | Self::Error { in_hash, .. } => in_hash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_stripped_hash(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
Self::Success { stripped_hash, .. } => Some(stripped_hash),
|
|
||||||
Self::Error { stripped_hash, .. } => *stripped_hash,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_audio_hash(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
Self::Success { audio_hash, .. } => Some(audio_hash),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_blocks(&self) -> Option<&[FlacBlockOutput]> {
|
|
||||||
match self {
|
|
||||||
Self::Success { blocks, .. } => Some(blocks),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_pictures(&self) -> Option<Vec<FlacBlockOutput>> {
|
|
||||||
match self {
|
|
||||||
Self::Success { blocks, .. } => {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for b in blocks {
|
|
||||||
match b {
|
|
||||||
FlacBlockOutput::Picture { .. } => out.push(b.clone()),
|
|
||||||
FlacBlockOutput::VorbisComment { pictures, .. } => {
|
|
||||||
for p in *pictures {
|
|
||||||
out.push(p.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::Error { pictures, .. } => {
|
|
||||||
pictures.as_ref().map(|x| x.iter().cloned().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of test files and their expected output
|
|
||||||
#[expect(clippy::unwrap_used)]
|
|
||||||
pub fn manifest() -> [FlacTestCase; 23] {
|
|
||||||
[
|
|
||||||
FlacTestCase::Error {
|
|
||||||
test_name: "uncommon_10",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_uncommon/10 - file starting at frame header.flac"
|
|
||||||
),
|
|
||||||
in_hash: "d95f63e8101320f5ac7ffe249bc429a209eb0e10996a987301eaa63386a8faa1",
|
|
||||||
check_error: &|x| matches!(x, FlacDecodeError::BadMagicBytes),
|
|
||||||
stripped_hash: None,
|
|
||||||
pictures: None,
|
|
||||||
},
|
|
||||||
FlacTestCase::Error {
|
|
||||||
test_name: "faulty_06",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_faulty/06 - missing streaminfo metadata block.flac"
|
|
||||||
),
|
|
||||||
in_hash: "53aed5e7fde7a652b82ba06a8382b2612b02ebbde7b0d2016276644d17cc76cd",
|
|
||||||
check_error: &|x| matches!(x, FlacDecodeError::BadFirstBlock),
|
|
||||||
stripped_hash: None,
|
|
||||||
pictures: None,
|
|
||||||
},
|
|
||||||
FlacTestCase::Error {
|
|
||||||
test_name: "faulty_07",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_faulty/07 - other metadata blocks preceding streaminfo metadata block.flac"
|
|
||||||
),
|
|
||||||
in_hash: "6d46725991ba5da477187fde7709ea201c399d00027257c365d7301226d851ea",
|
|
||||||
check_error: &|x| matches!(x, FlacDecodeError::BadFirstBlock),
|
|
||||||
stripped_hash: None,
|
|
||||||
pictures: None,
|
|
||||||
},
|
|
||||||
FlacTestCase::Error {
|
|
||||||
test_name: "faulty_10",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_faulty/10 - invalid vorbis comment metadata block.flac"
|
|
||||||
),
|
|
||||||
in_hash: "c79b0514a61634035a5653c5493797bbd1fcc78982116e4d429630e9e462d29b",
|
|
||||||
check_error: &|x| {
|
|
||||||
matches!(
|
|
||||||
x,
|
|
||||||
FlacDecodeError::VorbisComment(VorbisCommentDecodeError::MalformedData)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
// This file's vorbis comment is invalid, but that shouldn't stop us from removing it.
|
|
||||||
// As a general rule, we should NOT encounter an error when stripping invalid blocks.
|
|
||||||
//
|
|
||||||
// We should, however, get errors when we try to strip flac files with invalid *structure*
|
|
||||||
// (For example, the out-of-order streaminfo test in faulty_07).
|
|
||||||
stripped_hash: Some(
|
|
||||||
"4b994f82dc1699a58e2b127058b37374220ee41dc294d4887ac14f056291a1b0",
|
|
||||||
),
|
|
||||||
pictures: None,
|
|
||||||
},
|
|
||||||
FlacTestCase::Error {
|
|
||||||
test_name: "faulty_11",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_faulty/11 - incorrect metadata block length.flac"
|
|
||||||
),
|
|
||||||
in_hash: "3732151ba8c4e66a785165aa75a444aad814c16807ddc97b793811376acacfd6",
|
|
||||||
check_error: &|x| matches!(x, FlacDecodeError::BadMetablockType(127)),
|
|
||||||
stripped_hash: None,
|
|
||||||
pictures: None,
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_45",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/45 - no total number of samples set.flac"
|
|
||||||
),
|
|
||||||
in_hash: "336a18eb7a78f7fc0ab34980348e2895bc3f82db440a2430d9f92e996f889f9a",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 907,
|
|
||||||
max_frame_size: 8053,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 0,
|
|
||||||
md5_signature: "c41ae3b82c35d8f5c3dab1729f948fde",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "3fb3482ebc1724559bdd57f34de472458563d78a676029614e76e32b5d2b8816",
|
|
||||||
stripped_hash: "31631ac227ebe2689bac7caa1fa964b47e71a9f1c9c583a04ea8ebd9371508d0",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_46",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/46 - no min-max framesize set.flac"
|
|
||||||
),
|
|
||||||
in_hash: "9dc39732ce17815832790901b768bb50cd5ff0cd21b28a123c1cabc16ed776cc",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 0,
|
|
||||||
max_frame_size: 0,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 282866,
|
|
||||||
md5_signature: "fd131e6ebc75251ed83f8f4c07df36a4",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "a1eed422462b386a932b9eb3dff3aea3687b41eca919624fb574aadb7eb50040",
|
|
||||||
stripped_hash: "9e57cd77f285fc31f87fa4e3a31ab8395d68d5482e174c8e0d0bba9a0c20ba27",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_47",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/47 - only STREAMINFO.flac"
|
|
||||||
),
|
|
||||||
in_hash: "9a62c79f634849e74cb2183f9e3a9bd284f51e2591c553008d3e6449967eef85",
|
|
||||||
blocks: vec![FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 4747,
|
|
||||||
max_frame_size: 7034,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 232608,
|
|
||||||
md5_signature: "bba30c5f70789910e404b7ac727c3853",
|
|
||||||
}],
|
|
||||||
audio_hash: "5ee1450058254087f58c91baf0f70d14bde8782cf2dc23c741272177fe0fce6e",
|
|
||||||
stripped_hash: "9a62c79f634849e74cb2183f9e3a9bd284f51e2591c553008d3e6449967eef85",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_48",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/48 - Extremely large SEEKTABLE.flac"
|
|
||||||
),
|
|
||||||
in_hash: "4417aca6b5f90971c50c28766d2f32b3acaa7f9f9667bd313336242dae8b2531",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 2445,
|
|
||||||
max_frame_size: 7364,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 259884,
|
|
||||||
md5_signature: "97a0574290237563fbaa788ad77d2cdf",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Seektable {
|
|
||||||
hash: "21ca2184ae22fe26b690fd7cbd8d25fcde1d830ff6e5796ced4107bab219d7c0",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "c2d691f2c4c986fe3cd5fd7864d9ba9ce6dd68a4ffc670447f008434b13102c2",
|
|
||||||
stripped_hash: "abc9a0c40a29c896bc6e1cc0b374db1c8e157af716a5a3c43b7db1591a74c4e8",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_49",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/49 - Extremely large PADDING.flac",
|
|
||||||
),
|
|
||||||
in_hash: "7bc44fa2754536279fde4f8fb31d824f43b8d0b3f93d27d055d209682914f20e",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 1353,
|
|
||||||
max_frame_size: 7117,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 258939,
|
|
||||||
md5_signature: "6e78f221caaaa5d570a53f1714d84ded",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Padding { size: 16777215 },
|
|
||||||
],
|
|
||||||
audio_hash: "5007be7109b28b0149d1b929d2a0e93a087381bd3e68cf2a3ef78ea265ea20c3",
|
|
||||||
stripped_hash: "a2283bbacbc4905ad3df1bf9f43a0ea7aa65cf69523d84a7dd8eb54553cc437e",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_50",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/50 - Extremely large PICTURE.flac"
|
|
||||||
),
|
|
||||||
in_hash: "1f04f237d74836104993a8072d4223e84a5d3bd76fbc44555c221c7e69a23594",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 5099,
|
|
||||||
max_frame_size: 7126,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 265617,
|
|
||||||
md5_signature: "82164e4da30ed43b47e6027cef050648",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_JPEG,
|
|
||||||
description: "",
|
|
||||||
width: 3200,
|
|
||||||
height: 2252,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "b78c3a48fde4ebbe8e4090e544caeb8f81ed10020d57cc50b3265f9b338d8563",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "9778b25c5d1f56cfcd418e550baed14f9d6a4baf29489a83ed450fbebb28de8c",
|
|
||||||
stripped_hash: "20df129287d94f9ae5951b296d7f65fcbed92db423ba7db4f0d765f1f0a7e18c",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_51",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/51 - Extremely large VORBISCOMMENT.flac"
|
|
||||||
),
|
|
||||||
in_hash: "033160e8124ed287b0b5d615c94ac4139477e47d6e4059b1c19b7141566f5ef9",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 4531,
|
|
||||||
max_frame_size: 7528,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 289972,
|
|
||||||
md5_signature: "5ff622c88f8dd9bc201a6a541f3890d3",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Hash {
|
|
||||||
n_comments: 39,
|
|
||||||
hash: "01984e9ec0cfad41f27b3b4e84184966f6725ead84b7815bd0b3313549ee4229",
|
|
||||||
},
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "76419865d10eb22a74f020423a4e515e800f0177441676afd0418557c2d76c36",
|
|
||||||
stripped_hash: "c0ca6c6099b5d9ec53d6bb370f339b2b1570055813a6cd3616fac2db83a2185e",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_52",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/52 - Extremely large APPLICATION.flac"
|
|
||||||
),
|
|
||||||
in_hash: "0e45a4f8dbef15cbebdd8dfe690d8ae60e0c6abb596db1270a9161b62a7a3f1c",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 3711,
|
|
||||||
max_frame_size: 7056,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 317876,
|
|
||||||
md5_signature: "eb7140266bc194527488c21ab49bc47b",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Application {
|
|
||||||
application_id: 0x74657374,
|
|
||||||
hash: "cfc0b8969e4ba6bd507999ba89dea2d274df69d94749d6ae3cf117a7780bba09",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "89ad1a5c86a9ef35d33189c81c8a90285a23964a13f8325bf2c02043e8c83d63",
|
|
||||||
stripped_hash: "cc4a0afb95ec9bcde8ee33f13951e494dc4126a9a3a668d79c80ce3c14a3acd9",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_53",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/53 - CUESHEET with very many indexes.flac"
|
|
||||||
),
|
|
||||||
in_hash: "513fad18578f3225fae5de1bda8f700415be6fd8aa1e7af533b5eb796ed2d461",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 2798,
|
|
||||||
max_frame_size: 7408,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 2910025,
|
|
||||||
md5_signature: "d11f3717d628cfe6a90a10facc478340",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Seektable {
|
|
||||||
hash: "18629e1b874cb27e4364da72fb3fec2141eb0618baae4a1cee6ed09562aa00a8",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::CueSheet {
|
|
||||||
hash: "70638a241ca06881a52c0a18258ea2d8946a830137a70479c49746d2a1344bdd",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "e993070f2080f2c598be1d61d208e9187a55ddea4be1d2ed1f8043e7c03e97a5",
|
|
||||||
stripped_hash: "57c5b945e14c6fcd06916d6a57e5b036d67ff35757893c24ed872007aabbcf4b",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_54",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/54 - 1000x repeating VORBISCOMMENT.flac"
|
|
||||||
),
|
|
||||||
in_hash: "b68dc6644784fac35aa07581be8603a360d1697e07a2265d7eb24001936fd247",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 1694,
|
|
||||||
max_frame_size: 7145,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 433151,
|
|
||||||
md5_signature: "1d950e92b357dedbc5290a7f2210a2ef",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Hash {
|
|
||||||
n_comments: 20000,
|
|
||||||
hash: "433f34ae532d265835153139b1db79352a26ad0d3b03e2f1a1b88ada34abfc77",
|
|
||||||
},
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "4721b784058410c6263f73680079e9a71aee914c499afcf5580c121fce00e874",
|
|
||||||
stripped_hash: "5c8b92b83c0fa17821add38263fa323d1c66cfd2ee57aca054b50bd05b9df5c2",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_55",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/55 - file 48-53 combined.flac"
|
|
||||||
),
|
|
||||||
in_hash: "a756b460df79b7cc492223f80cda570e4511f2024e5fa0c4d505ba51b86191f6",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 3103,
|
|
||||||
max_frame_size: 11306,
|
|
||||||
sample_rate: 44100,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 2646000,
|
|
||||||
md5_signature: "2c78978cbbff11daac296fee97c3e061",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Seektable {
|
|
||||||
hash: "58dfa7bac4974edf1956b068f5aa72d1fbd9301c36a3085a8a57b9db11a2dbf0",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.3 20190804",
|
|
||||||
comments: VorbisCommentTestValue::Hash {
|
|
||||||
n_comments: 40036,
|
|
||||||
hash: "66cac9f9c42f48128e9fc24e1e96b46a06e885d233155556da16d9b05a23486e",
|
|
||||||
},
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::CueSheet {
|
|
||||||
hash: "db11916c8f5f39648256f93f202e00ff8d73d7d96b62f749b4c77cf3ea744f90",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Application {
|
|
||||||
application_id: 0x74657374,
|
|
||||||
hash: "6088a557a1bad7bfa5ebf79a324669fbf4fa2f8e708f5487305dfc5b2ff2249a",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_JPEG,
|
|
||||||
description: "",
|
|
||||||
width: 3200,
|
|
||||||
height: 2252,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "b78c3a48fde4ebbe8e4090e544caeb8f81ed10020d57cc50b3265f9b338d8563",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Padding { size: 16777215 },
|
|
||||||
],
|
|
||||||
audio_hash: "f1285b77cec7fa9a0979033244489a9d06b8515b2158e9270087a65a4007084d",
|
|
||||||
stripped_hash: "401038fce06aff5ebdc7a5f2fc01fa491cbf32d5da9ec99086e414b2da3f8449",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_56",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/56 - JPG PICTURE.flac"
|
|
||||||
),
|
|
||||||
in_hash: "5cebe7a3710cf8924bd2913854e9ca60b4cd53cfee5a3af0c3c73fddc1888963",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 3014,
|
|
||||||
max_frame_size: 7219,
|
|
||||||
sample_rate: 44100,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 220026,
|
|
||||||
md5_signature: "5b0e898d9c2626d0c28684f5a586813f",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_JPEG,
|
|
||||||
description: "",
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "7a3ed658f80f433eee3914fff451ea0312807de0af709e37cc6a4f3f6e8a47c6",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "ccfe90b0f15cd9662f7a18f40cd4c347538cf8897a08228e75351206f7804573",
|
|
||||||
stripped_hash: "31a38d59db2010790b7abf65ec0cc03f2bbe1fed5952bc72bee4ca4d0c92e79f",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_57",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/57 - PNG PICTURE.flac"
|
|
||||||
),
|
|
||||||
in_hash: "c6abff7f8bb63c2821bd21dd9052c543f10ba0be878e83cb419c248f14f72697",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 463,
|
|
||||||
max_frame_size: 6770,
|
|
||||||
sample_rate: 44100,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 221623,
|
|
||||||
md5_signature: "ad16957bcf8d5a3ec8caf261e43d5ff7",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_PNG,
|
|
||||||
description: "",
|
|
||||||
width: 960,
|
|
||||||
height: 540,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "39bf9981613ac2f35d253c0c21b76a48abba7792c27da5dbf23e6021e2e6673f",
|
|
||||||
stripped_hash: "3328201dd56289b6c81fa90ff26cb57fa9385cb0db197e89eaaa83efd79a58b1",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_58",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/58 - GIF PICTURE.flac"
|
|
||||||
),
|
|
||||||
in_hash: "7c2b1a963a665847167a7275f9924f65baeb85c21726c218f61bf3f803f301c8",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 2853,
|
|
||||||
max_frame_size: 6683,
|
|
||||||
sample_rate: 44100,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 219826,
|
|
||||||
md5_signature: "7c1810602a7db96d7a48022ac4aa495c",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_GIF,
|
|
||||||
description: "",
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 32,
|
|
||||||
img_data: "e33cccc1d799eb2bb618f47be7099cf02796df5519f3f0e1cc258606cf6e8bb1",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "30e3292e9f56cf88658eeadfdec8ad3a440690ce6d813e1b3374f60518c8e0ae",
|
|
||||||
stripped_hash: "4cd771e27870e2a586000f5b369e0426183a521b61212302a2f5802b046910b2",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "subset_59",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_subset/59 - AVIF PICTURE.flac"
|
|
||||||
),
|
|
||||||
in_hash: "7395d02bf8d9533dc554cce02dee9de98c77f8731a45f62d0a243bd0d6f9a45c",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 153,
|
|
||||||
max_frame_size: 7041,
|
|
||||||
sample_rate: 44100,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 221423,
|
|
||||||
md5_signature: "d354246011ca204159c06f52cad5f634",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: Mime::from_str("image/avif").unwrap(),
|
|
||||||
description: "",
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "a431123040c74f75096237f20544a7fb56b4eb71ddea62efa700b0a016f5b2fc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "b208c73d274e65b27232bfffbfcbcf4805ee3cbc9cfbf7d2104db8f53370273b",
|
|
||||||
stripped_hash: "d5215e16c6b978fc2c3e6809e1e78981497cb8514df297c5169f3b4a28fd875c",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "custom_01",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_custom/01 - many images.flac"
|
|
||||||
),
|
|
||||||
in_hash: "8a5df37488866cd91ac16773e549ef4e3a85d9f88a0d9d345f174807bb536b96",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 5099,
|
|
||||||
max_frame_size: 7126,
|
|
||||||
sample_rate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 265617,
|
|
||||||
md5_signature: "82164e4da30ed43b47e6027cef050648",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_PNG,
|
|
||||||
description: "",
|
|
||||||
width: 960,
|
|
||||||
height: 540,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_JPEG,
|
|
||||||
description: "",
|
|
||||||
width: 3200,
|
|
||||||
height: 2252,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "b78c3a48fde4ebbe8e4090e544caeb8f81ed10020d57cc50b3265f9b338d8563",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::ABrightColoredFish,
|
|
||||||
mime: mime::IMAGE_JPEG,
|
|
||||||
description: "lorem",
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "7a3ed658f80f433eee3914fff451ea0312807de0af709e37cc6a4f3f6e8a47c6",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::OtherFileIcon,
|
|
||||||
mime: mime::IMAGE_PNG,
|
|
||||||
description: "ipsum",
|
|
||||||
width: 960,
|
|
||||||
height: 540,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::Lyricist,
|
|
||||||
mime: mime::IMAGE_GIF,
|
|
||||||
description: "dolor",
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 32,
|
|
||||||
img_data: "e33cccc1d799eb2bb618f47be7099cf02796df5519f3f0e1cc258606cf6e8bb1",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::BackCover,
|
|
||||||
mime: Mime::from_str("image/avif").unwrap(),
|
|
||||||
description: "est",
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "a431123040c74f75096237f20544a7fb56b4eb71ddea62efa700b0a016f5b2fc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "9778b25c5d1f56cfcd418e550baed14f9d6a4baf29489a83ed450fbebb28de8c",
|
|
||||||
stripped_hash: "20df129287d94f9ae5951b296d7f65fcbed92db423ba7db4f0d765f1f0a7e18c",
|
|
||||||
},
|
|
||||||
FlacTestCase::Success {
|
|
||||||
test_name: "custom_02",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_custom/02 - picture in vorbis comment.flac"
|
|
||||||
),
|
|
||||||
in_hash: "f6bb1a726fe6a3e25a4337d36e29fdced8ff01a46d627b7c2e1988c88f461f8c",
|
|
||||||
blocks: vec![
|
|
||||||
FlacBlockOutput::Streaminfo {
|
|
||||||
min_block_size: 4096,
|
|
||||||
max_block_size: 4096,
|
|
||||||
min_frame_size: 463,
|
|
||||||
max_frame_size: 6770,
|
|
||||||
sample_rate: 44100,
|
|
||||||
channels: 2,
|
|
||||||
bits_per_sample: 16,
|
|
||||||
total_samples: 221623,
|
|
||||||
md5_signature: "ad16957bcf8d5a3ec8caf261e43d5ff7",
|
|
||||||
},
|
|
||||||
FlacBlockOutput::VorbisComment {
|
|
||||||
vendor: "reference libFLAC 1.3.2 20170101",
|
|
||||||
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
|
||||||
pictures: &[FlacBlockOutput::Picture {
|
|
||||||
picture_type: PictureType::FrontCover,
|
|
||||||
mime: mime::IMAGE_PNG,
|
|
||||||
description: "",
|
|
||||||
width: 960,
|
|
||||||
height: 540,
|
|
||||||
bit_depth: 24,
|
|
||||||
color_count: 0,
|
|
||||||
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
audio_hash: "39bf9981613ac2f35d253c0c21b76a48abba7792c27da5dbf23e6021e2e6673f",
|
|
||||||
stripped_hash: "3328201dd56289b6c81fa90ff26cb57fa9385cb0db197e89eaaa83efd79a58b1",
|
|
||||||
},
|
|
||||||
FlacTestCase::Error {
|
|
||||||
test_name: "custom_03",
|
|
||||||
file_path: concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/tests/files/flac_custom/03 - faulty picture in vorbis comment.flac"
|
|
||||||
),
|
|
||||||
in_hash: "7177f0ae4f04a563292be286ec05967f81ab16eb0a28b70fc07a1e47da9cafd0",
|
|
||||||
check_error: &|x| {
|
|
||||||
matches!(
|
|
||||||
x,
|
|
||||||
FlacDecodeError::VorbisComment(VorbisCommentDecodeError::MalformedPicture)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
stripped_hash: Some(
|
|
||||||
"3328201dd56289b6c81fa90ff26cb57fa9385cb0db197e89eaaa83efd79a58b1",
|
|
||||||
),
|
|
||||||
pictures: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn manifest_sanity_check() {
|
|
||||||
assert!(manifest().iter().map(|x| x.get_name()).all_unique());
|
|
||||||
assert!(manifest().iter().map(|x| x.get_path()).all_unique());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
//! A flac processor that strips metadata blocks from flac files
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use super::super::{
|
|
||||||
blockread::{FlacBlock, FlacBlockReader, FlacBlockReaderError, FlacBlockSelector},
|
|
||||||
errors::FlacEncodeError,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Removes all metadata from a flac file
|
|
||||||
pub struct FlacMetaStrip {
|
|
||||||
reader: FlacBlockReader,
|
|
||||||
|
|
||||||
/// The last block that `reader` produced.
|
|
||||||
///
|
|
||||||
/// We need this to detect the last metadata block
|
|
||||||
/// that `reader` produces.
|
|
||||||
last_block: Option<FlacBlock>,
|
|
||||||
|
|
||||||
/// Set to `false` on the first call to `self.write_data`.
|
|
||||||
/// Used to write fLaC magic bytes.
|
|
||||||
first_write: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlacMetaStrip {
|
|
||||||
/// Make a new [`FlacMetaStrip`]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
first_write: true,
|
|
||||||
last_block: None,
|
|
||||||
reader: FlacBlockReader::new(FlacBlockSelector {
|
|
||||||
pick_streaminfo: true,
|
|
||||||
pick_padding: false,
|
|
||||||
pick_application: false,
|
|
||||||
pick_seektable: true,
|
|
||||||
pick_vorbiscomment: false,
|
|
||||||
pick_cuesheet: true,
|
|
||||||
pick_picture: false,
|
|
||||||
pick_audio: true,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push some data to this flac processor
|
|
||||||
pub fn push_data(&mut self, buf: &[u8]) -> Result<(), FlacBlockReaderError> {
|
|
||||||
self.reader.push_data(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call after sending the entire flac file to this reader
|
|
||||||
pub fn finish(&mut self) -> Result<(), FlacBlockReaderError> {
|
|
||||||
self.reader.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If true, we have received all the data we need
|
|
||||||
pub fn is_done(&mut self) -> bool {
|
|
||||||
self.reader.is_done()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If false, this reader has sent all its data.
|
|
||||||
///
|
|
||||||
/// Note that `read_data` may write zero bytes if this method returns `true`.
|
|
||||||
/// If `has_data` is false, we don't AND WON'T have data. If we're waiting
|
|
||||||
/// for data, this is `true`.
|
|
||||||
pub fn has_data(&self) -> bool {
|
|
||||||
self.last_block.is_some() || !self.reader.is_done() || self.reader.has_block()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write available data from this struct into `target`
|
|
||||||
pub fn read_data(&mut self, target: &mut impl Write) -> Result<(), FlacEncodeError> {
|
|
||||||
if self.first_write {
|
|
||||||
target.write_all(&[0x66, 0x4C, 0x61, 0x43])?;
|
|
||||||
self.first_write = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(block) = self.reader.pop_block() {
|
|
||||||
if let Some(last_block) = self.last_block.take() {
|
|
||||||
last_block.encode(
|
|
||||||
// The last metadata block is the only one followed by an audio frame
|
|
||||||
!matches!(last_block, FlacBlock::AudioFrame(_))
|
|
||||||
&& matches!(block, FlacBlock::AudioFrame(_)),
|
|
||||||
true,
|
|
||||||
target,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
self.last_block = Some(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't need to store audioframes in our last_block buffer,
|
|
||||||
// since they do not have an `is_last` flag.
|
|
||||||
if matches!(self.last_block, Some(FlacBlock::AudioFrame(_))) {
|
|
||||||
#[expect(clippy::expect_used)]
|
|
||||||
let x = self
|
|
||||||
.last_block
|
|
||||||
.take()
|
|
||||||
.expect("last_block is Some(AudioFrame), just matched");
|
|
||||||
x.encode(false, true, target)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use paste::paste;
|
|
||||||
use rand::Rng;
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
|
|
||||||
use crate::flac::{
|
|
||||||
blockread::FlacBlockReaderError, proc::metastrip::FlacMetaStrip, tests::FlacTestCase,
|
|
||||||
tests::manifest,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[expect(clippy::unwrap_used)]
|
|
||||||
fn test_strip(
|
|
||||||
test_case: &FlacTestCase,
|
|
||||||
fragment_size_range: Option<std::ops::Range<usize>>,
|
|
||||||
) -> Result<(), FlacBlockReaderError> {
|
|
||||||
let file_data = std::fs::read(test_case.get_path()).unwrap();
|
|
||||||
|
|
||||||
// Make sure input file is correct
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&file_data);
|
|
||||||
assert_eq!(
|
|
||||||
test_case.get_in_hash(),
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut strip = FlacMetaStrip::new();
|
|
||||||
|
|
||||||
// Push file data to the reader, in parts or as a whole.
|
|
||||||
if let Some(fragment_size_range) = fragment_size_range {
|
|
||||||
let mut head = 0;
|
|
||||||
while head < file_data.len() {
|
|
||||||
let mut frag_size = rand::rng().random_range(fragment_size_range.clone());
|
|
||||||
if head + frag_size > file_data.len() {
|
|
||||||
frag_size = file_data.len() - head;
|
|
||||||
}
|
|
||||||
strip.push_data(&file_data[head..head + frag_size])?;
|
|
||||||
head += frag_size;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
strip.push_data(&file_data)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
strip.finish()?;
|
|
||||||
|
|
||||||
let mut out_data = Vec::new();
|
|
||||||
strip.read_data(&mut out_data).unwrap();
|
|
||||||
assert!(!strip.has_data());
|
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&out_data);
|
|
||||||
let result = hasher.finalize().map(|x| format!("{x:02x}")).join("");
|
|
||||||
assert_eq!(
|
|
||||||
result,
|
|
||||||
test_case.get_stripped_hash().unwrap(),
|
|
||||||
"Stripped FLAC hash doesn't match"
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! gen_tests {
|
|
||||||
( $test_name:ident ) => {
|
|
||||||
paste! {
|
|
||||||
#[test]
|
|
||||||
pub fn [<strip_ $test_name>]() {
|
|
||||||
let manifest =manifest();
|
|
||||||
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
|
||||||
match test_case {
|
|
||||||
FlacTestCase::Error { stripped_hash: Some(_), .. } |
|
|
||||||
FlacTestCase::Success { .. } => {
|
|
||||||
for _ in 0..5 {
|
|
||||||
test_strip(
|
|
||||||
test_case,
|
|
||||||
Some(5_000..100_000),
|
|
||||||
).unwrap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacTestCase::Error { check_error, .. } => {
|
|
||||||
let e = test_strip(test_case, Some(5_000..100_000)).unwrap_err();
|
|
||||||
match e {
|
|
||||||
FlacBlockReaderError::DecodeError(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
|
||||||
_ => panic!("Unexpected error {e:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
gen_tests!(custom_01);
|
|
||||||
gen_tests!(custom_02);
|
|
||||||
gen_tests!(custom_03);
|
|
||||||
|
|
||||||
gen_tests!(uncommon_10);
|
|
||||||
|
|
||||||
gen_tests!(faulty_06);
|
|
||||||
gen_tests!(faulty_07);
|
|
||||||
gen_tests!(faulty_10);
|
|
||||||
gen_tests!(faulty_11);
|
|
||||||
|
|
||||||
gen_tests!(subset_45);
|
|
||||||
gen_tests!(subset_47);
|
|
||||||
gen_tests!(subset_54);
|
|
||||||
gen_tests!(subset_55);
|
|
||||||
gen_tests!(subset_57);
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//! Flac processors. These are well-tested wrappers around [`crate::flac::blockread::FlacBlockReader`]
|
|
||||||
//! that are specialized for specific tasks.
|
|
||||||
|
|
||||||
pub mod metastrip;
|
|
||||||
pub mod pictures;
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
//! A flac processor that finds all images inside a flac file
|
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use super::super::{
|
|
||||||
blockread::{FlacBlock, FlacBlockReader, FlacBlockReaderError, FlacBlockSelector},
|
|
||||||
blocks::FlacPictureBlock,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Find all pictures in a flac file,
|
|
||||||
/// in both picture metablocks and vorbis comments.
|
|
||||||
pub struct FlacPictureReader {
|
|
||||||
reader: FlacBlockReader,
|
|
||||||
pictures: VecDeque<FlacPictureBlock>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FlacPictureReader {
|
|
||||||
/// Make a new [`FlacMetaStrip`]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
pictures: VecDeque::new(),
|
|
||||||
reader: FlacBlockReader::new(FlacBlockSelector {
|
|
||||||
pick_streaminfo: false,
|
|
||||||
pick_padding: false,
|
|
||||||
pick_application: false,
|
|
||||||
pick_seektable: false,
|
|
||||||
pick_vorbiscomment: true,
|
|
||||||
pick_cuesheet: false,
|
|
||||||
pick_picture: true,
|
|
||||||
pick_audio: false,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push some data to this flac processor
|
|
||||||
pub fn push_data(&mut self, buf: &[u8]) -> Result<(), FlacBlockReaderError> {
|
|
||||||
self.reader.push_data(buf)?;
|
|
||||||
|
|
||||||
while let Some(b) = self.reader.pop_block() {
|
|
||||||
match b {
|
|
||||||
FlacBlock::Picture(p) => self.pictures.push_back(p),
|
|
||||||
|
|
||||||
FlacBlock::VorbisComment(c) => {
|
|
||||||
for p in c.comment.pictures {
|
|
||||||
self.pictures.push_back(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call after sending the entire flac file to this reader
|
|
||||||
pub fn finish(&mut self) -> Result<(), FlacBlockReaderError> {
|
|
||||||
self.reader.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If true, we have received all the data we need
|
|
||||||
pub fn is_done(&mut self) -> bool {
|
|
||||||
self.reader.is_done()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If false, this reader has sent all its data.
|
|
||||||
///
|
|
||||||
/// Note that `read_data` may write zero bytes if this method returns `true`.
|
|
||||||
/// If `has_data` is false, we don't AND WON'T have data. If we're waiting
|
|
||||||
/// for data, this is `true`.
|
|
||||||
pub fn has_data(&self) -> bool {
|
|
||||||
!self.reader.is_done() || self.reader.has_block() || !self.pictures.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pop the next picture we read from this file, if any.
|
|
||||||
pub fn pop_picture(&mut self) -> Option<FlacPictureBlock> {
|
|
||||||
self.pictures.pop_front()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use paste::paste;
|
|
||||||
use rand::Rng;
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
|
|
||||||
use crate::flac::{
|
|
||||||
blockread::FlacBlockReaderError,
|
|
||||||
proc::pictures::FlacPictureReader,
|
|
||||||
tests::{FlacBlockOutput, FlacTestCase, manifest},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[expect(clippy::unwrap_used)]
|
|
||||||
fn test_pictures(
|
|
||||||
test_case: &FlacTestCase,
|
|
||||||
fragment_size_range: Option<std::ops::Range<usize>>,
|
|
||||||
) -> Result<(), FlacBlockReaderError> {
|
|
||||||
let file_data = std::fs::read(test_case.get_path()).unwrap();
|
|
||||||
|
|
||||||
// Make sure input file is correct
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&file_data);
|
|
||||||
assert_eq!(
|
|
||||||
test_case.get_in_hash(),
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut pic = FlacPictureReader::new();
|
|
||||||
|
|
||||||
// Push file data to the reader, in parts or as a whole.
|
|
||||||
if let Some(fragment_size_range) = fragment_size_range {
|
|
||||||
let mut head = 0;
|
|
||||||
while head < file_data.len() {
|
|
||||||
let mut frag_size = rand::rng().random_range(fragment_size_range.clone());
|
|
||||||
if head + frag_size > file_data.len() {
|
|
||||||
frag_size = file_data.len() - head;
|
|
||||||
}
|
|
||||||
pic.push_data(&file_data[head..head + frag_size])?;
|
|
||||||
head += frag_size;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pic.push_data(&file_data)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
pic.finish()?;
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
while let Some(p) = pic.pop_picture() {
|
|
||||||
out.push(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
let out_pictures = test_case.get_pictures().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
out.len(),
|
|
||||||
out_pictures.len(),
|
|
||||||
"Unexpected number of pictures"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (got, expected) in out.iter().zip(out_pictures) {
|
|
||||||
let (picture_type, mime, description, width, height, bit_depth, color_count, img_data) =
|
|
||||||
match expected {
|
|
||||||
FlacBlockOutput::Picture {
|
|
||||||
picture_type,
|
|
||||||
mime,
|
|
||||||
description,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bit_depth,
|
|
||||||
color_count,
|
|
||||||
img_data,
|
|
||||||
} => (
|
|
||||||
picture_type,
|
|
||||||
mime,
|
|
||||||
description,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bit_depth,
|
|
||||||
color_count,
|
|
||||||
img_data,
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(picture_type, got.picture_type, "{}", test_case.get_name());
|
|
||||||
assert_eq!(mime, got.mime, "{}", test_case.get_name());
|
|
||||||
assert_eq!(*description, got.description, "{}", test_case.get_name());
|
|
||||||
assert_eq!(width, got.width, "{}", test_case.get_name());
|
|
||||||
assert_eq!(height, got.height, "{}", test_case.get_name());
|
|
||||||
assert_eq!(bit_depth, got.bit_depth, "{}", test_case.get_name());
|
|
||||||
assert_eq!(color_count, got.color_count, "{}", test_case.get_name());
|
|
||||||
assert_eq!(
|
|
||||||
*img_data,
|
|
||||||
{
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&got.img_data);
|
|
||||||
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
|
||||||
},
|
|
||||||
"{}",
|
|
||||||
test_case.get_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! gen_tests {
|
|
||||||
( $test_name:ident ) => {
|
|
||||||
paste! {
|
|
||||||
#[test]
|
|
||||||
pub fn [<pictures_ $test_name>]() {
|
|
||||||
let manifest = manifest();
|
|
||||||
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
|
||||||
match test_case {
|
|
||||||
FlacTestCase::Error { pictures: Some(_), .. } |
|
|
||||||
FlacTestCase::Success { .. } => {
|
|
||||||
for _ in 0..5 {
|
|
||||||
test_pictures(
|
|
||||||
test_case,
|
|
||||||
Some(5_000..100_000),
|
|
||||||
).unwrap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
FlacTestCase::Error { check_error, .. } => {
|
|
||||||
let e = test_pictures(test_case, Some(5_000..100_000)).unwrap_err();
|
|
||||||
match e {
|
|
||||||
FlacBlockReaderError::DecodeError(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
|
||||||
_ => panic!("Unexpected error {e:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
gen_tests!(custom_01);
|
|
||||||
gen_tests!(custom_02);
|
|
||||||
gen_tests!(custom_03);
|
|
||||||
|
|
||||||
gen_tests!(subset_47);
|
|
||||||
gen_tests!(subset_50);
|
|
||||||
gen_tests!(subset_55);
|
|
||||||
gen_tests!(subset_56);
|
|
||||||
gen_tests!(subset_57);
|
|
||||||
gen_tests!(subset_58);
|
|
||||||
gen_tests!(subset_59);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
//! Read and write audio file metadata.
|
|
||||||
pub mod common;
|
|
||||||
pub mod flac;
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
use crate::flac::proc::pictures::FlacPictureReader;
|
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
|
||||||
use tracing::{debug, trace};
|
|
||||||
|
|
||||||
pub struct ExtractCovers {}
|
|
||||||
|
|
||||||
impl NodeBuilder for ExtractCovers {
|
|
||||||
fn build<'ctx>(&self) -> Box<dyn Node<'ctx>> {
|
|
||||||
Box::new(Self {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inputs: "data" - Bytes
|
|
||||||
#[async_trait]
|
|
||||||
impl<'ctx> Node<'ctx> for ExtractCovers {
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
ctx: &CopperContext<'ctx>,
|
|
||||||
this_node: ThisNodeInfo,
|
|
||||||
params: NodeParameters,
|
|
||||||
mut input: BTreeMap<PortName, Option<PipeData>>,
|
|
||||||
) -> Result<BTreeMap<PortName, PipeData>, RunNodeError> {
|
|
||||||
//
|
|
||||||
// Extract parameters
|
|
||||||
//
|
|
||||||
params.err_if_not_empty()?;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Extract arguments
|
|
||||||
//
|
|
||||||
let data = input.remove(&PortName::new("data"));
|
|
||||||
if data.is_none() {
|
|
||||||
return Err(RunNodeError::MissingInput {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some((port, _)) = input.pop_first() {
|
|
||||||
return Err(RunNodeError::UnrecognizedInput { port });
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!(
|
|
||||||
message = "Inputs ready, preparing reader",
|
|
||||||
node_id = ?this_node.id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut reader = match data.unwrap() {
|
|
||||||
None => {
|
|
||||||
return Err(RunNodeError::RequiredInputNull {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PipeData::Blob { source, .. }) => source.build(ctx).await?,
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
return Err(RunNodeError::BadInputType {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Setup is done, extract covers
|
|
||||||
//
|
|
||||||
debug!(
|
|
||||||
message = "Extracting covers",
|
|
||||||
node_id = ?this_node.id
|
|
||||||
);
|
|
||||||
let mut picreader = FlacPictureReader::new();
|
|
||||||
|
|
||||||
while let Some(data) = reader.next_fragment().await? {
|
|
||||||
picreader
|
|
||||||
.push_data(&data)
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
picreader
|
|
||||||
.finish()
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Send the first cover we find
|
|
||||||
//
|
|
||||||
|
|
||||||
let mut output = BTreeMap::new();
|
|
||||||
|
|
||||||
if let Some(picture) = picreader.pop_picture() {
|
|
||||||
debug!(
|
|
||||||
message = "Found a cover, sending",
|
|
||||||
node_id = ?this_node.id,
|
|
||||||
picture = ?picture
|
|
||||||
);
|
|
||||||
|
|
||||||
output.insert(
|
|
||||||
PortName::new("cover_data"),
|
|
||||||
PipeData::Blob {
|
|
||||||
source: BytesProcessorBuilder::new(RawBytesSource::Array {
|
|
||||||
mime: picture.mime.clone(),
|
|
||||||
data: Arc::new(picture.img_data),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
message = "Did not find a cover, sending None",
|
|
||||||
node_id = ?this_node.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
common::tagtype::TagType,
|
|
||||||
flac::blockread::{FlacBlock, FlacBlockReader, FlacBlockSelector},
|
|
||||||
};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use copper_piper::{
|
|
||||||
base::{Node, NodeBuilder, NodeParameterValue, PortName, RunNodeError, ThisNodeInfo},
|
|
||||||
data::PipeData,
|
|
||||||
helpers::NodeParameters,
|
|
||||||
CopperContext,
|
|
||||||
};
|
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
|
||||||
use tracing::{debug, trace};
|
|
||||||
|
|
||||||
/// Extract tags from audio metadata
|
|
||||||
pub struct ExtractTags {}
|
|
||||||
|
|
||||||
impl NodeBuilder for ExtractTags {
|
|
||||||
fn build<'ctx>(&self) -> Box<dyn Node<'ctx>> {
|
|
||||||
Box::new(Self {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inputs: "data" - Bytes
|
|
||||||
// Outputs: variable, depends on tags
|
|
||||||
#[async_trait]
|
|
||||||
impl<'ctx> Node<'ctx> for ExtractTags {
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
ctx: &CopperContext<'ctx>,
|
|
||||||
this_node: ThisNodeInfo,
|
|
||||||
mut params: NodeParameters,
|
|
||||||
mut input: BTreeMap<PortName, Option<PipeData>>,
|
|
||||||
) -> Result<BTreeMap<PortName, PipeData>, RunNodeError> {
|
|
||||||
//
|
|
||||||
// Extract parameters
|
|
||||||
//
|
|
||||||
|
|
||||||
let tags = {
|
|
||||||
let mut tags: BTreeMap<PortName, TagType> = BTreeMap::new();
|
|
||||||
let val = params.pop_val("tags")?;
|
|
||||||
|
|
||||||
match val {
|
|
||||||
NodeParameterValue::List(list) => {
|
|
||||||
for t in list {
|
|
||||||
match t {
|
|
||||||
NodeParameterValue::String(s) => {
|
|
||||||
tags.insert(PortName::new(s.as_str()), s.as_str().into());
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(RunNodeError::BadParameterType {
|
|
||||||
parameter: "tags".into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(RunNodeError::BadParameterType {
|
|
||||||
parameter: "tags".into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tags
|
|
||||||
};
|
|
||||||
|
|
||||||
params.err_if_not_empty()?;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Extract arguments
|
|
||||||
//
|
|
||||||
let data = input.remove(&PortName::new("data"));
|
|
||||||
if data.is_none() {
|
|
||||||
return Err(RunNodeError::MissingInput {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some((port, _)) = input.pop_first() {
|
|
||||||
return Err(RunNodeError::UnrecognizedInput { port });
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!(
|
|
||||||
message = "Inputs ready, preparing reader",
|
|
||||||
node_id = ?this_node.id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut reader = match data.unwrap() {
|
|
||||||
None => {
|
|
||||||
return Err(RunNodeError::RequiredInputNull {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PipeData::Blob { source, .. }) => source.build(ctx).await?,
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
return Err(RunNodeError::BadInputType {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Setup is done, extract tags
|
|
||||||
//
|
|
||||||
debug!(
|
|
||||||
message = "Extracting tags",
|
|
||||||
node_id = ?this_node.id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut block_reader = FlacBlockReader::new(FlacBlockSelector {
|
|
||||||
pick_vorbiscomment: true,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some(data) = reader.next_fragment().await? {
|
|
||||||
block_reader
|
|
||||||
.push_data(&data)
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
block_reader
|
|
||||||
.finish()
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Return tags
|
|
||||||
//
|
|
||||||
|
|
||||||
let mut output = BTreeMap::new();
|
|
||||||
|
|
||||||
while block_reader.has_block() {
|
|
||||||
let b = block_reader.pop_block().unwrap();
|
|
||||||
match b {
|
|
||||||
FlacBlock::VorbisComment(comment) => {
|
|
||||||
for (port, tag_type) in tags.iter() {
|
|
||||||
if let Some((_, tag_value)) =
|
|
||||||
comment.comment.comments.iter().find(|(t, _)| t == tag_type)
|
|
||||||
{
|
|
||||||
let x = output.insert(
|
|
||||||
port.clone(),
|
|
||||||
PipeData::Text {
|
|
||||||
value: tag_value.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Each insertion should be new
|
|
||||||
assert!(x.is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `reader` filters blocks for us
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should only have one comment block
|
|
||||||
assert!(!block_reader.has_block());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
//! Pipeline nodes for processing audio files
|
|
||||||
use copper_piper::base::{NodeDispatcher, RegisterNodeError};
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
mod extractcovers;
|
|
||||||
mod extracttags;
|
|
||||||
mod striptags;
|
|
||||||
|
|
||||||
/// Register all nodes in this module into the given dispatcher
|
|
||||||
pub fn register(dispatcher: &mut NodeDispatcher) -> Result<(), RegisterNodeError> {
|
|
||||||
dispatcher
|
|
||||||
.register_node(
|
|
||||||
"StripTags",
|
|
||||||
BTreeMap::new(),
|
|
||||||
Box::new(striptags::StripTags {}),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
dispatcher
|
|
||||||
.register_node(
|
|
||||||
"ExtractCovers",
|
|
||||||
BTreeMap::new(),
|
|
||||||
Box::new(extractcovers::ExtractCovers {}),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
dispatcher
|
|
||||||
.register_node(
|
|
||||||
"ExtractTags",
|
|
||||||
BTreeMap::new(),
|
|
||||||
Box::new(extracttags::ExtractTags {}),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
//! Strip all tags from an audio file
|
|
||||||
|
|
||||||
use crate::flac::proc::metastrip::FlacMetaStrip;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use copper_piper::{
|
|
||||||
base::{Node, NodeBuilder, NodeId, PortName, RunNodeError, ThisNodeInfo},
|
|
||||||
data::PipeData,
|
|
||||||
helpers::{
|
|
||||||
processor::{StreamProcessor, StreamProcessorBuilder},
|
|
||||||
NodeParameters,
|
|
||||||
},
|
|
||||||
CopperContext,
|
|
||||||
};
|
|
||||||
use copper_util::MimeType;
|
|
||||||
use smartstring::{LazyCompact, SmartString};
|
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
/// Strip all metadata from an audio file
|
|
||||||
pub struct StripTags {}
|
|
||||||
|
|
||||||
impl NodeBuilder for StripTags {
|
|
||||||
fn build<'ctx>(&self) -> Box<dyn Node<'ctx>> {
|
|
||||||
Box::new(Self {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input: "data" - Blob
|
|
||||||
// Output: "out" - Blob
|
|
||||||
#[async_trait]
|
|
||||||
impl<'ctx> Node<'ctx> for StripTags {
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
_ctx: &CopperContext<'ctx>,
|
|
||||||
this_node: ThisNodeInfo,
|
|
||||||
params: NodeParameters,
|
|
||||||
mut input: BTreeMap<PortName, Option<PipeData>>,
|
|
||||||
) -> Result<BTreeMap<PortName, PipeData>, RunNodeError> {
|
|
||||||
//
|
|
||||||
// Extract parameters
|
|
||||||
//
|
|
||||||
params.err_if_not_empty()?;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Extract arguments
|
|
||||||
//
|
|
||||||
let data = input.remove(&PortName::new("data"));
|
|
||||||
if data.is_none() {
|
|
||||||
return Err(RunNodeError::MissingInput {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some((port, _)) = input.pop_first() {
|
|
||||||
return Err(RunNodeError::UnrecognizedInput { port });
|
|
||||||
}
|
|
||||||
|
|
||||||
let source = match data.unwrap() {
|
|
||||||
None => {
|
|
||||||
return Err(RunNodeError::RequiredInputNull {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PipeData::Blob { source, .. }) => source,
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
return Err(RunNodeError::BadInputType {
|
|
||||||
port: PortName::new("data"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
message = "Setup done, stripping tags",
|
|
||||||
node_id = ?this_node.id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut output = BTreeMap::new();
|
|
||||||
|
|
||||||
output.insert(
|
|
||||||
PortName::new("out"),
|
|
||||||
PipeData::Blob {
|
|
||||||
source: source.add_processor(Arc::new(TagStripProcessor {
|
|
||||||
node_id: this_node.id.clone(),
|
|
||||||
node_type: this_node.node_type.clone(),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct TagStripProcessor {
|
|
||||||
node_id: NodeId,
|
|
||||||
node_type: SmartString<LazyCompact>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamProcessorBuilder for TagStripProcessor {
|
|
||||||
fn build(&self) -> Box<dyn StreamProcessor> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl StreamProcessor for TagStripProcessor {
|
|
||||||
fn mime(&self) -> &MimeType {
|
|
||||||
return &MimeType::Flac;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
"TagStripProcessor"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source_node_id(&self) -> &NodeId {
|
|
||||||
&self.node_id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the type of the node that created this processor
|
|
||||||
fn source_node_type(&self) -> &str {
|
|
||||||
&self.node_type
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
mut source: Receiver<Arc<Vec<u8>>>,
|
|
||||||
sink: Sender<Arc<Vec<u8>>>,
|
|
||||||
max_buffer_size: usize,
|
|
||||||
) -> Result<(), RunNodeError> {
|
|
||||||
//
|
|
||||||
// Strip tags
|
|
||||||
//
|
|
||||||
|
|
||||||
let mut strip = FlacMetaStrip::new();
|
|
||||||
let mut out_bytes = Vec::new();
|
|
||||||
|
|
||||||
while let Some(data) = source.recv().await {
|
|
||||||
strip
|
|
||||||
.push_data(&data)
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
|
|
||||||
strip
|
|
||||||
.read_data(&mut out_bytes)
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
|
|
||||||
if out_bytes.len() >= max_buffer_size {
|
|
||||||
let x = std::mem::take(&mut out_bytes);
|
|
||||||
|
|
||||||
match sink.send(Arc::new(x)).await {
|
|
||||||
Ok(()) => {}
|
|
||||||
|
|
||||||
// Not an error, our receiver was dropped.
|
|
||||||
// Exit early if that happens!
|
|
||||||
Err(_) => return Ok(()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strip
|
|
||||||
.finish()
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
|
|
||||||
while strip.has_data() {
|
|
||||||
strip
|
|
||||||
.read_data(&mut out_bytes)
|
|
||||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
match sink.send(Arc::new(out_bytes)).await {
|
|
||||||
Ok(()) => {}
|
|
||||||
|
|
||||||
// Not an error, our receiver was dropped.
|
|
||||||
// Exit early if that happens!
|
|
||||||
Err(_) => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,9 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pile-config = { workspace = true }
|
pile-config = { workspace = true }
|
||||||
pile-audio = { workspace = true }
|
|
||||||
pile-toolbox = { workspace = true }
|
pile-toolbox = { workspace = true }
|
||||||
|
pile-flac = { workspace = true }
|
||||||
|
|
||||||
|
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
use std::{
|
use std::{fmt::Debug, fs::File, io::BufReader, path::PathBuf};
|
||||||
fmt::Debug,
|
|
||||||
fs::File,
|
|
||||||
io::{Read, Seek},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use pile_audio::flac::blockread::{FlacBlock, FlacBlockReader, FlacBlockSelector};
|
|
||||||
use pile_config::Label;
|
use pile_config::Label;
|
||||||
|
use pile_flac::{FlacBlock, FlacReader};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
use crate::Item;
|
use crate::Item;
|
||||||
@@ -36,56 +31,34 @@ impl Item for FlacItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn json(&self) -> Result<serde_json::Value, std::io::Error> {
|
fn json(&self) -> Result<serde_json::Value, std::io::Error> {
|
||||||
let mut block_reader = FlacBlockReader::new(FlacBlockSelector {
|
let file = File::open(&self.path)?;
|
||||||
pick_vorbiscomment: true,
|
let reader = FlacReader::new(BufReader::new(file));
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut file = File::open(&self.path)?;
|
|
||||||
|
|
||||||
// TODO: do not read the whole file
|
|
||||||
file.rewind()?;
|
|
||||||
let mut data = Vec::new();
|
|
||||||
file.read_to_end(&mut data)?;
|
|
||||||
|
|
||||||
block_reader
|
|
||||||
.push_data(&data)
|
|
||||||
.map_err(std::io::Error::other)?;
|
|
||||||
block_reader.finish().map_err(std::io::Error::other)?;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Return tags
|
|
||||||
//
|
|
||||||
|
|
||||||
let mut output = Map::new();
|
let mut output = Map::new();
|
||||||
|
|
||||||
while let Some(block) = block_reader.pop_block() {
|
for block in reader {
|
||||||
match block {
|
if let FlacBlock::VorbisComment(comment) = block.unwrap() {
|
||||||
FlacBlock::VorbisComment(comment) => {
|
for (k, v) in comment.comment.comments {
|
||||||
for (k, v) in comment.comment.comments {
|
let k = k.to_string();
|
||||||
let k = k.to_string();
|
let v = Value::String(v.into());
|
||||||
let v = Value::String(v.into());
|
let e = output.get_mut(&k);
|
||||||
let e = output.get_mut(&k);
|
|
||||||
|
|
||||||
match e {
|
match e {
|
||||||
None => {
|
None => {
|
||||||
output.insert(k.clone(), Value::Array(vec![v]));
|
output.insert(k.clone(), Value::Array(vec![v]));
|
||||||
}
|
}
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
// We always insert an array
|
// We always insert an array
|
||||||
#[expect(clippy::unwrap_used)]
|
#[expect(clippy::unwrap_used)]
|
||||||
e.as_array_mut().unwrap().push(v);
|
e.as_array_mut().unwrap().push(v);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `reader` filters blocks for us
|
// We should only have one comment block,
|
||||||
_ => unreachable!(),
|
// stop reading when we find it
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should only have one comment block
|
|
||||||
assert!(!block_reader.has_block());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(serde_json::Value::Object(output));
|
return Ok(serde_json::Value::Object(output));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pile-audio"
|
name = "pile-flac"
|
||||||
version = { workspace = true }
|
version = { workspace = true }
|
||||||
rust-version = { workspace = true }
|
rust-version = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
@@ -16,6 +16,5 @@ smartstring = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
paste = { workspace = true }
|
paste = { workspace = true }
|
||||||
rand = { workspace = true }
|
|
||||||
sha2 = { workspace = true }
|
sha2 = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
io::{Cursor, Read},
|
io::{Cursor, Read},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
||||||
|
use crate::{FlacDecodeError, FlacEncodeError};
|
||||||
|
|
||||||
/// An application block in a flac file
|
/// An application block in a flac file
|
||||||
pub struct FlacApplicationBlock {
|
pub struct FlacApplicationBlock {
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
|
use crate::{FlacDecodeError, FlacEncodeError};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
|
||||||
|
|
||||||
/// An audio frame in a flac file
|
/// An audio frame in a flac file
|
||||||
pub struct FlacAudioFrame {
|
pub struct FlacAudioFrame {
|
||||||
/// The audio frame
|
/// The audio frame
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
common::vorbiscomment::VorbisComment,
|
|
||||||
flac::errors::{FlacDecodeError, FlacEncodeError},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
||||||
|
use crate::{FlacDecodeError, FlacEncodeError, VorbisComment};
|
||||||
|
|
||||||
/// A vorbis comment metablock in a flac file
|
/// A vorbis comment metablock in a flac file
|
||||||
pub struct FlacCommentBlock {
|
pub struct FlacCommentBlock {
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
|
||||||
|
|
||||||
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
||||||
|
use crate::{FlacDecodeError, FlacEncodeError};
|
||||||
|
|
||||||
/// A cuesheet meta in a flac file
|
/// A cuesheet meta in a flac file
|
||||||
pub struct FlacCuesheetBlock {
|
pub struct FlacCuesheetBlock {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//! FLAC metablock headers. See spec.
|
//! FLAC metablock headers. See spec.
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
use crate::{FlacDecodeError, FlacEncodeError};
|
||||||
|
|
||||||
/// A type of flac metadata block
|
/// A type of flac metadata block
|
||||||
#[expect(missing_docs)]
|
#[expect(missing_docs)]
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
//! Read and write implementations for all flac block types
|
//! Read and write implementations for all flac block types
|
||||||
|
|
||||||
// Not metadata blocks
|
|
||||||
mod header;
|
mod header;
|
||||||
pub use header::{FlacMetablockHeader, FlacMetablockType};
|
pub use header::{FlacMetablockHeader, FlacMetablockType};
|
||||||
|
|
||||||
mod audiodata;
|
mod audiodata;
|
||||||
pub use audiodata::FlacAudioFrame;
|
pub use audiodata::FlacAudioFrame;
|
||||||
|
|
||||||
// Metadata blocks
|
|
||||||
|
|
||||||
mod streaminfo;
|
mod streaminfo;
|
||||||
pub use streaminfo::FlacStreaminfoBlock;
|
pub use streaminfo::FlacStreaminfoBlock;
|
||||||
|
|
||||||
@@ -30,15 +27,12 @@ pub use cuesheet::FlacCuesheetBlock;
|
|||||||
mod comment;
|
mod comment;
|
||||||
pub use comment::FlacCommentBlock;
|
pub use comment::FlacCommentBlock;
|
||||||
|
|
||||||
use super::errors::{FlacDecodeError, FlacEncodeError};
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
/// A decode implementation for a
|
/// A decode implementation for a
|
||||||
/// flac metadata block
|
/// flac metadata block
|
||||||
pub trait FlacMetablockDecode: Sized {
|
pub trait FlacMetablockDecode: Sized {
|
||||||
/// Try to decode this block from bytes.
|
/// Try to decode this block from bytes.
|
||||||
/// `data` should NOT include the metablock header.
|
/// `data` should NOT include the metablock header.
|
||||||
fn decode(data: &[u8]) -> Result<Self, FlacDecodeError>;
|
fn decode(data: &[u8]) -> Result<Self, crate::FlacDecodeError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A encode implementation for a
|
/// A encode implementation for a
|
||||||
@@ -53,6 +47,6 @@ pub trait FlacMetablockEncode: Sized {
|
|||||||
&self,
|
&self,
|
||||||
is_last: bool,
|
is_last: bool,
|
||||||
with_header: bool,
|
with_header: bool,
|
||||||
target: &mut impl Write,
|
target: &mut impl std::io::Write,
|
||||||
) -> Result<(), FlacEncodeError>;
|
) -> Result<(), crate::FlacEncodeError>;
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::{fmt::Debug, io::Read};
|
use std::{fmt::Debug, io::Read};
|
||||||
|
|
||||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
|
||||||
|
|
||||||
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
||||||
|
use crate::{FlacDecodeError, FlacEncodeError};
|
||||||
|
|
||||||
/// A padding block in a FLAC file.
|
/// A padding block in a FLAC file.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
|
use mime::Mime;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
io::{Cursor, Read},
|
io::{Cursor, Read},
|
||||||
};
|
};
|
||||||
|
|
||||||
use mime::Mime;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
common::picturetype::PictureType,
|
|
||||||
flac::errors::{FlacDecodeError, FlacEncodeError},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
||||||
|
use crate::{FlacDecodeError, FlacEncodeError, PictureType};
|
||||||
|
|
||||||
/// A picture metablock in a flac file
|
/// A picture metablock in a flac file
|
||||||
pub struct FlacPictureBlock {
|
pub struct FlacPictureBlock {
|
||||||
@@ -59,7 +54,9 @@ impl FlacMetablockDecode for FlacPictureBlock {
|
|||||||
#[expect(clippy::map_err_ignore)]
|
#[expect(clippy::map_err_ignore)]
|
||||||
d.read_exact(&mut block)
|
d.read_exact(&mut block)
|
||||||
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
||||||
let picture_type = PictureType::from_idx(u32::from_be_bytes(block))?;
|
let picture_type = u32::from_be_bytes(block);
|
||||||
|
let picture_type = PictureType::from_idx(picture_type)
|
||||||
|
.ok_or(FlacDecodeError::InvalidPictureType(picture_type))?;
|
||||||
|
|
||||||
// Image format
|
// Image format
|
||||||
let mime = {
|
let mime = {
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
|
||||||
|
|
||||||
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
||||||
|
use crate::{FlacDecodeError, FlacEncodeError};
|
||||||
|
|
||||||
/// A seektable block in a flac file
|
/// A seektable block in a flac file
|
||||||
pub struct FlacSeektableBlock {
|
pub struct FlacSeektableBlock {
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
|
|
||||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
|
||||||
|
|
||||||
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType};
|
||||||
|
use crate::{FlacDecodeError, FlacEncodeError};
|
||||||
|
|
||||||
/// A streaminfo block in a flac file
|
/// A streaminfo block in a flac file
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
//! FLAC errors
|
//! FLAC errors
|
||||||
use crate::common::{
|
|
||||||
picturetype::PictureTypeError,
|
|
||||||
vorbiscomment::{VorbisCommentDecodeError, VorbisCommentEncodeError},
|
|
||||||
};
|
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@@ -22,47 +18,37 @@ pub enum FlacDecodeError {
|
|||||||
BadMetablockType(u8),
|
BadMetablockType(u8),
|
||||||
|
|
||||||
/// We encountered an i/o error while processing
|
/// We encountered an i/o error while processing
|
||||||
#[error("io error while reading flac")]
|
#[error("i/o error")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
/// We could not parse a vorbis comment
|
|
||||||
#[error("error while decoding vorbis comment")]
|
|
||||||
VorbisComment(#[from] VorbisCommentDecodeError),
|
|
||||||
|
|
||||||
/// We tried to decode a string, but found invalid UTF-8
|
/// We tried to decode a string, but found invalid UTF-8
|
||||||
#[error("error while decoding string")]
|
#[error("could not decode string")]
|
||||||
FailedStringDecode(#[from] FromUtf8Error),
|
FailedStringDecode(#[from] FromUtf8Error),
|
||||||
|
|
||||||
/// We tried to read a block, but it was out of spec.
|
/// We tried to read a block, but it was out of spec.
|
||||||
#[error("malformed flac block")]
|
#[error("malformed flac block")]
|
||||||
MalformedBlock,
|
MalformedBlock,
|
||||||
|
|
||||||
|
/// We could not parse a vorbis comment string
|
||||||
|
#[error("malformed vorbis comment string: {0:?}")]
|
||||||
|
MalformedCommentString(String),
|
||||||
|
|
||||||
|
/// We could not parse a vorbis comment string
|
||||||
|
#[error("malformed vorbis picture")]
|
||||||
|
MalformedPicture(base64::DecodeError),
|
||||||
|
|
||||||
/// We didn't find frame sync bytes where we expected them
|
/// We didn't find frame sync bytes where we expected them
|
||||||
#[error("bad frame sync bytes")]
|
#[error("bad frame sync bytes")]
|
||||||
BadSyncBytes,
|
BadSyncBytes,
|
||||||
|
|
||||||
/// We tried to decode a bad picture type
|
#[error("invalid picture type {0}")]
|
||||||
#[error("bad picture type")]
|
InvalidPictureType(u32),
|
||||||
PictureTypeError(#[from] PictureTypeError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(missing_docs)]
|
#[expect(missing_docs)]
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum FlacEncodeError {
|
pub enum FlacEncodeError {
|
||||||
/// We encountered an i/o error while processing
|
/// We encountered an i/o error while processing
|
||||||
#[error("io error while encoding block")]
|
#[error("i/o error")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
/// We could not encode a picture inside a vorbis comment
|
|
||||||
#[error("could not encode picture in vorbis comment")]
|
|
||||||
VorbisPictureEncodeError,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<VorbisCommentEncodeError> for FlacEncodeError {
|
|
||||||
fn from(value: VorbisCommentEncodeError) -> Self {
|
|
||||||
match value {
|
|
||||||
VorbisCommentEncodeError::IoError(e) => e.into(),
|
|
||||||
VorbisCommentEncodeError::PictureEncodeError => Self::VorbisPictureEncodeError,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
19
crates/pile-flac/src/lib.rs
Normal file
19
crates/pile-flac/src/lib.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
pub mod blocks;
|
||||||
|
|
||||||
|
mod tagtype;
|
||||||
|
pub use tagtype::*;
|
||||||
|
|
||||||
|
mod picturetype;
|
||||||
|
pub use picturetype::*;
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
pub use errors::*;
|
||||||
|
|
||||||
|
mod reader;
|
||||||
|
pub use reader::*;
|
||||||
|
|
||||||
|
mod vorbiscomment;
|
||||||
|
pub use vorbiscomment::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
@@ -1,21 +1,5 @@
|
|||||||
//! An audio picture type, according to the ID3v2 APIC frame
|
//! An audio picture type, according to the ID3v2 APIC frame
|
||||||
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
/// We failed to decode a picture type
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PictureTypeError {
|
|
||||||
idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for PictureTypeError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "Bad picture type `{}`", self.idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for PictureTypeError {}
|
|
||||||
|
|
||||||
/// A picture type according to the ID3v2 APIC frame
|
/// A picture type according to the ID3v2 APIC frame
|
||||||
#[expect(missing_docs)]
|
#[expect(missing_docs)]
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
@@ -46,8 +30,8 @@ pub enum PictureType {
|
|||||||
impl PictureType {
|
impl PictureType {
|
||||||
/// Try to decode a picture type from the given integer.
|
/// Try to decode a picture type from the given integer.
|
||||||
/// Returns an error if `idx` is invalid.
|
/// Returns an error if `idx` is invalid.
|
||||||
pub fn from_idx(idx: u32) -> Result<Self, PictureTypeError> {
|
pub fn from_idx(idx: u32) -> Option<Self> {
|
||||||
Ok(match idx {
|
Some(match idx {
|
||||||
0 => PictureType::Other,
|
0 => PictureType::Other,
|
||||||
1 => PictureType::PngFileIcon,
|
1 => PictureType::PngFileIcon,
|
||||||
2 => PictureType::OtherFileIcon,
|
2 => PictureType::OtherFileIcon,
|
||||||
@@ -69,7 +53,7 @@ impl PictureType {
|
|||||||
18 => PictureType::Illustration,
|
18 => PictureType::Illustration,
|
||||||
19 => PictureType::ArtistLogotype,
|
19 => PictureType::ArtistLogotype,
|
||||||
20 => PictureType::PublisherLogotype,
|
20 => PictureType::PublisherLogotype,
|
||||||
_ => return Err(PictureTypeError { idx }),
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
63
crates/pile-flac/src/reader/block.rs
Normal file
63
crates/pile-flac/src/reader/block.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
FlacDecodeError, FlacEncodeError,
|
||||||
|
blocks::{
|
||||||
|
FlacApplicationBlock, FlacAudioFrame, FlacCommentBlock, FlacCuesheetBlock,
|
||||||
|
FlacMetablockDecode, FlacMetablockEncode, FlacMetablockType, FlacPaddingBlock,
|
||||||
|
FlacPictureBlock, FlacSeektableBlock, FlacStreaminfoBlock,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[expect(missing_docs)]
|
||||||
|
pub enum FlacBlock {
|
||||||
|
Streaminfo(FlacStreaminfoBlock),
|
||||||
|
Picture(FlacPictureBlock),
|
||||||
|
Padding(FlacPaddingBlock),
|
||||||
|
Application(FlacApplicationBlock),
|
||||||
|
SeekTable(FlacSeektableBlock),
|
||||||
|
VorbisComment(FlacCommentBlock),
|
||||||
|
CueSheet(FlacCuesheetBlock),
|
||||||
|
AudioFrame(FlacAudioFrame),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlacBlock {
|
||||||
|
/// Encode this block
|
||||||
|
pub fn encode(
|
||||||
|
&self,
|
||||||
|
is_last: bool,
|
||||||
|
with_header: bool,
|
||||||
|
target: &mut impl Write,
|
||||||
|
) -> Result<(), FlacEncodeError> {
|
||||||
|
match self {
|
||||||
|
Self::Streaminfo(b) => b.encode(is_last, with_header, target),
|
||||||
|
Self::SeekTable(b) => b.encode(is_last, with_header, target),
|
||||||
|
Self::Picture(b) => b.encode(is_last, with_header, target),
|
||||||
|
Self::Padding(b) => b.encode(is_last, with_header, target),
|
||||||
|
Self::Application(b) => b.encode(is_last, with_header, target),
|
||||||
|
Self::VorbisComment(b) => b.encode(is_last, with_header, target),
|
||||||
|
Self::CueSheet(b) => b.encode(is_last, with_header, target),
|
||||||
|
Self::AudioFrame(b) => b.encode(target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to decode the given data as a block
|
||||||
|
pub fn decode(block_type: FlacMetablockType, data: &[u8]) -> Result<Self, FlacDecodeError> {
|
||||||
|
Ok(match block_type {
|
||||||
|
FlacMetablockType::Streaminfo => {
|
||||||
|
FlacBlock::Streaminfo(FlacStreaminfoBlock::decode(data)?)
|
||||||
|
}
|
||||||
|
FlacMetablockType::Application => {
|
||||||
|
FlacBlock::Application(FlacApplicationBlock::decode(data)?)
|
||||||
|
}
|
||||||
|
FlacMetablockType::Cuesheet => FlacBlock::CueSheet(FlacCuesheetBlock::decode(data)?),
|
||||||
|
FlacMetablockType::Padding => FlacBlock::Padding(FlacPaddingBlock::decode(data)?),
|
||||||
|
FlacMetablockType::Picture => FlacBlock::Picture(FlacPictureBlock::decode(data)?),
|
||||||
|
FlacMetablockType::Seektable => FlacBlock::SeekTable(FlacSeektableBlock::decode(data)?),
|
||||||
|
FlacMetablockType::VorbisComment => {
|
||||||
|
FlacBlock::VorbisComment(FlacCommentBlock::decode(data)?)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
9
crates/pile-flac/src/reader/mod.rs
Normal file
9
crates/pile-flac/src/reader/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod block;
|
||||||
|
pub use block::*;
|
||||||
|
|
||||||
|
#[expect(clippy::module_inception)]
|
||||||
|
mod reader;
|
||||||
|
pub use reader::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
184
crates/pile-flac/src/reader/reader.rs
Normal file
184
crates/pile-flac/src/reader/reader.rs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
use std::io::{Read, Seek, SeekFrom};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
FlacBlock, FlacDecodeError,
|
||||||
|
blocks::{FlacAudioFrame, FlacMetablockHeader, FlacMetablockType},
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: quickly skip blocks we do not need
|
||||||
|
|
||||||
|
/// The next block we expect to read
|
||||||
|
enum ReaderState {
|
||||||
|
MagicBits,
|
||||||
|
MetablockHeader { is_first: bool },
|
||||||
|
MetaBlock { header: FlacMetablockHeader },
|
||||||
|
AudioData,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FlacReader<R: Read + Seek> {
|
||||||
|
inner: R,
|
||||||
|
state: ReaderState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> FlacReader<R> {
|
||||||
|
const MIN_AUDIO_FRAME_LEN: usize = 5000;
|
||||||
|
|
||||||
|
pub fn new(inner: R) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
state: ReaderState::MagicBits,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> Iterator for FlacReader<R> {
|
||||||
|
type Item = Result<FlacBlock, FlacDecodeError>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
match &mut self.state {
|
||||||
|
ReaderState::Done => return None,
|
||||||
|
|
||||||
|
ReaderState::MagicBits => {
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
if let Err(e) = self.inner.read_exact(&mut data[..4]) {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if data != [0x66, 0x4C, 0x61, 0x43] {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(FlacDecodeError::BadMagicBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = ReaderState::MetablockHeader { is_first: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
ReaderState::MetablockHeader { is_first } => {
|
||||||
|
let mut data = [0u8; 4];
|
||||||
|
if let Err(e) = self.inner.read_exact(&mut data[..]) {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = match FlacMetablockHeader::decode(&data) {
|
||||||
|
Ok(h) => h,
|
||||||
|
Err(e) => {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if *is_first && !matches!(header.block_type, FlacMetablockType::Streaminfo) {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(FlacDecodeError::BadFirstBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = ReaderState::MetaBlock { header };
|
||||||
|
}
|
||||||
|
|
||||||
|
ReaderState::MetaBlock { header } => {
|
||||||
|
let mut data = vec![0u8; header.length as usize];
|
||||||
|
if let Err(e) = self.inner.read_exact(&mut data) {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = match FlacBlock::decode(header.block_type, &data) {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(e) => {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if header.is_last {
|
||||||
|
self.state = ReaderState::AudioData;
|
||||||
|
} else {
|
||||||
|
self.state = ReaderState::MetablockHeader { is_first: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(Ok(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
ReaderState::AudioData => {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
loop {
|
||||||
|
let mut byte = [0u8; 1];
|
||||||
|
match self.inner.read_exact(&mut byte) {
|
||||||
|
Ok(_) => {
|
||||||
|
data.push(byte[0]);
|
||||||
|
|
||||||
|
if data.len() >= Self::MIN_AUDIO_FRAME_LEN + 2 {
|
||||||
|
let len = data.len();
|
||||||
|
if data[len - 2] == 0b1111_1111
|
||||||
|
&& data[len - 1] & 0b1111_1100 == 0b1111_1000
|
||||||
|
{
|
||||||
|
let frame_data = data[..len - 2].to_vec();
|
||||||
|
|
||||||
|
if frame_data.len() < 2
|
||||||
|
|| frame_data[0] != 0b1111_1111 || frame_data[1]
|
||||||
|
& 0b1111_1100
|
||||||
|
!= 0b1111_1000
|
||||||
|
{
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(FlacDecodeError::BadSyncBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
let audio_frame = match FlacAudioFrame::decode(&frame_data)
|
||||||
|
{
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seek back 2 bytes so the next frame starts with the sync bytes
|
||||||
|
if let Err(e) = self.inner.seek(SeekFrom::Current(-2)) {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = ReaderState::AudioData;
|
||||||
|
|
||||||
|
return Some(Ok(FlacBlock::AudioFrame(audio_frame)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||||
|
if data.len() > 2 {
|
||||||
|
if data[0] != 0b1111_1111
|
||||||
|
|| data[1] & 0b1111_1100 != 0b1111_1000
|
||||||
|
{
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(FlacDecodeError::BadSyncBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
let audio_frame = match FlacAudioFrame::decode(&data) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Ok(FlacBlock::AudioFrame(audio_frame)));
|
||||||
|
} else {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.state = ReaderState::Done;
|
||||||
|
return Some(Err(e.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
393
crates/pile-flac/src/reader/tests.rs
Normal file
393
crates/pile-flac/src/reader/tests.rs
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use paste::paste;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::{fs::File, io::Write, str::FromStr};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
FlacDecodeError, TagType,
|
||||||
|
tests::{FlacBlockOutput, FlacTestCase, VorbisCommentTestValue},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[expect(clippy::unwrap_used)]
|
||||||
|
fn read_file(test_case: &FlacTestCase) -> Result<Vec<FlacBlock>, FlacDecodeError> {
|
||||||
|
let file_data = std::fs::read(test_case.get_path()).unwrap();
|
||||||
|
|
||||||
|
// Make sure input file is correct
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&file_data);
|
||||||
|
assert_eq!(
|
||||||
|
test_case.get_in_hash(),
|
||||||
|
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
let file = File::open(test_case.get_path()).unwrap();
|
||||||
|
|
||||||
|
let mut reader = FlacReader::new(file);
|
||||||
|
let mut out_blocks = Vec::new();
|
||||||
|
|
||||||
|
while let Some(b) = reader.next() {
|
||||||
|
out_blocks.push(b?)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(out_blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::unwrap_used)]
|
||||||
|
fn test_identical(test_case: &FlacTestCase) -> Result<(), FlacDecodeError> {
|
||||||
|
let out_blocks = read_file(test_case)?;
|
||||||
|
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.write_all(&[0x66, 0x4C, 0x61, 0x43]).unwrap();
|
||||||
|
|
||||||
|
for i in 0..out_blocks.len() {
|
||||||
|
let b = &out_blocks[i];
|
||||||
|
let is_last = if i == out_blocks.len() - 1 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
!matches!(b, FlacBlock::AudioFrame(_))
|
||||||
|
&& matches!(&out_blocks[i + 1], FlacBlock::AudioFrame(_))
|
||||||
|
};
|
||||||
|
|
||||||
|
b.encode(is_last, true, &mut out).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&out);
|
||||||
|
let result = hasher.finalize().map(|x| format!("{x:02x}")).join("");
|
||||||
|
assert_eq!(result, test_case.get_in_hash(), "Output hash doesn't match");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_blockread(test_case: &FlacTestCase) -> Result<(), FlacDecodeError> {
|
||||||
|
let out_blocks = read_file(test_case)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
test_case.get_blocks().unwrap().len(),
|
||||||
|
out_blocks
|
||||||
|
.iter()
|
||||||
|
.filter(|x| !matches!(*x, FlacBlock::AudioFrame(_)))
|
||||||
|
.count(),
|
||||||
|
"Number of blocks didn't match"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut audio_data_hasher = Sha256::new();
|
||||||
|
let mut result_i = 0;
|
||||||
|
|
||||||
|
for b in out_blocks {
|
||||||
|
match b {
|
||||||
|
FlacBlock::Streaminfo(s) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size,
|
||||||
|
max_block_size,
|
||||||
|
min_frame_size,
|
||||||
|
max_frame_size,
|
||||||
|
sample_rate,
|
||||||
|
channels,
|
||||||
|
bits_per_sample,
|
||||||
|
total_samples,
|
||||||
|
md5_signature,
|
||||||
|
} => {
|
||||||
|
assert_eq!(*min_block_size, s.min_block_size,);
|
||||||
|
assert_eq!(*max_block_size, s.max_block_size);
|
||||||
|
assert_eq!(*min_frame_size, s.min_frame_size);
|
||||||
|
assert_eq!(*max_frame_size, s.max_frame_size);
|
||||||
|
assert_eq!(*sample_rate, s.sample_rate);
|
||||||
|
assert_eq!(*channels, s.channels);
|
||||||
|
assert_eq!(*bits_per_sample, s.bits_per_sample);
|
||||||
|
assert_eq!(*total_samples, s.total_samples);
|
||||||
|
assert_eq!(
|
||||||
|
*md5_signature,
|
||||||
|
s.md5_signature.iter().map(|x| format!("{x:02x}")).join("")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected block type"),
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacBlock::Application(a) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||||
|
FlacBlockOutput::Application {
|
||||||
|
application_id,
|
||||||
|
hash,
|
||||||
|
} => {
|
||||||
|
assert_eq!(
|
||||||
|
*application_id, a.application_id,
|
||||||
|
"Application id doesn't match"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*hash,
|
||||||
|
{
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
|
||||||
|
hasher.update(&a.data);
|
||||||
|
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||||
|
},
|
||||||
|
"Application content hash doesn't match"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected block type"),
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacBlock::CueSheet(c) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||||
|
FlacBlockOutput::CueSheet { hash } => {
|
||||||
|
assert_eq!(*hash, {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
|
||||||
|
hasher.update(&c.data);
|
||||||
|
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected block type"),
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacBlock::Padding(p) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||||
|
FlacBlockOutput::Padding { size } => {
|
||||||
|
assert_eq!(p.size, *size);
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected block type"),
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacBlock::SeekTable(t) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||||
|
FlacBlockOutput::Seektable { hash } => {
|
||||||
|
assert_eq!(*hash, {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
|
||||||
|
hasher.update(&t.data);
|
||||||
|
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected block type"),
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacBlock::Picture(p) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type,
|
||||||
|
mime,
|
||||||
|
description,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bit_depth,
|
||||||
|
color_count,
|
||||||
|
img_data,
|
||||||
|
} => {
|
||||||
|
assert_eq!(*picture_type, p.picture_type, "{}", test_case.get_name());
|
||||||
|
assert_eq!(*mime, p.mime, "{}", test_case.get_name());
|
||||||
|
assert_eq!(*description, p.description, "{}", test_case.get_name());
|
||||||
|
assert_eq!(*width, p.width, "{}", test_case.get_name());
|
||||||
|
assert_eq!(*height, p.height, "{}", test_case.get_name());
|
||||||
|
assert_eq!(*bit_depth, p.bit_depth, "{}", test_case.get_name());
|
||||||
|
assert_eq!(*color_count, p.color_count, "{}", test_case.get_name());
|
||||||
|
assert_eq!(
|
||||||
|
*img_data,
|
||||||
|
{
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
|
||||||
|
hasher.update(&p.img_data);
|
||||||
|
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||||
|
},
|
||||||
|
"{}",
|
||||||
|
test_case.get_name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected block type"),
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacBlock::VorbisComment(v) => match &test_case.get_blocks().unwrap()[result_i] {
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor,
|
||||||
|
comments,
|
||||||
|
pictures,
|
||||||
|
} => {
|
||||||
|
assert_eq!(*vendor, v.comment.vendor, "Comment vendor doesn't match");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
v.comment.pictures.len(),
|
||||||
|
pictures.len(),
|
||||||
|
"Number of pictures doesn't match"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (p, e) in v.comment.pictures.iter().zip(*pictures) {
|
||||||
|
match e {
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type,
|
||||||
|
mime,
|
||||||
|
description,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bit_depth,
|
||||||
|
color_count,
|
||||||
|
img_data,
|
||||||
|
} => {
|
||||||
|
assert_eq!(*picture_type, p.picture_type);
|
||||||
|
assert_eq!(*mime, p.mime);
|
||||||
|
assert_eq!(*description, p.description);
|
||||||
|
assert_eq!(*width, p.width);
|
||||||
|
assert_eq!(*height, p.height);
|
||||||
|
assert_eq!(*bit_depth, p.bit_depth);
|
||||||
|
assert_eq!(*color_count, p.color_count);
|
||||||
|
assert_eq!(*img_data, {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&p.img_data);
|
||||||
|
hasher.finalize().map(|x| format!("{x:02x}")).join("")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => panic!("Bad test data: expected only Picture blocks."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match comments {
|
||||||
|
VorbisCommentTestValue::Raw { tags } => {
|
||||||
|
assert_eq!(
|
||||||
|
v.comment.comments.len(),
|
||||||
|
tags.len(),
|
||||||
|
"Number of comments doesn't match"
|
||||||
|
);
|
||||||
|
|
||||||
|
for ((got_tag, got_val), (exp_tag, exp_val)) in
|
||||||
|
v.comment.comments.iter().zip(*tags)
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
*got_tag,
|
||||||
|
TagType::from_str(exp_tag).unwrap(),
|
||||||
|
"Tag key doesn't match"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
got_val, exp_val,
|
||||||
|
"Tag value of {exp_tag} doesn't match"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VorbisCommentTestValue::Hash { n_comments, hash } => {
|
||||||
|
assert_eq!(
|
||||||
|
v.comment.comments.len(),
|
||||||
|
*n_comments,
|
||||||
|
"Number of comments doesn't match"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
|
||||||
|
for (got_tag, got_val) in &v.comment.comments {
|
||||||
|
hasher.update(format!("{}={got_val};", got_tag.to_vorbis_string()).as_bytes());
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
&hasher.finalize().map(|x| format!("{x:02x}")).join(""),
|
||||||
|
hash,
|
||||||
|
"Comment hash doesn't match"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected block type"),
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacBlock::AudioFrame(data) => {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
data.encode(&mut vec).unwrap();
|
||||||
|
audio_data_hasher.update(&vec);
|
||||||
|
|
||||||
|
if result_i != test_case.get_blocks().unwrap().len() {
|
||||||
|
panic!("There are metadata blocks between audio frames!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't increment result_i
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check audio data hash
|
||||||
|
assert_eq!(
|
||||||
|
test_case.get_audio_hash().unwrap(),
|
||||||
|
audio_data_hasher
|
||||||
|
.finalize()
|
||||||
|
.map(|x| format!("{x:02x}"))
|
||||||
|
.join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper macros to generate tests
|
||||||
|
macro_rules! gen_tests {
|
||||||
|
( $test_name:ident ) => {
|
||||||
|
paste! {
|
||||||
|
#[test]
|
||||||
|
pub fn [<blockread_small_ $test_name>]() {
|
||||||
|
let manifest = crate::tests::manifest();
|
||||||
|
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
||||||
|
|
||||||
|
match test_case {
|
||||||
|
FlacTestCase::Success { .. } => {
|
||||||
|
for _ in 0..5 {
|
||||||
|
test_blockread(
|
||||||
|
test_case,
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacTestCase::Error { check_error, .. } => {
|
||||||
|
let e = test_blockread(test_case);
|
||||||
|
match e {
|
||||||
|
Err(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
||||||
|
_ => panic!("Unexpected error {e:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn [<identical_small_ $test_name>]() {
|
||||||
|
let manifest = crate::tests::manifest();
|
||||||
|
let test_case = manifest.iter().find(|x| x.get_name() == stringify!($test_name)).unwrap();
|
||||||
|
|
||||||
|
match test_case {
|
||||||
|
FlacTestCase::Success { .. } => {
|
||||||
|
for _ in 0..5 {
|
||||||
|
test_identical(
|
||||||
|
test_case,
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
FlacTestCase::Error { check_error, .. } => {
|
||||||
|
let e = test_identical(test_case);
|
||||||
|
match e {
|
||||||
|
Err(e) => assert!(check_error(&e), "Unexpected error {e:?}"),
|
||||||
|
_ => panic!("Unexpected error {e:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
gen_tests!(custom_01);
|
||||||
|
gen_tests!(custom_02);
|
||||||
|
gen_tests!(custom_03);
|
||||||
|
|
||||||
|
gen_tests!(uncommon_10);
|
||||||
|
|
||||||
|
gen_tests!(faulty_06);
|
||||||
|
gen_tests!(faulty_07);
|
||||||
|
gen_tests!(faulty_10);
|
||||||
|
gen_tests!(faulty_11);
|
||||||
|
|
||||||
|
gen_tests!(subset_45);
|
||||||
|
gen_tests!(subset_46);
|
||||||
|
gen_tests!(subset_47);
|
||||||
|
gen_tests!(subset_48);
|
||||||
|
gen_tests!(subset_49);
|
||||||
|
gen_tests!(subset_50);
|
||||||
|
gen_tests!(subset_51);
|
||||||
|
gen_tests!(subset_52);
|
||||||
|
gen_tests!(subset_53);
|
||||||
|
gen_tests!(subset_54);
|
||||||
|
gen_tests!(subset_55);
|
||||||
|
gen_tests!(subset_56);
|
||||||
|
gen_tests!(subset_57);
|
||||||
|
gen_tests!(subset_58);
|
||||||
|
gen_tests!(subset_59);
|
||||||
@@ -4,37 +4,75 @@ use strum::{Display, EnumString};
|
|||||||
|
|
||||||
/// A universal tag type
|
/// A universal tag type
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, EnumString, Display)]
|
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, EnumString, Display)]
|
||||||
|
#[strum(ascii_case_insensitive)]
|
||||||
pub enum TagType {
|
pub enum TagType {
|
||||||
/// A tag we didn't recognize
|
/// A tag we didn't recognize
|
||||||
|
#[strum(default)]
|
||||||
Other(SmartString<LazyCompact>),
|
Other(SmartString<LazyCompact>),
|
||||||
|
|
||||||
/// Album name
|
/// Album name
|
||||||
Album,
|
Album,
|
||||||
|
|
||||||
/// Album artist
|
/// Album artist
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
|
|
||||||
/// Comment
|
/// Comment
|
||||||
Comment,
|
Comment,
|
||||||
|
|
||||||
/// Release date
|
/// Release date
|
||||||
ReleaseDate,
|
ReleaseDate,
|
||||||
|
|
||||||
/// Disk number
|
/// Disk number
|
||||||
DiskNumber,
|
DiskNumber,
|
||||||
|
|
||||||
/// Total disks in album
|
/// Total disks in album
|
||||||
DiskTotal,
|
DiskTotal,
|
||||||
|
|
||||||
/// Genre
|
/// Genre
|
||||||
Genre,
|
Genre,
|
||||||
|
|
||||||
/// International standard recording code
|
/// International standard recording code
|
||||||
Isrc,
|
Isrc,
|
||||||
|
|
||||||
/// Track lyrics, possibly time-coded
|
/// Track lyrics, possibly time-coded
|
||||||
Lyrics,
|
Lyrics,
|
||||||
|
|
||||||
/// This track's number in its album
|
/// This track's number in its album
|
||||||
TrackNumber,
|
TrackNumber,
|
||||||
|
|
||||||
/// The total number of tracks in this track's album
|
/// The total number of tracks in this track's album
|
||||||
TrackTotal,
|
TrackTotal,
|
||||||
|
|
||||||
/// The title of this track
|
/// The title of this track
|
||||||
TrackTitle,
|
TrackTitle,
|
||||||
|
|
||||||
/// This track's artist (the usual `Artist`,
|
/// This track's artist (the usual `Artist`,
|
||||||
/// compare to `AlbumArtist`)
|
/// compare to `AlbumArtist`)
|
||||||
TrackArtist,
|
TrackArtist,
|
||||||
|
|
||||||
/// The year this track was released
|
/// The year this track was released
|
||||||
Year,
|
Year,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TagType {
|
||||||
|
pub fn to_vorbis_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
TagType::TrackTitle => "TITLE",
|
||||||
|
TagType::Album => "ALBUM",
|
||||||
|
TagType::TrackNumber => "TRACKNUMBER",
|
||||||
|
TagType::TrackArtist => "ARTIST",
|
||||||
|
TagType::AlbumArtist => "ALBUMARTIST",
|
||||||
|
TagType::Genre => "GENRE",
|
||||||
|
TagType::Isrc => "ISRC",
|
||||||
|
TagType::ReleaseDate => "DATE",
|
||||||
|
TagType::TrackTotal => "TOTALTRACKS",
|
||||||
|
TagType::Lyrics => "LYRICS",
|
||||||
|
TagType::Comment => "COMMENT",
|
||||||
|
TagType::DiskNumber => "DISKNUMBER",
|
||||||
|
TagType::DiskTotal => "DISKTOTAL",
|
||||||
|
TagType::Year => "YEAR",
|
||||||
|
TagType::Other(x) => x,
|
||||||
|
}
|
||||||
|
.to_uppercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
919
crates/pile-flac/src/tests.rs
Normal file
919
crates/pile-flac/src/tests.rs
Normal file
@@ -0,0 +1,919 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use mime::Mime;
|
||||||
|
|
||||||
|
use crate::PictureType;
|
||||||
|
|
||||||
|
use super::errors::FlacDecodeError;
|
||||||
|
|
||||||
|
/// The value of a vorbis comment.
|
||||||
|
///
|
||||||
|
/// Some files have VERY large comments, and providing them
|
||||||
|
/// explicitly here doesn't make sense.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum VorbisCommentTestValue {
|
||||||
|
/// The comments, in order
|
||||||
|
Raw {
|
||||||
|
tags: &'static [(&'static str, &'static str)],
|
||||||
|
},
|
||||||
|
/// The hash of all comments concatenated together,
|
||||||
|
/// stringified as `{key}={value};`
|
||||||
|
Hash {
|
||||||
|
n_comments: usize,
|
||||||
|
hash: &'static str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum FlacBlockOutput {
|
||||||
|
Application {
|
||||||
|
application_id: u32,
|
||||||
|
hash: &'static str,
|
||||||
|
},
|
||||||
|
Streaminfo {
|
||||||
|
min_block_size: u32,
|
||||||
|
max_block_size: u32,
|
||||||
|
min_frame_size: u32,
|
||||||
|
max_frame_size: u32,
|
||||||
|
sample_rate: u32,
|
||||||
|
channels: u8,
|
||||||
|
bits_per_sample: u8,
|
||||||
|
total_samples: u128,
|
||||||
|
md5_signature: &'static str,
|
||||||
|
},
|
||||||
|
CueSheet {
|
||||||
|
// Hash of this block's data, without the header.
|
||||||
|
// This is easy to get with
|
||||||
|
//
|
||||||
|
// ```notrust
|
||||||
|
// metaflac \
|
||||||
|
// --list \
|
||||||
|
// --block-number=<n> \
|
||||||
|
// --data-format=binary-headerless \
|
||||||
|
// <file> \
|
||||||
|
// | sha256sum
|
||||||
|
//```
|
||||||
|
hash: &'static str,
|
||||||
|
},
|
||||||
|
Seektable {
|
||||||
|
hash: &'static str,
|
||||||
|
},
|
||||||
|
Padding {
|
||||||
|
size: u32,
|
||||||
|
},
|
||||||
|
Picture {
|
||||||
|
picture_type: PictureType,
|
||||||
|
mime: Mime,
|
||||||
|
description: &'static str,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
bit_depth: u32,
|
||||||
|
color_count: u32,
|
||||||
|
img_data: &'static str,
|
||||||
|
},
|
||||||
|
VorbisComment {
|
||||||
|
vendor: &'static str,
|
||||||
|
comments: VorbisCommentTestValue,
|
||||||
|
pictures: &'static [FlacBlockOutput],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FlacTestCase {
|
||||||
|
Success {
|
||||||
|
/// This test's name
|
||||||
|
test_name: &'static str,
|
||||||
|
|
||||||
|
/// The file to use for this test
|
||||||
|
file_path: &'static str,
|
||||||
|
|
||||||
|
/// The hash of the input files
|
||||||
|
in_hash: &'static str,
|
||||||
|
|
||||||
|
/// The flac metablocks we expect to find in this file, in order.
|
||||||
|
blocks: Vec<FlacBlockOutput>,
|
||||||
|
|
||||||
|
/// The hash of the audio frames in this file
|
||||||
|
///
|
||||||
|
/// Get this hash by running `metaflac --remove-all --dont-use-padding`,
|
||||||
|
/// then by manually deleting remaining headers in a hex editor
|
||||||
|
/// (Remember that the sync sequence is 0xFF 0xF8)
|
||||||
|
audio_hash: &'static str,
|
||||||
|
|
||||||
|
/// The hash we should get when we strip this file's tags.
|
||||||
|
///
|
||||||
|
/// A stripped flac file has unmodified STREAMINFO, SEEKTABLE,
|
||||||
|
/// CUESHEET, and audio data blocks; and nothing else (not even padding).
|
||||||
|
///
|
||||||
|
/// Reference implementation:
|
||||||
|
/// ```notrust
|
||||||
|
/// metaflac \
|
||||||
|
/// --remove \
|
||||||
|
/// --block-type=PADDING,APPLICATION,VORBIS_COMMENT,PICTURE \
|
||||||
|
/// --dont-use-padding \
|
||||||
|
/// <file>
|
||||||
|
/// ```
|
||||||
|
stripped_hash: &'static str,
|
||||||
|
},
|
||||||
|
Error {
|
||||||
|
/// This test's name
|
||||||
|
test_name: &'static str,
|
||||||
|
|
||||||
|
/// The file to use for this test
|
||||||
|
file_path: &'static str,
|
||||||
|
|
||||||
|
/// The hash of the input files
|
||||||
|
in_hash: &'static str,
|
||||||
|
|
||||||
|
/// The error we should encounter while reading this file
|
||||||
|
check_error: &'static dyn Fn(&FlacDecodeError) -> bool,
|
||||||
|
|
||||||
|
/// If some, stripping this file's metadata should produce the given hash.
|
||||||
|
/// If none, trying to strip metadata should produce `check_error`
|
||||||
|
stripped_hash: Option<&'static str>,
|
||||||
|
|
||||||
|
/// If some, the following images should be extracted from this file
|
||||||
|
/// If none, trying to strip images should produce `check_error`
|
||||||
|
pictures: Option<Vec<FlacBlockOutput>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
|
impl FlacTestCase {
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Error { test_name, .. } | Self::Success { test_name, .. } => test_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_path(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Success { file_path, .. } | Self::Error { file_path, .. } => file_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_in_hash(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Success { in_hash, .. } | Self::Error { in_hash, .. } => in_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_stripped_hash(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Success { stripped_hash, .. } => Some(stripped_hash),
|
||||||
|
Self::Error { stripped_hash, .. } => *stripped_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_audio_hash(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Success { audio_hash, .. } => Some(audio_hash),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_blocks(&self) -> Option<&[FlacBlockOutput]> {
|
||||||
|
match self {
|
||||||
|
Self::Success { blocks, .. } => Some(blocks),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pictures(&self) -> Option<Vec<FlacBlockOutput>> {
|
||||||
|
match self {
|
||||||
|
Self::Success { blocks, .. } => {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for b in blocks {
|
||||||
|
match b {
|
||||||
|
FlacBlockOutput::Picture { .. } => out.push(b.clone()),
|
||||||
|
FlacBlockOutput::VorbisComment { pictures, .. } => {
|
||||||
|
for p in *pictures {
|
||||||
|
out.push(p.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Error { pictures, .. } => pictures.as_ref().map(|x| x.iter().cloned().collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of test files and their expected output
|
||||||
|
#[expect(clippy::unwrap_used)]
|
||||||
|
pub fn manifest() -> [FlacTestCase; 23] {
|
||||||
|
[
|
||||||
|
FlacTestCase::Error {
|
||||||
|
test_name: "uncommon_10",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_uncommon/10 - file starting at frame header.flac"
|
||||||
|
),
|
||||||
|
in_hash: "d95f63e8101320f5ac7ffe249bc429a209eb0e10996a987301eaa63386a8faa1",
|
||||||
|
check_error: &|x| matches!(x, FlacDecodeError::BadMagicBytes),
|
||||||
|
stripped_hash: None,
|
||||||
|
pictures: None,
|
||||||
|
},
|
||||||
|
FlacTestCase::Error {
|
||||||
|
test_name: "faulty_06",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_faulty/06 - missing streaminfo metadata block.flac"
|
||||||
|
),
|
||||||
|
in_hash: "53aed5e7fde7a652b82ba06a8382b2612b02ebbde7b0d2016276644d17cc76cd",
|
||||||
|
check_error: &|x| matches!(x, FlacDecodeError::BadFirstBlock),
|
||||||
|
stripped_hash: None,
|
||||||
|
pictures: None,
|
||||||
|
},
|
||||||
|
FlacTestCase::Error {
|
||||||
|
test_name: "faulty_07",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_faulty/07 - other metadata blocks preceding streaminfo metadata block.flac"
|
||||||
|
),
|
||||||
|
in_hash: "6d46725991ba5da477187fde7709ea201c399d00027257c365d7301226d851ea",
|
||||||
|
check_error: &|x| matches!(x, FlacDecodeError::BadFirstBlock),
|
||||||
|
stripped_hash: None,
|
||||||
|
pictures: None,
|
||||||
|
},
|
||||||
|
FlacTestCase::Error {
|
||||||
|
test_name: "faulty_10",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_faulty/10 - invalid vorbis comment metadata block.flac"
|
||||||
|
),
|
||||||
|
in_hash: "c79b0514a61634035a5653c5493797bbd1fcc78982116e4d429630e9e462d29b",
|
||||||
|
check_error: &|x| matches!(x, FlacDecodeError::IoError(_)),
|
||||||
|
// This file's vorbis comment is invalid, but that shouldn't stop us from removing it.
|
||||||
|
// As a general rule, we should NOT encounter an error when stripping invalid blocks.
|
||||||
|
//
|
||||||
|
// We should, however, get errors when we try to strip flac files with invalid structure.
|
||||||
|
// (For example, the out-of-order streaminfo test in faulty_07).
|
||||||
|
stripped_hash: Some("4b994f82dc1699a58e2b127058b37374220ee41dc294d4887ac14f056291a1b0"),
|
||||||
|
pictures: None,
|
||||||
|
},
|
||||||
|
FlacTestCase::Error {
|
||||||
|
test_name: "faulty_11",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_faulty/11 - incorrect metadata block length.flac"
|
||||||
|
),
|
||||||
|
in_hash: "3732151ba8c4e66a785165aa75a444aad814c16807ddc97b793811376acacfd6",
|
||||||
|
check_error: &|x| matches!(x, FlacDecodeError::BadMetablockType(127)),
|
||||||
|
stripped_hash: None,
|
||||||
|
pictures: None,
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_45",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/45 - no total number of samples set.flac"
|
||||||
|
),
|
||||||
|
in_hash: "336a18eb7a78f7fc0ab34980348e2895bc3f82db440a2430d9f92e996f889f9a",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 907,
|
||||||
|
max_frame_size: 8053,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 0,
|
||||||
|
md5_signature: "c41ae3b82c35d8f5c3dab1729f948fde",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "3fb3482ebc1724559bdd57f34de472458563d78a676029614e76e32b5d2b8816",
|
||||||
|
stripped_hash: "31631ac227ebe2689bac7caa1fa964b47e71a9f1c9c583a04ea8ebd9371508d0",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_46",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/46 - no min-max framesize set.flac"
|
||||||
|
),
|
||||||
|
in_hash: "9dc39732ce17815832790901b768bb50cd5ff0cd21b28a123c1cabc16ed776cc",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 0,
|
||||||
|
max_frame_size: 0,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 282866,
|
||||||
|
md5_signature: "fd131e6ebc75251ed83f8f4c07df36a4",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "a1eed422462b386a932b9eb3dff3aea3687b41eca919624fb574aadb7eb50040",
|
||||||
|
stripped_hash: "9e57cd77f285fc31f87fa4e3a31ab8395d68d5482e174c8e0d0bba9a0c20ba27",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_47",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/47 - only STREAMINFO.flac"
|
||||||
|
),
|
||||||
|
in_hash: "9a62c79f634849e74cb2183f9e3a9bd284f51e2591c553008d3e6449967eef85",
|
||||||
|
blocks: vec![FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 4747,
|
||||||
|
max_frame_size: 7034,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 232608,
|
||||||
|
md5_signature: "bba30c5f70789910e404b7ac727c3853",
|
||||||
|
}],
|
||||||
|
audio_hash: "5ee1450058254087f58c91baf0f70d14bde8782cf2dc23c741272177fe0fce6e",
|
||||||
|
stripped_hash: "9a62c79f634849e74cb2183f9e3a9bd284f51e2591c553008d3e6449967eef85",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_48",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/48 - Extremely large SEEKTABLE.flac"
|
||||||
|
),
|
||||||
|
in_hash: "4417aca6b5f90971c50c28766d2f32b3acaa7f9f9667bd313336242dae8b2531",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 2445,
|
||||||
|
max_frame_size: 7364,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 259884,
|
||||||
|
md5_signature: "97a0574290237563fbaa788ad77d2cdf",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Seektable {
|
||||||
|
hash: "21ca2184ae22fe26b690fd7cbd8d25fcde1d830ff6e5796ced4107bab219d7c0",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "c2d691f2c4c986fe3cd5fd7864d9ba9ce6dd68a4ffc670447f008434b13102c2",
|
||||||
|
stripped_hash: "abc9a0c40a29c896bc6e1cc0b374db1c8e157af716a5a3c43b7db1591a74c4e8",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_49",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/49 - Extremely large PADDING.flac",
|
||||||
|
),
|
||||||
|
in_hash: "7bc44fa2754536279fde4f8fb31d824f43b8d0b3f93d27d055d209682914f20e",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 1353,
|
||||||
|
max_frame_size: 7117,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 258939,
|
||||||
|
md5_signature: "6e78f221caaaa5d570a53f1714d84ded",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Padding { size: 16777215 },
|
||||||
|
],
|
||||||
|
audio_hash: "5007be7109b28b0149d1b929d2a0e93a087381bd3e68cf2a3ef78ea265ea20c3",
|
||||||
|
stripped_hash: "a2283bbacbc4905ad3df1bf9f43a0ea7aa65cf69523d84a7dd8eb54553cc437e",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_50",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/50 - Extremely large PICTURE.flac"
|
||||||
|
),
|
||||||
|
in_hash: "1f04f237d74836104993a8072d4223e84a5d3bd76fbc44555c221c7e69a23594",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 5099,
|
||||||
|
max_frame_size: 7126,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 265617,
|
||||||
|
md5_signature: "82164e4da30ed43b47e6027cef050648",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_JPEG,
|
||||||
|
description: "",
|
||||||
|
width: 3200,
|
||||||
|
height: 2252,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "b78c3a48fde4ebbe8e4090e544caeb8f81ed10020d57cc50b3265f9b338d8563",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "9778b25c5d1f56cfcd418e550baed14f9d6a4baf29489a83ed450fbebb28de8c",
|
||||||
|
stripped_hash: "20df129287d94f9ae5951b296d7f65fcbed92db423ba7db4f0d765f1f0a7e18c",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_51",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/51 - Extremely large VORBISCOMMENT.flac"
|
||||||
|
),
|
||||||
|
in_hash: "033160e8124ed287b0b5d615c94ac4139477e47d6e4059b1c19b7141566f5ef9",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 4531,
|
||||||
|
max_frame_size: 7528,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 289972,
|
||||||
|
md5_signature: "5ff622c88f8dd9bc201a6a541f3890d3",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Hash {
|
||||||
|
n_comments: 39,
|
||||||
|
hash: "01984e9ec0cfad41f27b3b4e84184966f6725ead84b7815bd0b3313549ee4229",
|
||||||
|
},
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "76419865d10eb22a74f020423a4e515e800f0177441676afd0418557c2d76c36",
|
||||||
|
stripped_hash: "c0ca6c6099b5d9ec53d6bb370f339b2b1570055813a6cd3616fac2db83a2185e",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_52",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/52 - Extremely large APPLICATION.flac"
|
||||||
|
),
|
||||||
|
in_hash: "0e45a4f8dbef15cbebdd8dfe690d8ae60e0c6abb596db1270a9161b62a7a3f1c",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 3711,
|
||||||
|
max_frame_size: 7056,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 317876,
|
||||||
|
md5_signature: "eb7140266bc194527488c21ab49bc47b",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Application {
|
||||||
|
application_id: 0x74657374,
|
||||||
|
hash: "cfc0b8969e4ba6bd507999ba89dea2d274df69d94749d6ae3cf117a7780bba09",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "89ad1a5c86a9ef35d33189c81c8a90285a23964a13f8325bf2c02043e8c83d63",
|
||||||
|
stripped_hash: "cc4a0afb95ec9bcde8ee33f13951e494dc4126a9a3a668d79c80ce3c14a3acd9",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_53",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/53 - CUESHEET with very many indexes.flac"
|
||||||
|
),
|
||||||
|
in_hash: "513fad18578f3225fae5de1bda8f700415be6fd8aa1e7af533b5eb796ed2d461",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 2798,
|
||||||
|
max_frame_size: 7408,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 2910025,
|
||||||
|
md5_signature: "d11f3717d628cfe6a90a10facc478340",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Seektable {
|
||||||
|
hash: "18629e1b874cb27e4364da72fb3fec2141eb0618baae4a1cee6ed09562aa00a8",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::CueSheet {
|
||||||
|
hash: "70638a241ca06881a52c0a18258ea2d8946a830137a70479c49746d2a1344bdd",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "e993070f2080f2c598be1d61d208e9187a55ddea4be1d2ed1f8043e7c03e97a5",
|
||||||
|
stripped_hash: "57c5b945e14c6fcd06916d6a57e5b036d67ff35757893c24ed872007aabbcf4b",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_54",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/54 - 1000x repeating VORBISCOMMENT.flac"
|
||||||
|
),
|
||||||
|
in_hash: "b68dc6644784fac35aa07581be8603a360d1697e07a2265d7eb24001936fd247",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 1694,
|
||||||
|
max_frame_size: 7145,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 433151,
|
||||||
|
md5_signature: "1d950e92b357dedbc5290a7f2210a2ef",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Hash {
|
||||||
|
n_comments: 20000,
|
||||||
|
hash: "0371b6f158411f35121f1d62ccbc18c90c9b1b0263e51bfc1b8fc942892eaf12",
|
||||||
|
},
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "4721b784058410c6263f73680079e9a71aee914c499afcf5580c121fce00e874",
|
||||||
|
stripped_hash: "5c8b92b83c0fa17821add38263fa323d1c66cfd2ee57aca054b50bd05b9df5c2",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_55",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/55 - file 48-53 combined.flac"
|
||||||
|
),
|
||||||
|
in_hash: "a756b460df79b7cc492223f80cda570e4511f2024e5fa0c4d505ba51b86191f6",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 3103,
|
||||||
|
max_frame_size: 11306,
|
||||||
|
sample_rate: 44100,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 2646000,
|
||||||
|
md5_signature: "2c78978cbbff11daac296fee97c3e061",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Seektable {
|
||||||
|
hash: "58dfa7bac4974edf1956b068f5aa72d1fbd9301c36a3085a8a57b9db11a2dbf0",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.3 20190804",
|
||||||
|
comments: VorbisCommentTestValue::Hash {
|
||||||
|
n_comments: 40036,
|
||||||
|
hash: "8d8f21954b4aaee2c7ec92389125a9b28b7de5a8153c62abdd80330f445214df",
|
||||||
|
},
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::CueSheet {
|
||||||
|
hash: "db11916c8f5f39648256f93f202e00ff8d73d7d96b62f749b4c77cf3ea744f90",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Application {
|
||||||
|
application_id: 0x74657374,
|
||||||
|
hash: "6088a557a1bad7bfa5ebf79a324669fbf4fa2f8e708f5487305dfc5b2ff2249a",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_JPEG,
|
||||||
|
description: "",
|
||||||
|
width: 3200,
|
||||||
|
height: 2252,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "b78c3a48fde4ebbe8e4090e544caeb8f81ed10020d57cc50b3265f9b338d8563",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Padding { size: 16777215 },
|
||||||
|
],
|
||||||
|
audio_hash: "f1285b77cec7fa9a0979033244489a9d06b8515b2158e9270087a65a4007084d",
|
||||||
|
stripped_hash: "401038fce06aff5ebdc7a5f2fc01fa491cbf32d5da9ec99086e414b2da3f8449",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_56",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/56 - JPG PICTURE.flac"
|
||||||
|
),
|
||||||
|
in_hash: "5cebe7a3710cf8924bd2913854e9ca60b4cd53cfee5a3af0c3c73fddc1888963",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 3014,
|
||||||
|
max_frame_size: 7219,
|
||||||
|
sample_rate: 44100,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 220026,
|
||||||
|
md5_signature: "5b0e898d9c2626d0c28684f5a586813f",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_JPEG,
|
||||||
|
description: "",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "7a3ed658f80f433eee3914fff451ea0312807de0af709e37cc6a4f3f6e8a47c6",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "ccfe90b0f15cd9662f7a18f40cd4c347538cf8897a08228e75351206f7804573",
|
||||||
|
stripped_hash: "31a38d59db2010790b7abf65ec0cc03f2bbe1fed5952bc72bee4ca4d0c92e79f",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_57",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/57 - PNG PICTURE.flac"
|
||||||
|
),
|
||||||
|
in_hash: "c6abff7f8bb63c2821bd21dd9052c543f10ba0be878e83cb419c248f14f72697",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 463,
|
||||||
|
max_frame_size: 6770,
|
||||||
|
sample_rate: 44100,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 221623,
|
||||||
|
md5_signature: "ad16957bcf8d5a3ec8caf261e43d5ff7",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_PNG,
|
||||||
|
description: "",
|
||||||
|
width: 960,
|
||||||
|
height: 540,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "39bf9981613ac2f35d253c0c21b76a48abba7792c27da5dbf23e6021e2e6673f",
|
||||||
|
stripped_hash: "3328201dd56289b6c81fa90ff26cb57fa9385cb0db197e89eaaa83efd79a58b1",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_58",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/58 - GIF PICTURE.flac"
|
||||||
|
),
|
||||||
|
in_hash: "7c2b1a963a665847167a7275f9924f65baeb85c21726c218f61bf3f803f301c8",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 2853,
|
||||||
|
max_frame_size: 6683,
|
||||||
|
sample_rate: 44100,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 219826,
|
||||||
|
md5_signature: "7c1810602a7db96d7a48022ac4aa495c",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_GIF,
|
||||||
|
description: "",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 32,
|
||||||
|
img_data: "e33cccc1d799eb2bb618f47be7099cf02796df5519f3f0e1cc258606cf6e8bb1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "30e3292e9f56cf88658eeadfdec8ad3a440690ce6d813e1b3374f60518c8e0ae",
|
||||||
|
stripped_hash: "4cd771e27870e2a586000f5b369e0426183a521b61212302a2f5802b046910b2",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "subset_59",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_subset/59 - AVIF PICTURE.flac"
|
||||||
|
),
|
||||||
|
in_hash: "7395d02bf8d9533dc554cce02dee9de98c77f8731a45f62d0a243bd0d6f9a45c",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 153,
|
||||||
|
max_frame_size: 7041,
|
||||||
|
sample_rate: 44100,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 221423,
|
||||||
|
md5_signature: "d354246011ca204159c06f52cad5f634",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: Mime::from_str("image/avif").unwrap(),
|
||||||
|
description: "",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "a431123040c74f75096237f20544a7fb56b4eb71ddea62efa700b0a016f5b2fc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "b208c73d274e65b27232bfffbfcbcf4805ee3cbc9cfbf7d2104db8f53370273b",
|
||||||
|
stripped_hash: "d5215e16c6b978fc2c3e6809e1e78981497cb8514df297c5169f3b4a28fd875c",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "custom_01",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_custom/01 - many images.flac"
|
||||||
|
),
|
||||||
|
in_hash: "8a5df37488866cd91ac16773e549ef4e3a85d9f88a0d9d345f174807bb536b96",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 5099,
|
||||||
|
max_frame_size: 7126,
|
||||||
|
sample_rate: 48000,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 265617,
|
||||||
|
md5_signature: "82164e4da30ed43b47e6027cef050648",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_PNG,
|
||||||
|
description: "",
|
||||||
|
width: 960,
|
||||||
|
height: 540,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_JPEG,
|
||||||
|
description: "",
|
||||||
|
width: 3200,
|
||||||
|
height: 2252,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "b78c3a48fde4ebbe8e4090e544caeb8f81ed10020d57cc50b3265f9b338d8563",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::ABrightColoredFish,
|
||||||
|
mime: mime::IMAGE_JPEG,
|
||||||
|
description: "lorem",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "7a3ed658f80f433eee3914fff451ea0312807de0af709e37cc6a4f3f6e8a47c6",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::OtherFileIcon,
|
||||||
|
mime: mime::IMAGE_PNG,
|
||||||
|
description: "ipsum",
|
||||||
|
width: 960,
|
||||||
|
height: 540,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::Lyricist,
|
||||||
|
mime: mime::IMAGE_GIF,
|
||||||
|
description: "dolor",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 32,
|
||||||
|
img_data: "e33cccc1d799eb2bb618f47be7099cf02796df5519f3f0e1cc258606cf6e8bb1",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::BackCover,
|
||||||
|
mime: Mime::from_str("image/avif").unwrap(),
|
||||||
|
description: "est",
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "a431123040c74f75096237f20544a7fb56b4eb71ddea62efa700b0a016f5b2fc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "9778b25c5d1f56cfcd418e550baed14f9d6a4baf29489a83ed450fbebb28de8c",
|
||||||
|
stripped_hash: "20df129287d94f9ae5951b296d7f65fcbed92db423ba7db4f0d765f1f0a7e18c",
|
||||||
|
},
|
||||||
|
FlacTestCase::Success {
|
||||||
|
test_name: "custom_02",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_custom/02 - picture in vorbis comment.flac"
|
||||||
|
),
|
||||||
|
in_hash: "f6bb1a726fe6a3e25a4337d36e29fdced8ff01a46d627b7c2e1988c88f461f8c",
|
||||||
|
blocks: vec![
|
||||||
|
FlacBlockOutput::Streaminfo {
|
||||||
|
min_block_size: 4096,
|
||||||
|
max_block_size: 4096,
|
||||||
|
min_frame_size: 463,
|
||||||
|
max_frame_size: 6770,
|
||||||
|
sample_rate: 44100,
|
||||||
|
channels: 2,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
total_samples: 221623,
|
||||||
|
md5_signature: "ad16957bcf8d5a3ec8caf261e43d5ff7",
|
||||||
|
},
|
||||||
|
FlacBlockOutput::VorbisComment {
|
||||||
|
vendor: "reference libFLAC 1.3.2 20170101",
|
||||||
|
comments: VorbisCommentTestValue::Raw { tags: &[] },
|
||||||
|
pictures: &[FlacBlockOutput::Picture {
|
||||||
|
picture_type: PictureType::FrontCover,
|
||||||
|
mime: mime::IMAGE_PNG,
|
||||||
|
description: "",
|
||||||
|
width: 960,
|
||||||
|
height: 540,
|
||||||
|
bit_depth: 24,
|
||||||
|
color_count: 0,
|
||||||
|
img_data: "d804e5c7b9ee5af694b5e301c6cdf64508ff85997deda49d2250a06a964f10b2",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
audio_hash: "39bf9981613ac2f35d253c0c21b76a48abba7792c27da5dbf23e6021e2e6673f",
|
||||||
|
stripped_hash: "3328201dd56289b6c81fa90ff26cb57fa9385cb0db197e89eaaa83efd79a58b1",
|
||||||
|
},
|
||||||
|
FlacTestCase::Error {
|
||||||
|
test_name: "custom_03",
|
||||||
|
file_path: concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/tests/files/flac_custom/03 - faulty picture in vorbis comment.flac"
|
||||||
|
),
|
||||||
|
in_hash: "7177f0ae4f04a563292be286ec05967f81ab16eb0a28b70fc07a1e47da9cafd0",
|
||||||
|
check_error: &|x| matches!(x, FlacDecodeError::MalformedPicture(_)),
|
||||||
|
stripped_hash: Some("3328201dd56289b6c81fa90ff26cb57fa9385cb0db197e89eaaa83efd79a58b1"),
|
||||||
|
pictures: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn manifest_sanity_check() {
|
||||||
|
assert!(manifest().iter().map(|x| x.get_name()).all_unique());
|
||||||
|
assert!(manifest().iter().map(|x| x.get_path()).all_unique());
|
||||||
|
}
|
||||||
194
crates/pile-flac/src/vorbiscomment.rs
Normal file
194
crates/pile-flac/src/vorbiscomment.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
//! Decode and write Vorbis comment blocks
|
||||||
|
|
||||||
|
use base64::Engine;
|
||||||
|
use smartstring::{LazyCompact, SmartString};
|
||||||
|
use std::{
|
||||||
|
io::{Cursor, Read, Write},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::tagtype::TagType;
|
||||||
|
use crate::{
|
||||||
|
FlacDecodeError, FlacEncodeError,
|
||||||
|
blocks::{FlacMetablockDecode, FlacMetablockEncode, FlacPictureBlock},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A decoded vorbis comment block
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VorbisComment {
|
||||||
|
/// This comment's vendor string
|
||||||
|
pub vendor: SmartString<LazyCompact>,
|
||||||
|
|
||||||
|
/// List of (tag, value)
|
||||||
|
/// Repeated tags are allowed!
|
||||||
|
pub comments: Vec<(TagType, SmartString<LazyCompact>)>,
|
||||||
|
|
||||||
|
/// A list of pictures found in this comment
|
||||||
|
pub pictures: Vec<FlacPictureBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VorbisComment {
|
||||||
|
/// Try to decode the given data as a vorbis comment block
|
||||||
|
pub fn decode(data: &[u8]) -> Result<Self, FlacDecodeError> {
|
||||||
|
let mut d = Cursor::new(data);
|
||||||
|
|
||||||
|
// This is re-used whenever we need to read four bytes
|
||||||
|
let mut block = [0u8; 4];
|
||||||
|
|
||||||
|
let vendor = {
|
||||||
|
d.read_exact(&mut block)?;
|
||||||
|
let length = u32::from_le_bytes(block);
|
||||||
|
|
||||||
|
#[expect(clippy::expect_used)]
|
||||||
|
let mut text = vec![
|
||||||
|
0u8;
|
||||||
|
length
|
||||||
|
.try_into()
|
||||||
|
.expect("vendor length does not fit into usize")
|
||||||
|
];
|
||||||
|
|
||||||
|
d.read_exact(&mut text)?;
|
||||||
|
String::from_utf8(text)?
|
||||||
|
};
|
||||||
|
|
||||||
|
d.read_exact(&mut block)?;
|
||||||
|
|
||||||
|
#[expect(clippy::expect_used)]
|
||||||
|
let n_comments: usize = u32::from_le_bytes(block)
|
||||||
|
.try_into()
|
||||||
|
.expect("comment count does not fit into usize");
|
||||||
|
|
||||||
|
let mut comments = Vec::new();
|
||||||
|
let mut pictures = Vec::new();
|
||||||
|
for _ in 0..n_comments {
|
||||||
|
let comment = {
|
||||||
|
d.read_exact(&mut block)?;
|
||||||
|
|
||||||
|
let length = u32::from_le_bytes(block);
|
||||||
|
|
||||||
|
#[expect(clippy::expect_used)]
|
||||||
|
let mut text = vec![
|
||||||
|
0u8;
|
||||||
|
length
|
||||||
|
.try_into()
|
||||||
|
.expect("comment length does not fit into usize")
|
||||||
|
];
|
||||||
|
|
||||||
|
d.read_exact(&mut text)?;
|
||||||
|
|
||||||
|
String::from_utf8(text)?
|
||||||
|
};
|
||||||
|
let (var, val) = comment
|
||||||
|
.split_once('=')
|
||||||
|
.ok_or(FlacDecodeError::MalformedCommentString(comment.clone()))?;
|
||||||
|
|
||||||
|
if !val.is_empty() {
|
||||||
|
if var.to_uppercase() == "METADATA_BLOCK_PICTURE" {
|
||||||
|
pictures.push(FlacPictureBlock::decode(
|
||||||
|
&base64::prelude::BASE64_STANDARD
|
||||||
|
.decode(val)
|
||||||
|
.map_err(FlacDecodeError::MalformedPicture)?,
|
||||||
|
)?);
|
||||||
|
} else {
|
||||||
|
// Make sure empty strings are saved as "None"
|
||||||
|
comments.push((
|
||||||
|
TagType::from_str(var).unwrap_or(TagType::Other(var.into())),
|
||||||
|
val.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
vendor: vendor.into(),
|
||||||
|
comments,
|
||||||
|
pictures,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VorbisComment {
|
||||||
|
/// Get the number of bytes that `encode()` will write.
|
||||||
|
#[expect(clippy::expect_used)]
|
||||||
|
pub fn get_len(&self) -> u32 {
|
||||||
|
let mut sum: u32 = 0;
|
||||||
|
sum += u32::try_from(self.vendor.len()).expect("vendor length does not fit into u32") + 4;
|
||||||
|
sum += 4;
|
||||||
|
|
||||||
|
for (tagtype, value) in &self.comments {
|
||||||
|
let tagtype_str = tagtype.to_vorbis_string();
|
||||||
|
let str = format!("{tagtype_str}={value}");
|
||||||
|
sum +=
|
||||||
|
4 + u32::try_from(str.len()).expect("comment string length does not fit into u32");
|
||||||
|
}
|
||||||
|
|
||||||
|
for p in &self.pictures {
|
||||||
|
// Compute b64 len
|
||||||
|
let mut x = p.get_len();
|
||||||
|
if x % 3 != 0 {
|
||||||
|
x -= x % 3;
|
||||||
|
x += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::integer_division)]
|
||||||
|
{
|
||||||
|
sum += 4 * (x / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "METADATA_BLOCK_PICTURE="
|
||||||
|
sum += 23;
|
||||||
|
|
||||||
|
// Add length bytes
|
||||||
|
sum += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to encode this vorbis comment
|
||||||
|
#[expect(clippy::expect_used)]
|
||||||
|
pub fn encode(&self, target: &mut impl Write) -> Result<(), FlacEncodeError> {
|
||||||
|
target.write_all(
|
||||||
|
&u32::try_from(self.vendor.len())
|
||||||
|
.expect("vendor length does not fit into u32")
|
||||||
|
.to_le_bytes(),
|
||||||
|
)?;
|
||||||
|
target.write_all(self.vendor.as_bytes())?;
|
||||||
|
|
||||||
|
target.write_all(
|
||||||
|
&u32::try_from(self.comments.len() + self.pictures.len())
|
||||||
|
.expect("total comment count does not fit into u32")
|
||||||
|
.to_le_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for (tagtype, value) in &self.comments {
|
||||||
|
let tagtype_str = tagtype.to_vorbis_string();
|
||||||
|
let str = format!("{tagtype_str}={value}");
|
||||||
|
target.write_all(
|
||||||
|
&u32::try_from(str.len())
|
||||||
|
.expect("comment string length does not fit into u32")
|
||||||
|
.to_le_bytes(),
|
||||||
|
)?;
|
||||||
|
target.write_all(str.as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for p in &self.pictures {
|
||||||
|
let mut pic_data = Vec::new();
|
||||||
|
p.encode(false, false, &mut pic_data)?;
|
||||||
|
|
||||||
|
let pic_string = format!(
|
||||||
|
"METADATA_BLOCK_PICTURE={}",
|
||||||
|
&base64::prelude::BASE64_STANDARD.encode(&pic_data)
|
||||||
|
);
|
||||||
|
|
||||||
|
target.write_all(
|
||||||
|
&u32::try_from(pic_string.len())
|
||||||
|
.expect("picture string length does not fit into u32")
|
||||||
|
.to_le_bytes(),
|
||||||
|
)?;
|
||||||
|
target.write_all(pic_string.as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user