commit b3e85c70b61f511afca9be871a7f6a477e2a9e03
parent cde7927e9b29220fefc906101e0d420db9a84c0a
Author: Gavin Peacock <gpeacock@adobe.com>
Date: Wed, 27 Mar 2024 11:10:39 -0700
Implements embed_reference_to_stream for jpeg (#430)
* Implements embed_reference_to_stream for jpeg
This works without xmp_toolkit
* Share XMP_MIN and add stream based wasm test
* remove debug print
Diffstat:
2 files changed, 87 insertions(+), 5 deletions(-)
diff --git a/sdk/src/asset_handlers/jpeg_io.rs b/sdk/src/asset_handlers/jpeg_io.rs
@@ -37,6 +37,7 @@ use crate::{
RemoteRefEmbedType,
},
error::{Error, Result},
+ utils::xmp_inmemory_utils::{add_provenance, MIN_XMP},
};
static SUPPORTED_TYPES: [&str; 3] = ["jpg", "jpeg", "image/jpeg"];
@@ -574,11 +575,52 @@ impl RemoteRefEmbed for JpegIO {
fn embed_reference_to_stream(
&self,
- _source_stream: &mut dyn CAIRead,
- _output_stream: &mut dyn CAIReadWrite,
- _embed_ref: RemoteRefEmbedType,
+ source_stream: &mut dyn CAIRead,
+ output_stream: &mut dyn CAIReadWrite,
+ embed_ref: RemoteRefEmbedType,
) -> Result<()> {
- Err(Error::UnsupportedType)
+ match embed_ref {
+ crate::asset_io::RemoteRefEmbedType::Xmp(manifest_uri) => {
+ let mut buf = Vec::new();
+ // read the whole asset
+ source_stream.rewind()?;
+ source_stream
+ .read_to_end(&mut buf)
+ .map_err(Error::IoError)?;
+ let mut jpeg =
+ Jpeg::from_bytes(buf.into()).map_err(|_err| Error::EmbeddingError)?;
+
+ // first extract the xmp from APP1 markers
+ let app1_segs = jpeg.segments_by_marker(markers::APP1);
+ let mut xmp: String = app1_segs.filter_map(extract_xmp).collect();
+
+ // remove existing XMP segments
+ let segments = jpeg.segments_mut();
+ segments.retain(|seg| {
+ !(seg.marker() == markers::APP1 && seg.contents().starts_with(XMP_SIGNATURE))
+ });
+
+ if xmp.is_empty() {
+ // Init with minimal xmp segment
+ // JPEG APP1 segment with XMP are defined with this null terminated string
+ // todo: add format and other minimal metadata to xmp ?
+ xmp = format!("http://ns.adobe.com/xap/1.0/\0 {}", MIN_XMP);
+ };
+ let xmp = add_provenance(&xmp, &manifest_uri)?;
+ let xmp_bytes = Bytes::from(xmp);
+ let segment = JpegSegment::new_with_contents(markers::APP1, xmp_bytes);
+ segments.insert(1, segment);
+
+ output_stream.rewind()?;
+ jpeg.encoder()
+ .write_to(output_stream)
+ .map_err(|_err| Error::InvalidAsset("JPEG write error".to_owned()))?;
+ Ok(())
+ }
+ crate::asset_io::RemoteRefEmbedType::StegoS(_) => Err(Error::UnsupportedType),
+ crate::asset_io::RemoteRefEmbedType::StegoB(_) => Err(Error::UnsupportedType),
+ crate::asset_io::RemoteRefEmbedType::Watermark(_) => Err(Error::UnsupportedType),
+ }
}
}
@@ -1035,6 +1077,9 @@ pub mod tests {
use std::io::{Read, Seek};
+ #[cfg(target_arch = "wasm32")]
+ use wasm_bindgen_test::*;
+
use super::*;
#[test]
fn test_extract_xmp() {
@@ -1129,6 +1174,43 @@ pub mod tests {
assert!(read_xmp.contains(test_msg));
}
+ #[cfg_attr(not(target_arch = "wasm32"), actix::test)]
+ #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
+ async fn test_xmp_read_write_stream() {
+ let source_bytes = include_bytes!("../../tests/fixtures/CA.jpg");
+
+ let test_msg = "this some test xmp data";
+ let handler = JpegIO::new("");
+
+ let assetio_handler = handler.get_handler("jpg");
+
+ let remote_ref_handler = assetio_handler.remote_ref_writer_ref().unwrap();
+
+ let mut source_stream = Cursor::new(source_bytes.to_vec());
+ let mut output_stream = Cursor::new(Vec::new());
+ remote_ref_handler
+ .embed_reference_to_stream(
+ &mut source_stream,
+ &mut output_stream,
+ RemoteRefEmbedType::Xmp(test_msg.to_string()),
+ )
+ .unwrap();
+
+ output_stream.set_position(0);
+
+ // read back in XMP
+ let read_xmp = assetio_handler
+ .get_reader()
+ .read_xmp(&mut output_stream)
+ .unwrap();
+
+ output_stream.set_position(0);
+
+ //std::fs::write("../target/xmp_write.jpg", output_stream.into_inner()).unwrap();
+
+ assert!(read_xmp.contains(test_msg));
+ }
+
#[test]
fn test_embeddable_manifest() {
let jpeg_io = JpegIO {};
diff --git a/sdk/src/utils/xmp_inmemory_utils.rs b/sdk/src/utils/xmp_inmemory_utils.rs
@@ -26,7 +26,7 @@ use crate::{
const RDF_DESCRIPTION: &[u8] = b"rdf:Description";
-pub const MIN_XMP: &str = r#"<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" > </rdf:Description></rdf:RDF> </x:xmpmeta> "#;
+pub const MIN_XMP: &str = r#"<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" > </rdf:Description></rdf:RDF> </x:xmpmeta> "#;
#[derive(Default)]
pub struct XmpInfo {