Add tag_writer example

This commit is contained in:
Serial 2021-12-20 10:56:32 -05:00
parent 2273e4608f
commit ea0f3007e8
4 changed files with 149 additions and 8 deletions

View file

@ -32,6 +32,7 @@ riff_info_list = []
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.3.5", features = ["html_reports"] } criterion = { version = "0.3.5", features = ["html_reports"] }
structopt = { version = "0.3.25", default-features = false }
tempfile = "3.2.0" tempfile = "3.2.0"
[[bench]] [[bench]]

View file

@ -12,7 +12,7 @@ fn main() {
let tags = tagged_file.tags(); let tags = tagged_file.tags();
if tags.is_empty() { if tags.is_empty() {
println!("No tags found, exiting."); eprintln!("No tags found, exiting.");
std::process::exit(0); std::process::exit(0);
} }
@ -45,14 +45,14 @@ fn main() {
} }
input.clear(); input.clear();
println!("Bad input") eprintln!("ERROR: Unexpected input")
} }
let tag_remove = available_tag_types[to_remove.unwrap()]; let tag_remove = available_tag_types[to_remove.unwrap()];
if tag_remove.remove_from_path(path) { if tag_remove.remove_from_path(path) {
println!("Removed tag: {:?}", tag_remove); println!("INFO: Removed tag: `{:?}`", tag_remove);
} else { } else {
println!("Failed to remove the tag") eprintln!("ERROR: Failed to remove the tag")
} }
} }

83
examples/tag_writer.rs Normal file
View file

@ -0,0 +1,83 @@
use lofty::{Accessor, Probe, Tag};
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(name = "tag_writer", about = "A simple tag writer example")]
struct Opt {
#[structopt(short, long)]
title: Option<String>,
#[structopt(short, long)]
artist: Option<String>,
#[structopt(short = "A", long)]
album: Option<String>,
#[structopt(short, long)]
genre: Option<String>,
#[structopt(short, long)]
path: String,
}
fn main() {
let opt = Opt::from_args();
let mut tagged_file = Probe::open(opt.path.as_str())
.expect("Error: Bad path provided!")
.read()
.expect("Error: Failed to read file!");
let tag = match tagged_file.primary_tag_mut() {
Some(primary_tag) => primary_tag,
None => {
if let Some(first_tag) = tagged_file.first_tag_mut() {
first_tag
} else {
let tag_type = tagged_file.primary_tag_type();
eprintln!(
"WARN: No tags found, creating a new tag of type `{:?}`",
tag_type
);
tagged_file.insert_tag(Tag::new(tag_type));
tagged_file.primary_tag_mut().unwrap()
}
},
};
if let Opt {
title: None,
artist: None,
album: None,
genre: None,
..
} = opt
{
eprintln!("ERROR: No options provided!");
std::process::exit(1);
}
if let Some(title) = opt.title {
tag.set_title(title)
}
if let Some(artist) = opt.artist {
tag.set_artist(artist)
}
if let Some(album) = opt.album {
tag.set_album(album)
}
if let Some(genre) = opt.genre {
tag.set_genre(genre)
}
tag.save_to_path(opt.path)
.expect("ERROR: Failed to write the tag!");
println!("INFO: Tag successfully updated!");
}

View file

@ -4,6 +4,7 @@ use crate::error::{LoftyError, Result};
use std::convert::TryInto; use std::convert::TryInto;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use std::path::Path; use std::path::Path;
@ -53,18 +54,21 @@ impl TaggedFile {
/// | `FLAC`, `Opus`, `Vorbis` | `VorbisComments` | /// | `FLAC`, `Opus`, `Vorbis` | `VorbisComments` |
/// | `MP4` | `Mp4Ilst` | /// | `MP4` | `Mp4Ilst` |
pub fn primary_tag(&self) -> Option<&Tag> { pub fn primary_tag(&self) -> Option<&Tag> {
self.tag(&Self::primary_tag_type(self.ty)) self.tag(&self.primary_tag_type())
} }
/// Gets a mutable reference to the file's "Primary tag" /// Gets a mutable reference to the file's "Primary tag"
/// ///
/// See [`primary_tag`](Self::primary_tag) for an explanation /// See [`primary_tag`](Self::primary_tag) for an explanation
pub fn primary_tag_mut(&mut self) -> Option<&mut Tag> { pub fn primary_tag_mut(&mut self) -> Option<&mut Tag> {
self.tag_mut(&Self::primary_tag_type(self.ty)) self.tag_mut(&self.primary_tag_type())
} }
fn primary_tag_type(f_ty: FileType) -> TagType { /// Returns the file type's "primary" [`TagType`]
match f_ty { ///
/// See [`primary_tag`](Self::primary_tag) for an explanation
pub fn primary_tag_type(&self) -> TagType {
match self.ty {
#[cfg(feature = "id3v2")] #[cfg(feature = "id3v2")]
FileType::AIFF | FileType::MP3 | FileType::WAV => TagType::Id3v2, FileType::AIFF | FileType::MP3 | FileType::WAV => TagType::Id3v2,
#[cfg(feature = "ape")] #[cfg(feature = "ape")]
@ -76,6 +80,11 @@ impl TaggedFile {
} }
} }
/// Determines whether the file supports the given [`TagType`]
pub fn supports_tag_type(&self, tag_type: TagType) -> bool {
self.ty.supports_tag_type(&tag_type)
}
/// Returns all tags /// Returns all tags
pub fn tags(&self) -> &[Tag] { pub fn tags(&self) -> &[Tag] {
self.tags.as_slice() self.tags.as_slice()
@ -101,6 +110,32 @@ impl TaggedFile {
self.tags.iter_mut().find(|i| i.tag_type() == tag_type) self.tags.iter_mut().find(|i| i.tag_type() == tag_type)
} }
/// Inserts a [`Tag`]
///
/// If a tag is replaced, it will be returned
pub fn insert_tag(&mut self, tag: Tag) -> Option<Tag> {
let tag_type = *tag.tag_type();
if self.supports_tag_type(tag_type) {
let ret = self.remove_tag(tag_type);
self.tags.push(tag);
return ret;
}
None
}
/// Removes a specific [`TagType`]
///
/// This will return the tag if it is removed
pub fn remove_tag(&mut self, tag_type: TagType) -> Option<Tag> {
self.tags
.iter()
.position(|t| t.tag_type() == &tag_type)
.map(|pos| self.tags.remove(pos))
}
/// Returns the file's [`FileType`] /// Returns the file's [`FileType`]
pub fn file_type(&self) -> &FileType { pub fn file_type(&self) -> &FileType {
&self.ty &self.ty
@ -110,6 +145,28 @@ impl TaggedFile {
pub fn properties(&self) -> &FileProperties { pub fn properties(&self) -> &FileProperties {
&self.properties &self.properties
} }
/// Attempts to write all tags to a path
///
/// # Errors
///
/// See [TaggedFile::save_to]
pub fn save_to_path(&self, path: impl AsRef<Path>) -> Result<()> {
self.save_to(&mut OpenOptions::new().read(true).write(true).open(path)?)
}
/// Attempts to write all tags to a file
///
/// # Errors
///
/// See [`Tag::save_to`], however this is applicable to every tag in the `TaggedFile`.
pub fn save_to(&self, file: &mut File) -> Result<()> {
for tag in &self.tags {
tag.save_to(file)?;
}
Ok(())
}
} }
#[derive(PartialEq, Copy, Clone, Debug)] #[derive(PartialEq, Copy, Clone, Debug)]