1"use strict";(globalThis.webpackChunkopensource_contentauth_org=globalThis.webpackChunkopensource_contentauth_org||[]).push([[5984],{4888:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>d,default:()=>h,frontMatter:()=>r,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"manifest/writing/ingredients","title":"Writing ingredients","description":"Overview","source":"@site/docs/manifest/writing/ingredients.md","sourceDirName":"manifest/writing","slug":"/manifest/writing/ingredients","permalink":"/docs/manifest/writing/ingredients","draft":false,"unlisted":false,"editUrl":"https://github.com/contentauth/opensource.contentauth.org/edit/main/docs/manifest/writing/ingredients.md","tags":[],"version":"current","frontMatter":{"id":"ingredients","title":"Writing ingredients"},"sidebar":"docs","previous":{"title":"Building and writing manifest data","permalink":"/docs/manifest/writing/"},"next":{"title":"Writing assertions and actions","permalink":"/docs/manifest/writing/assertions-actions"}}');var s=i(4848),a=i(8453);const r={id:"ingredients",title:"Writing ingredients"},d=void 0,o={},c=[{value:"Overview",id:"overview",level:2},{value:"Ingredient objects",id:"ingredient-objects",level:2},{value:"Relationship",id:"relationship",level:3},{value:"Validation results",id:"validation-results",level:2},{value:"Linking actions and ingredients",id:"linking-actions-and-ingredients",level:2}];function l(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["For a video walkthrough of signing an edited image and preserving its provenance chain, see ",(0,s.jsx)(n.a,{href:"https://learn.contentauthenticity.org/signing-an-edited-image",children:"Signing an edited image and maintaining its provenance"})," from the Content Credentials Foundations course."]})}),"\n",(0,s.jsxs)(n.p,{children:["Digital assets are often not created entirely from scratch, but instead created from one or more existing assets, for example placing an image into a layer in Photoshop. Such constituent assets are called ",(0,s.jsx)(n.em,{children:"ingredients"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.a,{href:"/docs/manifest/reading/legacy-manifests",children:"Old manifests"})," may contain deprecated v1 and v2 ingredients, but applications should only write v3 ingredients."]}),"\n",(0,s.jsxs)(n.p,{children:["Applications should write only v3 ingredients, with label starting with ",(0,s.jsx)(n.code,{children:"c2pa.ingredient.v3"})," as described in the ",(0,s.jsx)(n.a,{href:"https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#ingredient_assertion",children:"C2PA specification"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"The API will only write v3 ingredients to a v2 claim. It will write v2 ingredients to a v1 claim and will read any of the three formats."}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["The C2PA Technical Specification describes ",(0,s.jsx)(n.em,{children:"ingredient assertions"})," but the CAI SDK treats ingredients separately as their own objects in the JSON manifest rather than as a type of assertion."]})}),"\n",(0,s.jsx)(n.h2,{id:"ingredient-objects",children:"Ingredient objects"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"ingredients"})," array contains an element for each ingredient used to create an asset. When an ingredient itself has Content Credentials, those manifests are included in the composed asset's manifest store to keep the provenance data intact."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"ingredients"})," array contains an ",(0,s.jsx)(n.a,{href:"/docs/manifest/json-ref/manifest-definition-schema#ingredient",children:"ingredient object"})," for each ingredient. The only required property of the ",(0,s.jsx)(n.code,{children:"ingredient"})," object is the ",(0,s.jsx)(n.code,{children:"title"})," property, which usually is the source file name."]}),"\n",(0,s.jsxs)(n.p,{children:["When reading an ingredient, ",(0,s.jsx)(n.code,{children:"label"})," property for the first ingredient in a manifest is ",(0,s.jsx)(n.code,{children:"c2pa.ingredient.v3"})," When there is more than one ingredient, subsequent labels have a monotonically increasing index: ",(0,s.jsx)(n.code,{children:"c2pa.ingredient.v3__1"}),", ",(0,s.jsx)(n.code,{children:"c2pa.ingredient.v3__2"}),", and so on. you can use your own labels when creating new ingredients, but those labels are only temporary and will be replaced."]}),"\n",(0,s.jsx)(n.p,{children:"Other important properties of the ingredient object include:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"format"}),": MIME type of the source file (optional)."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"document_id"})," (optional) and ",(0,s.jsx)(n.code,{children:"instance_id"})," (required) which are derived from the ingredient asset's XMP metadata."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"thumbnail"}),": Object with properties that identify the thumbnail image."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"active_manifest"}),": For an ingredient with a manifest store, the label of the active manifest."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"relationship"}),": One of ",(0,s.jsx)(n.code,{children:"parentOf"}),", ",(0,s.jsx)(n.code,{children:"componentOf"}),", or ",(0,s.jsx)(n.code,{children:"inputTo"}),". See ",(0,s.jsx)(n.a,{href:"#relationship",children:"Relationship"})," below."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For example:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'"ingredients": [\n {\n "title": "turkey.jpeg",\n "format": "image/jpeg",\n "instance_id": "xmp.iid:3250038a-22ca-459b-8392-de275f8b155c",\n "relationship": "parentOf",\n "label": "c2pa.ingredient.v3"\n },\n ...\n], \n'})}),"\n",(0,s.jsx)(n.h3,{id:"relationship",children:"Relationship"}),"\n",(0,s.jsxs)(n.p,{children:["The ingredient object's ",(0,s.jsx)(n.code,{children:"relationship"})," property describes its relationship to the current asset. This property can have one of three values, as de
1scribed in the table below."]}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsxs)(n.th,{children:["Value of ",(0,s.jsx)(n.code,{children:"relationship"})]}),(0,s.jsx)(n.th,{children:"Description"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"parentOf"})}),(0,s.jsx)(n.td,{children:"The current asset is a derived asset or asset rendition of this ingredient. This relationship value is also used with update manifests. There can be at most one parent ingredient in a manifest."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"componentOf"})}),(0,s.jsx)(n.td,{children:"This ingredient is one of the assets that composes the current asset."})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.code,{children:"inputTo"})}),(0,s.jsx)(n.td,{children:"This ingredient was used as input to a computational process, such as an AI/ML model, that led to the creation or modification of this asset."})]})]})]}),"\n",(0,s.jsxs)(n.p,{children:["Note that ",(0,s.jsx)(n.code,{children:"parentOf"})," ingredients must have a matching ",(0,s.jsx)(n.code,{children:"c2pa.opened"})," action as the first action in the manifest and ",(0,s.jsx)(n.code,{children:"componentOf"})," ingredients must have an associated ",(0,s.jsx)(n.code,{children:"c2pa.placed"})," action."]}),"\n",(0,s.jsx)(n.h2,{id:"validation-results",children:"Validation results"}),"\n",(0,s.jsx)(n.p,{children:"When ingredients are added, the SDK validates their Content Credentials (if any). However, the validation status of an ingredient does not imply anything about the validation status of the composed asset containing the ingredient. In other words:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"A composed asset's Content Credentials may be valid, but one or more of its ingredients may have invalid Content Credentials."}),"\n",(0,s.jsx)(n.li,{children:"A composed asset's Content Credentials may be invalid, but one or more of its ingredients may have valid Content Credentials."}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsx)(n.p,{children:"Ingredient certificates are validated when they are added to the manifest store, NOT during validation of the composed asset."})}),"\n",(0,s.jsx)(n.h2,{id:"linking-actions-and-ingredients",children:"Linking actions and ingredients"}),"\n",(0,s.jsxs)(n.p,{children:["To link an action and an ingredient, reuse the ingredient ID in the action's ",(0,s.jsx)(n.code,{children:"ingredientsId"})," array when building the manifest. The examples given here are for Python, but the same technique works in any language."]}),"\n",(0,s.jsx)(n.p,{children:"This example and others are in the Python library:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsxs)(n.a,{href:"https://github.com/contentauth/c2pa-python/blob/main/tests/test_unit_tests.py#L2927",children:["An ingredient with a ",(0,s.jsx)(n.code,{children:"c2pa.opened"})," action"]}),"."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsxs)(n.a,{href:"https://github.com/contentauth/c2pa-python/blob/main/tests/test_unit_tests.py#L3011",children:["An ingredient with one ",(0,s.jsx)(n.code,{children:"c2pa.opened"})," and one ",(0,s.jsx)(n.code,{children:"c2pa.placed"})," action"]}),"."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsxs)(n.a,{href:"https://github.com/contentauth/c2pa-python/blob/main/tests/test_unit_tests.py#L3117",children:["Multiple ingredients with ",(0,s.jsx)(n.code,{children:"c2pa.placed"})," action"]}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["First, get an ID for the ingredient; ",(0,s.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-python/blob/main/tests/test_unit_tests.py#L2934",children:"for example"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-python",children:'parent_ingredient_id = "xmp:iid:a965983b-36fb-445a-aa80-a2d911dcc53c"\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Use that ID when the manifest gets defined in an ",(0,s.jsx)(n.code,{children:"ingredientsId"})," array; ",(0,s.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-python/blob/main/tests/test_unit_tests.py#L2958",children:"for example"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'...\n"assertions": [\n {\n "label": "c2pa.actions.v2",\n "data": {\n "actions": [\n {\n "action": "c2pa.opened",\n "softwareAgent": {\n "name": "Tool XYZ",\n },\n "parameters": {\n "ingredientIds": [\n "xmp:iid:a965983b-36fb-445a-aa80-a2d911dcc53c"\n ]\n },\n },\n ...\n'})}
1),"\n",(0,s.jsx)(n.p,{children:"Then the SDK links the ingredient with the action."}),"\n",(0,s.jsxs)(n.p,{children:['This also works with an ingredient JSON with the "add ingredient" function; ',(0,s.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-python/blob/main/tests/test_unit_tests.py#L2971-L2985",children:"for example"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-python",children:'ingredient_json = {\n "relationship": "parentOf",\n "instance_id": parent_ingredient_id\n}\n# An opened ingredient is always a parent--exactly one parent ingredient is allowed.\n\n# Read the input file (A.jpg will be signed)\nwith open(self.testPath2, "rb") as test_file:\n file_content = test_file.read()\n\nbuilder = Builder.from_json(manifestDefinition)\n\n# Add C.jpg as the parent "opened" ingredient\nwith open(self.testPath, \'rb\') as f:\n builder.add_ingredient(ingredient_json, "image/jpeg", f)\n ...\n'})})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>r,x:()=>d});var t=i(6540);const s={},a=t.createContext(s);function r(e){const n=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),t.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.