CLI options, and better path/string handling

This commit is contained in:
Andrew Radev 2019-02-24 11:23:58 +02:00
parent 711314fe57
commit 432ee8f8b6
4 changed files with 68 additions and 43 deletions

View File

@ -1,21 +1,33 @@
use std::env;
use std::process; use std::process;
use std::path::PathBuf;
use structopt::StructOpt;
use id3_image::embed_image; use id3_image::embed_image;
#[derive(StructOpt, Debug)]
#[structopt(name = "id3-image-embed")]
struct Opt {
/// Verbose mode (-v, -vv, -vvv, etc.)
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
verbose: u8,
/// Music file to embed image into
#[structopt(name = "music-file.mp3", required = true, parse(from_os_str))]
music_filename: PathBuf,
/// Image file to embed
#[structopt(name = "image-file.jpg", required = true, parse(from_os_str))]
image_filename: PathBuf,
}
fn main() { fn main() {
let args: Vec<_> = env::args().collect(); let opt = Opt::from_args();
if args.len() < 3 { if let Err(e) = embed_image(&opt.music_filename, &opt.image_filename) {
eprintln!("USAGE: id3-image-embed <mp3-file> <image-file>");
process::exit(1);
}
let music_filename = args[1].clone();
let image_filename = args[2].clone();
if let Err(e) = embed_image(&music_filename, &image_filename) {
eprintln!("{}", e); eprintln!("{}", e);
process::exit(1); process::exit(1);
} }
if opt.verbose >= 1 {
println!("Embedded {:?} into {:?}", opt.image_filename, opt.music_filename);
}
} }

View File

@ -1,28 +1,41 @@
use std::env;
use std::process; use std::process;
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt;
use id3_image::extract_image; use id3_image::extract_image;
#[derive(StructOpt, Debug)]
#[structopt(name = "id3-image-embed")]
struct Opt {
/// Verbose mode (-v, -vv, -vvv, etc.)
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
verbose: u8,
/// Music file to extract image from
#[structopt(name = "music-file.mp3", required = true, parse(from_os_str))]
music_filename: PathBuf,
/// (Optional) Output image: defaults to music filename with .jpg extension
#[structopt(name = "image-file.jpg", parse(from_os_str))]
image_filename: Option<PathBuf>,
}
fn main() { fn main() {
let args: Vec<_> = env::args().collect(); let opt = Opt::from_args();
if args.len() < 2 { let image_filename = opt.image_filename.clone().
eprintln!("USAGE: id3-image-extract <mp3-file> [image-file]"); unwrap_or_else(|| opt.music_filename.with_extension("jpg"));
process::exit(1);
}
let music_filename = args[1].clone(); if let Err(e) = extract_image(&opt.music_filename, &image_filename) {
let image_filename = args.get(2).cloned().unwrap_or_else(|| replace_extension(&music_filename, "jpg"));
if let Err(e) = extract_image(&music_filename, &image_filename) {
eprintln!("{}", e); eprintln!("{}", e);
process::exit(1); process::exit(1);
} }
}
fn replace_extension(path: &str, replacement: &str) -> String { if opt.verbose == 1 {
let mut path = PathBuf::from(&path); // then just print the output filename for scripting purposes:
path.set_extension(replacement); println!("{}", image_filename.display());
path.to_string_lossy().to_string() } else if opt.verbose >= 2 {
// show a longer status message:
println!("Extracted cover art from {:?} to {:?}", opt.music_filename, image_filename);
}
} }

View File

@ -1,11 +1,12 @@
use std::path::Path;
use std::error::Error; use std::error::Error;
pub fn embed_image(music_filename: &str, image_filename: &str) -> Result<(), Box<Error>> { pub fn embed_image(music_filename: &Path, image_filename: &Path) -> Result<(), Box<Error>> {
let mut tag = id3::Tag::read_from_path(&music_filename). let mut tag = id3::Tag::read_from_path(&music_filename).
map_err(|e| format!("Error reading music file {}: {}", music_filename, e))?; map_err(|e| format!("Error reading music file {:?}: {}", music_filename, e))?;
let image = image::open(&image_filename). let image = image::open(&image_filename).
map_err(|e| format!("Error reading image {}: {}", image_filename, e))?; map_err(|e| format!("Error reading image {:?}: {}", image_filename, e))?;
let mut encoded_image_bytes = Vec::new(); let mut encoded_image_bytes = Vec::new();
// Unwrap: Writing to a Vec should always succeed; // Unwrap: Writing to a Vec should always succeed;
@ -19,14 +20,14 @@ pub fn embed_image(music_filename: &str, image_filename: &str) -> Result<(), Box
}); });
tag.write_to_path(music_filename, id3::Version::Id3v23). tag.write_to_path(music_filename, id3::Version::Id3v23).
map_err(|e| format!("Error writing image to music file {}: {}", music_filename, e))?; map_err(|e| format!("Error writing image to music file {:?}: {}", music_filename, e))?;
Ok(()) Ok(())
} }
pub fn extract_image(music_filename: &str, image_filename: &str) -> Result<(), Box<Error>> { pub fn extract_image(music_filename: &Path, image_filename: &Path) -> Result<(), Box<Error>> {
let tag = id3::Tag::read_from_path(&music_filename). let tag = id3::Tag::read_from_path(&music_filename).
map_err(|e| format!("Error reading music file {}: {}", music_filename, e))?; map_err(|e| format!("Error reading music file {:?}: {}", music_filename, e))?;
let first_picture = tag.pictures().next(); let first_picture = tag.pictures().next();
@ -34,8 +35,7 @@ pub fn extract_image(music_filename: &str, image_filename: &str) -> Result<(), B
match image::load_from_memory(&p.data) { match image::load_from_memory(&p.data) {
Ok(image) => { Ok(image) => {
image.save(&image_filename). image.save(&image_filename).
map_err(|e| format!("Couldn't write image file {}: {}", image_filename, e))?; map_err(|e| format!("Couldn't write image file {:?}: {}", image_filename, e))?;
println!("{}", image_filename);
}, },
Err(e) => return Err(format!("Couldn't load image: {}", e).into()), Err(e) => return Err(format!("Couldn't load image: {}", e).into()),
}; };
@ -44,14 +44,14 @@ pub fn extract_image(music_filename: &str, image_filename: &str) -> Result<(), B
Ok(()) Ok(())
} }
pub fn remove_images(music_filename: &str) -> Result<(), Box<Error>> { pub fn remove_images(music_filename: &Path) -> Result<(), Box<Error>> {
let mut tag = id3::Tag::read_from_path(&music_filename). let mut tag = id3::Tag::read_from_path(&music_filename).
map_err(|e| format!("Error reading music file {}: {}", music_filename, e))?; map_err(|e| format!("Error reading music file {:?}: {}", music_filename, e))?;
tag.remove("APIC"); tag.remove("APIC");
tag.write_to_path(music_filename, id3::Version::Id3v23). tag.write_to_path(music_filename, id3::Version::Id3v23).
map_err(|e| format!("Error updating music file {}: {}", music_filename, e))?; map_err(|e| format!("Error updating music file {:?}: {}", music_filename, e))?;
Ok(()) Ok(())
} }

View File

@ -18,10 +18,10 @@ impl AsRef<Path> for Fixture {
} }
impl Deref for Fixture { impl Deref for Fixture {
type Target = str; type Target = Path;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.path_buf.to_str().unwrap() self.path_buf.deref()
} }
} }
@ -44,7 +44,7 @@ macro_rules! fixture {
} }
} }
fn read_tag(path: &str) -> id3::Tag { fn read_tag(path: &Path) -> id3::Tag {
id3::Tag::read_from_path(path).unwrap() id3::Tag::read_from_path(path).unwrap()
} }
@ -54,9 +54,9 @@ fn test_unsuccessful_image_embedding() {
let image = fixture!("attempt_1.jpg"); let image = fixture!("attempt_1.jpg");
// Nonexistent files // Nonexistent files
assert!(embed_image(&song, "nonexisting.jpg").is_err()); assert!(embed_image(&song, &PathBuf::from("nonexistent.jpg")).is_err());
assert!(embed_image("nonexisting.mp3", &image).is_err()); assert!(embed_image(&PathBuf::from("nonexistent.mp3"), &image).is_err());
assert!(embed_image("nonexisting.mp3", "nonexisting.jpg").is_err()); assert!(embed_image(&PathBuf::from("nonexistent.mp3"), &PathBuf::from("nonexistent.jpg")).is_err());
// Wrong kinds of files // Wrong kinds of files
assert!(embed_image(&image, &song).is_err()); assert!(embed_image(&image, &song).is_err());