mirror of
https://codeberg.org/unspeaker/perch.git
synced 2025-12-06 09:36:42 +01:00
191 lines
7.2 KiB
Rust
191 lines
7.2 KiB
Rust
use crate::*;
|
|
use std::fs::File;
|
|
use std::io::{BufReader, Read};
|
|
use std::borrow::Borrow;
|
|
use byte_unit::{Byte, Unit::MB};
|
|
use lofty::{file::TaggedFileExt, probe::Probe, tag::{Accessor, Tag, TagItem, ItemKey, ItemValue}};
|
|
|
|
pub enum Metadata {
|
|
Directory {
|
|
hash_file: Option<()>,
|
|
catalog_file: Option<()>,
|
|
artist_file: Option<()>,
|
|
release_file: Option<()>,
|
|
},
|
|
Music {
|
|
invalid: bool,
|
|
hash: Arc<str>,
|
|
size: Arc<str>,
|
|
tag: Option<Arc<Tag>>,
|
|
new_tag: Option<Arc<Tag>>,
|
|
},
|
|
Image {
|
|
invalid: bool,
|
|
hash: Arc<str>,
|
|
size: Arc<str>,
|
|
title: Option<String>,
|
|
author: Option<String>,
|
|
},
|
|
Unknown {
|
|
hash: Arc<str>,
|
|
size: Arc<str>,
|
|
}
|
|
}
|
|
|
|
impl Metadata {
|
|
pub fn new (path: &Path) -> Usually<Self> {
|
|
let reader = BufReader::new(File::open(path)?);
|
|
let probe = Probe::new(reader)
|
|
//.options(ParseOptions::new().parsing_mode(ParsingMode::Strict))
|
|
.guess_file_type()?;
|
|
if probe.file_type().is_some() {
|
|
let file = lofty::read_from_path(path)?;
|
|
let tag = file.primary_tag();
|
|
let data = std::fs::read(path)?;
|
|
let hash = hex::encode(xxh3_64(data.as_slice()).to_be_bytes()).into();
|
|
let size = Byte::from_u64(data.len() as u64).get_adjusted_unit(MB);
|
|
Ok(Self::Music {
|
|
hash,
|
|
size: format!("{:#>8.2}", size).into(),
|
|
tag: tag.map(|t|t.clone().into()),
|
|
new_tag: tag.map(|t|t.clone().into()),
|
|
invalid: false,
|
|
})
|
|
} else {
|
|
Self::new_fallback(path)
|
|
}
|
|
}
|
|
pub fn new_fallback (path: &Path) -> Usually<Self> {
|
|
let file = File::open(path)?;
|
|
let size = Byte::from_u64(file.metadata()?.len() as u64).get_adjusted_unit(MB);
|
|
let mut reader = BufReader::new(file);
|
|
let mut bytes = vec![0;16];
|
|
reader.read(&mut bytes)?;
|
|
// PNG
|
|
if bytes.starts_with(&[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A]) {
|
|
let mut bytes = vec![];
|
|
BufReader::new(File::open(path)?).read(&mut bytes)?;
|
|
return Ok(Self::Image {
|
|
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),
|
|
size: format!("{:#>8.2}", size).into(),
|
|
title: None,
|
|
author: None,
|
|
invalid: false,
|
|
})
|
|
}
|
|
// JPG
|
|
if bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xDB])
|
|
|| bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE0,
|
|
0x00, 0x10, 0x4A, 0x46,
|
|
0x49, 0x46, 0x00, 0x01])
|
|
|| bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xEE])
|
|
|| (bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE1]) &&
|
|
bytes.get(6) == Some(&0x45) && bytes.get(7) == Some(&0x78) &&
|
|
bytes.get(8) == Some(&0x69) && bytes.get(9) == Some(&0x66) &&
|
|
bytes.get(10) == Some(&0x00) && bytes.get(11) == Some(&0x00))
|
|
{
|
|
return Ok(Self::Image {
|
|
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),
|
|
size: format!("{:#>8.2}", size).into(),
|
|
title: None,
|
|
author: None,
|
|
invalid: false,
|
|
})
|
|
}
|
|
Ok(Self::Unknown {
|
|
size: format!("{:#>8.2}", size).into(),
|
|
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),
|
|
})
|
|
}
|
|
pub fn hash (&self) -> Option<Arc<str>> {
|
|
match self {
|
|
Metadata::Image { hash, .. } => Some(hash.clone()),
|
|
Metadata::Music { hash, .. } => Some(hash.clone()),
|
|
Metadata::Unknown { hash, .. } => Some(hash.clone()),
|
|
_ => None
|
|
}
|
|
}
|
|
pub fn size (&self) -> Option<Arc<str>> {
|
|
match self {
|
|
Metadata::Image { size, .. } => Some(size.clone()),
|
|
Metadata::Music { size, .. } => Some(size.clone()),
|
|
Metadata::Unknown { size, .. } => Some(size.clone()),
|
|
_ => None
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! metadata_field {
|
|
(string: $get:ident $set:ident $key:expr) => {
|
|
impl Metadata {
|
|
pub fn $get (&self) -> Option<Arc<str>> {
|
|
if let Metadata::Music { tag: Some(tag), .. } = self {
|
|
return tag.$get().map(|t|t.into())
|
|
}
|
|
None
|
|
}
|
|
pub fn $set (&mut self, value: &impl AsRef<str>) -> Option<TagItem> {
|
|
if let Metadata::Music { tag: Some(old_tag), new_tag, .. } = self {
|
|
if old_tag.$get().is_none() {
|
|
}
|
|
if let Some(old_value) = old_tag.$get() {
|
|
if old_value != value.as_ref() {
|
|
}
|
|
}
|
|
}
|
|
//match self {
|
|
//Metadata::Music { tag: Some(tag), new_tag, .. } => {
|
|
//}
|
|
//_ => None
|
|
//}
|
|
//let old_tag = &mut self.tag;
|
|
//let new_tag = &mut self.new_tag;
|
|
//if let Metadata::Music { tag: Some(tag), .. } = self
|
|
//&& Some(value.as_ref()) != tag.$get().as_deref()
|
|
//{
|
|
//*$field = Some(value.as_ref().into());
|
|
//return Some(TagItem::new($key, ItemValue::Text(value.as_ref().into())))
|
|
//}
|
|
None
|
|
}
|
|
}
|
|
};
|
|
(number: $get:ident $set:ident $key:expr) => {
|
|
impl Metadata {
|
|
pub fn $get (&self) -> Option<Arc<str>> {
|
|
if let Metadata::Music { tag: Some(tag), .. } = self {
|
|
return tag.$get().map(|t|format!("{t}").into())
|
|
}
|
|
None
|
|
}
|
|
pub fn $set (&mut self, value: &impl AsRef<str>) -> Option<TagItem> {
|
|
if let Ok(value) = value.as_ref().trim().parse::<u32>() {
|
|
if let Metadata::Music { tag: Some(old_tag), new_tag, .. } = self {
|
|
if old_tag.$get().is_none() {
|
|
}
|
|
if let Some(old_value) = old_tag.$get() {
|
|
if old_value != value {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//let old_tag = &mut self.tag;
|
|
//let new_tag = &mut self.new_tag;
|
|
//if let Metadata::Music { tag: Some(tag), .. } = self
|
|
//&& let Ok(value) = value.as_ref().trim().parse::<u32>()
|
|
//&& Some(value) != *$field
|
|
//{
|
|
//*$field = Some(value);
|
|
//return Some(TagItem::new($key, ItemValue::Text(format!("{value}"))))
|
|
//}
|
|
None
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
metadata_field!(string: artist set_artist ItemKey::TrackArtist);
|
|
metadata_field!(number: year set_year ItemKey::Year);
|
|
metadata_field!(string: album set_album ItemKey::AlbumTitle);
|
|
metadata_field!(number: track set_track ItemKey::TrackNumber);
|
|
metadata_field!(string: title set_title ItemKey::TrackTitle);
|