Album cover support for ogg/opus/flac

This commit is contained in:
Serial 2021-04-23 21:15:33 -04:00
parent 946d19e0e1
commit c285b36629
8 changed files with 114 additions and 64 deletions

View file

@ -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"

View file

@ -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) {

View file

@ -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"),
});
}

View file

@ -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 {

View file

@ -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;
}
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) {
// 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());
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> {

View file

@ -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

View file

@ -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 }
}
}

View file

@ -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();