#![crate_type = "lib"]
#![crate_name = "rexiv2"]
extern crate gexiv2_sys as gexiv2;
extern crate libc;
extern crate num_rational as rational;
use std::ffi;
use std::ptr;
use std::str;
use std::os::unix::ffi::OsStrExt;
#[derive(Debug, PartialEq)]
pub enum Rexiv2Error {
NoValue,
Utf8(str::Utf8Error),
Internal(Option<String>),
}
impl std::fmt::Display for Rexiv2Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Rexiv2Error::NoValue => write!(f, "No value found"),
Rexiv2Error::Utf8(ref err) => write!(f, "IO error: {}", err),
Rexiv2Error::Internal(Some(ref msg)) => write!(f, "Internal error: {}", msg),
Rexiv2Error::Internal(None) => write!(f, "Unknown internal error"),
}
}
}
impl std::error::Error for Rexiv2Error {
fn description(&self) -> &str {
match *self {
Rexiv2Error::NoValue => "No value found",
Rexiv2Error::Utf8(ref err) => err.description(),
Rexiv2Error::Internal(Some(ref msg)) => msg,
Rexiv2Error::Internal(None) => "Unknown internal error",
}
}
fn cause(&self) -> Option<&std::error::Error> {
match *self {
Rexiv2Error::NoValue => None,
Rexiv2Error::Utf8(ref err) => Some(err),
Rexiv2Error::Internal(_) => None,
}
}
}
impl From<str::Utf8Error> for Rexiv2Error {
fn from(err: str::Utf8Error) -> Rexiv2Error {
Rexiv2Error::Utf8(err)
}
}
pub type Result<T> = std::result::Result<T, Rexiv2Error>;
#[derive(Debug, PartialEq)]
pub struct Metadata {
raw: *mut gexiv2::GExiv2Metadata,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GpsInfo {
pub longitude: f64,
pub latitude: f64,
pub altitude: f64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum TagType {
UnsignedByte,
AsciiString,
UnsignedShort,
UnsignedLong,
UnsignedRational,
SignedByte,
Undefined,
SignedShort,
SignedLong,
SignedRational,
TiffFloat,
TiffDouble,
TiffIfd,
String,
Date,
Time,
Comment,
Directory,
XmpText,
XmpAlt,
XmpBag,
XmpSeq,
LangAlt,
Invalid,
Unknown,
}
impl Default for TagType {
fn default() -> TagType {
TagType::Unknown
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum MediaType {
Bmp,
CanonCr2,
CanonCrw,
Eps,
FujiRaf,
Gif,
Jp2,
Jpeg,
MinoltaMrw,
OlympusOrf,
Png,
Psd,
PanasonicRw2,
Tga,
Tiff,
Other(String),
}
impl<'a> std::convert::From<&'a MediaType> for String {
fn from(t: &MediaType) -> String {
match t {
&MediaType::Bmp => "image/x-ms-bmp".to_string(),
&MediaType::CanonCr2 => "image/x-canon-cr2".to_string(),
&MediaType::CanonCrw => "image/x-canon-crw".to_string(),
&MediaType::Eps => "application/postscript".to_string(),
&MediaType::FujiRaf => "image/x-fuji-raf".to_string(),
&MediaType::Gif => "image/gif".to_string(),
&MediaType::Jp2 => "image/jp2".to_string(),
&MediaType::Jpeg => "image/jpeg".to_string(),
&MediaType::MinoltaMrw => "image/x-minolta-mrw".to_string(),
&MediaType::OlympusOrf => "image/x-olympus-orf".to_string(),
&MediaType::Png => "image/png".to_string(),
&MediaType::Psd => "image/x-photoshop".to_string(),
&MediaType::PanasonicRw2 => "image/x-panasonic-rw2".to_string(),
&MediaType::Tga => "image/targa".to_string(),
&MediaType::Tiff => "image/tiff".to_string(),
&MediaType::Other(ref s) => s.clone(),
}
}
}
impl<'a> std::convert::From<&'a str> for MediaType {
fn from(t: &str) -> MediaType {
match t {
"image/x-ms-bmp" => MediaType::Bmp,
"image/x-canon-cr2" => MediaType::CanonCr2,
"image/x-canon-crw" => MediaType::CanonCrw,
"application/postscript" => MediaType::Eps,
"image/x-fuji-raf" => MediaType::FujiRaf,
"image/gif" => MediaType::Gif,
"image/jp2" => MediaType::Jp2,
"image/jpeg" => MediaType::Jpeg,
"image/x-minolta-mrw" => MediaType::MinoltaMrw,
"image/x-olympus-orf" => MediaType::OlympusOrf,
"image/png" => MediaType::Png,
"image/x-photoshop" => MediaType::Psd,
"image/x-panasonic-rw2" => MediaType::PanasonicRw2,
"image/targa" => MediaType::Tga,
"image/tiff" => MediaType::Tiff,
_ => MediaType::Other(t.to_string()),
}
}
}
impl std::fmt::Display for MediaType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", String::from(self))
}
}
pub use gexiv2::Orientation;
impl Metadata {
pub fn new_from_path<S: AsRef<ffi::OsStr>>(path: S) -> Result<Metadata> {
let mut err: *mut gexiv2::GError = ptr::null_mut();
let c_str_path = ffi::CString::new(path.as_ref().as_bytes()).unwrap();
unsafe {
let metadata = gexiv2::gexiv2_metadata_new();
let ok = gexiv2::gexiv2_metadata_open_path(metadata, c_str_path.as_ptr(), &mut err);
if ok != 1 {
let err_msg = ffi::CStr::from_ptr((*err).message).to_str();
return Err(Rexiv2Error::Internal(err_msg.ok().map(|msg| msg.to_string())));
}
Ok(Metadata { raw: metadata })
}
}
pub fn new_from_buffer(data: &[u8]) -> Result<Metadata> {
let mut err: *mut gexiv2::GError = ptr::null_mut();
unsafe {
let metadata = gexiv2::gexiv2_metadata_new();
let ok = gexiv2::gexiv2_metadata_open_buf(metadata,
data.as_ptr(),
data.len() as libc::c_long,
&mut err);
if ok != 1 {
let err_msg = ffi::CStr::from_ptr((*err).message).to_str();
return Err(Rexiv2Error::Internal(err_msg.ok().map(|msg| msg.to_string())));
}
Ok(Metadata { raw: metadata })
}
}
pub fn save_to_file<S: AsRef<ffi::OsStr>>(&self, path: S) -> Result<()> {
let mut err: *mut gexiv2::GError = ptr::null_mut();
let c_str_path = ffi::CString::new(path.as_ref().as_bytes()).unwrap();
unsafe {
let ok = gexiv2::gexiv2_metadata_save_file(self.raw, c_str_path.as_ptr(), &mut err);
if ok != 1 {
let err_msg = ffi::CStr::from_ptr((*err).message).to_str();
return Err(Rexiv2Error::Internal(err_msg.ok().map(|msg| msg.to_string())));
}
Ok(())
}
}
pub fn supports_exif(&self) -> bool {
unsafe { gexiv2::gexiv2_metadata_get_supports_exif(self.raw) == 1 }
}
pub fn supports_iptc(&self) -> bool {
unsafe { gexiv2::gexiv2_metadata_get_supports_iptc(self.raw) == 1 }
}
pub fn supports_xmp(&self) -> bool {
unsafe { gexiv2::gexiv2_metadata_get_supports_xmp(self.raw) == 1 }
}
pub fn get_media_type(&self) -> Result<MediaType> {
unsafe {
let c_str_val = gexiv2::gexiv2_metadata_get_mime_type(self.raw);
if c_str_val.is_null() {
return Err(Rexiv2Error::NoValue);
}
Ok(MediaType::from(try!(ffi::CStr::from_ptr(c_str_val).to_str())))
}
}
pub fn get_pixel_width(&self) -> i32 {
unsafe { gexiv2::gexiv2_metadata_get_pixel_width(self.raw) }
}
pub fn get_pixel_height(&self) -> i32 {
unsafe { gexiv2::gexiv2_metadata_get_pixel_height(self.raw) }
}
pub fn has_tag(&self, tag: &str) -> bool {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe { gexiv2::gexiv2_metadata_has_tag(self.raw, c_str_tag.as_ptr()) == 1 }
}
pub fn clear_tag(&self, tag: &str) -> bool {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe { gexiv2::gexiv2_metadata_clear_tag(self.raw, c_str_tag.as_ptr()) == 1 }
}
pub fn clear(&self) {
unsafe { gexiv2::gexiv2_metadata_clear(self.raw) }
}
pub fn has_exif(&self) -> bool {
unsafe { gexiv2::gexiv2_metadata_has_exif(self.raw) == 1 }
}
pub fn clear_exif(&self) {
unsafe { gexiv2::gexiv2_metadata_clear_exif(self.raw) }
}
pub fn get_exif_tags(&self) -> Result<Vec<String>> {
let mut tags = vec![];
unsafe {
let c_tags = gexiv2::gexiv2_metadata_get_exif_tags(self.raw);
let mut cur_offset = 0;
while !(*c_tags.offset(cur_offset)).is_null() {
let tag = ffi::CStr::from_ptr(*c_tags.offset(cur_offset)).to_str();
match tag {
Ok(v) => tags.push(v.to_string()),
Err(e) => {
free_array_of_pointers(c_tags as *mut *mut libc::c_void);
return Err(Rexiv2Error::from(e));
}
}
cur_offset += 1;
}
free_array_of_pointers(c_tags as *mut *mut libc::c_void);
}
Ok(tags)
}
pub fn has_xmp(&self) -> bool {
unsafe { gexiv2::gexiv2_metadata_has_xmp(self.raw) == 1 }
}
pub fn clear_xmp(&self) {
unsafe { gexiv2::gexiv2_metadata_clear_xmp(self.raw) }
}
pub fn get_xmp_tags(&self) -> Result<Vec<String>> {
let mut tags = vec![];
unsafe {
let c_tags = gexiv2::gexiv2_metadata_get_xmp_tags(self.raw);
let mut cur_offset = 0;
while !(*c_tags.offset(cur_offset)).is_null() {
let tag = ffi::CStr::from_ptr(*c_tags.offset(cur_offset)).to_str();
match tag {
Ok(v) => tags.push(v.to_string()),
Err(e) => {
free_array_of_pointers(c_tags as *mut *mut libc::c_void);
return Err(Rexiv2Error::from(e));
}
}
cur_offset += 1;
}
free_array_of_pointers(c_tags as *mut *mut libc::c_void);
}
Ok(tags)
}
pub fn has_iptc(&self) -> bool {
unsafe { gexiv2::gexiv2_metadata_has_iptc(self.raw) == 1 }
}
pub fn clear_iptc(&self) {
unsafe { gexiv2::gexiv2_metadata_clear_iptc(self.raw) }
}
pub fn get_iptc_tags(&self) -> Result<Vec<String>> {
let mut tags = vec![];
unsafe {
let c_tags = gexiv2::gexiv2_metadata_get_iptc_tags(self.raw);
let mut cur_offset = 0;
while !(*c_tags.offset(cur_offset)).is_null() {
let tag = ffi::CStr::from_ptr(*c_tags.offset(cur_offset)).to_str();
match tag {
Ok(v) => tags.push(v.to_string()),
Err(e) => {
free_array_of_pointers(c_tags as *mut *mut libc::c_void);
return Err(Rexiv2Error::from(e));
}
}
cur_offset += 1;
}
free_array_of_pointers(c_tags as *mut *mut libc::c_void);
}
Ok(tags)
}
pub fn get_tag_string(&self, tag: &str) -> Result<String> {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe {
let c_str_val = gexiv2::gexiv2_metadata_get_tag_string(self.raw, c_str_tag.as_ptr());
if c_str_val.is_null() {
return Err(Rexiv2Error::NoValue);
}
let value = try!(ffi::CStr::from_ptr(c_str_val).to_str()).to_string();
libc::free(c_str_val as *mut libc::c_void);
Ok(value)
}
}
pub fn set_tag_string(&self, tag: &str, value: &str) -> Result<()> {
let c_str_tag = ffi::CString::new(tag).unwrap();
let c_str_val = ffi::CString::new(value).unwrap();
unsafe {
int_bool_to_result(gexiv2::gexiv2_metadata_set_tag_string(self.raw,
c_str_tag.as_ptr(),
c_str_val.as_ptr()))
}
}
pub fn get_tag_interpreted_string(&self, tag: &str) -> Result<String> {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe {
let c_str_val = gexiv2::gexiv2_metadata_get_tag_interpreted_string(self.raw,
c_str_tag.as_ptr());
if c_str_val.is_null() {
return Err(Rexiv2Error::NoValue);
}
let value = try!(ffi::CStr::from_ptr(c_str_val).to_str()).to_string();
libc::free(c_str_val as *mut libc::c_void);
Ok(value)
}
}
pub fn get_tag_multiple_strings(&self, tag: &str) -> Result<Vec<String>> {
let c_str_tag = ffi::CString::new(tag).unwrap();
let mut vals = vec![];
unsafe {
let c_vals = gexiv2::gexiv2_metadata_get_tag_multiple(self.raw, c_str_tag.as_ptr());
if c_vals.is_null() {
return Err(Rexiv2Error::NoValue);
}
let mut cur_offset = 0;
while !(*c_vals.offset(cur_offset)).is_null() {
let value = ffi::CStr::from_ptr(*c_vals.offset(cur_offset)).to_str();
match value {
Ok(v) => vals.push(v.to_string()),
Err(e) => {
free_array_of_pointers(c_vals as *mut *mut libc::c_void);
return Err(Rexiv2Error::from(e));
}
}
cur_offset += 1;
}
free_array_of_pointers(c_vals as *mut *mut libc::c_void);
}
Ok(vals)
}
pub fn set_tag_multiple_strings(&self, tag: &str, values: &[&str]) -> Result<()> {
let c_str_tag = ffi::CString::new(tag).unwrap();
let c_strs: std::result::Result<Vec<_>, _> =
values.iter().map(|&s| ffi::CString::new(s)).collect();
let c_strs = c_strs.unwrap();
let mut ptrs: Vec<_> = c_strs.iter().map(|c| c.as_ptr()).collect();
ptrs.push(ptr::null());
unsafe {
int_bool_to_result(gexiv2::gexiv2_metadata_set_tag_multiple(self.raw,
c_str_tag.as_ptr(),
ptrs.as_mut_ptr()))
}
}
pub fn get_tag_numeric(&self, tag: &str) -> i32 {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe { gexiv2::gexiv2_metadata_get_tag_long(self.raw, c_str_tag.as_ptr()) as i32 }
}
pub fn set_tag_numeric(&self, tag: &str, value: i32) -> Result<()> {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe {
int_bool_to_result(gexiv2::gexiv2_metadata_set_tag_long(self.raw,
c_str_tag.as_ptr(),
value as libc::c_long))
}
}
pub fn get_tag_rational(&self, tag: &str) -> Option<rational::Ratio<i32>> {
let c_str_tag = ffi::CString::new(tag).unwrap();
let num = &mut 0;
let den = &mut 0;
match unsafe {
gexiv2::gexiv2_metadata_get_exif_tag_rational(self.raw, c_str_tag.as_ptr(), num, den)
} {
0 => None,
_ => Some(rational::Ratio::new(*num, *den)),
}
}
pub fn set_tag_rational(&self, tag: &str, value: &rational::Ratio<i32>) -> Result<()> {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe {
int_bool_to_result(gexiv2::gexiv2_metadata_set_exif_tag_rational(self.raw,
c_str_tag.as_ptr(),
*value.numer(),
*value.denom()))
}
}
pub fn get_orientation(&self) -> Orientation {
unsafe { gexiv2::gexiv2_metadata_get_orientation(self.raw) }
}
pub fn set_orientation(&self, orientation: Orientation) {
unsafe { gexiv2::gexiv2_metadata_set_orientation(self.raw, orientation) }
}
pub fn get_exposure_time(&self) -> Option<rational::Ratio<i32>> {
let num = &mut 0;
let den = &mut 0;
match unsafe { gexiv2::gexiv2_metadata_get_exposure_time(self.raw, num, den) } {
0 => None,
_ => Some(rational::Ratio::new(*num, *den)),
}
}
pub fn get_fnumber(&self) -> Option<f64> {
match unsafe { gexiv2::gexiv2_metadata_get_fnumber(self.raw) } {
error_value if error_value < 0.0 => None, fnumber => Some(fnumber),
}
}
pub fn get_focal_length(&self) -> Option<f64> {
match unsafe { gexiv2::gexiv2_metadata_get_focal_length(self.raw) } {
error_value if error_value < 0.0 => None, focal => Some(focal),
}
}
pub fn get_iso_speed(&self) -> Option<i32> {
match unsafe { gexiv2::gexiv2_metadata_get_iso_speed(self.raw) } {
0 => None,
speed => Some(speed),
}
}
pub fn get_gps_info(&self) -> Option<GpsInfo> {
let lon = &mut 0.0;
let lat = &mut 0.0;
let alt = &mut 0.0;
match unsafe { gexiv2::gexiv2_metadata_get_gps_info(self.raw, lon, lat, alt) } {
0 => None,
_ => Some(GpsInfo { longitude: *lon, latitude: *lat, altitude: *alt }),
}
}
pub fn set_gps_info(&self, gps: &GpsInfo) -> Result<()> {
unsafe {
int_bool_to_result(gexiv2::gexiv2_metadata_set_gps_info(self.raw,
gps.longitude,
gps.latitude,
gps.altitude))
}
}
pub fn delete_gps_info(&self) {
unsafe { gexiv2::gexiv2_metadata_delete_gps_info(self.raw) }
}
}
impl Drop for Metadata {
fn drop(&mut self) {
unsafe { gexiv2::gexiv2_metadata_free(self.raw) }
}
}
pub fn is_exif_tag(tag: &str) -> bool {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe { gexiv2::gexiv2_metadata_is_exif_tag(c_str_tag.as_ptr()) == 1 }
}
pub fn is_iptc_tag(tag: &str) -> bool {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe { gexiv2::gexiv2_metadata_is_iptc_tag(c_str_tag.as_ptr()) == 1 }
}
pub fn is_xmp_tag(tag: &str) -> bool {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe { gexiv2::gexiv2_metadata_is_xmp_tag(c_str_tag.as_ptr()) == 1 }
}
pub fn get_tag_label(tag: &str) -> Result<String> {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe {
let c_str_val = gexiv2::gexiv2_metadata_get_tag_label(c_str_tag.as_ptr());
if c_str_val.is_null() {
return Err(Rexiv2Error::NoValue);
}
Ok(try!(ffi::CStr::from_ptr(c_str_val).to_str()).to_string())
}
}
pub fn get_tag_description(tag: &str) -> Result<String> {
let c_str_tag = ffi::CString::new(tag).unwrap();
unsafe {
let c_str_val = gexiv2::gexiv2_metadata_get_tag_description(c_str_tag.as_ptr());
if c_str_val.is_null() {
return Err(Rexiv2Error::NoValue);
}
Ok(try!(ffi::CStr::from_ptr(c_str_val).to_str()).to_string())
}
}
pub fn get_tag_type(tag: &str) -> Result<TagType> {
let c_str_tag = ffi::CString::new(tag).unwrap();
let tag_type = unsafe {
let c_str_val = gexiv2::gexiv2_metadata_get_tag_type(c_str_tag.as_ptr());
if c_str_val.is_null() {
return Err(Rexiv2Error::NoValue);
}
try!(ffi::CStr::from_ptr(c_str_val).to_str())
};
match tag_type {
"Byte" => Ok(TagType::UnsignedByte),
"Ascii" => Ok(TagType::AsciiString),
"Short" => Ok(TagType::UnsignedShort),
"Long" => Ok(TagType::UnsignedLong),
"Rational" => Ok(TagType::UnsignedRational),
"SByte" => Ok(TagType::SignedByte),
"Undefined" => Ok(TagType::Undefined),
"SShort" => Ok(TagType::SignedShort),
"SLong" => Ok(TagType::SignedLong),
"SRational" => Ok(TagType::SignedRational),
"Float" => Ok(TagType::TiffFloat),
"Double" => Ok(TagType::TiffDouble),
"Ifd" => Ok(TagType::TiffIfd),
"String" => Ok(TagType::String),
"Date" => Ok(TagType::Date),
"Time" => Ok(TagType::Time),
"Comment" => Ok(TagType::Comment),
"Directory" => Ok(TagType::Directory),
"XmpText" => Ok(TagType::XmpText),
"XmpAlt" => Ok(TagType::XmpAlt),
"XmpBag" => Ok(TagType::XmpBag),
"XmpSeq" => Ok(TagType::XmpSeq),
"LangAlt" => Ok(TagType::LangAlt),
"Invalid" => Ok(TagType::Invalid),
_ => Ok(TagType::Unknown),
}
}
pub fn register_xmp_namespace(name: &str, prefix: &str) -> Result<()> {
let c_str_name = ffi::CString::new(name).unwrap();
let c_str_prefix = ffi::CString::new(prefix).unwrap();
unsafe {
int_bool_to_result(gexiv2::gexiv2_metadata_register_xmp_namespace(c_str_name.as_ptr(),
c_str_prefix.as_ptr()))
}
}
pub fn unregister_xmp_namespace(name: &str) -> Result<()> {
let c_str_name = ffi::CString::new(name).unwrap();
unsafe {
int_bool_to_result(gexiv2::gexiv2_metadata_unregister_xmp_namespace(c_str_name.as_ptr()))
}
}
pub fn unregister_all_xmp_namespaces() {
unsafe { gexiv2::gexiv2_metadata_unregister_all_xmp_namespaces() }
}
fn free_array_of_pointers(list: *mut *mut libc::c_void) {
unsafe {
let mut idx = 0;
while !(*list.offset(idx)).is_null() {
libc::free(*list.offset(idx));
idx += 1;
}
libc::free(list as *mut libc::c_void);
}
}
fn int_bool_to_result(success: libc::c_int) -> Result<()> {
match success {
0 => Err(Rexiv2Error::Internal(None)),
_ => Ok(()),
}
}