captions
단어 수준의 전사 데이터로부터 ASS 자막 파일을 생성하고, 이를 렌더링 결과물에 자막으로 입힙니다(burn-in).
인자: $project 슬러그, $preset은 presets/captions/*.json에 있는 이름입니다. 전체 요청은 $ARGUMENTS에 담겨 있습니다. 빈 $-플레이스홀더는 스킬이 슬래시 명령이 아닌 자동 트리거되었음을 의미합니다: 세션 컨텍스트의 PIPELINE_STATE에서 프로젝트를 가져와서 선택한 프로젝트를 명시하세요. 동사 이름은 빌드 규칙을 따르며, python engine/ve.py --help가 우선합니다.
이 스킬의 작동 여부를 결정하는 두 가지 독일어 특화 규칙
1. max_chars로 절대 그룹화하지 마세요
독일어 합성 명사는 쪼갤 수 없는 단일 토큰으로, 어떤 글자 수 제한도 초과합니다. Geschwindigkeitsbegrenzung은 26자이며, 90px 폰트에서 1920 프레임의 약 **60%**를 차지합니다. max_chars: 22 규칙으로는 이 단어를 분할할 수 없으므로, 프레임을 넘치게 하거나 그룹화기가 약속한 것과 다른 줄을 조용히 생성하게 됩니다.
대신 **픽셀 너비 예산(pixel-width budget)**을 기준으로 그룹화하고, 단일 단어가 예산을 초과하는 경우 자동 축소 규칙을 적용하세요. estimate_text_width_px()가 측정을 담당하고, group_words()가 예산을 관리합니다. 자세한 내용은 docs/fonts.md와 engine/vedit/captions.py 모듈의 독스트링에 있습니다.
이것은 가장 흔한 독일어 자막 실패 사례이며, 완전히 피할 수 있습니다.
2. 폰트는 정적 단일 두께 파일이어야 하며, 정확한 이름ID 1로 지정되어야 합니다
가변 폰트(Variable font)는 ASS에서 두께를 선택할 수 없습니다. InterVariable.ttf는 libass를 통해 Arial과 바이트 단위로 동일하게 렌더링되며(동일한 md5), 파일을 로드했다는 로그만 남깁니다. Exit 0, 경고 없음. Style: D,Inter,...,Bold=-1은 DirectWrite Arial을 가져오며, 내보내기가 YouTube에 업로드될 때까지 아무도 눈치채지 못합니다.
다음만 사용하고, 정확한 Fontname 문자열과 Bold 값을 사용하세요:
| 파일 | ASS Fontname | Bold |
|---|---|---|
Inter-Regular.ttf | Inter | 0 |
Inter-Bold.ttf | Inter | -1 |
Inter-SemiBold.ttf | Inter SemiBold | 0 |
Inter-ExtraBold.ttf | Inter ExtraBold | 0 |
Montserrat-Bold.ttf | Montserrat | -1 |
Montserrat-ExtraBold.ttf | Montserrat ExtraBold | 0 |
assets/fonts/에는 폰트 파일만 있어야 합니다. libass는 그 디렉토리에서 찾은 모든 파일을 로드하며, 다른 파일이 있으면 오류를 발생시킵니다. 예를 들어 README.md 파일이 있으면 Error opening memory font 'README.md' 오류가 발생합니다. 설정에서 이 디렉토리는 ask 권한으로 설정되어 있습니다. 만약 폰트와 함께 문서가 필요하다면 docs/fonts.md에 넣으세요.
1단계 - 프리셋 선택
presets/captions/*.json에는 스타일, 그룹화 예산, 위치 및 하이라이트 동작이 정의되어 있습니다. 취향이 아니라 output.platform에 따라 선택하세요:
- 수직형 플랫폼은 안전 영역 상단에 크고 중앙 정렬된 1-3개 단어를 원합니다. 하단 1/3은 플랫폼 자체 UI 아래에 가려집니다.
- 유튜브 장형 콘텐츠는 두 줄, 하단 고정, 휴대폰 시청자가 읽을 수 있는 크기를 원합니다.
생성 전에 사용자와 선택 사항을 확인하고, 무엇이 변경되었는지 설명하세요.
2단계 - ASS 생성
python engine/ve.py captions $project --preset $preset
두 가지 구조적 요구 사항이 있으며, 위반 시 모두 조용히 실패합니다:
- 23개 필드의 V4+
Style: Format:줄(Name..Encoding, libass 자체 필드 목록 - 24번째 필드인AlphaLevel은 SSA v4이며 ASS V4+가 아님)과 10개 필드의 EventsFormat:줄은 표준이어야 하며, 각 줄 아래의 행은 정확히 그 수의 열을 가져야 합니다. 열 수가 일치하지 않으면 libass가 exit 0으로 종료되고, 렌더링된 텍스트에 쉼표를 하나 추가하고, 외곽선과 굵게 스타일을 조용히 제거합니다. 5개 필드 Format에 6개 필드 Dialogue가 있는 경우에서 재현되었습니다.captions.validate_ass()는 자막 렌더링 전에 두 개수를 모두 확인합니다. 이는 선택 사항이 아니며,burn_in()은 실패 시 거부합니다. - 파일은 UTF-8이어야 합니다. UTF-8일 때 독일어 발음 구별 기호가 libass를 통해 올바르게 처리됩니다.
Größe Straße Übermäßig äöüÄÖÜß가 종단 간 검증되었습니다.
사이드카 파일은 projects/<slug>/out/captions.ass에 기록되며, captions.sidecar_path에서 참조됩니다.
타이밍은 컷이 생성된 동일한 단어 목록에서 가져오며, _computed의 타임라인 위치를 통해 매핑됩니다. 따라서 자막은 컷 변경 시 무효화됩니다. 컷을 다시 한 후에는 패치하지 말고 재생성하세요.
3단계 - 텍스트 수정, 전사본은 수정하지 마세요
숫자는 예측 가능한 문제입니다. 전사 시 --suppress_numerals는 필수이므로, 화자가 "20"이라고 말했을 때 전사본에는 zwanzig라고 기록됩니다. captions.overrides[]에서 {word_index, text}로 수정하고, 절대 전사본을 편집하지 마세요. 전사본은 프로젝트의 모든 단어 범위에 대한 기준점이며, 이를 다시 쓰면 컷의 동기화가 깨집니다.
고유 명사, 제품 이름 및 GmbH 스타일 약어도 마찬가지입니다. 낮은 score 단어를 스캔하여 한 번에 수집하세요: 정렬 점수는 필러 신호로는 쓸모없지만, "정렬기가 이 단어가 무엇인지 확신하지 못했다"는 신호로는 좋으며, 이는 정확히 고유 명사가 보이는 방식입니다.
4단계 - 자막 렌더링 (Burn-in)
python engine/ve.py render $project
자막 렌더링은 최종 먹싱 단계에서, 연결된 비디오 위에 한 번만 수행됩니다. 세그먼트별로 렌더링하지 마세요. 세그먼트별 렌더링은 모든 경계에서 동일한 자막을 다시 렌더링하여 컷 양쪽에 부분적인 줄이 생깁니다.
subtitles= 및 fontsdir= 이스케이프는 정확하며 명확하지 않습니다. 모든 곳에서 슬래시를 사용하고, 값은 드라이브 콜론을 이스케이프한 작은따옴표로 인용해야 합니다:
subtitles='C\:/Users/me/.../out/captions.ass':fontsdir='C\:/Users/me/.../assets/fonts'
vedit/ffmpeg.py의 escape_filter_path()가 이를 처리하며, 인용 부호를 포함한 문자열을 반환합니다. 직접 인용 부호를 추가하지 마세요. 중첩 깊이가 아니라 인용이 중요합니다: 단일 이스케이프된 형태는 No option name near '/Users/...' 오류로 실패하며, Windows에서 합법적인 경로에 쉼표, 세미콜론 또는 대괄호가 있는 경우 인용된 형태만 작동합니다. escape_filter_path_nested()는 이중 이스케이프된 인용되지 않은 C\:/... 변형을 반환하며, 해당 제한 사항이 문서화되어 있습니다.
모든 것은 Python의 argv 목록을 통해 전달됩니다. Git Bash에서 동일한 경로를 빌드하면 C\;C:\Program Files\Git\Users\...가 됩니다. 실제로 재현된 사례입니다.
captions.enabled 및 captions.burn_in을 설정하고, stage를 "captions"로 설정하세요.
5단계 - 폰트가 실제로 로드되었는지 확인
QC 검사 (e)는 필수이며, 이것이 조용한 폴백(Fallback)을 잡을 수 있는 유일한 방법입니다. 프로젝트 스타일로 자막 프레임 하나와 Arial 제어 스타일로 자막 프레임 하나를 렌더링하고, 해시가 다른지 확인하세요. 해시가 동일하면 libass가 폴백했다는 의미이며, 내보내기가 잘못된 서체로 제공됩니다.
그런 다음 다음도 확인하세요: Format: 줄의 필드 수, 픽셀 예산을 초과하는 줄이 없는지, 세그먼트 경계를 넘어 단편이 남는 자막이 없는지.
실패 모드
| 증상 | 원인 | 해결책 |
|---|---|---|
| 자막이 Arial처럼 보임 | 가변 폰트 또는 잘못된 Fontname | 정적 파일 + 정확한 nameID 1 문자열; QC (e) 실행 |
| 텍스트에 쉼표가 추가되고 외곽선 없음 | 잘못된 Format: 줄 | 표준 24/10 필드 줄 생성 |
Error opening memory font 'README.md' | assets/fonts/에 폰트가 아닌 파일 | 해당 디렉토리에는 폰트만 |
| 긴 단어 하나가 프레임을 벗어남 | max_chars로 그룹화됨 | 픽셀 너비 예산 + 단일 단어 자동 축소 |
No option name near '/Users/...' | fontsdir 단일 이스케이프됨 | 콜론 앞의 백슬래시를 두 번 |
| 자막에 숫자가 풀려서 표시됨 | 설계된 대로 --suppress_numerals | captions.overrides 항목 |
| 재컷 후 자막 동기화 깨짐 | 오래된 ASS | 재생성; 절대 수동으로 타이밍 패치하지 마세요 |