콘텐츠로 이동

호스트 프로토콜

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에 있습니다.

  1. 표준 입력과 출력을 파이프로 연결해 alpha2-host serve를 실행합니다.
  2. 요청을 한 줄에 하나씩 씁니다. 각 요청에는 "protocolVersion": 1, requestId(비어 있지 않은 문자열), operation(capabilities, conform, export, still 중 하나)이 있습니다.
  3. 같은 requestId를 가진 응답이 올 때까지 줄을 읽습니다. export가 실행되는 동안에는 progress 줄이 먼저 옵니다.
  4. 더 보낼 요청이 없으면 표준 입력을 닫습니다. 호스트는 입력이 끝날 때까지 요청을 처리한 뒤 종료합니다.
{"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하고, 진행 상황을 출력하며, 호스트가 쓰는 모든 종류의 줄을 처리합니다.

export_client.py
"""표준 라이브러리만 사용해 `alpha2-host serve`로 프로젝트를 export합니다."""
import json
import subprocess
import 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"])

클라이언트가 읽는 프로젝트는 다음과 같습니다.

project.json
{
"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를 설정하세요.

다른 필드는 받지 않습니다. 결과는 이 호스트가 할 수 있는 일을 보고합니다.

  • 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 버전.

렌더링하지 않고 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입니다.

프로젝트의 한 구간을 렌더링해 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로 실패합니다. 강제 종료된 프로세스가 남긴 부분 파일은 이어받습니다.

프레임을 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가 이를 바탕으로 재시도 여부를 결정합니다. 검증과 오류를 참고하세요.

  • CLI: 같은 호스트로 export를 한 번에 하나씩 실행합니다.
  • Node.js와 Python: 이 프로토콜의 클라이언트.
  • 프로젝트 모델: project에 들어가는 내용.