Skip to content

Commit 18cc82a

Browse files
authored
docs: add docs on adding ingredient from archive (#54)
* docs: add docs on adding ingredient from archive
1 parent 2a42250 commit 18cc82a

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,125 @@ const ingredient = builder.addIngredientFromReader(sourceReader);
241241
console.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

246365
Builder archives allow you to save a builder's state (including ingredients) and reuse it later:

0 commit comments

Comments
 (0)