콘텐츠로 이동

자막

자막(caption)은 자체 트랙에 놓이는 시간 지정 텍스트이며, 엔진이 텍스트 제너레이터와 같은 텍스트 렌더러로 그립니다. 이 페이지에서는 자막 트랙, 큐, 스타일, 단어 타이밍, 그리고 SubRip과 WebVTT 파일을 가져오는 방법을 설명합니다. 다 읽고 나면 영상에 자막을 달고, 말하는 단어를 강조할 수 있습니다.

"kind": "caption"인 트랙에는 클립이 없습니다. 대신 captions가 다음을 담습니다.

  • cues: 각각 timeline_range와 text를 가진 CaptionCue. 큐는 타임라인 순서이고 서로 겹치지 않습니다. 트랙은 한 번에 큐 하나만 보여 줍니다.
  • style: 모든 큐의 출발점이 되는 TextStyle. 텍스트 제너레이터와 같은 스타일입니다. 기본값은 캔버스 짧은 변의 5% 크기의 흰 글자이며, 아래쪽 가운데 근처에 놓입니다.
  • styles: 이 스타일에 대한 이름 있는 패치. 최대 32개이며, 패치가 지정한 필드는 기본 스타일의 값을 대체합니다.

자막 트랙은 앞에 있는 트랙들 위에, 뒤에 있는 트랙들 아래에 그려집니다. 맨 위에 그리려면 마지막에 두세요.

{
"id": "subs",
"name": "Subtitles",
"kind": "caption",
"captions": {
"style": {
"font_family": "Hanken Grotesk",
"font_size": 0.06,
"background": { "color": { "red": 0, "green": 0, "blue": 0, "alpha": 0.6 }, "padding": 0.3 }
},
"styles": {
"Narrator": {
"fill": { "type": "solid", "color": { "red": 1, "green": 0.85, "blue": 0.3, "alpha": 1 } }
},
"karaoke": {
"animators": [
{
"selector": { "type": "timing", "state": "upcoming" },
"properties": { "opacity": { "mode": "constant", "value": 0.45 } }
}
]
}
},
"cues": [
{
"timeline_range": { "start": 0, "end": 240000 },
"text": "The tide comes in twice a day."
},
{
"timeline_range": { "start": 240000, "end": 480000 },
"text": "Every word lands on time.",
"speaker": "Narrator"
},
{
"timeline_range": { "start": 480000, "end": 720000 },
"style": "karaoke",
"text": [
{ "text": "One ", "time": { "start": 0, "end": 60000 } },
{ "text": "word ", "time": { "start": 60000, "end": 120000 } },
{ "text": "at ", "time": { "start": 120000, "end": 168000 } },
{ "text": "a time.", "time": { "start": 168000, "end": 240000 } }
]
}
]
}
}

큐는 트랙의 styles 중 하나를 두 가지 방법으로 고릅니다.

  • **style**로 직접 이름을 지정합니다. 세 번째 큐는 karaoke를 씁니다.
  • **speaker**는 누가 말하는지를 나타냅니다(예: WebVTT voice). 큐가 스타일을 지정하지 않으면 화자 이름과 같은 트랙 스타일이 적용됩니다. 두 번째 큐의 화자가 Narrator이므로 Narrator 스타일의 노란색으로 그려집니다.

큐의 텍스트는 문자열이거나, 텍스트를 이어 붙이면 전체 텍스트가 되는 **런(run)**의 목록입니다. 런에는 class 이름과 time 구간을 붙일 수 있습니다. time은 큐 시작을 기준으로 하며 큐 안에 있어야 합니다.

텍스트 애니메이터의 timing 선택자는 런의 시간을 현재 시각과 비교해 글자를 고릅니다. upcoming(아직 말하지 않음), active(말하는 중), spoken(말함) 중 하나입니다. 예제의 karaoke 스타일은 아직 오지 않은 단어를 불투명도 45%로 흐리게 하므로, 각 단어가 자기 차례가 되면 밝아집니다. ramp를 주면 즉시 바뀌는 대신 그 시간 동안 서서히 바뀌고, sweep을 켜면 런의 시간을 글자들에 나누어 강조가 단어 안에서 한 글자씩 지나갑니다.

자막 스타일 안의 애니메이터 키프레임은 각 큐가 시작한 뒤의 시간을 따릅니다.

alpha2/captions의 parseCaptions는 엔진 자체의 파서로 SubRip("srt")이나 WebVTT("webvtt") 텍스트를 읽고, captionTrack은 큐로 트랙을 만듭니다.

import init, * as wasm from "alpha2/wasm";
import { captionTrack, parseCaptions } from "alpha2/captions";
await init();
const { rows, skipped, notes } = parseCaptions(wasm, vttText, "webvtt");
const tracks = rows.map((cues, index) =>
captionTrack(`subtitles-${index}`, "Subtitles", cues, { font_family: "Hanken Grotesk" }),
);
  • 타임스탬프는 정확합니다. 1밀리초가 120틱입니다.
  • rows에는 자막 트랙 하나 분량의 큐가 한 행씩 들어 있습니다. 파일에서 겹치는 큐는 서로 다른 행으로 갑니다. 트랙은 한 번에 큐 하나만 보여 주기 때문입니다. 한 행에는 트랙이 담을 수 있는 최대치인 4096개까지 들어갑니다.
  • WebVTT voice는 큐의 speaker가 됩니다. voice, <c.class>, <b>, <i>, <u>는 런의 클래스가 되고, 인라인 타임스탬프는 큐를 시간이 지정된 런으로 나눕니다. 나머지 마크업은 버립니다.
  • skipped는 읽을 수 없었던 블록의 수이고, notes는 읽었지만 유지할 수 없었던 마크업의 수입니다.

예를 들어 다음 WebVTT 큐는 시간이 지정된 런 네 개가 되며, 위 예제의 세 번째 큐와 같습니다.

00:00:04.000 --> 00:00:06.000
One <00:00:04.500>word <00:00:05.000>at <00:00:05.400>a time.

splitCue와 trimCue는 런의 시간을 유지하고 기준점을 다시 잡으면서 큐를 나누거나 줄입니다. 에디터에서는 importCaptions가 자막 파일을 실행 취소 한 단계로 가져옵니다. Python에서는 alpha2.compose.Captions가 SubRip, WebVTT, 큐 목록, 단어 타임스탬프를 받습니다.