블로그/developers·2026년 9월 8일·5분·eroq 팀 작성
eroq Films API로 스토리보드 렌더링: 모든 씬을 한 번에
룩과 순서가 정해진 씬으로 영화 레시피를 만들고, 렌더 호출 한 번으로 모든 씬을 비동기 작업으로 촬영한 뒤, 작업을 폴링하고 커버와 함께 게시해요.
프롬프트 하나는 클립 하나를 만들어요. 영화는 같은 룩을 공유하며 이어 붙는 클립의 연속이고, 스튜디오의 영화 모드에서는 씬을 추가하고, 렌더하고, 다음 씬을 추가하는 식으로 대화하듯 영화를 만들어요. Films API는 같은 일을 클릭 없이 해요. 스토리보드를 한 번 정의하고, 호출 한 번으로 모든 씬을 촬영하고, 작업을 폴링하고, 게시하면 끝이에요. 같은 광고를 열 명의 클라이언트를 위해 열 가지 버전으로 만든다면, 바로 이 엔드포인트가 필요했던 거예요.
영화는 미디어가 아니라 레시피예요
POST /v1/films는 제목과 data 객체를 받은 그대로 저장해요. API는 렌더하기 전까지 레시피를 해석하지 않고, 렌더 결과는 영화가 아니라 항상 창작물에 저장돼요. 구성은 두 부분이에요.
look— 모든 씬이 공유하는 설정이에요.model,aspect,filmType,era,tempo,cameraType,lens,aperture,resolution.scenes—{ prompt, shot, seconds, cast }의 순서 있는 배열이에요.shot은 카메라 무빙이고,cast는 내 캐릭터 id 목록이에요.
curl https://eroq.ai/v1/films \
-H "Authorization: Bearer $EROQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Rooftop night — cut 1",
"data": {
"look": {
"model": "eroq-motion-one", "aspect": "16:9",
"filmType": "noir", "era": "1960s", "tempo": "tense",
"cameraType": "35mm", "lens": "anamorphic", "aperture": "f1-4"
},
"scenes": [
{ "prompt": "A woman in a silver dress steps out onto a rooftop bar at dusk, city lights flickering on below, slow pan following her to the railing, warm practicals against the deep blue sky, wind in her hair, expectant mood.", "shot": "slow-pan", "seconds": 5 },
{ "prompt": "Close-up of her hands on the cold railing, a glass of something amber beside them, push-in as the skyline blurs behind, neon reflections crawling across the glass, quiet and tense.", "shot": "push-in", "seconds": 5 },
{ "prompt": "Wide shot from behind as she turns toward the door, a silhouette waiting there against the bar light, crane-up revealing the whole rooftop and the city beyond, patient, ominous mood.", "shot": "crane-up", "seconds": 10 }
]
}
}'
응답에는 영화 id가 담겨 있어요. 초안은 계정당 최대 50개예요. GET /v1/films/{id}는 전체 레시피를 반환하고, PATCH는 title이나 data를 수정하고, DELETE는 초안을 지우되 렌더 결과는 라이브러리에 남겨 둬요. 스튜디오의 영화 모드도 바로 이 초안을 저장하고 불러오기 때문에, 스크립트로 만든 레시피를 /studio/video에서 열어 손으로 고칠 수 있어요. 반대 방향도 마찬가지고요.
씬 프롬프트는 엔진이 좋아하는 방식으로 쓰세요. 피사체, 움직임, 카메라, 빛, 분위기를 담은 자연스러운 한 문단으로요. 나머지는 룩이 채워 주니, 씬마다 "필름 누아르, 1960년대"를 반복하지 마세요.
호출 한 번으로 전부 촬영하기
curl -X POST https://eroq.ai/v1/films/FILM_ID/render \
-H "Authorization: Bearer $EROQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "store": true }'
프롬프트가 있는 모든 씬은 비동기 동영상 작업이 돼요. 씬마다 룩의 파라미터가 합쳐진 POST /v1/videos/generations가 하나씩 생기고, 호출은 씬별 보고서와 함께 202로 응답해요.
{
"id": "FILM_ID",
"scenes": [
{ "scene": 0, "id": "job-a…", "status": "processing" },
{ "scene": 1, "id": "job-b…", "status": "processing" },
{ "scene": 2, "id": "job-c…", "status": "processing" }
],
"usage": { "credits_spent": 380, "credits_remaining": 5620 }
}
씬은 하나씩 따로 과금되고 순서대로 대기열에 들어가기 때문에, 지갑이 바닥나면 남은 씬은 반쯤 과금되는 일 없이 깔끔하게 멈춰요. 대기열에 넣을 수 없는 씬은 그 자리에서 오류를 보고하고(예를 들어 잠긴 엔진은 plan_required로 응답해요), 나머지 씬은 그대로 진행돼요. 나중에 렌더가 실패한 씬은 자동으로 환불돼요.
플랜 조건은 룩의 model에 적용돼요. Motion One은 모든 계정에서 쓸 수 있는 무검열 엔진이고, Seedance, Kling, Hailuo, Veo는 각 제공업체가 업스트림에서 검수하며 해당 엔진을 여는 플랜이 필요해요. 씬당 seconds도 플랜에 따라 상한이 있어요(10, 15, 20, 30 s).
작업 폴링하기
대기열에 들어간 각 씬은 평범한 동영상 작업이에요. status가 succeeded나 failed가 될 때까지 몇 초마다 GET /v1/videos/generations/{id}를 폴링하세요. 폴링은 무료예요.
const BASE = 'https://eroq.ai/v1'
const headers = { Authorization: `Bearer ${process.env.EROQ_API_KEY}` }
async function renderFilm(filmId) {
const report = await fetch(`${BASE}/films/${filmId}/render`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({ store: true }),
}).then(r => r.json())
const segments = []
for (const scene of report.scenes.filter(s => s.status === 'processing')) {
let job
do {
await new Promise(r => setTimeout(r, 4000))
job = await fetch(`${BASE}/videos/generations/${scene.id}`, { headers }).then(r => r.json())
} while (job.status === 'processing')
if (job.status === 'succeeded') segments[scene.scene] = job.data[0].url
}
return segments.filter(Boolean)
}
씬이 세 개라면 순차 폴링으로 충분해요. 서른 개라면 병렬로 폴링하거나 video.generation.succeeded 웹훅을 구독해서 URL이 도착하는 대로 모으세요. 어느 쪽이든 순서대로 정렬된 클립 URL이 남고, 게시에 필요한 게 정확히 그거예요.
커버와 함께 게시하기
호출은 두 번이에요. 먼저 커버예요. 5 MB까지의 이미지를 멀티파트로 올리고, 무료예요.
curl -X POST https://eroq.ai/v1/films/FILM_ID/cover \
-H "Authorization: Bearer $EROQ_API_KEY" \
-F "[email protected]"
그다음 영화 자체예요.
curl -X POST https://eroq.ai/v1/films/FILM_ID/publish \
-H "Authorization: Bearer $EROQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Rooftop night",
"segments": ["https://…/scene-1.mp4", "https://…/scene-2.mp4", "https://…/scene-3.mp4"],
"durationSeconds": 20
}'
segments는 순서가 있는 URL 1~24개이고, 모두 내 라이브러리에 호스팅된 렌더여야 해요. 외부 미디어는 거부돼요. 영화는 커뮤니티 진열대에 세그먼트를 이어서 재생하는 항목 하나로 올라가고, 커버는 카드와 플레이어에 표시돼요. 보는 사람에게는 제목, 커버, 클립, 각종 수치가 보이고, 프롬프트와 룩, 캐스트는 비공개로 유지돼요. DELETE /v1/films/{id}/publish로 게시를 내리면 좋아요와 댓글은 다시 게시할 때를 위해 보관돼요. 초안을 삭제하면 커뮤니티 항목도 함께 삭제되지만, 씬 렌더는 라이브러리에 남아요.
에이전시를 위한 일괄 작업
규모를 키우는 패턴은 이래요. 클라이언트마다 템플릿 역할을 하는 레시피 하나, 변수를 바꿔 끼우는 반복문, 그리고 팀 전체가 지갑 하나를 함께 쓰는 워크스페이스예요.
- 변형은 레시피예요. 같은 세 씬이라도 YouTube용
16:9버전과 릴스용9:16버전은 룩이 다른 영화 두 편이에요. 만들고, 렌더하고, 폴링하고, 전달하세요. 검토 단계 전까지는 사람이 끼어들 필요가 없어요. - 비용은 산수예요. 5초, 5초, 10초짜리 세 씬으로 된 Motion One 영화는 180 크레딧, 입문 팩 요율로 약 $1.80 정도예요. Studio 플랜의 월 20,000 크레딧이면 이런 영화를 대략 50편 만들 수 있고, 요청 한도는 4배예요. 테이크 수만큼 숫자가 곱해지니, 검토에 실제로 필요한 대안이 몇 개인지 미리 정해 두세요.
- 처리량은 키 단위예요. 동영상 요청은 키마다 요청 한도가 있어요(기본 등급에서 분당 6회, 플랜에 따라 배수 적용). 파이프라인마다 별도의 키를 주면 한 클라이언트의 일괄 작업이 다른 클라이언트의 작업을 막을 일이 없어요.
- 캐릭터는 함께 다녀요. 클라이언트의 고정 진행자를
cast에 넣으면 이미지 투 비디오를 통해 모든 씬에서 같은 얼굴이 유지돼요. 더 넓은 워크플로는 에이전시 페이지를 참고하세요.
자주 묻는 질문
씬 하나가 실패하면 어떻게 되나요?
다른 씬에는 아무 영향이 없어요. 실패한 씬은 자동으로 환불되니, 같은 룩 파라미터로 POST /v1/videos/generations를 호출해 그 씬만 다시 렌더하고, 그 URL을 segments에 끼워 넣으세요. 영화에 render를 다시 호출하면 모든 씬을 다시 촬영하고 다시 과금해요.
다른 사람이 스튜디오에서 만든 영화를 렌더할 수 있나요?
네. GET /v1/films는 스튜디오의 영화 모드가 저장한 초안을 나열해요. 그중 아무거나 id로 렌더하면 돼요.
게시하는 데 크레딧이 드나요?
아니요. 커버 업로드와 게시 호출은 무료예요. 비용은 씬 렌더에 낸 것이 전부예요.
무료 크레딧으로 세 씬짜리 레시피부터 시작해 보세요. API 키를 받고 바로 촬영하면 돼요.
이 글에 나온 모델로 직접 만들어 보세요 — 무료 크레딧 50개로 시작하거나, 모든 엔진과 가격도 살펴보세요.