Merge pull request 'rust' (#1) from rust into main
Reviewed-on: john/jamdl#1
This commit is contained in:
commit
0fce5901b7
7 changed files with 1358 additions and 50 deletions
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
.aider*
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug
|
||||||
|
target
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
# Generated by cargo mutants
|
||||||
|
# Contains mutation testing data
|
||||||
|
**/mutants.out*/
|
||||||
|
# RustRover
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# 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.
|
||||||
|
#.idea/
|
||||||
1119
Cargo.lock
generated
Normal file
1119
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "jamdl"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.82"
|
||||||
|
clap = { version = "4.5.4", features = ["derive"] }
|
||||||
|
config = "0.14.0"
|
||||||
|
dirs = "5.0.1"
|
||||||
|
fs_extra = "1.3.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tempfile = "3.10.1"
|
||||||
61
README.md
61
README.md
|
|
@ -1,3 +1,62 @@
|
||||||
# jamdl
|
# jamdl
|
||||||
|
|
||||||
A shell script to download music using SCDL for soudcloud and gamdl for apple music
|
A command-line tool to download music from various sources like Apple Music, SoundCloud, and others supported by yt-dlp.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You must have the following command-line tools installed and available in your system's `PATH`:
|
||||||
|
- `gamdl` (for Apple Music)
|
||||||
|
- `scdl` (for SoundCloud)
|
||||||
|
- `yt-dlp` (for other sources)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install `jamdl` using `cargo`:
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
|
Clone this repository and install using cargo:
|
||||||
|
```bash
|
||||||
|
cargo install --path .
|
||||||
|
```
|
||||||
|
|
||||||
|
### From a git repository
|
||||||
|
|
||||||
|
You can also install it directly from a git repository:
|
||||||
|
```bash
|
||||||
|
cargo install --git <repository_url>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The first time you run `jamdl`, it will create a default configuration file at `~/.config/jamdl/config.toml`.
|
||||||
|
|
||||||
|
You **must** edit this file to match your setup before you can use `jamdl`.
|
||||||
|
|
||||||
|
The configuration file looks like this:
|
||||||
|
```toml
|
||||||
|
# Default configuration for jamdl
|
||||||
|
# Please edit these values to match your setup.
|
||||||
|
|
||||||
|
nas_host = "localhost"
|
||||||
|
nas_user = "your_ssh_user"
|
||||||
|
nas_path = "/path/on/nas"
|
||||||
|
cookies_file = "/path/to/your/apple-music-cookies-file.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `nas_host`: The hostname or IP address of your NAS. If set to `"localhost"`, files will be copied locally instead of using SCP.
|
||||||
|
- `nas_user`: The SSH username for your NAS.
|
||||||
|
- `nas_path`: The destination path on your NAS or local filesystem.
|
||||||
|
- `cookies_file`: The path to your Apple Music cookies file for `gamdl`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Once configured, you can download media by passing a URL to `jamdl`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jamdl "https://music.apple.com/us/album/some-album/123456789"
|
||||||
|
```
|
||||||
|
|
|
||||||
7
config.toml
Normal file
7
config.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Default configuration for jamdl
|
||||||
|
# Please edit these values to match your setup.
|
||||||
|
|
||||||
|
nas_host = "localhost"
|
||||||
|
nas_user = "your_ssh_user"
|
||||||
|
nas_path = "/path/on/nas"
|
||||||
|
cookies_file = "/path/to/your/apple-music-cookies-file.txt"
|
||||||
49
jamdl.sh
49
jamdl.sh
|
|
@ -1,49 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ==== CONFIGURATION ====
|
|
||||||
VIDEO_URL="$1"
|
|
||||||
NAS_HOST="(localaddress)"
|
|
||||||
NAS_USER="ssh_user"
|
|
||||||
NAS_PATH="/path/to/files/on/nas"
|
|
||||||
TEMP_DIR="/tmp/yt-nas-transfer"
|
|
||||||
COOKIES_FILE="/location/of/apple-music-cookies-file.txt"
|
|
||||||
|
|
||||||
# ==== BASIC CHECK ====
|
|
||||||
if [[ -z "$VIDEO_URL" ]]; then
|
|
||||||
echo "[ERROR] No URL provided. Usage: jamdl \"<link>\""
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create temp directory
|
|
||||||
mkdir -p "$TEMP_DIR"
|
|
||||||
cd "$TEMP_DIR" || exit 1
|
|
||||||
|
|
||||||
# ==== DOWNLOAD SECTION ====
|
|
||||||
if [[ "$VIDEO_URL" == *"music.apple.com"* ]]; then
|
|
||||||
echo "[INFO] Apple Music link detected. Using gamdl..."
|
|
||||||
if [[ -f "$COOKIES_FILE" ]]; then
|
|
||||||
gamdl --cookies-path "$COOKIES_FILE" "$VIDEO_URL"
|
|
||||||
else
|
|
||||||
echo "[WARN] cookies.txt not found at $COOKIES_FILE — running gamdl without it"
|
|
||||||
gamdl "$VIDEO_URL"
|
|
||||||
fi
|
|
||||||
elif [[ "$VIDEO_URL" == *"soundcloud.com"* ]]; then
|
|
||||||
echo "[INFO] Soundcloud link detected. Using scdl..."
|
|
||||||
scdl -l "$VIDEO_URL"
|
|
||||||
else
|
|
||||||
echo "[INFO] Non-Apple Music link. Using yt-dlp..."
|
|
||||||
yt-dlp -o "%(title)s.%(ext)s" "$VIDEO_URL"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ==== TRANSFER IF FILES EXIST ====
|
|
||||||
if compgen -G "$TEMP_DIR/*" > /dev/null; then
|
|
||||||
echo "[INFO] Transferring files to NAS..."
|
|
||||||
scp -r "$TEMP_DIR"/* "$NAS_USER@$NAS_HOST:$NAS_PATH"
|
|
||||||
else
|
|
||||||
echo "[WARN] No files found to transfer."
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
echo "[INFO] Cleaning up..."
|
|
||||||
rm -rf "$TEMP_DIR"/*%
|
|
||||||
139
src/main.rs
Normal file
139
src/main.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::Parser;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Settings {
|
||||||
|
nas_host: String,
|
||||||
|
nas_user: String,
|
||||||
|
nas_path: String,
|
||||||
|
cookies_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// The URL of the video/music to download
|
||||||
|
#[arg(name = "URL")]
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let config_dir = dirs::config_dir()
|
||||||
|
.context("Could not find config directory")?
|
||||||
|
.join("jamdl");
|
||||||
|
let config_path = config_dir.join("config.toml");
|
||||||
|
|
||||||
|
if !config_path.exists() {
|
||||||
|
println!("[INFO] No config file found, creating a default one...");
|
||||||
|
fs::create_dir_all(&config_dir).context("Failed to create config directory")?;
|
||||||
|
|
||||||
|
let default_config = include_str!("../config.toml");
|
||||||
|
fs::write(&config_path, default_config).context("Failed to write default config file")?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[INFO] A default config file was created at: {}",
|
||||||
|
config_path.display()
|
||||||
|
);
|
||||||
|
println!("[INFO] Please edit this file with your settings and re-run the command.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let settings = config::Config::builder()
|
||||||
|
.add_source(config::File::from(config_path))
|
||||||
|
.build()?
|
||||||
|
.try_deserialize::<Settings>()?;
|
||||||
|
|
||||||
|
// Create temp directory
|
||||||
|
let temp_dir = TempDir::new().context("Failed to create temporary directory")?;
|
||||||
|
let temp_path = temp_dir.path();
|
||||||
|
println!(
|
||||||
|
"[INFO] Created temporary directory at: {}",
|
||||||
|
temp_path.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
download_media(&args.url, temp_path, &settings)?;
|
||||||
|
transfer_files(temp_path, &settings)?;
|
||||||
|
|
||||||
|
// Cleanup is handled by TempDir's Drop trait
|
||||||
|
println!("[INFO] Cleaning up...");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_media(video_url: &str, download_path: &Path, settings: &Settings) -> Result<()> {
|
||||||
|
let mut cmd: Command;
|
||||||
|
|
||||||
|
if video_url.contains("music.apple.com") {
|
||||||
|
println!("[INFO] Apple Music link detected. Using gamdl...");
|
||||||
|
cmd = Command::new("gamdl");
|
||||||
|
if Path::new(&settings.cookies_file).exists() {
|
||||||
|
cmd.args(["--cookies-path", &settings.cookies_file, video_url]);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"[WARN] cookies.txt not found at {} — running gamdl without it",
|
||||||
|
settings.cookies_file
|
||||||
|
);
|
||||||
|
cmd.arg(video_url);
|
||||||
|
}
|
||||||
|
} else if video_url.contains("soundcloud.com") {
|
||||||
|
println!("[INFO] Soundcloud link detected. Using scdl...");
|
||||||
|
cmd = Command::new("scdl");
|
||||||
|
cmd.args(["-l", video_url]);
|
||||||
|
} else {
|
||||||
|
println!("[INFO] Non-Apple Music link. Using yt-dlp...");
|
||||||
|
cmd = Command::new("yt-dlp");
|
||||||
|
cmd.args(["-o", "%(title)s.%(ext)s", video_url]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.current_dir(download_path);
|
||||||
|
let status = cmd
|
||||||
|
.status()
|
||||||
|
.with_context(|| format!("Failed to execute command: {:?}", cmd))?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!("Download command failed with status: {}", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_files(source_path: &Path, settings: &Settings) -> Result<()> {
|
||||||
|
let is_empty = fs::read_dir(source_path)?.next().is_none();
|
||||||
|
|
||||||
|
if !is_empty {
|
||||||
|
if settings.nas_host == "localhost" {
|
||||||
|
println!("[INFO] NAS_HOST is localhost, copying files locally...");
|
||||||
|
let mut options = fs_extra::dir::CopyOptions::new();
|
||||||
|
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!(
|
||||||
|
"{}/.",
|
||||||
|
source_path.to_str().context("Invalid source path")?
|
||||||
|
);
|
||||||
|
let destination = format!(
|
||||||
|
"{}@{}:{}",
|
||||||
|
settings.nas_user, settings.nas_host, settings.nas_path
|
||||||
|
);
|
||||||
|
scp_cmd.args(["-r", &source, &destination]);
|
||||||
|
|
||||||
|
let status = scp_cmd.status().context("Failed to execute scp")?;
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!("scp failed with status: {}", status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("[WARN] No files found to transfer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue