튜토리얼
HiAPI API를 통해 omnihuman-1.5를 사용하는 방법: curl 및 Python 튜토리얼
오디오-투-비디오 디지털 휴먼 생성의 전체 요청 실습, 비동기 폴링 및 출력 다운로드
omnihuman-1.5 API의 기능과 적용 시나리오
omnihuman-1.5는 오디오 한 트랙과 인물 이미지 한 장을 디지털 휴먼이 말하는 영상으로 변환합니다. 이미지 속 얼굴이 구동되어 입과 머리 동작이 제공된 음성을 따라 움직입니다. HiAPI에서 이 모델은 오디오-투-비디오 엔드포인트 형태로 제공됩니다. 오디오, 참조 이미지, 소량의 생성 파라미터를 보내면 API가 작업을 반환하고, 해당 작업을 폴링하여 최종 영상을 받습니다.
수동 도구 대비 API의 장점
브라우저 도구는 일회성 편집에 적합합니다. 영상 생성이 더 큰 시스템의 한 단계일 때는 API가 올바른 선택입니다:
- 배치 파이프라인 — 스크립트와 오디오 트랙 큐에서 대량의 디지털 휴먼 클립을 수동 개입 없이 생성합니다.
- 제품 기능 — 앱 내 내레이션, 현지화된 안내 영상, 또는 매 렌더링마다 다른 오디오와 인물을 사용하는 개인화 메시지.
- 백엔드 자동화 — 콘텐츠 워크플로가 영상을 생성한 뒤 CMS, 스토리지 버킷 또는 전달 서비스로 넘깁니다.
요청의 구조적 형태
HiAPI의 모든 omnihuman-1.5 호출은 동일한 형태를 따릅니다. POST로 작업을 제출하고, 후속 요청으로 상태를 확인합니다. 렌더링이 즉시 완료되지 않으므로 통합 패턴은 제출 → 폴링 → 다운로드이며, 제출 → 동일 응답에서 영상 수신이 아닙니다. 이 튜토리얼의 나머지 부분에서는 먼저 curl로 이 흐름을 구성한 뒤 Python으로 구현합니다.
사전 요구 사항 및 HiAPI 자격 증명
첫 호출 전에 세 가지를 확인하세요.
1. 계정 및 API 키
HiAPI 계정과 콘솔에서 생성한 API 키가 필요합니다. 키를 기밀처럼 취급하세요: 소스 관리, 클라이언트 코드, 로그에 넣지 마세요. 개발 시에는 환경 변수에서, 프로덕션에서는 플랫폼의 비밀 관리자에서 로드하세요.
export HIAPI_API_KEY="your-api-key-here"
2. 네트워크 액세스 및 요청 도구
curl을 사용할 때는 비교적 최신 shell이면 충분합니다. Python 버전에는 requests 라이브러리만 있으면 됩니다. API가 JSON 요청 본문 기반의 일반 HTTPS이므로 특정 벤더의 SDK는 필요하지 않습니다.
pip install requests
3. 모델이 요구하는 입력
요청을 작성하기 전에 다음을 준비하세요:
- 오디오 — 입 모션을 구동하는 음성 트랙으로, 엔드포인트가 허용하는 입력 형식에 따라 API가 읽을 수 있는 URL 또는 업로드된 파일 참조 형태로 제공됩니다.
- 인물 이미지 — 애니메이션을 구동할 얼굴. 선명하고 정면이며 입이 보이는 사진이 가장 예측 가능한 결과를 냅니다.
- 생성 파라미터 — 모델이 노출하는 해상도, 길이 또는 품질 옵션. 실제로 필요한 필드만 보내세요. 첫 테스트에서는 기본값이 보통 합리적입니다.
모든 요청은 Bearer <your-key> 형식의 Authorization 요청 헤더로 키를 전달합니다. 첫 호출부터 401이 발생하면 이 헤더를 먼저 확인하세요.
curl로 요청 보내기
제출 호출은 JSON 본문을 담은 POST입니다. omnihuman-1.5의 정확한 경로와 필드명은 해당 모델의 API 참조 페이지에 있습니다. 아래 구조는 호출 형태와 입력할 필드를 보여줍니다.
curl -X POST "https://api.hiapi.ai/v1/omnihuman-1.5/generate" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://your-bucket.example.com/speech.mp3",
"image_url": "https://your-bucket.example.com/portrait.jpg"
}'
필드별 설명
Authorization— API 키를 전달합니다. 이것이 없으면 요청은 어떤 작업도 시작되기 전에 거부됩니다.Content-Type: application/json— 본문이 JSON임을 나타냅니다. 이 헤더를 빠뜨리는 것은 혼란스러운 검증 오류를 일으키는 흔한 원인입니다.audio_url— 음성 트랙. 공개적으로 읽을 수 있거나 API가 다른 방식으로 접근할 수 있는 URL을 사용하고, 렌더링 도중 만료되는 서명된 링크보다 안정적인 위치를 선호하세요.image_url— 애니메이션을 구동할 인물. 동일한 접근성 규칙이 적용됩니다.
반환 내용
성공한 제출은 영상을 반환하지 않습니다. 대신 식별자(보통 id 필드)와 queued 또는 processing 같은 상태를 포함하는 작업 객체를 반환합니다. 이 식별자를 저장하세요. 이후 모든 상태 확인에 사용할 핸들입니다.
{
"id": "job_abc123",
"status": "queued"
}
HTTP 상태 코드와 작업 식별자의 존재 여부를 확인하여 제출 성공을 알 수 있습니다. 응답에 검증 메시지가 포함되어 있으면 잘못된 필드를 가리키며, 보통 누락되었거나 형식이 잘못된 URL입니다.
동일한 요청을 Python으로 구현하기
curl 호출은 Python으로 직접 변환할 수 있습니다. 아래 버전은 서비스에 그대로 넣을 수 있도록 설계되었습니다. 키는 환경 변수에서 오고, 페이로드는 동적으로 구성되는 딕셔너리이며, 오류는 삼켜지지 않고 발생합니다.
import os
import requests
API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def submit_omnihuman_job(audio_url: str, image_url: str, **options) -> str:
payload = {
"audio_url": audio_url,
"image_url": image_url,
}
payload.update(options)
response = requests.post(
f"{BASE_URL}/omnihuman-1.5/generate",
headers=HEADERS,
json=payload,
timeout=60,
)
response.raise_for_status()
data = response.json()
return data["id"]
이렇게 작성하는 이유
json=payload는requests가 요청 본문을 직렬화하고 콘텐츠 타입을 설정하게 하므로 JSON 헤더를 잊지 않습니다.**options는 함수를 유연하게 유지합니다. 생성 파라미터를 추가할 때 페이로드 리터럴을 수정하지 않고 키워드 인수로 전달할 수 있습니다.timeout=60은 멈춘 연결이 작업 프로세스를 무기한 차단하지 않도록 합니다. 제출은 빠른 호출입니다. 긴 대기는 이곳이 아니라 폴링 단계에 두어야 합니다.raise_for_status()는 비2xx 응답을 잡아서 기록할 수 있는 예외로 변환하며, 진단을 위해 응답 본문을 얻을 수 있습니다.
반환된 작업 식별자를 보관하여 폴링 헬퍼에 전달하세요. 다음 섹션에서 해당 헬퍼를 구성합니다.
비동기 수명 주기 처리: 완료될 때까지 폴링
영상 생성에는 시간이 걸리므로 API는 설계상 비동기입니다. 제출 후에는 작업이 종료 상태에 도달할 때까지 상태를 반복 조회합니다.
폴링 루프
import time
def wait_for_job(job_id: str, interval: int = 5, max_wait: int = 600) -> dict:
waited = 0
while waited < max_wait:
response = requests.get(
f"{BASE_URL}/omnihuman-1.5/jobs/{job_id}",
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
job = response.json()
status = job["status"]
if status == "succeeded" or status == "completed":
return job
if status == "failed" or status == "error":
raise RuntimeError(f"Job {job_id} failed: {job}")
time.sleep(interval)
waited += interval
raise TimeoutError(f"Job {job_id} did not finish within {max_wait}s")
중지 시점 결정
세 가지 결과가 루프를 종료하며, 코드는 각각을 처리해야 합니다:
- 성공 — 작업이 완료 상태를 보고하고 출력 영상에 대한 참조를 포함합니다. 종료하고 다운로드를 계속합니다.
- 실패 — 작업이 오류 상태를 보고합니다. 맹목적으로 재시도하지 말고 오류 메시지를 먼저 확인하세요. 실패는 보통 읽을 수 없는 입력 URL이나 지원되지 않는 오디오 때문에 발생합니다.
- 시간 초과 — 자체적인 실제 경과 시간 상한에 도달합니다. 이를 실패와 다른 경우로 취급하세요. 서버 측에서는 작업이 여전히 완료될 수 있으므로 폴링을 계속할지, 시간 창을 늘릴지, 포기할지 의도적으로 결정해야 합니다.
폴링 예절
타이트한 루프 대신 적절한 간격을 사용하세요. 영상 렌더링의 경우 확인 사이에 몇 초 간격이면 충분하며 요청량을 예측 가능하게 만듭니다. 긴 대기열이 예상되면 점진적으로 백오프하고, 멈춘 작업 프로세스가 영원히 폴링하지 않도록 항상 상한을 설정하세요. 프로덕션에서는 웹 요청 핸들러 내부에서 폴링하기보다 백그라운드 작업 프로세스나 작업 큐를 선호하세요.
출력 영상 다운로드 및 저장
폴링이 완료된 작업을 반환하면 응답은 영상 참조를 담고 있습니다. 보통 결과 객체 내의 URL 필드입니다. 이를 가져와 자체 스토리지에 영구 저장하고, 해당 링크에 장기간 의존하지 마세요.
def download_video(job: dict, destination: str) -> None:
video_url = job.get("output_url") or job.get("result", {}).get("video_url")
if not video_url:
raise ValueError(f"No video reference in job payload: {job}")
with requests.get(video_url, stream=True, timeout=120) as response:
response.raise_for_status()
with open(destination, "wb") as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
디버깅 시간을 절약하는 주의 사항
- 스트리밍 다운로드. 영상은 용량이 큽니다. 청크 단위로 쓰면 전체 파일을 메모리에 로드하지 않아 메모리가 제한된 작업 프로세스에서 중요합니다.
- 결정적 파일명 사용. 작업 식별자에 타임스탬프를 더해 파생시키면 실행 추적이 가능하고 재실행 시 충돌하지 않습니다.
- 먼저 임시 파일에 다운로드한 뒤 이동. 임시 경로에 쓰고 완료 후 이름을 바꾸면 부분 다운로드가 완전한 파일로 오인되지 않습니다.
- 반환된 URL을 임시로 취급. 바이트를 자체 버킷이나 파일 시스템에 복사하고, 나중에 조회할 수 있도록 작업 식별자를 저장 객체와 함께 기록하세요.
- 성공 선언 전에 검증. 데이터베이스에서 작업을 완료로 표시하기 전에 다운로드한 파일이 비어 있지 않고 크기가 합리적인지 확인하세요.
일반적인 요청 및 폴링 장애 해결
대부분의 통합 문제는 몇 가지 범주로 나뉩니다. 순서대로 하나씩 점검하세요.
인증 및 요청 오류
401 Unauthorized— 키가 누락되었거나, 형식이 잘못되었거나,Bearer토큰으로 전송되지 않았습니다.Bearer뒤의 공백을 포함해 요청 헤더를 꼼꼼히 확인하세요.403— 키는 유효하지만 이 엔드포인트에 접근할 권한이 없습니다. 키가 의도한 프로젝트에 속하는지 확인하세요.422/400검증 오류 — 메시지를 읽으세요. 흔한 원인은audio_url누락,image_url누락, 읽을 수 없는 URL, 지원되지 않는 오디오 형식입니다.- 응답에 예상치 못한 HTML — 잘못된 호스트나 경로에 접근했습니다. 기억으로 재구성하지 말고 API 참조에서 기본 URL을 확인하세요.
작업 실패 또는 영원히 완료되지 않음
- API가 입력 URL에 접근할 수 없음 — 브라우저에서 유효한 링크가 사설 네트워크, 만료된 서명 또는 핫링크 보호로 차단될 수 있습니다. 안정적인 공개 URL로 먼저 테스트하세요.
- 오디오와 이미지 불일치 — 무음, 지나치게 길거나 지원되지 않는 형식의 트랙은 렌더링을 지연시키거나 실패시킬 수 있습니다. 짧고 표준 형식의 클립으로 먼저 파이프라인을 검증하세요.
- 너무 잦은 폴링 — 타이트한 루프는 속도 제한을 유발하고 작업 실패처럼 보이는 오류를 만들 수 있습니다. 속도를 늦추고 백오프를 추가하세요.
- 너무 짧은 폴링 시간 — 대기 중인 작업을 몇 초 만에 포기하면 잘못된 시간 초과가 발생합니다. 현실적인 최대 대기 시간을 설정하고, 중지 전에 본 상태를 기록하세요.
- 종료 상태 미처리 — 루프가 성공만 확인하면 실패는 시간 초과까지 계속 공회전합니다. 실패 상태를 명시적으로 분기 처리하세요.
최소화 검증 습관
문제가 생기면 최소 테스트로 축소하세요. 짧은 오디오 클립, 작은 초상화, 한 번의 제출, 몇 초 후의 상태 확인입니다. 이것이 성공하면 장애는 API 호출 자체가 아니라 페이로드, 폴링 로직 또는 입력 호스팅에 있습니다.
관련 글
모든 아티클 보기 →-
튜토리얼
HiAPI API로 hailuo-2.3-fast/image-to-video 호출하는 방법: curl과 Python 실전
빠르고 저렴한 이미지-투-비디오 등급의 첫 실행 요청 — 모델 ID부터 MP4 다운로드까지
-
튜토리얼
HiAPI API로 hailuo-2.3/text-to-video를 호출하는 방법: curl, Python 및 실행 가능한 요청 예시
-
튜토리얼
HiAPI에서 Kandinsky 6.0 Pro 텍스트-투-비디오 사용하기: curl 및 Python 튜토리얼
요청 우선 실전 데모: 프롬프트에서 비디오 클립까지의 호출 구성, 요청 제출, 완료 상태 폴링 및 결과 다운로드