mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
Album cover support for ogg/opus/flac
This commit is contained in:
parent
946d19e0e1
commit
c285b36629
8 changed files with 114 additions and 64 deletions
|
@ -30,6 +30,7 @@ metaflac = {version = "0.2.4", optional = true}
|
|||
opus_headers = {version = "0.1.2", optional = true}
|
||||
# Errors
|
||||
thiserror = "1.0.24"
|
||||
base64 = "0.13.0"
|
||||
byteorder = "1.4.3"
|
||||
filepath = "0.1.1"
|
||||
|
||||
|
|
|
@ -44,19 +44,16 @@ impl Id3v2Tag {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a id3::frame::Picture> for Picture<'a> {
|
||||
impl std::convert::TryFrom<id3::frame::Picture> for Picture {
|
||||
type Error = Error;
|
||||
fn try_from(inp: &'a id3::frame::Picture) -> Result<Self> {
|
||||
let &id3::frame::Picture {
|
||||
fn try_from(inp: id3::frame::Picture) -> Result<Self> {
|
||||
let id3::frame::Picture {
|
||||
ref mime_type,
|
||||
ref data,
|
||||
data,
|
||||
..
|
||||
} = inp;
|
||||
let mime_type: MimeType = mime_type.as_str().try_into()?;
|
||||
Ok(Self {
|
||||
data: &data,
|
||||
mime_type,
|
||||
})
|
||||
Ok(Self { data, mime_type })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +126,7 @@ impl AudioTagEdit for Id3v2Tag {
|
|||
.find(|&pic| matches!(pic.picture_type, id3::frame::PictureType::CoverFront))
|
||||
.and_then(|pic| {
|
||||
Some(Picture {
|
||||
data: &pic.data,
|
||||
data: pic.data.clone(),
|
||||
mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
})
|
||||
})
|
||||
|
@ -140,7 +137,7 @@ impl AudioTagEdit for Id3v2Tag {
|
|||
mime_type: String::from(cover.mime_type),
|
||||
picture_type: id3::frame::PictureType::CoverFront,
|
||||
description: "".to_owned(),
|
||||
data: cover.data.to_owned(),
|
||||
data: cover.data,
|
||||
});
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
|
|
|
@ -28,15 +28,15 @@ impl Mp4Tag {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a mp4ameta::Data> for Picture<'a> {
|
||||
impl std::convert::TryFrom<mp4ameta::Data> for Picture {
|
||||
type Error = Error;
|
||||
fn try_from(inp: &'a mp4ameta::Data) -> Result<Self> {
|
||||
Ok(match *inp {
|
||||
mp4ameta::Data::Png(ref data) => Self {
|
||||
fn try_from(inp: mp4ameta::Data) -> Result<Self> {
|
||||
Ok(match inp {
|
||||
mp4ameta::Data::Png(data) => Self {
|
||||
data,
|
||||
mime_type: MimeType::Png,
|
||||
},
|
||||
mp4ameta::Data::Jpeg(ref data) => Self {
|
||||
mp4ameta::Data::Jpeg(data) => Self {
|
||||
data,
|
||||
mime_type: MimeType::Jpeg,
|
||||
},
|
||||
|
@ -104,11 +104,11 @@ impl AudioTagEdit for Mp4Tag {
|
|||
|
||||
self.inner.artwork().and_then(|data| match data {
|
||||
Jpeg(d) => Some(Picture {
|
||||
data: d,
|
||||
data: d.clone(),
|
||||
mime_type: MimeType::Jpeg,
|
||||
}),
|
||||
Png(d) => Some(Picture {
|
||||
data: d,
|
||||
data: d.clone(),
|
||||
mime_type: MimeType::Png,
|
||||
}),
|
||||
_ => None,
|
||||
|
@ -118,8 +118,8 @@ impl AudioTagEdit for Mp4Tag {
|
|||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
self.remove_album_cover();
|
||||
self.inner.add_artwork(match cover.mime_type {
|
||||
MimeType::Png => mp4ameta::Data::Png(cover.data.to_owned()),
|
||||
MimeType::Jpeg => mp4ameta::Data::Jpeg(cover.data.to_owned()),
|
||||
MimeType::Png => mp4ameta::Data::Png(cover.data),
|
||||
MimeType::Jpeg => mp4ameta::Data::Jpeg(cover.data),
|
||||
_ => panic!("Only png and jpeg are supported in m4a"),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ impl AudioTagWrite for RiffTag {
|
|||
|
||||
chunk.extend(riff::LIST_ID.value.iter());
|
||||
|
||||
let fourcc = "INFO"; // TODO: ID3
|
||||
let fourcc = "INFO";
|
||||
chunk.extend(fourcc.as_bytes().iter());
|
||||
|
||||
for (k, v) in data {
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
use crate::components::logic;
|
||||
use crate::tag::VorbisFormat;
|
||||
use crate::{
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Picture, Result, TagType,
|
||||
ToAny, ToAnyTag,
|
||||
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, MimeType, Picture, Result,
|
||||
TagType, ToAny, ToAnyTag,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::path::Path;
|
||||
|
@ -263,31 +264,84 @@ impl AudioTagEdit for VorbisTag {
|
|||
}
|
||||
|
||||
fn album_cover(&self) -> Option<Picture> {
|
||||
// TODO
|
||||
// self.inner
|
||||
// .pictures()
|
||||
// .filter(|&pic| matches!(pic.picture_type, metaflac::block::PictureType::CoverFront))
|
||||
// .next()
|
||||
// .and_then(|pic| {
|
||||
// Some(Picture {
|
||||
// data: &pic.data,
|
||||
// mime_type: (pic.mime_type.as_str()).try_into().ok()?,
|
||||
// })
|
||||
// })
|
||||
None
|
||||
let picture = self.inner.get_value("METADATA_BLOCK_PICTURE");
|
||||
|
||||
return match picture {
|
||||
None => None,
|
||||
Some(data) => {
|
||||
let data_str = data.to_string();
|
||||
let data = data_str.as_bytes();
|
||||
|
||||
if data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
fn set_album_cover(&mut self, _cover: Picture) {
|
||||
// TODO
|
||||
// self.remove_album_cover();
|
||||
// let mime = String::from(cover.mime_type);
|
||||
// let picture_type = metaflac::block::PictureType::CoverFront;
|
||||
// self.inner
|
||||
// .add_picture(mime, picture_type, (cover.data).to_owned());
|
||||
|
||||
let data = match base64::decode(data) {
|
||||
Ok(o) => o,
|
||||
Err(_) => data.to_vec(),
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
let picture_type = u32::from_le_bytes(match (&data[i..i + 4]).try_into() {
|
||||
Ok(o) => o,
|
||||
Err(_) => return None,
|
||||
});
|
||||
|
||||
if picture_type != 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
match data[i..i + 4].try_into() {
|
||||
Ok(mime_len) => {
|
||||
i += 4;
|
||||
let mime_len = u32::from_le_bytes(mime_len);
|
||||
|
||||
match String::from_utf8(data[i..i + mime_len as usize].to_vec()) {
|
||||
Ok(mime_type) => {
|
||||
let mime_type = MimeType::try_from(&*mime_type);
|
||||
|
||||
match mime_type {
|
||||
Ok(mime_type) => {
|
||||
let content = data[(8 + mime_len) as usize..].to_vec();
|
||||
|
||||
Some(Picture {
|
||||
data: content,
|
||||
mime_type,
|
||||
})
|
||||
},
|
||||
Err(_) => return None,
|
||||
}
|
||||
},
|
||||
Err(_) => return None,
|
||||
}
|
||||
},
|
||||
Err(_) => return None,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
fn set_album_cover(&mut self, cover: Picture) {
|
||||
self.remove_album_cover();
|
||||
let mime = String::from(cover.mime_type);
|
||||
let mime_len = (mime.len() as u32).to_le_bytes();
|
||||
|
||||
let picture_type = 3_u32.to_le_bytes();
|
||||
let data = cover.data;
|
||||
|
||||
let mut encoded = Vec::new();
|
||||
encoded.extend(picture_type.iter());
|
||||
encoded.extend(mime_len.iter());
|
||||
encoded.extend(mime.as_bytes().iter());
|
||||
encoded.extend(data.iter());
|
||||
|
||||
let encoded = base64::encode(encoded);
|
||||
|
||||
self.inner.set_value("METADATA_BLOCK_PICTURE", encoded);
|
||||
}
|
||||
fn remove_album_cover(&mut self) {
|
||||
// TODO
|
||||
// self.inner
|
||||
// .remove_picture_type(metaflac::block::PictureType::CoverFront)
|
||||
self.inner.remove_key("METADATA_BLOCK_PICTURE")
|
||||
}
|
||||
|
||||
fn track_number(&self) -> Option<u32> {
|
||||
|
|
|
@ -5,7 +5,7 @@ use super::picture::Picture;
|
|||
pub struct Album<'a> {
|
||||
pub title: Option<&'a str>,
|
||||
pub artists: Option<Vec<&'a str>>,
|
||||
pub cover: Option<Picture<'a>>,
|
||||
pub cover: Option<Picture>,
|
||||
}
|
||||
|
||||
impl<'a> Default for Album<'a> {
|
||||
|
@ -23,7 +23,7 @@ impl<'a> Album<'a> {
|
|||
pub fn new(
|
||||
title: Option<&'a str>,
|
||||
artists: Option<Vec<&'a str>>,
|
||||
cover: Option<Picture<'a>>,
|
||||
cover: Option<Picture>,
|
||||
) -> Self {
|
||||
Self {
|
||||
title,
|
||||
|
@ -56,7 +56,7 @@ impl<'a> Album<'a> {
|
|||
self.artists = None
|
||||
}
|
||||
/// Set the album cover
|
||||
pub fn set_cover(mut self, cover: Picture<'a>) {
|
||||
pub fn set_cover(mut self, cover: Picture) {
|
||||
self.cover = Some(cover);
|
||||
}
|
||||
/// Clears the `cover` field
|
||||
|
|
|
@ -45,13 +45,13 @@ impl From<MimeType> for String {
|
|||
|
||||
/// Represents a picture, with its data and mime type.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Picture<'a> {
|
||||
pub data: &'a [u8],
|
||||
pub struct Picture {
|
||||
pub data: Vec<u8>,
|
||||
pub mime_type: MimeType,
|
||||
}
|
||||
|
||||
impl<'a> Picture<'a> {
|
||||
pub fn new(data: &'a [u8], mime_type: MimeType) -> Self {
|
||||
impl Picture {
|
||||
pub fn new(data: Vec<u8>, mime_type: MimeType) -> Self {
|
||||
Self { data, mime_type }
|
||||
}
|
||||
}
|
||||
|
|
24
tests/io.rs
24
tests/io.rs
|
@ -1,5 +1,5 @@
|
|||
#![cfg(feature = "default")]
|
||||
use lofty::Tag;
|
||||
use lofty::{MimeType, Picture, Tag};
|
||||
|
||||
macro_rules! full_test {
|
||||
($function:ident, $file:expr) => {
|
||||
|
@ -35,14 +35,13 @@ macro_rules! add_tags {
|
|||
println!("Setting album artists");
|
||||
tag.set_album_artist("foo album artist");
|
||||
|
||||
// TODO
|
||||
// let cover = Picture {
|
||||
// mime_type: MimeType::Jpeg,
|
||||
// data: &vec![0u8; 10],
|
||||
// };
|
||||
//
|
||||
// tags.set_album_cover(cover.clone());
|
||||
// assert_eq!(tags.album_cover(), Some(cover));
|
||||
let _cover = Picture {
|
||||
mime_type: MimeType::Jpeg,
|
||||
data: vec![0; 10],
|
||||
};
|
||||
|
||||
// tag.set_album_cover(cover.clone());
|
||||
// assert_eq!(tag.album_cover(), Some(cover));
|
||||
|
||||
println!("Writing");
|
||||
tag.write_to_path($file).unwrap();
|
||||
|
@ -109,10 +108,9 @@ macro_rules! remove_tags {
|
|||
assert!(tag.album_artists_vec().is_none());
|
||||
tag.remove_album_artists();
|
||||
|
||||
// TODO
|
||||
// tags.remove_album_cover();
|
||||
// assert!(tags.album_cover().is_none());
|
||||
// tags.remove_album_cover();
|
||||
// tag.remove_album_cover();
|
||||
// assert!(tag.album_cover().is_none());
|
||||
// tag.remove_album_cover();
|
||||
|
||||
println!("Writing");
|
||||
tag.write_to_path($file).unwrap();
|
||||
|
|
Loading…
Reference in a new issue