importing music

This commit is contained in:
2026-08-23 10:45:14 -04:00
parent 4e9b124b6d
commit 92da4cce7b

View File

@@ -2,9 +2,10 @@ use clap::{
Parser, Subcommand, Parser, Subcommand,
builder::styling::{AnsiColor, Effects, Styles}, builder::styling::{AnsiColor, Effects, Styles},
}; };
use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::{ use std::{
fs::{self, File, create_dir_all}, fs::{self, File, create_dir_all, remove_dir},
io::{BufReader, Read}, io::{BufReader, Read},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@@ -60,6 +61,8 @@ enum Commands {
Scan, Scan,
/// attempt to repair timbre's home, this won't restore lost tracks /// attempt to repair timbre's home, this won't restore lost tracks
Repair, Repair,
/// import music files
Import,
} }
fn is_audio(path: &Path) -> bool { fn is_audio(path: &Path) -> bool {
@@ -94,7 +97,38 @@ fn scan(dir: &Path, out: &mut Vec<PathBuf>) {
} }
} }
} }
enum SafeName {
Ok(String),
Empty,
Malicious,
}
fn sanitize(name: &str) -> SafeName {
let trimmed = name.trim();
if trimmed == ".." || trimmed == "." {
return SafeName::Malicious;
}
let cleaned = trimmed.replace(['/', '\\'], "-");
let cleaned = cleaned.trim();
if cleaned.is_empty() {
SafeName::Empty
} else {
SafeName::Ok(cleaned.to_string())
}
}
fn remove_empty_dirs(dir: &Path) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
remove_empty_dirs(&path);
let _ = fs::remove_dir(&path);
}
}
}
}
fn main() { fn main() {
let cat = include_bytes!("../cat_01.jpg"); // my kitty! let cat = include_bytes!("../cat_01.jpg"); // my kitty!
@@ -119,7 +153,8 @@ fn main() {
Commands::Init => { Commands::Init => {
if !timbre_dir.exists() { if !timbre_dir.exists() {
println!("making a home..."); println!("making a home...");
create_dir_all(&timbre_dir).expect("wat"); create_dir_all(&timbre_dir)
.expect("i couldn't create my house :( i don't know what to do :(\n");
fs::write(timbre_dir.join("cat_01.jpg"), cat) fs::write(timbre_dir.join("cat_01.jpg"), cat)
.expect("my cat! he can't come! what did you do!?!\n"); .expect("my cat! he can't come! what did you do!?!\n");
for dir in ["Albums", "Singles", "Unidentified"] { for dir in ["Albums", "Singles", "Unidentified"] {
@@ -141,6 +176,115 @@ fn main() {
println!("i found {} audio files :)", tracks.len()); println!("i found {} audio files :)", tracks.len());
} }
} }
Commands::Import => {
let import_dir = timbre_dir.join("Import");
if !import_dir.exists() {
create_dir_all(&import_dir)
.expect("i can't make the folder for you to put your music in :(\n");
println!("i made an Import folder! put your music there and run this again.");
return;
}
let mut to_import = Vec::new();
scan(&import_dir, &mut to_import);
if to_import.is_empty() {
println!("the Import folder is empty :( put some music in it first!");
return;
}
println!("found {} files to import!", to_import.len());
for file in to_import {
let tagged = Probe::open(&file)
.expect("i couldn't open this file :(\n")
.read()
.expect("i couldn't read this file's tags :(\n");
match tagged.primary_tag() {
Some(tag) => {
let track = tag.track();
let artist = match tag.artist() {
None => SafeName::Empty,
Some(a) => sanitize(&a),
};
let album = match tag.album() {
None => None,
Some(a) => Some(sanitize(&a)),
};
let title = match tag.title() {
None => SafeName::Empty,
Some(t) => sanitize(&t),
};
if matches!(artist, SafeName::Malicious)
|| matches!(title, SafeName::Malicious)
|| matches!(album, Some(SafeName::Malicious))
{
println!(
"hey, help! i just found this file: {}\nit seems to be trying to do something bad and hurt your computer!\ni'm moving it to a Quarantine folder for you to check\nyou should probably check the source of it.\ntip: upload it to https://virustotal.com\n",
file.display()
);
create_dir_all(&timbre_dir.join("Quarantine")).expect("i can't create the Quarantine folder, could be something normal, potentially malware :(\nbe careful with the file! it's still in the Import folder.\ntip: upload it to https://virustotal.com\n");
fs::rename(&file, &timbre_dir.join("Quarantine").join(file.file_name().expect("no filename??\n")))
.expect("i couldn't move this file :(, still in the Import folder, be careful!\ntip: upload it to https://virustotal.com\n");
continue;
}
let (SafeName::Ok(artist), SafeName::Ok(title)) = (artist, title) else {
let unid = timbre_dir.join("Unidentified");
fs::rename(&file, unid.join(file.file_name().unwrap()))
.expect("i couldn't move an Unidentified file to a folder :(\nis the folder missing? try timbre repair!\n");
continue;
};
let ext = file.extension().and_then(|e| e.to_str()).unwrap_or("");
let dest = match album {
Some(SafeName::Ok(album)) => {
let filename = match track {
Some(n) => format!("{:02} {}.{}", n, title, ext),
None => format!("{}.{}", title, ext),
};
timbre_dir
.join("Albums")
.join(&artist)
.join(&album)
.join(filename)
}
_ => timbre_dir
.join("Singles")
.join(&artist)
.join(format!("{}.{}", title, ext)),
};
if !dest.starts_with(&timbre_dir) {
println!(
"hey, help! i just found this file: {}\nit seems to be trying to do something bad and hurt your computer!\ni'm moving it to a Quarantine folder for you to check\nyou should probably check the source of it.\ntip: upload it to https://virustotal.com\n",
file.display()
);
create_dir_all(&timbre_dir.join("Quarantine")).expect("i can't create the Quarantine folder, could be something normal, potentially malware :(\nbe careful with the file! it's still in the Import folder.\ntip: upload it to https://virustotal.com\n");
fs::rename(&file, &timbre_dir.join("Quarantine").join(file.file_name().expect("no filename??\n")))
.expect("i couldn't move this file :(, still in the Import folder, be careful!\ntip: upload it to https://virustotal.com\n");
continue;
}
if dest.exists() {
println!("already there, skipping {} :(", file.display());
continue;
}
create_dir_all(dest.parent().unwrap())
.expect("can't create directory to put a file in :(\n");
fs::rename(&file, &dest).expect("can't move file to directory :(\n");
}
None => {
println!("no tags on {} :(", file.display());
}
}
}
remove_empty_dirs(&import_dir);
let _ = remove_dir(&import_dir);
println!("all done! cleaned up the Import folder if it's empty :)");
}
Commands::Repair => { Commands::Repair => {
println!("repairing my home..."); println!("repairing my home...");
let mut fixed_something = false; let mut fixed_something = false;
@@ -149,7 +293,8 @@ fn main() {
println!( println!(
"it appears that my home is gone. your songs may have been deleted :(\nrebuilding my home..." "it appears that my home is gone. your songs may have been deleted :(\nrebuilding my home..."
); );
create_dir_all(&timbre_dir).expect("wat"); create_dir_all(&timbre_dir)
.expect("i couldn't create my house :( i don't know what to do :(\n");
fs::write(timbre_dir.join("cat_01.jpg"), cat) fs::write(timbre_dir.join("cat_01.jpg"), cat)
.expect("my cat! he can't come! what did you do!?!\n"); .expect("my cat! he can't come! what did you do!?!\n");
for dir in ["Albums", "Singles", "Unidentified"] { for dir in ["Albums", "Singles", "Unidentified"] {