#![forbid(unsafe_code)]
use std::sync::Arc;
use parking_lot::RwLock;
use crate::{
exif::{Exif, error::ExifFatalError},
iptc::{Iptc, error::IptcError},
xmp::{Xmp, error::XmpError},
};
pub mod exif;
pub mod iptc;
pub mod magic_number;
pub mod providers;
pub mod xmp;
#[inline(always)]
pub fn parse(input: &impl AsRef<[u8]>) -> Option<magic_number::AnyProvider> {
magic_number::parse(input)
}
#[inline(always)]
pub fn get(input: &impl AsRef<[u8]>) -> Option<magic_number::MagicNumber> {
magic_number::get(input)
}
pub trait MetadataProvider:
Clone
+ core::fmt::Debug
+ Sized
+ Send
+ Sync
+ MetadataProviderRaw
+ magic_number::_MagicNumberMarker
{
type ConstructionError: Clone
+ core::fmt::Debug
+ PartialEq
+ PartialOrd
+ core::error::Error
+ Sized
+ Send
+ Sync;
fn new(input: &impl AsRef<[u8]>)
-> Result<Self, <Self as MetadataProvider>::ConstructionError>;
fn exif(&self) -> Option<Result<Arc<RwLock<Exif>>, ExifFatalError>> {
fn handle_already_parsed(
p: &Wrapped<Exif>,
) -> Option<Result<Arc<RwLock<Exif>>, ExifFatalError>> {
log::trace!("Cached Exif found! Returning...");
Some(Ok(Arc::clone(&p.0))) }
fn handle_none<A>() -> Option<A> {
log::trace!("No Exif is present in this struct. Returning early.");
None
}
match &*self.exif_raw().read() {
Some(MaybeParsed::Raw(_)) => (),
Some(MaybeParsed::Parsed(p)) => return handle_already_parsed(p),
None => return handle_none(),
}
let raw = self.exif_raw();
let locked = &mut *raw.write();
match locked {
Some(MaybeParsed::Raw(r)) => {
match Exif::new(&mut r.as_slice()) {
Ok(p) => {
let wrapped: Wrapped<Exif> = Wrapped(Arc::new(RwLock::new(p)));
log::trace!("Completed Exif parsing! Cached internally.");
if let Some(locked) = locked {
*locked = MaybeParsed::Parsed(wrapped.clone());
}
Some(Ok(wrapped.0))
}
Err(e) => {
log::error!("Failed to parse Exif! err: {e}");
*locked = None;
Some(Err(e))
}
}
}
Some(MaybeParsed::Parsed(p)) => handle_already_parsed(p),
None => handle_none(),
}
}
fn iptc(&self) -> Option<Result<Arc<RwLock<Iptc>>, IptcError>> {
log::error!(
"Attempted to parse for IPTC, but IPTC IIC isn't \
implemented in this library yet. \
Returning None..."
);
None
}
fn xmp(&self) -> Option<Result<Arc<RwLock<Xmp>>, XmpError>> {
fn handle_already_parsed(p: &Wrapped<Xmp>) -> Option<Result<Arc<RwLock<Xmp>>, XmpError>> {
log::trace!("Cached XMP found! Returning...");
Some(Ok(Arc::clone(&p.0))) }
fn handle_none<A>() -> Option<A> {
log::trace!("No XMP is present in this struct. Returning early.");
None
}
match &*self.xmp_raw().read() {
Some(MaybeParsed::Raw(_)) => (),
Some(MaybeParsed::Parsed(p)) => return handle_already_parsed(p),
None => return handle_none(),
}
let raw = self.xmp_raw();
let locked = &mut *raw.write();
match locked {
Some(MaybeParsed::Raw(r)) => {
let creation_result: Result<Xmp, XmpError> = core::str::from_utf8(r)
.map_err(|e| {
log::error!("XMP was not in UTF-8 format! err: {e}");
XmpError::NotUtf8
})
.and_then(Xmp::new);
match creation_result {
Ok(p) => {
let wrapped: Wrapped<Xmp> = Wrapped(Arc::new(RwLock::new(p)));
log::trace!("Completed XMP parsing! Cached internally.");
if let Some(locked) = locked {
*locked = MaybeParsed::Parsed(wrapped.clone());
}
Some(Ok(wrapped.0))
}
Err(e) => {
log::error!("Failed to parse XMP! err: {e}");
*locked = None;
Some(Err(e))
}
}
}
Some(MaybeParsed::Parsed(p)) => handle_already_parsed(p),
None => handle_none(),
}
}
fn magic_number(input: &[u8]) -> bool;
}
pub trait MetadataProviderRaw {
fn exif_raw(&self) -> Arc<RwLock<Option<MaybeParsedExif>>> {
Arc::new(const { RwLock::new(None) })
}
fn xmp_raw(&self) -> Arc<RwLock<Option<MaybeParsedXmp>>> {
Arc::new(const { RwLock::new(None) })
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Hash)]
pub enum MaybeParsed<R, P>
where
R: Clone + core::fmt::Debug + PartialEq + PartialOrd + core::hash::Hash,
P: Clone + core::fmt::Debug + PartialEq + PartialOrd + core::hash::Hash,
{
Raw(R),
Parsed(Wrapped<P>),
}
#[expect(missing_docs)]
pub type MaybeParsedExif = MaybeParsed<Vec<u8>, Exif>;
#[expect(missing_docs)]
pub type MaybeParsedIptc = MaybeParsed<Vec<u8>, Iptc>;
#[expect(missing_docs)]
pub type MaybeParsedXmp = MaybeParsed<Vec<u8>, Xmp>;
#[derive(Clone, Debug)]
pub struct Wrapped<P: PartialEq + PartialOrd + core::hash::Hash>(
pub Arc<RwLock<P>>,
);
impl<P: PartialEq + PartialOrd + core::hash::Hash> PartialEq for Wrapped<P> {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl<P: PartialEq + PartialOrd + core::hash::Hash> PartialOrd for Wrapped<P> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
(Arc::as_ptr(&self.0)).partial_cmp(&(Arc::as_ptr(&other.0)))
}
}
impl<P: PartialEq + PartialOrd + core::hash::Hash> core::hash::Hash for Wrapped<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
(Arc::as_ptr(&self.0) as usize).hash(state);
}
}
pub(crate) mod util {
#[cfg(test)]
pub fn logger() {
_ = env_logger::builder()
.is_test(true)
.filter_level(log::LevelFilter::max())
.format_file(true)
.format_line_number(true)
.try_init();
}
}