Communitygithub.com

자막(vedit)

사용자가 자막 또는 화면 내 텍스트를 요청할 때 사용하는 스킬입니다. ASS 자막 생성, 독일어를 위한 픽셀 너비 그룹화 규칙, 폰트 테이블, libass를 통한 자막 렌더링을 다룹니다.

자막(vedit)란 무엇인가요?

이 스킬은 AI 에이전트가 사용자가 요청한 자막 생성 및 삽입 작업을 완전히 자동화할 수 있도록 설계되었습니다. 사용자의 자연어 프롬프트(예: '자막 넣어줘', '캡션 추가해')를 인식하여, 단어 수준의 전사 데이터로부터 ASS 서브타이틀 파일을 생성하고 이를 비디오에 자막으로 입히는(burn-in) 전 과정을 처리합니다. 이 스킬은 특히 독일어 자막 처리에 특화되어 있는데, 독일어의 긴 합성 명사(예: 'Geschwindigkeitsbegrenzung')가 최대 글자 수 기반 그룹화에서 문제를 일으키는 것을 피하기 위해 픽셀 너비 예산(pixel-width budget)을 기준으로 자막 줄을 나누는 고유의 규칙을 적용합니다. 또한, ASS 형식의 엄격한 필드 수를 검증하고, 가변 폰트로 인한 자막 폰트 대체 문제를 방지하기 위해 정적 단일 두께 폰트 파일만 사용하도록 강제합니다. 지원 플랫폼은 수직형 숏폼(예: 틱톡)과 유튜브 장형 콘텐츠를 포함하며, 각 플랫폼에 맞는 미리 설정된 스타일 프리셋을 제공합니다. 마지막으로, 생성된 자막이 올바르게 렌더링되었는지 해시 비교를 통해 검증하는 QC 단계를 포함하여 최종 출력물의 품질을 보장합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/businessbaum/vedit/tree/HEAD/.agents/skills/captions

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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 FontnameBold
Inter-Regular.ttfInter0
Inter-Bold.ttfInter-1
Inter-SemiBold.ttfInter SemiBold0
Inter-ExtraBold.ttfInter ExtraBold0
Montserrat-Bold.ttfMontserrat-1
Montserrat-ExtraBold.ttfMontserrat ExtraBold0

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개 필드의 Events Format: 줄은 표준이어야 하며, 각 줄 아래의 행은 정확히 그 수의 열을 가져야 합니다. 열 수가 일치하지 않으면 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_numeralscaptions.overrides 항목
재컷 후 자막 동기화 깨짐오래된 ASS재생성; 절대 수동으로 타이밍 패치하지 마세요

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

businessbaum/vedit

This skill should be used when the user asks for mechanical tightening of material that is already selected - "mach den Rohschnitt", "bau den Rohschnitt", "schneid die Ähms raus", "nimm die Füllwörter raus", "entferne die Pausen", "kürz die Pausen", "schneid die Versprecher raus", "mach den Radioschnitt", "straff das", "build the rough cut", "cut the fillers", "remove the ums", "tighten the pauses", "trim the dead air", "cut the retakes", "radio edit". Turns kept story blocks into enabled segments in project.json. Does not decide what the video is about - that is cut-story.

businessbaum/vedit

This skill should be used when the user asks to decide what the video is actually about before any mechanical cutting - "bau die Story", "was soll ins Video", "was kann raus", "welche Themen bleiben", "kürz das auf 6 Minuten", "mach daraus ein kurzes Video", "wo ist der Hook", "sortier die Themen", "worum geht es hier eigentlich", "build the story", "decide what stays", "structure this", "cut this down to N minutes", "find the hook", "reorder the topics". This is the CONTENT-selection pass that writes the "story" block in project.json; it runs after transcribe and before cut-rough.

businessbaum/vedit

This skill should be used when the user asks for the finished video file - "exportier das", "render das fertig", "mach das final", "renderte das Video", "mach den Endexport", "bau das Video zusammen", "mach es fertig für YouTube", "gib mir die MP4", "export the final", "render the final cut", "make the master", "export for TikTok", "give me the mp4". Covers segment rendering with the cache, guarded concat, the one-pass audio, loudness normalisation and the single mux.

관련 스킬