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 3de41524ce32bb45f1dd100f8ddc6bb009aa10e2
parent b3e85c70b61f511afca9be871a7f6a477e2a9e03
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date:   Wed, 27 Mar 2024 15:14:32 -0400

Added support for XMP streaming writes for TIFF/DNG (#433)

* TIFF read support

* Start of alternate apporach

* simplify experiment

* More intermeadiate work

* Working support for stripped TIFFs

* More TIFF enhnacements

* SubFile stuff

* DNG push

* DNG bug fixes

* Endian fixes

* Support for GPS IFD
Clippy fixes

* Patch support
bug fixes

* Code cleanup

* Review changes

* clippy fmt fix

* wasm fix

* Fix for linux build

* Address PR comments

* fix up comments

* TIFF streaming write support
Build fixes

* Add streaming xmp writes

* preserve existing XMP

---------

Co-authored-by: mauricefisher64 <maurice_fisher@hotmail.com>
Diffstat:
Msdk/src/asset_handlers/tiff_io.rs | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 74 insertions(+), 10 deletions(-)

diff --git a/sdk/src/asset_handlers/tiff_io.rs b/sdk/src/asset_handlers/tiff_io.rs @@ -32,6 +32,7 @@ use crate::{ RemoteRefEmbedType, }, error::{Error, Result}, + utils::xmp_inmemory_utils::{add_provenance, MIN_XMP}, }; const II: [u8; 2] = *b"II"; @@ -64,6 +65,7 @@ static SUPPORTED_TYPES: [&str; 9] = [ ]; // The type of an IFD entry +#[derive(Debug, PartialEq)] enum IFDEntryType { Byte = 1, // 8-bit unsigned integer Ascii = 2, // 8-bit byte that contains a 7-bit ASCII code; the last byte must be zero @@ -1371,6 +1373,11 @@ where None => return None, }; + // make sure the tag type is correct + if IFDEntryType::from_u16(xmp_ifd_entry.entry_type)? != IFDEntryType::Byte { + return None; + } + // move read point to start of entry let decoded_offset = decode_offset(xmp_ifd_entry.value_offset, e, big_tiff).ok()?; asset_reader.seek(SeekFrom::Start(decoded_offset)).ok()?; @@ -1610,15 +1617,22 @@ impl RemoteRefEmbed for TiffIO { ) -> Result<()> { match embed_ref { crate::asset_io::RemoteRefEmbedType::Xmp(manifest_uri) => { - #[cfg(feature = "xmp_write")] - { - crate::embedded_xmp::add_manifest_uri_to_file(asset_path, &manifest_uri) - } + let output_buf = Vec::new(); + let mut output_stream = Cursor::new(output_buf); - #[cfg(not(feature = "xmp_write"))] + // block so that source file is closed after embed { - Err(crate::error::Error::MissingFeature("xmp_write".to_string())) + let mut source_stream = std::fs::File::open(asset_path)?; + self.embed_reference_to_stream( + &mut source_stream, + &mut output_stream, + RemoteRefEmbedType::Xmp(manifest_uri), + )?; } + + // write will replace exisiting contents + std::fs::write(asset_path, output_stream.into_inner())?; + Ok(()) } crate::asset_io::RemoteRefEmbedType::StegoS(_) => Err(Error::UnsupportedType), crate::asset_io::RemoteRefEmbedType::StegoB(_) => Err(Error::UnsupportedType), @@ -1628,11 +1642,35 @@ impl RemoteRefEmbed for TiffIO { 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 xmp = match self.get_reader().read_xmp(source_stream) { + Some(xmp) => add_provenance(&xmp, &manifest_uri)?, + None => { + let xmp = format!("http://ns.adobe.com/xap/1.0/\0 {}", MIN_XMP); + add_provenance(&xmp, &manifest_uri)? + } + }; + + let l = u64::value_from(xmp.len()) + .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?; + + let entry = IfdClonedEntry { + entry_tag: XMP_TAG, + entry_type: IFDEntryType::Byte as u16, + value_count: l, + value_bytes: xmp.as_bytes().to_vec(), + }; + tiff_clone_with_tags(output_stream, source_stream, vec![entry]) + } + crate::asset_io::RemoteRefEmbedType::StegoS(_) => Err(Error::UnsupportedType), + crate::asset_io::RemoteRefEmbedType::StegoB(_) => Err(Error::UnsupportedType), + crate::asset_io::RemoteRefEmbedType::Watermark(_) => Err(Error::UnsupportedType), + } } } @@ -1678,6 +1716,32 @@ pub mod tests { } #[test] + fn test_write_xmp() { + let data = "some data"; + + let source = crate::utils::test::fixture_path("TUSCANY.TIF"); + + let temp_dir = tempdir().unwrap(); + let output = temp_dir_path(&temp_dir, "test.tif"); + + std::fs::copy(source, &output).unwrap(); + + let tiff_io = TiffIO {}; + + // save data to tiff + let eh = tiff_io.remote_ref_writer_ref().unwrap(); + eh.embed_reference(&output, RemoteRefEmbedType::Xmp(data.to_string())) + .unwrap(); + + // read data back + let mut output_stream = std::fs::File::open(&output).unwrap(); + let xmp = tiff_io.read_xmp(&mut output_stream).unwrap(); + let loaded = crate::utils::xmp_inmemory_utils::extract_provenance(&xmp).unwrap(); + + assert_eq!(&loaded, data); + } + + #[test] fn test_remove_manifest() { let data = "some data";