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 }