Skip to content

.ovm Format Specification ​

The .ovm (Object Video Media) format is an open media standard designed for interactive, multilingual video on the web. It pairs hardware-accelerated video containers (MP4 and WebM) with zero-latency metadata demuxing via HTTP Range requests and dynamic 60 FPS temporal tracking overlays.


Architectural Overview ​

text
+-------------------------------------------------------------------------+
| Layer +1: Dynamic Interactive Video Overlay                             |
| - Rendered synchronously over video canvas in Shadow DOM (<ovm-player>) |
| - Multilingual subtitles, dynamic hotspot buttons, interactive cards    |
| - Continuous 60 FPS temporal tracking keyframe interpolation            |
+-------------------------------------------------------------------------+
| Layer 0: Separator (symbolic, stores nothing)                           |
+-------------------------------------------------------------------------+
| Layer −1: Baked Video Frames                                            |
| - Standard MP4 (H.264/HEVC/AV1) or WebM (VP9/AV1)                       |
| - Zero JS decoding overhead: the browser handles buffering and decoding |
| - Instantaneous playback: no waiting for the full video download        |
+-------------------------------------------------------------------------+

Traditional interactive video implementations either require custom WebAssembly demuxers, proprietary video containers, or separate sidecar downloads that break when videos are mirrored or shared. .ovm solves this by packaging the metadata directly inside the video container while enabling instantaneous streaming.


Zero-Latency Demuxing Protocol (HTTP Range Requests) ​

To initiate playback immediately without waiting for megabytes of video data to download, the <ovm-player> client executes a 3-tier progressive inspection strategy:

text
[Fetch video.ovm]
       |
       v
Tier 1: Head Range Probe (Range: bytes=0-262143)
       |
       +---> "OVM_Metadata" signature found?
             |-- YES --> Extract Base64 -> Trim trailing bytes -> Decompress raw Deflate
             |-- NO  --> Proceed to Tier 2
       |
       v
Tier 2: Tail Range Probe (Range: bytes=-65536)
       |
       +---> "ovm_version" or "slots" found in last 64 KB?
             |-- YES --> Parse balanced JSON via bracket-depth scanner
             |-- NO  --> Proceed to Tier 3
       |
       v
Tier 3: Sidecar Fallback (Fetch video.ovm.json)
       |
       +---> Server lacks HTTP 206 support -> Fetch external companion JSON

Tier 1: Head Range Request (256 KB) ​

The player dispatches an initial probe:

http
GET /media/intro.ovm HTTP/1.1
Range: bytes=0-262143
  1. Searches the first 262,144 bytes for the ASCII signature OVM_Metadata (or fallback metadata.ovmdata).
  2. Extracts the continuous Base64 payload matching regex /[A-Za-z0-9+/=]{100,}/.
  3. Iteratively strips trailing container padding bytes and decompresses via DecompressionStream('deflate-raw').

Tier 2: Tail Range Request (64 KB) ​

If the encoder placed custom user metadata boxes at the file end:

http
GET /media/intro.ovm HTTP/1.1
Range: bytes=-65536
  1. Scans the last 65,536 bytes for opening brace { followed by ovm_version or slots.
  2. Extracts the balanced JSON string using an in-memory bracket-depth parser that respects string literals and escape characters.

Tier 3: Sidecar Fallback ​

If the hosting server returns HTTP 200 OK ignoring the Range header (lacking partial content support), the player issues a graceful fallback request for ${src}.json.


60 FPS Temporal Tracking Interpolation ​

Interactive elements in .ovm track moving objects on screen using quadrilateral bounding box keyframes stored in slot.tracking_data:

typescript
// Keyframe format: [t, x1, y1, x2, y2, x3, y3, x4, y4]
tracking_data: [
  [0.00, 0.20, 0.30, 0.40, 0.30, 0.40, 0.50, 0.20, 0.50],
  [1.50, 0.35, 0.32, 0.55, 0.32, 0.55, 0.52, 0.35, 0.52]
]

Where:

  • t: Timestamp in seconds.
  • (x1,y1) through (x4,y4): Normalized polygon coordinates ($0.0 \le x, y \le 1.0$) representing the 4 corners of the tracked object quad.

Linear Interpolation (LERP) Algorithm ​

During playback on every animation frame:

  1. Identify bounding keyframes where $K_{\text{before}}[0] \le t$ and $K_{\text{after}}[0] > t$.
  2. Calculate normalized interpolation progress: $$p = \operatorname{clamp}\left(\frac{t - t_0}{t_1 - t_0}, 0, 1\right)$$
  3. Linearly interpolate all 8 polygon coordinates: $$\operatorname{interp}[i] = K_{\text{before}}[i] + \left(K_{\text{after}}[i] - K_{\text{before}}[i]\right) \times p$$
  4. Compute the center position: $$\operatorname{left} = \left(\frac{x_1 + x_3}{2}\right) \times 100%$$ $$\operatorname{top} = \left(\frac{y_1 + y_3}{2}\right) \times 100%$$
  5. Position slot element with transform: translate(-50%, -50%).

Authoritative Zod Schema Reference ​

typescript
import { z } from "zod";
import { slotSchema, zoneSchema } from "@oim-media/core";

export const OvmMetadataSchema = z.object({
  ovm_version: z.string().optional(),                // Format version ("1.0")
  version: z.string().optional(),
  ovm_id: z.string().max(128).optional(),            // Global document UUID
  image_hash: z.string().max(64).optional(),          // SHA-256 hash of video track
  immutable_lock: z.boolean().optional(),            // Edit lock flag
  metadata: z.object({
    fps: z.number().min(1).max(120).optional(),      // Native video frame rate
    resolution: z.tuple([z.number(), z.number()]).optional(), // [Width, Height]
  }).optional(),
  zones: z.array(zoneSchema).optional(),             // Bounding box zones
  slots: z.array(slotSchema),                        // Interactive tracking slots
  signature: z.string().optional(),                  // ECDSA P-256 signature
  key_version: z.string().optional(),
  timestamp: z.string().datetime({ offset: true }).optional(),
}).passthrough();

Canonical JSON Contract Example ​

json
{
  "ovm_version": "1.0",
  "ovm_id": "ovm_98a7b6c5d4e3",
  "metadata": {
    "fps": 60,
    "resolution": [1920, 1080]
  },
  "slots": [
    {
      "id": "speaker_label",
      "name": "Presenter Tag",
      "type": "text",
      "content": {
        "en": "Dr. Elena Vance — Lead Researcher",
        "es": "Dra. Elena Vance — Investigadora Principal"
      },
      "style": {
        "color": "#ffffff",
        "fontSize": "16px",
        "font": "Inter"
      },
      "tracking_data": [
        [0.0, 0.40, 0.20, 0.60, 0.20, 0.60, 0.35, 0.40, 0.35],
        [2.5, 0.45, 0.22, 0.65, 0.22, 0.65, 0.37, 0.45, 0.37],
        [5.0, 0.50, 0.25, 0.70, 0.25, 0.70, 0.40, 0.50, 0.40]
      ]
    }
  ],
  "signature": "MEUCIQ...64_byte_raw_signature...",
  "key_version": "v1",
  "timestamp": "2026-09-03T03:10:00.000000+00:00"
}

Reference Implementation: <ovm-player> Demuxer Core ​

javascript
async function extractOvmMetadata(src) {
  // 1. Head Range Probe (First 256 KB)
  try {
    const resp = await fetch(src, { headers: { Range: "bytes=0-262143" } });
    if (resp.ok || resp.status === 206) {
      const buffer = await resp.arrayBuffer();
      const text = new TextDecoder().decode(new Uint8Array(buffer));
      let idx = text.indexOf("OVM_Metadata");
      if (idx === -1) idx = text.indexOf("metadata.ovmdata");

      if (idx !== -1) {
        const match = text.substring(idx).match(/[A-Za-z0-9+/=]{100,}/);
        if (match) {
          const b64 = match[0];
          for (let i = b64.length; i >= 10; i--) {
            let cand = b64.substring(0, i);
            if (cand.length % 4 !== 0) cand += "=".repeat(4 - (cand.length % 4));
            try {
              const bytes = Uint8Array.from(atob(cand), (c) => c.charCodeAt(0));
              const ds = new DecompressionStream("deflate-raw");
              const writer = ds.writable.getWriter();
              writer.write(bytes);
              writer.close();
              const plain = await new Response(ds.readable).text();
              const parsed = JSON.parse(plain);
              if (parsed.slots || parsed.elements) return parsed;
            } catch (_) {}
          }
        }
      }
    }
  } catch (_) {}

  // 2. Tail Range Probe Fallback (Last 64 KB)
  try {
    const resp = await fetch(src, { headers: { Range: "bytes=-65536" } });
    if (resp.ok || resp.status === 206) {
      const text = await resp.text();
      for (let i = 0; i < text.length; i++) {
        if (text[i] === "{" && (text.includes("ovm_version") || text.includes("slots"))) {
          let depth = 0, inStr = false;
          for (let j = i; j < text.length; j++) {
            if (text[j] === '"' && text[j - 1] !== "\\") inStr = !inStr;
            if (!inStr) {
              if (text[j] === "{") depth++;
              else if (text[j] === "}" && --depth === 0) {
                return JSON.parse(text.substring(i, j + 1));
              }
            }
          }
        }
      }
    }
  } catch (_) {}

  // 3. Sidecar Fallback
  const sidecar = await fetch(src + ".json");
  if (sidecar.ok) return await sidecar.json();

  throw new Error("No OVM metadata found");
}