콘텐츠로 이동

서버에서 렌더링하기

이 레시피를 마치면 Linux 서버의 컨테이너에서 GPU나 CPU만으로 프로젝트를 렌더링하고, Node.js에서 렌더 작업 큐를 처리할 수 있습니다.

서버는 네이티브 렌더러인 alpha2-host를 실행합니다. 브라우저 프리뷰와 같은 렌더링 코드를 쓰므로 같은 프레임을 그립니다. 결정성을 참고하십시오.

  • glibc 2.35 이상인 Linux x64 또는 arm64. 릴리스 아카이브는 glibc용으로 빌드되며, Alpine 같은 musl 시스템은 거부됩니다. npx @alpha2-sh/node install은 아무것도 내려받기 전에 멈추고, 그런 시스템에서 시작한 호스트는 HOST_LOAD_FAILED로 실패합니다. Debian 12와 Ubuntu 22.04 이후는 조건을 만족합니다.
  • @alpha2-sh/node에는 Node.js 22.21 이상(Node 24는 24.5 이상), alpha2 Python 패키지에는 Python 3.11.4 이상이 필요합니다.
  • GPU 렌더링에는 그 GPU의 Vulkan 드라이버가 필요합니다. 하드웨어 디코딩은 Vulkan Video를 씁니다. NVIDIA GPU의 NVENC에는 드라이버 530.41.03 이상이 필요하고, 컨테이너 안에서는 드라이버의 video capability가 있어야 합니다.
  • CPU 렌더링에는 더 필요한 것이 없습니다. CPU 렌더러는 그래픽 드라이버를 전혀 열지 않습니다.

아카이브의 lib/에 FFmpeg 8 라이브러리가 들어 있으므로 이미지에 FFmpeg 패키지는 필요 없습니다.

alpha2-host는 시작할 때 렌더러를 정합니다. export의 --renderer나 환경 변수 ALPHA2_RENDERER를 보며, serve와 Node.js·Python 클라이언트는 자체 renderer 옵션과 함께 이 변수를 읽습니다.

모드 렌더링 위치 실패하는 경우
auto(기본값) 하드웨어 GPU가 있으면 GPU, 없으면 CPU GPU가 없다는 이유로는 실패하지 않음
gpu 소프트웨어 어댑터를 포함한 아무 GPU 어댑터 어댑터가 없으면 GPU_INIT_FAILED
cpu CPU 렌더러 GPU가 없다는 이유로는 실패하지 않음

auto는 소프트웨어 어댑터(lavapipe, llvmpipe, WARP, SwiftShader)로는 절대 렌더링하지 않습니다. 같은 그림을 CPU 렌더러보다 몇 배나 느리게 그리기 때문입니다. 그래서 소프트웨어 Vulkan 드라이버만 있는 클라우드 VM에서는 auto가 CPU로 렌더링합니다.

CPU 렌더러는 GPU와 같은 렌더 그래프를 그리며, 그 결과는 어느 머신에서든, 스레드 수와 관계없이 같은 바이트입니다. 프로세스가 쓸 수 있는 스레드를 모두 쓰며, ALPHA2_RENDER_THREADS로 수를 정할 수 있습니다. 내장 이펙트와 트랜지션은 모두 그리지만, 런타임 WGSL 이펙트는 GPU가 필요하므로 CPU에서는 EFFECT_ERROR로 실패합니다.

어느 렌더러가 그렸는지는 export 결과에 나옵니다. renderer(gpu 또는 cpu), auto가 CPU를 고른 이유인 rendererReason, CPU일 때는 rendererThreads입니다.

하드웨어 인코더 없이 H.264 만들기

섹션 제목: “하드웨어 인코더 없이 H.264 만들기”

아카이브의 FFmpeg는 LGPL 빌드라 소프트웨어 H.264 인코더가 없습니다. 하드웨어 H.264 인코더가 없는 서버(CPU 전용 머신, arm64 서버, A100이나 H100 같은 NVIDIA 데이터센터 GPU)에는 Cisco의 OpenH264나 다른 코덱이 필요합니다.

  • npx @alpha2-sh/node install --openh264(또는 python -m alpha2 install --openh264)로 OpenH264를 설치합니다. Cisco 라이선스에 따라 Cisco에서 바이너리를 직접 내려받습니다.
  • 또는 런타임이 CPU로 인코딩하는 코덱으로 export합니다. VP9나 AV1(.mp4, .mkv, .webm에서 --video-codec vp9), 또는 mpeg4-part2입니다.

getCapabilities()는 아무것도 렌더링하기 전에 encode.h264.status(available, gpu-only, unavailable)를 reason, remedy와 함께 알려 줍니다.

이 이미지는 @alpha2-sh/node, 같은 버전의 호스트 아카이브, OpenH264를 설치합니다. alpha2의 설치 테스트는 node:22-slim과 python:3.12-slim 이미지에서 호스트를 실행합니다.

Dockerfile
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# 빌드 사용자와 실행 사용자가 같은 캐시를 씁니다.
ENV ALPHA2_HOST_CACHE_DIR=/opt/alpha2
RUN npx @alpha2-sh/node install && npx @alpha2-sh/node install --openh264
ENV ALPHA2_RENDERER=cpu
COPY . .
CMD ["node", "worker.mjs"]
  • npx @alpha2-sh/node install은 패키지 버전에 맞는 릴리스 아카이브를 내려받아, 패키지에 고정된 SHA-256으로 검사합니다.
  • 설치는 캐시 디렉터리에 씁니다. ALPHA2_HOST_CACHE_DIR를 지정하면 컨테이너를 어떤 사용자로 실행하든 빌드 시점과 실행 시점의 캐시가 같아집니다.
  • 이미지 빌드에서 OpenH264를 설치하는 것이 Cisco 약관이 요구하는 내려받기입니다. 공개 배포하는 이미지에 이 라이브러리를 복사해 넣지 마십시오.

GPU 렌더링을 하려면 GPU의 Vulkan 드라이버가 든 이미지에서 시작하고, 컨테이너에 GPU를 넘기고, ALPHA2_RENDERER는 비워 두거나(auto) GPU가 없을 때 바로 실패하도록 gpu로 두십시오.

터미널 창
alpha2-host export --renderer cpu \
--project job.json \
--asset clip-1=/data/in/clip-1.mp4 \
--font /data/fonts/Inter.ttf \
--out /data/out/job.mp4

이 명령은 결과를 JSON으로 표준 출력에, 진행 상황을 표준 오류에 씁니다. 성공하면 0, 실패하면 1, GPU 디바이스를 만들지 못하면 70으로 종료합니다. CLI 레퍼런스를 참고하십시오.

alpha2-host conform은 렌더링도 GPU도 없이 작업의 미디어를 검사하고, export를 막는 것이 있으면 1로 종료합니다. 작업이 렌더 슬롯을 차지하기 전에 저렴하게 확인할 수 있습니다.

Alpha2NodeClient는 export마다 별도의 alpha2-host 프로세스를 실행합니다. concurrency는 동시에 실행할 수, 곧 머신을 함께 쓸 수이며, 나머지 요청은 슬롯을 기다립니다.

worker.mjs
import { Alpha2HostError, Alpha2NodeClient } from "@alpha2-sh/node";
import { isRetryableFailure } from "alpha2/export-retry";
const client = new Alpha2NodeClient({ concurrency: 2 });
// 타임라인에 보이는 모든 것이 끝나는 시점.
function projectEnd(project) {
let end = 0;
for (const track of project.timeline.tracks) {
for (const clip of track.clips) end = Math.max(end, clip.timeline_range.end);
for (const cue of track.captions?.cues ?? []) end = Math.max(end, cue.timeline_range.end);
}
return end;
}
async function render(job) {
const { project } = job;
for (let attempt = 1; ; attempt++) {
try {
return await client.exportProject({
project,
assets: job.assets, // { "<source-id>": "/data/in/..." }
fonts: job.fonts,
startTicks: 0,
endTicks: projectEnd(project),
frameRate: project.settings.frame_rate,
output: job.output,
overwrite: true,
onProgress: ({ phase, completed, total }) => job.report?.(phase, completed, total),
});
} catch (error) {
if (!(error instanceof Alpha2HostError)) throw error;
if (attempt < 3 && isRetryableFailure(error.failure)) continue;
throw error; // 잘못된 입력, 없는 미디어, 취소 등: 재시도해도 소용없음
}
}
}
// 큐에서 작업을 가져옵니다. 여기서는 async iterable입니다.
for await (const job of queue) {
render(job).then(job.done, job.fail);
}
  • 프로젝트는 프로세스를 시작하기 전에 검증됩니다. 잘못된 프로젝트는 problems를 담은 Alpha2ProjectValidationError로 거부됩니다. 미디어가 없거나 오프라인이면 아무것도 렌더링하기 전에 conform report를 담은 Alpha2ConformError로 거부됩니다.
  • error.failure는 모든 실패를 분류합니다(디바이스 손실, 메모리 부족, 잘못된 입력 등). alpha2/export-retry의 isRetryableFailure는 재시도로 해결되지 않는 실패에 false를 돌려주고, decideRetry는 CPU 렌더러로 바꾸는 것까지 제안합니다. import하려면 @alpha2-sh/node 옆에 alpha2도 설치하십시오.
  • 커널의 OOM killer가 호스트를 종료하면 HOST_OOM_KILLED로 실패합니다. concurrency를 낮추거나 export의 memory 예산을 줄이십시오.
  • signal(AbortSignal)로 export를 취소할 수 있습니다. 호스트가 멈추고 부분 파일을 지운 뒤, 호출은 Alpha2AbortError로 거부됩니다.
  • 출력은 최종 경로 옆에 <stem>.a2partial.<ext>로 쓰였다가 완성되면 제자리로 옮겨지므로, 읽는 쪽이 반쯤 쓰인 파일을 볼 일이 없습니다.