Skip to content

Commit f8e6ae6

Browse files
authored
Merge pull request #192 from contentauth/sb-alg-list
Add page on c2pa approved watermarks and fingerprints
2 parents 9d35b29 + 6196d79 commit f8e6ae6

File tree

5 files changed

+172
-2
lines changed

5 files changed

+172
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
# Generated files
88
.docusaurus
99
.cache-loader
10+
11+
# External files loaded by fetch-readmes script
1012
/docs/js-sdk/api
1113
/docs/js-sdk/examples/quickstart/*.ts
1214
/docs/js-sdk/examples/view-manifest/*.ts
@@ -23,6 +25,7 @@
2325
/docs/rust-sdk/*.md
2426
/docs/rust-sdk/docs/*.md
2527
/docs/**/readme.md
28+
/static/sb-alg-list.json
2629

2730
# Misc
2831
.DS_Store

docs/soft-bindings.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
id: sb-algs
3+
title: Watermarking and fingerprinting algorithms
4+
hide_table_of_contents: true
5+
---
6+
7+
[_Durable Content Credentials_](https://contentauthenticity.org/blog/durable-content-credentials) is a concept that helps content provenance to persist across content platforms by also:
8+
9+
- **Invisible watermarks**, actively inserted into the content
10+
- **Content fingerprinting**, passively computed from the content.
11+
12+
Platforms might remove C2PA manifest data, if, for example, they use software that does not yet support the standard. By storing a copy of the manifest data in an online database, you can use a watermark or a fingerprint to find it again.
13+
Combining both watermark and fingerprints further improves the robustness of the provenance information.
14+
15+
In the C2PA specification, watermarks and content fingerprinting are known as [soft bindings](https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_soft_bindings), which can be used to find digital content, even if the underlying bits differ. The C2PA specification requires that soft bindings be generated using one of the [algorithms](https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_soft_binding_algorithm_list) approved by the C2PA Technical Working Group. The following table lists the algorithms from the [C2PA Soft Binding Algorithm List](https://github.com/c2pa-org/softbinding-algorithm-list).
16+
17+
:::note
18+
The table below is provided **for information only** and is created and automatically updated based on data from the C2PA [softbinding-algorithm-list](https://github.com/c2pa-org/softbinding-algorithm-list/blob/main/softbinding-algorithm-list.json) repository, which is the single authoritative source of the information.
19+
:::
20+
21+
import JSONToTable from '@site/src/components/JSONToTable';
22+
23+
<JSONToTable />

scripts/fetch-readme.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ const readmes = [
189189
repo: 'contentauth/c2pa-min',
190190
path: 'README.md',
191191
},
192+
{
193+
dest: resolve(__dirname, '../static/sb-alg-list.json'),
194+
repo: 'c2pa-org/softbinding-algorithm-list',
195+
path: 'softbinding-algorithm-list.json',
196+
},
192197
];
193198

194199
function resolveMarkdownLinks(linkBase, content) {

sidebars.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,17 @@ const sidebars = {
259259
id: 'faqs',
260260
},
261261
{
262-
type: 'doc',
263-
id: 'community-resources',
262+
type: 'category',
263+
label: 'Community resources',
264+
link: { type: 'doc', id: 'community-resources' },
265+
collapsed: true,
266+
items: [
267+
{
268+
type: 'doc',
269+
label: 'Watermarking and fingerprinting',
270+
id: 'sb-algs',
271+
},
272+
],
264273
},
265274
{
266275
type: 'doc',

src/components/JSONToTable.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React, { useState, useEffect } from 'react';
2+
3+
// Define the fields to display and their custom headers
4+
const DISPLAY_FIELDS = {
5+
alg: 'Algorithm Name',
6+
decodedMediaTypes: 'Media Types',
7+
'entryMetadata.informationalUrl': 'URL',
8+
'entryMetadata.contact': 'Contact',
9+
};
10+
11+
// Helper function to safely extract nested values and flatten arrays
12+
const getNestedValue = (obj, path) => {
13+
const value = path
14+
.split('.')
15+
.reduce(
16+
(acc, key) => (acc && acc[key] !== undefined ? acc[key] : 'N/A'),
17+
obj,
18+
);
19+
return Array.isArray(value) ? value.join(', ') : value; // Flatten arrays into a comma-separated string
20+
};
21+
22+
// Function to extract and filter only the required fields
23+
const extractAndFilterObject = (obj) => {
24+
let result = {};
25+
for (const key in DISPLAY_FIELDS) {
26+
result[key] = getNestedValue(obj, key);
27+
}
28+
return result;
29+
};
30+
31+
// Helper function to capitalize each word in a string
32+
const capitalizeWords = (str) => {
33+
return str
34+
.split(' ')
35+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
36+
.join(' ');
37+
};
38+
39+
const JSONToTable = () => {
40+
const [jsonData, setJsonData] = useState(null);
41+
const [error, setError] = useState(null);
42+
43+
useEffect(() => {
44+
fetch('/sb-alg-list.json')
45+
.then((response) => {
46+
if (!response.ok) {
47+
throw new Error('Failed to fetch the JSON file');
48+
}
49+
return response.json();
50+
})
51+
.then((data) => {
52+
setJsonData(data);
53+
setError(null);
54+
})
55+
.catch((err) => {
56+
setError(err.message);
57+
setJsonData(null);
58+
});
59+
}, []);
60+
61+
// Function to group data by the 'type' field
62+
const groupDataByType = (data) => {
63+
return data.reduce((acc, item) => {
64+
const type = getNestedValue(item, 'type');
65+
if (!acc[type]) acc[type] = [];
66+
acc[type].push(item);
67+
return acc;
68+
}, {});
69+
};
70+
71+
const generateTableHeaders = () => {
72+
return Object.keys(DISPLAY_FIELDS).map((key) => (
73+
<th key={key}>{DISPLAY_FIELDS[key]}</th>
74+
));
75+
};
76+
77+
const generateTableRows = (data) => {
78+
return data.map((item, rowIndex) => {
79+
const filteredData = extractAndFilterObject(item);
80+
const url = filteredData['entryMetadata.informationalUrl'];
81+
const description = getNestedValue(item, 'entryMetadata.description');
82+
83+
return (
84+
<tr key={rowIndex}>
85+
{Object.keys(DISPLAY_FIELDS).map((key, index) => (
86+
<td key={index}>
87+
{key === 'entryMetadata.informationalUrl' ? (
88+
url !== 'N/A' && description !== 'N/A' ? (
89+
<a href={url} target="_blank" rel="noopener noreferrer">
90+
{description}
91+
</a>
92+
) : (
93+
'N/A'
94+
)
95+
) : (
96+
filteredData[key]
97+
)}
98+
</td>
99+
))}
100+
</tr>
101+
);
102+
});
103+
};
104+
105+
if (error) return <p style={{ color: 'red' }}>{error}</p>;
106+
107+
if (!jsonData || !Array.isArray(jsonData)) {
108+
return <p>No data found in the JSON. Expected an array of objects.</p>;
109+
}
110+
111+
const groupedData = groupDataByType(jsonData);
112+
113+
return (
114+
<div>
115+
{Object.entries(groupedData).map(([type, data]) => (
116+
<div key={type} style={{ marginBottom: '30px' }}>
117+
<h2>{capitalizeWords(type)} algorithms</h2>
118+
<table style={{ borderCollapse: 'collapse' }}>
119+
<thead>
120+
<tr>{generateTableHeaders()}</tr>
121+
</thead>
122+
<tbody>{generateTableRows(data)}</tbody>
123+
</table>
124+
</div>
125+
))}
126+
</div>
127+
);
128+
};
129+
130+
export default JSONToTable;

0 commit comments

Comments
 (0)