#![deny(clippy::all, clippy::pedantic)]
#![allow(clippy::single_match_else, clippy::module_name_repetitions)]
mod config;
mod error;
mod fetchers;
mod utils;
use crate::config::Config;
use crate::error::{Error, Result};
use std::path::PathBuf;
use std::{fs, process};
use clap::{Parser, ValueHint};
use lofty::{Accessor, AudioFile, ItemKey};
#[derive(Parser)]
#[clap(name = "lyr")]
#[clap(author = "Serial-ATA")]
#[clap(about = "Fetches and embeds lyrics from multiple sources")]
struct Args {
#[clap(long, short)]
title: Option<String>,
#[clap(long, short)]
artist: Option<String>,
#[clap(
long,
short,
required_unless_present_all = &["title", "artist"],
value_hint = ValueHint::FilePath,
help = "A file to extract the artist/title from, and optionally embed the lyrics into"
)]
input: Option<PathBuf>,
#[clap(
long,
requires = "input",
help = "If the lyrics should be embedded in the tags of the input file"
)]
no_embed: bool,
#[clap(name = "OUTPUT TEXT FILE", value_hint = ValueHint::FilePath)]
output: Option<PathBuf>,
}
fn main() {
utils::setup_logger();
let args = Args::parse();
if let Err(e) = real_main(args) {
log::error!("{e}");
process::exit(-1);
}
}
fn real_main(args: Args) -> Result<()> {
let config = Config::read()?;
let (title, artist) = {
if let (Some(title), Some(artist)) = (args.title, args.artist) {
(title.to_lowercase(), artist.to_lowercase())
} else {
let file = lofty::read_from_path(args.input.as_ref().unwrap(), false)?;
let mut title = None;
let mut artist = None;
for tag in file.tags() {
if title.is_some() && artist.is_some() {
break;
}
title = tag.title().map(str::to_lowercase);
artist = tag.artist().map(str::to_lowercase);
}
match (title, artist) {
(Some(title), Some(artist)) => (title, artist),
(None, _) | (_, None) => return Err(Error::InvalidTags),
}
}
};
let mut fetchers = config.fetchers.iter();
let lyrics;
loop {
if let Some(fetcher) = fetchers.next() {
if let Ok(lyrics_) = fetchers::fetch(fetcher, &title, &artist) {
lyrics = lyrics_;
break;
}
continue;
}
return Err(Error::NoLyrics);
}
if args.output.is_none() && (args.input.is_none() || args.no_embed) {
println!("{}", lyrics);
return Ok(());
}
if let Some(ref output) = args.output {
fs::write(output, &lyrics)?;
}
if let Some(ref input) = args.input {
if !args.no_embed {
let mut file = lofty::read_from_path(input, false)?;
let contains_tags = file.contains_tag();
let tag = match file.primary_tag_mut() {
Some(t) => t,
_ if contains_tags => file.first_tag_mut().unwrap(),
_ => {
log::warn!("The input file doesn't have any eligible tags to write the lyrics");
process::exit(0);
},
};
tag.insert_text(ItemKey::Lyrics, lyrics);
file.save_to_path(input)?;
}
}
Ok(())
}