use crate::{
app::{
field::{ArgsTable, Field, Text},
Run,
},
error::{Error, FileReadFailure},
text_data::{comment::Comment, picture_type::PictureType},
text_format::TextFormat,
utils::ModifyTags,
};
use clap::{Args, Subcommand};
use id3::{frame, Content, Tag, TagLike};
use pipe_trait::Pipe;
use std::{borrow::Cow, fs::read as read_file, path::PathBuf};
pub type Set = Field<SetArgsTable>;
impl Run for Text<SetArgsTable> {
fn run(self) -> Result<(), Error> {
fn set_text(args: SetText, set: impl FnOnce(&mut Tag, String)) -> Result<(), Error> {
let SetText {
no_backup,
target_audio,
value,
} = args;
ModifyTags::builder()
.no_backup(no_backup)
.target_audio(&target_audio)
.build()
.run(move |tag| set(tag, value))
}
match self {
Text::Title(args) => set_text(args, Tag::set_title),
Text::Artist(args) => set_text(args, Tag::set_artist),
Text::Album(args) => set_text(args, Tag::set_album),
Text::AlbumArtist(args) => set_text(args, Tag::set_album_artist),
Text::Genre(SetGenre::Code(args)) => set_text(args, Tag::set_genre),
}
}
}
#[derive(Debug)]
pub struct SetArgsTable;
impl ArgsTable for SetArgsTable {
type Text = SetText;
type Genre = SetGenre;
type Comment = SetComment;
type Picture = SetPicture;
}
#[derive(Debug, Args)]
#[clap(about = "")]
pub struct SetText {
#[clap(long)]
pub no_backup: bool,
pub target_audio: PathBuf,
pub value: String,
}
#[derive(Debug, Subcommand)]
pub enum SetGenre {
#[clap(name = "genre-code")]
Code(SetText),
}
#[derive(Debug, Args)]
#[clap(about = "")]
pub struct SetComment {
#[clap(long)]
pub no_backup: bool,
#[clap(long)]
pub language: Option<String>,
#[clap(long)]
pub description: Option<String>,
#[clap(long, value_enum)]
pub format: Option<TextFormat>,
pub target_audio: PathBuf,
pub content: String,
}
impl Run for SetComment {
fn run(self) -> Result<(), Error> {
let SetComment {
no_backup,
language,
description,
format,
ref target_audio,
content,
} = self;
let language = language.unwrap_or_else(|| "\0\0\0".to_string());
let description = description.unwrap_or_default();
let ejected_frame = ModifyTags {
no_backup,
target_audio,
}
.run(|tag| {
tag.add_frame(Comment {
language,
description,
content,
})
})?;
let ejected_comment = match ejected_frame.as_ref().map(id3::Frame::content) {
None => return Ok(()),
Some(Content::Comment(comment)) => comment,
Some(content) => panic!("Impossible! The ejected frame wasn't a comment: {content:?}"),
};
let output_text: Cow<str> = if let Some(format) = format {
let output_object = Comment::from(ejected_comment);
format.serialize(&output_object)?.pipe(Cow::Owned)
} else {
Cow::Borrowed(&ejected_comment.text)
};
println!("{output_text}");
Ok(())
}
}
#[derive(Debug, Args)]
#[clap(about = "")]
pub struct SetPicture {
#[clap(long)]
pub no_backup: bool,
#[clap(long)]
pub mime_type: Option<String>,
#[clap(long)]
pub description: Option<String>,
pub target_audio: PathBuf,
pub target_picture: PathBuf,
#[clap(value_enum)]
pub picture_type: PictureType,
}
impl Run for SetPicture {
fn run(self) -> Result<(), Error> {
let SetPicture {
no_backup,
mime_type,
description,
ref target_audio,
ref target_picture,
picture_type,
} = self;
let data = read_file(target_picture).map_err(|error| FileReadFailure {
file: target_picture.to_path_buf(),
error,
})?;
let mime_type = mime_type
.or_else(|| infer::get(&data)?.mime_type().to_string().pipe(Some))
.unwrap_or_default();
let description = description.unwrap_or_else(|| {
target_picture
.file_name()
.map(|file_name| file_name.to_string_lossy())
.map(|file_name| file_name.to_string())
.unwrap_or_default()
});
let picture_type: frame::PictureType = picture_type.into();
let frame = frame::Picture {
data,
description,
mime_type,
picture_type,
};
let ejected_picture = ModifyTags::builder()
.target_audio(target_audio)
.no_backup(no_backup)
.build()
.run(|tag| tag.add_frame(frame))?;
if ejected_picture.is_some() {
eprintln!("A picture had been replaced");
} else {
eprintln!("A picture had been added");
}
Ok(())
}
}