Compare commits

...
Sign in to create a new pull request.

23 commits
rust ... main

Author SHA1 Message Date
087742430d feat: switch NAS transfer from scp to rsync over SSH and update README
Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
2025-10-31 23:17:32 +00:00
d9f63a5fc1 version bump 2025-10-31 22:53:58 +00:00
75bbc3d654 docs: add Profiles section with multi-profile config example
Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
2025-10-31 22:45:29 +00:00
67c08343f6 chore: remove trailing newline from README.md 2025-10-31 22:45:22 +00:00
7f74ea9e3e feat: add ConfigPath subcommand to output config path
Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
2025-10-31 22:38:46 +00:00
53bbfd9501 ds files 2025-10-31 22:30:29 +00:00
5631a12c7c feat: add profile option to Args with default 'default'
Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
2025-10-31 22:28:22 +00:00
c1013feda6 feat: add multi-profile config support with profile selection
Co-authored-by: aider (openai/gpt-5) <aider@aider.chat>
2025-10-31 22:27:49 +00:00
ee23385af9 cabges 2025-09-11 11:05:10 +01:00
8f1ecf490f adding lock to ignore 2025-09-11 11:05:03 +01:00
3d926a5130 with updates 2025-09-11 11:04:48 +01:00
b7392fc7b4 fix: handle SoundCloud URLs with query parameters
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-09-11 11:00:52 +01:00
7eaff29e6d feat: force mp3 downloads with scdl
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-09-09 14:00:59 +01:00
8b53f310cf fix: pass thumbnail arguments to scdl via --yt-dlp-args
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-09-09 13:58:34 +01:00
c5addb147e feat: embed thumbnails for scdl and yt-dlp downloads
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-09-09 13:55:24 +01:00
e112149ee4 fix: download artwork for SoundCloud links
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-09-09 13:06:01 +01:00
95513d3901 fix: use ~/.config for config directory
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-09-08 23:10:35 +01:00
4bf8ca0296 feat: log config file path on startup
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-09-08 23:04:51 +01:00
a66660fbe9 Update README.md 2025-09-06 14:10:09 +01:00
469472d289 fix: Remove unsupported scdl flag and use rsync for local copy
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-08-19 22:17:12 +01:00
cd3748d33f fix: Add --no-mtime flag to scdl downloads
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
2025-08-19 21:45:16 +01:00
0dbbf6a369 repo url 2025-08-17 20:19:17 +01:00
john
0fce5901b7 Merge pull request 'rust' (#1) from rust into main
Reviewed-on: john/jamdl#1
2025-08-17 19:41:08 +01:00
5 changed files with 217 additions and 236 deletions

2
.gitignore vendored
View file

@ -16,3 +16,5 @@ target
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
Cargo.lock
.DS_Store

281
Cargo.lock generated
View file

@ -2,24 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.20" version = "0.6.20"
@ -171,20 +153,21 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]] [[package]]
name = "config" name = "config"
version = "0.14.1" version = "0.15.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" checksum = "0faa974509d38b33ff89282db9c3295707ccf031727c0de9772038ec526852ba"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"convert_case", "convert_case",
"json5", "json5",
"nom",
"pathdiff", "pathdiff",
"ron", "ron",
"rust-ini", "rust-ini",
"serde", "serde",
"serde-untagged",
"serde_json", "serde_json",
"toml", "toml",
"winnow",
"yaml-rust2", "yaml-rust2",
] ]
@ -254,23 +237,23 @@ dependencies = [
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "5.0.1" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys",
] ]
[[package]] [[package]]
name = "dirs-sys" name = "dirs-sys"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users",
"windows-sys 0.48.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -292,10 +275,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "equivalent" name = "erased-serde"
version = "1.0.2" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]] [[package]]
name = "errno" name = "errno"
@ -313,6 +300,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]] [[package]]
name = "fs_extra" name = "fs_extra"
version = "1.3.0" version = "1.3.0"
@ -357,24 +350,23 @@ name = "hashbrown"
version = "0.14.5" version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]] [[package]]
name = "hashlink" name = "hashlink"
version = "0.8.4" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [ dependencies = [
"hashbrown 0.14.5", "hashbrown 0.15.5",
] ]
[[package]] [[package]]
@ -383,16 +375,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indexmap"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown 0.15.5",
]
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.1" version = "1.70.1"
@ -457,22 +439,6 @@ version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.3"
@ -514,7 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
dependencies = [ dependencies = [
"memchr", "memchr",
"thiserror 2.0.15", "thiserror",
"ucd-trie", "ucd-trie",
] ]
@ -577,13 +543,13 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.6" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
"libredox", "libredox",
"thiserror 1.0.69", "thiserror",
] ]
[[package]] [[package]]
@ -600,9 +566,9 @@ dependencies = [
[[package]] [[package]]
name = "rust-ini" name = "rust-ini"
version = "0.20.0" version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"ordered-multimap", "ordered-multimap",
@ -636,6 +602,17 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-untagged"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34836a629bcbc6f1afdf0907a744870039b1e14c0561cb26094fa683b158eff3"
dependencies = [
"erased-serde",
"serde",
"typeid",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.219" version = "1.0.219"
@ -661,9 +638,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.9" 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 = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -709,33 +686,13 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.15" version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850"
dependencies = [ dependencies = [
"thiserror-impl 2.0.15", "thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@ -760,44 +717,40 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.23" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_edit", "toml_parser",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "toml_write" name = "toml_datetime"
version = "0.1.2" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]]
name = "toml_parser"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [
"winnow",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]] [[package]]
name = "typenum" name = "typenum"
@ -856,15 +809,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
@ -883,21 +827,6 @@ dependencies = [
"windows-targets 0.53.3", "windows-targets 0.53.3",
] ]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"
@ -931,12 +860,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.0", "windows_x86_64_msvc 0.53.0",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.6" version = "0.52.6"
@ -949,12 +872,6 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
@ -967,12 +884,6 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
@ -997,12 +908,6 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
@ -1015,12 +920,6 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
@ -1033,12 +932,6 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
@ -1051,12 +944,6 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
@ -1089,31 +976,11 @@ dependencies = [
[[package]] [[package]]
name = "yaml-rust2" name = "yaml-rust2"
version = "0.8.1" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" checksum = "4ce2a4ff45552406d02501cea6c18d8a7e50228e7736a872951fe2fe75c91be7"
dependencies = [ dependencies = [
"arraydeque", "arraydeque",
"encoding_rs", "encoding_rs",
"hashlink", "hashlink",
] ]
[[package]]
name = "zerocopy"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -1,6 +1,6 @@
[package] [package]
name = "jamdl" name = "jamdl"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -8,8 +8,8 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.82" anyhow = "1.0.82"
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
config = "0.14.0" config = "0.15.15"
dirs = "5.0.1" dirs = "6.0.0"
fs_extra = "1.3.0" fs_extra = "1.3.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tempfile = "3.10.1" tempfile = "3.10.1"

View file

@ -4,7 +4,7 @@ A command-line tool to download music from various sources like Apple Music, Sou
## Description ## Description
`jamdl` is a wrapper around popular downloaders (`gamdl`, `scdl`, `yt-dlp`) that automates the process of downloading media and transferring it to a specified location, such as a local directory or a remote NAS via SCP. `jamdl` is a wrapper around popular downloaders (`gamdl`, `scdl`, `yt-dlp`) that automates the process of downloading media and transferring it to a specified location, such as a local directory or a remote NAS via rsync over SSH.
## Prerequisites ## Prerequisites
@ -12,6 +12,7 @@ You must have the following command-line tools installed and available in your s
- `gamdl` (for Apple Music) - `gamdl` (for Apple Music)
- `scdl` (for SoundCloud) - `scdl` (for SoundCloud)
- `yt-dlp` (for other sources) - `yt-dlp` (for other sources)
- `rsync` (for file transfers; remote copies use rsync over SSH)
## Installation ## Installation
@ -28,7 +29,7 @@ cargo install --path .
You can also install it directly from a git repository: You can also install it directly from a git repository:
```bash ```bash
cargo install --git <repository_url> cargo install --git https://git.shed.gay/chaos/jamdl
``` ```
## Configuration ## Configuration
@ -53,6 +54,39 @@ cookies_file = "/path/to/your/apple-music-cookies-file.txt"
- `nas_path`: The destination path on your NAS or local filesystem. - `nas_path`: The destination path on your NAS or local filesystem.
- `cookies_file`: The path to your Apple Music cookies file for `gamdl`. - `cookies_file`: The path to your Apple Music cookies file for `gamdl`.
## Profiles
`jamdl` supports multiple profiles so you can target different destinations (NAS paths, hosts, or local folders) without editing the config each time. Profiles live under a top-level `[profiles]` table. The profile named `default` is used when no `--profile` is specified.
Example multi-profile configuration:
```toml
# Multi-profile configuration for jamdl
[profiles.default]
nas_host = "localhost"
nas_user = "your_ssh_user"
nas_path = "/path/on/nas"
cookies_file = "/path/to/your/apple-music-cookies-file.txt"
[profiles.work]
nas_host = "work-nas"
nas_user = "alice"
nas_path = "/srv/media/music"
cookies_file = "/home/alice/apple-cookies.txt"
[profiles.laptop]
nas_host = "localhost"
nas_user = "your_ssh_user"
nas_path = "/Users/you/Music"
cookies_file = "/Users/you/cookies.txt"
```
- Choose a profile when running `jamdl`:
- `jamdl --profile work "https://music.apple.com/us/album/some-album/123456789"`
- Short form: `jamdl -p work "https://music.apple.com/us/album/some-album/123456789"`
- If the requested profile is missing, `jamdl` falls back to `default` and lists available profiles.
- Backwards compatibility: legacy single-profile configs (top-level keys without `[profiles]`) are still supported.
## Usage ## Usage
Once configured, you can download media by passing a URL to `jamdl`: Once configured, you can download media by passing a URL to `jamdl`:

View file

@ -1,12 +1,13 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::Parser; use clap::{Parser, Subcommand};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use tempfile::TempDir; use tempfile::TempDir;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
struct Settings { struct Settings {
nas_host: String, nas_host: String,
nas_user: String, nas_user: String,
@ -14,22 +15,48 @@ struct Settings {
cookies_file: String, cookies_file: String,
} }
#[derive(Debug, Deserialize)]
struct AppConfig {
profiles: HashMap<String, Settings>,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Show the path to the configuration file and exit
ConfigPath,
}
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct Args { struct Args {
/// The URL of the video/music to download /// The URL of the video/music to download
#[arg(name = "URL")] #[arg(name = "URL")]
url: String, url: Option<String>,
/// Profile name to use from config (falls back to 'default')
#[arg(short, long, default_value = "default")]
profile: String,
/// Command to run
#[command(subcommand)]
cmd: Option<Commands>,
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let args = Args::parse(); let args = Args::parse();
let config_dir = dirs::config_dir() let config_dir = dirs::home_dir()
.context("Could not find config directory")? .context("Could not find home directory")?
.join(".config")
.join("jamdl"); .join("jamdl");
let config_path = config_dir.join("config.toml"); let config_path = config_dir.join("config.toml");
// Handle 'config-path' subcommand early without creating or reading config
if matches!(args.cmd, Some(Commands::ConfigPath)) {
println!("{}", config_path.display());
return Ok(());
}
if !config_path.exists() { if !config_path.exists() {
println!("[INFO] No config file found, creating a default one..."); println!("[INFO] No config file found, creating a default one...");
fs::create_dir_all(&config_dir).context("Failed to create config directory")?; fs::create_dir_all(&config_dir).context("Failed to create config directory")?;
@ -45,10 +72,39 @@ fn main() -> Result<()> {
return Ok(()); return Ok(());
} }
let settings = config::Config::builder() println!("[INFO] Using config file at: {}", config_path.display());
.add_source(config::File::from(config_path))
.build()? let cfg = config::Config::builder()
.try_deserialize::<Settings>()?; .add_source(config::File::from(config_path.clone()))
.build()?;
let selected_profile = args.profile.clone();
let settings: Settings = match cfg.clone().try_deserialize::<AppConfig>() {
Ok(app_cfg) => {
if let Some(s) = app_cfg.profiles.get(&selected_profile).cloned() {
println!("[INFO] Using profile: {}", selected_profile);
s
} else if let Some(s) = app_cfg.profiles.get("default").cloned() {
if selected_profile != "default" {
println!("[WARN] Profile '{}' not found. Falling back to 'default'.", selected_profile);
let available = app_cfg.profiles.keys().cloned().collect::<Vec<_>>().join(", ");
println!("[INFO] Available profiles: {}", available);
} else {
println!("[INFO] Using profile: default");
}
s
} else {
anyhow::bail!("No profile '{}' found and no 'default' profile defined in config.", selected_profile);
}
}
Err(_) => {
if selected_profile != "default" {
println!("[WARN] Requested profile '{}' but config file uses single-profile format. Using that single profile.", selected_profile);
}
println!("[INFO] Using legacy single-profile configuration.");
cfg.try_deserialize::<Settings>()?
}
};
// Create temp directory // Create temp directory
let temp_dir = TempDir::new().context("Failed to create temporary directory")?; let temp_dir = TempDir::new().context("Failed to create temporary directory")?;
@ -58,7 +114,8 @@ fn main() -> Result<()> {
temp_path.display() temp_path.display()
); );
download_media(&args.url, temp_path, &settings)?; let url = args.url.clone().context("A URL is required when not using a subcommand. Use '--help' for usage.")?;
download_media(&url, temp_path, &settings)?;
transfer_files(temp_path, &settings)?; transfer_files(temp_path, &settings)?;
// Cleanup is handled by TempDir's Drop trait // Cleanup is handled by TempDir's Drop trait
@ -83,12 +140,26 @@ fn download_media(video_url: &str, download_path: &Path, settings: &Settings) ->
} }
} else if video_url.contains("soundcloud.com") { } else if video_url.contains("soundcloud.com") {
println!("[INFO] Soundcloud link detected. Using scdl..."); println!("[INFO] Soundcloud link detected. Using scdl...");
let clean_url = video_url.split('?').next().unwrap();
cmd = Command::new("scdl"); cmd = Command::new("scdl");
cmd.args(["-l", video_url]); cmd.args([
"-l",
clean_url,
"-c",
"--onlymp3",
"--yt-dlp-args",
"--embed-thumbnail --write-thumbnail",
]);
} else { } else {
println!("[INFO] Non-Apple Music link. Using yt-dlp..."); println!("[INFO] Non-Apple Music link. Using yt-dlp...");
cmd = Command::new("yt-dlp"); cmd = Command::new("yt-dlp");
cmd.args(["-o", "%(title)s.%(ext)s", video_url]); cmd.args([
"-o",
"%(title)s.%(ext)s",
"--write-thumbnail",
"--embed-thumbnail",
video_url,
]);
} }
cmd.current_dir(download_path); cmd.current_dir(download_path);
@ -108,27 +179,34 @@ fn transfer_files(source_path: &Path, settings: &Settings) -> Result<()> {
if !is_empty { if !is_empty {
if settings.nas_host == "localhost" { if settings.nas_host == "localhost" {
println!("[INFO] NAS_HOST is localhost, copying files locally..."); println!("[INFO] NAS_HOST is localhost, copying files locally via rsync...");
let mut options = fs_extra::dir::CopyOptions::new(); let mut rsync_cmd = Command::new("rsync");
options.content_only = true;
fs_extra::dir::copy(source_path, &settings.nas_path, &options)
.context("Failed to copy files locally")?;
} else {
println!("[INFO] Transferring files to NAS via scp...");
let mut scp_cmd = Command::new("scp");
let source = format!( let source = format!(
"{}/.", "{}/",
source_path.to_str().context("Invalid source path")?
);
rsync_cmd.args(["-r", &source, &settings.nas_path]);
let status = rsync_cmd.status().context("Failed to execute rsync")?;
if !status.success() {
anyhow::bail!("rsync failed with status: {}", status);
}
} else {
println!("[INFO] Transferring files to NAS via rsync over SSH...");
let mut rsync_cmd = Command::new("rsync");
let source = format!(
"{}/",
source_path.to_str().context("Invalid source path")? source_path.to_str().context("Invalid source path")?
); );
let destination = format!( let destination = format!(
"{}@{}:{}", "{}@{}:{}",
settings.nas_user, settings.nas_host, settings.nas_path settings.nas_user, settings.nas_host, settings.nas_path
); );
scp_cmd.args(["-r", &source, &destination]); rsync_cmd.args(["-r", "-e", "ssh", &source, &destination]);
let status = scp_cmd.status().context("Failed to execute scp")?; let status = rsync_cmd.status().context("Failed to execute rsync")?;
if !status.success() { if !status.success() {
anyhow::bail!("scp failed with status: {}", status); anyhow::bail!("rsync failed with status: {}", status);
} }
} }
} else { } else {