-
Notifications
You must be signed in to change notification settings - Fork 25
Feature/UUID #231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/UUID #231
Changes from all commits
cc5f06a
cc68ddf
1519256
078fa90
74dd32a
74baa80
817a5ef
d86a7cd
ca63a24
272e270
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -369,6 +369,7 @@ any! { | |
| Mfra, | ||
| Tfra, | ||
| Mfro, | ||
| Uuid, | ||
| ], | ||
| boxed: [ | ||
| Trak, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| use crate::*; | ||
|
|
||
| use std::io::Cursor; | ||
|
|
||
| #[test] | ||
| fn read_c2pa_from_c2pa_mp4() -> Result<()> { | ||
| // Example file from c2pa, and independently verified with bento4 - mp4dump and c2patool | ||
| // https://github.com/c2pa-org/public-testfiles/tree/main/legacy/1.4/video/mp4 | ||
| const ENCODED: &[u8] = include_bytes!("c2pa.mp4"); | ||
|
|
||
| let mut cursor = Cursor::new(ENCODED); | ||
|
|
||
| let recieved_ftyp = Ftyp::decode(&mut cursor)?; | ||
| let expected_ftyp = Ftyp { | ||
| major_brand: b"mp42".into(), | ||
| minor_version: 0, | ||
| compatible_brands: vec![b"isom".into(), b"mp42".into()], | ||
| }; | ||
|
|
||
| assert_eq!(recieved_ftyp, expected_ftyp); | ||
|
|
||
| let uuid = Uuid::decode(&mut cursor)?; | ||
|
|
||
| let c2pa_data = match uuid { | ||
| Uuid::C2pa(data) => data, | ||
| other => panic!("Expected Uuid::C2pa, got {:?}", other), | ||
| }; | ||
|
|
||
| assert_eq!(c2pa_data.box_purpose, "manifest"); | ||
|
|
||
| Ok(()) | ||
|
Comment on lines
+5
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win The fixture test decodes the UUID box directly with 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| mod av1; | ||
| mod av1_mdta; | ||
| mod bbb; | ||
| mod c2pa; | ||
| mod esds; | ||
| mod flac; | ||
| mod h264; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| use crate::*; | ||
|
|
||
| pub trait UuidAtom: Sized { | ||
| const EXTENDED_TYPE: ExtendedType; | ||
|
|
||
| fn decode_uuid_body<B: Buf>(buf: &mut B) -> Result<Self>; | ||
| fn encode_uuid_body<B: BufMut>(&self, buf: &mut B) -> Result<()>; | ||
| } | ||
|
|
||
| pub(crate) trait UuidAtomExt: Sized { | ||
| const EXTENDED_TYPE_EXT: ExtendedType; | ||
| type Ext: Ext; | ||
|
|
||
| fn decode_uuid_body_ext<B: Buf>(buf: &mut B, ext: Self::Ext) -> Result<Self>; | ||
| fn encode_uuid_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<Self::Ext>; | ||
| } | ||
|
|
||
| impl<T: UuidAtomExt> UuidAtom for T { | ||
| const EXTENDED_TYPE: ExtendedType = Self::EXTENDED_TYPE_EXT; | ||
|
|
||
| // logic borrowed from ./atom_ext.rs | ||
| fn decode_uuid_body<B: Buf>(buf: &mut B) -> Result<Self> { | ||
| let ext = Ext::decode(u32::decode(buf)?)?; | ||
| UuidAtomExt::decode_uuid_body_ext(buf, ext) | ||
| } | ||
|
|
||
| fn encode_uuid_body<B: BufMut>(&self, buf: &mut B) -> Result<()> { | ||
| // Here's the magic, we reserve space for the version/flags first | ||
| let start = buf.len(); | ||
| 0u32.encode(buf)?; | ||
|
|
||
| // That way we can return them as part of the trait, avoiding boilerplate | ||
| let ext = self.encode_uuid_body_ext(buf)?; | ||
|
|
||
| // Go back and update the version/flags | ||
| let header = ext.encode()?; | ||
| buf.set_slice(start, &header.to_be_bytes()); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| // This can be encapuslated in a macro to allow new uuid boxes to be made | ||
| // without adding new match arms. In the same way as in ./any.rs | ||
| #[derive(Clone, Eq, PartialEq, Debug)] | ||
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | ||
| #[non_exhaustive] | ||
| pub enum Uuid { | ||
| C2pa(C2pa), | ||
| Unknown(ExtendedType, Vec<u8>), | ||
| } | ||
|
|
||
| impl Atom for Uuid { | ||
| const KIND: FourCC = FourCC::new(b"uuid"); | ||
|
|
||
| fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> { | ||
| let et = ExtendedType::decode(buf)?; | ||
|
|
||
| match et { | ||
| C2pa::EXTENDED_TYPE => Ok(Uuid::C2pa(C2pa::decode_uuid_body(buf)?)), | ||
| _ => { | ||
| let payload = Vec::<u8>::decode(buf)?; | ||
| Ok(Uuid::Unknown(et, payload)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> { | ||
| match self { | ||
| Uuid::C2pa(c2pa) => { | ||
| C2pa::EXTENDED_TYPE.encode(buf)?; | ||
| c2pa.encode_uuid_body(buf)?; | ||
| } | ||
| Uuid::Unknown(et, payload) => { | ||
| et.encode(buf)?; | ||
| payload.encode(buf)?; | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_c2pa_round_trip() -> Result<()> { | ||
| let original = Uuid::C2pa(C2pa { | ||
| box_purpose: "urn:uuid:...".to_string(), | ||
| data: vec![1, 2, 3, 4], | ||
| }); | ||
|
|
||
| // 1. Encode into a byte buffer | ||
| let mut buf = Vec::new(); | ||
| original.encode_body(&mut buf)?; | ||
|
|
||
| // 2. Decode back from the buffer | ||
| let mut cursor = &buf[..]; | ||
| let decoded = Uuid::decode_body(&mut cursor)?; | ||
|
|
||
| // 3. Assert equality | ||
| assert_eq!(original, decoded, "They werent equal"); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_c2pa_uuid_golden_vector() -> Result<()> { | ||
| let mut bytes = Vec::new(); | ||
|
|
||
| // 1. C2pa Extended Type bytes (16 bytes from the specification) | ||
| bytes.extend_from_slice(&[ | ||
| 0xD8, 0xFE, 0xC3, 0xD6, 0x1B, 0x0E, 0x48, 0x3C, 0x92, 0x97, 0x58, 0x28, 0x87, 0x7E, | ||
| 0xC4, 0x81, | ||
| ]); | ||
|
|
||
| // 2. FullBox version and flags (version = 0, flags = 0 -> 4 bytes of 0x00) | ||
| bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); | ||
|
|
||
| // 3. box_purpose string bytes (null-terminated UTF-8 string) | ||
| bytes.extend_from_slice(b"urn:uuid:test\0"); | ||
|
|
||
| // 4. Data payload bytes (0xDEADBEEF) | ||
| bytes.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); | ||
|
|
||
| let mut cursor = &bytes[..]; | ||
| let decoded = Uuid::decode_body(&mut cursor)?; | ||
|
|
||
| let expected = Uuid::C2pa(C2pa { | ||
| box_purpose: "urn:uuid:test".to_string(), | ||
| data: vec![0xDE, 0xAD, 0xBE, 0xEF], | ||
| }); | ||
|
|
||
| assert_eq!(decoded, expected); | ||
| Ok(()) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| use crate::*; | ||
|
|
||
| /// Represents the C2PA box/atom. | ||
| /// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#uuid_box | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | ||
| pub struct C2pa { | ||
| pub box_purpose: String, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| pub data: Vec<u8>, | ||
| } | ||
|
|
||
| impl UuidAtomExt for C2pa { | ||
| const EXTENDED_TYPE_EXT: ExtendedType = ExtendedType::new(&[ | ||
| 0xD8, 0xFE, 0xC3, 0xD6, 0x1B, 0x0E, 0x48, 0x3C, 0x92, 0x97, 0x58, 0x28, 0x87, 0x7E, 0xC4, | ||
| 0x81, | ||
| ]); | ||
|
|
||
| // The ext version is restricted to 0, and flags 0 for C2PA, so they are ignored | ||
| type Ext = (); | ||
|
|
||
| fn decode_uuid_body_ext<B: Buf>(buf: &mut B, _ext: ()) -> Result<Self> { | ||
| Ok(C2pa { | ||
| box_purpose: String::decode(buf)?, | ||
| data: Vec::decode(buf)?, | ||
| }) | ||
| } | ||
|
|
||
| fn encode_uuid_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<()> { | ||
| self.box_purpose.as_str().encode(buf)?; | ||
| self.data.encode(buf)?; | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| // Testing the encoding and decoding logic | ||
| #[test] | ||
| fn test_round_trip() -> Result<()> { | ||
| let input = C2pa { | ||
| box_purpose: String::from("uuid:test"), | ||
| data: vec![0xDE, 0xAD, 0xBE, 0xEF], | ||
| }; | ||
|
|
||
| let mut buf = Vec::new(); | ||
|
|
||
| C2pa::encode_uuid_body_ext(&input, &mut buf)?; | ||
|
|
||
| let mut cursor = &buf[..]; | ||
|
|
||
| let output = C2pa::decode_uuid_body_ext(&mut cursor, ())?; | ||
|
|
||
| assert_eq!(input, output); | ||
|
|
||
| Ok(()) | ||
|
bradh marked this conversation as resolved.
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| mod c2pa; | ||
| pub use c2pa::*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a lot of test data....
While I like the source (and your validation) I'm concerned that its significant overhead.
Can you look for another option, even if it has to be self-generated and run through a separate tool to make the C2PA bits?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah i can self-generate a smaller .mp4 file, and sign it with c2patool myself if that is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to use one of the examples we already have in https://github.com/kixelated/mp4-atom/tree/main/src/test