FOR DEVELOPERS

Add MPDF to your app

MPDF carries music information in standard PDF attachments. Implement the base format separately from optional extensions to add playback while retaining your existing PDF rendering.

Read the specification GitHub

The base format: mpdf.json

Read mpdf.json from the PDF EmbeddedFiles name tree. The MPDF 1.x base format describes tracks, page cues, and playback options. Embedded audio uses separate PDF attachments referenced with file:.

mpdf.json · base example
{
  "mpdf": "1.0",
  "title": "Practice score",
  "tracks": [
    { "id": "backing", "title": "Backing track", "src": "file:backing.m4a", "start": 10, "end": 70 }
  ],
  "cues": [ { "page": 1, "track": "backing", "at": 10 } ],
  "options": { "autoplay": false, "followPage": false, "loop": false }
}

Pages are one-based. start and end are seconds in the original recording; cue at uses the same absolute source-recording clock. In this example, at: 10 means second 10 in the original recording. Optional timeMarkers.seconds is relative to the track's start.

Optional extensions: read and preserve

DataPurpose
buttonsOn-page playback button positions and track references
timeMarkersPage positions and relative times within tracks
seetogether-mpdf.jsonSEETO production metadata, playback choices, creator claims, and sharing state
seetogether-score.jsonIncluded Copy Note score and chord playback data
Editable source and markup attachmentsCreator revision material, excluded from public sharing copies

These extensions are not required for base MPDF playback. Ignore unsupported fields when reading, but preserve them when making revisions. To download an unchanged original, return its original bytes without rewriting the PDF. A sharing copy is a separate, explicit export retaining only the data intended for recipients.

Read and play

JavaScript · pdf.js
const pdf = await pdfjsLib.getDocument({ data: bytes.slice() }).promise
const attachments = (await pdf.getAttachments()) || {}
const raw = attachments['mpdf.json']
if (raw) {
  const manifest = JSON.parse(new TextDecoder().decode(raw.content))
  // Validate version, size limits, unique IDs, page ranges and track references.
  // Render text with textContent, never as HTML. Keep unknown extension fields.
  // Resolve file: names against attachments; never treat them as filesystem paths.
  // Retain `bytes` for an unchanged download; rewriting changes the file hash.
}

Create a new MPDF

Python · pypdf
from pypdf import PdfWriter
import json

# Create a NEW MPDF from a plain PDF; do not overwrite the input.
w = PdfWriter(clone_from="score.pdf")
w.add_attachment("mpdf.json", json.dumps(manifest).encode("utf-8"))
w.add_attachment("backing.m4a", audio_bytes)
w.write("score-with-music.pdf")

This is a minimal example for producing a new file from a plain PDF. Revising existing MPDFs also requires handling name trees, unknown attachments, and extensions. The repository's JavaScript and Python base references are distinct from the website's compatibility layer in site/mpdf-browser.js.

A creator claim is not proof of ownership

SEETO creator revisions use a non-anonymous account and SHA-256 of the entire file. The immutable registry's file hash, publication ID, and owner UID must all match. Embedded creatorUID alone must never unlock editing. sharedReadOnly: true remains read-only for the creator as well. Register the finalized file before offering its registered creator original.

Public links are a separate service

This website's Cloudflare Pages Functions store sharing PDFs. They reject embedded audio, editable sources, and unsupported attachments, and stop serving links 90 days after creation. This hosting policy and SEETO account integration are not mandatory parts of the MPDF base format.

Code is MIT licensed; specification text is CC BY 4.0. Use GitHub Issues for technical proposals and support email for private material or removal requests.