Can read image from memory

This commit is contained in:
Pierre HUBERT 2022-03-22 17:44:13 +01:00
parent 7276236936
commit b94d312a9d
2 changed files with 78 additions and 28 deletions

View File

@ -6,6 +6,7 @@
use std::path::Path; use std::path::Path;
use anyhow::anyhow; use anyhow::anyhow;
use image::DynamicImage;
/// Embed the image from `image_filename` into `music_filename`, in-place. Any errors reading ID3 /// 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. /// 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. /// Tags get written as ID3v2.3.
/// ///
pub fn embed_image(music_filename: &Path, image_filename: &Path) -> anyhow::Result<()> { 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). let image = image::open(&image_filename).
map_err(|e| anyhow!("Error reading image {:?}: {}", image_filename, e))?; 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(); let mut encoded_image_bytes = Vec::new();
// Unwrap: Writing to a Vec should always succeed; // Unwrap: Writing to a Vec should always succeed;
image.write_to(&mut encoded_image_bytes, image::ImageOutputFormat::Jpeg(90)).unwrap(); 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. /// there's no embedded images in the mp3 file.
/// ///
pub fn extract_first_image(music_filename: &Path, image_filename: &Path) -> anyhow::Result<()> { 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<DynamicImage> {
let tag = read_tag(music_filename)?; let tag = read_tag(music_filename)?;
let first_picture = tag.pictures().next(); let first_picture = tag.pictures().next();
if let Some(p) = first_picture { if let Some(p) = first_picture {
match image::load_from_memory(&p.data) { return image::load_from_memory(&p.data)
Ok(image) => { .map_err(|e| anyhow!("Couldn't load image: {}", e));
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(())
} else { } else {
Err(anyhow!("No image found in music file")) Err(anyhow!("No image found in music file"))
} }

View File

@ -1,9 +1,10 @@
use std::env; use std::env;
use std::fs; use std::fs;
use std::path::{PathBuf, Path};
use std::ops::Deref; use std::ops::Deref;
use std::path::{Path, PathBuf};
use tempfile::TempDir; use tempfile::TempDir;
use id3_image::*; use id3_image::*;
struct Fixture { struct Fixture {
@ -45,9 +46,10 @@ fn read_tag(path: &Path) -> id3::Tag {
id3::Tag::read_from_path(path).unwrap() id3::Tag::read_from_path(path).unwrap()
} }
#[test] #[test]
fn test_unsuccessful_image_embedding() { 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"); let image = Fixture::copy("attempt_1.jpg");
// Nonexistent files // Nonexistent files
@ -63,11 +65,11 @@ fn test_unsuccessful_image_embedding() {
#[test] #[test]
fn test_successful_jpeg_image_embedding() { 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 image = Fixture::copy("attempt_1.jpg");
let tag = read_tag(&song); let tag = read_tag(&song);
assert!(tag.pictures().count() == 0); assert_eq!(tag.pictures().count(), 0);
embed_image(&song, &image).unwrap(); embed_image(&song, &image).unwrap();
@ -77,7 +79,7 @@ fn test_successful_jpeg_image_embedding() {
#[test] #[test]
fn test_successful_jpeg_image_embedding_with_a_broken_file() { 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"); let image = Fixture::copy("attempt_1.jpg");
embed_image(&song, &image).unwrap(); embed_image(&song, &image).unwrap();
@ -88,11 +90,11 @@ fn test_successful_jpeg_image_embedding_with_a_broken_file() {
#[test] #[test]
fn test_successful_png_image_embedding() { 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 image = Fixture::copy("attempt_1.png");
let tag = read_tag(&song); let tag = read_tag(&song);
assert!(tag.pictures().count() == 0); assert_eq!(tag.pictures().count(), 0);
embed_image(&song, &image).unwrap(); embed_image(&song, &image).unwrap();
@ -100,9 +102,41 @@ fn test_successful_png_image_embedding() {
assert!(tag.pictures().count() > 0); 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] #[test]
fn test_successful_image_embedding_in_a_file_that_already_has_an_image() { 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 image = Fixture::copy("attempt_1.jpg");
let tag = read_tag(&song); let tag = read_tag(&song);
@ -116,7 +150,7 @@ fn test_successful_image_embedding_in_a_file_that_already_has_an_image() {
#[test] #[test]
fn test_removing_and_adding_an_image() { 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 image = Fixture::copy("attempt_1.jpg");
let tag = read_tag(&song); let tag = read_tag(&song);
@ -125,7 +159,7 @@ fn test_removing_and_adding_an_image() {
remove_images(&song).unwrap(); remove_images(&song).unwrap();
let tag = read_tag(&song); let tag = read_tag(&song);
assert!(tag.pictures().count() == 0); assert_eq!(tag.pictures().count(), 0);
embed_image(&song, &image).unwrap(); embed_image(&song, &image).unwrap();
@ -135,13 +169,13 @@ fn test_removing_and_adding_an_image() {
#[test] #[test]
fn test_removing_and_adding_an_image_to_a_broken_file() { 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"); let image = Fixture::copy("attempt_1.jpg");
remove_images(&song).unwrap(); remove_images(&song).unwrap();
let tag = read_tag(&song); let tag = read_tag(&song);
assert!(tag.pictures().count() == 0); assert_eq!(tag.pictures().count(), 0);
embed_image(&song, &image).unwrap(); embed_image(&song, &image).unwrap();
@ -151,7 +185,7 @@ fn test_removing_and_adding_an_image_to_a_broken_file() {
#[test] #[test]
fn test_extracting_a_jpg_image() { 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 image = Fixture::blank("attempt_1.jpg");
let tag = read_tag(&song); let tag = read_tag(&song);
@ -165,7 +199,7 @@ fn test_extracting_a_jpg_image() {
#[test] #[test]
fn test_extracting_a_jpg_image_from_a_broken_file() { 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"); let image = Fixture::blank("attempt_1.jpg");
extract_first_image(&song, &image).unwrap(); extract_first_image(&song, &image).unwrap();
@ -175,7 +209,7 @@ fn test_extracting_a_jpg_image_from_a_broken_file() {
#[test] #[test]
fn test_extracting_a_png_image() { 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 image = Fixture::blank("attempt_1.png");
let tag = read_tag(&song); let tag = read_tag(&song);
@ -189,7 +223,7 @@ fn test_extracting_a_png_image() {
#[test] #[test]
fn test_overwriting_an_existing_image() { 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 image = Fixture::copy("attempt_1.jpg");
let tag = read_tag(&song); let tag = read_tag(&song);
@ -203,11 +237,11 @@ fn test_overwriting_an_existing_image() {
#[test] #[test]
fn test_extracting_an_image_with_no_pictures() { 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 image = Fixture::blank("attempt_1.jpg");
let tag = read_tag(&song); let tag = read_tag(&song);
assert!(tag.pictures().count() == 0); assert_eq!(tag.pictures().count(), 0);
assert!(!image.exists()); assert!(!image.exists());
assert!(extract_first_image(&song, &image).is_err()); assert!(extract_first_image(&song, &image).is_err());