diff --git a/.gitignore b/.gitignore index 0500d8a..a039e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ # Generated files .docusaurus .cache-loader + +# External files loaded by fetch-readmes script /docs/js-sdk/api /docs/js-sdk/examples/quickstart/*.ts /docs/js-sdk/examples/view-manifest/*.ts @@ -23,6 +25,7 @@ /docs/rust-sdk/*.md /docs/rust-sdk/docs/*.md /docs/**/readme.md +/static/sb-alg-list.json # Misc .DS_Store diff --git a/docs/soft-bindings.mdx b/docs/soft-bindings.mdx new file mode 100644 index 0000000..78693a2 --- /dev/null +++ b/docs/soft-bindings.mdx @@ -0,0 +1,23 @@ +--- +id: sb-algs +title: Watermarking and fingerprinting algorithms +hide_table_of_contents: true +--- + +[_Durable Content Credentials_](https://contentauthenticity.org/blog/durable-content-credentials) is a concept that helps content provenance to persist across content platforms by also: + +- **Invisible watermarks**, actively inserted into the content +- **Content fingerprinting**, passively computed from the content. + +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. +Combining both watermark and fingerprints further improves the robustness of the provenance information. + +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). + +:::note +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. +::: + +import JSONToTable from '@site/src/components/JSONToTable'; + + diff --git a/scripts/fetch-readme.js b/scripts/fetch-readme.js index 850add6..903baf2 100644 --- a/scripts/fetch-readme.js +++ b/scripts/fetch-readme.js @@ -189,6 +189,11 @@ const readmes = [ repo: 'contentauth/c2pa-min', path: 'README.md', }, + { + dest: resolve(__dirname, '../static/sb-alg-list.json'), + repo: 'c2pa-org/softbinding-algorithm-list', + path: 'softbinding-algorithm-list.json', + }, ]; function resolveMarkdownLinks(linkBase, content) { diff --git a/sidebars.js b/sidebars.js index 168ed59..e618ab0 100644 --- a/sidebars.js +++ b/sidebars.js @@ -259,8 +259,17 @@ const sidebars = { id: 'faqs', }, { - type: 'doc', - id: 'community-resources', + type: 'category', + label: 'Community resources', + link: { type: 'doc', id: 'community-resources' }, + collapsed: true, + items: [ + { + type: 'doc', + label: 'Watermarking and fingerprinting', + id: 'sb-algs', + }, + ], }, { type: 'doc', diff --git a/src/components/JSONToTable.js b/src/components/JSONToTable.js new file mode 100644 index 0000000..2e13ef7 --- /dev/null +++ b/src/components/JSONToTable.js @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from 'react'; + +// Define the fields to display and their custom headers +const DISPLAY_FIELDS = { + alg: 'Algorithm Name', + decodedMediaTypes: 'Media Types', + 'entryMetadata.informationalUrl': 'URL', + 'entryMetadata.contact': 'Contact', +}; + +// Helper function to safely extract nested values and flatten arrays +const getNestedValue = (obj, path) => { + const value = path + .split('.') + .reduce( + (acc, key) => (acc && acc[key] !== undefined ? acc[key] : 'N/A'), + obj, + ); + return Array.isArray(value) ? value.join(', ') : value; // Flatten arrays into a comma-separated string +}; + +// Function to extract and filter only the required fields +const extractAndFilterObject = (obj) => { + let result = {}; + for (const key in DISPLAY_FIELDS) { + result[key] = getNestedValue(obj, key); + } + return result; +}; + +// Helper function to capitalize each word in a string +const capitalizeWords = (str) => { + return str + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +}; + +const JSONToTable = () => { + const [jsonData, setJsonData] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + fetch('/sb-alg-list.json') + .then((response) => { + if (!response.ok) { + throw new Error('Failed to fetch the JSON file'); + } + return response.json(); + }) + .then((data) => { + setJsonData(data); + setError(null); + }) + .catch((err) => { + setError(err.message); + setJsonData(null); + }); + }, []); + + // Function to group data by the 'type' field + const groupDataByType = (data) => { + return data.reduce((acc, item) => { + const type = getNestedValue(item, 'type'); + if (!acc[type]) acc[type] = []; + acc[type].push(item); + return acc; + }, {}); + }; + + const generateTableHeaders = () => { + return Object.keys(DISPLAY_FIELDS).map((key) => ( + {DISPLAY_FIELDS[key]} + )); + }; + + const generateTableRows = (data) => { + return data.map((item, rowIndex) => { + const filteredData = extractAndFilterObject(item); + const url = filteredData['entryMetadata.informationalUrl']; + const description = getNestedValue(item, 'entryMetadata.description'); + + return ( + + {Object.keys(DISPLAY_FIELDS).map((key, index) => ( + + {key === 'entryMetadata.informationalUrl' ? ( + url !== 'N/A' && description !== 'N/A' ? ( + + {description} + + ) : ( + 'N/A' + ) + ) : ( + filteredData[key] + )} + + ))} + + ); + }); + }; + + if (error) return

{error}

; + + if (!jsonData || !Array.isArray(jsonData)) { + return

No data found in the JSON. Expected an array of objects.

; + } + + const groupedData = groupDataByType(jsonData); + + return ( +
+ {Object.entries(groupedData).map(([type, data]) => ( +
+

{capitalizeWords(type)} algorithms

+ + + {generateTableHeaders()} + + {generateTableRows(data)} +
+
+ ))} +
+ ); +}; + +export default JSONToTable;