@@ -241,6 +241,125 @@ const ingredient = builder.addIngredientFromReader(sourceReader);
241241console .log (ingredient .title ); // Contains ingredient metadata
242242```
243243
244+ #### Adding Ingredients from Archives (.c2pa files)
245+
246+ You can add ingredients from ` .c2pa ` archive files. Archives are binary files that contain a manifest store with ingredients and their associated resources (thumbnails, manifest data, etc.). To work with them, read the archive with ` Reader ` using the ` application/c2pa ` MIME type, then extract the ingredients and transfer their binary resources to a new ` Builder ` .
247+
248+ There are two types of archives sharing the same binary format:
249+
250+ - ** Builder archives** (working store archives): Serialized snapshots of a ` Builder ` , created by ` builder.toArchive() ` . They can contain multiple ingredients.
251+ - ** Ingredient archives** : Contain exactly one ingredient from a source asset. They carry the provenance history for reuse in other manifests.
252+
253+ ##### Reading an archive and adding its ingredients
254+
255+ ``` javascript
256+ import { Reader , Builder } from ' @contentauth/c2pa-node' ;
257+ import * as fs from ' node:fs/promises' ;
258+
259+ // Read the archive using the application/c2pa MIME type
260+ const archiveBuffer = await fs .readFile (' ingredients.c2pa' );
261+ const reader = await Reader .fromAsset (
262+ { buffer: archiveBuffer, mimeType: ' application/c2pa' },
263+ { verify: { verify_after_reading: false } }
264+ );
265+
266+ // Get the ingredients from the active manifest
267+ const activeManifest = reader .getActive ();
268+ const ingredients = activeManifest .ingredients ;
269+
270+ // Create a new builder with the ingredients from the archive
271+ const builder = Builder .withJson ({
272+ claim_generator_info: [{ name: ' my-app' , version: ' 1.0.0' }],
273+ ingredients: ingredients,
274+ });
275+
276+ // Transfer binary resources (thumbnails, manifest_data) for each ingredient
277+ for (const ingredient of ingredients) {
278+ if (ingredient .thumbnail ) {
279+ const resource = await reader .resourceToAsset (ingredient .thumbnail .identifier , { buffer: null });
280+ await builder .addResource (ingredient .thumbnail .identifier , {
281+ buffer: resource .buffer ,
282+ mimeType: ingredient .thumbnail .format ,
283+ });
284+ }
285+ if (ingredient .manifest_data ) {
286+ const resource = await reader .resourceToAsset (ingredient .manifest_data .identifier , { buffer: null });
287+ await builder .addResource (ingredient .manifest_data .identifier , {
288+ buffer: resource .buffer ,
289+ mimeType: ' application/c2pa' ,
290+ });
291+ }
292+ }
293+
294+ // Sign the manifest
295+ const signer = LocalSigner .newSigner (cert, key, ' es256' );
296+ builder .sign (signer, inputAsset, outputAsset);
297+ ```
298+
299+ ##### Selecting specific ingredients from an archive
300+
301+ When an archive contains multiple ingredients, you can filter to include only the ones you need:
302+
303+ ``` javascript
304+ const reader = await Reader .fromAsset (
305+ { buffer: archiveBuffer, mimeType: ' application/c2pa' },
306+ { verify: { verify_after_reading: false } }
307+ );
308+
309+ const activeManifest = reader .getActive ();
310+ const allIngredients = activeManifest .ingredients ;
311+
312+ // Select only the ingredients you want (e.g., by title or instance_id)
313+ const selected = allIngredients .filter (
314+ (ing ) => ing .title === ' photo_1.jpg' || ing .instance_id === ' catalog:logo'
315+ );
316+
317+ // Build with only the selected ingredients
318+ const builder = Builder .withJson ({
319+ claim_generator_info: [{ name: ' my-app' , version: ' 1.0.0' }],
320+ ingredients: selected,
321+ });
322+
323+ // Transfer resources only for selected ingredients
324+ for (const ingredient of selected) {
325+ if (ingredient .thumbnail ) {
326+ const resource = await reader .resourceToAsset (ingredient .thumbnail .identifier , { buffer: null });
327+ await builder .addResource (ingredient .thumbnail .identifier , {
328+ buffer: resource .buffer ,
329+ mimeType: ingredient .thumbnail .format ,
330+ });
331+ }
332+ if (ingredient .manifest_data ) {
333+ const resource = await reader .resourceToAsset (ingredient .manifest_data .identifier , { buffer: null });
334+ await builder .addResource (ingredient .manifest_data .identifier , {
335+ buffer: resource .buffer ,
336+ mimeType: ' application/c2pa' ,
337+ });
338+ }
339+ }
340+ ```
341+
342+ ##### Building an ingredient archive
343+
344+ To create an ingredient archive, add ingredients to a ` Builder ` and save it as an archive:
345+
346+ ``` javascript
347+ const builder = Builder .new ();
348+
349+ // Add ingredients with stable instance_id for later catalog lookups
350+ await builder .addIngredient (
351+ JSON .stringify ({
352+ title: ' photo-A.jpg' ,
353+ relationship: ' componentOf' ,
354+ instance_id: ' catalog:photo-A' ,
355+ }),
356+ { path: ' photo-A.jpg' }
357+ );
358+
359+ // Save as a .c2pa archive
360+ await builder .toArchive ({ path: ' ingredient-catalog.c2pa' });
361+ ```
362+
244363#### Creating and Reusing Builder Archives
245364
246365Builder archives allow you to save a builder's state (including ingredients) and reuse it later:
0 commit comments