Add m3u8 loading function syntax -- playlist(filepath)

This commit is contained in:
NGnius (Graham) 2022-10-26 20:50:11 -04:00
parent 7b92c340ee
commit d264b84c90
12 changed files with 718 additions and 316 deletions

462
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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())

View file

@ -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<Context>,
generator: Box<dyn FnMut(&mut Context) -> Option<Result<Item, RuntimeMsg>>>,
}
impl GeneratorOp {
pub fn new<F: FnMut(&mut Context) -> Option<Result<Item, RuntimeMsg>> + '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", &"<boxed function>")
.finish()
}
}
impl Iterator for GeneratorOp {
type Item = IteratorItem;
fn next(&mut self) -> Option<Self::Item> {
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<dyn Op> {
// this shouldn't be called
Box::new(Self {
context: None,
generator: Box::new(|_| None)
})
}
}

View file

@ -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};

View file

@ -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<dyn Op>).into(),
op: PseudoOp::from_printable(self),
msg: e,
})),
None => None,

View file

@ -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};

View file

@ -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<Context>,
// function params
file: Lookup,
// state
playlist_iter: Option<GeneratorOp>,
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<Self::Item> {
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<usize>) {
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<dyn Op> {
let mut clone = self.clone();
clone.reset().unwrap();
Box::new(clone)
}
}
pub struct PlaylistFunctionFactory;
impl FunctionFactory<PlaylistStatement> for PlaylistFunctionFactory {
fn is_function(&self, name: &str) -> bool {
name == "playlist"
}
fn build_function_params(
&self,
_name: String,
tokens: &mut VecDeque<Token>,
_dict: &LanguageDictionary,
) -> Result<PlaylistStatement, SyntaxError> {
// 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<PlaylistStatement, PlaylistFunctionFactory>;
#[inline(always)]
pub fn playlist_function_factory() -> PlaylistStatementFactory {
PlaylistStatementFactory::new(PlaylistFunctionFactory)
}

View file

@ -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);

View file

@ -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<String, TagType>,
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<P: AsRef<Path>>(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<String> {
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<String> {
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<String> {
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<String> {
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<u64> {
self.data
.get("TRACKNUMBER")
.get(std_tag_to_str(StandardTagKey::TrackNumber))
.unwrap_or(&TagType::Unknown)
.uint()
}
#[inline]
pub fn cover_art(&self) -> Option<String> {
self.data
.get("cover")
.unwrap_or(&TagType::Unknown)
.str()
.map(|s| s.to_string())
}
#[inline]
pub fn track_date(&self) -> Option<u64> {
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<Self> {
match value {
Value::Binary(_val) => None,
@ -242,6 +394,11 @@ impl TagType {
}
}
#[inline]
fn from_symphonia_visual(visual: &Visual) -> Option<Self> {
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<crate::lang::TypePrimitive> {
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,
}
}
}

View file

@ -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<artist>[^/]+)/(?P<album>[^/]+)/(?:(?:(?P<disc>\d+)\s+)?(?P<track>\d+)\.?\s+)?(?P<title>[^/]+)\.(?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())))
}
}
}

View file

@ -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)
}