ElevenLabs API로 첫 내레이션 만들기: Ofox에서 생성·저장·검증하는 방법
Ofox에서 ElevenLabs API를 Python과 cURL로 호출해 첫 MP3를 생성하고 검증합니다. 실제 음성, 모델·음성 설정, 예제 코드와 오류 처리 방법을 제공합니다.
Ofox를 통해 음성을 생성하려면 Ofox API 키, 모델 ID elevenlabs/eleven_v4, 호환되는 voice ID, 지원되는 오디오 형식으로 /v1/audio/speech에 텍스트를 전송합니다. 요청이 성공하면 바이너리 응답을 오디오 파일로 저장하고 디코딩되는지 확인합니다. 파일 이름이 speech.mp3라고 해서 요청이 성공한 것은 아닙니다. 그 이름으로 저장된 인증 오류 응답은 여전히 JSON입니다.
이 튜토리얼은 2026년 10월 10일에 완료한 실제 요청을 따라갑니다. 제공된 영어 원문 스크립트로 10.00초 길이의 MP3를 생성했고, 별도의 Scribe 요청으로 해당 오디오를 전사했습니다. 다운로드 가능한 파일과 클라이언트로 결과를 확인할 수 있습니다. 이는 Ofox 게이트웨이를 통해 성공한 한 가지 사례이며, 신뢰성 벤치마크나 이 경로에서 ElevenLabs의 모든 기능을 사용할 수 있다는 주장은 아닙니다.
완성할 결과물
완성 결과물은 재생 가능한 내레이션 음성, 원문, 비밀 정보를 제외한 요청 설정, 간단한 검증 기록입니다. 이 결과물을 제품 영상에 사용하거나 전사 작업에 넘길 수 있습니다. Ofox 요청을 재현하기 위해 영상 편집기, 복제 음성, 본인 명의의 유료 ElevenLabs 계정부터 준비할 필요는 없습니다. 액세스 권한과 잔액이 충분한 Ofox 키가 필요하며, 설정된 업스트림 경로가 정상 작동해야 합니다.
이 예제에서는 기존 voice ID를 사용합니다. 새 음성을 등록하거나 특정 인물의 음성을 복제하지 않습니다. 나중에 실제 인물과 관련된 음성을 사용한다면 필요한 허가를 받고 관련 권리도 별도로 확인하세요. 음성 합성 요청에 성공했다고 해서 누군가를 사칭하거나 그 음성을 어떤 용도로든 게시할 권한이 생기는 것은 아닙니다.
audio_api.py, comparison.txt, elevenlabs.mp3, 저장된 응답 메타데이터가 포함된 작동 예제 전체 파일을 다운로드하세요. 예제 파일은 인증 정보를 환경 변수로 가져오며, 비용이 청구될 수 있는 POST 요청을 자동으로 반복하지 않고 실패 시 중단합니다.
ElevenLabs 생성 음성
1. API 키, 도구, 정확한 스크립트 준비하기
Python, requests 패키지, ffprobe가 포함된 FFmpeg를 설치합니다. HTTP 요청에는 HTTP 클라이언트만 필요하지만, 이 튜토리얼에서는 디코딩과 재생 시간 확인도 완료 기준에 포함하므로 FFmpeg를 사용합니다. 튜토리얼을 실행할 터미널에서 다음 명령어를 사용할 수 있는지 확인하세요.
python3 -m pip install requests
ffmpeg -version
ffprobe -version
평소 사용하는 비밀 정보 관리자나 비공개 환경 설정을 통해 OFOX_API_KEY를 설정합니다. 키를 Markdown 문서, 소스 파일, 스크린샷 또는 커밋하는 .env 파일에 넣지 마세요. 키를 이미 환경 변수로 내보냈다면 제공된 클라이언트가 바로 읽습니다. 스크립트는 Authorization 헤더를 출력하지 않도록 작성되어 있습니다.
첫 테스트에는 comparison.txt의 다음 문장을 사용합니다.
A clear product video starts with a clear brief. Show the real interface, explain one useful task, and check the exported video before sharing it.
파일을 UTF-8로 저장합니다. 문제를 해결하는 동안에는 문장을 그대로 유지하세요. 텍스트, 음성, 모델, 형식을 한꺼번에 바꾸면 실패 원인을 찾기 어렵습니다. 다국어 프로젝트라면 생성 전에 각 언어의 스크립트를 준비하고 검토하세요. 번역과 음성 합성은 서로 다른 작업입니다.
직접 쓸 스크립트에서는 설명이 없는 약어를 풀어 쓰고 모호한 날짜를 명확히 하세요. “10/15”보다 “October 15, 2026”이 덜 모호합니다. 특정 발음이 필요한 경우 텍스트 미리보기만 믿지 말고 최종 오디오에서 확인해야 합니다. 이 예제는 Ofox 게이트웨이에서 검증하지 않은 특정 감정이나 발음 제어 기능을 제공한다고 약속하지 않습니다.
2. 게이트웨이의 모델 및 음성 필드 사용하기
테스트 요청에서 중요한 콘텐츠 설정은 다음 네 가지입니다.
| 필드 | 테스트 값 | 중요한 이유 |
|---|---|---|
model | elevenlabs/eleven_v4 | Ofox에서 사용하는 공급자 접두사가 포함된 모델 ID |
voice | JBFqnCBsd6RMkjVDRZzb | 이 테스트에서 허용된 음성 식별자 |
response_format | mp3_22050_32 | 요청할 MP3 프리셋이며 파일 이름이 아님 |
speed | 1.0 | 요청에 제출한 속도 값이며, 재생 시간을 보장하지 않음 |
다른 모델이나 형식을 사용하기 전에 최신 ElevenLabs 모델 페이지를 확인하세요. 표시 이름을 정확한 ID 대신 사용하지 말고, 다른 공급자의 음성 이름이 유효한 ElevenLabs 식별자라고 가정하지 마세요.
Ofox 엔드포인트와 ElevenLabs 네이티브 API는 서로 다른 인터페이스입니다. 네이티브 API 예제에서는 voice ID를 경로에 넣거나 공급자별 필드를 받을 수 있습니다. 이 튜토리얼에서는 표시된 JSON 필드에 음성을 지정하고 Ofox 기본 URL을 사용합니다. 네이티브 공급자 문서의 옵션을 모두 어댑터에 전달하는 것은 호환성 테스트가 아닙니다.
현재 예제는 의도적으로 간단하게 구성했습니다. 언어 필드, 다른 음성 또는 고급 설정을 추가하기 전에 현재 Ofox 경로에서 해당 설정을 지원하는지 확인하세요. 재사용할 래퍼를 만든다면 공급자별 옵션을 별도의 설정 객체에 두세요. 모든 음성 모델이 서로 바꿔 쓸 수 있는 것처럼 표현하지 않는 것이 좋습니다.
3. 검증된 Python 클라이언트로 첫 MP3 생성하기
압축을 푼 파일이 있는 디렉터리에서 다음 명령어를 실행합니다.
python3 audio_api.py speech \
--engine elevenlabs \
--text comparison.txt \
--output my-first-voiceover.mp3
새 출력 파일 이름을 선택하세요. 클라이언트는 기존 출력 경로가 있으면 요청을 거부하므로 두 번째 실험이 첫 번째 결과를 조용히 덮어쓰지 않습니다. 제출한 콘텐츠 설정을 응답 메타데이터와 함께 저장하며, API 키는 환경 변수에서만 가져옵니다.
요청이 성공하면 클라이언트가 응답 유형을 확인하고 바이트를 저장한 뒤 오디오 재생 시간을 측정하고 디코딩을 시도합니다. 기대하는 결과는 오디오 파일과 메타데이터이지, 오디오 파일 링크가 담긴 JSON 객체가 아닙니다. 실패하면 별도로 저장된 오류 기록을 확인하세요. 해당 응답을 음성인 것처럼 영상 편집기에 넣으면 안 됩니다.
예제에서는 HTTP 200과 audio/mpeg 응답이 반환되었으며, 파일 크기는 40,456바이트, 측정된 재생 시간은 10.00초였습니다. 클라이언트에서 측정한 요청 소요 시간은 2.861초였습니다. 이 시간에는 해당 요청의 네트워크 및 처리 조건이 포함됩니다. 첫 오디오가 나올 때까지 걸린 시간이 아니며 서비스 지연 시간의 보장값도 아닙니다.
실제 ElevenLabs 샘플을 재생하거나 다운로드하세요. 요청을 기록할 때 원본 오디오는 변경하지 말고 보관하세요. 영상에 맞춰 음량을 정규화하거나 무음을 잘라내는 경우, 다른 개발자가 원본 결과를 확인할 수 있도록 편집본을 별도 파일로 저장하세요.
4. cURL로 HTTP 요청 재현하기
다음 대안은 응답을 임시 파일에 저장한 뒤, HTTP 요청이 성공한 경우에만 최종 파일 이름으로 이동합니다.
python3 - <<'PY'
import json
from pathlib import Path
payload = {
'model': 'elevenlabs/eleven_v4',
'voice': 'JBFqnCBsd6RMkjVDRZzb',
'input': Path('comparison.txt').read_text().strip(),
'response_format': 'mp3_22050_32',
'speed': 1.0,
}
Path('speech-request.json').write_text(json.dumps(payload))
PY
curl --fail-with-body --silent --show-error \
https://api.ofox.run/v1/audio/speech \
-H "Authorization: Bearer $OFOX_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @speech-request.json \
--output speech-response.tmp \
&& mv speech-response.tmp curl-voiceover.mp3
이는 대체 호출 방법이며, 두 번째 유료 요청까지 해야 한다는 뜻은 아닙니다. 두 클라이언트를 모두 실행하면 음성이 두 번 생성됩니다. 이 글의 성공 결과는 제공된 Python 클라이언트로 생성했으며, cURL 예제는 이에 해당하는 요청 구성을 보여 줍니다.
--fail-with-body가 없는 구버전 cURL을 사용한다면 Python 클라이언트를 이용하거나, 결과를 인정하기 전에 HTTP 상태 코드를 명시적으로 확인하세요. 실패 후 임시 파일에 유용한 오류 응답 본문이 남아 있을 수 있습니다. 오류를 읽기 전에 파일을 삭제하거나 보관 처리하지 마세요. 실패 응답의 확장자를 .mp3로 바꿔 플레이어에서 열려고 해도 문제가 해결되지 않습니다.
5. 작업을 완료 처리하기 전에 파일 검증하기
ffprobe로 실제 컨테이너와 스트림 정보를 확인한 다음 파일 전체를 디코딩합니다.
ffprobe -v error -show_entries \
stream=codec_name,sample_rate,channels:format=duration \
-of json my-first-voiceover.mp3
ffmpeg -v error -i my-first-voiceover.mp3 -f null -
오류 없이 디코딩되었다는 것은 기술적인 검증 결과입니다. 브랜드 이름이 올바르게 발음되는지, 영상 컷에 맞는 간격으로 쉬는지, 음성이 시청자에게 적절한지는 알려 주지 않습니다. 게시 전에는 최종 파일 전체를 보통 음량으로 듣고 승인된 스크립트와 대조하세요.
예제에서는 후속 Scribe 전사 결과의 단어가 문장 부호 차이를 제외하면 원문과 같았습니다. 이는 추가 확인에 유용하지만 실제로 듣는 것을 대신할 수는 없습니다. 음성 인식은 실수를 표준화하거나 원치 않는 소리를 놓칠 수 있습니다. 다른 모델의 결과가 일치한다는 사실을 완벽한 오디오 품질의 증거로 사용하지 마세요.
실무적인 검수 기록에는 입력 버전, 모델, 음성, 요청한 형식, 실제 재생 시간, 디코딩 통과 여부, 발음을 확인한 사람을 적어야 합니다. 확인하지 못한 항목도 명시적으로 기록하세요. 성공한 API 요청이라도 콘텐츠를 확인하지 않았다면 고객 캠페인에 바로 사용할 수 있는 상태가 아니라 편집 검토를 기다리는 상태입니다.
6. 타이밍 문제를 감추지 않고 내레이션을 영상에 맞추기
측정된 재생 시간을 기준으로 영상을 계획하세요. 같은 스크립트라도 다른 음성을 사용하거나 나중에 다시 생성하면 재생 시간이 달라질 수 있습니다. speed를 1.0으로 설정한다고 해서 모든 결과가 10초 타임라인에 맞는 것은 아닙니다.
내레이션이 화면보다 길면 내보내기 전에 해당 장면을 늘리거나 구성을 조정하세요. 내레이션이 더 짧다면 음성을 늘리는 것보다 의도적으로 화면을 더 보여 주는 편이 나을 수 있습니다. 최단 스트림 옵션을 사용할 경우 영상이나 내레이션의 끝부분이 잘리는지 먼저 확인하세요.
관련된 Ofox 영상 내레이션 튜토리얼에서는 오디오 길이 측정과 MP4 합치기를 다룹니다. 자막 작업에서는 텍스트 길이로 자막 구간을 추측하지 말고, 실제 타이밍을 보존할 수 있도록 Scribe 녹음 파일을 자막으로 변환하는 안내를 참고하세요.
개발 예제와 마케팅 문구는 구분하세요. 이 오디오는 기록된 설정에서 요청이 사용 가능한 파일을 생성했다는 점을 보여 줍니다. 모든 언어, 기술 용어, 상업용 납품 형식에서 음성이 똑같이 잘 작동한다는 뜻은 아닙니다.
7. 사용량 지표를 혼동하지 않고 비용 이해하기
음성 생성 비용은 기준 단위를 확인해야 합니다. 텍스트 글자 수당 가격, 오디오 토큰 가격, 초당 전사 가격은 숫자만 나란히 놓고 비교할 수 없습니다. 해당 설정에 적용되는 가격은 모델 페이지와 계정의 요청별 청구 기록에서 확인하세요.
파일 크기나 요청 소요 시간을 청구 사용량이라고 표현하지 않습니다. 40킬로바이트 파일을 다운로드했다고 해서 특정 요금이 부과된다는 뜻은 아닙니다. 이 튜토리얼은 사용자가 자신의 사용 내역에서 해당 항목을 대조할 수 있도록 모델과 요청 정보를 보존합니다. 카탈로그에 표시된 예상 요금을 이 샘플의 확정 청구 금액으로 제시하지 않습니다.
프로덕션 작업에서는 승인된 결과물과 재시도 및 반려된 결과물을 구분하세요. 요청한 10개 결과 중 하나를 승인한 경우와 한 번의 성공적인 생성으로 결과물을 얻은 경우는 실질 비용이 다릅니다. 청구된 모든 호출을 추적하고 승인된 결과물 하나당 비용을 계산하세요. 표시 요금이 낮다는 사실만으로 작업 흐름의 비용이 더 적다고 단정할 수는 없습니다.
8. 실패한 단계를 정확히 찾아 문제 해결하기
| 증상 | 확인할 항목 | 안전한 다음 조치 |
|---|---|---|
| 할당량을 언급하는 HTTP 401 | 응답 본문과 요청 ID | 실제 사용한 계정 또는 업스트림 경로를 확인합니다. Ofox 지갑 잔액이 없다고 단정하지 마세요. |
| 인증 정보를 언급하는 HTTP 401 | 키 환경 변수와 인증 설정 | 인증 정보를 출력하지 말고 키를 가져오는 위치를 바로잡습니다. |
| HTTP 400 또는 지원되지 않는 형식 | 모델, 음성, 형식 조합 | 테스트된 설정으로 돌아간 다음 필드를 한 번에 하나씩 변경합니다. |
| 재생되지 않는 작은 “MP3” 파일 | 콘텐츠 유형과 응답의 첫 바이트 | 오류를 텍스트로 읽고 원인을 해결한 뒤에만 다시 생성합니다. |
| 결과를 알 수 없는 타임아웃 | 공급자 및 요청 로그 | 호출이 완료되었는지 확인한 뒤 재시도합니다. |
| 단어가 누락되었거나 적절하지 않음 | 원본 오디오와 스크립트 | 스크립트나 음성 설정을 수정하고 새 결과물을 별도로 저장합니다. |
이 튜토리얼의 초기 요청에서는 Ofox 지갑 잔액이 양수로 표시된 상태에서도 업스트림 할당량 오류가 반환되었습니다. 경로가 복구된 뒤 새 요청은 성공했습니다. 이는 해당 요청들에 관한 기록이지, 모든 401 오류가 공급자 잔액 문제에서 발생한다는 규칙은 아닙니다.
자주 묻는 질문
- 이 예제에 ElevenLabs 키를 입력해야 하나요?
- 아니요. 이 예제는 Ofox API 키로 Ofox 게이트웨이에 인증합니다. ElevenLabs 네이티브 API 인증은 별도의 연동 방식입니다.
- 어떤 음성 이름이든 사용할 수 있나요?
- 그렇게 가정하지 마세요. 성공한 요청에서는 위에 표시된 정확한 식별자를 사용했습니다. 표시 이름이나 다른 공급자의 음성으로 바꾸기 전에 호환성을 확인하세요.
- 저장한 MP3에 JSON이 들어 있는 이유는 무엇인가요?
- 클라이언트가 HTTP 상태와 응답 유형을 확인하지 않은 채 서버의 오류 응답을 저장했을 가능성이 큽니다. 먼저 오류를 읽으세요. 확장자를 바꿔도 문제가 해결되지는 않습니다.
- HTTP 200이면 내레이션을 바로 게시해도 되나요?
- 성공적인 응답이 반환되었다는 뜻이지, 편집 검수가 끝났다는 뜻은 아닙니다. 게시 전에 파일을 디코딩하고 전체 재생 시간을 확인한 다음, 누락된 단어와 발음 및 타이밍을 들어 보세요.


