호스트 프로토콜
This content is for v0.4. Switch to the latest version for up-to-date documentation.
alpha2-host serve는 표준 입력으로 요청을 받고 표준 출력으로 응답하며, 한 줄에 JSON 객체 하나를 주고받습니다. 이 페이지를 읽고 나면 프로세스를 띄우고 줄 단위로 읽고 쓸 수 있는 어떤 언어로든 alpha2 클라이언트를 작성할 수 있습니다. Go, Python, Ruby, C#, 셸 스크립트 모두 가능합니다.
@alpha2-sh/node와 Python 패키지도 이 프로토콜의 클라이언트입니다. Node.js나 Python에서는 이 패키지들을 쓰세요. 이 페이지는 프로토콜 버전 1을 설명합니다. alpha2-host를 구하는 방법은 CLI에 있습니다.
세션의 흐름
섹션 제목: “세션의 흐름”- 표준 입력과 출력을 파이프로 연결해
alpha2-host serve를 실행합니다. - 요청을 한 줄에 하나씩 씁니다. 각 요청에는
"protocolVersion": 1,requestId(비어 있지 않은 문자열),operation(capabilities,conform,export,still중 하나)이 있습니다. - 같은
requestId를 가진 응답이 올 때까지 줄을 읽습니다. export가 실행되는 동안에는progress줄이 먼저 옵니다. - 더 보낼 요청이 없으면 표준 입력을 닫습니다. 호스트는 입력이 끝날 때까지 요청을 처리한 뒤 종료합니다.
{"protocolVersion":1,"requestId":"1","operation":"capabilities"}호스트는 각 요청에 해당 requestId를 담은 응답 하나로 답합니다.
{"protocolVersion":1,"requestId":"1","ok":true,"result":{...}}{"protocolVersion":1,"requestId":"1","ok":false,"error":{"code":"INVALID_INPUT","message":"..."}}메시지
섹션 제목: “메시지”모든 메시지는 방향과 관계없이 UTF-8 텍스트 한 줄에 담긴 JSON 객체 하나이며, "protocolVersion": 1을 포함합니다.
- 앞선 요청의 응답을 받기 전에 다음 요청을 써도 됩니다. 응답과 요청은
requestId로 짝지으세요. - 호스트가 읽을 수 없는 요청에 쓸 만한
requestId도 없으면"requestId": "unknown"으로 응답합니다. - 요청 한 줄은 최대 128 MiB, 그 안의
project는 최대 64 MiB입니다. - 표준 오류에는 사람이 읽는 진단 메시지만 나오며, 프로토콜 메시지는 나오지 않습니다. 로그로 흘려보내거나 별도 스레드에서 읽으세요. 아무도 읽지 않는 파이프는 가득 차서 호스트를 멈추게 할 수 있습니다.
진행 상황
섹션 제목: “진행 상황”export가 실행되는 동안 호스트는 진행 상황 줄도 씁니다.
{"protocolVersion":1,"type":"progress","requestId":"1","phase":"frames","completed":12,"total":30}phase는 프레임을 인코딩하는 동안 frames, 모든 프레임을 인코딩하고 파일을 마무리하는 동안 finalizing입니다. export는 응답이 도착해야 끝난 것입니다.
호스트가 멈출 때
섹션 제목: “호스트가 멈출 때”- GPU가 필요한 첫 요청에서 호스트가 GPU 디바이스(
hardware-required디코딩 모드에서는 디코딩 디바이스)를 시작하지 못하면{"protocolVersion":1,"type":"fatal","error":{...}}를 쓰고 종료합니다. 그 요청과 이후 요청은 응답을 받지 못합니다.conform에는 GPU가 필요 없습니다. - 디바이스를 잃었거나 디코더 또는 인코더 스레드가 멈추면 호스트는 해당 응답을 쓴 뒤 종료합니다. 이후 요청은 새 프로세스를 띄워 보내세요.
Python으로 만든 완전한 클라이언트
섹션 제목: “Python으로 만든 완전한 클라이언트”이 클라이언트는 Python 표준 라이브러리만 씁니다. 미디어가 없는(단색) 2초짜리 프로젝트를 export하고, 진행 상황을 출력하며, 호스트가 쓰는 모든 종류의 줄을 처리합니다.
"""표준 라이브러리만 사용해 `alpha2-host serve`로 프로젝트를 export합니다."""import jsonimport subprocessimport sys
HOST = sys.argv[1] if len(sys.argv) > 1 else "alpha2-host"
def send(host, message): # 한 줄에 JSON 객체 하나, 프로토콜 버전 1. host.stdin.write(json.dumps({"protocolVersion": 1, **message}) + "\n") host.stdin.flush()
def read(host): line = host.stdout.readline() if not line: raise RuntimeError("alpha2-host exited without answering") return json.loads(line)
with open("project.json", encoding="utf-8") as file: project = json.load(file)
host = subprocess.Popen( [HOST, "serve"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, encoding="utf-8",)
send(host, { "requestId": "export-1", "operation": "export", "project": project, "assets": {}, "startTicks": 0, "endTicks": 240_000, # 2초: 1초에 120,000틱 "frameRate": {"num": 30, "den": 1}, "output": "solid.webm", "overwrite": True,})
while True: message = read(host) if message.get("type") == "progress": print(f"{message['phase']}: {message['completed']}/{message['total']}", file=sys.stderr) elif message.get("type") == "fatal": raise RuntimeError(message["error"]["message"]) elif message.get("requestId") == "export-1": break
host.stdin.close() # 더 보낼 요청 없음: 호스트는 입력이 끝나면 종료합니다host.wait()
if not message["ok"]: error = message["error"] raise SystemExit(f"{error['code']}: {error['message']}")print("wrote", message["result"]["output"])클라이언트가 읽는 프로젝트는 다음과 같습니다.
{ "schema_version": 1, "id": "solid", "name": "Solid", "settings": { "width": 1280, "height": 720, "frame_rate": { "num": 30, "den": 1 }, "sample_rate": 48000 }, "sources": [ { "id": "backdrop", "kind": "generated", "generator": { "type": "fill", "paint": { "type": "solid", "color": { "red": 0.17, "green": 0.36, "blue": 0.99, "alpha": 1 } } } } ], "timeline": { "tracks": [ { "id": "video-1", "name": "Main video", "kind": "video", "clips": [ { "id": "clip-1", "source_id": "backdrop", "timeline_range": { "start": 0, "end": 240000 }, "source_range": { "start": 0, "end": 240000 } } ] } ] }}호스트 경로를 인수로 주어 실행합니다.
python3 export_client.py ./alpha2-0.4.6-linux-x64-bundled/bin/alpha2-host모든 릴리스 아카이브에서 VP9는 소프트웨어로 인코딩되므로, 출력을 WebM으로 해서 GPU나 H.264 인코더가 없는 머신에서도 예제가 실행되게 했습니다. CPU 렌더러를 강제하려면 호스트 환경에 ALPHA2_RENDERER=cpu를 설정하세요.
capabilities
섹션 제목: “capabilities”다른 필드는 받지 않습니다. 결과는 이 호스트가 할 수 있는 일을 보고합니다.
status:"ready".capabilities.gpu: GPU와 그 등급(tier).capabilities.decode: 디코딩 모드와 하드웨어 디코더.capabilities.encode: 컨테이너별로 제공하는 코덱(encode.containers), 비디오와 오디오 인코더, GPU 상주 인코딩 경로,encode.h264.status(available,gpu-only,unavailable중 하나이며reason,remedy가 함께 옴).capabilities.features.effectCatalog: 내장 이펙트와 트랜지션.- 어댑터와 FFmpeg 버전.
conform
섹션 제목: “conform”렌더링하지 않고 GPU도 쓰지 않은 채, 각 소스가 어떤 미디어를 읽게 되는지와 무엇이 이를 막는지 보고합니다.
| 필드 | 타입 | 기본값 | |
|---|---|---|---|
project |
프로젝트 문서 | 필수 | |
assets |
{ "<source-id>": "<path>" } |
{} |
소스별 원본 파일. |
proxies |
{ "<source-id>": [{ "path", "madeFrom"?, "colorPipeline"? }] } |
{} |
원본별 프록시, 선호 순서대로. |
frames |
{ "<source-id>": ["<path>" or null, ...] } |
{} |
이미지 시퀀스별 프레임 파일. 빈 자리는 null. |
purpose |
preview, preview-proxy, thumbnail, remote-edit, export-original, export-proxy |
export-original |
미디어를 읽는 용도. |
결과는 { "catalog": ..., "report": ... }입니다. export를 막는 문제가 있으면 report.ready가 false입니다. 타입은 alpha2/assets의 AssetCatalog와 ConformReport입니다.
export
섹션 제목: “export”프로젝트의 한 구간을 렌더링해 MP4, MOV, Matroska, WebM 파일로 인코딩합니다.
| 필드 | 기본값 | |
|---|---|---|
project |
필수 | 프로젝트 문서. |
assets |
필수 | { "<source-id>": "<path>" }. export가 읽는 소스별 파일. |
startTicks, endTicks |
필수 | 틱 단위 타임라인 범위. 프레임 그리드에 맞게 넓혀집니다. |
frameRate |
필수 | { "num", "den" }. |
output |
필수 | 쓸 파일. container가 없으면 확장자(.mp4, .m4v, .mov, .mkv, .webm)가 컨테이너를 정하며, 확장자는 컨테이너와 일치해야 합니다. |
overwrite |
false |
export가 모두 끝나면 output 위치의 일반 파일을 교체합니다. |
container |
출력 확장자에 따름 | mp4, mov, mkv, webm. |
videoCodec |
컨테이너 기본값 | h264, hevc, vp9, av1, mpeg4-part2. |
audioCodec |
컨테이너 기본값 | aac, opus. |
videoQuality |
high |
very-low, low, medium, high, very-high, 또는 { "bitrate", "mode"?, "quantizer"? }. |
audioQuality |
high |
레벨, 또는 { "bitrate" }. |
includeAudio |
true |
|
loudness |
없음 | { "targetLufs", "truePeakCeilingDbtp" }. includeAudio가 필요합니다. |
fonts |
[] |
폴백 순서대로 나열한 폰트 파일. 텍스트가 나오는 프로젝트에는 필요합니다. |
effects |
[] |
내장 이펙트 외의 런타임 이펙트 정의. |
effectAssets |
{} |
{ "<resource-id>": "<path>" }. .cube LUT 등. |
proxies, frames |
{} |
conform과 같습니다. |
media |
original |
proxies는 최신 프록시가 있는 곳에서 프록시를 읽습니다. |
resources |
{} |
{ "<source-id>": { "<locator>": "<path>" } }. SVG나 Lottie 소스가 참조하는 파일. |
videoEncoder |
호스트가 선택 | capabilities가 보고하는 인코더. |
inputPolicy |
auto |
auto, gpu-required, cpu-readback. |
strategy |
render |
render, prefer-copy, require-copy. |
renderCache |
없음 | { "directory", "quotaBytes"?, "backlog"? }. |
memory |
호스트 기본값 | 바이트 단위 예산: inFlightBytes, decodeQueueBytes, reverseCacheBytes, audioWindowBytes, providerSourceBytes, decoderSessionBytes, scale. |
컨테이너마다 지원하는 코덱이 정해져 있으며, 각 목록의 첫 번째가 기본값입니다.
| 컨테이너 | 비디오 | 오디오 |
|---|---|---|
mp4 |
h264, hevc, vp9, av1, mpeg4-part2 |
aac, opus |
mov |
h264, hevc, mpeg4-part2 |
aac |
mkv |
h264, hevc, vp9, av1, mpeg4-part2 |
aac, opus |
webm |
vp9, av1 |
opus |
결과에는 프레임, 사용한 인코더와 레이트 컨트롤, 프레임이 인코더에 전달된 방식(videoPipeline), 기록한 파일의 절대 경로(output), 각 소스가 읽은 미디어(media), 소요 시간(renderMs, fps) 등이 보고됩니다. @alpha2-sh/node는 요청과 결과의 타입을 ExportProjectRequest와 NativeExportResult로 정의합니다.
호스트는 output 옆에 <stem>.alpha2-partial.<ext>를 쓰고, 완성되면 이름을 바꿔 제자리에 놓습니다. 실행 중인 다른 export가 쓰고 있는 부분 파일이 있으면 overwrite와 관계없이 요청은 OUTPUT_IN_USE로 실패합니다. 강제 종료된 프로세스가 남긴 부분 파일은 이어받습니다.
still
섹션 제목: “still”프레임을 PNG 파일로 렌더링합니다. 각 프레임은 export가 그 시점에 그리는 프레임과 같습니다. 요청 하나는 모든 프레임에 대해 디코딩을 한 번만 하며, 진행 상황 줄은 쓰지 않습니다.
| 필드 | 기본값 | |
|---|---|---|
project, assets |
필수 | export와 같습니다. |
stills |
필수 | [{ "time": <ticks>, "output": "<path>" }, ...], 1~256프레임. |
size |
캔버스 크기 | { "width", "height" }, 각각 1~16384. |
overwrite |
false |
export와 같으며, 모든 출력에 적용됩니다. |
proxies, frames, media, resources, effectAssets, fonts, effects |
export와 같습니다. |
결과는 { "stills": [{ "output", "frameTime", "width", "height" }], "renderer", "renderMs", "timings", "media", "decodeDiagnostics" }이며, 요청한 프레임 순서를 따릅니다. 각 PNG는 8비트 RGBA이고, 디스플레이 인코딩된 sRGB에 straight 알파이며, 프로젝트의 background 위에 불투명하게 그려집니다.
실행 중인 export를 취소하려면 프로세스에 SIGTERM이나 SIGINT(Windows에서는 콘솔 제어 이벤트)를 보냅니다. export는 멈추고 부분 파일을 지운 뒤 CANCELLED로 응답하며, 프로세스는 종료합니다. 유휴 상태의 프로세스는 즉시 종료합니다. 신호를 한 번 더 보내거나 SIGKILL을 보내면 프로세스가 즉시 끝나며, 그렇게 끝난 export는 부분 파일을 남기고 응답을 받지 못합니다.
| 코드 | 의미 |
|---|---|
UNSUPPORTED_PROTOCOL |
protocolVersion이 없거나 1이 아닙니다. |
INVALID_REQUEST |
줄이 요청이 아닙니다. UTF-8이 아님, JSON 객체가 아님, 너무 김, operation이 알 수 없거나 없음, 필드가 없거나 타입이 틀림, requestId가 비어 있음 등입니다. 또는 출력이 요청의 입력 중 하나입니다. |
INVALID_INPUT |
받아들일 수 없는 값입니다. 유효하지 않은 프로젝트, 범위, 레이트, 설정이거나 함께 쓸 수 없는 설정 조합입니다. |
CONFORM_FAILED |
export의 미디어가 준비되지 않았습니다. details가 conform 보고서입니다. |
MISSING_INPUT |
렌더링에 필요한 파일(에셋, 이펙트 리소스, 폰트, 프레임)이 주어지지 않았습니다. |
INCOMPLETE_COMPOSITION |
컴포지션 그래프에 연결되지 않은 입력이나 출력이 있습니다. |
EFFECT_ERROR |
런타임 이펙트의 정의, 파라미터, 셰이더가 실패했습니다. |
GPU_INIT_FAILED |
호스트가 GPU 디바이스를 만들지 못했습니다. |
HARDWARE_DECODE_UNAVAILABLE |
디코딩 모드가 hardware-required인데 호스트가 하드웨어 디코딩을 할 수 없습니다. |
GPU_DEVICE_LOST |
GPU 디바이스를 잃었거나, 디바이스가 주어진 작업을 더 이상 실행하지 않습니다. |
GPU_TIMEOUT |
GPU 작업이 제시간에 끝나지 않았습니다. |
DECODE_STALLED |
디코더가 더 이상 진행하지 않습니다. |
ENCODE_STALLED |
인코더나 먹서가 더 이상 진행하지 않습니다. |
COPY_INELIGIBLE |
strategy: "require-copy"인데 범위를 복사할 수 없습니다. |
GPU_OUTPUT_UNAVAILABLE |
inputPolicy: "gpu-required"인데 GPU 상주 인코딩 경로가 이 export를 처리할 수 없습니다. |
ENCODE_ERROR |
인코딩이나 먹싱이 실패했습니다. |
IO_ERROR |
파일을 읽거나 쓸 수 없거나, output이 이미 있는데 요청이 덮어쓰지 않습니다. |
OUTPUT_IN_USE |
실행 중인 다른 export가 같은 output에 쓰고 있습니다. |
CANCELLED |
export가 취소되었습니다. |
RESOURCE_LIMIT |
예산이나 한도 때문에 작업이 거부되었습니다. 예를 들어 메모리 예산이 export에 비해 너무 작은 경우입니다. |
INTERNAL |
호스트의 결함으로 요청이 실패했습니다. 호스트는 이후 요청에 계속 응답합니다. |
ALPHA2_ERROR |
다른 코드로 설명되지 않는 실패입니다. |
실패한 export에는 error.failure도 있습니다. 실패 분류(code, 예: OUT_OF_MEMORY, DEVICE_LOST, INVALID_INPUT, CANCELLED), 실패한 단계와 프레임, 프로토콜 코드, 진단 정보가 들어 있습니다. 타입은 alpha2/export-failure의 ExportFailure이며, alpha2/export-retry가 이를 바탕으로 재시도 여부를 결정합니다. 검증과 오류를 참고하세요.
