use base64::Engine;
use image::{DynamicImage, GenericImageView, RgbImage};
use std::collections::HashMap;
use std::convert::AsRef;
use std::fs::File;
use std::io::{Cursor, Read, Seek, Write};
use std::path::Path;
use thiserror::Error;
use crate::utils::read_picture_block;
mod reading;
mod utils;
mod writing;
const VORBIS_HEADER: [u8; 7] = [3, 118, 111, 114, 98, 105, 115];
const THEORA_HEADER: [u8; 7] = [0x81, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61];
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("No vorbis or theora comment packet found. oggmeta only supports vorbis and theora comments. your file may be malformed.")]
NoComments,
#[error("{0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
InvalidString(#[from] std::string::FromUtf8Error),
#[error("{0}")]
InvalidLength(#[from] std::num::TryFromIntError),
#[error("there was an error parsing the ogg file. your file is most likely malformed.")]
ParseError,
#[error("{0}")]
NullError(#[from] std::ffi::NulError),
#[error("{0}")]
ImageError(#[from] image::error::ImageError),
#[error("{0}")]
StrError(#[from] std::str::Utf8Error),
#[error("{0}")]
OggError(#[from] ogg::OggReadError),
#[error("{0}")]
Base64Error(#[from] base64::DecodeError),
}
#[derive(Clone, Debug, Default)]
pub struct Tag {
pub vendor: String,
pub comments: HashMap<String, Vec<String>>,
pub pictures: Vec<Picture>,
}
#[derive(Clone, Debug)]
pub struct Picture {
pub picture_type: PictureType,
pub media_type: String,
pub description: String,
pub width: u32,
pub height: u32,
pub color_depth: u32,
pub number_colors: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum PictureType {
Other = 0,
PngIcon = 1,
GeneralIcon = 2,
FrontCover = 3,
BackCover = 4,
LinerNotesPage = 5,
MediaLabel = 6,
LeadArtist = 7,
Artist = 8,
Conductor = 9,
Band = 10,
Composer = 11,
Lyricist = 12,
RecordingLocation = 13,
DuringRecording = 14,
DuringPerformance = 15,
MovieScreenCapture = 16,
BrightColoredFish = 17,
Illustration = 18,
BandLogo = 19,
PublisherLogo = 20,
}
impl TryFrom<u32> for PictureType {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(PictureType::Other),
1 => Ok(PictureType::PngIcon),
2 => Ok(PictureType::GeneralIcon),
3 => Ok(PictureType::FrontCover),
4 => Ok(PictureType::BackCover),
5 => Ok(PictureType::LinerNotesPage),
6 => Ok(PictureType::MediaLabel),
7 => Ok(PictureType::LeadArtist),
8 => Ok(PictureType::Artist),
9 => Ok(PictureType::Conductor),
10 => Ok(PictureType::Band),
11 => Ok(PictureType::Composer),
12 => Ok(PictureType::Lyricist),
13 => Ok(PictureType::RecordingLocation),
14 => Ok(PictureType::DuringRecording),
15 => Ok(PictureType::DuringPerformance),
16 => Ok(PictureType::MovieScreenCapture),
17 => Ok(PictureType::BrightColoredFish),
18 => Ok(PictureType::Illustration),
19 => Ok(PictureType::BandLogo),
20 => Ok(PictureType::PublisherLogo),
_ => Err(()),
}
}
}
impl Tag {
pub fn read_from<R: Read + Seek>(read: &mut R) -> Result<Tag, Error> {
reading::parse_file(read)
}
pub fn read_from_path<P: AsRef<Path>>(path: &P) -> Result<Tag, Error> {
let mut file = File::open(path)?;
reading::parse_file(&mut file)
}
pub fn write_to<W: Read + Write + Seek>(&mut self, mut f_in: W) -> Result<(), crate::Error> {
let mut buf = Vec::new();
crate::writing::insert_comments(&mut f_in, &mut buf, self)?;
f_in.rewind()?;
std::io::copy(&mut buf.as_slice(), &mut f_in)?;
Ok(())
}
pub fn write_to_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), crate::Error> {
let mut file = File::options()
.read(true)
.write(true)
.create(false)
.open(path)?;
self.write_to(&mut file)?;
Ok(())
}
}
impl Picture {
pub fn from_raw_block(data: &Vec<u8>) -> Result<Picture, crate::Error> {
let mut buf = base64::engine::general_purpose::STANDARD_NO_PAD.decode(data)?;
read_picture_block(&mut Cursor::new(&mut buf))
}
}
impl From<RgbImage> for Picture {
fn from(img: RgbImage) -> Self {
let mut img_buf = Cursor::new(vec![]);
let (width, height) = img.dimensions();
img.write_to(&mut img_buf, image::ImageFormat::Jpeg)
.unwrap();
Picture {
picture_type: PictureType::FrontCover,
media_type: "image/jpeg".to_string(),
description: "Cover (front)".to_string(),
width,
height,
color_depth: 24,
number_colors: 0,
data: img_buf.into_inner(),
}
}
}
impl From<DynamicImage> for Picture {
fn from(img: DynamicImage) -> Self {
let mut img_buf = Cursor::new(vec![]);
let (width, height) = img.dimensions();
img.write_to(&mut img_buf, image::ImageFormat::Jpeg)
.unwrap();
Picture {
picture_type: PictureType::FrontCover,
media_type: "image/jpeg".to_string(),
description: "Cover (front)".to_string(),
width,
height,
color_depth: 24,
number_colors: 0,
data: img_buf.into_inner(),
}
}
}
impl TryFrom<&[u8]> for Picture {
type Error = crate::Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let dyn_img = image::load_from_memory(value)?;
Ok(dyn_img.into())
}
}