단어 하이라이트 TTS: 단어별 타이밍을 재생에 동기화하기
TTS 재생에 맞춰 단어를 강조하세요. 오디오와 함께 단어별 타임스탬프를 받고, 플레이어의 현재 시간이 시간 범위 안에 드는 단어를 표시합니다.

텍스트 음성 변환(TTS)이 재생되는 동안 단어를 강조하려면 오디오와 함께 단어별 타임스탬프를 받은 뒤, 플레이어 코드에서 시작·끝 시간 사이에 오디오의 현재 재생 시간이 들어가는 단어를 표시하세요. Sume에서는 POST /v1/tts-1.0/generate에 timestamps: { "words": true }를 지정하면, 생성한 파일 기준으로 시작·끝 초가 담긴 words[]를 반환합니다.
Sume 관련 사실은 2026-09-27에 확인한 Sume API 레퍼런스의 TTS 1.0과 STT 1.0 스키마에서 가져왔습니다. 이 레퍼런스는 API 레퍼런스 문서의 바탕이 되는 OpenAPI 문서입니다. 브라우저 동작은 MDN의 `timeupdate`, `currentTime`, `requestAnimationFrame()` 페이지를 따릅니다. Sume는 오디오와 타이밍을 반환하고, 하이라이트는 직접 작성한 페이지 코드에서 처리합니다.
오디오와 함께 단어별 타이밍은 어떻게 받나요?
TTS 요청에 timestamps를 추가하세요. 전체 요청은 텍스트 음성 변환 API에 있습니다. 그러면 완료된 Job 결과에 시작과 끝이 초 단위로 표시된, 단조 증가하는 words[]가 담깁니다. 호출은 API 키를 보관하는 서버에서 하고, 페이지에는 파일 URL과 타이밍만 넘기세요.
- 현재 코드에서 각 항목은 파일 시작부터 초 단위로 센
{ word, start, end }이며, 아래 발췌처럼 결과에는audio_url과 파일의duration_seconds도 들어 있습니다. segmentation: { "mode": "sentence" }를 추가하면 문장마다 하나씩 빈틈없는segments[]도 받으므로, 현재 문장을 강조하는 데 쓸 수 있습니다.
{
"audio_url": "https://media.sume.com/artifacts/artf_demo/tts.mp3",
"duration_seconds": 1.52,
"words": [
{ "word": "Read", "start": 0.08, "end": 0.34 },
{ "word": "along", "start": 0.34, "end": 0.71 },
{ "word": "with", "start": 0.71, "end": 0.9 },
{ "word": "me.", "start": 0.9, "end": 1.35 }
]
}브라우저에서 현재 단어는 어떻게 강조하나요?
words[]의 항목마다 <span>을 하나씩 렌더링하세요. 틱마다 플레이어의 currentTime(초 단위의 현재 재생 시간)을 읽고, start가 지난 마지막 단어를 표시하세요. 예제는 토큰을 공백으로 이어 붙이므로, 띄어쓰기 없이 쓰는 언어라면 이 부분을 조정하세요.
timeupdate가 가장 간단한 틱이지만, MDN에 따르면 시스템 부하에 따라 약 4 Hz에서 66 Hz 사이로 발생하므로, 느릴 때는 짧은 단어가 두 틱 사이에 지나가 버릴 수 있습니다.requestAnimationFrame()루프는 디스플레이가 새로 고쳐질 때마다 대략 한 번, 가장 흔하게는 초당 60번 실행되며, 대부분의 브라우저는 백그라운드 탭에서 이 루프를 멈춥니다.currentTime을 설정하면 탐색이 일어나므로, 단어를 클릭하면 오디오를 그 단어의start로 이동시킬 수 있습니다.
// words: result.words, passed down by your server
const audio = document.querySelector("audio");
const box = document.querySelector("#transcript");
const spans = words.map((w) => {
const span = document.createElement("span");
span.textContent = w.word + " ";
span.onclick = () => { audio.currentTime = w.start; };
box.append(span);
return span;
});
let active = -1, frame = 0;
function highlight() {
const t = audio.currentTime;
let i = active > -1 && t >= words[active].start ? active : -1;
while (i + 1 < words.length && words[i + 1].start <= t) i++;
if (i !== active) {
spans[active]?.classList.remove("current");
spans[i]?.classList.add("current");
active = i;
}
}
function loop() { highlight(); frame = audio.paused ? 0 : requestAnimationFrame(loop); }
audio.addEventListener("play", () => { if (!frame) frame = requestAnimationFrame(loop); });
audio.addEventListener("seeked", highlight);단어가 내 텍스트와 정확히 일치하지 않으면 어떻게 하나요?
원래 문자열을 나누지 말고 words[] 자체로 span을 만드세요. 그래야 목록과 타이밍이 항상 맞습니다. 자체 마크업을 꼭 유지해야 한다면, 같은 단어가 여러 번 나올 수 있으므로 텍스트로 단어를 찾지 말고 두 목록을 순서대로 따라가며 토큰을 하나씩 짝지으세요.
타이밍은 생성된 파일 기준입니다. 나중에 인트로를 붙이는 등 오디오를 편집했다면, 모든 start와 end를 같은 오프셋만큼 옮기세요.
직접 생성하지 않은 내레이션도 강조할 수 있나요?
네, 음성 인식(STT)으로 할 수 있습니다. POST /v1/stt-1.0/transcribe는 공개 HTTPS audio_url을 받아 단어별 타이밍을 항상 반환하므로, 따로 설정할 플래그가 없습니다. 각 항목에는 word와 초 단위의 start, end가 있고, 제공되는 경우 word나 spacing 같은 type도 있습니다. spacing 항목은 건너뛰세요. 호출 방법은 단어별 타임스탬프를 주는 음성 인식 API에서 다룹니다.
비용은 얼마인가요?
각 Job은 공개된 요율로 과금되며, 기본적으로 5.5% 에이전트 수수료가 더해집니다. TTS는 대본 글자 수로 가격이 매겨지고, 현재 코드에서 예상 금액은 글자 수만 세므로 단어별 타이밍을 요청해도 금액이 늘지 않습니다.
| 가진 것 | 호출 | 요율 |
|---|---|---|
| 텍스트 | timestamps: { "words": true }를 넣은 POST /v1/tts-1.0/generate | 1,000자당 $0.0475. 요청당 최대 20,000자 |
| 오디오 | POST /v1/stt-1.0/transcribe | 오디오 분당 $0.01 |
출처
관련 글
개발자 카테고리의 다른 글
- 웹훅 URL이 유효하지 않다고 거부되나요? Sume 웹훅 URL 규칙
웹훅 URL이 공개 HTTPS가 아니면 Sume는 400 invalid_request로 응답합니다. 스킴, 호스트, 포트, 자격 증명 규칙과 전달 시점의 검사를 정리했습니다.
- Claude Code·Cursor·Codex를 호스팅 MCP로 Sume에 연결
mcp.sume.com/mcp의 Sume 호스팅 MCP 서버를 쓰면 코딩 에이전트가 이미지, 영상, 오디오, 아바타를 생성할 수 있습니다. 설정 방법, OAuth 스코프, 지출 게이트를 정리했습니다.
- AI 영상 API 멱등성 키: 이중 과금 없이 재시도하기
멱등성 키를 쓰면 재시도한 생성 요청이 두 번째 유료 작업 대신 원래 실행이나 Job을 돌려줍니다. Sume의 Idempotency-Key가 API별로 어떻게 동작하는지 설명합니다.
- Sume 영상 실행용 서명된 웹훅: 이벤트, 재시도, 검증
Format·Action·Agent Completion 실행이 완료되거나 실패하면 Sume가 HMAC-SHA256 서명 POST를 한 번 보냅니다. 원본 본문을 검증하고 request_id로 중복을 제거하세요.
작성자 Sume