c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

exif.rs (7112B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 //! Exif Assertion
     15 use std::collections::HashMap;
     16 
     17 use serde::{de::DeserializeOwned, Deserialize, Serialize};
     18 use serde_json::{json, Value};
     19 
     20 use crate::{
     21     assertion::{Assertion, AssertionBase, AssertionJson},
     22     assertions::labels,
     23     Error, Result,
     24 };
     25 
     26 /// The EXIF assertion as defined in the C2PA spec section 17.13
     27 ///  See <https://c2pa.org/specifications/specifications/1.0/specs/C2PA_Specification.html#_exif_information>
     28 ///
     29 /// This does not yet define or validate individual fields, but will ensure the correct assertion structure
     30 #[derive(Serialize, Deserialize, Debug)]
     31 pub struct Exif {
     32     #[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
     33     object_context: Option<Value>,
     34     #[serde(flatten)]
     35     value: HashMap<String, Value>,
     36 }
     37 
     38 impl Exif {
     39     pub fn new() -> Self {
     40         Self {
     41             object_context: Some(json!({
     42               "dc": "http://purl.org/dc/elements/1.1/",
     43               "exifEX": "http://cipa.jp/exif/2.32/",
     44               "exif": "http://ns.adobe.com/exif/1.0/",
     45               "tiff": "http://ns.adobe.com/tiff/1.0/",
     46               "xmp": "http://ns.adobe.com/xap/1.0/",
     47               "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     48             })),
     49             value: HashMap::new(),
     50         }
     51     }
     52 
     53     /// sets the @context field for Schema dot org.
     54     pub fn set_context(mut self, context: Value) -> Self {
     55         self.object_context = Some(context);
     56         self
     57     }
     58 
     59     /// get values by key as an instance of type `T`.
     60     /// This return T is owned, not a reference
     61     /// # Errors
     62     ///
     63     /// This conversion can fail if the structure of the field at key does not match the
     64     /// structure expected by `T`
     65     pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
     66         self.value
     67             .get(key)
     68             .and_then(|v| serde_json::from_value(v.clone()).ok())
     69     }
     70 
     71     /// insert key / value pair of instance of type `T`
     72     /// # Errors
     73     ///
     74     /// This conversion can fail if `T`'s implementation of `Serialize` decides to
     75     /// fail, or if `T` contains a map with non-string keys.
     76     pub fn insert<S: Into<String>, T: Serialize>(mut self, key: S, value: T) -> Result<Self> {
     77         self.value.insert(key.into(), serde_json::to_value(value)?);
     78         Ok(self)
     79     }
     80 
     81     // add a value to a Vec stored at key
     82     pub fn insert_push<S: Into<String>, T: Serialize + DeserializeOwned>(
     83         self,
     84         key: S,
     85         value: T,
     86     ) -> Result<Self> {
     87         let key = key.into();
     88         Ok(match self.get(&key) as Option<Vec<T>> {
     89             Some(mut v) => {
     90                 v.push(value);
     91                 self
     92             }
     93             None => self.insert(&key, Vec::from([value]))?,
     94         })
     95     }
     96 
     97     /// creates the struct from a correctly formatted JSON string
     98     pub fn from_json_str(json: &str) -> Result<Self> {
     99         serde_json::from_slice(json.as_bytes()).map_err(Error::JsonError)
    100     }
    101 }
    102 
    103 // Implementing default is a good idea
    104 impl Default for Exif {
    105     fn default() -> Self {
    106         Self::new()
    107     }
    108 }
    109 
    110 // Implement as AssertionJson
    111 impl AssertionJson for Exif {}
    112 
    113 impl AssertionBase for Exif {
    114     // A label for our assertion, use reverse domain name syntax
    115     const LABEL: &'static str = labels::EXIF;
    116 
    117     fn to_assertion(&self) -> Result<Assertion> {
    118         Self::to_json_assertion(self)
    119     }
    120 
    121     fn from_assertion(assertion: &Assertion) -> Result<Self> {
    122         Self::from_json_assertion(assertion)
    123     }
    124 }
    125 
    126 #[cfg(test)]
    127 pub mod tests {
    128     #![allow(clippy::expect_used)]
    129     #![allow(clippy::unwrap_used)]
    130 
    131     use super::*;
    132     use crate::Manifest;
    133 
    134     const SPEC_EXAMPLE: &str = r#"{
    135         "@context" : {
    136           "dc": "http://purl.org/dc/elements/1.1/",
    137           "exifEX": "http://cipa.jp/exif/2.32/",
    138           "exif": "http://ns.adobe.com/exif/1.0/",
    139           "tiff": "http://ns.adobe.com/tiff/1.0/",
    140           "xmp": "http://ns.adobe.com/xap/1.0/",
    141           "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    142         },
    143         "exif:GPSVersionID": "2.2.0.0",
    144         "exif:GPSLatitude": "39,21.102N",
    145         "exif:GPSLongitude": "74,26.5737W",
    146         "exif:GPSAltitudeRef": 0,
    147         "exif:GPSAltitude": "100963/29890",
    148         "exif:GPSTimeStamp": "2019-09-22T18:22:57Z",
    149         "exif:GPSSpeedRef": "K",
    150         "exif:GPSSpeed": "4009/161323",
    151         "exif:GPSImgDirectionRef": "T",
    152         "exif:GPSImgDirection": "296140/911",
    153         "exif:GPSDestBearingRef": "T",
    154         "exif:GPSDestBearing": "296140/911",
    155         "exif:GPSHPositioningError": "13244/2207",
    156         "exif:ExposureTime": "1/100",
    157         "exif:FNumber": 4.0,
    158         "exif:ColorSpace": 1,
    159         "exif:DigitalZoomRatio": 2.0,
    160         "tiff:Make": "CameraCompany",
    161         "tiff:Model": "Shooter S1",
    162         "exifEX:LensMake": "CameraCompany",
    163         "exifEX:LensModel": "17.0-35.0 mm",
    164         "exifEX:LensSpecification": { "@list": [ 1.55, 4.2, 1.6, 2.4 ] }
    165       }"#;
    166 
    167     #[test]
    168     fn exif_new() {
    169         let mut manifest = Manifest::new("my_app".to_owned());
    170         let original = Exif::new()
    171             .insert("exif:GPSLatitude", "39,21.102N")
    172             .unwrap();
    173         manifest.add_assertion(&original).expect("adding assertion");
    174         println!("{manifest}");
    175         let exif: Exif = manifest
    176             .find_assertion(Exif::LABEL)
    177             .expect("find_assertion");
    178         let latitude: String = exif.get("exif:GPSLatitude").unwrap();
    179         assert_eq!(&latitude, "39,21.102N")
    180     }
    181 
    182     #[test]
    183     fn exif_from_json() {
    184         let mut manifest = Manifest::new("my_app".to_owned());
    185         let original = Exif::from_json_str(SPEC_EXAMPLE).expect("from_json");
    186         manifest.add_assertion(&original).expect("adding assertion");
    187         println!("{manifest}");
    188         let exif: Exif = manifest
    189             .find_assertion(Exif::LABEL)
    190             .expect("find_assertion");
    191         let latitude: String = exif.get("exif:GPSLatitude").unwrap();
    192         assert_eq!(&latitude, "39,21.102N")
    193     }
    194 
    195     #[test]
    196     fn exif_to_assertoin() {
    197         let original = Exif::from_json_str(SPEC_EXAMPLE).expect("from_json");
    198         let assertion = original.to_assertion().expect("to_assertion");
    199         assert_eq!(assertion.content_type(), "application/json");
    200         println!("{assertion:?}");
    201         let result = Exif::from_assertion(&assertion).expect("from_assertion");
    202         println!("{result:?}");
    203         let latitude: String = result.get("exif:GPSLatitude").unwrap();
    204         assert_eq!(&latitude, "39,21.102N")
    205     }
    206 }