From b94d312a9d4dfbd69d3ff1692d141a851f039a33 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Tue, 22 Mar 2022 17:44:13 +0100 Subject: [PATCH] Can read image from memory --- src/lib.rs | 36 +++++++++++++++------ tests/test_processing.rs | 70 +++++++++++++++++++++++++++++----------- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7b9ae55..cc1fb6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use std::path::Path; use anyhow::anyhow; +use image::DynamicImage; /// Embed the image from `image_filename` into `music_filename`, in-place. Any errors reading ID3 /// tags from the music file or parsing the image get propagated upwards. @@ -14,10 +15,21 @@ use anyhow::anyhow; /// Tags get written as ID3v2.3. /// pub fn embed_image(music_filename: &Path, image_filename: &Path) -> anyhow::Result<()> { - let mut tag = read_tag(music_filename)?; let image = image::open(&image_filename). map_err(|e| anyhow!("Error reading image {:?}: {}", image_filename, e))?; + embed_image_from_memory(music_filename, &image) +} + +/// Embed the image `image` into `music_filename`, in-place. Any errors reading ID3 +/// tags from the music file get propagated upwards. +/// +/// The image is encoded as a JPEG with a 90% quality setting, and embedded as a "Front cover". +/// Tags get written as ID3v2.3. +/// +pub fn embed_image_from_memory(music_filename: &Path, image: &image::DynamicImage) -> anyhow::Result<()> { + let mut tag = read_tag(music_filename)?; + let mut encoded_image_bytes = Vec::new(); // Unwrap: Writing to a Vec should always succeed; image.write_to(&mut encoded_image_bytes, image::ImageOutputFormat::Jpeg(90)).unwrap(); @@ -42,19 +54,23 @@ pub fn embed_image(music_filename: &Path, image_filename: &Path) -> anyhow::Resu /// there's no embedded images in the mp3 file. /// pub fn extract_first_image(music_filename: &Path, image_filename: &Path) -> anyhow::Result<()> { + extract_first_image_as_img(music_filename)? + .save(&image_filename). + map_err(|e| anyhow!("Couldn't write image file {:?}: {}", image_filename, e)) +} + +/// Extract the first found embedded image from `music_filename` and return it as image object +/// +/// Any errors from parsing id3 tags will be propagated. The function will also return an error if +/// there's no embedded images in the mp3 file. +/// +pub fn extract_first_image_as_img(music_filename: &Path) -> anyhow::Result { let tag = read_tag(music_filename)?; let first_picture = tag.pictures().next(); if let Some(p) = first_picture { - match image::load_from_memory(&p.data) { - Ok(image) => { - image.save(&image_filename). - map_err(|e| anyhow!("Couldn't write image file {:?}: {}", image_filename, e))?; - }, - Err(e) => return Err(anyhow!("Couldn't load image: {}", e)), - }; - - Ok(()) + return image::load_from_memory(&p.data) + .map_err(|e| anyhow!("Couldn't load image: {}", e)); } else { Err(anyhow!("No image found in music file")) } diff --git a/tests/test_processing.rs b/tests/test_processing.rs index f001f78..8616161 100644 --- a/tests/test_processing.rs +++ b/tests/test_processing.rs @@ -1,9 +1,10 @@ use std::env; use std::fs; -use std::path::{PathBuf, Path}; use std::ops::Deref; +use std::path::{Path, PathBuf}; use tempfile::TempDir; + use id3_image::*; struct Fixture { @@ -45,9 +46,10 @@ fn read_tag(path: &Path) -> id3::Tag { id3::Tag::read_from_path(path).unwrap() } + #[test] fn test_unsuccessful_image_embedding() { - let song = Fixture::copy("attempt_1_no_image.mp3"); + let song = Fixture::copy("attempt_1_no_image.mp3"); let image = Fixture::copy("attempt_1.jpg"); // Nonexistent files @@ -63,11 +65,11 @@ fn test_unsuccessful_image_embedding() { #[test] fn test_successful_jpeg_image_embedding() { - let song = Fixture::copy("attempt_1_no_image.mp3"); + let song = Fixture::copy("attempt_1_no_image.mp3"); let image = Fixture::copy("attempt_1.jpg"); let tag = read_tag(&song); - assert!(tag.pictures().count() == 0); + assert_eq!(tag.pictures().count(), 0); embed_image(&song, &image).unwrap(); @@ -77,7 +79,7 @@ fn test_successful_jpeg_image_embedding() { #[test] fn test_successful_jpeg_image_embedding_with_a_broken_file() { - let song = Fixture::copy("attempt_1_broken.mp3"); + let song = Fixture::copy("attempt_1_broken.mp3"); let image = Fixture::copy("attempt_1.jpg"); embed_image(&song, &image).unwrap(); @@ -88,11 +90,11 @@ fn test_successful_jpeg_image_embedding_with_a_broken_file() { #[test] fn test_successful_png_image_embedding() { - let song = Fixture::copy("attempt_1_no_image.mp3"); + let song = Fixture::copy("attempt_1_no_image.mp3"); let image = Fixture::copy("attempt_1.png"); let tag = read_tag(&song); - assert!(tag.pictures().count() == 0); + assert_eq!(tag.pictures().count(), 0); embed_image(&song, &image).unwrap(); @@ -100,9 +102,41 @@ fn test_successful_png_image_embedding() { assert!(tag.pictures().count() > 0); } +#[test] +fn test_successful_png_image_embedding_from_memory() { + let song = Fixture::copy("attempt_1_no_image.mp3"); + let image = Fixture::copy("attempt_1.png"); + + let tag = read_tag(&song); + assert_eq!(tag.pictures().count(), 0); + + embed_image_from_memory(&song, &image::open(&*image).unwrap()).unwrap(); + + let tag = read_tag(&song); + assert!(tag.pictures().count() > 0); +} + +#[test] +fn test_successful_png_image_embedding_and_extracting() { + let song = Fixture::copy("attempt_1_no_image.mp3"); + let image = Fixture::copy("attempt_1.png"); + + let tag = read_tag(&song); + assert_eq!(tag.pictures().count(), 0); + + extract_first_image_as_img(&song).unwrap_err(); + + embed_image(&song, &image).unwrap(); + + let tag = read_tag(&song); + assert!(tag.pictures().count() > 0); + + extract_first_image_as_img(&song).unwrap(); +} + #[test] fn test_successful_image_embedding_in_a_file_that_already_has_an_image() { - let song = Fixture::copy("attempt_1.mp3"); + let song = Fixture::copy("attempt_1.mp3"); let image = Fixture::copy("attempt_1.jpg"); let tag = read_tag(&song); @@ -116,7 +150,7 @@ fn test_successful_image_embedding_in_a_file_that_already_has_an_image() { #[test] fn test_removing_and_adding_an_image() { - let song = Fixture::copy("attempt_1.mp3"); + let song = Fixture::copy("attempt_1.mp3"); let image = Fixture::copy("attempt_1.jpg"); let tag = read_tag(&song); @@ -125,7 +159,7 @@ fn test_removing_and_adding_an_image() { remove_images(&song).unwrap(); let tag = read_tag(&song); - assert!(tag.pictures().count() == 0); + assert_eq!(tag.pictures().count(), 0); embed_image(&song, &image).unwrap(); @@ -135,13 +169,13 @@ fn test_removing_and_adding_an_image() { #[test] fn test_removing_and_adding_an_image_to_a_broken_file() { - let song = Fixture::copy("attempt_1_broken.mp3"); + let song = Fixture::copy("attempt_1_broken.mp3"); let image = Fixture::copy("attempt_1.jpg"); remove_images(&song).unwrap(); let tag = read_tag(&song); - assert!(tag.pictures().count() == 0); + assert_eq!(tag.pictures().count(), 0); embed_image(&song, &image).unwrap(); @@ -151,7 +185,7 @@ fn test_removing_and_adding_an_image_to_a_broken_file() { #[test] fn test_extracting_a_jpg_image() { - let song = Fixture::copy("attempt_1.mp3"); + let song = Fixture::copy("attempt_1.mp3"); let image = Fixture::blank("attempt_1.jpg"); let tag = read_tag(&song); @@ -165,7 +199,7 @@ fn test_extracting_a_jpg_image() { #[test] fn test_extracting_a_jpg_image_from_a_broken_file() { - let song = Fixture::copy("attempt_1_broken.mp3"); + let song = Fixture::copy("attempt_1_broken.mp3"); let image = Fixture::blank("attempt_1.jpg"); extract_first_image(&song, &image).unwrap(); @@ -175,7 +209,7 @@ fn test_extracting_a_jpg_image_from_a_broken_file() { #[test] fn test_extracting_a_png_image() { - let song = Fixture::copy("attempt_1.mp3"); + let song = Fixture::copy("attempt_1.mp3"); let image = Fixture::blank("attempt_1.png"); let tag = read_tag(&song); @@ -189,7 +223,7 @@ fn test_extracting_a_png_image() { #[test] fn test_overwriting_an_existing_image() { - let song = Fixture::copy("attempt_1.mp3"); + let song = Fixture::copy("attempt_1.mp3"); let image = Fixture::copy("attempt_1.jpg"); let tag = read_tag(&song); @@ -203,11 +237,11 @@ fn test_overwriting_an_existing_image() { #[test] fn test_extracting_an_image_with_no_pictures() { - let song = Fixture::copy("attempt_1_no_image.mp3"); + let song = Fixture::copy("attempt_1_no_image.mp3"); let image = Fixture::blank("attempt_1.jpg"); let tag = read_tag(&song); - assert!(tag.pictures().count() == 0); + assert_eq!(tag.pictures().count(), 0); assert!(!image.exists()); assert!(extract_first_image(&song, &image).is_err());