jamdl/src/main.rs
John Rogers 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

139 lines
4.4 KiB
Rust

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, "--no-mtime"]);
} 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(())
}