Opus 영상에 AI 내레이션 넣기: Ofox Gemini TTS 호출부터 MP4 합성까지
Ofox에서 생성한 실제 WAV 다섯 개를 측정하고 장면을 넘는 음성을 조정해 MP4로 합성합니다. Python 코드, 음성 파일, 재현 가능한 영상 예제를 제공합니다.
Opus의 도움으로 만든 영상에 AI 내레이션을 넣으려면 음성을 별도로 생성하고, 실제 길이를 측정한 뒤, 타임라인에 배치하고 두 스트림이 포함된 파일로 내보내면 됩니다. 이 글은 Ofox API의 Gemini 3.8 Flash TTS, Python, FFmpeg로 32초 MP4를 만드는 과정을 다룹니다. 제공하는 음성은 2026년 10월 9일 실제 API 응답에서 받은 파일입니다.
Opus 영상 제작 가이드의 후반 작업에 해당합니다. 화면은 기존 시리즈의 30초 스크린샷 데모를 재사용합니다. 인터페이스 캡처는 9월 30일의 참고 자료이며 현재 제품의 모든 세부 사항을 보여주는 것은 아닙니다. 이번에 새로 Opus를 호출해 화면을 생성했다고 주장하지 않습니다. Gemini는 음성을 만들고, 로컬 코드는 배치와 내보내기를 담당합니다.
전체 재현 키트 다운로드, 내레이션 MP4 보기, 합성 WAV 다운로드. 저장된 음성으로 다시 합성할 때는 API를 호출하지 않습니다. 샘플 내레이션은 영어이며, 글을 한국어로 제공하는 것과 한국어 음성을 검증하는 것은 별개입니다.
1. 긴 문단 대신 장면별 대사를 준비하기
연속 음성은 팟캐스트에는 편하지만 화면 시연에는 다른 제약이 있습니다. 문서를 설명하는 동안에는 해당 문서가 보여야 합니다. 따라서 이 예제는 장면별로 다섯 문장을 따로 생성합니다. 한 문장이 마음에 들지 않으면 전체 음성 대신 해당 문장만 교체할 수 있습니다.
대사는 제작 목적, 모델 목록, 문서, API 참조, 마지막 확인을 설명합니다. 모델 수나 할인, 성능 순위는 말하지 않습니다. 예전 화면을 재사용할 때 화면 속 숫자를 현재 제품의 약속처럼 전달하지 않기 위해서입니다.
생성 전에 각 문장의 시작, 허용 종료 시각, 정확한 발화 내용을 기록하세요. 원래 소리를 교체할지 믹싱할지도 결정합니다. 이 예제는 원래 오디오를 교체하며 배경 음악, 음성 복제, 입 모양 동기화, 단어별 자막 정렬은 포함하지 않습니다. 이런 작업에는 별도 입력과 검증이 필요합니다.
화면 프로젝트는 스크린샷으로 제품 데모 만들기에서 설명합니다. 자막 시간표가 목적이라면 내레이션과 자막 동기화 가이드를 함께 보세요. 이 글은 그 위에 실제 Ofox TTS 요청과 출력 측정을 추가합니다.
2. 정확한 모델 ID, 음색, 형식 확인하기
검증한 설정은 google/gemini-3.8-flash-tts, 음색 Kore, 언어 en-US, speed: 1.0, response_format: wav입니다. Gemini TTS 모델 페이지의 정확한 ID를 쓰고 일반 Gemini 텍스트 모델과 혼동하지 마세요. 실제 파일은 24,000 Hz, 모노, 16비트 PCM WAV였습니다. 확장자만으로는 이를 확인할 수 없으므로 스트림 검사와 전체 디코딩을 진행합니다.

2026년 10월 9일 실제 캡처이며 영어 인터페이스를 그대로 사용했습니다. 페이지 예제는 speed 1.1, 이번 기록은 1.0입니다. 캡처는 표시된 연결 방식을 보여주고, 생성 성공은 제공한 WAV와 요청 기록이 뒷받침합니다.
Python 3, requests, FFmpeg, ffprobe가 필요합니다. 키는 OFOX_API_KEY 환경 변수로 전달하고 공유 스크립트, 브라우저 코드, 저장소에 넣지 않습니다. 아래 명령은 Unix 계열 셸 기준입니다. Windows에서는 가상 환경 활성화와 환경 변수 설정 방식이 다르지만 Python 요청은 동일합니다.
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install requests
ffmpeg -version
ffprobe -version
WAV는 압축 음성보다 크지만 검사하고 편집하기 편합니다. 최종 MP4에서만 AAC로 인코딩해 중간 파일의 반복 압축을 피합니다. Google의 음성 생성과 형식 문서는 상위 공급자 인터페이스를 설명합니다. 여기서 쓰는 게이트웨이 요청은 Ofox 페이지를 기준으로 확인해야 합니다. 공급자에 있는 옵션이 모든 중개 API에서도 지원되는 것은 아닙니다.
3. 작은 요청으로 시작하고 오류를 음성과 분리하기
키트의 audio_api.py는 고정된 Ofox 기본 URL을 사용하며, 유료 요청 전에 미디어 도구를 확인하고, 환경 변수에서 키를 읽으며, Authorization 헤더 없이 응답 메타데이터를 저장합니다. HTTP 상태와 Content-Type을 검사한 뒤 오디오를 저장하고 길이 측정과 전체 디코딩을 수행합니다. 비용이 발생할 수 있는 POST를 자동으로 재시도하지 않습니다.
python3 audio_api.py speech --engine gemini \
--text narration.en.txt --output first-take.wav --language en-US
이 명령은 함께 제공한 짧은 전체 대사로 연속 시험 음성을 만듭니다. 완성본은 다섯 문장을 각각 요청했습니다. 기본 Python 요청은 다음과 같습니다. 추가 검증과 기록이 필요하면 키트의 클라이언트를 사용하세요.
import os
from pathlib import Path
import requests
response = requests.post(
"https://api.ofox.ai/v1/audio/speech",
headers={"Authorization": "Bearer " + os.environ["OFOX_API_KEY"]},
json={
"model": "google/gemini-3.8-flash-tts",
"voice": "Kore",
"input": "A clear product video starts with a clear brief.",
"language_code": "en-US",
"speed": 1.0,
"response_format": "wav",
},
timeout=(15, 120),
)
response.raise_for_status()
if not response.headers.get("content-type", "").startswith("audio/"):
raise RuntimeError("Expected audio; inspect the response before saving")
Path("line.wav").write_bytes(response.content)
첫 연속 시험은 HTTP 200과 10.4초 WAV를 반환했고, 이후 장면별 다섯 요청도 성공했습니다. 이는 해당 요청의 성공을 보여줄 뿐, 재생성 길이가 같거나 서비스 용량이 무제한이라는 뜻은 아닙니다. 요청 설정과 음성 해시를 함께 보관하세요. 대사를 바꾼 뒤 옛 WAV를 새 출력처럼 취급하면 기록이 틀어집니다.
시간 초과가 나면 재전송 전에 원래 요청의 상태와 사용량을 확인해야 합니다. 응답을 받지 못했다고 생성도 없었다고 단정할 수 없습니다. JSON 오류를 speech.wav로 저장한 경우도 오디오 코덱 문제로 찾기 전에 HTTP 처리부터 고쳐야 합니다. 오류 본문과 request ID를 별도 파일로 보관하세요.
4. 다섯 문장의 길이를 측정하고 넘치는 구간 수정하기
각 WAV에 ffprobe를 실행합니다. 아래 길이는 끝의 무음을 포함할 수 있는 전체 파일 길이이며 음소 단위 발화 경계는 아닙니다.
ffprobe -v error -show_entries stream=codec_name,sample_rate,channels \
-show_entries format=duration -of json scenes/line-1.wav
| 장면 | 실제 영어 대사 | 시작 | WAV 길이 | 종료 |
|---|---|---|---|---|
| 목적 | A clear product video starts with a clear brief. | 0.35초 | 3.56초 | 3.91초 |
| 목록 | Show the real interface. Here, we begin with the model catalog. | 4.35초 | 4.64초 | 8.99초 |
| 문서 | Then show where a viewer can find the documentation. | 11.35초 | 3.44초 | 14.79초 |
| API 참조 | Connect each scene to an actual page, such as this API reference. | 18.35초 | 4.96초 | 23.31초 |
| 마무리 | Keep the message simple. Plan, build, and verify. | 25.35초 | 5.44초 | 30.79초 |
두 문장이 원래 시간창 검사를 통과하지 못했습니다. 첫 문장은 계획한 3.60초 종료를 0.31초 넘었습니다. 다음 장면 전에 끝나도록 허용 종료를 4.00초로 늘렸습니다. 마지막 문장은 29.50초 종료를 1.29초 넘었고 원래 30초 영상보다도 길었습니다. 마지막 프레임을 2초 더 유지해 32초 영상으로 바꾸고, 마지막 시간창은 31.50초까지로 조정했습니다.
단어 끝을 자르거나 설명 없이 속도를 높이지 않았습니다. 납품 조건이 정확히 30초라면 이 32초 버전은 쓸 수 없습니다. 마지막 대사를 줄이고 해당 문장만 다시 생성한 뒤 측정해야 합니다. speed는 말하기 속도 요청이지, 5.44초 파일을 목표 길이로 정확히 바꾸는 계산식이 아닙니다.
5. 저장된 음성으로 MP4 합성하기
압축 파일에는 scenes/line-1.wav부터 line-5.wav, 시간표, 기존 내레이션 없는 참고 영상인 source.mp4가 있습니다. 압축을 푼 디렉터리에서 실행합니다.
python3 assemble_scenes.py scenes source.mp4 rebuilt
이 단계는 API나 키가 필요 없습니다. 합성기는 각 음성 해시, PCM 형식, 수정된 시간창을 확인하고 시작 위치에 샘플을 배치합니다. 나머지는 무음으로 채운 뒤 원본 영상과 새 음성만 선택해 H.264/AAC MP4로 만듭니다. 시간창을 넘으면 오류를 내므로 -shortest에 의해 발화 끝이 별도 경고 없이 잘리는 일을 방지합니다.
24 kHz 오디오 시계와 영상의 프레임 시계는 다릅니다. 샘플 단위로 배치해도 단어별 자막 시간이 생기지는 않습니다. 일반용 mux.py는 완성된 내레이션을 교체하는 도구로, 영상보다 긴 음성을 거부합니다. 이번 예제처럼 마지막 장면을 늘리는 편집 결정은 전용 장면 합성기를 사용하세요.
측정이 끝난 뒤 자신의 프로젝트를 Opus에 수정하도록 요청할 때는 다음 템플릿을 쓸 수 있습니다. 재사용 가능한 작업 지시이며, 이번에 추가 모델 호출을 수행했다는 기록은 아닙니다.
현재 영상 합성과 시간 데이터만 수정하세요.
승인한 스크린샷과 장면 순서를 유지합니다.
첨부 WAV를 사용하고 음성을 다시 생성하거나 발화 단어를 줄이지 마세요.
timing.json의 실측 시작 시각에 각 문장을 배치합니다.
렌더링 전에 초과 구간을 보고하세요. 장면을 늘려야 하면 변경되는
컷 위치와 전체 길이를 설명하세요. 화면의 모든 글자를 보존합니다.
변경 파일, 렌더 명령, 확인할 프레임을 반환하세요.
문장 단위 시간으로 입 모양이나 단어별 정렬을 완료했다고 주장하지 마세요.
6. 편집기 미리보기 대신 최종 파일 검사하기
ffprobe -v error -show_streams -show_format -of json rebuilt/narrated.mp4
ffmpeg -v error -i rebuilt/narrated.mp4 -f null -
제공 영상의 타임라인은 32초이고 H.264 영상과 AAC 음성이 들어 있습니다. 합성 원본 WAV는 정확히 32초입니다. 압축 오디오 인코딩 패딩 때문에 스트림과 컨테이너 길이가 조금 다르게 표시될 수 있으므로 작은 소수점 차이만으로 내용이 잘렸다고 판단하지 않습니다.
전체 디코딩은 통과해야 하지만 디코더는 발음이나 화면과 대사의 일치를 평가하지 못합니다. 첫 문장, 컷 전환, 가장 긴 문장, 마지막 단어를 확인하고 제품명과 약어, 쉼을 들어보세요. 이번 기록은 API 성공, 형식, 길이, 위치, 디코딩을 확인한 것이며 사람으로 구성된 청취 평가나 Gemini가 다른 서비스보다 좋다는 결론은 아닙니다. 제공한 파일로 자신의 용도에 맞는지 판단할 수 있습니다.
한국어, 일본어, 러시아어 버전은 대사를 별도로 생성하고 검사해야 합니다. 번역하면 길이와 발음, 쉬는 위치가 달라지므로 영어 타이밍을 그대로 쓰면 안 됩니다. 자막 번역 역시 음성 현지화를 대신하지 않습니다.
7. 실제 실패한 계층부터 확인하기
| 증상 | 확인할 것 | 조치 |
|---|---|---|
| 401과 quota 오류 | 오류 원문, request ID, 공급자 제한인지 여부 | Ofox 잔액 부족으로 단정하지 않고 해당 계정·경로 확인 |
| 형식 지정 시 400 | 정확한 모델과 형식 조합 | 이 예제는 검증한 WAV 설정으로 시작 |
| WAV가 열리지 않음 | JSON 오류를 오디오로 저장했는지 | 상태와 Content-Type 확인, 오류 분리 저장 |
| 대사가 컷을 넘음 | 실측 길이와 다음 컷 | 대사를 줄이거나 장면을 늘리고 재검사 |
| MP4에 소리가 없음 | 최종 스트림과 매핑 | 새 음성 스트림을 명시적으로 선택하고 디코딩 |
| 재시도 후 중복 비용 | 최초 요청 결과와 사용 기록 | 유료 POST를 무조건 재전송하지 않기 |
준비 과정에서 같은 Ofox 키는 Gemini 음성을 성공적으로 생성했지만 ElevenLabs 음성과 Scribe 경로는 401 할당량 오류를 반환했습니다. 읽기 전용 잔액 조회에서는 Ofox 잔액이 양수였습니다. 따라서 이 오류만으로 Ofox를 충전하라고 안내해서는 안 됩니다. 영향을 받은 상위 공급자 경로를 조사해야 하며, 구체적인 공급자 계정 상태는 아직 확인하지 않았습니다. ElevenLabs는 이런 401 할당량 오류를 문서화하고 있습니다. 이는 검증 날짜의 관찰이며 항상 서비스가 중단된다는 주장은 아닙니다.
요금표, 사용량, 최종 차감액도 분리하세요. 오디오 토큰 과금은 영상 길이만으로 정확한 분당 비용으로 환산할 수 없습니다. 이번 글에서는 청구를 독립적으로 대조하지 않았으므로 총비용이나 절감률을 제시하지 않습니다. 대량 생성 전에 현재 모델 페이지와 자신의 요청별 사용 기록을 확인해야 합니다.
대사, 원본 WAV 다섯 개, 측정 시간표, 합성 음성, MP4를 하나의 버전으로 관리하세요. 다음 수정에서는 해당 문장만 교체하고 다시 측정한 뒤 내보냅니다. 글자 수가 비슷하다는 이유로 이전 길이를 그대로 적용하지 마세요.
자주 묻는 질문
- 예제 음성을 Opus 5.5가 생성했나요?
- 아닙니다. WAV는 Ofox를 통한 Gemini 3.8 Flash TTS 호출로 생성했고, 음성과 영상은 로컬 FFmpeg로 합성했습니다. Opus는 영상 프로젝트 편집을 도울 수 있지만 이번 글에서 새 Opus 호출을 테스트한 것은 아닙니다.
- API 크레딧을 쓰지 않고 완성 영상을 재현할 수 있나요?
- 네. 다운로드에 생성된 WAV 다섯 개와 원본 MP4가 들어 있습니다. 로컬 합성은 API를 호출하지 않습니다. 새로운 음성 생성에는 유효한 키와 모델 접근 권한, 사용 가능한 할당량이 필요합니다.
- speed를 높이면 원하는 길이에 정확히 맞출 수 있나요?
- 정확한 길이를 보장하지 않습니다. 실제 출력을 측정하고 대사를 줄이거나 장면을 늘린 뒤 다시 확인하세요. 발화 끝부분이 잘리지 않았는지도 확인하세요.


