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

commit 665c5a359b761e3e48ada9c23a57d38dd681ce7c
parent 48df00c7ac5d81557c8f4c8bcc2a281555daf2ab
Author: Gavin  Peacock <gpeacock@adobe.com>
Date:   Fri, 10 Mar 2023 11:20:59 -0800

(MINOR) Resource format and is_parent / relationship changes (#202)

change resource.content_type  to format
change ingredient.is_parent to relationship.

Diffstat:
Msdk/src/assertions/ingredient.rs | 2+-
Msdk/src/ingredient.rs | 47++++++++++++++++++++---------------------------
Msdk/src/manifest.rs | 26+++++++++++++-------------
Msdk/src/resource_store.rs | 16++++++++--------
Msdk/src/utils/thumbnail.rs | 8++++----
5 files changed, 46 insertions(+), 53 deletions(-)

diff --git a/sdk/src/assertions/ingredient.rs b/sdk/src/assertions/ingredient.rs @@ -24,7 +24,7 @@ use crate::{ const ASSERTION_CREATION_VERSION: usize = 1; // Used to differentiate a parent from a component -#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)] pub enum Relationship { #[serde(rename = "parentOf")] ParentOf, diff --git a/sdk/src/ingredient.rs b/sdk/src/ingredient.rs @@ -65,11 +65,13 @@ pub struct Ingredient { #[serde(skip_serializing_if = "Option::is_none")] hash: Option<String>, - /// Set to `true` if this is the parent ingredient. + /// Set to `ParentOf` if this is the parent ingredient. /// /// There can only be one parent ingredient in the ingredients. - #[serde(skip_serializing_if = "Option::is_none")] - is_parent: Option<bool>, + // #[serde(skip_serializing_if = "Option::is_none")] + // is_parent: Option<bool>, + #[serde(default = "default_relationship")] + relationship: Relationship, /// The active manifest label (if one exists). /// @@ -110,6 +112,10 @@ fn default_format() -> String { "application/octet-stream".to_owned() } +fn default_relationship() -> Relationship { + Relationship::default() +} + impl Ingredient { /// Constructs a new `Ingredient`. /// @@ -167,12 +173,12 @@ impl Ingredient { self.thumbnail.as_ref() } - /// Returns thumbnail tuple Some((content_type, bytes)) or None + /// Returns thumbnail tuple Some((format, bytes)) or None /// pub fn thumbnail(&self) -> Option<(&str, Cow<Vec<u8>>)> { self.thumbnail .as_ref() - .and_then(|t| Some(t.content_type.as_str()).zip(self.resources.get(&t.identifier).ok())) + .and_then(|t| Some(t.format.as_str()).zip(self.resources.get(&t.identifier).ok())) } /// Returns a Cow of thumbnail bytes or Err(Error::NotFound)`. @@ -191,7 +197,7 @@ impl Ingredient { /// Returns `true` if this is labeled as the parent ingredient. pub fn is_parent(&self) -> bool { - self.is_parent.unwrap_or(false) + self.relationship == Relationship::ParentOf } /// Returns a reference to the [`ValidationStatus`]s if they exist. @@ -261,7 +267,7 @@ impl Ingredient { /// Only one ingredient should be flagged as a parent. /// Use Manifest.set_parent to ensure this is the only parent ingredient pub fn set_is_parent(&mut self) -> &mut Self { - self.is_parent = Some(true); + self.relationship = Relationship::ParentOf; self } @@ -275,17 +281,14 @@ impl Ingredient { Ok(self) } - /// Sets the thumbnail content_type and image data. + /// Sets the thumbnail format and image data. pub fn set_thumbnail<S: Into<String>, B: Into<Vec<u8>>>( &mut self, - content_type: S, + format: S, bytes: B, ) -> Result<&mut Self> { let base_id = self.instance_id().to_string(); - self.thumbnail = Some( - self.resources - .add_with(&base_id, &content_type.into(), bytes)?, - ); + self.thumbnail = Some(self.resources.add_with(&base_id, &format.into(), bytes)?); Ok(self) } @@ -609,11 +612,6 @@ impl Ingredient { None => Vec::new(), }; - let is_parent = match ingredient_assertion.relationship { - Relationship::ParentOf => Some(true), - Relationship::ComponentOf => None, - }; - let active_manifest = ingredient_assertion .c2pa_manifest .and_then(|hash_url| jumbf::labels::manifest_label_from_uri(&hash_url.url())); @@ -659,7 +657,7 @@ impl Ingredient { ingredient.set_thumbnail(format, image)?; } - ingredient.is_parent = is_parent; + ingredient.relationship = ingredient_assertion.relationship; ingredient.active_manifest = active_manifest; if !validation_status.is_empty() { ingredient.validation_status = Some(validation_status) @@ -752,12 +750,6 @@ impl Ingredient { None => None, }; - let relationship = if self.is_parent() { - Relationship::ParentOf - } else { - Relationship::ComponentOf - }; - // add ingredient thumbnail assertion if one is given and we don't already have one from the parent claim if thumbnail.is_none() { if let Some((format, data)) = self.thumbnail() { @@ -778,7 +770,7 @@ impl Ingredient { ); ingredient_assertion.c2pa_manifest = c2pa_manifest; - ingredient_assertion.relationship = relationship; + ingredient_assertion.relationship = self.relationship.clone(); ingredient_assertion.thumbnail = thumbnail; ingredient_assertion.metadata = self.metadata.clone(); ingredient_assertion.validation_status = self.validation_status.clone(); @@ -1086,7 +1078,8 @@ mod tests_file_io { let ap = fixture_path("CIE-sig-CA.jpg"); let ingredient = Ingredient::from_file(ap).expect("from_file"); println!("ingredient = {ingredient}"); - assert_eq!(ingredient.validation_status(), None); + assert!(ingredient.validation_status().is_none()); + assert!(ingredient.manifest_data().is_some()); } #[test] diff --git a/sdk/src/manifest.rs b/sdk/src/manifest.rs @@ -151,12 +151,12 @@ impl Manifest { self.title.as_deref() } - /// Returns thumbnail tuple with Some((content_type, bytes)) or None + /// Returns thumbnail tuple with Some((format, bytes)) or None /// pub fn thumbnail(&self) -> Option<(&str, Cow<Vec<u8>>)> { self.thumbnail .as_ref() - .and_then(|t| Some(t.content_type.as_str()).zip(self.resources.get(&t.identifier).ok())) + .and_then(|t| Some(t.format.as_str()).zip(self.resources.get(&t.identifier).ok())) } /// Returns a thumbnail ResourceRef or `None`. @@ -664,9 +664,9 @@ impl Manifest { } claim.format = self.format().to_owned(); claim.instance_id = self.instance_id().to_owned(); - if let Some((content_type, data)) = self.thumbnail() { + if let Some((format, data)) = self.thumbnail() { claim.add_assertion(&Thumbnail::new( - &labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, content_type), + &labels::add_thumbnail_format(labels::CLAIM_THUMBNAIL, format), data.to_vec(), ))?; } @@ -1496,8 +1496,8 @@ pub(crate) mod tests { manifest.embed(&output, &output, &signer).expect("embed"); let manifest_store = crate::ManifestStore::from_file(&output).expect("from_file"); let active_manifest = manifest_store.get_active().unwrap(); - let (content_type, image) = active_manifest.thumbnail().unwrap(); - assert_eq!(content_type, "image/jpeg"); + let (format, image) = active_manifest.thumbnail().unwrap(); + assert_eq!(format, "image/jpeg"); assert_eq!(image.into_owned(), thumb_data); } @@ -1506,16 +1506,16 @@ pub(crate) mod tests { "claim_generator": "test", "format" : "image/jpeg", "thumbnail": { - "content_type": "image/jpeg", + "format": "image/jpeg", "identifier": "IMG_0003.jpg" }, "ingredients": [{ "title": "A.jpg", "format": "image/jpeg", "document_id": "xmp.did:813ee422-9736-4cdc-9be6-4e35ed8e41cb", - "is_parent": true, + "relationship": "parentOf", "thumbnail": { - "content_type": "image/png", + "format": "image/png", "identifier": "exp-test1.png" } }] @@ -1556,8 +1556,8 @@ pub(crate) mod tests { let m = manifest_store.get_active().unwrap(); assert!(m.thumbnail().is_some()); - let (content_type, image) = m.thumbnail().unwrap(); - assert_eq!(content_type, "image/jpeg"); + let (format, image) = m.thumbnail().unwrap(); + assert_eq!(format, "image/jpeg"); assert_eq!(image.to_vec(), b"my value"); // println!("{manifest_store}"); } @@ -1602,8 +1602,8 @@ pub(crate) mod tests { let manifest_store = crate::ManifestStore::from_file(&output).expect("from_file"); println!("{manifest_store}"); let active_manifest = manifest_store.get_active().unwrap(); - let (content_type, _) = active_manifest.thumbnail().unwrap(); - assert_eq!(content_type, "image/jpeg"); + let (format, _) = active_manifest.thumbnail().unwrap(); + assert_eq!(format, "image/jpeg"); } #[test] diff --git a/sdk/src/resource_store.rs b/sdk/src/resource_store.rs @@ -29,14 +29,14 @@ pub(crate) fn skip_serializing_resources(_: &ResourceStore) -> bool { /// A reference to a resource to be used in JSON serialization #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub struct ResourceRef { - pub content_type: String, + pub format: String, pub identifier: String, } impl ResourceRef { - pub fn new<S: Into<String>, I: Into<String>>(content_type: S, identifier: I) -> Self { + pub fn new<S: Into<String>, I: Into<String>>(format: S, identifier: I) -> Self { Self { - content_type: content_type.into(), + format: format.into(), identifier: identifier.into(), } } @@ -99,7 +99,7 @@ impl ResourceStore { R: Into<Vec<u8>>, { let id = self.id_from(key, format); - self.add(id.clone(), value)?; + self.add(&id, value)?; Ok(ResourceRef::new(format, id)) } @@ -114,7 +114,7 @@ impl ResourceStore { let path = base.join(id.into()); std::fs::create_dir_all(path.parent().unwrap_or(Path::new("")))?; #[allow(clippy::expect_used)] - std::fs::write(path, value.into()).expect("write failed"); + std::fs::write(path, value.into())?; return Ok(()); } self.resources.insert(id.into(), value.into()); @@ -199,7 +199,7 @@ mod tests { "instance_id": "12345", "assertions": [], "thumbnail": { - "content_type": "image/jpeg", + "format": "image/jpeg", "identifier": "abc123" }, "ingredients": [{ @@ -207,9 +207,9 @@ mod tests { "format": "image/jpeg", "document_id": "xmp.did:813ee422-9736-4cdc-9be6-4e35ed8e41cb", "instance_id": "xmp.iid:813ee422-9736-4cdc-9be6-4e35ed8e41cb", - "is_parent": true, + "relationship": "parentOf", "thumbnail": { - "content_type": "image/jpeg", + "format": "image/jpeg", "identifier": "cba321" } }] diff --git a/sdk/src/utils/thumbnail.rs b/sdk/src/utils/thumbnail.rs @@ -35,7 +35,7 @@ pub fn make_thumbnail(path: &std::path::Path) -> Result<(String, Vec<u8>)> { } // for png files, use png thumbnails if there is an alpha channel // for other supported types try a jpeg thumbnail - let (output_format, content_type) = match format { + let (output_format, format) = match format { ImageFormat::Png if img.color().has_alpha() => (image::ImageOutputFormat::Png, "image/png"), _ => ( image::ImageOutputFormat::Jpeg(THUMBNAIL_JPEG_QUALITY), @@ -46,7 +46,7 @@ pub fn make_thumbnail(path: &std::path::Path) -> Result<(String, Vec<u8>)> { let mut cursor = std::io::Cursor::new(thumbnail_bits); img.write_to(&mut cursor, output_format)?; - let format = content_type.to_owned(); + let format = format.to_owned(); Ok((format, cursor.into_inner())) } @@ -73,7 +73,7 @@ pub fn make_thumbnail_from_stream( // for png files, use png thumbnails for transparency // for other supported types try a jpeg thumbnail - let (output_format, content_type) = match format { + let (output_format, format) = match format { ImageFormat::Png => (image::ImageOutputFormat::Png, "image/png"), _ => ( image::ImageOutputFormat::Jpeg(THUMBNAIL_JPEG_QUALITY), @@ -84,6 +84,6 @@ pub fn make_thumbnail_from_stream( let mut cursor = std::io::Cursor::new(thumbnail_bits); img.write_to(&mut cursor, output_format)?; - let format = content_type.to_owned(); + let format = format.to_owned(); Ok((format, cursor.into_inner())) }