exempi2 0.3.0

Safe Rust bindings to Exempi 2.4.
Documentation
//
// Copyright (c) 2016-2025, Hubert Figuière
//
// License: BSD-2-Clause
// See top-level LICENSE file.
//

extern crate exempi_sys as c;
#[macro_use]
extern crate bitflags;

mod error;
mod xmp;
mod xmpfile;
mod xmpiterator;
mod xmpstring;

use std::cmp::Ordering;
use std::ffi::CString;
use std::mem::transmute;
use std::result;
use std::sync::Once;

pub use c::consts::*;
pub use c::FileType;
pub use c::TzSign;
pub use c::XmpError;
pub use error::Error;
pub use xmp::{PropFlags, SerialFlags, Xmp};
pub use xmpfile::{CloseFlags, FormatOptionFlags, OpenFlags, XmpFile};
pub use xmpiterator::{IterFlags, IterSkipFlags, XmpIterator};
pub use xmpstring::XmpString;

/// Result type
pub type Result<T> = result::Result<T, Error>;

static START: Once = Once::new();

/// Initialize the library.
///
/// This will ensure xmp_init() is called only once so that
/// concurrent calls are exclusive.
///
/// It is automatically called by any entry point to the library.
pub(crate) fn init() -> bool {
    let mut inited = true;
    START.call_once(|| {
        // run initialization here
        inited = unsafe { c::xmp_init() };
    });
    inited
}

/// Get the last error code on the thread
/// Set when a function return false or None.
pub(crate) fn get_error() -> Error {
    let err = unsafe { c::xmp_get_error() };
    Error::from(match err {
        -15..=0 | -110..=-101 | -211..=-201 => unsafe { transmute::<i32, c::XmpError>(err) },
        _ => c::XmpError::TBD,
    })
}

/// Register namespace with uri and suggested prefix
/// Returns the actual registered prefix.
pub fn register_namespace(uri: &str, prefix: &str) -> Result<XmpString> {
    init();
    let s_uri = CString::new(uri).unwrap();
    let s_prefix = CString::new(prefix).unwrap();
    let mut reg_prefix = XmpString::new();
    if unsafe {
        c::xmp_register_namespace(s_uri.as_ptr(), s_prefix.as_ptr(), reg_prefix.as_mut_ptr())
    } {
        Ok(reg_prefix)
    } else {
        Err(get_error())
    }
}

/// Return the prefix for the namespace uri.
pub fn namespace_prefix<U: AsRef<[u8]>>(uri: U) -> Result<XmpString> {
    init();
    let s = CString::new(uri.as_ref()).unwrap();
    let mut prefix = XmpString::new();
    if unsafe { c::xmp_namespace_prefix(s.as_ptr(), prefix.as_mut_ptr()) } {
        Ok(prefix)
    } else {
        Err(get_error())
    }
}

/// Return the namespace uri for the prefix.
pub fn prefix_namespace<P: AsRef<[u8]>>(prefix: P) -> Result<XmpString> {
    init();
    let s = CString::new(prefix.as_ref()).unwrap();
    let mut uri = XmpString::new();
    if unsafe { c::xmp_prefix_namespace_uri(s.as_ptr(), uri.as_mut_ptr()) } {
        Ok(uri)
    } else {
        Err(get_error())
    }
}

/// A wrapper around the C type DateTime
#[derive(Clone, Debug, Default)]
pub struct DateTime(c::XmpDateTime);

impl DateTime {
    /// Create a new DateTime
    pub fn new() -> Self {
        DateTime::default()
    }
    /// Return the native pointer
    pub fn as_ptr(&self) -> *const c::XmpDateTime {
        &self.0 as *const c::XmpDateTime
    }
    /// Return the native mutable pointer
    pub fn as_mut_ptr(&mut self) -> *mut c::XmpDateTime {
        &mut self.0 as *mut c::XmpDateTime
    }
    /// Set date
    pub fn set_date(&mut self, year: i32, month: i32, day: i32) {
        self.0.year = year;
        self.0.month = month;
        self.0.day = day;
        // self.0.has_date = 1;
    }
    /// Set time
    pub fn set_time(&mut self, hour: i32, min: i32, sec: i32) {
        self.0.hour = hour;
        self.0.minute = min;
        self.0.second = sec;
        // self.0.has_time = 1;
    }
    /// Set nano_second
    pub fn set_nano_second(&mut self, nano_second: i32) {
        self.0.nano_second = nano_second;
    }
    /// Set Timezone
    pub fn set_timezone(&mut self, sign: TzSign, hour: i32, min: i32) {
        self.0.tz_sign = sign;
        self.0.tz_hour = hour;
        self.0.tz_minute = min;
        // self.0.has_tz = 1;
    }
    /// Year of the DateTime
    pub fn year(&self) -> i32 {
        self.0.year
    }
    /// Month of the DateTime
    pub fn month(&self) -> i32 {
        self.0.month
    }
    /// Day of the DateTime
    pub fn day(&self) -> i32 {
        self.0.day
    }
    /// Hour of the DateTime
    pub fn hour(&self) -> i32 {
        self.0.hour
    }
    /// Minute of the DateTime
    pub fn minute(&self) -> i32 {
        self.0.minute
    }
    /// Seconds of the DateTime
    pub fn second(&self) -> i32 {
        self.0.second
    }
    /// nano seconds of the DateTime
    pub fn nano_second(&self) -> i32 {
        self.0.nano_second
    }

    /// Return if there is a timezone information
    pub fn has_tz(&self) -> bool {
        self.tz_sign() != TzSign::UTC && (self.tz_hours() != 0 || self.tz_minutes() != 0)
    }

    /// Return the timezone sign
    pub fn tz_sign(&self) -> TzSign {
        self.0.tz_sign
    }

    /// Return the timezone offset hours (absolute)
    pub fn tz_hours(&self) -> i32 {
        self.0.tz_hour
    }

    /// Return the timezone offset minutes
    pub fn tz_minutes(&self) -> i32 {
        self.0.tz_minute
    }
}

impl From<c::XmpDateTime> for DateTime {
    /// Construct from the native C type
    fn from(d: c::XmpDateTime) -> DateTime {
        DateTime(d)
    }
}

impl PartialEq for DateTime {
    fn eq(&self, other: &DateTime) -> bool {
        unsafe {
            c::xmp_datetime_compare(
                &self.0 as *const c::XmpDateTime,
                &other.0 as *const c::XmpDateTime,
            ) == 0
        }
    }
}

impl PartialOrd for DateTime {
    fn partial_cmp(&self, other: &DateTime) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Eq for DateTime {}

impl Ord for DateTime {
    fn cmp(&self, other: &DateTime) -> Ordering {
        match unsafe {
            c::xmp_datetime_compare(
                &self.0 as *const c::XmpDateTime,
                &other.0 as *const c::XmpDateTime,
            )
        } {
            n if n < 0 => Ordering::Less,
            n if n > 0 => Ordering::Greater,
            _ => Ordering::Equal,
        }
    }
}

#[cfg(test)]
mod test {

    #[test]
    fn test_xmpdate() {
        let mut date = crate::DateTime::new();
        date.set_date(2012, 02, 17);
        date.set_time(11, 10, 49);
        date.set_timezone(crate::TzSign::West, 4, 0);

        assert_eq!(std::mem::offset_of!(crate::DateTime, 0.tz_sign), 24);

        let mut xmp = crate::Xmp::new();
        let r = xmp.set_property_date(
            "http://ns.adobe.com/exif/1.0/",
            "DateTimeOriginal",
            &date,
            crate::PropFlags::NONE,
        );
        assert!(r.is_ok());

        let mut flags = crate::PropFlags::default();
        let d = xmp
            .get_property_date(
                "http://ns.adobe.com/exif/1.0/",
                "DateTimeOriginal",
                &mut flags,
            )
            .unwrap();
        assert_eq!(d, date);

        let d = xmp
            .get_property(
                "http://ns.adobe.com/exif/1.0/",
                "DateTimeOriginal",
                &mut flags,
            )
            .unwrap();
        assert_eq!(&d.to_string(), "2012-02-17T11:10:49-04:00");
    }
}