From d264b84c902cec4dec2b7b9711a831a7e86ecd49 Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Wed, 26 Oct 2022 20:50:11 -0400 Subject: [PATCH] Add m3u8 loading function syntax -- playlist(filepath) --- Cargo.lock | 462 ++++++++------------ interpreter/Cargo.toml | 11 +- interpreter/src/interpretor.rs | 1 + interpreter/src/lang/generator_op.rs | 78 ++++ interpreter/src/lang/mod.rs | 2 + interpreter/src/lang/vocabulary/files.rs | 2 +- interpreter/src/lang/vocabulary/mod.rs | 2 + interpreter/src/lang/vocabulary/playlist.rs | 153 +++++++ interpreter/src/music/library.rs | 24 +- interpreter/src/music/tag.rs | 216 ++++++++- interpreter/src/processing/filesystem.rs | 68 ++- interpreter/tests/single_line.rs | 15 + 12 files changed, 718 insertions(+), 316 deletions(-) create mode 100644 interpreter/src/lang/generator_op.rs create mode 100644 interpreter/src/lang/vocabulary/playlist.rs diff --git a/Cargo.lock b/Cargo.lock index feec813..1ba581a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,6 +97,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bindgen" version = "0.59.2" @@ -168,7 +174,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "symphonia 0.5.0", + "symphonia", "thiserror", ] @@ -440,27 +446,27 @@ dependencies = [ [[package]] name = "cpal" -version = "0.13.5" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116" +checksum = "7d466b47cf0ea4100186a7c12d7d0166813dda7cf648553554c9c39c6324841b" dependencies = [ "alsa", "core-foundation-sys", "coreaudio-rs", "jni", "js-sys", - "lazy_static 1.4.0", "libc", "mach", - "ndk", - "ndk-glue", + "ndk 0.7.0", + "ndk-context", "nix", "oboe", + "once_cell", "parking_lot", "stdweb", "thiserror", "web-sys", - "winapi 0.3.9", + "windows", ] [[package]] @@ -610,39 +616,10 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.13.4" +name = "cty" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn", -] +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "dbus" @@ -814,12 +791,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "fs_extra" version = "1.2.0" @@ -939,12 +910,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "indexmap" version = "1.9.1" @@ -955,15 +920,6 @@ dependencies = [ "hashbrown 0.12.2", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "itertools" version = "0.10.3" @@ -1258,16 +1214,18 @@ dependencies = [ name = "muss-interpreter" version = "0.9.0" dependencies = [ + "base64", "bliss-audio-symphonia", "criterion", "dirs", + "m3u8-rs", "mpd", "rand", "regex 1.6.0", "rusqlite", "shellexpand", "sqlparser", - "symphonia 0.5.0", + "symphonia", "unidecode", ] @@ -1343,45 +1301,31 @@ checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" dependencies = [ "bitflags", "jni-sys", - "ndk-sys", + "ndk-sys 0.3.0", "num_enum", "thiserror", ] +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.4.0", + "num_enum", + "raw-window-handle", + "thiserror", +] + [[package]] name = "ndk-context" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-glue" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" -dependencies = [ - "lazy_static 1.4.0", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-macro", - "ndk-sys", -] - -[[package]] -name = "ndk-macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" -dependencies = [ - "darling", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ndk-sys" version = "0.3.0" @@ -1391,6 +1335,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d83ec9c63ec5bf950200a8e508bdad6659972187b625469f58ef8c08e29046" +dependencies = [ + "jni-sys", +] + [[package]] name = "nix" version = "0.23.1" @@ -1542,7 +1495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" dependencies = [ "jni", - "ndk", + "ndk 0.6.0", "ndk-context", "num-derive", "num-traits", @@ -1608,27 +1561,25 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -1836,6 +1787,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -1947,16 +1907,16 @@ dependencies = [ [[package]] name = "rodio" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0939e9f626e6c6f1989adb6226a039c855ca483053f0ee7c98b90e41cf731e" +checksum = "eb10b653d5ec0e9411a2e7d46e2c7f4046fd87d35b9955bd73ba4108d69072b5" dependencies = [ "claxon", "cpal", "hound", "lewton", "minimp3", - "symphonia 0.4.0", + "symphonia", ] [[package]] @@ -2157,25 +2117,6 @@ dependencies = [ "syn", ] -[[package]] -name = "symphonia" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e5f38aa07e792f4eebb0faa93cee088ec82c48222dd332897aae1569d9a4b7" -dependencies = [ - "lazy_static 1.4.0", - "symphonia-bundle-flac 0.4.0", - "symphonia-bundle-mp3 0.4.0", - "symphonia-codec-aac 0.4.0", - "symphonia-codec-pcm 0.4.0", - "symphonia-codec-vorbis 0.4.0", - "symphonia-core 0.4.0", - "symphonia-format-isomp4 0.4.0", - "symphonia-format-ogg 0.4.0", - "symphonia-format-wav 0.4.1", - "symphonia-metadata 0.4.0", -] - [[package]] name = "symphonia" version = "0.5.0" @@ -2183,30 +2124,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb30457ee7a904dae1e4ace25156dcabaf71e425db318e7885267f09cd8fb648" dependencies = [ "lazy_static 1.4.0", - "symphonia-bundle-flac 0.5.0", - "symphonia-bundle-mp3 0.5.0", - "symphonia-codec-aac 0.5.0", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", "symphonia-codec-alac", - "symphonia-codec-pcm 0.5.0", - "symphonia-codec-vorbis 0.5.0", - "symphonia-core 0.5.0", - "symphonia-format-isomp4 0.5.0", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", "symphonia-format-mkv", - "symphonia-format-ogg 0.5.0", - "symphonia-format-wav 0.5.0", - "symphonia-metadata 0.5.0", -] - -[[package]] -name = "symphonia-bundle-flac" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "116e5412f5fb4e5d07efd6628d50d6fcd7a61ebef43d98f5012f3cf763b25d02" -dependencies = [ - "log", - "symphonia-core 0.4.0", - "symphonia-metadata 0.4.0", - "symphonia-utils-xiph 0.4.0", + "symphonia-format-ogg", + "symphonia-format-wav", + "symphonia-metadata", ] [[package]] @@ -2216,22 +2145,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f34f8f90825ee2692df0ee64981312267d6cf640358c3db7a4805d1805340665" dependencies = [ "log", - "symphonia-core 0.5.0", - "symphonia-metadata 0.5.0", - "symphonia-utils-xiph 0.5.0", -] - -[[package]] -name = "symphonia-bundle-mp3" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4d97c4a61ece4651751dddb393ebecb7579169d9e758ae808fe507a5250790" -dependencies = [ - "bitflags", - "lazy_static 1.4.0", - "log", - "symphonia-core 0.4.0", - "symphonia-metadata 0.4.0", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] @@ -2243,19 +2159,8 @@ dependencies = [ "bitflags", "lazy_static 1.4.0", "log", - "symphonia-core 0.5.0", - "symphonia-metadata 0.5.0", -] - -[[package]] -name = "symphonia-codec-aac" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd3d7ab37eb9b7df16ddedd7adb7cc382afe708ff078e525a14dc9b05e57558f" -dependencies = [ - "lazy_static 1.4.0", - "log", - "symphonia-core 0.4.0", + "symphonia-core", + "symphonia-metadata", ] [[package]] @@ -2266,7 +2171,7 @@ checksum = "96671dbcf83a4415e899812c5820dd26f48b9a6fece8b8d680e3004553080468" dependencies = [ "lazy_static 1.4.0", "log", - "symphonia-core 0.5.0", + "symphonia-core", ] [[package]] @@ -2276,17 +2181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a95d0cc9d94c55d9467e71e26990e509bd5a602fabde3ee422d87f77bbda860a" dependencies = [ "log", - "symphonia-core 0.5.0", -] - -[[package]] -name = "symphonia-codec-pcm" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1d54738758993546107e3a4be2c1da827f2d4489fcffee0fa47867254e44c7" -dependencies = [ - "log", - "symphonia-core 0.4.0", + "symphonia-core", ] [[package]] @@ -2296,18 +2191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0812a197602dff1f963ff212f174c4aa4d9b695d6511ba7a8fe2470296cf8310" dependencies = [ "log", - "symphonia-core 0.5.0", -] - -[[package]] -name = "symphonia-codec-vorbis" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a29ed6748078effb35a05064a451493a78038918981dc1a76bdf5a2752d441fa" -dependencies = [ - "log", - "symphonia-core 0.4.0", - "symphonia-utils-xiph 0.4.0", + "symphonia-core", ] [[package]] @@ -2317,21 +2201,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "746fc459966b37e277565f9632e5ffd6cbd83d9381152727123f68484cb8f9c4" dependencies = [ "log", - "symphonia-core 0.5.0", - "symphonia-utils-xiph 0.5.0", -] - -[[package]] -name = "symphonia-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa135e97be0f4a666c31dfe5ef4c75435ba3d355fd6a73d2100aa79b14c104c9" -dependencies = [ - "arrayvec", - "bitflags", - "bytemuck", - "lazy_static 1.4.0", - "log", + "symphonia-core", + "symphonia-utils-xiph", ] [[package]] @@ -2347,18 +2218,6 @@ dependencies = [ "log", ] -[[package]] -name = "symphonia-format-isomp4" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feee3a7711e7ec1b7540756f3868bbb3cbb0d1195569b9bc26471a24a02f57b5" -dependencies = [ - "encoding_rs", - "log", - "symphonia-core 0.4.0", - "symphonia-metadata 0.4.0", -] - [[package]] name = "symphonia-format-isomp4" version = "0.5.0" @@ -2367,9 +2226,9 @@ checksum = "2a335816c1840bf3ce92b968a93b7b5c14a5d74737ad9ed63567ea451eac1951" dependencies = [ "encoding_rs", "log", - "symphonia-core 0.5.0", - "symphonia-metadata 0.5.0", - "symphonia-utils-xiph 0.5.0", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] @@ -2380,21 +2239,9 @@ checksum = "901a52e62285b3794a3ecb9b8a00b1d92d639e0dabf51eac0823a16493752726" dependencies = [ "lazy_static 1.4.0", "log", - "symphonia-core 0.5.0", - "symphonia-metadata 0.5.0", - "symphonia-utils-xiph 0.5.0", -] - -[[package]] -name = "symphonia-format-ogg" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b2357288a79adfec532cfd86049696cfa5c58efeff83bd51687a528f18a519" -dependencies = [ - "log", - "symphonia-core 0.4.0", - "symphonia-metadata 0.4.0", - "symphonia-utils-xiph 0.4.0", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] @@ -2404,20 +2251,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5b92a2a6370873d9dbe3326dad1bf795b3151efcadca6e5f47d732499a518" dependencies = [ "log", - "symphonia-core 0.5.0", - "symphonia-metadata 0.5.0", - "symphonia-utils-xiph 0.5.0", -] - -[[package]] -name = "symphonia-format-wav" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9fa5e5b420dea6763ba2547887eb1a02a142c676c5b02ed1b113a247101dad" -dependencies = [ - "log", - "symphonia-core 0.4.0", - "symphonia-metadata 0.4.0", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", ] [[package]] @@ -2427,20 +2263,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b2016576a9f7e5e95f9354993116458170a9077845a908ee67a4c81e8072c0" dependencies = [ "log", - "symphonia-core 0.5.0", - "symphonia-metadata 0.5.0", -] - -[[package]] -name = "symphonia-metadata" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5260599daba18d8fe905ca3eb3b42ba210529a6276886632412cc74984e79b1a" -dependencies = [ - "encoding_rs", - "lazy_static 1.4.0", - "log", - "symphonia-core 0.4.0", + "symphonia-core", + "symphonia-metadata", ] [[package]] @@ -2452,17 +2276,7 @@ dependencies = [ "encoding_rs", "lazy_static 1.4.0", "log", - "symphonia-core 0.5.0", -] - -[[package]] -name = "symphonia-utils-xiph" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37026c6948ff842e0bf94b4008579cc71ab16ed0ff9ca70a331f60f4f1e1e9" -dependencies = [ - "symphonia-core 0.4.0", - "symphonia-metadata 0.4.0", + "symphonia-core", ] [[package]] @@ -2471,8 +2285,8 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abadfa53359fa437836f2554a0019dd06bfdf742fbb735d0645db3b6c5a763e0" dependencies = [ - "symphonia-core 0.5.0", - "symphonia-metadata 0.5.0", + "symphonia-core", + "symphonia-metadata", ] [[package]] @@ -2813,6 +2627,92 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "zip" version = "0.5.13" diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml index 7352002..a939e55 100644 --- a/interpreter/Cargo.toml +++ b/interpreter/Cargo.toml @@ -19,6 +19,8 @@ shellexpand = { version = "2", optional = true } bliss-audio-symphonia = { version = "0.5", optional = true, path = "../bliss-rs" } mpd = { version = "0.0.12", optional = true } unidecode = { version = "0.3.0", optional = true } +base64 = { version = "0.13", optional = true } +m3u8-rs = { version = "3.0.0", optional = true } [dev-dependencies] criterion = "0.3" @@ -28,10 +30,11 @@ name = "file_parse" harness = false [features] -default = [ "music_library", "ergonomics", "advanced", "advanced-bliss", "fakesql" ] -music_library = [ "symphonia", "mpd" ] # song metadata parsing and database auto-population +default = [ "music_library", "ergonomics", "advanced", "advanced-bliss", "fakesql", "collections" ] +music_library = [ "symphonia", "mpd", "base64" ] # song metadata parsing and database auto-population +collections = [ "m3u8-rs" ] # read from m3u8 playlists (and other song collections, eventually) ergonomics = ["shellexpand", "unidecode"] # niceties like ~ in paths and unicode string sanitisation advanced = [] # advanced language features like music analysis advanced-bliss = ["bliss-audio-symphonia"] # bliss audio analysis -sql = [ "rusqlite" ] -fakesql = [ "sqlparser" ] +sql = [ "rusqlite" ] # sqlite database for music +fakesql = [ "sqlparser" ] # transpiled sqlite interpreter diff --git a/interpreter/src/interpretor.rs b/interpreter/src/interpretor.rs index a4cc186..5b7f392 100644 --- a/interpreter/src/interpretor.rs +++ b/interpreter/src/interpretor.rs @@ -54,6 +54,7 @@ pub(crate) fn standard_vocab(vocabulary: &mut LanguageDictionary) { .add(crate::lang::vocabulary::AssignStatementFactory) .add(crate::lang::vocabulary::sql_init_function_factory()) .add(crate::lang::vocabulary::files_function_factory()) + .add(crate::lang::vocabulary::playlist_function_factory()) .add(crate::lang::vocabulary::empty_function_factory()) .add(crate::lang::vocabulary::empties_function_factory()) .add(crate::lang::vocabulary::reset_function_factory()) diff --git a/interpreter/src/lang/generator_op.rs b/interpreter/src/lang/generator_op.rs new file mode 100644 index 0000000..168890d --- /dev/null +++ b/interpreter/src/lang/generator_op.rs @@ -0,0 +1,78 @@ +use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::Iterator; + +use crate::lang::{IteratorItem, Op, RuntimeError}; +use crate::lang::{RuntimeOp, RuntimeMsg, PseudoOp}; +use crate::Context; +use crate::Item; + +pub struct GeneratorOp { + context: Option, + generator: Box Option>>, +} + +impl GeneratorOp { + pub fn new Option> + 'static>(generator_fn: F) -> Self { + Self { + context: None, + generator: Box::new(generator_fn), + } + } +} + +impl Display for GeneratorOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "*generator*[...]") + } +} + +impl Debug for GeneratorOp { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_struct("GeneratorOp") + .field("context", &self.context) + .field("generator", &"") + .finish() + } +} + +impl Iterator for GeneratorOp { + type Item = IteratorItem; + + fn next(&mut self) -> Option { + let ctx = self.context.as_mut().unwrap(); + match (self.generator)(ctx) { + Some(Err(e)) => Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))), + Some(Ok(item)) => Some(Ok(item)), + None => None, + } + } +} + +impl Op for GeneratorOp { + fn enter(&mut self, ctx: Context) { + self.context = Some(ctx) + } + + fn escape(&mut self) -> Context { + self.context.take().unwrap() + } + + fn is_resetable(&self) -> bool { + false + } + + fn reset(&mut self) -> Result<(), RuntimeError> { + Err( + RuntimeMsg("Cannot reset generator op".to_string()) + .with(RuntimeOp(PseudoOp::from_printable(self))) + ) + } + + fn dup(&self) -> Box { + // this shouldn't be called + Box::new(Self { + context: None, + generator: Box::new(|_| None) + }) + } +} diff --git a/interpreter/src/lang/mod.rs b/interpreter/src/lang/mod.rs index 2ca431c..c41a88d 100644 --- a/interpreter/src/lang/mod.rs +++ b/interpreter/src/lang/mod.rs @@ -7,6 +7,7 @@ mod error; mod filter; mod filter_replace; mod function; +mod generator_op; mod iter_block; mod lookup; mod operation; @@ -25,6 +26,7 @@ pub use error::{RuntimeError, RuntimeMsg, RuntimeOp, SyntaxError}; pub use filter::{FilterFactory, FilterPredicate, FilterStatement, FilterStatementFactory}; pub use filter_replace::FilterReplaceStatement; pub use function::{FunctionFactory, FunctionStatementFactory}; +pub use generator_op::GeneratorOp; pub use iter_block::{ItemBlockFactory, ItemOp, ItemOpFactory}; pub use lookup::Lookup; pub use operation::{BoxedOpFactory, IteratorItem, Op, OpFactory, SimpleOpFactory}; diff --git a/interpreter/src/lang/vocabulary/files.rs b/interpreter/src/lang/vocabulary/files.rs index 0a058aa..75f3d1a 100644 --- a/interpreter/src/lang/vocabulary/files.rs +++ b/interpreter/src/lang/vocabulary/files.rs @@ -87,7 +87,7 @@ impl Iterator for FilesStatement { Some(Ok(item)) => Some(Ok(item)), Some(Err(e)) => Some(Err(RuntimeError { line: 0, - op: (Box::new(self.clone()) as Box).into(), + op: PseudoOp::from_printable(self), msg: e, })), None => None, diff --git a/interpreter/src/lang/vocabulary/mod.rs b/interpreter/src/lang/vocabulary/mod.rs index fb1c6d2..4773811 100644 --- a/interpreter/src/lang/vocabulary/mod.rs +++ b/interpreter/src/lang/vocabulary/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod empty; mod files; mod intersection; mod mpd_query; +mod playlist; mod repeat; mod reset; mod sql_init; @@ -17,6 +18,7 @@ pub use empty::{empty_function_factory, EmptyStatementFactory}; pub use files::{files_function_factory, FilesStatementFactory}; pub use intersection::{intersection_function_factory, IntersectionStatementFactory}; pub use mpd_query::{mpd_query_function_factory, MpdQueryStatementFactory}; +pub use playlist::{playlist_function_factory, PlaylistStatementFactory}; pub use repeat::{repeat_function_factory, RepeatStatementFactory}; pub use reset::{reset_function_factory, ResetStatementFactory}; pub use sql_init::{sql_init_function_factory, SqlInitStatementFactory}; diff --git a/interpreter/src/lang/vocabulary/playlist.rs b/interpreter/src/lang/vocabulary/playlist.rs new file mode 100644 index 0000000..9fa20ab --- /dev/null +++ b/interpreter/src/lang/vocabulary/playlist.rs @@ -0,0 +1,153 @@ +use std::collections::VecDeque; +use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::Iterator; + +use crate::tokens::Token; +use crate::Context; + +use crate::lang::LanguageDictionary; +use crate::lang::{FunctionFactory, FunctionStatementFactory, IteratorItem, Op, GeneratorOp}; +use crate::lang::{PseudoOp, RuntimeError, RuntimeOp, SyntaxError, Lookup, TypePrimitive}; +//use crate::processing::general::FileIter; +use crate::processing::general::Type; + +#[derive(Debug)] +pub struct PlaylistStatement { + context: Option, + // function params + file: Lookup, + // state + playlist_iter: Option, + has_tried: bool, +} + +impl Display for PlaylistStatement { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "playlist({})", self.file) + } +} + +impl std::clone::Clone for PlaylistStatement { + fn clone(&self) -> Self { + Self { + context: None, + file: self.file.clone(), + playlist_iter: None, + has_tried: self.has_tried, + } + } +} + +impl Iterator for PlaylistStatement { + type Item = IteratorItem; + + fn next(&mut self) -> Option { + if self.playlist_iter.is_none() { + if self.has_tried { + return None; + } else { + self.has_tried = true; + } + let ctx = self.context.as_mut().unwrap(); + let file = match self.file.get(ctx) { + Ok(Type::Primitive(TypePrimitive::String(s))) => s.to_owned(), + Ok(x) => return Some(Err( + RuntimeError { + msg: format!("Cannot use {} as filepath", x), + line: 0, + op: PseudoOp::from_printable(self), + } + )), + Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))), + }; + let iter = ctx.filesystem.read_file(&file); + self.playlist_iter = Some(match iter { + Ok(mut x) => { + x.enter(self.context.take().unwrap()); + x + }, + Err(e) => return Some(Err(e.with(RuntimeOp(PseudoOp::from_printable(self))))), + }); + } + match self.playlist_iter.as_mut().unwrap().next() { + Some(Ok(item)) => Some(Ok(item)), + Some(Err(e)) => Some(Err(e)), + None => None, + } + } + + fn size_hint(&self) -> (usize, Option) { + self.playlist_iter + .as_ref() + .map(|x| x.size_hint()) + .unwrap_or((0, None)) + } +} + +impl Op for PlaylistStatement { + fn enter(&mut self, ctx: Context) { + if let Some(playlist) = &mut self.playlist_iter { + playlist.enter(ctx); + } else { + self.context = Some(ctx) + } + } + + fn escape(&mut self) -> Context { + if let Some(playlist) = &mut self.playlist_iter { + playlist.escape() + } else { + self.context.take().unwrap() + } + } + + fn is_resetable(&self) -> bool { + true + } + + fn reset(&mut self) -> Result<(), RuntimeError> { + self.has_tried = false; + if let Some(playlist) = &mut self.playlist_iter { + self.context = Some(playlist.escape()); + } + self.playlist_iter = None; + Ok(()) + } + + fn dup(&self) -> Box { + let mut clone = self.clone(); + clone.reset().unwrap(); + Box::new(clone) + } +} + +pub struct PlaylistFunctionFactory; + +impl FunctionFactory for PlaylistFunctionFactory { + fn is_function(&self, name: &str) -> bool { + name == "playlist" + } + + fn build_function_params( + &self, + _name: String, + tokens: &mut VecDeque, + _dict: &LanguageDictionary, + ) -> Result { + // playlist(filepath) + let filepath_lookup = Lookup::parse(tokens)?; + Ok(PlaylistStatement { + context: None, + file: filepath_lookup, + playlist_iter: None, + has_tried: false, + }) + } +} + +pub type PlaylistStatementFactory = FunctionStatementFactory; + +#[inline(always)] +pub fn playlist_function_factory() -> PlaylistStatementFactory { + PlaylistStatementFactory::new(PlaylistFunctionFactory) +} diff --git a/interpreter/src/music/library.rs b/interpreter/src/music/library.rs index 8ac7425..ca92225 100644 --- a/interpreter/src/music/library.rs +++ b/interpreter/src/music/library.rs @@ -145,14 +145,22 @@ impl Library { if let Some(rev) = metadata.current() { for tag in rev.tags() { //println!("(pre) metadata tag ({},{})", tag.key, tag.value); - tags.add(tag.key.clone(), &tag.value); + tags.add(tag); + } + for vis in rev.visuals() { + //println!("Got visual for song `{}`", path.display()); + tags.add_visual(vis); } } } if let Some(rev) = probed.format.metadata().current() { for tag in rev.tags() { //println!("(post) metadata tag ({},{})", tag.key, tag.value); - tags.add(tag.key.clone(), &tag.value); + tags.add(tag); + } + for vis in rev.visuals() { + //println!("Got visual for song `{}`", path.display()); + tags.add_visual(vis); } } } @@ -178,14 +186,22 @@ impl Library { if let Some(rev) = metadata.current() { for tag in rev.tags() { //println!("(pre) metadata tag ({},{})", tag.key, tag.value); - tags.add(tag.key.clone(), &tag.value); + tags.add(tag); + } + for vis in rev.visuals() { + //println!("Got visual for song `{}`", path.display()); + tags.add_visual(vis); } } } if let Some(rev) = probed.format.metadata().current() { for tag in rev.tags() { //println!("(post) metadata tag ({},{})", tag.key, tag.value); - tags.add(tag.key.clone(), &tag.value); + tags.add(tag); + } + for vis in rev.visuals() { + //println!("Got visual for song `{}`", path.display()); + tags.add_visual(vis); } } self.generate_entries(&tags); diff --git a/interpreter/src/music/tag.rs b/interpreter/src/music/tag.rs index 692ec0e..1897c44 100644 --- a/interpreter/src/music/tag.rs +++ b/interpreter/src/music/tag.rs @@ -1,15 +1,134 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use symphonia::core::meta::Value; +use symphonia::core::meta::{Value, Visual, Tag, StandardTagKey}; use crate::lang::db::*; +const BASE64_CONF: base64::Config = base64::Config::new(base64::CharacterSet::Standard, false); + pub struct Tags { data: HashMap, filename: PathBuf, } +#[inline] +fn std_tag_to_str(key: StandardTagKey) -> &'static str { + match key { + StandardTagKey::AcoustidFingerprint => "acoustid_fingerprint", + StandardTagKey::AcoustidId => "acoustid_id", + StandardTagKey::Album => "album", + StandardTagKey::AlbumArtist => "albumartist", + StandardTagKey::Arranger => "arranger", + StandardTagKey::Artist => "artist", + StandardTagKey::Bpm => "bpm", + StandardTagKey::Comment => "comment", + StandardTagKey::Compilation => "compilation", + StandardTagKey::Composer => "composer", + StandardTagKey::Conductor => "conductor", + StandardTagKey::ContentGroup => "contentgroup", + StandardTagKey::Copyright => "copyright", + StandardTagKey::Date => "date", + StandardTagKey::Description => "description", + StandardTagKey::DiscNumber => "disc", + StandardTagKey::DiscSubtitle => "disc_subtitle", + StandardTagKey::DiscTotal => "disc_total", + StandardTagKey::EncodedBy => "encoded_by", + StandardTagKey::Encoder => "encoder", + StandardTagKey::EncoderSettings => "encoder_settings", + StandardTagKey::EncodingDate => "encoding_date", + StandardTagKey::Engineer => "engineer", + StandardTagKey::Ensemble => "ensemble", + StandardTagKey::Genre => "genre", + StandardTagKey::IdentAsin => "ident_asin", + StandardTagKey::IdentBarcode => "ident_barcode", + StandardTagKey::IdentCatalogNumber => "ident_catalog_number", + StandardTagKey::IdentEanUpn => "ident_ean_upn", + StandardTagKey::IdentIsrc => "ident_isrc", + StandardTagKey::IdentPn => "ident_pn", + StandardTagKey::IdentPodcast => "ident_podcast", + StandardTagKey::IdentUpc => "ident_upc", + StandardTagKey::Label => "label", + StandardTagKey::Language => "language", + StandardTagKey::License => "license", + StandardTagKey::Lyricist => "lyricist", + StandardTagKey::Lyrics => "lyrics", + StandardTagKey::MediaFormat => "mediaformat", + StandardTagKey::MixDj => "mixdj", + StandardTagKey::MixEngineer => "mix_engineer", + StandardTagKey::Mood => "mood", + StandardTagKey::MovementName => "movement_name", + StandardTagKey::MovementNumber => "movement_number", + StandardTagKey::MusicBrainzAlbumArtistId => "MusicBrainz_albumartist_id", + StandardTagKey::MusicBrainzAlbumId => "MusicBrainz_album_id", + StandardTagKey::MusicBrainzArtistId => "MusicBrainz_artist_id", + StandardTagKey::MusicBrainzDiscId => "MusicBrainz_disc_id", + StandardTagKey::MusicBrainzGenreId => "MusicBrainz_genre_id", + StandardTagKey::MusicBrainzLabelId => "MusicBrainz_label_id", + StandardTagKey::MusicBrainzOriginalAlbumId => "MusicBrainz_original_album_id", + StandardTagKey::MusicBrainzOriginalArtistId => "MusicBrainz_original_artist_id", + StandardTagKey::MusicBrainzRecordingId => "MusicBrainz_recording_id", + StandardTagKey::MusicBrainzReleaseGroupId => "MusicBrainz_release_group_id", + StandardTagKey::MusicBrainzReleaseStatus => "MusicBrainz_release_status", + StandardTagKey::MusicBrainzReleaseTrackId => "MusicBrainz_release_track_id", + StandardTagKey::MusicBrainzReleaseType => "MusicBrainz_release_type", + StandardTagKey::MusicBrainzTrackId => "MusicBrainz_track_id", + StandardTagKey::MusicBrainzWorkId => "MusicBrainz_work_id", + StandardTagKey::Opus => "Opus", + StandardTagKey::OriginalAlbum => "original_album", + StandardTagKey::OriginalArtist => "original_artist", + StandardTagKey::OriginalDate => "original_date", + StandardTagKey::OriginalFile => "original_file", + StandardTagKey::OriginalWriter => "original_writer", + StandardTagKey::Owner => "owner", + StandardTagKey::Part => "part", + StandardTagKey::PartTotal => "part_total", + StandardTagKey::Performer => "performer", + StandardTagKey::Podcast => "podcast", + StandardTagKey::PodcastCategory => "podcast_category", + StandardTagKey::PodcastDescription => "podcast_description", + StandardTagKey::PodcastKeywords => "podcast_keywords", + StandardTagKey::Producer => "producer", + StandardTagKey::PurchaseDate => "purchase_date", + StandardTagKey::Rating => "rating", + StandardTagKey::ReleaseCountry => "release_country", + StandardTagKey::ReleaseDate => "release_date", + StandardTagKey::Remixer => "remixer", + StandardTagKey::ReplayGainAlbumGain => "ReplayGain_album_gain", + StandardTagKey::ReplayGainAlbumPeak => "ReplayGain_album_peak", + StandardTagKey::ReplayGainTrackGain => "ReplayGain_track_gain", + StandardTagKey::ReplayGainTrackPeak => "ReplayGain_track_peak", + StandardTagKey::Script => "script", + StandardTagKey::SortAlbum => "sort_album", + StandardTagKey::SortAlbumArtist => "sort_albumartist", + StandardTagKey::SortArtist => "sort_artist", + StandardTagKey::SortComposer => "sort_composer", + StandardTagKey::SortTrackTitle => "sort_title", + StandardTagKey::TaggingDate => "tagging_date", + StandardTagKey::TrackNumber => "track", + StandardTagKey::TrackSubtitle => "track_subtitle", + StandardTagKey::TrackTitle => "title", + StandardTagKey::TrackTotal => "track_total", + StandardTagKey::TvEpisode => "tv_episode", + StandardTagKey::TvEpisodeTitle => "tv_episode_title", + StandardTagKey::TvNetwork => "tv_network", + StandardTagKey::TvSeason => "tv_season", + StandardTagKey::TvShowTitle => "tv_show_title", + StandardTagKey::Url => "url", + StandardTagKey::UrlArtist => "url_artist", + StandardTagKey::UrlCopyright => "url_copyright", + StandardTagKey::UrlInternetRadio => "url_internet_radio", + StandardTagKey::UrlLabel => "url_label", + StandardTagKey::UrlOfficial => "url_official", + StandardTagKey::UrlPayment => "url_payment", + StandardTagKey::UrlPodcast => "url_podcast", + StandardTagKey::UrlPurchase => "url_purchase", + StandardTagKey::UrlSource => "url_source", + StandardTagKey::Version => "version", + StandardTagKey::Writer => "writer", + } +} + impl Tags { pub fn new>(path: P) -> Self { Self { @@ -18,9 +137,21 @@ impl Tags { } } - pub fn add(&mut self, key: String, value: &Value) { + pub fn add(&mut self, tag: &Tag) { + let key = if let Some(std_key) = tag.std_key { + std_tag_to_str(std_key).to_owned() + } else { + tag.key.clone() + }; + let value = &tag.value; if let Some(tag_type) = TagType::from_symphonia_value(value) { - self.data.insert(key.trim().to_uppercase(), tag_type); + self.data.insert(key.trim().to_lowercase(), tag_type); + } + } + + pub fn add_visual(&mut self, visual: &Visual) { + if let Some(tag_type) = TagType::from_symphonia_visual(visual) { + self.data.insert("cover".to_owned(), tag_type); } } @@ -31,7 +162,7 @@ impl Tags { #[inline] pub fn track_title(&self) -> String { self.data - .get("TITLE") + .get(std_tag_to_str(StandardTagKey::TrackTitle)) .unwrap_or(&TagType::Unknown) .str() .map(|s| s.to_string()) @@ -55,7 +186,7 @@ impl Tags { #[inline] pub fn artist_name(&self) -> Option { self.data - .get("ARTIST") + .get(std_tag_to_str(StandardTagKey::Artist)) .unwrap_or(&TagType::Unknown) .str() .map(|s| s.to_string()) @@ -64,7 +195,7 @@ impl Tags { #[inline] pub fn album_title(&self) -> Option { self.data - .get("ALBUM") + .get(std_tag_to_str(StandardTagKey::Album)) .unwrap_or(&TagType::Unknown) .str() .map(|s| s.to_string()) @@ -73,7 +204,7 @@ impl Tags { #[inline] pub fn albumartist_name(&self) -> Option { self.data - .get("ALBUMARTIST") + .get(std_tag_to_str(StandardTagKey::AlbumArtist)) .unwrap_or(&TagType::Unknown) .str() .map(|s| s.to_string()) @@ -82,7 +213,7 @@ impl Tags { #[inline] pub fn genre_title(&self) -> Option { self.data - .get("GENRE") + .get(std_tag_to_str(StandardTagKey::Genre)) .unwrap_or(&TagType::Unknown) .str() .map(|s| s.to_string()) @@ -91,14 +222,26 @@ impl Tags { #[inline] pub fn track_number(&self) -> Option { self.data - .get("TRACKNUMBER") + .get(std_tag_to_str(StandardTagKey::TrackNumber)) .unwrap_or(&TagType::Unknown) .uint() } + #[inline] + pub fn cover_art(&self) -> Option { + self.data + .get("cover") + .unwrap_or(&TagType::Unknown) + .str() + .map(|s| s.to_string()) + } + #[inline] pub fn track_date(&self) -> Option { - self.data.get("DATE").unwrap_or(&TagType::Unknown).uint() + self.data + .get(std_tag_to_str(StandardTagKey::Date)) + .unwrap_or(&TagType::Unknown) + .uint() } pub fn song( @@ -125,20 +268,20 @@ impl Tags { meta_id: id, plays: self .data - .get("PLAYS") + .get("plays") .unwrap_or(&TagType::Unknown) .uint() .unwrap_or(0), track: self.track_number().unwrap_or(id), disc: self .data - .get("DISCNUMBER") + .get(std_tag_to_str(StandardTagKey::DiscNumber)) .unwrap_or(&TagType::Unknown) .uint() .unwrap_or(1), duration: self .data - .get("DURATION") + .get("duration") .unwrap_or(&TagType::Unknown) .uint() .unwrap_or(0), @@ -159,13 +302,8 @@ impl Tags { pub fn album_artist(&self, id: u64, genre_id: u64) -> DbArtistItem { DbArtistItem { artist_id: id, - name: self - .data - .get("ALBUMARTIST") - .unwrap_or(&TagType::Unknown) - .str() - .unwrap_or("Unknown Artist") - .into(), + name: self.albumartist_name() + .unwrap_or("Unknown Artist".into()), genre: genre_id, } } @@ -185,26 +323,26 @@ impl Tags { meta_id: id, plays: self .data - .get("PLAYS") + .get("plays") .unwrap_or(&TagType::Unknown) .uint() .unwrap_or(0), track: self .data - .get("TRACKTOTAL") + .get(std_tag_to_str(StandardTagKey::TrackTotal)) .unwrap_or(&TagType::Unknown) .uint() .unwrap_or(0), disc: self .data - .get("DISCTOTAL") + .get(std_tag_to_str(StandardTagKey::DiscTotal)) .unwrap_or(&TagType::Unknown) .uint() .unwrap_or(1), duration: 0, date: self .data - .get("DATE") + .get(std_tag_to_str(StandardTagKey::Date)) .unwrap_or(&TagType::Unknown) .uint() .unwrap_or(0), @@ -217,6 +355,19 @@ impl Tags { title: self.genre_title().unwrap_or_else(|| "Unknown Genre".into()), } } + + pub fn export_to_item(self, item: &mut crate::Item, overwrite: bool) { + for (key, val) in self.data { + if let Some(primitive_val) = val.to_primitive() { + if overwrite || item.field(&key).is_none() { + item.set_field(&key, primitive_val); + } + } + } + if overwrite || item.field("filename").is_none() { + item.set_field("filename", self.filename.display().to_string().into()); + } + } } #[derive(Clone)] @@ -230,6 +381,7 @@ enum TagType { } impl TagType { + #[inline] fn from_symphonia_value(value: &Value) -> Option { match value { Value::Binary(_val) => None, @@ -242,6 +394,11 @@ impl TagType { } } + #[inline] + fn from_symphonia_visual(visual: &Visual) -> Option { + Some(Self::Str(format!("data:{};base64,{}", &visual.media_type, base64::encode_config(&visual.data, BASE64_CONF)))) + } + fn str(&self) -> Option<&str> { match self { Self::Str(s) => Some(s), @@ -257,4 +414,15 @@ impl TagType { _ => None, } } + + fn to_primitive(self) -> Option { + match self { + Self::Boolean(b) => Some(crate::lang::TypePrimitive::Bool(b)), + Self::Flag => None, + Self::I64(i) => Some(crate::lang::TypePrimitive::Int(i)), + Self::U64(u) => Some(crate::lang::TypePrimitive::UInt(u)), + Self::Str(s) => Some(crate::lang::TypePrimitive::String(s.clone())), + Self::Unknown => None, + } + } } diff --git a/interpreter/src/processing/filesystem.rs b/interpreter/src/processing/filesystem.rs index 92c8222..b5da755 100644 --- a/interpreter/src/processing/filesystem.rs +++ b/interpreter/src/processing/filesystem.rs @@ -2,10 +2,11 @@ use std::fmt::{Debug, Display, Error, Formatter}; use std::fs::{DirEntry, ReadDir}; use std::iter::Iterator; use std::path::{Path, PathBuf}; +use std::io::Read; use regex::Regex; -use crate::lang::{RuntimeMsg, TypePrimitive}; +use crate::lang::{RuntimeMsg, TypePrimitive, GeneratorOp}; use crate::Item; const DEFAULT_REGEX: &str = r"/(?P[^/]+)/(?P[^/]+)/(?:(?:(?P\d+)\s+)?(?P\d+)\.?\s+)?(?P[^/]+)\.(?P<format>(?:mp3)|(?:wav)|(?:ogg)|(?:flac)|(?:mp4)|(?:aac))$"; @@ -165,7 +166,8 @@ impl FileIter { match crate::music::Library::read_media_tags(path) { Ok(tags) => { let mut item = Item::new(); - item.set_field("title", tags.track_title().into()); + tags.export_to_item(&mut item, true); + /*item.set_field("title", tags.track_title().into()); if let Some(artist) = tags.artist_name() { item.set_field("artist", artist.into()); } @@ -192,6 +194,9 @@ impl FileIter { if let Some(year) = tags.track_date() { item.set_field("year", year.into()); } + if let Some(cover) = tags.cover_art() { + item.set_field("cover", cover.into()); + }*/ self.populate_item_impl_simple(&mut item, path_str, captures, capture_names); Some(item) } @@ -334,6 +339,8 @@ pub trait FilesystemQuerier: Debug { fn single(&mut self, path: &str, pattern: Option<&str>) -> Result<Item, RuntimeMsg>; + fn read_file(&mut self, path: &str) -> Result<GeneratorOp, RuntimeMsg>; + fn expand(&self, folder: Option<&str>) -> Result<Option<String>, RuntimeMsg> { #[cfg(feature = "shellexpand")] match folder { @@ -360,6 +367,50 @@ pub trait FilesystemQuerier: Debug { #[derive(Default, Debug)] pub struct FilesystemExecutor {} +impl FilesystemExecutor { + #[cfg(feature = "collections")] + fn read_m3u8<P: AsRef<Path> + 'static>(&self, path: P) -> Result<GeneratorOp, RuntimeMsg> { + let mut file = std::fs::File::open(&path).map_err(|e| RuntimeMsg(format!("Path read error: {}", e)))?; + let mut file_bytes = Vec::new(); + file.read_to_end(&mut file_bytes).map_err(|e| RuntimeMsg(format!("File read error: {}", e)))?; + let (_, playlist) = m3u8_rs::parse_playlist(&file_bytes).map_err(|e| RuntimeMsg(format!("Playlist read error: {}", e)))?; + let playlist = match playlist { + m3u8_rs::Playlist::MasterPlaylist(_) => return Err(RuntimeMsg(format!("Playlist not supported: `{}` is a master (not media) playlist", path.as_ref().display()))), + m3u8_rs::Playlist::MediaPlaylist(l) => l, + }; + let mut index = 0; + Ok(GeneratorOp::new(move |ctx| { + if let Some(segment) = playlist.segments.get(index) { + let path = path.as_ref(); + index += 1; + let item_path = if let Some(parent) = path.parent() { + let joined_path = parent.join(&segment.uri); + if let Some(s) = joined_path.to_str() { + s.to_owned() + } else { + return Some(Err(RuntimeMsg(format!("Failed to convert path to string for `{}`", joined_path.display())))); + } + } else { + segment.uri.clone() + }; + let item = match ctx.filesystem.single(&item_path, None) { + Err(e) => Err(e), + Ok(mut item) => { + item.set_field("duration", (segment.duration as f64).into()); + if let Some(title) = &segment.title { + item.set_field("title", title.to_owned().into()); + } + Ok(item) + } + }; + Some(item) + } else { + None + } + })) + } +} + impl FilesystemQuerier for FilesystemExecutor { fn raw( &mut self, @@ -376,4 +427,17 @@ impl FilesystemQuerier for FilesystemExecutor { let mut file_iter = FileIter::new(path, pattern, false).map_err(RuntimeMsg)?; file_iter.only_once().map_err(RuntimeMsg) } + + fn read_file(&mut self, path: &str) -> Result<GeneratorOp, RuntimeMsg> { + let path: PathBuf = self.expand(Some(path))?.unwrap().into(); + if let Some(ext) = path.extension().and_then(|ext| ext.to_str()) { + match &ext.to_lowercase() as &str { + #[cfg(feature = "collections")] + "m3u8" => self.read_m3u8(path), + ext => Err(RuntimeMsg(format!("Unrecognised extension `{}` in path `{}`", ext, path.display()))) + } + } else { + Err(RuntimeMsg(format!("Unrecognised path `{}`", path.display()))) + } + } } diff --git a/interpreter/tests/single_line.rs b/interpreter/tests/single_line.rs index f69e115..fe4d0cb 100644 --- a/interpreter/tests/single_line.rs +++ b/interpreter/tests/single_line.rs @@ -842,3 +842,18 @@ fn execute_mpdfunction_line() -> Result<(), InterpreterError> { execute_single_line("mpd(`default`)", false, true)?; execute_single_line("mpd(`127.0.0.1:6600`)", false, true) } + +#[test] +fn execute_playlist_line() -> Result<(), InterpreterError> { + execute_single_line( + r"playlist(`~/Music/Playlists/cabello.m3u8`)", + false, + true, + )?; + execute_single_line( + r"playlist(`/home/ngnius/Music/Playlists/powers.m3u8`)", + false, + true, + )?; + execute_single_line(r"playlist(`~/Music/Playlists/empty.m3u8`)", true, true) +}