가입하면 프리미엄 플랜 할인할인 받기

블로그/developers·2026년 9월 24일·5분·eroq 팀 작성

films API로 Cinema 프로젝트 다루기: 대본부터 내보내기까지

Cinema의 대본 기반 기능. /v1/films의 v2 영화 프로젝트 구조, 렌더 라우트가 대본을 읽는 방식, 미디어 재서명, 편집본 저장과 게시까지.


Cinema 워크스페이스가 하는 모든 일은 /v1/films를 거치고, 스튜디오도 다른 클라이언트와 똑같이 이 API를 쓰는 클라이언트일 뿐이에요. 이 가이드는 영화를 만들거나 자동화하려는 개발자를 위한 거예요. 라우트가 불투명하게 다루는 프로젝트 포맷, 대본을 읽는 렌더 호출, 미디어 맵, 내보내기 라우트, 그리고 각종 한도를 다뤄요. v1 기본 내용은 스토리보드 렌더링 가이드를 읽었다고 가정하고, 여기서는 v2에서 추가된 부분을 설명해요.

프로젝트

영화 행(row)에는 title, cover_url, published_creation_id, 그리고 data 문서가 있어요. data가 프로젝트 전체예요. 라우트는 크기(400 KB)를 검증하고 읽을 때 정규화할 뿐, 나머지는 보낸 그대로 저장해요. v2 구조는 다음과 같아요.

{
  "v": 2,
  "look": {
    "model": "seedance-2-0-mini",
    "aspect": "16:9",
    "resolution": "720p",
    "audio": true,
    "seed": 4242,
    "filmType": "noir", "era": "1960s", "tempo": "tense",
    "cameraType": "35mm film", "lens": "anamorphic", "aperture": "f/1.4",
    "palette": "neon noir", "lighting": "practicals",
    "chain": true,
    "negative": ""
  },
  "scenes": [
    {
      "id": "s1", "heading": "EXT. JAZZ CLUB — NIGHT",
      "prompt": "A detective in a rain-soaked trench coat waits under a flickering neon sign, slow push-in.",
      "shot": "push-in", "seconds": 5, "cast": ["char:9f3…"], "takes": 1,
      "dialogue": [{ "id": "l1", "speaker": "char:9f3…", "manner": "whispering", "line": "It's still lit." }],
      "options": [{ "libraryId": "cr_…", "contentType": "video/mp4", "seconds": 5 }],
      "pick": 0
    }
  ],
  "script": [
    { "id": "b1", "type": "h1", "html": "Rooftop night" },
    { "id": "b2", "type": "scene", "sceneId": "s1" },
    { "id": "b3", "type": "p", "html": "Rain machine on for this one." }
  ],
  "edit": { "auto": true, "clips": [], "audio": [], "titles": [], "fadeOut": 0 }
}

씬의 대사는 씬 안에 들어 있어요(dialogue: char:<id> 형식이나 자유롭게 쓴 이름으로 된 화자, 말투, 대사). 그 밖의 대본 블록은 모두 메모이고 엔진에 전달되지 않아요. 정규화 단계가 네 가지 규칙을 대신 지켜 줘요. 모든 씬은 대본에 정확히 하나의 scene 블록을 가지고(짝이 없는 블록은 삭제되고, 빠진 블록은 끝에 덧붙여져요), 씬 순서는 대본 순서를 따르고, 초기 초안의 dialogue 블록은 바로 위 씬으로 합쳐지며(어떤 씬보다 먼저 쓰인 블록은 메모가 돼요), options의 모든 libraryId는 창작물 ID예요. 미디어는 프로젝트에 절대 저장되지 않고 가리키기만 해요. v1 초안(look + prompt/shot/seconds/cast/takes를 가진 scenes)은 읽을 때 이 구조로 정규화되기 때문에, 기존 연동도 그대로 동작해요.

한도: 씬 24개, 씬당 30초, 씬당 옵션 8개, 씬당 대사 30줄, 블록 400개, 클립 80개, 오디오 클립 40개, 타이틀 40개, 프롬프트 또는 블록당 4,000자.

생성, 조회, 수정

POST /v1/films
{ "title": "Rooftop night", "data": { … } }

GET /v1/films              → cards: scenes, rendered, seconds, aspect, cover, preview
GET /v1/films/{id}         → the row + data + media
PATCH /v1/films/{id}       → title and/or data (whole document)
DELETE /v1/films/{id}      → the draft; renders stay in the library

GET /v1/films/{id}는 프로젝트 옆에 media 맵을 함께 반환해요. 프로젝트가 가리키는 모든 테이크, 클립, 사운드가 creation:<id> 또는 upload:<id> 키로 들어 있고, 각각 새로 서명된 URL, 콘텐츠 타입, 길이, 썸네일을 담고 있어요. 저장소가 비공개라 URL은 만료돼요. 이 맵 덕분에 클라이언트는 파일마다 키를 갖지 않고도 동작하는 링크를 얻을 수 있어요. 일부만 갱신하려면(서명된 URL이 만료되기 전이나 새 파일이 미디어 목록에 추가됐을 때) { "keys": ["creation:…", "upload:…"] }(1~300개)로 POST /v1/films/media를 호출하세요. 해당 키에 대해 같은 구조를 반환해요. 알 수 없거나 다른 계정의 키는 그냥 빠져요.

PATCH는 data를 통째로 교체해요. 읽고, 수정하고, 쓰세요. 부분 병합은 없고, 오래된 쓰기가 더 새로운 쓰기를 덮어쓸 수 있으니 쓰기 작업은 하나씩 순서대로 처리하세요.

렌더

POST /v1/films/{id}/render
{ "store": false }

한 번의 호출로 프롬프트가 있는 모든 씬을 룩에 지정된 엔진에서 테이크당 비동기 동영상 작업 하나씩으로 대기열에 넣어요. POST /v1/videos/generations와 같은 파이프라인이고, 각각 따로 과금돼요. 응답은 각 씬 ID를 작업 ID에, 또는 대기열에 들어가지 못한 이유인 오류에 매핑해요. 씬은 서로 독립적으로 실패하고, 과금된 실패는 자동으로 환불돼요. 작업은 다른 렌더와 똑같이 폴링하세요.

v2에서 라우트가 씬마다 구성하는 내용은 씬 헤딩(슬러그라인), 지문, 그리고 dialogue 대사(Mina (whispering) says: "…")이고, 화자의 캐릭터 시트와 보이스가 레퍼런스에 합류해요. 스튜디오가 보내는 것과 같은 프롬프트예요. 룩은 audio와 seed를 포함해 모든 씬에 적용돼요. 과금 전에 엔진 한도에 맞춰 포맷, 해상도, 길이, 레퍼런스가 조정되고, 엔진에 없는 기능을 요구하는 씬은 몰래 축소되는 게 아니라 거부돼요.

테이크가 완성되면 그 libraryId와 함께 씬의 options에 기록하고 pick을 설정하세요. 스튜디오는 이걸 대신 해 주고, 연동에서는 폴링한 뒤 직접 해야 해요. 이어지는 샷(look.chain)은 클라이언트 동작이에요. 스튜디오가 이전 테이크의 마지막 프레임을 캡처해 업로드하고 첫 번째 레퍼런스로 넘겨요. API에서 체인을 원한다면 씬별 POST /v1/videos/generations에 references[0]을 직접 보내세요. 일괄 렌더 라우트는 씬을 쓰인 그대로 렌더해요.

편집과 내보내기

edit 문서는 순수한 데이터예요. { "kind": "scene", "sceneId" }, creation, upload를 참조하는 클립(인/아웃 트림, 트랜지션, 볼륨, 맞춤 방식 포함), 페이드가 적용된 세 트랙의 오디오 클립, 네 가지 스타일의 타이틀, 그리고 페이드아웃. 스튜디오는 이걸 브라우저 안에서 MP4로 렌더해요. API는 편집을 서버에서 렌더하지 않아요.

API가 하는 일은 결과를 보관하는 거예요.

POST /v1/films/{id}/export?seconds=42.5&signature=<edit signature>
Content-Type: video/mp4

<raw MP4 bytes>

바디는 파일 자체이고, 스트리밍으로 보내요. 결과물은 라이브러리에 동영상 창작물로 들어가서 다른 클립처럼 재생, 다운로드, 게시할 수 있고, 다른 라이브러리 파일과 마찬가지로 워크스페이스 저장 용량에 포함돼요. 무료예요. signature는 내보낼 때의 편집 상태를 해시한 값이에요. 영화에 기록되고, 이후 편집이 바뀌면 저장된 편집본은 오래된 것이 돼요. 이전 편집본이 한 번도 게시되지 않았고 편집에서 쓰이지도 않는다면, 다시 내보낼 때 그 편집본을 대체해요.

업로드와 같은 레이트 리밋이 적용되고, 버킷이 가득 차 있으면 아무것도 읽기 전에 거부해요.

게시

POST /v1/films/{id}/cover        (multipart file, image ≤ 5 MB, free)
POST /v1/films/{id}/publish
{ "title": "Rooftop night", "segments": ["<the saved cut's URL>"], "durationSeconds": 42 }

저장된 편집본이 있는 영화는 그 편집본을 단일 세그먼트로 게시하고, 없는 영화는 선택된 테이크를 순서대로 게시해요. 모든 세그먼트는 라이브러리에 있는 동영상이어야 해요. 게시물은 검토를 거쳐요(GET /v1/films/{id}에서 review가 pending으로 시작해요). DELETE /v1/films/{id}/publish로 게시를 내려도 좋아요와 댓글 같은 반응은 그대로 남아요.

API가 하지 않는 일

  • 편집 렌더. 합성은 스튜디오에서 하고, API는 결과를 저장해요.
  • 마지막 프레임 캡처. 씬마다 references[0]을 보내서 체인을 만드세요.
  • 원본 저장소 URL 제공. 모든 URL은 GET /v1/films/{id}나 POST /v1/films/media를 통해 서명돼요.

자주 묻는 질문

프로젝트 포맷이 문서화되어 있나요?

라우트는 data를 크기 제한이 있는 불투명한 JSON으로 다루고 읽을 때 정규화해요. 위 구조는 스튜디오가 쓰는 형태예요. 필드는 안정적이고, 새로 추가되는 필드는 선택 사항이에요.

이미 테이크가 있는 씬도 렌더 라우트에서 과금되나요?

프롬프트가 있는 모든 씬을 렌더하고, 테이크가 있는 씬도 포함돼요. 씬 하나만 다시 렌더하려면 그 씬에 대해 POST /v1/videos/generations를 호출하고 결과를 해당 씬의 options에 기록하세요.

직접 찍은 영상을 영화에 업로드할 수 있나요?

POST /v1/uploads로 업로드한 뒤 편집에서 { "kind": "upload", "id": … }로 참조하세요. 미디어 맵이 다른 테이크처럼 서명해 줘요.

렌더에 store 옵션이 있는 이유는 무엇인가요?

store: true는 모든 클립을 Store 요금(10 MB당 2 크레딧)으로 저장소에 영구 보관해요. 기본값은 무료 라이브러리 사본을 유지해요. 어느 쪽이든 Cinema의 테이크는 라이브러리에 남아요.

모든 필드는 films 레퍼런스에서, v1 단계별 설명은 스토리보드 가이드에서 확인하세요.

태그films-apicinemaapivideostoryboard

이 글에 나온 모델로 직접 만들어 보세요 — 무료 크레딧 50개로 시작하거나, 모든 엔진과 가격도 살펴보세요.