ListenHubDocs
API 레퍼런스설명 영상과 슬라이드

설명 영상과 슬라이드

텍스트나 URL을 내레이션이 있는 페이지 묶음(설명, 스토리, 프레젠테이션 슬라이드)으로 바꾸고 영상으로 렌더링하는 하나의 엔드포인트 패밀리입니다.

설명 영상, 스토리 영상, 프레젠테이션 슬라이드는 하나의 엔드포인트 패밀리의 세 가지 모드입니다. 소스와 보이스를 보내면 ListenHub가 내레이션을 작성하고 페이지마다 비주얼을 생성합니다. 생성된 페이지는 그대로 소재로 활용할 수도 있고, 내레이션이 포함된 영상으로 렌더링할 수도 있습니다.

세 엔드포인트는 모든 모드에서 공통입니다:

엔드포인트용도
POST /v1/storybook/episodes에피소드를 생성합니다. mode로 설명, 스토리, 슬라이드를 선택합니다
GET /v1/storybook/episodes/{episodeId}진행 상태를 폴링한 뒤 페이지와 에셋 URL을 읽습니다
POST /v1/storybook/episodes/{episodeId}/video완성된 페이지를 영상으로 렌더링합니다

이 페이지의 모든 엔드포인트는 OpenAPI Base URL https://api.marswave.ai/openapi를 사용하며, Authorization: Bearer $LISTENHUB_API_KEY 헤더로 API 키를 인증합니다. 키는 listenhub.ai/settings/api-keys에서 만드세요.

모드 선택

mode는 선택 사항이며 기본값은 info입니다. 세 모드 사이에서 다른 필드는 이것 하나뿐이며 — 이 페이지의 나머지 내용은 세 모드 모두에 적용됩니다.

mode생성되는 것1페이지적합한 용도
info (기본값)인포그래픽, 일러스트, 데이터 시각화매거진 스타일 표지지식 설명, 제품 소개
story스토리 장면 일러스트스토리 표지스토리 공유, 사례 연구
slidesPPT 레이아웃(그리드, 프로세스 플로우, 큰 숫자 히어로)프레젠테이션 타이틀 페이지회의 프레젠테이션, 비즈니스 리포트, 컨퍼런스 발표

워크플로

에피소드 생성

소스, 화자, 원하는 mode를 지정해 POST /v1/storybook/episodes를 호출합니다. 반환된 episodeId를 저장하세요.

완료까지 폴링

최초 60초 대기 후, processStatus가 success가 될 때까지 GET /v1/storybook/episodes/{episodeId}를 10초마다 폴링합니다.

원본 소재 활용(선택)

pages[]에는 생성된 이미지와 내레이션 스크립트가 들어 있습니다. 그것만 필요하다면 여기서 끝내도 됩니다.

영상 렌더링

POST /v1/storybook/episodes/{episodeId}/video를 호출해 각 페이지를 내레이션이 포함된 영상으로 합성합니다.

영상 폴링

videoStatus가 success가 될 때까지 폴링한 뒤 videoUrl을 다운로드합니다.

에피소드 생성

POST /v1/storybook/episodes

curl -X POST "https://api.marswave.ai/openapi/v1/storybook/episodes" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      {"type": "url", "content": "https://example.com/article"}
    ],
    "speakers": [
      {"speakerId": "<SPEAKER_ID>"}
    ],
    "language": "en",
    "mode": "info"
  }'
const response = await fetch(
  'https://api.marswave.ai/openapi/v1/storybook/episodes',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      sources: [{ type: 'url', content: 'https://example.com/article' }],
      speakers: [{ speakerId: '<SPEAKER_ID>' }],
      language: 'en',
      mode: 'info',
    }),
  },
)
const { data } = await response.json()
const episodeId = data.episodeId
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/storybook/episodes',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'sources': [{'type': 'url', 'content': 'https://example.com/article'}],
        'speakers': [{'speakerId': '<SPEAKER_ID>'}],
        'language': 'en',
        'mode': 'info',
    },
)
episode_id = response.json()['data']['episodeId']

응답:

{
  "code": 0,
  "message": "",
  "data": { "episodeId": "665f1d4e8b3a3f001234abcd" }
}

요청 파라미터

파라미터타입필수설명
sourcesarray(1)예콘텐츠 소스. 정확히 1개
sources[].typestring예"text" 또는 "url"
sources[].contentstring예텍스트 콘텐츠, 또는 type이 "url"일 때는 URL 자체
sources[].uristring아니요허용되지만 무시됩니다 — url 소스에서는 서버가 content에서 uri를 도출합니다
sources[].metadataobject아니요소스 메타데이터
speakersarray(1)예¹음성 설정. 최대 1개
speakers[].speakerIdstring예화자 ID(화자 참고)
skipAudioboolean아니요기본값은 false. true이면 이미지와 텍스트만 생성되며 내레이션 오디오는 만들어지지 않습니다
languagestring아니요언어 코드(예: "en", "zh"). 기본값은 en이며, 소스에서 추론되지 않습니다
modestring아니요"info"(기본값), "story", "slides" 중 하나
stylestring아니요비주얼 스타일 ID. 생략하면 해당 모드의 기본 스타일이 사용됩니다. 허용되는 ID는 공개 계약에 포함되지 않습니다

¹ skipAudio가 true가 아닌 한 speakers는 필수이며, true인 경우에는 생략할 수 있습니다.

language는 콘텐츠에서 감지되지 않습니다. 생략하면 소스 언어와 상관없이 영어 에피소드가 생성되므로, 영어가 아닌 소스에서는 명시적으로 지정하세요.

비주얼과 스크립트만 필요할 때는 skipAudio: true로 설정하세요 — 오디오 렌더링 없이도 페이지 이미지와 내레이션 텍스트는 에피소드에서 그대로 받을 수 있습니다.

에피소드 폴링

GET /v1/storybook/episodes/{episodeId}

반환된 episodeId로 processStatus가 success가 될 때까지 폴링합니다.

curl "https://api.marswave.ai/openapi/v1/storybook/episodes/{episodeId}" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"

응답(processStatus가 success일 때):

{
  "code": 0,
  "message": "",
  "data": {
    "episodeId": "{episodeId}",
    "createdAt": 1700000000,
    "mode": "info",
    "processStatus": "success",
    "credits": 30,
    "title": "How AI Is Changing the World",
    "cover": "https://assets.listenhub.app/covers/{episodeId}.png",
    "audioUrl": "https://assets.listenhub.app/storybook/{episodeId}.mp3",
    "audioDuration": 180,
    "videoUrl": "",
    "videoStatus": "not_generated",
    "pages": [
      {
        "text": "Artificial intelligence has transformed industries worldwide...",
        "pageNumber": 1,
        "imageUrl": "https://assets.listenhub.app/pages/{episodeId}-1.png",
        "audioTimestamp": 0
      },
      {
        "text": "From healthcare to finance, AI applications continue to expand...",
        "pageNumber": 2,
        "imageUrl": "https://assets.listenhub.app/pages/{episodeId}-2.png",
        "audioTimestamp": 25.3
      }
    ]
  }
}

원본 소재: pages[]의 각 항목에는 imageUrl(생성된 비주얼), text(내레이션 스크립트), audioTimestamp(해당 페이지가 audioUrl에서 시작하는 지점)가 들어 있습니다. 이를 다운로드하면 영상을 렌더링하지 않고도 자체 덱을 구성하거나 편집할 수 있습니다.

응답 필드

필드타입설명
episodeIdstring폴링에 사용한 에피소드 식별자
modestring이 에피소드가 생성된 모드
processStatusstring아래 참고
videoStatusstring아래 참고
creditsnumber이 에피소드가 지금까지 소모한 크레딧
failCodenumber실패 코드. processStatus가 fail일 때 존재합니다
messagestring현재 상태에 대한 사람이 읽을 수 있는 상세 설명
title / coverstring생성된 제목과 표지 이미지
audioUrl / audioDurationstring / number내레이션 오디오와 그 길이(초)
videoUrlstring렌더링된 영상. videoStatus가 success가 된 뒤 제공됩니다
pages[]array페이지별 text, pageNumber, imageUrl, audioTimestamp

크레딧은 미리 견적으로 제시되지 않고 에피소드 단위로 보고됩니다 — 플랫폼이 생성 작업에 어떻게 과금하는지는 크레딧 및 요금을 참고하세요.

processStatus

값의미
pending처리 중
success완료
fail실패 — failCode와 message를 확인하고 에러 처리를 참고하세요

videoStatus

값의미
not_generated영상 생성이 아직 트리거되지 않음
pending영상 생성 중
success영상 준비 완료(videoUrl 사용 가능)
fail영상 생성 실패

생성에는 보통 2–5분이 걸립니다. 권장 폴링 방식: 60초 대기 후 10초마다 폴링합니다.

영상 렌더링

POST /v1/storybook/episodes/{episodeId}/video

완료된 에피소드의 영상 생성을 트리거합니다. 먼저 processStatus가 success여야 합니다.

curl -X POST "https://api.marswave.ai/openapi/v1/storybook/episodes/{episodeId}/video" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"

응답:

{
  "code": 0,
  "message": "",
  "data": { "success": true }
}

트리거한 후에는 videoStatus가 success가 될 때까지 GET /v1/storybook/episodes/{episodeId}를 폴링한 뒤 videoUrl을 읽습니다.

관련 문서

이 페이지의 내용