확장
This content is for v0.4. Switch to the latest version for up-to-date documentation.
확장 프로바이더는 일반 비디오·이미지 디코더가 읽지 못하는 포맷의 프레임을 공급합니다. 이 페이지를 읽고 나면 이런 소스를 프로젝트에 선언하고, 네이티브 호스트로 export하고, 브라우저에서 확장 모듈로 렌더링할 수 있습니다.
이 포맷들은 어디서나 같은 Rust 코드가 읽습니다. alpha2-host에는 이 코드가 내장되어 있고, 브라우저는 페이지가 이런 소스를 열 때만 별도의 WebAssembly 모듈(alpha2_extensions_bg.wasm)로 로드합니다.
프로바이더
섹션 제목: “프로바이더”소스는 provider에 프로바이더를 지정합니다. 프로바이더의 format에 따라 소스의 kind가 정해집니다.
provider.format |
읽는 대상 | 소스 kind |
설정 |
|---|---|---|---|
svg |
정적 SVG 문서. 화면에 표시되는 크기로 래스터화합니다 | image |
|
animated_image |
GIF, 애니메이션 WebP 또는 APNG. 프레임마다 고유한 길이를 갖는 한 주기의 프레임입니다. 반복은 클립이 정합니다. | video |
|
lottie |
JSON 파일 또는 dotLottie 패키지 형태의 Lottie 애니메이션. 자체 프레임 단위로 평가합니다 | video |
animation: 보여 줄 dotLottie 애니메이션의 id. 없으면 패키지의 첫 애니메이션입니다. |
exr |
정지 OpenEXR 이미지. 선형 광(linear-light) RGB(A)를 하프 정밀도로 읽습니다 | image |
primaries: 파일의 색도 정보를 덮어씁니다 |
image_sequence |
고정 속도로 차례대로 재생하는 PNG 또는 OpenEXR 정지 이미지 | video |
frames, frame_rate, start_number, missing, primaries |
primaries는 bt709, bt2020, p3_d65, aces_ap0, aces_ap1 중 하나입니다. 지정하지 않으면 OpenEXR 파일 자체의 색도 정보를 쓰고, 그것도 없으면 BT.709를 씁니다. 색상과 HDR을 참고하세요.
이미지 시퀀스
섹션 제목: “이미지 시퀀스”이미지 시퀀스는 각 프레임의 로케이터를 재생 순서대로 나열합니다.
frames: 로케이터 목록.null은 빠진 번호를 나타내며,missing이 그 표시 방법을 지정할 때만 쓸 수 있습니다.frame_rate: 프레임을 재생하는 속도,{ "num", "den" }.start_number: 첫 프레임의 번호. 파일에 매겨진 번호를 그대로 씁니다(기본값0).missing:reject(기본값),hold(이전 프레임 표시),blank중 하나.
모든 프로바이더를 쓰는 프로젝트
섹션 제목: “모든 프로바이더를 쓰는 프로젝트”{ "schema_version": 1, "id": "extensions", "name": "Extensions", "settings": { "width": 1280, "height": 720, "frame_rate": { "num": 30, "den": 1 }, "sample_rate": 48000 }, "sources": [ { "id": "logo", "kind": "image", "locator": "logo.svg", "provider": { "format": "svg" } }, { "id": "sticker", "kind": "video", "locator": "sticker.gif", "provider": { "format": "animated_image" } }, { "id": "intro", "kind": "video", "locator": "intro.lottie", "provider": { "format": "lottie", "animation": "main" } }, { "id": "plate", "kind": "image", "locator": "plate.exr", "provider": { "format": "exr", "primaries": "p3_d65" } }, { "id": "render", "kind": "video", "locator": "sequence://render", "provider": { "format": "image_sequence", "frames": ["render_0001.png", "render_0002.png", null, "render_0004.png"], "frame_rate": { "num": 24, "den": 1 }, "start_number": 1, "missing": "hold" } } ], "timeline": { "tracks": [ { "id": "back", "name": "Background", "kind": "video", "clips": [ { "id": "render-clip", "source_id": "render", "timeline_range": { "start": 0, "end": 20000 }, "source_range": { "start": 0, "end": 20000 } }, { "id": "plate-clip", "source_id": "plate", "timeline_range": { "start": 20000, "end": 120000 }, "source_range": { "start": 0, "end": 100000 } } ] }, { "id": "front", "name": "Graphics", "kind": "video", "clips": [ { "id": "intro-clip", "source_id": "intro", "timeline_range": { "start": 0, "end": 60000 }, "source_range": { "start": 0, "end": 60000 } }, { "id": "logo-clip", "source_id": "logo", "timeline_range": { "start": 60000, "end": 120000 }, "source_range": { "start": 0, "end": 60000 } } ] }, { "id": "top", "name": "Sticker", "kind": "video", "clips": [ { "id": "sticker-clip", "source_id": "sticker", "timeline_range": { "start": 0, "end": 120000 }, "source_range": { "start": 0, "end": 120000 } } ] } ] }}엔진은 소스의 locator를 해석하지 않으며, 직접 열지도 않습니다. 로케이터의 의미는 호스트가 정하므로, 같은 프로젝트가 네이티브에서는 디스크의 파일로, 브라우저에서는 페이지가 가져온 바이트로 동작합니다.
네이티브로 export하기
섹션 제목: “네이티브로 export하기”alpha2-host는 비디오와 이미지를 읽을 때와 같은 방식으로, 넘겨받은 파일에서 프로바이더 소스를 읽습니다.
| 대상 | CLI | 요청 필드(Node.js, Python, 프로토콜) |
|---|---|---|
| SVG, 애니메이션 이미지, Lottie, OpenEXR 소스의 파일 | --asset <source-id>=<path> |
assets |
| 이미지 시퀀스의 프레임 파일(순서대로, 빈 자리는 빈 경로) | --frames <source-id>=<path>,<path>,... |
frames(빈 자리는 null) |
SVG <image href>나 Lottie 이미지 에셋이 참조하는 이미지. 소스에 적힌 로케이터로 지정합니다 |
--resource <source-id> <locator> <path> |
resources |
| SVG 텍스트용 폰트 | --font <path> |
fonts |
alpha2-host export --project project.json \ --asset logo=art/logo.svg \ --asset sticker=art/sticker.gif \ --asset intro=art/intro.lottie \ --asset plate=plates/plate.exr \ --frames render=render/render_0001.png,render/render_0002.png,,render/render_0004.png \ --font fonts/HankenGrotesk.ttf \ --out extensions.mp4같은 export를 Node.js에서 실행하면 다음과 같습니다.
await client.exportProject({ project, assets: { logo: "art/logo.svg", sticker: "art/sticker.gif", intro: "art/intro.lottie", plate: "plates/plate.exr", }, frames: { render: ["render/render_0001.png", "render/render_0002.png", null, "render/render_0004.png"], }, resources: { logo: { "art/badge.png": "/media/badge.png" } }, // logo.svg 안의 <image href> fonts: ["fonts/HankenGrotesk.ttf"], startTicks: 0, endTicks: 120_000, frameRate: { num: 30, den: 1 }, output: "extensions.mp4",});resources 항목에 없는 참조 이미지는 그려지지 않으며, export는 missing_image를 보고합니다. memory.providerSourceBytes(기본값 512 MiB)는 열려 있는 프로바이더 소스가 차지하는 메모리의 상한입니다. 이를 넘으면 직전 두 프레임에 보이지 않은 소스를 닫고, 필요할 때 다시 엽니다.
브라우저에서 렌더링하기
섹션 제목: “브라우저에서 렌더링하기”브라우저에서는 alpha2/extensions가 이 포맷들을 읽고, 여러분은 렌더링된 프레임을 다른 프레임과 똑같이 엔진에 업로드합니다(프레임 공급 참고).
- 페이지마다 한 번
loadExtensions()로 모듈을 로드합니다. - SVG 텍스트를 그릴 폰트를
setFonts로 등록합니다. - 각 프로바이더 소스를 파일 바이트로 엽니다:
open(provider, bytes). frameNeeds가 보고하는 요구마다 해당 소스 시간으로 소스를 렌더링하고 결과를 업로드합니다.
import { loadExtensions } from "alpha2/extensions";
// 번들러가 alpha2/wasm/alpha2_extensions_bg.wasm을 옮긴다면 그 URL을 넘깁니다.const extensions = await loadExtensions();extensions.setFonts([fontBytes]); // 네이티브 export와 같은 폰트를 같은 순서로 넘깁니다
const bytesOf = async (url) => new Uint8Array(await (await fetch(url)).arrayBuffer());const opened = new Map();for (const source of project.sources) { if (!source.provider || source.provider.format === "image_sequence") continue; opened.set(source.id, { kind: source.kind, source: extensions.open(source.provider, await bytesOf(`/media/${source.locator}`)), });}
const { width, height } = project.settings;const time = 60_000n;for (const need of JSON.parse(engine.frameNeeds(time))) { const entry = opened.get(need.sourceId); if (!entry) continue; // 프로바이더 소스가 아닙니다 const ticks = BigInt(need.mediaTimeTicks); const frame = entry.source.render(ticks, { width, height }); const still = entry.kind === "image"; if (frame.precision === "linear_f16") { // OpenEXR: BT.709 원색 기준의 프리멀티플라이드 선형 광 하프 플로트. if (still) engine.uploadPresentedLinearImage(need.sourceId, frame.width, frame.height, frame.pixels, width, height); else engine.uploadPresentedLinearFrame(need.sourceId, ticks, frame.width, frame.height, frame.pixels, width, height); } else if (still) { engine.uploadPresentedRgbaImage(need.sourceId, frame.width, frame.height, frame.pixels, true, width, height); } else { engine.uploadPresentedRgbaFrame(need.sourceId, ticks, frame.width, frame.height, frame.pixels, true, width, height); }}engine.renderAt(time);render(ticks, fit)은 벡터 소스(SVG, Lottie)를 종횡비를 유지한 채fit에 맞게 래스터화하고, 래스터 소스는 원래 크기로 렌더링합니다.uploadPresented*메서드는 엔진이 모든 소스를 배치하는 방식과 똑같이 프레임을 출력에 배치합니다.- 8비트 프레임은 프리멀티플라이드 sRGB RGBA8(
precision: "rgba8")입니다. OpenEXR 프레임은precision: "linear_f16"이며, 0~1 범위 밖의 값도 유지됩니다. frame.diagnostics는missing_font,missing_image,unsupported_feature처럼 사용자에게 보여 줄 만한 알림을 나열합니다.source.reader()는 같은 디코딩 데이터를 공유하면서 위치를 따로 갖는 리더를 만듭니다. 한 애니메이션을 서로 멀리 떨어진 시간에서 읽는 두 클립에 씁니다.source.release()로 해제합니다.
이미지 시퀀스와 참조 이미지
섹션 제목: “이미지 시퀀스와 참조 이미지”이미지 시퀀스는 자체 바이트를 받지 않습니다. 시퀀스의 프레임은 SVG가 참조하는 이미지와 마찬가지로 로케이터로 등록하는 리소스입니다. withResources는 호출 한 번에 필요한 것만 정확히 등록하고 끝나면 해제하므로, 시퀀스는 한 번에 한 프레임씩만 모듈에 들어갑니다.
const provider = project.sources.find((source) => source.id === "render").provider;const read = (locator) => bytesOf(`/media/render/${locator}`);
const sequence = await extensions.withResources( read, () => extensions.openNeeds(provider), () => extensions.open(provider),);const frame = await extensions.withResources( read, () => sequence.needs(ticks), () => sequence.render(ticks, { width, height }),);리소스를 직접 관리하려면 registerResource(locator, bytes)와 releaseResource(locator)를 씁니다. 두 방식 중 하나만 쓰세요. withResources는 직접 등록한 로케이터도 해제합니다.
파일 프로브하기
섹션 제목: “파일 프로브하기”extensions.probe(bytes)는 프로바이더들에게 파일을 읽을 수 있는지 묻습니다. { provider, report }를 반환하고, 그 바이트를 맡는 프로바이더가 없으면 null을 반환하므로 그때는 여러분의 디코더로 읽으면 됩니다.
const found = extensions.probe(bytes);if (found) { const { format, width, height, timing, loop_count, items, color } = found.report; // timing: { kind: "static" }, { kind: "frames", durations } 또는 { kind: "rate", rate, frames }}report.items는 dotLottie 패키지의 애니메이션 목록입니다. 그중 하나를 provider.animation으로 넘기세요.
모듈 실패에서 복구하기
섹션 제목: “모듈 실패에서 복구하기”모듈은 Rust 코드를 실행하므로, 손상된 파일을 만나면 중단될 수 있습니다. ExtensionModuleError는 호출 도중 모듈이 중단되었다는 뜻입니다. 모듈은 이미 재시작했으므로 이후 호출은 동작하지만, 관련된 소스는 실패 상태로 남습니다. StaleExtensionSourceError는 재시작 전에 열린 소스라는 뜻입니다. 리소스를 다시 등록하고 소스를 다시 여세요.
