Skip to main content

C4 universal content identification — SMPTE ST 2114:2017

Project description

c4py

CI Apache 2.0 Python 3.10+ Tests

Pure Python implementation of C4 universal content identification (SMPTE ST 2114:2017).

import c4py

# Verify a delivery matches the manifest
manifest = c4py.load("delivery-v3.c4m")
for path, entry in manifest.flat_entries():
    if entry.c4id and not c4py.verify(f"/deliveries/v3/{path}", entry.c4id):
        print(f"MISMATCH: {path}")

# Identify any file — same content always produces the same ID
c4id = c4py.identify_file("render.1001.exr")

# Compare two snapshots of a project
old = c4py.load("delivery-v2.c4m")
new = c4py.load("delivery-v3.c4m")
diff = c4py.diff(old, new)
print(f"+{len(diff.added)} -{len(diff.removed)} ~{len(diff.modified)}")

Install

pip install c4py

What is C4?

C4 IDs are universally unique, unforgeable identifiers derived from content using SHA-512. They are standardized as SMPTE ST 2114:2017. Same content always produces the same 90-character ID, regardless of filename, location, or time.

>>> import c4py
>>> c4py.identify_bytes(b"hello world")
C4ID('c41yP4cqy7jmaRDzC2bmcGNZkuQb3VdftMk6YH7ynQ2Qw4zktKsyA9fk52xghNQNAdkpF9iFmFkKh2bNVG4kDWhsok')

C4M Format

A c4m file is a human-readable text file that describes a filesystem. It captures file names, sizes, permissions, timestamps, and C4 IDs in a format you can read, edit, diff, and email.

-rw-r--r-- 2025-06-15T12:00:00Z      3 README.md   c45xZeXwMSpq...
drwxr-xr-x 2025-06-15T12:00:00Z      3 src/        -
  -rw-r--r-- 2025-06-15T12:00:00Z    3 main.go     c45KgBYEvEE7...

A 2 KB c4m file can describe an 8 TB project. Compare two c4m files to find exactly which frames changed across a delivery — in seconds, not hours.

API

Identification

# Identify a file on disk
c4id = c4py.identify_file("render.1001.exr")

# Verify a file matches an expected ID
assert c4py.verify("render.1001.exr", expected_id)

# From file-like object (streaming, constant memory)
c4id = c4py.identify(file_obj)

# From bytes
c4id = c4py.identify_bytes(b"data")

# Parse a C4 ID string (also works as C4ID constructor)
c4id = c4py.parse("c45xZeXwMSpq...")
c4id = c4py.C4ID("c45xZeXwMSpq...")  # same thing

# C4 ID properties
str(c4id)       # 90-character string
bytes(c4id)     # 64-byte digest
c4id.hex()      # hex digest
bool(c4id)      # False for nil ID, True otherwise

Tree IDs (Set Identity)

# Compute a single ID for a set of IDs (order-independent)
tree_id = c4py.tree_id([id_a, id_b, id_c])

C4M Files

# Parse
manifest = c4py.load("project.c4m")
manifest = c4py.loads(text)

# Write
c4py.dump(manifest, file_obj)
text = c4py.dumps(manifest)
text = c4py.dumps(manifest, pretty=True)

# Scan directory
manifest = c4py.scan("/path/to/dir")

# Iterate entries
for entry in manifest:
    print(entry.name, entry.size, entry.c4id)

Content Store

c4py shares the same content store as the c4 CLI and c4sh. Content stored by any tool is immediately available to the others.

# Open a store (auto-discovers from C4_STORE env or ~/.c4/config)
store = c4py.open_store()

# Or specify a path
store = c4py.open_store("/data/c4store")

# Store and retrieve content
c4id = store.put(open("render.exr", "rb"))
assert store.has(c4id)
content = store.get(c4id)

# Scan + store in one pass (zero extra I/O)
manifest = c4py.scan("/projects/HERO", store=store)

Diff and Patch Chains

# Compare two manifests
diff = c4py.diff(old_manifest, new_manifest)
diff.added      # entries only in new
diff.removed    # entries only in old
diff.modified   # same path, different content
diff.same       # identical entries

# Produce a c4m patch (like `c4 diff before.c4m after.c4m`)
patch_text = c4py.patch_diff(old_manifest, new_manifest)

# c4m files can contain version histories (base + patches)
# Resolve a chain to its final state (like `c4 patch project.c4m`)
final = c4py.resolve_chain(manifest)

# Enumerate patches (like `c4 log project.c4m`)
for info in c4py.log_chain(manifest):
    print(f"{info.index}  {info.c4id}  +{info.added} -{info.removed} ~{info.modified}")

Validation

result = c4py.validate(manifest)
for issue in result.errors:
    print(issue)

Works With

c4py is part of the C4 ecosystem:

  • c4 — Go CLI for identification and content storage (c4 id, c4 cat, c4 diff)
  • c4sh — Shell integration that makes c4m files behave as directories
  • c4git — Git clean/smudge filter for large media assets

All tools share the same content store and produce identical C4 IDs.

Compatibility

c4py produces byte-identical output to the Go reference implementation. Cross-language test vectors ensure this.

Zero external dependencies. Pure Python. Works offline.

Design Decisions

See the FAQ for design decisions including SHA-512 permanence, the c4m format, and content store scaling.

License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

c4py-1.0.16.tar.gz (97.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

c4py-1.0.16-py3-none-any.whl (61.1 kB view details)

Uploaded Python 3

File details

Details for the file c4py-1.0.16.tar.gz.

File metadata

  • Download URL: c4py-1.0.16.tar.gz
  • Upload date:
  • Size: 97.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.5

File hashes

Hashes for c4py-1.0.16.tar.gz
Algorithm Hash digest
SHA256 cc9efc1343cb063de834f03acb3fc6142ef0ed672b49e17e2d278f5894966245
MD5 3aae784a5da86f30c32977cc5f1a7b53
BLAKE2b-256 e3059ab34da7040446ae9cb1db3b0271644e8a14028593e5b88fcb4f57632381

See more details on using hashes here.

File details

Details for the file c4py-1.0.16-py3-none-any.whl.

File metadata

  • Download URL: c4py-1.0.16-py3-none-any.whl
  • Upload date:
  • Size: 61.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.5

File hashes

Hashes for c4py-1.0.16-py3-none-any.whl
Algorithm Hash digest
SHA256 ad5f710ad5ab8787b7fc6013e42634c3911c3f7fd583290a32c56cd4b6a841b9
MD5 7fc5f00e625f1335f2984c65283d2e5a
BLAKE2b-256 d33512f66368e733b2f310aca3c838be180377483a7e619fe131639ae34d0934

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page