Communitygithub.com

znlgis/video-studio

코드로 중국어 동영상(과학/다큐/홍보/애니메이션)을 처음부터 제작합니다: 시나리오 작성 → edge-tts 내레이션 → 실제 재생 시간에 따른 타임라인 → PIL 또는 Canvas 프레임 단위 렌더링 → ffmpeg 인코딩 → numpy 샘플 수준 오디오 믹싱 → 자동 품질 검사. '동영상 만들기 / 홍보 영상 제작 / 보고서를 동영상으로 / 애니메이션 / 더빙 및 자막 / 완성본 / AV 싱크 불일치 수정 / 화질 저하' 요청 시, 또는 ffmpeg, edge-tts, 프레임별 렌더링, 자막, 스토리보드, 배경 음악, 오디오 믹싱이 언급될 때 사용합니다.

video-studio란 무엇인가요?

이 스킬은 코드를 사용하여 처음부터 완전한 중국어 내레이션 동영상을 제작하는 전문적인 AI 에이전트 능력입니다. 주로 Claude Code, Cursor, Codex와 같은 AI 코딩 에이전트에서 실행되며, 사용자가 '동영상 제작' 또는 '보고서를 동영상으로 변환'과 같은 요청을 할 때 자동으로 활성화됩니다. 핵심 워크플로우는 확정적이며 재현 가능합니다: 먼저 `film.json` 스크립트를 작성한 후, `propose.py`를 사용하여 여러 디자인 후보를 생성하고(모든 요청에 필수), 사용자가 선택하면 `render.py`가 나머지 파이프라인을 처리합니다(TTS 오디오 생성, 프레임별 이미지 렌더링, ffmpeg를 사용한 비디오 인코딩, numpy를 사용한 샘플 수준 오디오 믹싱). 오디오는 먼저 실제로 측정된 edge-tts 지속 시간을 사용하여 생성되며, 프레임 렌더링은 '순수 함수'라는 엄격한 규칙을 따릅니다(동일한 프레임 번호는 항상 동일한 이미지를 생성). 모든 단계는 `render.py`의 `--stage` 플래그(`tts`, `frames`, `qa` 등)를 사용하여 독립적으로 중단 및 재개할 수 있습니다. 이 스킬에는 제안 단계를 우회하는 것을 방지하기 위한 엄격한 품질 관리 게이트가 내장되어 있으며, 단순한 프레임워크가 아닌 전체 제작 도구 역할을 합니다. PIL(빠르고 확정적)과 브라우저 Canvas2D(입자 및 셰이더에 적합)의 두 가지 렌더링 엔진 중에서 선택할 수 있습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/znlgis/dsh-video-studio-skill/tree/HEAD/skills/video-studio

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

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

문서

비디오 스튜디오 (video-studio)

한 줄 요약: 동영상 = 대본 + 오디오 실제 재생 시간 + 확정적 프레임 함수 + ffmpeg 인코딩 + 자동 품질 검사. 화면과 사운드는 모두 코드로 생성되며,素材 없음, 재현 가능, 중단 및 재개 가능, 스크립트화된 검증 가능.

0. 불변의 법칙 (위반 시 완성본에 반드시 문제 발생)

  1. 오디오를 먼저, 화면은 나중에. 타임라인은 TTS 실제 재생 시간으로 역산해야 하며, 절대 글자 수로 추정하지 마세요. 글자 수로 만든 타임라인은 20번째 문장부터 눈에 띄게 어긋납니다.
  2. 모든 프레임은 순수 함수: draw(t, frame) -> Image. random/time()은 금지, 무작위성은 filmkit.hash2/hash3/vnoise를 사용하세요. 동일한 프레임 번호는 항상 동일한 이미지를 그려야 합니다.
  3. 자막과 큰 글자는 안전 영역 안에 있어야 함: 본문 y ∈ [58, 974], 자막 줄 y ∈ [872, 1030], 절대 1030을 초과하지 않음.
  4. 원본 프레임 직접 인코딩: PIL 프레임 → rawvideo 파이프 → libx264. 중간 파일로 cv2.VideoWriter를 사용하지 마세요 (2배 느리고 이차 손실 발생).
  5. 오디오 믹싱은 numpy 샘플 수준 오버레이 사용, 수백 개의 입력을 ffmpeg 명령 하나에 전달하지 마세요 (Windows에서 비확정적 무음 발생).
  6. 배송 전 반드시 품질 검사 스크립트 실행 + 3장의 스틸 프레임 직접 확인 (read_image 사용). "이미지가 나온다"고 "문제없다"는 아닙니다.
  7. 사운드는 화면을 따라감: 소리가 나야 하는 지점은 장면 코드에 큐 지점을 등록 (K.CueBus / scenes.py의 cues() 훅), 사후에 수동으로 시간을 맞추지 마세요. 화면이 변경되면(체류 창 변경, 리듬 조정), 큐 지점도 자동으로 변경됩니다.
  8. 잘 안 보이면 체류 시간을 추가하고, 전체 영상을 느리게 하지 마세요: film.json의 hold (전역) 또는 lines[].hold (개별 문장)를 음성이 끝난 후에 추가하고, 배경 음악은 bpm을 사용하여 마디를 정렬하세요.

0.5 【불변의 법칙】 먼저 제안하고, 그 다음에 제작 (사용자가 요구하지 않더라도)

사용자가 요구하지 않아도 반드시 먼저 방안을 제시해야 합니다. 이 스킬에서 가장 중요한 제품 동작이며, 권장 사항이 아닙니다. 이유: 외형이 프로그램에 의해 사용자를 대신해 선택되면, 사용자는 처음부터 끝까지 의사 결정에 참여하지 못하게 되고, 결과는 "모든 동영상이 비슷해 보인다"는 것입니다. 따라서 프로세스는 고정되어 있습니다: 현장에서 여러 방안 샘플링 → 사용자가 하나 선택 → 디자인 확정 → 그 다음 제작.

고정 동작 (새로운 완성본 제작 작업의 첫 번째 일):

python propose.py --brief "<주제/대상/시간/톤>" --script film.json --n 3

이 명령은 N개의 후보 방안을 생성하고 (각 방안 = 디자인 1개 + 스틸 프레임 4장 + 비용 + 다른 후보와의 디자인 거리), work/proposal.json에 저장하며, "하나를 확인하세요 / 모두 마음에 안 들면 --revise"라는 메시지를 출력합니다. 전체 과정 오프라인, 1초 미만.

사용자 반응수행할 작업
"이걸로" / 하나 선택python propose.py --confirm --candidate B를 실행한 후에만 렌더링 허용
"너무 차갑다/너무 화려하다/다른 목소리/템포가 더 빠르게"python propose.py --revise "<원문>" —— 새 개정, 이전 개정은 diff 가능하도록 유지
"다 안 돼, 다시"위와 동일, 요구 사항을 구체적으로 작성 (--revise 텍스트가 proposal.feedback에 저장됨)
명확히 "네가 정해"그래도 먼저 방안 제시, 당신이 추천하는 하나를 선택하고 이유를 설명하여 사용자 동의 받기

엄격한 제약 조건:

  1. propose.py --confirm 전에, render.py의 frames/all은 제안 게이트에 의해 차단됩니다 (기본 경고, --require-proposal 시 실패).
  2. 후보는 두 개 이상의 디자인 차원에서 서로 달라야 함 (색상 계열/모티프/전환/이징/타이포그래피/배경 음악/음성). 색상만 바꾸는 것은 다중 방안이 아닙니다, diversity.vs[].n < 2는 플래그 지정됩니다.
  3. 각 후보는 하나의 중국어 입안 (pitch) 이 있어야 하며, "이 방안이 달성하려는 비전"을 설명해야 함 —— "방안 A/방안 B"가 아닙니다.
  4. 내장 템플릿 없음: design.json의 모든 필드는 개방되어 있습니다 (35개 필드 테마 + 5개 시각적 언어 시그니처 + 사운드/배경 음악/리듬), 색상은 연속 색상 공간의 임의의 한 점이며, 기성 외형 세트 중에서 고르는 것이 아닙니다. 각 제안은 현장에서 생성됩니다 – 동일한 brief를 연속 두 번 실행하면 다른 디자인을 얻습니다 (재현하려면 --seed 전달).
  5. 작품 간 중복 제거 원장 (기본 활성화): --confirm은 확정된 디자인을 ~/.dsh/video-studio/design-ledger.json에 기록하고 (최대 500개), 다음 제안 시 최근에 배송된 외형을 적극적으로 회피합니다 – 후보와 원장에 있는 어떤 영화와의 디자인 거리가 < 2차원이면 자동으로 nonce를 교체하여 재샘플링하고 경고합니다. 이는 "다른 영화는 반복하지 않는다"를 "무작위 의존"에서 "기억 있음"으로 바꾸는 메커니즘입니다. --show-ledger로 원장 조회, --no-ledger로 이번 실행 무시, 환경 변수 VIDEO_STUDIO_LEDGER로 경로 변경 또는 off 설정으로 비활성화. 원장 읽기/쓰기 실패 시 항상 빈 테이블로 처리되며, 출시를 절대 차단하지 않음.

제안을 건너뛸 수 있는 유일한 경우: 순수 수리 / 순수 재렌더링 (내용 변경 없음, 디자인 변경 없음, 오디오/비디오 싱크 불일치만 수정하거나 재출력). 이 경우 --require-proposal 외의 방식으로 실행하고, 응답에 "이번에는 다음 이유로 제안하지 않았습니다..."라고 설명.

0.5.1 외형은 어디서 오는가: 두 가지 방법뿐

'내장 스타일'은 선택할 수 없습니다. film.json에 style 값을 쓰는 것 (auto / legacy / 특정 id)은 명확히 거부되고 두 단계 안내가 제공됩니다 – 대본이 외형을 결정하지 않으며, 외형은 work/design.json이 결정합니다. 마찬가지로, 스킬에는 THEMES/STYLES/VISUAL_LANGUAGES 테이블이 없고, presets/도 없습니다. 배경 음악은 musicgen.py가 매개변수에 따라 현장에서 편곡하고, scenes.draw_frame에도 레거시 브랜치가 없습니다.

특정 외형을 원한다면 두 가지 방법뿐입니다:

# ① 자연어로 요청 (must_have/avoid의 키워드 인식)
python propose.py --brief "기술 발표회, 다크 블루, 시네마스코프 비율" --script film.json

# ② 직접 design.json 작성 (모든 필드 개방, 색상은 임의의 RGB)
python render.py --script film.json --stage all     # 전제 조건: work/design.json 이미 존재

상세 계약은 references/proposal.md 및 schemas/design.schema.json 참조.


1. 5단계 시작하기 (제로 카피: 스크립트, 템플릿 복사 불필요)

이 경로에는 '복사' 동작이 전혀 없습니다. 스킬 스크립트는 스킬 디렉토리 내에서 직접 실행됩니다. 프로젝트 디렉토리에는 film.json (및 실행 중 생성되는 work/ 및 out/)만 있으면 됩니다.

<SKILL_DIR>은 **스킬 로드 시 skill 도구가 반환한 resourceBase.path**를 의미합니다. 플러그인으로 설치되면 이 플러그인 패키지 내 skills/video-studio/입니다 (프로필의 node_modules/@dsh-gis/dsh-video-studio-skill은 링크). 찾을 수 없으면 이름으로 위치 확인: find ~ -path '*/skills/video-studio/scripts/filmkit.py'.

<REPO>는 패키지 루트 (저장소 루트, 또는 설치 후 프로필의 node_modules/@dsh-gis/dsh-video-studio-skill/)를 의미하며, 즉 <SKILL_DIR>의 두 레벨 위입니다. tools/ 및 schemas/는 <REPO> 아래에 있으며, <SKILL_DIR> 아래에 있지 않습니다.

# 1) film.json 하나만 작성 (프로젝트 디렉토리에 수동으로 작성해야 하는 유일한 파일)
mkdir -p myfilm && cd myfilm
cat > film.json <<'JSON'
{
  "title": "내 영화",
  "lines": [
    {"id": "l01", "kind": "hook",  "chapter": "01 · 시작",
     "text": "이것은 코드로 처음부터 생성된 화면과 소리입니다.", "sub": "素材 없음, 전체 코드 생성", "big": "素材 없음, 전체 코드 생성"},
    {"id": "l02", "kind": "body",  "chapter": "02 · 본문", "kicker": "PIPELINE",
     "text": "전체 파이프라인은 다섯 단계뿐입니다: 시나리오 작성, 내레이션 생성, 타임라인 구성, 프레임별 렌더링, 오디오 믹싱 합성.",
     "sub": "5단계 완성, 각 단계 저장", "big": "5단계 완성",
     "items": [{"text": "시나리오 film.json"}, {"text": "내레이션"}, {"text": "타임라인"}, {"text": "프레임별 렌더링"}, {"text": "오디오 믹싱"}]},
    {"id": "l03", "kind": "outro", "chapter": "99 · 마무리",
     "text": "먼저 대본을 정하고, 다음으로 사운드를 정하고, 마지막으로 화면을 정합니다.", "sub": "순서가 노력보다 중요", "big": "순서가 노력보다 중요"}
  ]
}
JSON
#    lines[].text = 낭독 텍스트 (자연스러운 구어체); sub = 화면 자막 (짧게); kind = hook/body/quote/outro/emphasis/calm
#    그림 스타일/배경 음악/음성/리듬은 모두 design이 결정합니다. film.json에는 **작성할 수 있는 style 필드가 없습니다**.
#    계약 검증 (선택 사항, 몇 초):
python <SKILL_DIR>/scripts/filmcheck.py film.json

# 2) 먼저 환경 자체 점검 (선택 사항; 69개 회귀 가드, 약 10–20초, 오프라인; 선택 모듈 누락 시 SKIP 표시)
python <SKILL_DIR>/scripts/selfcheck.py

# 3) 【불변의 법칙】 먼저 방안을 제시한 후, 그 다음 제작 – 동일한 brief라도 매번 다르므로 사용자가 반드시 확인해야 함
python <SKILL_DIR>/scripts/propose.py \
       --brief "AI 대형 언어 모델의 3년, 30초 과학, 다크 블루" \
       --script film.json --work work
#    3개의 후보 생성: 각각 디자인 1개 + 스틸 프레임 접촉 시트 (work/proposals/rev1/_compare.png) + 디자인 거리,
#    약 2초 결과, 전체 오프라인. 나란히 놓인 그림을 보고 하나 선택.

# 4) 후보 확인, 디자인 확정 (work/design.json) – 이후 렌더링은 이 디자인만 인식
python <SKILL_DIR>/scripts/propose.py --confirm --candidate A --work work
#    마음에 안 들면 다른 것으로: --revise "좀 더 차갑게, 장식 줄이고, 여성 음성으로 변경" (새 개정, 이전 개정은 diff용 유지)
#    특정 실행 재현: 첫 번째 제안 시 --seed 42 추가 (seed 미전달 = 매번 새 nonce, 이것이 '매 영화 다름'의 기원)

# 5) 스틸 프레임 먼저 확인 후 전체 영화 제작; --scenes를 작성하지 않으면 스킬 내장 렌더러 사용
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage stills \
       --stills "l01:3,l02:5,l03:2.5"
#    → work/stills/_sheet.png: read_image로 직접 확인, 건너뛰지 마세요

python <SKILL_DIR>/scripts/render.py --script film.json --work work \
       --out out/film.mp4 --stage all
#    TTS→레이아웃→배경 음악→음향 효과→오디오 믹싱→프레임별→인코딩→품질 검사 올인원; 중간 산출물 전부 work/에, 중단 및 재개 가능.

--scenes에 관하여 (제로 카피의 핵심): render.py에 --scenes를 작성하지 않을 때의 위치 순서는 **① 시나리오와 동일한 디렉토리의 scenes.py → ② 스킬 내장 assets/scenes.py**입니다. 따라서 기본적으로 아무것도 작성하거나 복사할 필요 없이, 내장 렌더러가 직접 영화를 제작합니다. 화면을 변경하고 싶을 때만 시나리오와 같은 디렉토리에 자신만의 scenes.py를 넣으세요 (아래 draw_frame 및 선택적 cues() 구현) – 자동으로 발견됩니다. 명시적으로 --scenes my_scenes.py를 사용할 수도 있습니다. 내장 렌더러 소스 코드가 가장 좋은 예시입니다: <SKILL_DIR>/assets/scenes.py.

# 실제 시그니처 (6개 매개변수, 하나라도 빠지면 오류): `scenes.draw_frame`
def draw_frame(scene, t_local, dur, t_global, script, frame):
    # scene    : 타임라인에 있는 이 세그먼트의 딕셔너리 (id / kind / scene_type / sub / big / items / stats ...)
    # t_local  : 세그먼트 내 시간 (초, 0..dur) – 입장/지속 효과 모두 이에 따라 세그먼트 내에서 진행
    # dur      : 이 세그먼트의 총 지속 시간 (초)
    # t_global : 전체 영화 시간 (초) – 세그먼트를 가로지르는 지속 효과 (느린 줌, 브리싱)에 사용
    # script   : 전체 시나리오/계획 딕셔너리 (plan, 테마 색상, words 등; 캐시 있음, 원본 수정 금지)
    # frame    : 절대 프레임 번호 (정수) – 확정적 노이즈/위상이 필요할 때 사용, time() 사용 금지
    return img   # PIL.Image, 반드시 1920x1080 RGB (K.new_frame()으로 시작하는 것이 가장 안정적)

사용자 정의 scenes.py 작성 시 먼저 <SKILL_DIR>/assets/scenes.py의 드로어 하나를 복사하세요 (_draw_hero 등). 순수 함수, 캐시된 레이아웃, 사전 베이킹된 베이스 플레이트 등의 함정을 이미 우회한 코드입니다.

왜 먼저 제안하는가: '내장 스타일'을 선택할 수 없으며, 외형은 design.json이 결정합니다. 동일한 brief를 연속 두 번 실행하면 다른 디자인을 얻습니다 (색상/모티프/전환/배경 음악/음성이 모두 다를 수 있음). 따라서 프로세스는 "방안 생성 → 사람 확인 → 확정 → 제작"이어야 합니다. 확인 없이 렌더링하면 render.py가 제안 경고를 표시합니다 (--require-proposal 추가 시 직접 실패). 디자인 계약은 references/proposal.md 참조.

단계별 실행 (각 stage는 자신의 작업만 수행하며, 저장 후 개별적으로 재실행 가능):

python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage style   # 확인된 디자인 파싱, 테마/시각적 언어/타이포그래피 순환/자막 스타일 출력
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage tts      # 내레이션만 합성 + 실제 재생 시간 (입력 검증 포함 캐시)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage layout   # 타임라인만 구성 + 구도 선택, timeline.json / sub.srt / storyboard.json 저장
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage cues     # 큐 포인트만 생성 -> work/cues.json
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage score    # 배경 음악만 생성 -> work/music.wav (musicgen 현장 편곡)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage sfx      # 음향 효과 트랙만 생성 (sfxkit, cues.json 기반)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage audio    # 오디오 믹싱만 (내레이션+배경 음악+음향 효과, 내레이션 우선)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage frames   # 프레임만 렌더링 (--engine pil|web, --jobs N 분할 병렬)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage mux      # 합성만 (비디오 스트림 복사, 2패스 loudnorm)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage qa       # 품질 검사만 (읽기 전용 입력, work/qa/에만 쓰기)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage stills   # 마스터 스틸 프레임 + 접촉 시트만 렌더링
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage accept --gate stills   # 수동 서명 게이트 (work/signoff.json에 쓰기)
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage explain  # "어떤 단계가 구식이고, 이유와 실행해야 할 것"만 보고, 어떤 stage도 실행하지 않음

--stage all의 전체 순서: style → tts → layout → cues → score → sfx → audio → frames → mux → qa. 어느 단계에서든 문제가 발생하면 해당 단계에서 중단되며, 잘못된 입력을 가지고 계속 실행되지 않습니다.

렌더링 시작 전 비용 계산 (선택 사항; estimate.py는 저장소 도구, <REPO>로 호출):

python <REPO>/tools/estimate.py --script film.json --jobs 4

이는 프레임 수/각 단계 소요 시간/디스크/분할 권장 사항을 제공하며 (--json으로 기계 판독 가능), 구간 상한이지 약속이 아닙니다. work/timeline.json이 있으면 실제 지속 시간을 사용하고, 없으면 글자 수로 대략 추정합니다. 전체 영화 VMAF를 생략하려면 film.json에 "qa": {"vmaf": {"mode": "off"}}를 작성하세요 (estimate와 render 모두 이 필드를 읽고, G11은 skipped로 기록).

배송 증명서 확인 후 서명 검증:

cat out/QC-REPORT.md     # 게이트 매트릭스 pass / fail / skipped 세 등급; skipped = 이번에 실행되지 않음, 통과 의미 아님
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage accept --gate release

서명 검증은 콘텐츠 지문에 바인딩됩니다: 대본 / scenes.py / 디자인을 변경하면 이전 서명은 자동으로 무효화되며, 다시 보고 다시 서명해야 합니다. 배송 전 강제 (skipped도 실패로 간주): --strict-gates 추가.

장편 렌더링 (분할 병렬 + 중단 및 재개):

python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage frames --jobs 4
python <SKILL_DIR>/scripts/render.py --script film.json --work work --stage frames --jobs 4 --resume

분할은 work/segments/에 저장되며, seg_<시작_프레임>_<끝_프레임>.mp4 형식으로 명명됩니다. 연결 전에 갭을 명시적으로 감지하고, 누락된 블록은 직접 오류를 발생시키며, "일부 세그먼트가 없어도 영화를 만들 수" 없습니다. 기본 청크는 총_프레임_수 / jobs / 4를 정수 초로 반올림하여 결정됩니다 (jobs=4, 900프레임일 때 약 8초 분할). film.json의 chunk_frames로 재정의 가능.

대사 / scenes.py / 디자인 / 인코딩 매개변수 변경 후, --resume은 이전 분할의 재사용을 거부합니다 (work/segments/_plan.json에 계획 지문 저장), 그리고 rm -rf work/segments를提示합니다 – 이는 의도적인 것입니다: 이전 분할과 새 타임라인을 결합하면 "화면과 내레이션이 맞지 않는" 영화가 만들어지며, 지속 시간 게이트로 감지할 수 없습니다.

2. 표준 8단계 (P0–P7)

단계산출물주요 동작일반적인 오류
P0.5 디자인 확정work/design.json + work/style.jsonpropose.py로 방안 생성 → --confirm으로 디자인 확정; --stage style는 확인된 디자인만 파싱하고 테마/시각적 언어/타이포그래피 순환/자막 출력건너뛰기 → render.py 오류 (design 없으면 외형 없음); 확인 없이 렌더링 시 제안 경고
P0 기준 설정CONVENTIONS.md해상도/프레임레이트/지속 시간/BPM→프레임 변환을 한 번에 계산하여 고정, 전체 프로젝트에서 동일한 참조 사용각 파일이 150과 같은 매직 넘버를 자체적으로 작성 → 장면 경계 정렬 불일치
P1 대본 작성film.json각 문장을 text(낭독)와 sub(자막)로 분리; 각 단락에 kind; 선택적 lines[].type로 구도 고정하나의 필드를 두 용도로 사용 → 자막이 너무 길거나 읽기에 어색함; style 작성 금지 (더 이상 화면에 영향을 미치지 않음, 외형은 work/design.json이 결정)
P2 사실 확인FACTS.md화면에 표시되는 모든 숫자/연도는 출처 번호가 있어야 함; "표시 금지" 블랙리스트 작성이 단계 건너뛰기 → 화면과 내레이션 숫자가 충돌, 댓글 첫 번째가 당신이 틀렸다고 지적
P3 TTSwork/tts/*.mp3 + tts.jsonedge-tts로 문장별 합성, ffmpeg -f f32le로 실제 재생 시간 측정, 디스크 캐시글자 수로 시간 추정 → 자막 어긋남
P4 타임라인 구성work/timeline.json + sub.srtdur = lead + 실제 음성 + tail, 문장은 반드시 해당 문단 내에 있어야 함문단이 너무 짧음 → 내레이션이 다음 문단에서 잘림
P5 오디오 믹싱work/audio.wavnumpy 버스: 내레이션 + 배경 음악 + 음향 효과를 샘플 단위로 오버레이ffmpeg 다중 입력 믹싱 → 비확정적 무음
P6 마스터3장의 스틸 프레임먼저 3프레임을 렌더링하여 구도/색상/글자 두께 확인, 결정 후 전체 영화 렌더링전체 영화 직접 렌더링 → 36000프레임 후에야 폰트가 나쁘다는 것을 알게 됨
P7 완성본out/film.mp4rawvideo→libx264 CRF18 + 2패스 loudnorm으로 -16 LUFS비트레이트过低/음량 -25 LUFS → "초안 같음"

참고: filmkit / render.py의 기본값은 crf=18입니다. assets/film.json은 테스트 픽스처일 뿐입니다 (전체 체인과 골드 프레임/게이트 회귀 테스트용, 복사할 템플릿이 아님), 17로 설정되어 있습니다. 직접 대본을 작성할 때 crf를 작성하지 않으면 18입니다. 더 선명한 텍스트를 원하면 17을 작성할 수 있습니다 (볼륨 약 +30%).

3. 렌더링 엔진 선택 방법

시나리오선택실제 속도적합
그림/텍스트/데이터/과학/UI 스타일PIL (numpy+cv2 보조) — filmkit 기본40–80 fps @1080p30 (예시 75)대부분의 중국어 과학 영화, 보고서를 비디오로 변환
부드러운 벡터/입자/3D/종이 질감 필요헤드리스 브라우저 Canvas2D/WebGL + CDP 스크린샷 → image2pipe (webengine.py)80–130 ms/프레임 (내장 그래픽, PNG 스크린샷)다큐멘터리 수준 장편, 복잡한 시각적 언어 필요
순수 데이터 애니메이션manim (설치된 경우)시나리오에 따라 다름수학/공식 유도

위 표 속도는 단일 시스템 유휴 실제 측정 기준입니다 (시스템 변경, 무거운 화면 변경 시 달라짐; §12 참조). 구간을 보장으로 간주하지 마세요.

디자인은 PIL 엔진만 다룹니다 (engine: pil, 기본값): work/design.json의 색상/폰트/레이아웃/모티프는 assets/scenes.py가 읽습니다. --engine web의 페이지는 직접 작성해야 합니다 (assets/web/는 실행 가능한 예시 페이지일 뿐, 제작 템플릿이 아님), 화면은 디자인을 따르지 않습니다. 하지만 배경 음악과 내레이션은 그대로 design.json을 따릅니다 (렌더링 엔진에 의존하지 않음).

기본 PIL: 의존성 적음 (numpy+Pillow만 있으면 됨), 시작 빠름, 확정성 강함, 디버깅 비용 낮음. "원샷 원킬 입자 변형 / 실시간 셰이더 질감"이 필요한 경우에만 브라우저 방식 사용:

cp -r <SKILL_DIR>/assets/web ./web                     # 페이지 템플릿 (네이티브 Canvas2D, 제로 서드파티 라이브러리)
pip install playwright && python -m playwright install chromium   # 웹 엔진 사용 시에만 필요
python render.py ... --engine web --stage cues        # 페이지에서 큐 포인트 내보내기
python render.py ... --engine web --stage frames --jobs 3 --resume
python webengine.py textqa ./web/index.html            # 텍스트 중복/프레임 이탈/번쩍임/한 번도 나타나지 않음 검사

두 경로의 선택 (먼저 명확히 생각하고 선택하세요, 엔진 변경 시 화면을 다시 만들어야 함):

PIL (engine: pil)브라우저 (engine: web)
의존성numpy + Pillow (+cv2 선명도 비교)+ playwright + Chromium/Edge
속도40–80 프레임/초8–12 프레임/초 (스크린샷+인코딩, 한 자릿수 느림)
화면 언어비트맵 합성, 그라데이션, 기하학, 텍스트Canvas2D/WebGL: 입자, 변형, 셰이더, 벡터 애니메이션
확정성자연스러움 (순수 함수 그림)타임라인을 정확히 f/FPS로 seek하여 보장 (window.renderFrame)
디버깅프레임 하나 수정 후 실행브라우저에서 index.html?dev=1&t=초로 직접 확인, 버그 발견 쉬움
적합그림/텍스트/데이터/과학/홍보 영상다큐멘터리 수준 장편, 원샷 원킬 시각적 언어 필요

브라우저 엔진의 세 가지 엄격한 제약 조건 (webengine.py에서 이미 구현됨, 직접 페이지를 작성할 때도 따라야 함):

  1. 모든 트위닝은 일시 정지된 하나의 메인 타임라인에 배치되며, f번째 프레임 렌더링은 f / FPS 초로 seek하는 것임; 재생에 의존 금지.
  2. 루프/입자/파도와 같은 프로그래밍 방식 콘텐츠는 "시간 기준 직접 계산" 업데이터 (update(fn))를 사용하며, setInterval 누적 금지.
  3. 페이지는 반드시 window.ready / DURATION / renderFrame(f) / getCues()를 노출해야 함; CDN 연결 금지, 네트워크 요청 금지.

브라우저 세션 간 바이트 수준 재현성 (충돌 후 재시작, 여러 번 렌더링하여 diff, 품질 검사에서 두 이미지 비교)이 필요할 때 확정성 모드 활성화: --deterministic 또는 환경 변수 WEBENGINE_DETERMINISTIC=1 (GPU 래스터화 비활성화, 8/8 세션 일치 실제 확인, 비용 약 +22%/프레임). 기본 비활성화: 동일 세션 내에서는 원래 일치하며, 세션 간에는 ≤4 픽셀, ±1 레벨의 앤티앨리어싱 차이만 있습니다.

4. 화질 체크리스트 (이 항목들이 "전문가 수준 영화처럼 보이는지" 결정)

  • 밝기와 계조: 어두운 배경 (#0a1420) + 상단 밝은 악센트; 순수 흑백 사용 금지, 사용자 화면이 그라데이션을 먹어버림.
  • 글자 두께와 크기: 큰 글자 84–96px, 부제목 34–38px, 자막 46px; 중국어는 msyhbd.ttc 사용 (Microsoft YaHei Bold).
  • 자막: ≤ 2줄, 한 줄 약 24자, 반투명 배경 포함, y ≥ 872. 실제 줄바꿈은 픽셀 너비를 기준으로 합니다 (fit_sub_lines가 max_w=1560 px 기준으로 자동 줄바꿈). 24자는 "대략적인 상한"일 뿐 하드 검증이 아닙니다 —— SUB_MAX_CHARS=24라는 상수는 현재 어떤 코드에서도 참조되지 않으며, 이를 막아줄 것이라고 기대하지 마세요.
  • 리듬: 3–6초마다 시각적 변화가 있어야 함 (새 카드 / 이동 / 숫자 스크롤); 6초 이상 정적이면 시청자가 이탈.
  • 카메라: 3–15% 느린 줌만 사용, 큰 확대/축소 금지.
  • 색보정: 전체 영화에 하나의 주색 + 하나의 강조색, 4개 이상의 색상 사용 금지.
  • 오디오: 마스터 -16 LUFS / 실제 피크 ≤ -1 dBTP; 내레이션이 간격보다 12 dB 이상 큼; AAC 전에 1.4 dB 여유 확보.

5. 사운드: 배경 음악 + 음향 효과 + 큐 포인트

이 섹션의 아이디어는 실제 완성본 (ai-history-film)의 회고에서 비롯되었습니다: 사운드가 좋지 않은 것은 "믹싱이 충분히 크지 않아서"가 아니라 "사운드가 화면을 인식하지 못하기 때문" 입니다. 해결 순서는 항상: 먼저 사운드가 화면에서 무슨 일이 일어나는지 알게 한 다음 (큐 포인트), 음색에 대해 논하고, 마지막으로 마스터링입니다.

5.1 사운드의 세 층

층담당자저장설명
내레이션filmkit.tts (edge-tts)work/tts/*.mp3유일한 지속 시간 권위자: 타임라인이 이를 통해 역산됨
배경 음악musicgen.py (매개변수화 편곡)work/music.wav디자인의 bpm/key/scale/mood/seed + 편곡 매개변수에 따라 현장 생성; 기성 음악 스타일 선택 불가
음향 효과sfxkit.py (물리 모델링)work/sfx.wavcues.json에 등록된 큐 포인트에 따라 개별 합성

세 버스는 --stage audio에서 합성됩니다: 음향 효과가 울리면 배경 음악이 자동으로 양보 (duck_db 기본 4 dB, 시네마/에픽 5.5 dB – 3 dB의 양보량은 단락 전환 시 배경 음악이 음향 효과와 거의 동일한 레벨이 되어 청감이 뭉개짐), 마지막으로 ffmpeg 2패스 loudnorm에 맡겨 -16 LUFS / 실제 피크 ≤ -1 dBTP로 만듦. 직접 numpy에서 최종 음량 표준화를 수행하지 마세요 – 당신은 loudnorm을 가지고 있으며, 이는 수동 정규화보다 훨씬 정확하고, 실제 피크는 게이트 통과 조건입니다.

영화 내 분리: 배경 음악은 단락 경계에서 원래 전환음이 있습니다 (편곡기가 작성한 swell / riser / sub_boom), sfx 트랙은 동일한 경계에서 whoosh/riser를 반복 재생하지 않습니다 – --stage sfx는 [경계 - 2.2 s, 경계] 내에 있는 전환 유형 cue에 대해 자동으로 -3 dB를 적용합니다 (sfx.duck_transitions=

…

[truncated for size; full content provided in the translation above]

관련 스킬