nostream dev Developers
Developer reference

Dot Hub JSON format

How to read and write Dot Hub animation files in your own app, and where they differ from the Glyph Museum format they are built on. Every field is specified in full below.

Base format
Glyph Museum v1 / v4
dotHubMeta
1.1
Encoding
UTF-8 JSON

Summary

A Dot Hub file is a Glyph Museum file with two extra root blocks. Anything that reads one reads the other. If you implement nothing else, implement these five things:

  1. Frames are the only required part. frames[].p holds LED brightness 0–255 in reading order; array length alone tells you the resolution.
  2. Never fail an import over metadata. Parse frames first, metadata second. A broken block is an absent block.
  3. Carry blocks you do not own through untouched. meta belongs to Glyph Museum and dotHubMeta to Dot Hub. Read them, write them back, never author them.
  4. tools is the block that is yours. Append one entry for your own app; that is where you record what you did.
  5. A stamp is not a credit. Only claim a user name on work your app actually created or edited.

Why support this format

Dot Hub is the most downloaded Glyph app, so its file format is the one designs already travel in. Supporting it is worth your while twice over:

  • Your work stays attributed to you. The tools chain travels with every file. Your app's name and your users' handles stay on a design wherever it ends up, which is how your tool gets discovered by the people who see the result.
  • Dot Hub users can actually use your app. A shared format means import and export just work — no converter, no re-drawing, no metadata lost on the way in or out.

Most downloaded based on AppBrain data for the search term “Glyph”, excluding pre-installed apps.

Chapter 1

Object model

One JSON object at the root. Five possible keys, of which one is required.

Structure at a glance

{
  "v":          number             // target matrix
  "frames":     [ { "p", "d" } ]   // required, ordered
  "meta":       { … }              // Glyph Museum's — carry it
  "dotHubMeta": { … }              // Dot Hub's — carry it
  "tools":      [ { … } ]          // yours — append yourself
}

A complete file, all blocks present

{
  "v": 4,
  "meta": { "author": "pauwma", "postId": 123 },
  "dotHubMeta": { "version": "1.1", "user": "paulg", "edited": true },
  "tools": [
    { "id": "app.glyphmuseum.com", "name": "Glyph Museum", "user": "pauwma", "created": true },
    { "id": "com.gesekus.dothub",  "name": "Dot Hub",      "user": "paulg",  "edited": true }
  ],
  "frames": [
    { "d": 100, "p": [0, 0, 80, 120, 255, …137 or 489 values] }
  ]
}
Chapter 2

Field reference

Every element of the format. The your app column is the short answer to what to do with each block when you save a file.

Root object

KeyTypeRequirementYour appNotes
vnumber optionalwrite 1 = 25×25, 4 = 13×13. Write it correctly; Dot Hub does not read it.
framesarray requiredwrite Ordered, non-empty. One frame is a static design, more is an animation.
metaobject optionalcarry Glyph Museum attribution. Absent on files never published there.
dotHubMetaobject optionalcarry Dot Hub's own stamp. Only Dot Hub writes it.
toolsarray optionalappend Chain of applications, oldest first. Omit the key entirely when empty.

frames[ ]

KeyTypeRequirementDefaultNotes
pnumber[] required Brightness per LED, 0–255, reading order. See chapter 3.
dnumber optional100 Frame duration in milliseconds. Only meaningful in animations.

meta { }

Defined and written by Glyph Museum. Read these fields to display credit; never author the block yourself.

KeyTypeRequirementNotes
authorstringoptional Creator's handle, without the @. May be absent when unresolvable.
postIdnumberoptional The stable pointer; prefer it over url. Must be a JSON number greater than 0 — "123" as a string is dropped, not coerced.
urlstringoptional Canonical post page. Only http and https are ever opened.

dotHubMeta { }

Written only by Dot Hub. Read it to know a file passed through Dot Hub and who worked on it there; carry it through unchanged when you re-save. Record your own involvement in tools instead.

KeyTypeRequirementNotes
versionstringoptional Version of this block, currently "1.1". Not the same thing as v. Dot Hub writes it on every save.
userstringoptional A handle the user typed in Dot Hub. Never an account, never an email. Present only alongside edited.
editedbooleanoptional true when the frames were created or modified inside Dot Hub.

tools[ ] { }

The block your app writes into. One entry per application, oldest first.

KeyTypeRequirementNotes
idstringrequired Your package name, or the host of your website. An entry without it is dropped.
namestringoptional Human-readable application name, for display.
userstringoptional The user's handle in your app. Write only alongside created or edited.
createdbooleanoptional true when the frames came into existence in your app.
editedbooleanoptional true when you modified existing frames. Neither flag = passed through.

Limits

LimitValueOn breach
Frames per file240Rejected, too_many_frames
Import file size4 MiBRejected before parsing
Entries in tools16Trimmed from the middle, never rejected
Distinct resolutions per file1Rejected, mixed_sizes
Chapter 3

The p array

The Glyph Matrix is a circle cut out of a square grid, so p holds only the LEDs that physically exist. Each row is centred inside the grid. Index 0 is the start of the top row; values continue left to right, then down.

137
values — real LEDs only
0
wasted positions

The form to write

13×13 rows, top to bottom: [5, 9, 11, 11, 13, 13, 13, 13, 13, 11, 11, 9, 5]. 25×25: [7, 11, 15, 17, 19, 21, 21, 23, 23, 25, 25, 25, 25, 25, 25, 25, 23, 23, 21, 21, 19, 17, 15, 11, 7].

LengthGridFormDeviceAccepted by
13713 × 13compactPhone (4a) Proboth
48925 × 25compactPhone (3)both
16913 × 13full squarePhone (4a) ProDot Hub only
62525 × 25full squarePhone (3)Dot Hub only

Walking a flat p array back onto the grid

let i = 0;
for (let row = 0; row < gridSize; row++) {
  const leds = rowPattern[row];
  const colStart = (gridSize - leds) / 2;   // rows are centred
  for (let col = 0; col < leds; col++) {
    draw(colStart + col, row, p[i++]);
  }
}

Writing p

Always write the compact form. Dot Hub accepts the full square grid on read as a convenience, but a Glyph Museum reader does not — 169 or 625 values will not open there.

Chapter 4

What your app needs to implement

Work down these until one stops describing your app. Each level assumes the ones above it.

Do you open files a user exported themselves?

Baseline · everyone

This is the whole requirement for reading. Parse frames, take p and d from each, infer the resolution from p.length, and render. Nothing else is needed and no permission is involved.

Add this

  • The defensive-reading rules in chapter 6, so a bad metadata block never blocks a good file.
  • The four accepted p lengths from chapter 3.

Do you show designs to anyone but their author?

Display · any shared or browsable view

Then you need to say where a design came from. Read meta.author for the original creator, and tools for the apps involved. Show the credit wherever the design appears.

Add this

  • A credit line from meta.author, falling back to the newest tools entry that has a user.
  • Link back to the post when meta.postId is present.
  • Show nothing rather than guessing when neither is available.

Do you support Glyph Museum designs?

Glyph Museum · designs published by the community

Letting people bring in designs published by the community — as opposed to files they exported themselves — puts you under Glyph Museum's conditions, written into their Terms of Service §5.4 and summarised on their developer page:

  • Credit the author. Show the handle wherever the design appears and link back to its post. The meta block hands you both.
  • Keep community designs free. No paywall on other people's work. Charging for your own tools and features is entirely your call.
  • No bulk collection. No scraping, mirroring or bundling the catalog. One design at a time, because a user asked for it. Removed posts must stay gone.
  • Publishing stays with them. There is no third-party publish path. Users can always export a file and bring it over themselves.

Add this to every file you write

// Read it, keep the original object, write it straight back.
// Including keys you do not recognise. Never build one yourself.
"meta": { "author": "pauwma", "postId": 123, "url": "…", …unknown keys… }

If url and postId disagree, trust postId. Once a file leaves Glyph Museum's own apps this block is the only thing holding credit to the work, and dropping it on a re-save is the one thing their format asks you not to do.

Do you save, edit or export files?

Writing · the point at which you join the chain

Then add yourself to tools. This is the block that exists for you: it is how a design records which apps shaped it, and it is the only place you should describe your own involvement.

Add this

"tools": [
  // …every entry that was already there, unknown keys intact…
  {
    "id":      "com.yourcompany.yourapp",   // package name, or your site's host
    "name":    "Your App",
    "user":    "their handle",              // only with created or edited
    "created": true                         // or "edited": true, or neither
  }
]
  • Carry meta and dotHubMeta through unchanged. Neither is yours to write, and dotHubMeta.edited would claim Dot Hub made your edit.
  • Set created when the frames originated in your app, edited when you changed existing ones, neither when you only re-saved.
  • Write user only alongside one of those flags.
  • Follow the merge rule in chapter 5 so repeated saves do not stack up entries.
Chapter 5

The tools chain

meta and dotHubMeta answer who made this. tools answers what it was made with, and is explicitly allowed to grow as a design travels between apps.

The merge rule

When you write a file, append an entry for yourself. If the newest entry is already your id with a compatible user — equal, or one of the two absent, in which case the absent one is filled in — merge your flags into it instead of appending.

SequenceResulting chainRationale
A, A, A1 entryRepeated saves in one app are one visit.
A, B, A3 entriesThe file genuinely went out and came back.
A(ann), A(—)1 entry, still annAn anonymous stamp does not fork the chain.
A(ann), A(bo)2 entriesA different person is a different link.

Rules

  • Order is oldest first. Append at the end.
  • Neither flag set means the file only passed through — opened, re-saved, exported, not changed. Still worth recording.
  • Cap at 16 entries. Drop from the middle: index 0 is always kept, and so are the newest.
  • Omit the key entirely when the chain is empty. Never write "tools": [].
  • Preserve unknown keys inside every entry you did not write. This is where other apps record things, so a re-save must not trim them.
  • Never back-fill. A file with a meta block but no tools does not get a synthesised Glyph Museum entry, obvious though the origin is. The chain holds only what apps wrote about themselves.
Chapter 6

Reading a file

Metadata must never get between a user and their file. These are the rules Dot Hub's own importer follows, taken from Glyph Museum's guidance.

  1. Parse frames first, metadata second. A broken metadata block can then never turn a good file into an error.
  2. Treat a block that is missing, malformed, or the wrong shape as absent.
  3. Validate field by field. Drop what does not fit, keep what does.
  4. Validate tools entry by entry. One bad entry is skipped; the rest of the chain survives.
  5. Treat an empty or whitespace-only string as absent.
  6. Ignore keys you do not recognise — and preserve them when writing.
  7. Infer resolution from p.length, not from v.

Rejection reasons

CodeCause
emptyInput was null or blank.
no_framesframes missing, unparseable, not an array, or empty.
bad_frameAn element is not an object, or has no p array.
bad_sizep length is not 137, 489, 169 or 625.
mixed_sizesFrames in one file resolve to different matrices.
too_many_framesMore than 240 frames.

Less information beats wrong information. A design with no author shown is fine; a design credited to the wrong person is not.

Chapter 7

Writing a file

A checklist for anything that produces one of these files.

  1. Write v from the matrix size — 25 → 1, 13 → 4 — even though Dot Hub ignores it. Other readers use it.
  2. Write p in the compact form, values clamped to 0–255.
  3. Write d per frame, or omit it to accept the 100 ms default.
  4. Copy meta and dotHubMeta through byte for byte, including keys you do not recognise. Do not create either.
  5. Append your own tools entry per chapter 5, carrying every existing entry through with its unknown keys intact.
  6. Only write a user where your app genuinely created or edited the frames.
  7. Omit optional keys rather than writing null or "".

Brightness scale

On disk brightness is always 0–255. Dot Hub scales to 0–4095 in memory by 16.0588; that factor is an implementation detail and never appears in a file.

Chapter 8

Differences from Glyph Museum

Everything that is not identical between the two formats, in one table. The base format is documented at glyphmuseum.com/developers.

ElementGlyph MuseumDot Hub
v, frames, p, d Defines them Identical — except v is written but never read
p length 489 or 137 only Also accepts 625 / 169; always writes compact
meta Owns and writes it Reads and re-emits verbatim; never creates it
dotHubMeta Unknown key, ignored Owns it; written on every save
tools Unknown key, ignored Defines the rules; any app may append
Unknown keys Ignore on read, keep on write Kept in meta and tools[]; rebuilt in dotHubMeta
Publishing Official apps only Does not publish or fetch
Document scope Specification and licence Specification only

Forward compatibility

Both formats grow by adding keys, never by removing or repurposing them. Ignore what you do not recognise instead of rejecting the file, never assume a metadata block is there, and carry through whatever you were handed. New keys in dotHubMeta raise its version; a new resolution arrives as a new v with its own row pattern.

Questions about the format, or shipped something that reads it? Write to contact@nostream.de — it goes straight to the developer.