This commit is contained in:
2026-04-05 00:00:45 +03:00
parent a82d4d3819
commit 87b925be5f
2 changed files with 108 additions and 92 deletions
+55 -39
View File
@@ -52,8 +52,6 @@ async fn main() -> Result<()> {
let threads = args.threads;
let overwrite = args.overwrite_existing;
let allow_inaccurate = args.allow_inaccurate;
let lyrics_sources_raw = args.sources;
let extensions: Vec<String> = args.extensions.iter().map(|s| s.to_lowercase()).collect();
if !music_dir_path.exists() {
eprintln!("[ERROR] The specified music directory does not exist.");
@@ -65,22 +63,29 @@ async fn main() -> Result<()> {
exit(1);
}
if lyrics_sources_raw.is_empty() {
if args.sources.is_empty() {
eprintln!("[ERROR] At least one source must be specified.");
exit(1);
}
if extensions.is_empty() {
if args.extensions.is_empty() {
eprintln!("[ERROR] At least one file extension must be specified.");
exit(1);
}
let lyrics_sources_raw: Vec<String> = lyrics_sources_raw
.iter()
let lyrics_sources_raw: Vec<String> = args
.sources
.into_iter()
.unique()
.map(|s| s.to_lowercase())
.collect();
let extensions: Vec<String> = args
.extensions
.into_iter()
.map(|s| s.to_lowercase())
.collect();
println!("[INFO] Music directory: {}", music_dir_path.display());
println!("[INFO] Number of threads: {}", threads);
println!("[INFO] Sources: {}", lyrics_sources_raw.join(", "));
@@ -106,36 +111,48 @@ async fn main() -> Result<()> {
}
let sources = Arc::new(sources);
let semaphone = Arc::new(Semaphore::new(threads));
let semaphore = Arc::new(Semaphore::new(threads));
let mut entries = WalkDir::new(music_dir_path);
let mut tasks = Vec::new();
while let Some(entry) = entries.next().await {
match entry {
Ok(entry) => {
if entry.file_type().await?.is_file()
&& extensions.contains(
&entry
let entry = match entry {
Ok(e) => e,
Err(e) => {
eprintln!("[ERROR] Failed to read directory entry: {}", e);
continue;
}
};
let is_file = entry
.file_type()
.await
.map(|ft| ft.is_file())
.unwrap_or(false);
if !is_file {
continue;
}
let ext = entry
.path()
.extension()
.unwrap_or_default()
.to_string_lossy()
.to_lowercase(),
)
{
.to_lowercase();
if !extensions.contains(&ext) {
continue;
}
let file_path = entry.path().to_path_buf();
let semaphone_clone = Arc::clone(&semaphone);
let semaphore_clone = Arc::clone(&semaphore);
let sources_clone = Arc::clone(&sources);
let task = tokio::spawn(async move {
let _permit = semaphone_clone.acquire().await.unwrap();
let _permit = semaphore_clone.acquire().await.unwrap();
process_file(&file_path, sources_clone, overwrite, allow_inaccurate).await;
});
tasks.push(task);
}
}
Err(e) => eprintln!("[ERROR] Failed to read directory entry: {}", e),
}
}
for task in tasks {
let _ = task.await;
@@ -150,12 +167,19 @@ async fn process_file(
overwrite: bool,
allow_inaccurate: bool,
) {
let tag = Tag::async_read_from_path(&file_path).await;
let mut tag = match Tag::async_read_from_path(file_path).await {
Ok(tag) => tag,
Err(e) => {
eprintln!(
"[ERROR] Failed to read ID3 tag for file '{}': {}",
file_path.display(),
e
);
return;
}
};
match tag {
Ok(tag) => {
let lyrics: Vec<&Lyrics> = tag.lyrics().collect();
if !lyrics.is_empty() && !overwrite {
if !overwrite && tag.lyrics().next().is_some() {
println!(
"[INFO] File '{}' already has lyrics, skipping (use --overwrite to force)",
file_path.display()
@@ -182,7 +206,6 @@ async fn process_file(
source.name()
);
let mut tag = tag.clone();
tag.remove_all_lyrics();
tag.remove_all_synchronised_lyrics();
@@ -192,30 +215,23 @@ async fn process_file(
text: lyrics.to_string(),
});
tag.write_to_path(&file_path, Version::Id3v24)
.map_err(|e| {
if let Err(e) = tag.write_to_path(file_path, Version::Id3v24) {
eprintln!(
"[ERROR] Failed to write tags into {}: {}",
file_path.display(),
e
);
})
.ok();
}
break;
}
Err(e) => eprintln!(
Err(e) => {
eprintln!(
"[ERROR] Failed to fetch lyrics for file '{}' from source '{}': {}",
file_path.display(),
source.name(),
e
),
);
}
}
}
Err(e) => eprintln!(
"[ERROR] Failed to read ID3 tag for file '{}': {}",
file_path.display(),
e
),
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::sources::LyricsSource;
use anyhow::{Context, Result, anyhow};
use anyhow::{Context, Ok, Result, anyhow};
use async_trait::async_trait;
use id3::{Tag, TagLike};
use reqwest::Client;
@@ -116,7 +116,7 @@ impl LyricsSource for GeniusSource {
.first()
.ok_or_else(|| anyhow!("No results found on Genius"))?
} else {
return Err(anyhow!("No accurate match found on Genius"));
return Ok("".to_string());
};
let lyrics_url = &selected.result.url;