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
+106 -90
View File
@@ -52,8 +52,6 @@ async fn main() -> Result<()> {
let threads = args.threads; let threads = args.threads;
let overwrite = args.overwrite_existing; let overwrite = args.overwrite_existing;
let allow_inaccurate = args.allow_inaccurate; 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() { if !music_dir_path.exists() {
eprintln!("[ERROR] The specified music directory does not exist."); eprintln!("[ERROR] The specified music directory does not exist.");
@@ -65,22 +63,29 @@ async fn main() -> Result<()> {
exit(1); exit(1);
} }
if lyrics_sources_raw.is_empty() { if args.sources.is_empty() {
eprintln!("[ERROR] At least one source must be specified."); eprintln!("[ERROR] At least one source must be specified.");
exit(1); exit(1);
} }
if extensions.is_empty() { if args.extensions.is_empty() {
eprintln!("[ERROR] At least one file extension must be specified."); eprintln!("[ERROR] At least one file extension must be specified.");
exit(1); exit(1);
} }
let lyrics_sources_raw: Vec<String> = lyrics_sources_raw let lyrics_sources_raw: Vec<String> = args
.iter() .sources
.into_iter()
.unique() .unique()
.map(|s| s.to_lowercase()) .map(|s| s.to_lowercase())
.collect(); .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] Music directory: {}", music_dir_path.display());
println!("[INFO] Number of threads: {}", threads); println!("[INFO] Number of threads: {}", threads);
println!("[INFO] Sources: {}", lyrics_sources_raw.join(", ")); println!("[INFO] Sources: {}", lyrics_sources_raw.join(", "));
@@ -106,35 +111,47 @@ async fn main() -> Result<()> {
} }
let sources = Arc::new(sources); 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 entries = WalkDir::new(music_dir_path);
let mut tasks = Vec::new(); let mut tasks = Vec::new();
while let Some(entry) = entries.next().await { while let Some(entry) = entries.next().await {
match entry { let entry = match entry {
Ok(entry) => { Ok(e) => e,
if entry.file_type().await?.is_file() Err(e) => {
&& extensions.contains( eprintln!("[ERROR] Failed to read directory entry: {}", e);
&entry continue;
.path()
.extension()
.unwrap_or_default()
.to_string_lossy()
.to_lowercase(),
)
{
let file_path = entry.path().to_path_buf();
let semaphone_clone = Arc::clone(&semaphone);
let sources_clone = Arc::clone(&sources);
let task = tokio::spawn(async move {
let _permit = semaphone_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), };
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();
if !extensions.contains(&ext) {
continue;
}
let file_path = entry.path().to_path_buf();
let semaphore_clone = Arc::clone(&semaphore);
let sources_clone = Arc::clone(&sources);
let task = tokio::spawn(async move {
let _permit = semaphore_clone.acquire().await.unwrap();
process_file(&file_path, sources_clone, overwrite, allow_inaccurate).await;
});
tasks.push(task);
} }
for task in tasks { for task in tasks {
@@ -150,72 +167,71 @@ async fn process_file(
overwrite: bool, overwrite: bool,
allow_inaccurate: 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 { if !overwrite && tag.lyrics().next().is_some() {
Ok(tag) => { println!(
let lyrics: Vec<&Lyrics> = tag.lyrics().collect(); "[INFO] File '{}' already has lyrics, skipping (use --overwrite to force)",
if !lyrics.is_empty() && !overwrite { file_path.display()
println!( );
"[INFO] File '{}' already has lyrics, skipping (use --overwrite to force)", return;
file_path.display() }
);
return;
}
for source in sources.iter() { for source in sources.iter() {
match source.fetch_lyrics(&tag, allow_inaccurate).await { match source.fetch_lyrics(&tag, allow_inaccurate).await {
Ok(lyrics) => { Ok(lyrics) => {
let lyrics = lyrics.trim(); let lyrics = lyrics.trim();
if lyrics.is_empty() { if lyrics.is_empty() {
println!( println!(
"[INFO] Source '{}' did not return any lyrics for file '{}'", "[INFO] Source '{}' did not return any lyrics for file '{}'",
source.name(),
file_path.display()
);
continue;
}
println!(
"[INFO] Successfully fetched lyrics for file '{}' from source '{}'",
file_path.display(),
source.name()
);
let mut tag = tag.clone();
tag.remove_all_lyrics();
tag.remove_all_synchronised_lyrics();
tag.add_frame(Lyrics {
lang: "XXX".to_string(),
description: format!("Fetched from {}", source.name()),
text: lyrics.to_string(),
});
tag.write_to_path(&file_path, Version::Id3v24)
.map_err(|e| {
eprintln!(
"[ERROR] Failed to write tags into {}: {}",
file_path.display(),
e
);
})
.ok();
break;
}
Err(e) => eprintln!(
"[ERROR] Failed to fetch lyrics for file '{}' from source '{}': {}",
file_path.display(),
source.name(), source.name(),
e file_path.display()
), );
continue;
} }
println!(
"[INFO] Successfully fetched lyrics for file '{}' from source '{}'",
file_path.display(),
source.name()
);
tag.remove_all_lyrics();
tag.remove_all_synchronised_lyrics();
tag.add_frame(Lyrics {
lang: "XXX".to_string(),
description: format!("Fetched from {}", source.name()),
text: lyrics.to_string(),
});
if let Err(e) = tag.write_to_path(file_path, Version::Id3v24) {
eprintln!(
"[ERROR] Failed to write tags into {}: {}",
file_path.display(),
e
);
}
break;
}
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 crate::sources::LyricsSource;
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Ok, Result, anyhow};
use async_trait::async_trait; use async_trait::async_trait;
use id3::{Tag, TagLike}; use id3::{Tag, TagLike};
use reqwest::Client; use reqwest::Client;
@@ -116,7 +116,7 @@ impl LyricsSource for GeniusSource {
.first() .first()
.ok_or_else(|| anyhow!("No results found on Genius"))? .ok_or_else(|| anyhow!("No results found on Genius"))?
} else { } else {
return Err(anyhow!("No accurate match found on Genius")); return Ok("".to_string());
}; };
let lyrics_url = &selected.result.url; let lyrics_url = &selected.result.url;