Skip to main content

gufo_xmp/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod parsing;
4mod predefined;
5
6use std::collections::BTreeMap;
7use std::sync::Arc;
8
9use gufo_common::xmp::Namespace;
10use xml::name::OwnedName;
11
12#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
13pub struct Tag {
14    namespace: Namespace,
15    name: String,
16}
17
18impl Tag {
19    pub fn new(namespace: Namespace, name: String) -> Self {
20        Self { namespace, name }
21    }
22
23    fn from_name(name: &OwnedName) -> Option<Self> {
24        if let Some(namespace_url) = &name.namespace {
25            let namespace = Namespace::from_url(namespace_url);
26
27            let name = name.local_name.to_owned();
28            Some(Self::new(namespace, name))
29        } else {
30            None
31        }
32    }
33}
34
35impl<T: gufo_common::xmp::Field> From<T> for Tag {
36    fn from(_: T) -> Self {
37        Self {
38            name: T::NAME.to_string(),
39            namespace: T::NAMESPACE,
40        }
41    }
42}
43
44#[derive(Debug, Clone)]
45pub struct Xmp {
46    inner: Vec<u8>,
47    entries: BTreeMap<Tag, String>,
48}
49
50#[derive(Debug, Clone, thiserror::Error)]
51#[non_exhaustive]
52pub enum Error {
53    #[error("XmlReader: {0}")]
54    XmlReader(xml::reader::Error),
55    #[error("XmlWriter: {0}")]
56    XmlWriter(Arc<xml::writer::Error>),
57}
58
59impl From<xml::reader::Error> for Error {
60    fn from(value: xml::reader::Error) -> Self {
61        Self::XmlReader(value)
62    }
63}
64
65impl From<xml::writer::Error> for Error {
66    fn from(value: xml::writer::Error) -> Self {
67        Self::XmlWriter(Arc::new(value))
68    }
69}
70
71impl Xmp {
72    pub fn new(data: Vec<u8>) -> Result<Self, Error> {
73        let entries = Self::lookup(&data)?;
74
75        Ok(Self {
76            inner: data,
77            entries,
78        })
79    }
80
81    pub fn update(&mut self, updates: BTreeMap<Tag, String>) -> Result<(), Error> {
82        let (entries, data) = Self::lookup_and_update(&self.inner, updates)?;
83        self.entries = entries;
84        self.inner = data;
85
86        Ok(())
87    }
88
89    pub fn get(&self, tag: impl Into<Tag>) -> Option<&str> {
90        self.entries.get(&tag.into()).map(|x| x.as_str())
91    }
92
93    pub fn get_frac(&self, tag: impl Into<Tag>) -> Option<(u32, u32)> {
94        let (x, y) = self.get(tag)?.split_once('/')?;
95        let x = x.parse().ok()?;
96        let y = y.parse().ok()?;
97
98        Some((x, y))
99    }
100
101    pub fn get_frac_f32(&self, tag: impl Into<Tag>) -> Option<f32> {
102        let (x, y) = self.get_frac(tag)?;
103
104        let res = x as f32 / y as f32;
105        if res.is_finite() {
106            Some(res)
107        } else {
108            None
109        }
110    }
111
112    pub fn get_u16(&self, tag: impl Into<Tag>) -> Option<u16> {
113        self.get(tag)?.parse().ok()
114    }
115
116    #[cfg(feature = "chrono")]
117    pub fn get_date_time(&self, tag: impl Into<Tag>) -> Option<gufo_common::datetime::DateTime> {
118        Some(gufo_common::datetime::DateTime::FixedOffset(
119            self.get(tag)
120                .and_then(|x| chrono::DateTime::parse_from_rfc3339(x).ok())?,
121        ))
122    }
123
124    pub fn entries(&self) -> &BTreeMap<Tag, String> {
125        &self.entries
126    }
127
128    pub fn into_inner(self) -> Vec<u8> {
129        self.inner
130    }
131}