openvibers/openvibe.media STATUS.json

STATUS.json

main 7.3 KB · 56 lines · json

1{
2 "repository": "OpenVibers/OpenVibe.Media",
3 "stage": "beta",
4 "code": true,
5 "plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
6 "runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9, ffmpeg",
7 "port": 4100,
8 "domain": "openvibe.media",
9 "contracts": "openvibe-contracts v0.423.0",
10 "sdk": "openvibe-sdk v0.161.0",
11 "shared": "openvibe-shared v4.235.0",
12 "capabilities": [
13 "media.object.upload",
14 "media.object.read",
15 "media.object.list",
16 "media.object.delete",
17 "media.upload.create",
18 "media.derivative.create",
19 "media.derivative.read",
20 "media.lifecycle.read",
21 "media.lifecycle.transition",
22 "media.object.review.view",
23 "media.object.review.decide"
24 ],
25 "capabilitiesRegistered": true,
26 "pastes": "OpenVibe.Community is the paste authority since 2026-09-22 (PASTES_MOVED_TO, default https://openvibe.community); Media's read-only paste API is deleted (T10 step 2: /api/v1/:app/pastes is unmounted) and /p/:slug and /p/:slug/raw are unconditional 301s there (no row read), so Media serves only the frozen rows' screenshot bytes",
27 "deployed": true,
28 "deployment": "Public at https://openvibe.media: deployed with `sudo ovhost deploy media` (restart refused while a VOD records; --wait-idle), /opt/openvibe.media, unit openvibe-media on 127.0.0.1:4100 behind the openvibe.media nginx vhost, principal media.",
29 "features": [
30 "VOD recording (RTMP pull, RTP ingest), chunk ingest and finalize",
31 "clip cutting",
32 "storage tiering local, Backblaze B2, Cloudflare R2 with copy verification and a disk guardian",
33 "canonical object model (/api/v2) with namespaces, holds, derivatives and lifecycle",
34 "media jobs with leases, retries and proposals",
35 "multi-tenant apps and developer-project tenants (sandbox separated)",
36 "public serving with presigned URLs; signed access for private and sandbox items",
37 "the object explorer (/me) and operator views",
38 "media.* events through the SDK outbox as the only outcome announcement",
39 "restore-drill mode and N-1 compatibility tests",
40 "per-actor limits (openvibe-sdk/limits)",
41 "placement demand rollups on Valkey (F2.4): reads per object × region × 5-minute bucket and, for a timeline segment, per segment too (object total is the sum over its segments; hotness over the hour, media.object.hot once per object per hour)",
42 "tiering on demand (F2.5): the object sweep promotes and demotes on the hour's Valkey reads against per-class hysteresis bands, one MGET per page, falling back to the daily PostgreSQL counts for a whole sweep when Valkey fails",
43 "placement constraints (F2.6): sweep moves refused while their provider fails its class (breaker or probe), and a per-class monthly R2 storage ceiling (maxHotUsdPerMonth) applied in value order, each explained in the decision's inputs",
44 "every storage class in the native-object sweep: kind and mime type classify video/image/download, an app may declare game-asset/attachment/backup (or any of the six) in the object's metadata (metadata.class, or media_class/placement_class), and each class spends its own per-sweep budget, hysteresis band and R2 storage ceiling",
45 "segment-native video (F3.1, behind MEDIA_HLS_ENABLED): the media_timeline index, the object.cmaf job (CMAF/fMP4 segments of a finished video on its own keyframes, sha256 and durability per row, idempotent; optional MEDIA_CMAF_TRANSCODE encodes incompatible streams as H.264/AAC up to MEDIA_CMAF_TRANSCODE_MAX_SECONDS, default 14400) and HLS playlists written from the timeline at /o/:id/master.m3u8 under the object's access checks",
46 "local timeline cache (F3): 10 GiB default budget and 8 GiB free-disk floor, least-recently-served eviction of verified durable copies in the hourly sweep, durable segment fallback, optional timeline_cache readiness and local bytes/evictions metrics; lazy CMAF backfill capped at 20 per hour while recording finalize is exempt",
47 "packing after finalize (F3.3, behind MEDIA_HLS_ENABLED): the object.pack job concatenates durable segments into ~60 s chunk objects verified by sha256, each media_timeline row naming its byte range; the segment route serves a packed segment as a ranged read of its chunk; the per-segment keys are deleted once the chunk is durable; segment providers come from the placement router; a finished object.cmaf queues object.pack by itself (dedupeActive: a rerun joins an active pack, a later cut queues a new one)",
48 "on-demand renditions (F4 slice 1, behind MEDIA_HLS_ENABLED + MEDIA_RENDITIONS): the rendition.create job transcodes one 720p H.264/AAC rung of a ready media object into media_timeline through the same content-addressed rows, placement-router durable provider and two-phase commit as object.cmaf, forcing the source timeline's segment boundaries as keyframes; a source already 720p or smaller is skipped and a held object is left alone. The master playlist lists a rendition only once its rows exist and queues the missing ones lazily from /o/:id/master.m3u8 and download?format=json (dedupeActive + an object/rendition idempotency key), and /o/:id/<rendition>/index.m3u8 plus /o/:id/<rendition>/<name> serve it",
49 "timeline sprites (F3.1, behind MEDIA_HLS_ENABLED): object.sprite cuts its sheet from the object's media_timeline rows — each sample time picks the row covering it and one frame is decoded from that row's bytes (this node's segment, or a packed row's byte range in its chunk), checked against the row's sha256 — with the same layout and metadata as the seek path, so a packed long VOD is never decoded end to end; a source without a timeline keeps one fast seek per frame",
50 "HLS discovery on the v1 shapes (F3.c, behind MEDIA_HLS_ENABLED): the VOD and clip answers (vodPublic/clipPublic — get, list, create, update) carry hls_url (and hls_expires_at when the object is private or sandbox), the same field and access rules as GET /o/:id/download?format=json, read through one shared discovery (server/objects/hls.js); an object without a timeline gets object.cmaf queued and the field appears once it ran, and a virtual clip plays its source's window without being cut a timeline of its own",
51 "materialized clips (F3.6, behind MEDIA_HLS_ENABLED + MEDIA_MATERIALIZED_CLIPS, off by default): clip.cut over a source timeline writes the clip's own media_timeline rows — the interior copies the source's segments (packed chunk ranges included), only the two window edges are re-encoded into fMP4 with their own inits (init-head.mp4/init-tail.mp4, negative seq, EXT-X-MAP + EXT-X-DISCONTINUITY in the playlist) — inserting them in one transaction that locks the named source rows FOR SHARE and aborts with a retryable error when one is gone or any location column moved (a pack), so a racing delete either waits and keeps the bytes or aborts the insert; the projection keeps virtual: true and gains materialized: true (readiness, playableSql and the verify job treat it as a virtual clip), sprites decode each edge with its own init, deleting the clip removes its rows and edge bytes while the source's bytes stay, and download?format=mp4 answers the playlist for a timeline-only clip instead of queueing a remux that cannot read its input",
52 "watch ids: every VOD and clip answers to an 11-character random id, resolved by GET /api/v1/:app/watch/:watch_id"
53 ],
54 "updated": "2026-11-13"
55}
56