├── .gitignore ├── CHANGELOG.md ├── CNAME ├── README.md ├── docs └── logo │ ├── og-logo-colour.svg │ ├── og-logo-mono-black.svg │ ├── og-logo-mono-white.svg │ ├── ograf-logo-app.svg │ ├── ograf-logo-colour.svg │ ├── ograf-logo-mono-black.svg │ └── ograf-logo-mono-white.svg └── v1-draft-0 ├── examples ├── .gitignore ├── README.md ├── l3rd-name │ ├── README.md │ ├── graphic.mjs │ ├── lib │ │ ├── CSSPlugin.js │ │ ├── TextPlugin.js │ │ ├── gsap-core.js │ │ ├── gsap.min.js │ │ ├── ograf-logo-app.svg │ │ └── utils │ │ │ └── strings.js │ └── manifest.json ├── minimal │ ├── README.md │ ├── graphic.mjs │ └── manifest.json ├── ograf-logo │ ├── README.md │ ├── graphic.mjs │ ├── lib │ │ └── ograf-logo-app.svg │ └── manifest.json └── renderer-test │ ├── README.md │ ├── graphic.mjs │ └── manifest.json ├── specification ├── docs │ ├── Specification.md │ └── images │ │ ├── components.svg │ │ ├── ograf.excalidraw │ │ └── step-model.svg └── json-schemas │ ├── graphics │ └── schema.json │ └── lib │ ├── action.json │ └── constraints │ ├── number.json │ └── string.json └── typescript-definitions ├── .gitignore ├── .npmignore ├── README.md ├── package-lock.json ├── package.json ├── scripts ├── generate-types.js └── local-testing.js ├── src ├── apis │ └── graphicsAPI.ts ├── definitions │ ├── render.ts │ ├── types.ts │ └── vendor.ts ├── generated │ └── graphics-manifest.ts └── main.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | __old_from_tmp_repo/ 2 | 3 | # scripts used in local testing 4 | local-scripts/ 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This log lists changes between versions. 4 | 5 | 6 | 7 | 8 | ## Version 1 - Draft 0 9 | 10 | This is a draft for Version 1. During its development, it may contain breaking changes. 11 | 12 | The log below details the changes during development of this version: 13 | 14 | * 2025-03-27: Draft 0 made public. 15 | * 2025-04-23: Fix in JSON-schemas: Changed `main` property in Graphics manifest to be **mandatory**. 16 | (Before, it was defined as mandatory in the specification document, but not in the JSON-schemas.) 17 | * 2025-05-16: Add `renderRequirements` property to Graphics manifest 18 | * 2025-05-16: Add `data` argument to the `load` method. 19 | Before, the `data`-payload was only sent using the `updateData()` method. Now it must be sent on `load()` as well. 20 | * 2025-05-16: Change return values of Graphic methods to optionally be `undefined`. 21 | (An `undefined` value should be treated as `{ code: 200 }`) 22 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | ograf.ebu.io -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OGraf 2 | 3 | 4 | 5 | **OGraf** is an upcoming Open specification for HTML based Graphics, used in live television and post production workflows. 6 | 7 | * [Link to Project Web Page](https://ograf.ebu.io) 8 | * [Link to Project Github repository ](https://github.com/ebu/ograf) 9 | 10 | ## Project status 11 | 12 | _**The OGraf specification is currently a draft**, breaking changes may be introduced before version 1 is finalized._ 13 | 14 | EBU members as well as the general industry is invited to join the [HTML Graphics Working Group](https://tech.ebu.ch/groups/html_graphics) to participate in discussions and development of the OGraf specification. 15 | 16 | Feedback can also be submitted using [GitHub Issues](https://github.com/ebu/ograf/issues). 17 | 18 | Graphics and Render system developers are encouraged to follow changes in the [Changelog](./CHANGELOG.md). 19 | 20 | ### Time plan 21 | 22 | - April 2025: 23 | - First public draft to be presented, industry feedback.
24 | _This draft includes definitions for **Graphics**._ 25 | - September 2025: 26 | - Version 1 to be finalized.
27 | This version includes definitions for **Graphics**. 28 | - First draft of **Server API** to be presented. 29 | - Late 2025(?): 30 | - Version 1 to be extended with **Server API** definitions. 31 | 32 | ## Introduction 33 | 34 | An OGraf graphic that follows the OGraf specification can be used in any OGraf compatible Rendering system and controlled by any OGraf compatible Control system. 35 | 36 | ## Getting Started 37 | 38 | Useful resources: 39 | * [Examples of OGraf Graphics](https://github.com/ebu/ograf/tree/main/v1-draft-0/examples). 40 | * [OGraf Specification](./v1-draft-0/specification/docs/Specification.md). 41 | 42 | 43 | ### Tools 44 | 45 | * The **[OGraf Devtool](https://github.com/SuperFlyTV/ograf-devtool)** is a tool for developing OGraf graphics. 46 | * The **[Simple Rendering system](https://github.com/SuperFlyTV/ograf-server)** can be used to play OGraf Graphics in a browser (for use in any existing system capable of rendering HTML graphics). 47 | -------------------------------------------------------------------------------- /docs/logo/og-logo-colour.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/logo/og-logo-mono-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/logo/og-logo-mono-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/logo/ograf-logo-app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/logo/ograf-logo-colour.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/logo/ograf-logo-mono-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/logo/ograf-logo-mono-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /v1-draft-0/examples/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.zip 2 | -------------------------------------------------------------------------------- /v1-draft-0/examples/README.md: -------------------------------------------------------------------------------- 1 | # OGraf Examples 2 | 3 | [Click here to download this folder as a zip file](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Febu%2Fograf%2Ftree%2Fmain%2Fv1-draft-0%2Fexamples) 4 | -------------------------------------------------------------------------------- /v1-draft-0/examples/l3rd-name/README.md: -------------------------------------------------------------------------------- 1 | # Reference: Lower 3rd - Name 2 | 3 | This is a simple lower 3rd that displays a name. 4 | -------------------------------------------------------------------------------- /v1-draft-0/examples/l3rd-name/graphic.mjs: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Note: 5 | * 6 | * @typedef { import('../../typescript-definitions/src/main').GraphicsAPI.Graphic } Graphic 7 | */ 8 | 9 | /** @implements { Graphic } */ 10 | class MyGraphic extends HTMLElement { 11 | constructor() { 12 | super(); 13 | this.nonRealTimeState = {}; 14 | this.displayState = {} 15 | } 16 | connectedCallback() { 17 | // Called when the element is added to the DOM 18 | // Note: Don't paint any pixels at this point, wait for load() to be called 19 | } 20 | 21 | async load(loadParams) { 22 | // 1. Load resources 23 | // 2. Setup the DOM 24 | // 3. Initialize the GSAP timeline 25 | 26 | 27 | // Load the GSAP scripts --------------------------------------------------- 28 | const importsPromises = { 29 | gsap: import(import.meta.resolve("./lib/gsap-core.js")), 30 | CSSPlugin: import(import.meta.resolve("./lib/CSSPlugin.js")), 31 | TextPlugin: import(import.meta.resolve("./lib/TextPlugin.js")), 32 | }; 33 | 34 | this.g = await importsPromises.gsap; 35 | this.g.gsap.registerPlugin((await importsPromises.CSSPlugin).CSSPlugin); 36 | this.g.gsap.registerPlugin((await importsPromises.TextPlugin).TextPlugin); 37 | 38 | // Setup our DOM ----------------------------------------------------------- 39 | const container = document.createElement("div"); 40 | this.appendChild(container); 41 | 42 | container.style.position = "absolute"; 43 | container.style.left = "calc(10% + 60px)"; 44 | container.style.bottom = "calc(10% + 30px)"; 45 | container.style.height = "32px"; 46 | 47 | container.style.padding = "6px 20px"; 48 | container.style.backgroundColor = "#f00"; 49 | container.style.color = "#fff"; 50 | container.style.fontFamily = "Roboto, sans-serif"; 51 | container.style.fontSize = "28px"; 52 | container.style.fontWeight = "bold"; 53 | container.style.zIndex = "2"; 54 | container.style.borderBottomRightRadius = "20px"; 55 | container.style.borderTopRightRadius = "20px"; 56 | container.style.borderTopLeftRadius = "20px"; 57 | 58 | const nameText = document.createElement("div"); 59 | nameText.innerText = ""; 60 | container.appendChild(nameText); 61 | 62 | const container2 = document.createElement("div"); 63 | this.appendChild(container2); 64 | 65 | container2.style.position = "absolute"; 66 | container2.style.left = "calc(10% + 60px)"; 67 | container2.style.bottom = "10%"; 68 | container2.style.height = "23px"; 69 | container2.style.padding = "5px 20px 2px 20px"; 70 | container2.style.backgroundColor = "#b00"; 71 | container2.style.color = "#fff"; 72 | container2.style.fontFamily = "Roboto, sans-serif"; 73 | container2.style.fontSize = "18px"; 74 | container2.style.fontWeight = "bold"; 75 | container2.style.zIndex = "1"; 76 | container2.style.borderBottomRightRadius = "10px"; 77 | container2.style.borderBottomLeftRadius = "10px"; 78 | 79 | const nameText2 = document.createElement("div"); 80 | nameText2.innerText = ""; 81 | container2.appendChild(nameText2); 82 | 83 | const logo = document.createElement("img"); 84 | logo.src = await this._loadImage(import.meta.resolve("./lib/ograf-logo-app.svg")) 85 | logo.style.position = "absolute"; 86 | logo.style.left = `calc(10%)` 87 | logo.style.bottom = `calc(10% + 10px)` 88 | logo.style.width = "50px"; 89 | logo.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)"; 90 | logo.style.borderRadius = "5px"; 91 | this.appendChild(logo); 92 | 93 | 94 | 95 | this.elements = { 96 | container, 97 | nameText, 98 | container2, 99 | nameText2, 100 | logo 101 | }; 102 | 103 | // Initialize the GSAP timeline -------------------------------------------- 104 | this.timeline = this.g.gsap.timeline(); 105 | this._resetTimeline(); 106 | 107 | if (loadParams.renderType === "realtime") { 108 | this.timeline.play(); // not really needed, it's playing by default 109 | } else if (loadParams.renderType === "non-realtime") { 110 | this.timeline.pause(); 111 | } else throw new Error("Unsupported renderType: " + loadParams.renderType); 112 | 113 | // Load initial data: 114 | this.initialData = loadParams.data 115 | await this._doAction("updateAction", { 116 | data: this.initialData || {}, 117 | skipAnimation: true 118 | }); 119 | 120 | // When everything is loaded we can return: 121 | return; 122 | } 123 | async dispose(_params) { 124 | this.innerHTML = ""; 125 | this.g = null; 126 | } 127 | async getStatus(_params) { 128 | return {}; 129 | } 130 | async updateAction(params) { 131 | // params.data 132 | // console.log("params", params); 133 | 134 | await this._doAction("updateAction", params); 135 | } 136 | async playAction(params) { 137 | // params.delta 138 | // params.goto 139 | // params.skipAnimation 140 | 141 | await this._doAction("playAction", params); 142 | } 143 | async stopAction(params) { 144 | // params.skipAnimation 145 | 146 | await this._doAction("stopAction", params); 147 | } 148 | async customAction(params) { 149 | // params.id 150 | // params.payload 151 | 152 | await this._doAction('customAction', params); 153 | } 154 | async _doAction(type, params) { 155 | const timeline = this.g.gsap.timeline(); 156 | 157 | // Retrieve the timeline for the action: 158 | // Add the tweens to the timeline, so that they'll animate: 159 | const actionTimeline = this._getActionAnimation(type, params); 160 | 161 | timeline.add(actionTimeline, 0); 162 | 163 | // Wait for timeline to finish animating: 164 | await timeline.then(); 165 | } 166 | async goToTime(payload) { 167 | this.nonRealTimeState.timestamp = payload.timestamp; 168 | 169 | await this._updateState(); 170 | } 171 | async setActionsSchedule(payload) { 172 | this.nonRealTimeState.schedule = payload.schedule; 173 | 174 | await this._updateState(); 175 | } 176 | async _updateState() { 177 | this._resetTimeline(); 178 | 179 | // Initial state: 180 | 181 | // Load initial data: 182 | await this._doAction("updateAction", { 183 | data: this.initialData || {}, 184 | skipAnimation: true 185 | }); 186 | 187 | for (const event of (this.nonRealTimeState.schedule || [])) { 188 | const eventTimeline = this._getActionAnimation( 189 | event.action.type, 190 | event.action.params 191 | ) 192 | 193 | this.timeline.add(eventTimeline, event.timestamp / 1000); 194 | } 195 | 196 | this.timeline.seek(this.nonRealTimeState.timestamp / 1000); 197 | } 198 | _getActionAnimation(type, params) { 199 | let tweens = [] 200 | 201 | if (type === "updateAction") { 202 | tweens = this._getUpdateAnimation(params); 203 | } else if (type === "playAction") { 204 | tweens = this._getPlayAnimation(params); 205 | } else if (type === "stopAction") { 206 | tweens = this._getStopAnimation(params); 207 | } else if (type === "customAction") { 208 | if (params.id === 'highlight') { 209 | tweens = this._getHighlightAnimation(params.payload); 210 | } else { 211 | throw new Error(`Unknown customAction id "${params.id}"`); 212 | } 213 | } else { 214 | throw new Error(`Unknown action type "${type}"`); 215 | } 216 | 217 | const actionTimeline = this.g.gsap.timeline(); 218 | 219 | for (const tween of tweens) { 220 | actionTimeline.add(tween, 0); 221 | } 222 | 223 | if (params.skipAnimation) { 224 | actionTimeline.timeScale(999999) 225 | } 226 | } 227 | _resetTimeline() { 228 | const gsap = this.g.gsap; 229 | 230 | this.displayState = {} 231 | 232 | // Clear the timeline: 233 | this.timeline.clear(); 234 | 235 | // Initial animation state: 236 | const tweens = [ 237 | gsap.set(this.elements.container, { 238 | x: -30, 239 | backgroundColor: "#f00", 240 | color: "#fff", 241 | opacity: 0, 242 | }), 243 | gsap.set(this.elements.nameText, { 244 | text: "", 245 | }), 246 | gsap.set(this.elements.container2, { 247 | y: -20, 248 | opacity: 0, 249 | }), 250 | gsap.set(this.elements.nameText2, { 251 | text: "", 252 | }), 253 | gsap.set(this.elements.logo, { 254 | scale: 0.5, 255 | opacity: 0 256 | }), 257 | ]; 258 | 259 | // Add Tweens to the beginning of the timeline: 260 | for (const tween of tweens) { 261 | this.timeline.add(tween, 0); 262 | } 263 | } 264 | 265 | // -------------------- Actions -------------------- 266 | _getUpdateAnimation(params) { 267 | const gsap = this.g.gsap; 268 | 269 | 270 | this.displayState.data = params.data || {} 271 | 272 | 273 | 274 | const showTitle = this.displayState.data.title 275 | const isPlaying = this.displayState.isPlaying 276 | 277 | return [ 278 | gsap.to(this.elements.nameText, { 279 | duration: 0.4, 280 | text: this.displayState.data.name, 281 | }), 282 | gsap.to(this.elements.nameText2, { 283 | duration: 0.4, 284 | text: this.displayState.data.title, 285 | }), 286 | ( 287 | isPlaying && 288 | gsap.to(this.elements.container2, { 289 | duration: 0.3, 290 | y: 0, 291 | opacity: showTitle ? 1 : 0, 292 | ease: "power2.out", 293 | }) 294 | ), 295 | ( 296 | isPlaying && 297 | gsap.to(this.elements.container, { 298 | duration: 0.3, 299 | borderBottomLeftRadius: showTitle ? "0" : "20px", 300 | ease: "power2.out", 301 | }) 302 | ) 303 | ]; 304 | } 305 | _getPlayAnimation(params) { 306 | const gsap = this.g.gsap; 307 | 308 | this.displayState.isPlaying = true 309 | 310 | const showTitle = Boolean(this.displayState.data.title) 311 | 312 | 313 | return [ 314 | gsap.to(this.elements.container, { 315 | duration: 0.8, 316 | x: 0, 317 | opacity: 1, 318 | borderBottomLeftRadius: showTitle ? "0" : "20px", 319 | ease: "power2.out", 320 | }), 321 | 322 | gsap.to(this.elements.container2, { 323 | delay: 0.3, 324 | duration: 0.8, 325 | y: 0, 326 | opacity: showTitle ? 1 : 0, 327 | ease: "power2.out", 328 | }), 329 | gsap.to(this.elements.nameText2, { 330 | delay: 0.7, 331 | duration: 0.8, 332 | }), 333 | gsap.to(this.elements.logo, { 334 | duration: 0.5, 335 | opacity: 1, 336 | }), 337 | gsap.to(this.elements.logo, { 338 | duration: 1.5, 339 | rotation: 360, 340 | scale: 1 341 | }), 342 | ]; 343 | } 344 | _getStopAnimation(params) { 345 | const gsap = this.g.gsap; 346 | 347 | this.displayState.isPlaying = false 348 | 349 | return [ 350 | gsap.to(this.elements.container, { 351 | delay: 0.3, 352 | duration: 0.8, 353 | x: -30, 354 | opacity: 0, 355 | ease: "power2.in", 356 | }), 357 | gsap.to(this.elements.container2, { 358 | duration: 0.5, 359 | y: -20, 360 | opacity: 0, 361 | ease: "power2.in", 362 | }), 363 | gsap.to(this.elements.logo, { 364 | duration: 0.8, 365 | rotation: 180, 366 | scale: 0.8, 367 | opacity: 0 368 | }), 369 | ]; 370 | } 371 | _getHighlightAnimation(params) { 372 | const gsap = this.g.gsap; 373 | 374 | return [ 375 | gsap.to(this.elements.container, { 376 | duration: 0.8, 377 | backgroundColor: "#ff0", 378 | color: "#000", 379 | 380 | ease: "power2.in", 381 | }), 382 | gsap 383 | .to(this.elements.container, { 384 | delay: 2, 385 | duration: 0.8, 386 | backgroundColor: "#f00", 387 | color: "#fff", 388 | 389 | ease: "power2.in", 390 | }), 391 | gsap.to(this.elements.logo, { 392 | duration: 1, 393 | scale: 1.5, 394 | ease: "bounce.out", 395 | }), 396 | gsap.to(this.elements.logo, { 397 | delay: 1, 398 | duration: 1, 399 | scale: 1, 400 | }), 401 | 402 | ]; 403 | } 404 | _loadImage(url) { 405 | return new Promise((resolve, reject) => { 406 | const newImg = new Image; 407 | newImg.onload = function() { 408 | resolve(this.src) 409 | } 410 | newImg.onerror = reject; 411 | newImg.src = url 412 | }) 413 | } 414 | } 415 | 416 | export default MyGraphic; 417 | 418 | // Note: The renderer will render the component 419 | -------------------------------------------------------------------------------- /v1-draft-0/examples/l3rd-name/lib/TextPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * TextPlugin 3.12.5 3 | * https://gsap.com 4 | * 5 | * @license Copyright 2008-2024, GreenSock. All rights reserved. 6 | * Subject to the terms at https://gsap.com/standard-license or for 7 | * Club GSAP members, the agreement issued with that membership. 8 | * @author: Jack Doyle, jack@greensock.com 9 | */ 10 | 11 | /* eslint-disable */ 12 | import { emojiSafeSplit, getText, splitInnerHTML } from "./utils/strings.js"; 13 | 14 | var gsap, 15 | _tempDiv, 16 | _getGSAP = function _getGSAP() { 17 | return gsap || typeof window !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap; 18 | }; 19 | 20 | export var TextPlugin = { 21 | version: "3.12.5", 22 | name: "text", 23 | init: function init(target, value, tween) { 24 | typeof value !== "object" && (value = { 25 | value: value 26 | }); 27 | 28 | var i = target.nodeName.toUpperCase(), 29 | data = this, 30 | _value = value, 31 | newClass = _value.newClass, 32 | oldClass = _value.oldClass, 33 | preserveSpaces = _value.preserveSpaces, 34 | rtl = _value.rtl, 35 | delimiter = data.delimiter = value.delimiter || "", 36 | fillChar = data.fillChar = value.fillChar || (value.padSpace ? " " : ""), 37 | _short, 38 | text, 39 | original, 40 | j, 41 | condensedText, 42 | condensedOriginal, 43 | aggregate, 44 | s; 45 | 46 | data.svg = target.getBBox && (i === "TEXT" || i === "TSPAN"); 47 | 48 | if (!("innerHTML" in target) && !data.svg) { 49 | return false; 50 | } 51 | 52 | data.target = target; 53 | 54 | if (!("value" in value)) { 55 | data.text = data.original = [""]; 56 | return; 57 | } 58 | 59 | original = splitInnerHTML(target, delimiter, false, preserveSpaces); 60 | _tempDiv || (_tempDiv = document.createElement("div")); 61 | _tempDiv.innerHTML = value.value; 62 | text = splitInnerHTML(_tempDiv, delimiter, false, preserveSpaces); 63 | data.from = tween._from; 64 | 65 | if ((data.from || rtl) && !(rtl && data.from)) { 66 | // right-to-left or "from()" tweens should invert things (but if it's BOTH .from() and rtl, inverting twice equals not inverting at all :) 67 | i = original; 68 | original = text; 69 | text = i; 70 | } 71 | 72 | data.hasClass = !!(newClass || oldClass); 73 | data.newClass = rtl ? oldClass : newClass; 74 | data.oldClass = rtl ? newClass : oldClass; 75 | i = original.length - text.length; 76 | _short = i < 0 ? original : text; 77 | 78 | if (i < 0) { 79 | i = -i; 80 | } 81 | 82 | while (--i > -1) { 83 | _short.push(fillChar); 84 | } 85 | 86 | if (value.type === "diff") { 87 | j = 0; 88 | condensedText = []; 89 | condensedOriginal = []; 90 | aggregate = ""; 91 | 92 | for (i = 0; i < text.length; i++) { 93 | s = text[i]; 94 | 95 | if (s === original[i]) { 96 | aggregate += s; 97 | } else { 98 | condensedText[j] = aggregate + s; 99 | condensedOriginal[j++] = aggregate + original[i]; 100 | aggregate = ""; 101 | } 102 | } 103 | 104 | text = condensedText; 105 | original = condensedOriginal; 106 | 107 | if (aggregate) { 108 | text.push(aggregate); 109 | original.push(aggregate); 110 | } 111 | } 112 | 113 | value.speed && tween.duration(Math.min(0.05 / value.speed * _short.length, value.maxDuration || 9999)); 114 | data.rtl = rtl; 115 | data.original = original; 116 | data.text = text; 117 | 118 | data._props.push("text"); 119 | }, 120 | render: function render(ratio, data) { 121 | if (ratio > 1) { 122 | ratio = 1; 123 | } else if (ratio < 0) { 124 | ratio = 0; 125 | } 126 | 127 | if (data.from) { 128 | ratio = 1 - ratio; 129 | } 130 | 131 | var text = data.text, 132 | hasClass = data.hasClass, 133 | newClass = data.newClass, 134 | oldClass = data.oldClass, 135 | delimiter = data.delimiter, 136 | target = data.target, 137 | fillChar = data.fillChar, 138 | original = data.original, 139 | rtl = data.rtl, 140 | l = text.length, 141 | i = (rtl ? 1 - ratio : ratio) * l + 0.5 | 0, 142 | applyNew, 143 | applyOld, 144 | str; 145 | 146 | if (hasClass && ratio) { 147 | applyNew = newClass && i; 148 | applyOld = oldClass && i !== l; 149 | str = (applyNew ? "" : "") + text.slice(0, i).join(delimiter) + (applyNew ? "" : "") + (applyOld ? "" : "") + delimiter + original.slice(i).join(delimiter) + (applyOld ? "" : ""); 150 | } else { 151 | str = text.slice(0, i).join(delimiter) + delimiter + original.slice(i).join(delimiter); 152 | } 153 | 154 | if (data.svg) { 155 | //SVG text elements don't have an "innerHTML" in Microsoft browsers. 156 | target.textContent = str; 157 | } else { 158 | target.innerHTML = fillChar === " " && ~str.indexOf(" ") ? str.split(" ").join("  ") : str; 159 | } 160 | } 161 | }; 162 | TextPlugin.splitInnerHTML = splitInnerHTML; 163 | TextPlugin.emojiSafeSplit = emojiSafeSplit; 164 | TextPlugin.getText = getText; 165 | _getGSAP() && gsap.registerPlugin(TextPlugin); 166 | export { TextPlugin as default }; -------------------------------------------------------------------------------- /v1-draft-0/examples/l3rd-name/lib/ograf-logo-app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /v1-draft-0/examples/l3rd-name/lib/utils/strings.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * strings: 3.12.5 3 | * https://gsap.com 4 | * 5 | * Copyright 2008-2024, GreenSock. All rights reserved. 6 | * Subject to the terms at https://gsap.com/standard-license or for 7 | * Club GSAP members, the agreement issued with that membership. 8 | * @author: Jack Doyle, jack@greensock.com 9 | */ 10 | 11 | /* eslint-disable */ 12 | var _trimExp = /(?:^\s+|\s+$)/g; 13 | export var emojiExp = /([\uD800-\uDBFF][\uDC00-\uDFFF](?:[\u200D\uFE0F][\uD800-\uDBFF][\uDC00-\uDFFF]){2,}|\uD83D\uDC69(?:\u200D(?:(?:\uD83D\uDC69\u200D)?\uD83D\uDC67|(?:\uD83D\uDC69\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]\uFE0F|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC6F\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3C-\uDD3E\uDDD6-\uDDDF])\u200D[\u2640\u2642]\uFE0F|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F\u200D[\u2640\u2642]|(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642])\uFE0F|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\uD83D\uDC69\u200D[\u2695\u2696\u2708]|\uD83D\uDC68(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708]))\uFE0F|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83D\uDC69\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|\uD83D\uDC68(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]))|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDD1-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\u200D(?:(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F)/; 14 | export function getText(e) { 15 | var type = e.nodeType, 16 | result = ""; 17 | 18 | if (type === 1 || type === 9 || type === 11) { 19 | if (typeof e.textContent === "string") { 20 | return e.textContent; 21 | } else { 22 | for (e = e.firstChild; e; e = e.nextSibling) { 23 | result += getText(e); 24 | } 25 | } 26 | } else if (type === 3 || type === 4) { 27 | return e.nodeValue; 28 | } 29 | 30 | return result; 31 | } 32 | export function splitInnerHTML(element, delimiter, trim, preserveSpaces) { 33 | var node = element.firstChild, 34 | result = [], 35 | s; 36 | 37 | while (node) { 38 | if (node.nodeType === 3) { 39 | s = (node.nodeValue + "").replace(/^\n+/g, ""); 40 | 41 | if (!preserveSpaces) { 42 | s = s.replace(/\s+/g, " "); 43 | } 44 | 45 | result.push.apply(result, emojiSafeSplit(s, delimiter, trim, preserveSpaces)); 46 | } else if ((node.nodeName + "").toLowerCase() === "br") { 47 | result[result.length - 1] += "
"; 48 | } else { 49 | result.push(node.outerHTML); 50 | } 51 | 52 | node = node.nextSibling; 53 | } 54 | 55 | s = result.length; 56 | 57 | while (s--) { 58 | result[s] === "&" && result.splice(s, 1, "&"); 59 | } 60 | 61 | return result; 62 | } 63 | /* 64 | //smaller kb version that only handles the simpler emoji's, which is often perfectly adequate. 65 | 66 | let _emoji = "[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2694-\u2697]|\uD83E[\uDD10-\uDD5D]|[\uD800-\uDBFF][\uDC00-\uDFFF]", 67 | _emojiExp = new RegExp(_emoji), 68 | _emojiAndCharsExp = new RegExp(_emoji + "|.", "g"), 69 | _emojiSafeSplit = (text, delimiter, trim) => { 70 | if (trim) { 71 | text = text.replace(_trimExp, ""); 72 | } 73 | return ((delimiter === "" || !delimiter) && _emojiExp.test(text)) ? text.match(_emojiAndCharsExp) : text.split(delimiter || ""); 74 | }; 75 | */ 76 | 77 | export function emojiSafeSplit(text, delimiter, trim, preserveSpaces) { 78 | text += ""; // make sure it's cast as a string. Someone may pass in a number. 79 | 80 | trim && (text = text.trim ? text.trim() : text.replace(_trimExp, "")); // IE9 and earlier compatibility 81 | 82 | if (delimiter && delimiter !== "") { 83 | return text.replace(/>/g, ">").replace(/= 0xD800 && character.charCodeAt(0) <= 0xDBFF || text.charCodeAt(i + 1) >= 0xFE00 && text.charCodeAt(i + 1) <= 0xFE0F) { 96 | //special emoji characters use 2 or 4 unicode characters that we must keep together. 97 | j = ((text.substr(i, 12).split(emojiExp) || [])[1] || "").length || 2; 98 | character = text.substr(i, j); 99 | result.emoji = 1; 100 | i += j - 1; 101 | } 102 | 103 | result.push(character === ">" ? ">" : character === "<" ? "<" : preserveSpaces && character === " " && (text.charAt(i - 1) === " " || text.charAt(i + 1) === " ") ? " " : character); 104 | } 105 | 106 | return result; 107 | } -------------------------------------------------------------------------------- /v1-draft-0/examples/l3rd-name/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json", 3 | "name": "Lower 3rd - Name", 4 | "description": "Name lower third", 5 | "id": "l3rd-name", 6 | "version": "0", 7 | "author": { 8 | "name": "Johan Nyman, SuperFly.tv" 9 | }, 10 | "customActions": [ 11 | { 12 | "id": "highlight", 13 | "name": "Highlight", 14 | "description": "Highlight the name", 15 | "schema": null 16 | } 17 | ], 18 | "supportsRealTime": true, 19 | "supportsNonRealTime": true, 20 | "schema": { 21 | "type": "object", 22 | "properties": { 23 | "name": { 24 | "type": "string", 25 | "title": "Name", 26 | "default": "John Doe" 27 | }, 28 | "title": { 29 | "type": "string", 30 | "title": "Title", 31 | "default": "Ograf expert" 32 | } 33 | } 34 | }, 35 | "v_myLittleNote": "Vendor-specific properties can be added using the 'v_' prefix, like this!" 36 | } 37 | -------------------------------------------------------------------------------- /v1-draft-0/examples/minimal/README.md: -------------------------------------------------------------------------------- 1 | # Reference: Minimal Graphic 2 | 3 | This is a minimal implementation of a Graphic. 4 | It contains the minimum required properties and methods to be a valid Graphic. 5 | 6 | _Note: This is intended to be used when developing and validating renderer systems._ 7 | _If you're looking for a good starting point for developing Graphics, use the [L3rd Name example](../l3rd-name/) instead._ 8 | -------------------------------------------------------------------------------- /v1-draft-0/examples/minimal/graphic.mjs: -------------------------------------------------------------------------------- 1 | class Graphic extends HTMLElement { 2 | connectedCallback() { 3 | // Called when the element is added to the DOM 4 | // Note: Don't paint any pixels at this point, wait for load() to be called 5 | } 6 | 7 | async load(params) { 8 | if (params.renderType !== "realtime") 9 | throw new Error("Only realtime rendering is supported by this graphic"); 10 | 11 | const elText = document.createElement("p"); 12 | elText.style.backgroundColor = "#ffffff"; 13 | elText.style.color = "#000000"; 14 | elText.style.display = "inline-block"; 15 | elText.style.padding = "10px"; 16 | elText.style.border = "1px solid #000000"; 17 | elText.innerHTML = "Hello world!"; 18 | this.appendChild(elText); 19 | 20 | // When everything is loaded we can return: 21 | return { 22 | statusCode: 200, 23 | }; 24 | } 25 | async dispose(_params) { 26 | this.innerHTML = ""; 27 | } 28 | async getStatus(_params) { 29 | return { 30 | statusCode: 200, 31 | status: { 32 | // nothing 33 | }, 34 | }; 35 | } 36 | async updateAction(_params) { 37 | // No actions are implemented in this minimal example 38 | } 39 | async playAction(_params) { 40 | // No actions are implemented in this minimal example 41 | } 42 | async stopAction(_params) { 43 | // No actions are implemented in this minimal example 44 | } 45 | async customAction(params) { 46 | // No actions are implemented in this minimal example 47 | } 48 | } 49 | 50 | export default Graphic; 51 | 52 | // Note: The renderer will render the component 53 | -------------------------------------------------------------------------------- /v1-draft-0/examples/minimal/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json", 3 | "name": "Minimal Test Graphic", 4 | "description": "This Graphic includes the bare minimum required to be a valid OGraf Graphic. It displays a 'Hello World!' message.", 5 | "id": "minimal-example", 6 | "supportsRealTime": true, 7 | "supportsNonRealTime": false 8 | } 9 | -------------------------------------------------------------------------------- /v1-draft-0/examples/ograf-logo/README.md: -------------------------------------------------------------------------------- 1 | # Reference: Minimal Graphic 2 | 3 | This is a version of minimal implementation of a Graphic. 4 | It fades IN and OUT a image-file. 5 | It contains the minimum required properties and methods to be a valid Graphic. 6 | 7 | _Note: This is intended to be used when developing and validating renderer systems._ 8 | _If you're looking for a good starting point for developing Graphics, use the [L3rd Name example](../l3rd-name/) instead._ 9 | -------------------------------------------------------------------------------- /v1-draft-0/examples/ograf-logo/graphic.mjs: -------------------------------------------------------------------------------- 1 | class Graphic extends HTMLElement { 2 | connectedCallback() { 3 | // Called when the element is added to the DOM 4 | // Note: Don't paint any pixels at this point, wait for load() to be called 5 | } 6 | 7 | async load(params) { 8 | if (params.renderType !== "realtime") 9 | throw new Error("Only realtime rendering is supported by this graphic"); 10 | 11 | const logo = document.createElement("img"); 12 | logo.src = await this._loadImage( 13 | import.meta.resolve("./lib/ograf-logo-app.svg") 14 | ); 15 | logo.style.opacity = 0; 16 | logo.style.position = "absolute"; 17 | logo.style.left = `calc(5%)`; 18 | logo.style.bottom = `calc(85%)`; 19 | logo.style.width = "50px"; 20 | logo.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)"; 21 | logo.style.borderRadius = "5px"; 22 | this.appendChild(logo); 23 | 24 | 25 | this.elements = { 26 | logo, 27 | }; 28 | 29 | // When everything is loaded we can return: 30 | return { 31 | statusCode: 200, 32 | }; 33 | } 34 | async dispose(_params) { 35 | this.innerHTML = ""; 36 | } 37 | async getStatus(_params) { 38 | return { 39 | statusCode: 200, 40 | status: { 41 | // nothing 42 | }, 43 | }; 44 | } 45 | async updateAction(_params) { 46 | // No updateActions are implemented in this example 47 | } 48 | async playAction(_params) { 49 | // Fade in logo 50 | this.elements.logo.style.transition = "opacity 1s"; 51 | this.elements.logo.style.opacity = "1"; 52 | } 53 | async stopAction(_params) { 54 | // fade out logo 55 | this.elements.logo.style.transition = "opacity 1s"; 56 | this.elements.logo.style.opacity = "0"; 57 | } 58 | async customAction(params) { 59 | // No customAction are implemented in this example 60 | } 61 | async goToTime(_payload) { 62 | throw new Error("Non-realtime not supported!"); 63 | } 64 | async setActionsSchedule(_payload) { 65 | throw new Error("Non-realtime not supported!"); 66 | } 67 | _loadImage(url) { 68 | return new Promise((resolve, reject) => { 69 | const newImg = new Image(); 70 | newImg.onload = function () { 71 | resolve(this.src); 72 | }; 73 | newImg.onerror = reject; 74 | newImg.src = url; 75 | }); 76 | } 77 | } 78 | 79 | export default Graphic; 80 | 81 | // Note: The renderer will render the component 82 | -------------------------------------------------------------------------------- /v1-draft-0/examples/ograf-logo/lib/ograf-logo-app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /v1-draft-0/examples/ograf-logo/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json", 3 | "name": "Minimal Test Graphics", 4 | "description": "This Graphic shows an OGraf logo", 5 | "id": "minimal-logo", 6 | "supportsRealTime": true, 7 | "supportsNonRealTime": false, 8 | "author": { 9 | "name": "Markus Nygård, YLE" 10 | }, 11 | "renderRequirements": [ 12 | { 13 | "resolution": { 14 | "width": { "min": 600, "max": 4000, "ideal": 1920 } 15 | }, 16 | "frameRate": { "min": 1, "max": 240, "ideal": 60 } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /v1-draft-0/examples/renderer-test/README.md: -------------------------------------------------------------------------------- 1 | # Renderer Test Graphic 2 | 3 | This Graphic presents a number of steps for the renderer to perform. 4 | 5 | It intended to be used to test the functionality of a renderer. 6 | -------------------------------------------------------------------------------- /v1-draft-0/examples/renderer-test/graphic.mjs: -------------------------------------------------------------------------------- 1 | class Graphic extends HTMLElement { 2 | connectedCallback() { 3 | // Called when the element is added to the DOM 4 | // Note: Don't paint any pixels at this point, wait for load() to be called 5 | } 6 | 7 | async load(params) { 8 | this.style.fontSize = "24px"; 9 | 10 | // Note: the OGraf specification says that the load() method should not paint any pixels. 11 | // This directive is ignored in this test graphic. 12 | 13 | const infoMessageContainer = document.createElement("div"); 14 | infoMessageContainer.style.color = "#000"; 15 | infoMessageContainer.style.position = "absolute"; 16 | infoMessageContainer.style.top = "25%"; 17 | infoMessageContainer.style.left = "0%"; 18 | infoMessageContainer.style.right = "0%"; 19 | infoMessageContainer.style.textAlign = "center"; 20 | infoMessageContainer.style.zIndex = 1; 21 | this.appendChild(infoMessageContainer); 22 | 23 | 24 | const infoMessage = document.createElement("div"); 25 | infoMessage.style.backgroundColor = "#ffffff"; 26 | infoMessage.style.display = "inline-block"; 27 | infoMessage.style.padding = "1em"; 28 | infoMessageContainer.appendChild(infoMessage); 29 | this.infoMessage = infoMessage 30 | 31 | this._infoMessage('Starting up...') 32 | 33 | if (params.renderCharacteristics && 34 | params.renderCharacteristics.resolution 35 | ) { 36 | // This fills the background with a red box, which should be covered by the blue fill below 37 | const fullContainer = document.createElement("div"); 38 | fullContainer.style.position = "absolute"; 39 | fullContainer.style.top = '0' 40 | fullContainer.style.left = '0' 41 | fullContainer.style.right = '0' 42 | fullContainer.style.bottom = '0' 43 | fullContainer.style.backgroundColor = "#f00"; 44 | this.appendChild(fullContainer); 45 | 46 | // This paints a blue box, which should be the size of the renderer 47 | const sizedContainer = document.createElement("div"); 48 | sizedContainer.style.position = "absolute"; 49 | sizedContainer.style.top = '0' 50 | sizedContainer.style.left = '0' 51 | sizedContainer.style.width = `${params.renderCharacteristics.resolution.width}px` 52 | sizedContainer.style.height = `${params.renderCharacteristics.resolution.height}px` 53 | sizedContainer.style.backgroundColor = "#87a0de"; 54 | this.appendChild(sizedContainer); 55 | 56 | } 57 | 58 | return this._handleAction('load', params); 59 | } 60 | async dispose(_params) { 61 | this.innerHTML = ""; 62 | } 63 | async updateAction(params) { 64 | return this._handleAction('updateAction', params); 65 | } 66 | async playAction(params) { 67 | return this._handleAction('playAction', params); 68 | } 69 | async stopAction(params) { 70 | return this._handleAction('stopAction', params); 71 | } 72 | async customAction(params) { 73 | return this._handleAction('customAction', params); 74 | } 75 | 76 | loggedActions = [] 77 | _handleAction(type, params) { 78 | const lastAction = { type, params } 79 | this.loggedActions.push(lastAction); 80 | 81 | const currentStep = this.steps[this.stepIndex] 82 | if (!currentStep) { 83 | return this._infoMessage('No more steps to verify, this test is completed'); 84 | } 85 | let error = currentStep.verify(lastAction) 86 | 87 | 88 | if (!error) { 89 | this.stepIndex++ 90 | 91 | const nextStep = this.steps[this.stepIndex] 92 | 93 | if (nextStep) { 94 | return this._infoMessage(`${currentStep.completed ? `${currentStep.completed}\n` : ''}Step ${this.stepIndex} completed.\nNext, ${nextStep.prompt}`); 95 | } else { 96 | return this._infoMessage('No more steps to verify, this test is completed'); 97 | } 98 | } else { 99 | 100 | this._errorMessage(`Expected ${currentStep.prompt}\n${error}`) 101 | 102 | } 103 | } 104 | 105 | 106 | _errorMessage(error) { 107 | this.infoMessage.innerHTML = this._formatText(error) 108 | this.infoMessage.style.backgroundColor = "#bb0000"; 109 | this.infoMessage.style.color = "#fff"; 110 | 111 | return { 112 | statusCode: 400, 113 | statusMessage: error 114 | } 115 | } 116 | _infoMessage(str) { 117 | this.infoMessage.innerHTML = this._formatText(str) 118 | this.infoMessage.style.backgroundColor = "#fff"; 119 | this.infoMessage.style.color = "#000"; 120 | 121 | return { 122 | statusCode: 200, 123 | statusMessage: str 124 | } 125 | } 126 | _formatText(str) { 127 | return str.replaceAll(/\n/g, "
"); 128 | } 129 | 130 | 131 | steps = [ 132 | { 133 | prompt: 'N/A', 134 | completed: 'load() method seems okay!', 135 | verify: (lastAction) => { 136 | if (lastAction.type !== 'load') return 'Expected the load() method to be called' 137 | if (lastAction.params.renderType !== 'realtime') return 'Only realtime rendering is supported by this graphic!' 138 | if (!lastAction.params.data) return 'No `data` argument provided to the load() method' 139 | if (!lastAction.params.data.message) return 'Expected a `message` argument in the `data` object provided to the load() method' 140 | } 141 | }, 142 | { 143 | prompt: 'call the playAction() method (with no skipAnimation set)', 144 | verify: (lastAction) => { 145 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 146 | if (lastAction.params.skipAnimation) return 'Expected "skipAnimation" to be undefined or false, got true'; 147 | } 148 | }, 149 | { 150 | prompt: 'call the playAction() method, with skipAnimation set to true.', 151 | verify: (lastAction) => { 152 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 153 | if (lastAction.params.skipAnimation !== true) return `Expected the playAction to be called with { skipAnimation: true }, got ${JSON.stringify(lastAction.params)}`; 154 | } 155 | }, 156 | { 157 | prompt: `call the playAction() method, with 'goto' set to 4.`, 158 | verify: (lastAction) => { 159 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 160 | if (lastAction.params.goto !== 4) return `Expected the playAction to be called with 'goto' set to 4, got ${JSON.stringify(lastAction.params)}`; 161 | if (lastAction.params.delta !== undefined) return `Expected the playAction to be called with 'delta' being undefined, got ${JSON.stringify(lastAction.params)}`; 162 | } 163 | }, 164 | { 165 | prompt: `call the playAction() method, with 'delta' set to -1.`, 166 | verify: (lastAction) => { 167 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 168 | if (lastAction.params.delta !== -1) return `Expected the playAction to be called with 'delta' set to -1, got ${JSON.stringify(lastAction.params)}`; 169 | if (lastAction.params.goto !== undefined) return `Expected the playAction to be called with 'goto' being undefined, got ${JSON.stringify(lastAction.params)}`; 170 | } 171 | }, 172 | { 173 | prompt: `call the updateAction() method, with data { message: 'Hello' }`, 174 | verify: (lastAction) => { 175 | if (lastAction.type !== 'updateAction') return 'Expected the updateAction() method to be called'; 176 | if (lastAction.params.data.message !== 'Hello') return `Expected the updateAction to be called with 'delta' set to -1, got ${JSON.stringify(lastAction.params)}`; 177 | } 178 | }, 179 | { 180 | prompt: `call the stopAction() method`, 181 | verify: (lastAction) => { 182 | if (lastAction.type !== 'stopAction') return 'Expected the stopAction() method to be called'; 183 | } 184 | }, 185 | { 186 | prompt: 'call the stopAction() method, with skipAnimation set to true.', 187 | verify: (lastAction) => { 188 | if (lastAction.type !== 'stopAction') return 'Expected the stopAction() method to be called'; 189 | if (lastAction.params.skipAnimation !== true) return `Expected the stopAction to be called with { skipAnimation: true }, got ${JSON.stringify(lastAction.params)}`; 190 | } 191 | }, 192 | { 193 | prompt: 'call the customAction() method for the "highlight" action', 194 | verify: (lastAction) => { 195 | if (lastAction.type !== 'customAction') return 'Expected the customAction() method to be called'; 196 | if (lastAction.params.id !== 'highlight') return `Expected the customAction() method to be called with id "highlight", got ${JSON.stringify(lastAction.params)}`; 197 | } 198 | }, 199 | { 200 | prompt: 'call the customAction() method for the "setColor" action, with payload { color: "red" }', 201 | verify: (lastAction) => { 202 | if (lastAction.type !== 'customAction') return 'Expected the customAction() method to be called'; 203 | if (lastAction.params.id !== 'setColor') return `Expected the customAction() method to be called with id "setColor", got ${JSON.stringify(lastAction.params)}`; 204 | if (typeof lastAction.params.payload !== 'object') return `Property "payload" is missing in argument, got ${JSON.stringify(lastAction.params)}`; 205 | if (lastAction.params.payload.color !== 'red') return `Expected property "payload.color" to have the value "red", got ${JSON.stringify(lastAction.params)}`; 206 | } 207 | }, 208 | ] 209 | 210 | stepIndex = 0 211 | } 212 | 213 | export default Graphic; 214 | -------------------------------------------------------------------------------- /v1-draft-0/examples/renderer-test/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json", 3 | "name": "Renderer Test Graphic", 4 | "description": "This Graphic is used to validate a Renderer", 5 | "id": "renderer-test", 6 | "supportsRealTime": true, 7 | "supportsNonRealTime": false, 8 | "schema": { 9 | "type": "object", 10 | "properties": { 11 | "message": { 12 | "type": "string", 13 | "title": "Message", 14 | "default": "Hello" 15 | } 16 | } 17 | }, 18 | "customActions": [ 19 | { 20 | "id": "highlight", 21 | "name": "Highlight", 22 | "schema": null 23 | }, 24 | { 25 | "id": "setColor", 26 | "name": "Set Color", 27 | "schema": { 28 | "type": "object", 29 | "properties": { 30 | "color": { 31 | "type": "string", 32 | "title": "Color", 33 | "default": "red" 34 | } 35 | } 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /v1-draft-0/specification/docs/Specification.md: -------------------------------------------------------------------------------- 1 | # EBU OGraf 2 | 3 | ## About 4 | 5 | EBU OGraf is a specification for Web-based Broadcast Graphics. 6 | It allows users to create a Graphic once and use it in multiple compatible Graphic renderers. 7 | 8 | ## Introduction 9 | 10 | A Web-based Graphic is implemented using standardized Web technologies (e.g. HTML, Javascript, CSS, Canvas, Web 11 | Components, ...) and can be rendered with engines implementing these Web technologies (e.g. browsers or browser engines 12 | such as Blink and WebKit). 13 | 14 | A Web-based Graphics workflow typically consists of the following components (see figure below): 15 | * Editor: an application or service where a user can create and edit Graphics. 16 | * Controller: a user interface or automation layer controlling the playout of the Graphics. 17 | * Server: provides the API endpoints for the controllers and editors. They consist of managing graphics 18 | (importing/exporting/listing graphics) and graphics control (i.e. playout of the graphics). The latter is done by 19 | sending/receiving commands to/from the Renderer. 20 | * Renderer: is able to render one or more Web-based graphic instances. Based on incoming commands from the Server, a 21 | Graphic is animated in, updated or animated out. In order to achieve this, communication between the Renderer and the 22 | Graphic instance is necessary. 23 | 24 | Components 25 | 26 | Although vendors usually provide all components in one solution, allowing optionally third-party Editors and Controllers 27 | (typically automation layers), it is certainly possible to see each of the different parts (Editor, Controller, 28 | Server, and Renderer) coming from different vendors. 29 | 30 | The scope of this specification is the format definition of a Graphic and how a Renderer should interpret this format 31 | in order to render the Graphic. Graphic creators (developers and tools) producing Graphics compatible with this format 32 | are guaranteed that these Graphics can be rendered in compliant Renderers. This enables more straightforward 33 | exchanges between different Web-based graphic engine solutions and can support use cases such as marketplaces for Web-based Graphics. 34 | 35 | ## Use of Normative Language 36 | 37 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). 38 | 39 | ## Requirements for a Graphic 40 | 41 | A Graphic MUST consist of the following files: 42 | 43 | - a JSON file containing metadata about the Graphic, referred to as the Manifest file. 44 | See [Manifest Model](#manifest-model) for more information. 45 | - a Javascript file that exports the Graphic Web Component. The Manifest file MUST contain a reference to this Javascript file. 46 | See [Web Component Interface](#web-component-interface) for more information. 47 | - any resources used by the Graphic, such as images, videos, fonts, etc. 48 | These resources MAY be organised in a folder structure. 49 | 50 | The Manifest file SHOULD be considered as the representation and entrypoint for the Graphic, all other files are either 51 | directly or indirectly referenced from the Manifest file and can be seen as dependencies. 52 | 53 | ### Manifest Model 54 | 55 | The manifest file is a JSON file containing metadata about the Graphic. It consists of the following fields: 56 | 57 | | Field | Type | Required | Default | Description | 58 | |---------------------|--------------------|:--------:|:-------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| 59 | | $schema | string | X | | MUST be the string "https://ograf.ebu.io/v1/speficiation/json-schemas/schema.json".
This doubles as a reference to the JSON Schema of the manifest file as well as a OGraf version. | 60 | | id | string | X | | A unique identifier for the Graphic. | 61 | | version | string | | | A version descriptor of the Graphic. The versioning scheme is beyond the scope of this specification. | 62 | | name | string | X | | The name of the Graphic. | 63 | | description | string | | | A longer description of the Graphic. | 64 | | author | Author | | | An object providing information about the author of the Graphic. When provided, the object MUST contain a `name` field and MAY contain an `email` and `url` field. | 65 | | main | string | X | | Reference to the Javascript file that exports the Graphic Web Component. | 66 | | customActions | Action[] | | | An array of `Action` objects. They correspond to the custom actions that can be invoked on the Graphic. See below for details about the fields inside an `Action`. | 67 | | supportsRealTime | boolean | X | | Indicates whether the Graphic supports real-time rendering. | 68 | | supportsNonRealTime | boolean | X | | Indicates whether the Graphic supports non-real-time rendering. If true, the Graphic MUST implement the non-real-time functions `goToTime()` and `setActionsSchedule()`. | 69 | | schema | object | | | The JSON schema definition for the `data` argument to the `load()` and `updateAction()` methods. This schema can be seen as the (public) state model of the Graphic. | 70 | | stepCount | integer | | 1 | The number of steps a Graphic consists of. | 71 | | renderRequirements | RenderRequirement[]| | | A list of requirements that this Graphic has for the rendering environment. At least one of the requirements must be met for the graphic to be expected to work. | 72 | 73 | #### Real-time vs. non-real-time 74 | 75 | Real-time rendering of a Graphic means that the Graphic is animated at real-time speed, typically in the context of live TV. 76 | Non-real-time rendering has no requirement on the speed of rendering, it can be slower or faster than real-time and is 77 | typically used in post-production scenarios. A Graphic MUST be either marked as real-time, non-real-time or both, 78 | by means of the `supportsRealTime` and `supportsNonRealTime` fields. 79 | 80 | In case of a non-real-time Graphic, there are two additional functions that need to be implemented by the Graphic: 81 | `goToTime()` and `setActionsSchedule()` (see [Web Component Interface](#web-component-interface) for their definition). 82 | 83 | #### Step model 84 | 85 | A Graphic contains zero or more steps. A step can be defined as a 'paused' state of the Graphic. 86 | Going from one step to another is done via a transition (with or without animation). The figure below shows three example 87 | step models. Every model has a start and an end node. The start node represents the start of a Graphic rendering, where 88 | typically nothing is visible in the rendered output. Similarly, the end node represents the end of the Graphic rendering, 89 | also typically nothing visible in the rendered output at that moment. The arrows between the nodes represent the transitions. 90 | 91 | The first model represents a Graphic containing zero steps. When `playAction()` is called on this Graphic, it will 92 | animate the Graphic in and after some predefined time the Graphic will animate out automatically. 93 | 94 | The second model represents a Graphic containing one step. When `playAction()` is called on this Graphic, it will 95 | animate the Graphic in and will pause at step 1. Pausing here doesn't mean that the Graphic is not moving, it refers to 96 | the fact that there is an interaction necessary with the Graphic to move to the next step (in this case the end). 97 | The `stopAction()` function SHOULD be used to go to the end of the Graphic. 98 | 99 | The third model represents a multi-step Graphic containing two steps. It is similar to the one-step model, 100 | but now the `playAction()` function MUST be used again to transition between different steps, except for the end node, where the `stopAction()` 101 | function SHOULD be used. The normal flow is to go to step 1, then to step 2 and finally to the end node. However, it is 102 | possible that you transition to any step or directly to the end node (indicated by the dotted lines in the figure). 103 | 104 | Step model 105 | 106 | #### Custom actions 107 | 108 | A custom action is an action that is specific for a particular Graphic. It is a mechanism to support any action 109 | a Graphic can execute. The Manifest file defines the custom actions by means of the `actions` field. It represents 110 | a Map where the keys correspond to the id of the custom action and the values are `Action` objects. The `Action` object 111 | supports the following fields: 112 | 113 | | Field | Type | Required | Default | Description | 114 | |-------------|--------|:--------:|:-------:|-----------------------------------------------------------| 115 | | id | string | X | | The identity of the action. The id must be unique within the graphic. | 116 | | name | string | X | | The name of the action (for use in GUIs). | 117 | | description | string | | | A longer description of the action. | 118 | | schema | object | | | The JSON schema definition for the payload of the action. | 119 | 120 | #### RenderRequirements 121 | 122 | A RenderRequirement in the manifest file is an object that describes which requirements a Graphic has for the rendering environment. 123 | The `renderRequirements` is a list of RenderRequirements, where at least one requirement must be fulfilled by the renderer for a 124 | Graphic to be expected to work. 125 | 126 | The RenderRequirement object contains the following fields: 127 | 128 | 129 | | Field | Type | Required | Default | Description | 130 | |-------------------|------------------|:--------:|:-------:|-----------------------------------------------------------| 131 | | resolution | object | | | Object that describes resolution requirements. | 132 | | resolution.width | NumberConstraint | | | Specifies renderer width resolution requirement. | 133 | | resolution.height | NumberConstraint | | | Specifies renderer height resolution requirement. | 134 | | frameRate | NumberConstraint | | | Specifies renderer frameRate requirement. | 135 | 136 | ##### NumberConstraint 137 | 138 | A NumberConstraint is an object that describes a constraints for a numerical value. 139 | (This is inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble) 140 | It contains the following fields: 141 | 142 | | Field | Type | Required | Default | Description | 143 | |-------------------|------------------|:--------:|:-------:|-----------------------------------------------------------| 144 | | max | number | | | A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail. | 145 | | min | number | | | A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail. | 146 | | exact | number | | | A number specifying a specific, required, value the property must have to be considered acceptable. | 147 | | ideal | number | | | A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. | 148 | 149 | ##### StringConstraint 150 | 151 | A StringConstraint is an object that describes a constraints for a textual value. 152 | (This is inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble) 153 | It contains the following fields: 154 | 155 | | Field | Type | Required | Default | Description | 156 | |-------------------|------------------|:--------:|:-------:|-----------------------------------------------------------| 157 | | exact | string | | | A string specifying a specific, required, value the property must have to be considered acceptable. | 158 | | ideal | string, string[] | | | A string (or an array of strings), specifying ideal values for the property. If possible, one of the listed values will be used, but if it's not possible, the user agent will use the closest possible match. | 159 | 160 | #### Vendor-specific fields 161 | 162 | Vendor-specific fields are additional fields that are not part of this specification, but used for vendor-specific means. 163 | They can occur in the Manifest file or in the payload of the requests/responses for each of the functions in the Web Component. 164 | Every vendor-specific field MUST use the prefix `v_`. For example, `v_editor` is a valid vendor-specific field, `editor` is not a valid field. 165 | 166 | ### Web Component Interface 167 | 168 | A Graphic MUST be implemented in the form of a custom HTML element (i.e. a Web Component). 169 | The [HTML5 Custom Elements specification](https://html.spec.whatwg.org/multipage/custom-elements.html) defines the 170 | requirements for such a custom HTML element. 171 | 172 | Therefore, the contents of the `main` Javascript file of a Graphic MUST contain a `class` that extends from `HTMLElement`. 173 | Depending on the rendering capabilities (defined in the Manifest file), a Graphic is expected to implement a number of functions. 174 | 175 | To describe the functions in this document, the Typescript interface notation is used. For simplicity, we omit the indication 176 | that vendor-specific fields can be included in both request and response payloads. 177 | For the 'action' methods (`playAction()`, `stopAction()`, `updateAction()` and `customAction()`), a Promise MUST be returned that 178 | resolves to `undefined` or to an `ReturnPayload` object containing the following fields: 179 | * `code`: a number that corresponds to an HTTP status code (2xx indicates a successful result, 4xx and 5xx indicate an error). 180 | * `message`: an optional human-readable message that corresponds to the `code`. 181 | * `result`: an optional Graphics-specific response object. 182 | If the returned Promise resolves to `undefined`, it should be treated as a `{ code: 200 }`. 183 | 184 | Similarly, for simplicity reasons, we omit these three fields in the description of the functions below. 185 | In [Typescript interface](#typescript-interface-for-graphic), the full interface is provided. 186 | 187 | 188 | Every Graphic MUST implement the following functions: 189 | * `load: ({ data:any }) => Promise`: Called by the Renderer when the Graphic has been loaded into the DOM. 190 | The `data`-payload MUST contain the initial internal state of the Graphic. 191 | The schema of the `data`-payload of this function is described in the Manifest using the `schema` field. 192 | A Promise is returned that resolves when everything is loaded by the Graphic. 193 | * `dispose: ({}) => Promise`: Called by the Renderer to force the Graphic to terminate/dispose/clear any loaded resources. A Promise 194 | is returned that resolves when the Graphic completed the necessary cleanup. 195 | * `playAction: ({delta: number, goto: number, skipAnimation: boolean}) => Promise`: 196 | Called by the Renderer to play a given step. The `skipAnimation` field indicates whether the Graphic should transition with or without animation. 197 | The `delta` and `goto` fields indicate the target step; `delta` is used for relative steps, `goto` for an absolute step number. When the target 198 | step number is higher or equal to the `stepCount` defined in the Manifest, the Graphic MUST transition to the end. The returned Promise 199 | resolves to an `ReturnPayload` object with an additional `currentStep` field which indicates the current step after the execution of the `playAction()` function. 200 | In case `stepCount` is equal to zero or the `playAction()` function is used to transition to the end, the `currentStep` field in the response is `undefined`. 201 | * `stopAction: ({skipAnimation: boolean}) => Promise`: Called by the Renderer to stop the Graphic from being displayed. 202 | This can be with or without animation, depending on the value of the `skipAnimation` field. The returned Promise resolves to an `ReturnPayload` object. 203 | * `updateAction: ({ data: any }) => Promise`: Called by the Renderer to update one or more fields of the internal state of the Graphic. The schema of the 204 | `data`-payload of this function is described in the Manifest using the `schema` field. The returned Promise resolves to an `ReturnPayload` object. 205 | * `customAction: ({ id: string, payload: any}) => Promise`: Called by the Renderer to invoke a custom action on the Graphic. 206 | The `id` field MUST correspond to an `id` of an Action that is defined in the Manifest file, inside the `actions` field. 207 | The schema for the `payload` field is the described in the corresponding Action inside the Manifest file. A Promise 208 | is returned that resolves to an `ReturnPayload` object when the action is executed. 209 | 210 | Additionally, every non-real-time Graphic MUST implement the following functions: 211 | * `goToTime: ({timestamp: number}) => Promise`: Called to make the Graphic jump to a certain `timestamp`, expressed in milliseconds. A Promise is returned with an `ReturnPayload` that resolves when the frame is rendered at the requested position. 212 | * `setActionsSchedule: (payload: {schedule: {timestamp: number, action: {type, params}}}) => Promise`: Called to schedule actions to be invoked at a certain point in time. When this is called, the Graphic is expected to store the scheduled actions and invoke them when the time comes. A call to this replaces any previous scheduled actions. For every `timestamp`, expressed in milliseconds, an action type is provided together with corresponding parameters. The action type is either `playAction`, `stopAction`, `updateAction` or the id of a custom action. 213 | 214 | The default export MUST be used to export the `class` representing the Graphic. 215 | This type of export allows you to import the Graphic using any name. 216 | 217 | 218 | ## Requirements for a Renderer 219 | 220 | The way a Graphic is added into a Renderer is non-normative. 221 | Different examples are provided [here](TODO) showing how a Graphic can be added to the DOM tree of the Renderer. 222 | 223 | When a Graphic is added into a Renderer, the following steps are executed by the Renderer: 224 | * MUST call the `load()` function of the Graphic. 225 | * MUST wait for the promise to resolve. 226 | 227 | When a Graphic is removed from the Renderer, the following steps are executed by the Renderer: 228 | * MUST call the `dispose()` function of the Graphic. 229 | * SHOULD wait for the promise to resolve. 230 | 231 | 232 | ## JSON Schema for Manifest file 233 | 234 | The normative JSON Schema for the Manifest file can be found [here](../json-schemas/graphics/schema.json). 235 | 236 | ## Typescript interface for Graphic 237 | 238 | The informative Typescript interface for the Graphic Web Component can be found [here](../../typescript-definitions/src/apis/graphicsAPI.ts). 239 | 240 | ## Examples 241 | 242 | ### Lower Third 243 | 244 | The following manifest describes a simple Lower Third Graphic. It does not contain any custom actions and has one state property: `name`. 245 | 246 | ```json 247 | { 248 | "$schema": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json", 249 | "id": "l3rd-name", 250 | "version": "1.0.0", 251 | "name": "Lower 3rd - Name", 252 | "description": "Name lower third", 253 | "author": { 254 | "name": "John Doe", 255 | "email": "john.doe@foo.com" 256 | }, 257 | "main": "lower-third.mjs", 258 | "schema": { 259 | "type": "object", 260 | "properties": { 261 | "name": { 262 | "type": "string", 263 | "title": "Name", 264 | "default": "John Doe" 265 | } 266 | } 267 | }, 268 | "supportsRealTime": true, 269 | "supportsNonRealTime": false 270 | } 271 | ``` 272 | 273 | The above manifest refers to the Javascript file `lower-third.mjs`, which is the Web Component implementing this Graphic: 274 | 275 | ```typescript 276 | class Graphic extends HTMLElement { 277 | async load({ data: { name: string } } ) { 278 | // Load resources and initialize 279 | } 280 | async dispose() { 281 | // Dispose the necessary resources, if any 282 | } 283 | async playAction({ delta: number, goto: number, skipAnimation: boolean }) { 284 | // Play the Graphic according to the incoming params 285 | return {code: 200, message: 'OK', currentStep} 286 | } 287 | async stopAction({ skipAnimation: boolean }) { 288 | // Stop the Graphic, with or without animation 289 | return {code: 200, message: 'OK'} 290 | } 291 | async updateAction({ data: { name: string } }) { 292 | // Update the state of the Graphic 293 | return {code: 200, message: 'OK'} 294 | } 295 | async customAction({ id: string, payload: any}) { 296 | return {code: 400, message: 'No custom actions supported'} 297 | } 298 | } 299 | 300 | export default Graphic; 301 | ``` 302 | -------------------------------------------------------------------------------- /v1-draft-0/specification/docs/images/components.svg: -------------------------------------------------------------------------------- 1 | ControllerServerRendererGraphic InstanceGraphicGraphicGraphicEditor -------------------------------------------------------------------------------- /v1-draft-0/specification/docs/images/step-model.svg: -------------------------------------------------------------------------------- 1 | step 1startendstartendstep 1step 2startend -------------------------------------------------------------------------------- /v1-draft-0/specification/json-schemas/graphics/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json", 4 | "type": "object", 5 | "properties": { 6 | "$schema": { 7 | "type": "string", 8 | "const": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json", 9 | "description": "Reference to the JSON-schema for this manifest" 10 | }, 11 | "id": { 12 | "type": "string", 13 | "description": "The id of the Graphic uniquely identifies it. It is recommended to use a reverse domain name notation. For example: com.my-company.my-lowerthird." 14 | }, 15 | "version": { 16 | "type": "string", 17 | "description": "The version of the Graphic. The version SHOULD be alphabetically sortable. Examples: ['0', '1', '2'], ['1.0', '1.1', '1.2'], ['2024-07-01_final', '2024-07-01_final_final2']" 18 | }, 19 | "main": { 20 | "type": "string", 21 | "description": "The main entry point, ie the path to the main javascript file of the Graphic." 22 | }, 23 | "name": { 24 | "type": "string", 25 | "description": "Name of the Graphic" 26 | }, 27 | "description": { 28 | "type": "string", 29 | "description": "(optional) A longer description of the Graphic" 30 | }, 31 | "author": { 32 | "type": "object", 33 | "description": "(optional) About the author", 34 | "properties": { 35 | "name": { 36 | "type": "string", 37 | "description": "Name of the author" 38 | }, 39 | "email": { 40 | "type": "string", 41 | "description": "(optional) Email of the author" 42 | }, 43 | "url": { 44 | "type": "string", 45 | "description": "(optional) URL of the author" 46 | } 47 | }, 48 | "required": [ 49 | "name" 50 | ], 51 | "patternProperties": { 52 | "^v_.*": {} 53 | }, 54 | "additionalProperties": false 55 | }, 56 | "customActions": { 57 | "type": "array", 58 | "description": "Custom Actions that can be invoked on the Graphic.", 59 | "items": { 60 | "$ref": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/lib/action.json" 61 | } 62 | }, 63 | "supportsRealTime": { 64 | "type": "boolean", 65 | "description": "Indicates if the Graphic supports real-time rendering" 66 | }, 67 | "supportsNonRealTime": { 68 | "type": "boolean", 69 | "description": "Indicates if the Graphic supports non-real-time rendering. Note: If true, the Graphic must implement the 'goToTime()' and the 'setActionsSchedule()' methods." 70 | }, 71 | "stepCount": { 72 | "type": "number", 73 | "description": "The number of steps in the Graphic. If the Graphic is simply triggered by a play, then a stop, this is considered a stepCount of 1 (defaults to 1).", 74 | "default": 1 75 | }, 76 | "schema": { 77 | "description": "The schema is used by a Graphic to define the data parameters of the 'update' method.", 78 | "type": "object", 79 | "$ref": "https://superflytv.github.io/GraphicsDataDefinition/gdd-meta-schema/v1/lib/object.json" 80 | }, 81 | "renderRequirements": { 82 | "description": "A list of requirements that this Graphic has for the rendering environment. At least one of the requirements must be met for the graphic to be expected to work.", 83 | "type": "array", 84 | "items": { 85 | "type": "object", 86 | "properties": { 87 | "resolution": { 88 | "description": "If set, specifies requirements for the resolution of the Renderer.", 89 | "type": "object", 90 | "properties": { 91 | "width": { 92 | "$ref": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/lib/constraints/number.json" 93 | }, 94 | "height": { 95 | "$ref": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/lib/constraints/number.json" 96 | } 97 | } 98 | }, 99 | "frameRate": { 100 | "description": "If set, specifies requirements for frame rate of the Renderer. Example: 60 fps", 101 | "$ref": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/lib/constraints/number.json" 102 | } 103 | 104 | }, 105 | "patternProperties": { 106 | "^v_.*": {} 107 | }, 108 | "additionalProperties": false 109 | } 110 | } 111 | }, 112 | "required": [ 113 | "$schema", 114 | "id", 115 | "name", 116 | "supportsRealTime", 117 | "supportsNonRealTime" 118 | ], 119 | "patternProperties": { 120 | "^v_.*": {} 121 | }, 122 | "additionalProperties": false 123 | } 124 | -------------------------------------------------------------------------------- /v1-draft-0/specification/json-schemas/lib/action.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/lib/action.json", 4 | "type": "object", 5 | "properties": { 6 | "id": { 7 | "type": "string", 8 | "description": "The identity of the action. The id must be unique within the graphic." 9 | }, 10 | "name": { 11 | "type": "string", 12 | "description": "The name of the action. This is displayed to the user." 13 | }, 14 | "description": { 15 | "type": "string", 16 | "description": "A longer description of the action. This is displayed to the user." 17 | }, 18 | "schema": { 19 | "description": "The schema of the action. This is used to validate the action parameters as well as auto-generate a GUI for the action. If the action does not require any parameters, set this to null.", 20 | "oneOf": [ 21 | { 22 | "type": "object", 23 | "$ref": "https://superflytv.github.io/GraphicsDataDefinition/gdd-meta-schema/v1/lib/object.json" 24 | }, 25 | { 26 | "type": "null" 27 | } 28 | ] 29 | } 30 | }, 31 | "required": [ 32 | "id", 33 | "name" 34 | ], 35 | "patternProperties": { 36 | "^v_.*": {} 37 | }, 38 | "additionalProperties": false 39 | } 40 | -------------------------------------------------------------------------------- /v1-draft-0/specification/json-schemas/lib/constraints/number.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/lib/constraints/number.json", 4 | "type": "object", 5 | "description": "The number constraint is used to specify a constraint for a numerical property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble)", 6 | "properties": { 7 | "max": { 8 | "description": "A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail.", 9 | "type": "number" 10 | }, 11 | "min": { 12 | "description": "A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail.", 13 | "type": "number" 14 | }, 15 | "exact": { 16 | "description": "A number specifying a specific, required, value the property must have to be considered acceptable.", 17 | "type": "number" 18 | }, 19 | "ideal": { 20 | "description": "A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match.", 21 | "type": "number" 22 | } 23 | }, 24 | "patternProperties": { 25 | "^v_.*": {} 26 | }, 27 | "additionalProperties": false 28 | } 29 | -------------------------------------------------------------------------------- /v1-draft-0/specification/json-schemas/lib/constraints/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/lib/constraints/string.json", 4 | "type": "object", 5 | "description": "The string constraint is used to specify a constraint for a string property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindomstring)", 6 | "properties": { 7 | "exact": { 8 | "description": "A string or an array of strings, one of which must be the value of the property. If the property can't be set to one of the listed values, matching will fail.", 9 | "type": "string" 10 | }, 11 | "ideal": { 12 | "description": "A string (or an array of strings), specifying ideal values for the property. If possible, one of the listed values will be used, but if it's not possible, the user agent will use the closest possible match.", 13 | "oneOf": [ 14 | { 15 | "type": "string" 16 | }, 17 | { 18 | "type": "array", 19 | "items": { 20 | "type": "string" 21 | } 22 | } 23 | ] 24 | } 25 | }, 26 | "patternProperties": { 27 | "^v_.*": {} 28 | }, 29 | "additionalProperties": false 30 | } 31 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tsconfig.tsbuildinfo 4 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | scripts 3 | tsconfig.json 4 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/README.md: -------------------------------------------------------------------------------- 1 | # OGraf Typescript Definitions 2 | 3 | These are Typescript definitions for the OGraf API. 4 | 5 | https://github.com/ebu/ograf 6 | 7 | ## Getting Started 8 | 9 | ```typescript 10 | 11 | import { GraphicsAPI } from 'ograf'; 12 | 13 | 14 | // Setup my OGraf graphic WebComponent: 15 | class MyOGrafGraphic extends HTMLElement implements GraphicsAPI.Graphic { 16 | connectedCallback() { 17 | // Called when the element is added to the DOM 18 | // Note: Don't paint any pixels at this point, wait for load() to be called 19 | } 20 | 21 | async load(params) { 22 | if (params.renderType !== "realtime") 23 | throw new Error("Only realtime rendering is supported by this graphic"); 24 | 25 | const elText = document.createElement("p"); 26 | elText.innerHTML = "Hello world!"; 27 | this.appendChild(elText); 28 | 29 | // When everything is loaded we can return: 30 | return { 31 | statusCode: 200, 32 | }; 33 | } 34 | async dispose(_params) { 35 | this.innerHTML = ""; 36 | } 37 | async getStatus(_params) { 38 | return { 39 | statusCode: 200, 40 | status: { 41 | // nothing 42 | }, 43 | }; 44 | } 45 | async updateAction(_params) { 46 | // No actions are implemented in this minimal example 47 | } 48 | async playAction(_params) { 49 | // No actions are implemented in this minimal example 50 | } 51 | async stopAction(_params) { 52 | // No actions are implemented in this minimal example 53 | } 54 | async customAction(params) { 55 | // No actions are implemented in this minimal example 56 | } 57 | async goToTime(_payload) { 58 | throw new Error("Non-realtime not supported!"); 59 | } 60 | async setActionsSchedule(_payload) { 61 | throw new Error("Non-realtime not supported!"); 62 | } 63 | } 64 | 65 | ``` 66 | 67 | 68 | ## For Developers 69 | 70 | The instructions below are for developers who want to work on the Typescript definitions. 71 | 72 | ### Install & build 73 | 74 | ```bash 75 | 76 | npm install 77 | npm run build 78 | # or 79 | npm run watch 80 | 81 | ``` 82 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ograf", 3 | "version": "0.0.5", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "ograf", 9 | "version": "0.0.5", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "json-schema-to-typescript": "^15.0.3", 13 | "typescript": "^5.6.3" 14 | } 15 | }, 16 | "node_modules/@apidevtools/json-schema-ref-parser": { 17 | "version": "11.7.2", 18 | "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", 19 | "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", 20 | "dev": true, 21 | "dependencies": { 22 | "@jsdevtools/ono": "^7.1.3", 23 | "@types/json-schema": "^7.0.15", 24 | "js-yaml": "^4.1.0" 25 | }, 26 | "engines": { 27 | "node": ">= 16" 28 | }, 29 | "funding": { 30 | "url": "https://github.com/sponsors/philsturgeon" 31 | } 32 | }, 33 | "node_modules/@jsdevtools/ono": { 34 | "version": "7.1.3", 35 | "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", 36 | "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", 37 | "dev": true 38 | }, 39 | "node_modules/@types/json-schema": { 40 | "version": "7.0.15", 41 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 42 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 43 | "dev": true 44 | }, 45 | "node_modules/@types/lodash": { 46 | "version": "4.17.13", 47 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", 48 | "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", 49 | "dev": true 50 | }, 51 | "node_modules/argparse": { 52 | "version": "2.0.1", 53 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 54 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 55 | "dev": true 56 | }, 57 | "node_modules/fdir": { 58 | "version": "6.4.2", 59 | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", 60 | "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", 61 | "dev": true, 62 | "peerDependencies": { 63 | "picomatch": "^3 || ^4" 64 | }, 65 | "peerDependenciesMeta": { 66 | "picomatch": { 67 | "optional": true 68 | } 69 | } 70 | }, 71 | "node_modules/is-extglob": { 72 | "version": "2.1.1", 73 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 74 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 75 | "dev": true, 76 | "engines": { 77 | "node": ">=0.10.0" 78 | } 79 | }, 80 | "node_modules/is-glob": { 81 | "version": "4.0.3", 82 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 83 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 84 | "dev": true, 85 | "dependencies": { 86 | "is-extglob": "^2.1.1" 87 | }, 88 | "engines": { 89 | "node": ">=0.10.0" 90 | } 91 | }, 92 | "node_modules/js-yaml": { 93 | "version": "4.1.0", 94 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 95 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 96 | "dev": true, 97 | "dependencies": { 98 | "argparse": "^2.0.1" 99 | }, 100 | "bin": { 101 | "js-yaml": "bin/js-yaml.js" 102 | } 103 | }, 104 | "node_modules/json-schema-to-typescript": { 105 | "version": "15.0.3", 106 | "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.3.tgz", 107 | "integrity": "sha512-iOKdzTUWEVM4nlxpFudFsWyUiu/Jakkga4OZPEt7CGoSEsAsUgdOZqR6pcgx2STBek9Gm4hcarJpXSzIvZ/hKA==", 108 | "dev": true, 109 | "dependencies": { 110 | "@apidevtools/json-schema-ref-parser": "^11.5.5", 111 | "@types/json-schema": "^7.0.15", 112 | "@types/lodash": "^4.17.7", 113 | "is-glob": "^4.0.3", 114 | "js-yaml": "^4.1.0", 115 | "lodash": "^4.17.21", 116 | "minimist": "^1.2.8", 117 | "prettier": "^3.2.5", 118 | "tinyglobby": "^0.2.9" 119 | }, 120 | "bin": { 121 | "json2ts": "dist/src/cli.js" 122 | }, 123 | "engines": { 124 | "node": ">=16.0.0" 125 | } 126 | }, 127 | "node_modules/lodash": { 128 | "version": "4.17.21", 129 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 130 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 131 | "dev": true 132 | }, 133 | "node_modules/minimist": { 134 | "version": "1.2.8", 135 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 136 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 137 | "dev": true, 138 | "funding": { 139 | "url": "https://github.com/sponsors/ljharb" 140 | } 141 | }, 142 | "node_modules/picomatch": { 143 | "version": "4.0.2", 144 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 145 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 146 | "dev": true, 147 | "engines": { 148 | "node": ">=12" 149 | }, 150 | "funding": { 151 | "url": "https://github.com/sponsors/jonschlinkert" 152 | } 153 | }, 154 | "node_modules/prettier": { 155 | "version": "3.4.0", 156 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.0.tgz", 157 | "integrity": "sha512-/OXNZcLyWkfo13ofOW5M7SLh+k5pnIs07owXK2teFpnfaOEcycnSy7HQxldaVX1ZP/7Q8oO1eDuQJNwbomQq5Q==", 158 | "dev": true, 159 | "bin": { 160 | "prettier": "bin/prettier.cjs" 161 | }, 162 | "engines": { 163 | "node": ">=14" 164 | }, 165 | "funding": { 166 | "url": "https://github.com/prettier/prettier?sponsor=1" 167 | } 168 | }, 169 | "node_modules/tinyglobby": { 170 | "version": "0.2.10", 171 | "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", 172 | "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", 173 | "dev": true, 174 | "dependencies": { 175 | "fdir": "^6.4.2", 176 | "picomatch": "^4.0.2" 177 | }, 178 | "engines": { 179 | "node": ">=12.0.0" 180 | } 181 | }, 182 | "node_modules/typescript": { 183 | "version": "5.6.3", 184 | "dev": true, 185 | "license": "Apache-2.0", 186 | "bin": { 187 | "tsc": "bin/tsc", 188 | "tsserver": "bin/tsserver" 189 | }, 190 | "engines": { 191 | "node": ">=14.17" 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ograf", 3 | "version": "0.0.5", 4 | "main": "dist/main.js", 5 | "types": "dist/main.d.ts", 6 | "license": "MIT", 7 | "description": "TypeScript definitions for Ograf graphics", 8 | "homepage": "https://ograf.ebu.io", 9 | "keywords": [ 10 | "ograf", 11 | "types", 12 | "typescript", 13 | "ebu", 14 | "broadcast", 15 | "graphics" 16 | ], 17 | "scripts": { 18 | "build": "tsc --build", 19 | "watch": "tsc --watch", 20 | "generate-types": "node scripts/generate-types.js", 21 | "do:publish": "npm run generate-types && npm run build && npm publish --access public" 22 | }, 23 | "devDependencies": { 24 | "typescript": "^5.6.3", 25 | "json-schema-to-typescript": "^15.0.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/scripts/generate-types.js: -------------------------------------------------------------------------------- 1 | const { compile, compileFromFile } = require("json-schema-to-typescript"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | 5 | async function main() { 6 | 7 | 8 | const outputPath = path.resolve("./src/generated"); 9 | const schemaPath = path.resolve("../specification/json-schemas/graphics"); 10 | const options = { 11 | bannerComment: `/* eslint-disable */ 12 | /** 13 | * This file was automatically generated by json-schema-to-typescript. 14 | * DO NOT MODIFY IT BY HAND! Instead, modify the source JSON Schema file, 15 | * and run 'npm run generate-types' to regenerate this file. 16 | */`, 17 | customName: (schema, name) => { 18 | // console.log("name", name, schema); 19 | return undefined; 20 | }, 21 | }; 22 | 23 | // Compile JSON schemas to TypeScript types: 24 | await saveFile( 25 | path.join(outputPath, "graphics-manifest.ts"), 26 | await compileFromFile( 27 | path.join(schemaPath, "schema.json"), 28 | options 29 | ) 30 | ); 31 | // await saveFile( 32 | // path.join(outputPath, "renderer-manifest.d.ts"), 33 | // await compileFromFile( 34 | // path.join(schemaPath, "renderer-manifest/schema.json"), 35 | // options 36 | // ) 37 | // ); 38 | 39 | console.log(`Generated types at: ${outputPath}`); 40 | } 41 | async function saveFile(savePath, contents) { 42 | // Ensure the folder exists: 43 | await fs.promises.mkdir(path.dirname(savePath), { recursive: true }); 44 | 45 | // Replace contents in case of using localhost during testing: 46 | 47 | contents = contents.replaceAll('HttpLocalhost8080', 'HttpsOgrafEbuIo') 48 | 49 | await fs.promises.writeFile(savePath, contents); 50 | } 51 | 52 | main().catch((err) => { 53 | console.error(err); 54 | process.exit(1); 55 | }); 56 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/scripts/local-testing.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | /* 5 | ************************************************************************************************* 6 | 7 | This scripts replaces URLs in the manifest with local paths, for testing purposes. 8 | Usage: 9 | * `node scripts/local-testing.js` to replace URLs with local paths 10 | * `node scripts/local-testing.js --restore` to restore the original URLs 11 | * 12 | 13 | This is useful when testing the json-manifests locally. 14 | One simple way to serve the files locally is: 15 | * `cd ograf` 16 | * `npm install -g http-server` 17 | * `http-server -p 8080` // serves the files on http://localhost:8080 18 | 19 | ************************************************************************************************* 20 | */ 21 | 22 | let restore = false; 23 | process.argv.forEach((arg) => { 24 | if (arg === "--restore") restore = true; 25 | }); 26 | 27 | let replacements = [ 28 | { 29 | from: "https://ograf.ebu.io/", 30 | to: "http://localhost:8080/", 31 | }, 32 | ]; 33 | 34 | if (restore) { 35 | replacements = replacements.map((r) => { 36 | return { 37 | from: r.to, 38 | to: r.from, 39 | }; 40 | }); 41 | } 42 | 43 | let updateCount = 0; 44 | 45 | async function replaceInAllFiles(folderPath) { 46 | const files = await fs.promises.readdir(folderPath); 47 | 48 | for (const file of files) { 49 | if (file === "node_modules") continue; 50 | if (file === "local-testing.js") continue; 51 | // Only process these file types: 52 | 53 | const filePath = path.join(folderPath, file); 54 | 55 | // is dir? 56 | if ((await fs.promises.stat(filePath)).isDirectory()) { 57 | await replaceInAllFiles(filePath); 58 | } else { 59 | // Only process these file types: 60 | if ( 61 | !file.endsWith(".ts") && 62 | !file.endsWith(".js") && 63 | !file.endsWith(".json") 64 | ) 65 | continue; 66 | 67 | const fileContents = await fs.promises.readFile(filePath, "utf-8"); 68 | 69 | let newContents = fileContents; 70 | 71 | for (const replacement of replacements) { 72 | newContents = newContents.replaceAll(replacement.from, replacement.to); 73 | } 74 | 75 | if (newContents !== fileContents) { 76 | await fs.promises.writeFile(filePath, newContents); 77 | console.log(`Updated ${filePath}`); 78 | updateCount++; 79 | } 80 | } 81 | } 82 | } 83 | 84 | const basePath = path.resolve(__dirname, "../.."); 85 | replaceInAllFiles(basePath) 86 | .then(() => { 87 | if (!restore) { 88 | console.log(`Updated ${updateCount} files.`); 89 | console.log("To restore, run `node scripts/local-testing.js --restore`"); 90 | } else { 91 | console.log(`${updateCount} Files restored`); 92 | } 93 | }) 94 | .catch(console.error); 95 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/src/apis/graphicsAPI.ts: -------------------------------------------------------------------------------- 1 | import { RenderCharacteristics } from "../definitions/render"; 2 | import { 3 | PlayActionReturnPayload, 4 | ActionInvokeParams, 5 | ReturnPayload, 6 | EmptyPayload, 7 | EmptyParams, 8 | VendorExtend, 9 | } from "../definitions/types"; 10 | 11 | /** 12 | * ================================================================================================ 13 | * 14 | * The GraphicsAPI is a javascript interface, ie javascript methods exposed by the OGraf Graphics WebComponent. 15 | * 16 | * ================================================================================================ 17 | */ 18 | 19 | /** 20 | * This interface defines the methods that the Renderer can call on the Graphic. 21 | * @example class MyOGrafGraphic extends HTMLElement implements GraphicsAPI.Graphic {} 22 | * 23 | */ 24 | export interface Graphic { 25 | /** 26 | * Called by the Renderer when the Graphic has been loaded into the DOM 27 | * @returns a Promise that resolves when the Graphic has finished loading it's resources. 28 | */ 29 | load: ( 30 | params: { 31 | /** The data send here is defined in the manifest "schema". Note: This data MUST HAVE the same type as the `data` argument in updateAction method. */ 32 | data: unknown; 33 | 34 | /** Whether the rendering is done in realtime or non-realtime */ 35 | renderType: "realtime" | "non-realtime"; 36 | 37 | /** A set of characteristics / capabilities of the Renderer, that affects how the Graphic will be rendered. */ 38 | renderCharacteristics: RenderCharacteristics; 39 | } & VendorExtend 40 | ) => Promise; 41 | 42 | /** 43 | * Called by the Renderer to force the Graphic to terminate/dispose/clear any loaded resources. 44 | * This is called after the Renderer has unloaded the Graphic from the DOM. 45 | */ 46 | dispose: (params: EmptyParams) => Promise; 47 | 48 | /** This is called whenever user send a new data payload. */ 49 | updateAction: ( 50 | params: { 51 | /** The data send here is defined in the manifest "schema". Note: This data MUST HAVE the same type as the `data` argument in the load method. */ 52 | data: unknown; 53 | } & VendorExtend 54 | ) => Promise; 55 | 56 | /** This is called when user calls the "play" action. */ 57 | playAction: ( 58 | params: { 59 | /** How far to advance. 1 = next step/segment. (defaults to 1) */ 60 | delta: number; 61 | /** Jump to a specific step/segment (defaults to undefined) */ 62 | goto: number; 63 | /** If true, skips animation (defaults to false) */ 64 | skipAnimation: boolean; 65 | } & VendorExtend 66 | ) => Promise; 67 | 68 | /** This is called when user calls the "stop" action. */ 69 | stopAction: ( 70 | params: { skipAnimation: boolean } & VendorExtend 71 | ) => Promise; 72 | 73 | /** 74 | * Called by the Renderer to invoke an Action on the Graphic 75 | * @returns The return value of the invoked method (vendor-specific) 76 | */ 77 | customAction: ( 78 | params: ActionInvokeParams 79 | ) => Promise; 80 | 81 | /** 82 | * If the Graphic supports non-realtime rendering, this is called to make the graphic jump to a certain point in time. 83 | * @returns A Promise that resolves when the Graphic has finished rendering the requested frame. 84 | */ 85 | goToTime: ( 86 | params: { timestamp: number } & VendorExtend 87 | ) => Promise; 88 | 89 | /** 90 | * If the Graphic supports non-realtime rendering, this is called to schedule actions to be invoked at a certain point in time. 91 | * When this is called, the Graphic is expected to store the scheduled actions and invoke them when the time comes. 92 | * (A call to this replaces any previous scheduled actions.) 93 | * @returns A Promise that resolves when the Graphic has stored the scheduled actions. 94 | */ 95 | setActionsSchedule: ( 96 | params: { 97 | /** 98 | * A list of the scheduled actions to call at certain points in time. 99 | */ 100 | schedule: { 101 | timestamp: number; 102 | action: 103 | | ({ 104 | type: "updateAction"; 105 | params: Parameters[0]; 106 | } & VendorExtend) 107 | | ({ 108 | type: "playAction"; 109 | params: Parameters[0]; 110 | } & VendorExtend) 111 | | ({ 112 | type: "stopAction"; 113 | params: Parameters[0]; 114 | } & VendorExtend) 115 | | ({ 116 | type: "customAction"; 117 | params: Parameters[0]; 118 | } & VendorExtend); 119 | }[]; 120 | } & VendorExtend 121 | ) => Promise; 122 | } 123 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/src/definitions/render.ts: -------------------------------------------------------------------------------- 1 | import { VendorExtend } from "./types"; 2 | 3 | /** 4 | * A RenderCharacteristics is a set of characteristics / capabilities 5 | * of the Renderer, that affects how the Graphic will be rendered. 6 | */ 7 | export type RenderCharacteristics = { 8 | resolution?: { 9 | width: number; 10 | height: number; 11 | } & VendorExtend; 12 | /** Which frameRate the renderer will be rendering in. Examples: 50, 60, 29.97 */ 13 | frameRate?: number; 14 | 15 | // Ideas for future: 16 | // webcamInputs 17 | // accessToPublicInternet?: boolean // Whether the renderer has access to the public internet (so the graphic can fetch resources) 18 | // keyer?: boolean; // 19 | 20 | } & VendorExtend; 21 | 22 | // These are inspired by the MediaTrackConstraints Web API. 23 | // see https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints 24 | 25 | export type ConstrainBoolean = { 26 | /** A Boolean which must be the value of the property. If the property can't be set to this value, matching will fail. */ 27 | exact?: boolean; 28 | 29 | /** A Boolean specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. */ 30 | ideal?: boolean; 31 | } & VendorExtend; 32 | 33 | export type ConstrainNumber = { 34 | /** A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail. */ 35 | max?: number; 36 | 37 | /** A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail. */ 38 | min?: number; 39 | 40 | /** A number specifying a specific, required, value the property must have to be considered acceptable. */ 41 | exact?: number; 42 | 43 | /** A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. */ 44 | ideal?: number; 45 | } & VendorExtend; 46 | 47 | /** The ConstrainString constraint type is used to specify a constraint for a property whose value is a string. */ 48 | export type ConstrainString = { 49 | /** A string or an array of strings, one of which must be the value of the property. If the property can't be set to one of the listed values, matching will fail. */ 50 | exact: T | T[]; 51 | 52 | /** A string or an array of strings, specifying ideal values for the property. If possible, one of the listed values will be used, but if it's not possible, the user agent will use the closest possible match. */ 53 | ideal: T | T[]; 54 | } & VendorExtend; 55 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/src/definitions/types.ts: -------------------------------------------------------------------------------- 1 | import { VendorSpecific } from "./vendor"; 2 | 3 | /** 4 | * Default return data of any action 5 | */ 6 | export type ReturnPayload = { 7 | /** 8 | * HTTP response code. 200 if the method was executed successfully, 4xx if client error, 5xx if server error 9 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 10 | */ 11 | statusCode: number; 12 | /** 13 | * (Optional) A human-readable message to help understand the status code. 14 | */ 15 | statusMessage?: string; 16 | } & VendorExtend; 17 | 18 | /** 19 | * This indicates that a payload is empty 20 | * (but a vendor may choose to add their own vendor-specific properties) 21 | */ 22 | export type EmptyPayload = VendorExtend; 23 | 24 | /** 25 | * This indicates that the Parameters object is empty 26 | * (but a vendor may choose to add their own vendor-specific properties) 27 | */ 28 | export type EmptyParams = VendorExtend; 29 | 30 | /** 31 | * All parameters and return values can be extended with vendor-specific properties 32 | */ 33 | export interface VendorExtend { 34 | [vendorSpecific: VendorSpecific]: unknown; 35 | } 36 | 37 | /** Payload when invoking an action of a GraphicInstance or a Renderer */ 38 | export type ActionInvokeParams = { 39 | /** Graphic id, as defined by the Graphic manifest*/ 40 | id: string; 41 | /** Params to send into the method */ 42 | payload: unknown; 43 | } & VendorExtend; 44 | 45 | export type PlayActionReturnPayload = (ReturnPayload | {}) & { 46 | /** The resulting step from a PlayAction */ 47 | currentStep: number; 48 | }; 49 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/src/definitions/vendor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vendors MUST use the prefix "v_" in any additional properties to ensure forward compatibility. For example "v_SuperFlyFluxCapacitorStatus". 3 | */ 4 | export type VendorSpecific = `v_${VendorName}${string}`; 5 | type VendorName = string; 6 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/src/generated/graphics-manifest.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by json-schema-to-typescript. 4 | * DO NOT MODIFY IT BY HAND! Instead, modify the source JSON Schema file, 5 | * and run 'npm run generate-types' to regenerate this file. 6 | */ 7 | 8 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson = 9 | CoreAndValidationSpecificationsMetaSchema & 10 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibGddTypesJson & 11 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibBasicTypesJson & { 12 | type: "boolean" | "string" | "number" | "integer" | "array" | "object"; 13 | gddType?: string; 14 | gddOptions?: { 15 | [k: string]: unknown; 16 | }; 17 | [k: string]: unknown; 18 | }; 19 | export type CoreAndValidationSpecificationsMetaSchema = CoreVocabularyMetaSchema & 20 | ApplicatorVocabularyMetaSchema & 21 | UnevaluatedApplicatorVocabularyMetaSchema & 22 | ValidationVocabularyMetaSchema & 23 | MetaDataVocabularyMetaSchema & 24 | FormatVocabularyMetaSchemaForAnnotationResults & 25 | ContentVocabularyMetaSchema & { 26 | /** 27 | * @deprecated 28 | */ 29 | definitions?: { 30 | [k: string]: { 31 | [k: string]: unknown; 32 | }; 33 | }; 34 | /** 35 | * @deprecated 36 | */ 37 | dependencies?: { 38 | [k: string]: 39 | | { 40 | [k: string]: unknown; 41 | } 42 | | string[]; 43 | }; 44 | /** 45 | * @deprecated 46 | */ 47 | $recursiveAnchor?: string; 48 | /** 49 | * @deprecated 50 | */ 51 | $recursiveRef?: string; 52 | [k: string]: unknown; 53 | } & ( 54 | | { 55 | /** 56 | * @deprecated 57 | */ 58 | definitions?: { 59 | [k: string]: { 60 | [k: string]: unknown; 61 | }; 62 | }; 63 | /** 64 | * @deprecated 65 | */ 66 | dependencies?: { 67 | [k: string]: 68 | | { 69 | [k: string]: unknown; 70 | } 71 | | string[]; 72 | }; 73 | /** 74 | * @deprecated 75 | */ 76 | $recursiveAnchor?: string; 77 | /** 78 | * @deprecated 79 | */ 80 | $recursiveRef?: string; 81 | [k: string]: unknown; 82 | } 83 | | boolean 84 | ); 85 | export type CoreVocabularyMetaSchema = { 86 | $id?: string; 87 | $schema?: string; 88 | $ref?: string; 89 | $anchor?: string; 90 | $dynamicRef?: string; 91 | $dynamicAnchor?: string; 92 | $vocabulary?: { 93 | [k: string]: boolean; 94 | }; 95 | $comment?: string; 96 | $defs?: { 97 | [k: string]: { 98 | [k: string]: unknown; 99 | }; 100 | }; 101 | [k: string]: unknown; 102 | } & ( 103 | | { 104 | $id?: string; 105 | $schema?: string; 106 | $ref?: string; 107 | $anchor?: string; 108 | $dynamicRef?: string; 109 | $dynamicAnchor?: string; 110 | $vocabulary?: { 111 | [k: string]: boolean; 112 | }; 113 | $comment?: string; 114 | $defs?: { 115 | [k: string]: { 116 | [k: string]: unknown; 117 | }; 118 | }; 119 | [k: string]: unknown; 120 | } 121 | | boolean 122 | ); 123 | export type ApplicatorVocabularyMetaSchema = { 124 | /** 125 | * @minItems 1 126 | */ 127 | prefixItems?: [ 128 | { 129 | [k: string]: unknown; 130 | }, 131 | ...{ 132 | [k: string]: unknown; 133 | }[] 134 | ]; 135 | items?: { 136 | [k: string]: unknown; 137 | }; 138 | contains?: { 139 | [k: string]: unknown; 140 | }; 141 | additionalProperties?: { 142 | [k: string]: unknown; 143 | }; 144 | properties?: { 145 | [k: string]: { 146 | [k: string]: unknown; 147 | }; 148 | }; 149 | patternProperties?: { 150 | [k: string]: { 151 | [k: string]: unknown; 152 | }; 153 | }; 154 | dependentSchemas?: { 155 | [k: string]: { 156 | [k: string]: unknown; 157 | }; 158 | }; 159 | propertyNames?: { 160 | [k: string]: unknown; 161 | }; 162 | if?: { 163 | [k: string]: unknown; 164 | }; 165 | then?: { 166 | [k: string]: unknown; 167 | }; 168 | else?: { 169 | [k: string]: unknown; 170 | }; 171 | /** 172 | * @minItems 1 173 | */ 174 | allOf?: [ 175 | { 176 | [k: string]: unknown; 177 | }, 178 | ...{ 179 | [k: string]: unknown; 180 | }[] 181 | ]; 182 | /** 183 | * @minItems 1 184 | */ 185 | anyOf?: [ 186 | { 187 | [k: string]: unknown; 188 | }, 189 | ...{ 190 | [k: string]: unknown; 191 | }[] 192 | ]; 193 | /** 194 | * @minItems 1 195 | */ 196 | oneOf?: [ 197 | { 198 | [k: string]: unknown; 199 | }, 200 | ...{ 201 | [k: string]: unknown; 202 | }[] 203 | ]; 204 | not?: { 205 | [k: string]: unknown; 206 | }; 207 | [k: string]: unknown; 208 | } & ( 209 | | { 210 | /** 211 | * @minItems 1 212 | */ 213 | prefixItems?: [ 214 | { 215 | [k: string]: unknown; 216 | }, 217 | ...{ 218 | [k: string]: unknown; 219 | }[] 220 | ]; 221 | items?: { 222 | [k: string]: unknown; 223 | }; 224 | contains?: { 225 | [k: string]: unknown; 226 | }; 227 | additionalProperties?: { 228 | [k: string]: unknown; 229 | }; 230 | properties?: { 231 | [k: string]: { 232 | [k: string]: unknown; 233 | }; 234 | }; 235 | patternProperties?: { 236 | [k: string]: { 237 | [k: string]: unknown; 238 | }; 239 | }; 240 | dependentSchemas?: { 241 | [k: string]: { 242 | [k: string]: unknown; 243 | }; 244 | }; 245 | propertyNames?: { 246 | [k: string]: unknown; 247 | }; 248 | if?: { 249 | [k: string]: unknown; 250 | }; 251 | then?: { 252 | [k: string]: unknown; 253 | }; 254 | else?: { 255 | [k: string]: unknown; 256 | }; 257 | /** 258 | * @minItems 1 259 | */ 260 | allOf?: [ 261 | { 262 | [k: string]: unknown; 263 | }, 264 | ...{ 265 | [k: string]: unknown; 266 | }[] 267 | ]; 268 | /** 269 | * @minItems 1 270 | */ 271 | anyOf?: [ 272 | { 273 | [k: string]: unknown; 274 | }, 275 | ...{ 276 | [k: string]: unknown; 277 | }[] 278 | ]; 279 | /** 280 | * @minItems 1 281 | */ 282 | oneOf?: [ 283 | { 284 | [k: string]: unknown; 285 | }, 286 | ...{ 287 | [k: string]: unknown; 288 | }[] 289 | ]; 290 | not?: { 291 | [k: string]: unknown; 292 | }; 293 | [k: string]: unknown; 294 | } 295 | | boolean 296 | ); 297 | export type UnevaluatedApplicatorVocabularyMetaSchema = { 298 | unevaluatedItems?: { 299 | [k: string]: unknown; 300 | }; 301 | unevaluatedProperties?: { 302 | [k: string]: unknown; 303 | }; 304 | [k: string]: unknown; 305 | } & ( 306 | | { 307 | unevaluatedItems?: { 308 | [k: string]: unknown; 309 | }; 310 | unevaluatedProperties?: { 311 | [k: string]: unknown; 312 | }; 313 | [k: string]: unknown; 314 | } 315 | | boolean 316 | ); 317 | export type ValidationVocabularyMetaSchema = { 318 | type?: 319 | | ("array" | "boolean" | "integer" | "null" | "number" | "object" | "string") 320 | | [ 321 | "array" | "boolean" | "integer" | "null" | "number" | "object" | "string", 322 | ...("array" | "boolean" | "integer" | "null" | "number" | "object" | "string")[] 323 | ]; 324 | const?: unknown; 325 | enum?: unknown[]; 326 | multipleOf?: number; 327 | maximum?: number; 328 | exclusiveMaximum?: number; 329 | minimum?: number; 330 | exclusiveMinimum?: number; 331 | maxLength?: number; 332 | minLength?: number; 333 | pattern?: string; 334 | maxItems?: number; 335 | minItems?: number; 336 | uniqueItems?: boolean; 337 | maxContains?: number; 338 | minContains?: number; 339 | maxProperties?: number; 340 | minProperties?: number; 341 | required?: string[]; 342 | dependentRequired?: { 343 | [k: string]: string[]; 344 | }; 345 | [k: string]: unknown; 346 | } & ( 347 | | { 348 | type?: 349 | | ("array" | "boolean" | "integer" | "null" | "number" | "object" | "string") 350 | | [ 351 | "array" | "boolean" | "integer" | "null" | "number" | "object" | "string", 352 | ...("array" | "boolean" | "integer" | "null" | "number" | "object" | "string")[] 353 | ]; 354 | const?: unknown; 355 | enum?: unknown[]; 356 | multipleOf?: number; 357 | maximum?: number; 358 | exclusiveMaximum?: number; 359 | minimum?: number; 360 | exclusiveMinimum?: number; 361 | maxLength?: number; 362 | minLength?: number; 363 | pattern?: string; 364 | maxItems?: number; 365 | minItems?: number; 366 | uniqueItems?: boolean; 367 | maxContains?: number; 368 | minContains?: number; 369 | maxProperties?: number; 370 | minProperties?: number; 371 | required?: string[]; 372 | dependentRequired?: { 373 | [k: string]: string[]; 374 | }; 375 | [k: string]: unknown; 376 | } 377 | | boolean 378 | ); 379 | export type MetaDataVocabularyMetaSchema = { 380 | title?: string; 381 | description?: string; 382 | default?: unknown; 383 | deprecated?: boolean; 384 | readOnly?: boolean; 385 | writeOnly?: boolean; 386 | examples?: unknown[]; 387 | [k: string]: unknown; 388 | } & ( 389 | | { 390 | title?: string; 391 | description?: string; 392 | default?: unknown; 393 | deprecated?: boolean; 394 | readOnly?: boolean; 395 | writeOnly?: boolean; 396 | examples?: unknown[]; 397 | [k: string]: unknown; 398 | } 399 | | boolean 400 | ); 401 | export type FormatVocabularyMetaSchemaForAnnotationResults = { 402 | format?: string; 403 | [k: string]: unknown; 404 | } & ( 405 | | { 406 | format?: string; 407 | [k: string]: unknown; 408 | } 409 | | boolean 410 | ); 411 | export type ContentVocabularyMetaSchema = { 412 | contentEncoding?: string; 413 | contentMediaType?: string; 414 | contentSchema?: { 415 | [k: string]: unknown; 416 | }; 417 | [k: string]: unknown; 418 | } & ( 419 | | { 420 | contentEncoding?: string; 421 | contentMediaType?: string; 422 | contentSchema?: { 423 | [k: string]: unknown; 424 | }; 425 | [k: string]: unknown; 426 | } 427 | | boolean 428 | ); 429 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibGddTypesJson = { 430 | [k: string]: unknown; 431 | }; 432 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibBasicTypesJson = { 433 | [k: string]: unknown; 434 | }; 435 | /** 436 | * The schema is used by a Graphic to define the data parameters of the 'update' method. 437 | */ 438 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson1 = 439 | CoreAndValidationSpecificationsMetaSchema & 440 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibGddTypesJson & 441 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibBasicTypesJson & { 442 | type: "boolean" | "string" | "number" | "integer" | "array" | "object"; 443 | gddType?: string; 444 | gddOptions?: { 445 | [k: string]: unknown; 446 | }; 447 | [k: string]: unknown; 448 | }; 449 | 450 | export interface HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasGraphicsSchemaJson { 451 | /** 452 | * Reference to the JSON-schema for this manifest 453 | */ 454 | $schema: "https://ograf.ebu.io/v1-draft-0/specification/json-schemas/graphics/schema.json"; 455 | /** 456 | * The id of the Graphic uniquely identifies it. It is recommended to use a reverse domain name notation. For example: com.my-company.my-lowerthird. 457 | */ 458 | id: string; 459 | /** 460 | * The version of the Graphic. The version SHOULD be alphabetically sortable. Examples: ['0', '1', '2'], ['1.0', '1.1', '1.2'], ['2024-07-01_final', '2024-07-01_final_final2'] 461 | */ 462 | version?: string; 463 | /** 464 | * The main entry point, ie the path to the main javascript file of the Graphic. 465 | */ 466 | main?: string; 467 | /** 468 | * Name of the Graphic 469 | */ 470 | name: string; 471 | /** 472 | * (optional) A longer description of the Graphic 473 | */ 474 | description?: string; 475 | /** 476 | * (optional) About the author 477 | */ 478 | author?: { 479 | /** 480 | * Name of the author 481 | */ 482 | name: string; 483 | /** 484 | * (optional) Email of the author 485 | */ 486 | email?: string; 487 | /** 488 | * (optional) URL of the author 489 | */ 490 | url?: string; 491 | /** 492 | * This interface was referenced by `undefined`'s JSON-Schema definition 493 | * via the `patternProperty` "^v_.*". 494 | */ 495 | [k: string]: unknown; 496 | }; 497 | /** 498 | * Custom Actions that can be invoked on the Graphic. 499 | */ 500 | customActions?: HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibActionJson[]; 501 | /** 502 | * Indicates if the Graphic supports real-time rendering 503 | */ 504 | supportsRealTime: boolean; 505 | /** 506 | * Indicates if the Graphic supports non-real-time rendering. Note: If true, the Graphic must implement the 'goToTime()' and the 'setActionsSchedule()' methods. 507 | */ 508 | supportsNonRealTime: boolean; 509 | /** 510 | * The number of steps in the Graphic. If the Graphic is simply triggered by a play, then a stop, this is considered a stepCount of 1 (defaults to 1). 511 | */ 512 | stepCount?: number; 513 | schema?: HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson1; 514 | /** 515 | * A list of requirements that this Graphic has for the rendering environment. At least one of the requirements must be met for the graphic to be expected to work. 516 | */ 517 | renderRequirements?: { 518 | /** 519 | * If set, specifies requirements for the resolution of the Renderer. 520 | */ 521 | resolution?: { 522 | width?: HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson; 523 | height?: HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson; 524 | [k: string]: unknown; 525 | }; 526 | frameRate?: HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson1; 527 | /** 528 | * This interface was referenced by `undefined`'s JSON-Schema definition 529 | * via the `patternProperty` "^v_.*". 530 | */ 531 | [k: string]: unknown; 532 | }[]; 533 | /** 534 | * This interface was referenced by `HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasGraphicsSchemaJson`'s JSON-Schema definition 535 | * via the `patternProperty` "^v_.*". 536 | */ 537 | [k: string]: unknown; 538 | } 539 | export interface HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibActionJson { 540 | /** 541 | * The identity of the action. The id must be unique within the graphic. 542 | */ 543 | id: string; 544 | /** 545 | * The name of the action. This is displayed to the user. 546 | */ 547 | name: string; 548 | /** 549 | * A longer description of the action. This is displayed to the user. 550 | */ 551 | description?: string; 552 | /** 553 | * The schema of the action. This is used to validate the action parameters as well as auto-generate a GUI for the action. If the action does not require any parameters, set this to null. 554 | */ 555 | schema?: HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson | null; 556 | /** 557 | * This interface was referenced by `HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibActionJson`'s JSON-Schema definition 558 | * via the `patternProperty` "^v_.*". 559 | */ 560 | [k: string]: unknown; 561 | } 562 | /** 563 | * The number constraint is used to specify a constraint for a numerical property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble) 564 | */ 565 | export interface HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson { 566 | /** 567 | * A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail. 568 | */ 569 | max?: number; 570 | /** 571 | * A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail. 572 | */ 573 | min?: number; 574 | /** 575 | * A number specifying a specific, required, value the property must have to be considered acceptable. 576 | */ 577 | exact?: number; 578 | /** 579 | * A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. 580 | */ 581 | ideal?: number; 582 | /** 583 | * This interface was referenced by `HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson`'s JSON-Schema definition 584 | * via the `patternProperty` "^v_.*". 585 | * 586 | * This interface was referenced by `HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson1`'s JSON-Schema definition 587 | * via the `patternProperty` "^v_.*". 588 | */ 589 | [k: string]: unknown; 590 | } 591 | /** 592 | * The number constraint is used to specify a constraint for a numerical property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble) 593 | */ 594 | export interface HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson1 { 595 | /** 596 | * A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail. 597 | */ 598 | max?: number; 599 | /** 600 | * A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail. 601 | */ 602 | min?: number; 603 | /** 604 | * A number specifying a specific, required, value the property must have to be considered acceptable. 605 | */ 606 | exact?: number; 607 | /** 608 | * A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. 609 | */ 610 | ideal?: number; 611 | /** 612 | * This interface was referenced by `HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson`'s JSON-Schema definition 613 | * via the `patternProperty` "^v_.*". 614 | * 615 | * This interface was referenced by `HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibConstraintsNumberJson1`'s JSON-Schema definition 616 | * via the `patternProperty` "^v_.*". 617 | */ 618 | [k: string]: unknown; 619 | } 620 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/src/main.ts: -------------------------------------------------------------------------------- 1 | export * as GraphicsAPI from "./apis/graphicsAPI"; 2 | 3 | export * from "./definitions/render"; 4 | export * from "./definitions/types"; 5 | export * from "./definitions/vendor"; 6 | import * as GeneratedGraphicsManifest from "./generated/graphics-manifest"; 7 | 8 | export { GeneratedGraphicsManifest } 9 | 10 | // Also export the GraphicsManifest types using simplified names 11 | export type GraphicsManifest = GeneratedGraphicsManifest.HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasGraphicsSchemaJson 12 | export type GraphicsManifestCustomAction = GeneratedGraphicsManifest.HttpsOgrafEbuIoV1Draft0SpecificationJsonSchemasLibActionJson 13 | 14 | -------------------------------------------------------------------------------- /v1-draft-0/typescript-definitions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": ["ES2018"], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "exclude": [ 67 | "./test", 68 | "./dist" 69 | ] 70 | } 71 | --------------------------------------------------------------------------------