Skip to content

MinwooPark2026/Weft

Repository files navigation

Weft

한국어 · English


한국어

Weft는 긴 호흡의 설명형 영상을 위한 이중 트랙(dual-track) 워크플로우입니다. 입력은 사용자가 직접 쓴 산문 대본이고, 주제는 가리지 않습니다 — 역사, 과학, 문화 분석, 사고실험, 철학 비교 등 무엇이든 됩니다 (대본 작성 자체는 Weft 범위 밖).

다음 둘을 분리합니다:

  • 나레이션 비트(narration beats): 대본, 자막, TTS 타이밍
  • 비주얼 샷(visual shots): 생성 이미지, 텍스트 카드, 재사용, 몽타주, 모션

이렇게 하면 하나의 비주얼이 여러 나레이션 비트를 덮거나, 하나의 나레이션 비트가 여러 비주얼을 쓸 수 있습니다. 실질적 목표는 "문장마다 이미지 한 장" 식의 편집 노동을 줄이면서, ffmpeg로 완성 MP4를 기본 산출물로 뽑는 것입니다. NLE 편집이 필요할 때는 CapCut 드래프트나 FCPXML로 핸드오프합니다.

구성 요소

  • install.sh: 설치 스크립트 (venv 생성, weft 명령 설치, .env 시드)
  • uninstall.sh: 제거 스크립트 (weft 명령·venv 제거, .env는 보존)
  • weft/: 파이썬 CLI와 핵심 파이프라인
  • .claude/skills/script-to-conti/: 대본을 Weft 이중 트랙 CONTI.md로 바꾸는 스킬
  • .claude/skills/conti-qa/: 콘티 품질 검토(리듬·프롬프트·카드·사실/추측 lint) 스킬
  • .claude/skills/visual-qa/: 생성 이미지 후보·최종 MP4를 비전으로 검수하는 스킬
  • .claude/skills/animation-render/: remotion/hyperframe 샷을 clip.mp4로 렌더하는 스킬
  • .agents/skills/: Codex에서도 같은 스킬들을 쓰기 위한 미러
  • weft/picker/: 로컬 이미지 후보 선택기(picker)
  • STYLE_GUIDE.md: 이미지 스타일 커스터마이즈 가이드
  • WORKFLOW.html: 시각적 워크플로우 개요
  • example/CONTI.md, example/SCRIPT.md: 예시 입력

생성물(오디오·이미지·CapCut 드래프트)은 로컬에서 만들어지며 저장소에 커밋되지 않습니다.

설치 (최초 1회)

저장소 안에서 한 번만 실행하세요:

./install.sh

install.sh는 venv를 만들고 패키지를 editable로 설치(weft 명령 생성)한 뒤 ~/.local/bin에 심볼릭 링크하고 .env.example로부터 .env를 시드합니다. 그다음부터 weft어느 디렉터리에서나 동작합니다.

설치 후 저장소의 .env에 본인 키를 채우세요:

  • TTS용 TYPECAST_API_KEY, TYPECAST_VOICE
  • 이미지 생성용 OPENAI_API_KEY

editable 설치라서 weft는 어느 작업 디렉터리에서든 이 .env를 찾습니다. 현재 폴더에 둔 프로젝트별 .env도 인식됩니다.

가장 쉬운 방법 — Claude Code 한마디

설치(./install.sh)와 .env 키 입력만 끝났으면, 그다음부터는 명령어를 외울 필요가 없습니다.

cd ~/my-video        # 대본·문서(.md 등)가 들어 있는 폴더
claude               # 이 폴더에서 Claude Code 실행

그리고 한마디 하세요:

이 폴더 대본으로 weft 영상 만들어줘

Claude Code가 weft whereisskillscript-to-conti 스킬을 찾아 읽고 전체 파이프라인을 돕니다 — 대본을 콘티(CONTI.md)로 바꾸고, weft conti로 검증하고, conti-qa로 품질을 점검한 뒤, weft tts(나레이션) → weft images(이미지) → weft ffmpeg(렌더)로 EXPORTS/weft_render.mp4 를 만들어 줍니다. 중간중간 콘티·이미지를 함께 확인하며 진행합니다. (스킬은 전역 설치하지 않습니다 — weft --help가 AI에게 weft whereisskill을 안내하고, 스킬을 그때그때 읽어 옵니다.)

실제 산출물(나레이션·이미지)은 .envOPENAI_API_KEY(이미지)·TYPECAST_API_KEY(TTS)가 있어야 나옵니다. 키가 없으면 stub(플레이스홀더)로만 흐름을 확인합니다. 반복 캐릭터·스타일·BGM을 쓰려면 폴더에 CHARACTER.png/STYLE.txt/BGM.json을 두라고 일러주면 됩니다.

명령을 직접 한 단계씩 돌리고 싶으면 아래 빠른 시작을 참고하세요.

스킬이 모든 Claude Code 세션에서 자동으로 뜨길 원하면(트레이드오프: weft와 무관한 작업에서도 로드됨) weft 저장소에서 한 번 전역 링크하세요. uninstall.sh가 정리합니다.

mkdir -p ~/.claude/skills && ln -sfn "$PWD"/.claude/skills/*/ ~/.claude/skills/

제거

설치한 것을 깔끔히 되돌리려면 저장소 안에서:

./uninstall.sh

weft 명령(심볼릭 링크)·venv·빌드 산물을 지웁니다. 키가 든 .env보존됩니다(완전히 지우려면 안내대로 직접 삭제).

대본을 CONTI.md로 바꾸기

직접 쓴 산문 대본(SCRIPT.md 등)이 있으면 먼저 AI 어시스턴트에게 script-to-conti 스킬을 사용해서 Weft CONTI.md로 변환하라고 요청하세요. 이 스킬은 한 문장마다 이미지를 하나씩 만들지 않고, 새 그림· 홀드· 몽타주· 재사용 같은 이중 트랙 지시를 넣어 이미지 수를 줄이는 콘티를 만듭니다.

  • Claude용: .claude/skills/script-to-conti/
  • Codex용: .agents/skills/script-to-conti/

터미널에서 스킬 파일 위치를 확인하려면:

weft whereisskill

예시 요청:

이 대본을 script-to-conti 스킬로 Weft CONTI.md로 바꿔줘.
스킬 위치는 `weft whereisskill`로 확인해서 SKILL.md를 읽어줘.
검증은 weft conti까지 돌려서 validation_errors=0이 되게 해줘.

빠른 시작

프로젝트는 CONTI.md가 들어 있는 폴더입니다. 그 폴더로 cd한 뒤, 현재 디렉터리를 대상으로 동작하는 하위 명령을 실행하세요(경로 인자 불필요):

cd my-video       # CONTI.md가 있는 폴더
weft conti        # ./CONTI.md -> ./generated_project/ (파싱 + 검증 + 컴파일)
weft tts          # ./generated_project (Typecast 나레이션; API 비용)
weft images       # ./generated_project (OpenAI/Gemini 이미지; API 비용)
weft animate      # Remotion/HyperFrame shot spec 생성·렌더된 clip 확인
weft ffmpeg       # ./generated_project -> EXPORTS/weft_render.mp4 (먼저 볼 MP4)
weft pick         # 선택: 이미지 후보를 사람이 고름
weft capcut       # 선택: 마음에 안 들 때 CapCut 드래프트 생성
weft fcpxml       # 선택: FCPXML 핸드오프

한 번에:

weft all          # 사람용 빠른 자동 실행: conti -> tts -> images -> animate check -> ffmpeg
weft all --capcut # MP4에 더해 CapCut 드래프트도 생성
weft all --fcpxml # MP4에 더해 FCPXML도 생성

프로젝트 명령

CONTI.md가 있는 폴더 안에서 실행하세요:

weft conti            # ./CONTI.md -> JSON 트랙·픽·SRT·렌더 플랜
weft parse            # CONTI.md 파싱 결과 JSON을 출력만 (파일 생성 없음)
weft validate         # CONTI.md 파싱+검증 결과만 출력 (파일 생성 없음)
weft tts              # Typecast 나레이션 WAV
weft images           # OpenAI 이미지 후보
weft pick             # 로컬 브라우저 picker
weft animate          # Remotion/HyperFrame animation shot 준비·검사
weft ffmpeg           # ffmpeg MP4 렌더러 (자막 기본 burn-in)
weft capcut           # 선택: CapCut 드래프트 빌더
weft fcpxml           # 선택: Final Cut/Premiere/Resolve용 FCPXML 핸드오프
weft all              # 사람용 빠른 자동 MP4 렌더
weft all --capcut     # MP4 + CapCut
weft all --fcpxml     # MP4 + FCPXML
weft settings         # WEFT_SETTINGS.txt 생성/확인
weft whereisskill     # AI에게 읽힐 script-to-conti 스킬 경로 출력

별칭: weft dryrunweft conti와 같고, weft renderweft ffmpeg와 같습니다.

weft upload(선택): 완성 MP4를 비공개로 YouTube에 업로드 — ./install.sh youtube-upload로 의존성을 설치한 뒤 자세한 설정은 manual4youtubeupload.md를 보세요.

conti의 입력은 기본값이 ./CONTI.md, 나머지 프로젝트 명령의 프로젝트 디렉터리는 기본값이 ./generated_project입니다. 파워 유저는 경로를 명시적으로 넘길 수도 있습니다.

weft capcutweft all --capcut--no-register로 CapCut 목록 등록을 건너뛸 수 있습니다(드래프트 파일만 생성).

WEFT_SETTINGS.txt

weft conti 또는 weft all을 실행하면 프로젝트 폴더(CONTI.md 옆)에 WEFT_SETTINGS.txt가 없을 때 기본 파일을 만듭니다. VASP INCAR처럼 이 파일 하나를 다른 프로젝트에 복사하면 같은 렌더/provider 옵션을 재사용할 수 있습니다. CLI로 직접 준 옵션은 이 파일보다 우선합니다. 기본 빠른 산출물은 ffmpeg MP4이고, CapCut/FCPXML은 필요할 때 추가 실행합니다.

바로 쓸 수 있는 예시는 setting_examples/에 있습니다:

  • youtube_4k_high.WEFT_SETTINGS.txt
  • youtube_high.WEFT_SETTINGS.txt
  • standard.WEFT_SETTINGS.txt
  • low.WEFT_SETTINGS.txt

테스트 영상 용량을 줄이는 예:

EXPORT_FFMPEG=true
FFMPEG_ENCODER=libx264
FFMPEG_CRF=32
FFMPEG_PRESET=veryfast
FFMPEG_BITRATE=2M

그 뒤:

weft settings

로 현재 프로젝트 설정 파일을 만들거나 확인할 수도 있습니다.

weft all

weft ffmpegEXPORTS/render_plan.json, 선택 이미지, TTS WAV, 컴파일된 자막 이벤트를 바로 EXPORTS/weft_render.mp4로 렌더합니다. 자막은 기본으로 영상에 입혀지며(--no-subtitles로 비활성화), --encoder auto가 macOS의 h264_videotoolbox를 우선 시도하고 실패하면 libx264로 fallback합니다. 소프트웨어 인코딩 확인용은 --preset ultrafast, 더 작은/고화질 출력은 --crf 값 조정으로 맞추세요.

배경음악(BGM) — 캡컷 불필요, ffmpeg가 자동 덕킹

기본은 BGM 없음입니다. 유튜브 오디오 라이브러리처럼 라이선스가 확보된 음원 파일(mp3/wav/m4a, 서사 없는 배경음 권장)을 준비해 WEFT_SETTINGS.txt에 지정하면, weft ffmpeg가 BGM을 깔고 나레이션이 나오는 동안 자동으로 BGM을 낮춥니다(사이드체인 덕킹) — ⏸ 정적 구간과 문장 사이에서는 다시 올라옵니다. CapCut을 거칠 필요가 없습니다.

BGM_FILE=music/bgm.mp3   # CONTI.md 기준 상대 경로 또는 절대 경로 (전곡 1개, 짧으면 자동 반복)
#BGM_GAIN_DB=-16         # BGM 기본 음량(dB)
#BGM_DUCK_DB=-12         # 나레이션 중 추가로 낮추는 깊이(dB)
#BGM_FADE_SECONDS=2.0    # 곡(구간) 시작/끝 페이드(초)

막(구간)별로 다른 곡을 쓰려면 CONTI.md 옆에 BGM.json을 만듭니다. BGM_FILE보다 우선하고, CARDS.json처럼 weft conti/weft all이 generated_project로 복사합니다:

[
  {"file": "music/opening.mp3", "from": "0:00", "to": "1:30", "gain_db": -16},
  {"file": "music/ending.mp3",  "from": "1:30", "to": ""}
]

to를 비우면 영상 끝까지이고, 마지막 곡은 영상 끝에서 페이드아웃됩니다. 설정해 두고 한 번만 끄려면 weft ffmpeg --no-bgm(또는 weft all --no-bgm). weft capcut 드래프트에는 BGM이 별도 오디오 트랙으로 단순 배치만 됩니다(덕킹·정밀 페이드는 CapCut에서 직접).

weft animatesource_kind=remotion 또는 source_kind=hyperframe shot마다 SHOTS/<shot>/animation/SPEC.md를 만들고, AI가 렌더해야 할 출력 경로 SHOTS/<shot>/rendered/clip.mp4를 검사합니다. 렌더된 MP4는 일반 clip처럼 weft ffmpeg, weft capcut, weft fcpxml에 들어갑니다.

weft fcpxml은 같은 render_plan에서 편집 가능한 FCPXML을 만듭니다. 이미지/클립/애니메이션 클립은 비디오 레인, TTS는 오디오 레인, 자막은 타이틀 레인으로 들어갑니다.

provider는 .env에서 바꿀 수 있습니다:

IMAGE_PROVIDER=openai   # openai | gemini | comfyui | stub
TTS_PROVIDER=typecast   # typecast | stub

OPENAI_IMAGE_MODEL=gpt-image-2            # 기본(네이티브 16:9). 더 싸게는 gpt-image-1-mini(2026-12-01 종료)
GEMINI_API_KEY=                           # IMAGE_PROVIDER=gemini 일 때 (Google AI Studio 키)
GEMINI_IMAGE_MODEL=gemini-3.1-flash-image # 기본. 저가형 gemini-2.5-flash-image / 고품질 gemini-3-pro-image
IMAGE_ASPECT=16:9                         # 16:9 | 9:16 | 1:1 | 3:2

stub은 API 키 없이 로컬 테스트용 PNG/WAV를 만듭니다.

이미지 비율(IMAGE_ASPECT, 기본 16:9)은 provider가 어떤 크기를 반환하든 저장 시점에 센터 크롭으로 정확히 맞춰집니다. Gemini와 gpt-image-2는 16:9를 네이티브로 생성하고, gpt-image-1 계열은 1536x1024(3:2)로 요청한 뒤 1536x864로 크롭됩니다.

⚠ 기본 모델은 gpt-image-2입니다. 구모델을 선택했다면 종료 일정에 주의하세요: gpt-image-1은 2026-10-23, gpt-image-1-mini/gpt-image-1.5는 2026-12-01 서비스 종료. 모델/provider를 바꾸면 캐시 키가 달라져 weft images 재실행 시 이미지가 재생성(재과금)됩니다.

반복 캐릭터: CHARACTER.pngCONTI.md 옆(또는 generated_project 안)에 두고 shot 프롬프트에 @char를 쓰면, OpenAI(images.edit)/Gemini(이미지 입력)에 캐릭터 시트가 레퍼런스로 전달되고 마커는 "the recurring channel character exactly as shown in the reference sheet"로 치환됩니다. 다른 경로는 CHARACTER_SHEET=로 지정하세요. 시트가 없거나 미지원 provider(comfyui/stub)면 마커만 제거되고 경고가 한 번 출력됩니다. weft pick 화면의 재생성 패널에서도 provider/모델을 골라 shot별로 다시 생성할 수 있습니다.

comfyui는 로컬 ComfyUI 서버(COMFYUI_URL, 기본 http://127.0.0.1:8188)로 이미지를 생성합니다. COMFYUI_WORKFLOW에는 ComfyUI에서 **Save (API Format)**으로 내보낸 워크플로 JSON 경로를 지정하고(weft가 실행되는 이 PC 기준 경로), JSON 안의 긍정 프롬프트(CLIPTextEncode 등) text 값을 __WEFT_PROMPT__로 바꿔 두면 shot 프롬프트로 치환됩니다. (선택: __WEFT_SEED__를 넣으면 후보마다 다른 시드로 치환 — 없으면 "seed" 키를 자동으로 바꿔 후보가 서로 다른 변주가 됩니다.) 워크플로는 16:9에 가까운 해상도(예: 1344×768, 1920×1088)로 출력하게 두면 저장 시 크롭 손실이 없습니다. @char 캐릭터 시트는 comfyui에서 지원하지 않습니다(마커만 제거됨 — 워크플로에 IP-Adapter 등을 직접 구성).

원격 GPU의 ComfyUI (SSH) — 다른 머신에서 도는 ComfyUI를 쓰려면 SSH 포트 포워딩으로 로컬 포트에 연결하는 게 가장 안전합니다(ComfyUI는 인증이 없습니다). ComfyUI가 원격에서 기본값(127.0.0.1:8188)으로 떠 있으면, weft를 돌리는 PC에서 터널만 열어 두고 COMFYUI_URL은 기본값 그대로 둡니다:

ssh -N -L 8188:localhost:8188 homegpu   # 이 PC의 8188 → 원격 8188 (켜둔 채 사용)
IMAGE_PROVIDER=comfyui
COMFYUI_URL=http://127.0.0.1:8188        # 터널 끝점 — SSH 별칭(homegpu)을 URL에 직접 쓰지 말 것
COMFYUI_WORKFLOW=/path/to/workflow_api.json

(SSH 별칭 homegpussh 명령만 아는 이름이라 HTTP URL에는 못 씁니다. 워크플로 JSON은 원격 ComfyUI를 브라우저로 열어 만든 뒤 이 PC에 저장하세요.)

AI 에이전트 주의: API 키가 __로 시작하거나 placeholder처럼 보여도 문자열 모양만으로 무효라고 판단하지 마세요. 키 유효성은 실제 실행 결과로 판단합니다. TYPECAST_API_KEY/TYPECAST_VOICE가 비어 있거나 TTS_PROVIDER=stub일 때만 stub/placeholder 운용으로 보고, 그 외에는 weft tts 실행 결과(Typecast HTTP 응답, 생성 WAV, sidecar의 provider=typecast)로 확인하세요. secret 값은 로그나 답변에 출력하지 마세요.

다른 프로젝트 폴더 사용 — 그냥 그 폴더로 cd하세요:

cd that-folder
weft all

이미지 후보 개수 지정:

weft images --n 3

Script-To-Conti 스킬

AI 어시스턴트로 대본을 Weft CONTI.md로 변환할 때는 script-to-conti 스킬을 사용하세요. Claude에서는 .claude/skills/script-to-conti/, Codex에서는 .agents/skills/script-to-conti/가 같은 내용을 제공합니다. AI agent의 기본 작업 방식은 weft all이 아니라 단계별 CLI 실행입니다. 정적인 은유/삽화는 image, 숫자 변화·도표·수식 전개는 remotion 또는 hyperframe, 기존 영상은 clip으로 고르게 하세요.

AI agent가 CLI로 weft를 제어할 때의 권장 순서: weft conti가 0건으로 통과하면 먼저 conti-qa 스킬로 품질 검토를 한 차례 돌리고 나서 weft tts/weft images(API 과금)로 진행하세요 — 과금 전에 콘티를 고치는 것이 가장 쌉니다. 나레이션 칸의 TTS 발음 표기는 script-to-conti가 변환 시점에 적용합니다.

콘티 이후 단계에도 전용 스킬이 있습니다 (weft whereisskill로 경로 확인):

  • conti-qa: weft conti 통과 후 콘티 품질 검토 — 리듬·이미지 프롬프트·텍스트카드·사실/추측 lint
  • visual-qa: weft images/weft ffmpeg 후 후보 PNG와 최종 MP4를 비전으로 검수, 픽 수정·재생성 제안
  • animation-render: weft animate가 만든 SPEC.md를 받아 remotion/hyperframe 샷을 clip.mp4로 렌더

이 스킬의 출력:

  • 나레이션 비트 행
  • 비주얼 샷 지시자: , , , , , ,
  • 샷 프롬프트
  • 모션 메모
  • 자막

그다음 프로젝트 폴더 안에서 실행:

weft conti

이미지 스타일

모든 생성 이미지는 다음을 받습니다:

샷별 프롬프트 + 공유 Style 접미사

프로젝트 폴더(CONTI.md 옆)에 STYLE.txt 파일을 두면 공유 스타일을 덮어쓸 수 있습니다. 파일이 없으면 처음 이미지를 생성할 때 기본 3b1b 스타일 문장이 STYLE.txt로 자동 생성됩니다:

  • STYLE.txt (프로젝트 폴더, CONTI.md 옆)
  • 또는 generated_project/STYLE.txt

기본값은 weft/assets.pyDEFAULT_STYLE과 같은 문장입니다. 그다음 프로젝트 폴더 안에서 이미지를 다시 생성:

weft images

템플릿과 예시는 STYLE_GUIDE.md를 참고하세요.

참고

  • conti를 다시 실행하면 generated_project가 새로 만들어지고 픽이 초기화됩니다.
  • picker로 고른 뒤에는, 프로젝트를 의도적으로 다시 만들 게 아니라면 capcut만 바로 실행하세요.
  • weft capcut은 **CapCut이 바로 여는 드래프트(프로젝트)**를 만들어 CapCut 목록에 등록합니다. 등록은 CapCut의 프로젝트 목록 파일(root_meta_info.json)을 고치는데, CapCut이 켜져 있으면 종료할 때 그 파일을 자기 메모리로 덮어써 새 드래프트가 사라집니다. 그래서 빌드는 CapCut을 종료한 상태에서 하세요. (켜진 채 실행하면 등록을 건너뛰고 파일만 만들어 둡니다 — CapCut을 종료한 뒤 weft capcut을 다시 실행하면 목록에 등록됩니다.)

라이선스

MIT © 2026 Minwoo Park. LICENSE 참고.


English

Weft is a dual-track workflow for long-form explainer videos. The input is a prose script you write yourself, on any topic — history, science, cultural analysis, thought experiments, philosophical comparisons, and so on (writing the script is outside Weft's scope).

It separates:

  • narration beats: script, subtitles, TTS timing
  • visual shots: generated images, text cards, reuse, montage, motion

This lets one visual cover multiple narration beats, or one narration beat use several visuals. The practical goal is to reduce one-image-per-sentence editing work while producing a finished MP4 via ffmpeg as the default output; CapCut drafts and FCPXML are optional handoffs when NLE editing is needed.

Included Tools

  • install.sh: installer (creates venv, installs the weft command, seeds .env)
  • uninstall.sh: uninstaller (removes the weft command and venv; keeps .env)
  • weft/: Python CLI and core pipeline
  • .claude/skills/script-to-conti/: skill for turning a script into a Weft dual-track CONTI.md
  • .claude/skills/conti-qa/: conti quality-review skill (rhythm, prompt, card, fact/speculation lint)
  • .claude/skills/visual-qa/: vision review skill for generated image candidates and the final MP4
  • .claude/skills/animation-render/: skill that renders remotion/hyperframe shots into clip.mp4
  • .agents/skills/: Codex mirrors of the same skills
  • weft/picker/: local image candidate picker
  • STYLE_GUIDE.md: image style customization guide
  • WORKFLOW.html: visual workflow overview
  • example/CONTI.md, example/SCRIPT.md: sample input

Generated media (audio, images, CapCut drafts) is produced locally and is not committed.

Setup (once)

Run this once inside the repo:

./install.sh

install.sh creates a venv, editable-installs the package (creating the weft command), symlinks it into ~/.local/bin, and seeds .env from .env.example. After that, weft works from any directory.

Then fill the repo's .env with your own keys:

  • TYPECAST_API_KEY, TYPECAST_VOICE for TTS
  • OPENAI_API_KEY for image generation

Because the install is editable, weft finds this .env from any working directory. A per-project .env in the current folder also works.

The easy way — one line in Claude Code

Once ./install.sh and your .env keys are done, you don't have to memorize commands.

cd ~/my-video        # a folder containing your script/document (.md, etc.)
claude               # start Claude Code in that folder

Then just say:

make a weft video from the script in this folder

Claude Code finds the script-to-conti skill via weft whereisskill, reads it, and drives the whole pipeline: it turns the script into a CONTI.md, validates it with weft conti, reviews it with conti-qa, then runs weft tts (narration) → weft imagesweft ffmpeg to produce EXPORTS/weft_render.mp4, checking the conti and images with you along the way. (Skills are not installed globally — weft --help points the AI to weft whereisskill, which loads them on demand.)

Real output (narration, images) needs OPENAI_API_KEY (images) and TYPECAST_API_KEY (TTS) in .env; without them it runs in stub mode for a dry walkthrough. For a recurring character, custom style, or BGM, just tell it to use CHARACTER.png / STYLE.txt / BGM.json in the folder.

Prefer driving each step yourself? See Quick Start below.

Want the skills to load automatically in every Claude Code session (trade-off: they load even for non-weft work)? Link them once from the weft repo; uninstall.sh cleans them up.

mkdir -p ~/.claude/skills && ln -sfn "$PWD"/.claude/skills/*/ ~/.claude/skills/

Uninstall

To cleanly undo the install, run inside the repo:

./uninstall.sh

It removes the weft command (symlink), the venv, and build artifacts. Your .env (with keys) is kept.

Turn A Script Into CONTI.md

If you already have your own prose script (SCRIPT.md, etc.), first ask the AI assistant to use the script-to-conti skill and convert it into a Weft CONTI.md. The skill writes dual-track visual directives such as new image, hold, montage, and reuse, so the result does not default to one image per sentence.

  • Claude: .claude/skills/script-to-conti/
  • Codex: .agents/skills/script-to-conti/

To print the skill file paths from a terminal:

weft whereisskill

Example prompt:

Use the script-to-conti skill to convert this script into a Weft CONTI.md.
Find the SKILL.md path with `weft whereisskill` and read it first.
Run weft conti and iterate until validation_errors=0.

Quick Start

A project is a folder containing CONTI.md. cd into it and run subcommands that operate on the current directory (no path args needed):

cd my-video       # folder containing CONTI.md
weft conti        # ./CONTI.md -> ./generated_project/ (parse + validate + compile)
weft tts          # ./generated_project (Typecast narration; API cost)
weft images       # ./generated_project (OpenAI/Gemini images; API cost)
weft animate      # prepare/check Remotion/HyperFrame animation shot clips
weft ffmpeg       # ./generated_project -> EXPORTS/weft_render.mp4 (first review MP4)
weft pick         # optional: manual image candidate picking
weft capcut       # optional: build CapCut draft if the MP4 needs editing
weft fcpxml       # optional: FCPXML handoff

One shot:

weft all          # human quick run: conti -> tts -> images -> animate check -> ffmpeg
weft all --capcut # also build a CapCut draft
weft all --fcpxml # also export FCPXML

Project Commands

Run these inside the folder that contains CONTI.md:

weft conti            # ./CONTI.md -> JSON tracks, picks, SRT, render plan
weft parse            # print parsed CONTI.md as JSON only (writes nothing)
weft validate         # print parse+validation results only (writes nothing)
weft tts              # Typecast narration WAVs
weft images           # OpenAI image candidates
weft pick             # local browser picker
weft animate          # prepare/check Remotion/HyperFrame animation shot clips
weft ffmpeg           # ffmpeg MP4 renderer (burns in subtitles by default)
weft capcut           # optional CapCut draft builder
weft fcpxml           # optional FCPXML handoff for Final Cut/Premiere/Resolve
weft all              # human quick MP4 render
weft all --capcut     # MP4 + CapCut
weft all --fcpxml     # MP4 + FCPXML
weft settings         # create/show WEFT_SETTINGS.txt
weft whereisskill     # print script-to-conti skill paths for AI assistants

Aliases: weft dryrun equals weft conti, and weft render equals weft ffmpeg.

weft upload (optional): upload the finished MP4 to YouTube as private — install deps with ./install.sh youtube-upload, then see manual4youtubeupload.md for setup.

conti's CONTI source defaults to ./CONTI.md; the other project commands default their project dir to ./generated_project. Power users can still pass paths explicitly.

weft capcut and weft all --capcut accept --no-register to skip registering the draft in CapCut's list (the draft files are still written).

WEFT_SETTINGS.txt

weft conti or weft all creates a default WEFT_SETTINGS.txt next to CONTI.md when it is missing. Like VASP INCAR, copy this one file into another project to reuse the same render/provider options. Explicit CLI flags override this file for one-off runs. The default quick output is an ffmpeg MP4; CapCut/FCPXML are extra handoff commands when needed.

Ready-to-copy examples live in setting_examples/:

  • youtube_4k_high.WEFT_SETTINGS.txt
  • youtube_high.WEFT_SETTINGS.txt
  • standard.WEFT_SETTINGS.txt
  • low.WEFT_SETTINGS.txt

Smaller test-render example:

EXPORT_FFMPEG=true
FFMPEG_ENCODER=libx264
FFMPEG_CRF=32
FFMPEG_PRESET=veryfast
FFMPEG_BITRATE=2M

Then:

weft settings

also creates or shows the current project settings file.

weft all

weft ffmpeg renders EXPORTS/render_plan.json, picked images, TTS WAVs, and compiled subtitle events directly into EXPORTS/weft_render.mp4. Subtitles are burned in by default (--no-subtitles disables them), and --encoder auto tries macOS h264_videotoolbox first before falling back to libx264. Use --preset ultrafast for faster software-encoding checks, or adjust --crf for size/quality.

Background music (BGM) — no CapCut needed, ffmpeg auto-ducks

The default is no BGM. Prepare a licensed music file yourself (mp3/wav/m4a, e.g. from the YouTube Audio Library; non-narrative background music works best) and point WEFT_SETTINGS.txt at it — weft ffmpeg lays the BGM under the video and automatically lowers it while narration plays (sidechain ducking), letting it rise back during ⏸ pauses and gaps. No CapCut round-trip required.

BGM_FILE=music/bgm.mp3   # relative to CONTI.md or absolute (one track; loops if shorter)
#BGM_GAIN_DB=-16         # base BGM level (dB)
#BGM_DUCK_DB=-12         # extra attenuation while narration plays (dB)
#BGM_FADE_SECONDS=2.0    # fade in/out per track segment (seconds)

For different songs per act, create BGM.json next to CONTI.md. It takes priority over BGM_FILE and is copied into generated_project by weft conti/weft all (like CARDS.json):

[
  {"file": "music/opening.mp3", "from": "0:00", "to": "1:30", "gain_db": -16},
  {"file": "music/ending.mp3",  "from": "1:30", "to": ""}
]

An empty to means "until the end of the video", and the last track fades out at the video end. To skip BGM for one run, use weft ffmpeg --no-bgm (or weft all --no-bgm). The weft capcut draft only places the BGM segments on a separate audio track (do ducking/precise fades inside CapCut yourself).

weft animate creates SHOTS/<shot>/animation/SPEC.md for source_kind=remotion and source_kind=hyperframe shots, then checks for the expected rendered output SHOTS/<shot>/rendered/clip.mp4. Once rendered, these MP4s enter weft ffmpeg, weft capcut, and weft fcpxml like normal clips.

weft fcpxml exports the same render_plan as an editable FCPXML timeline: images/clips/animation clips on a video lane, TTS on an audio lane, and subtitles on a title lane.

Providers are selectable in .env:

IMAGE_PROVIDER=openai   # openai | gemini | comfyui | stub
TTS_PROVIDER=typecast   # typecast | stub

OPENAI_IMAGE_MODEL=gpt-image-2            # default (native 16:9). gpt-image-1-mini is cheaper (retires 2026-12-01)
GEMINI_API_KEY=                           # for IMAGE_PROVIDER=gemini (Google AI Studio key)
GEMINI_IMAGE_MODEL=gemini-3.1-flash-image # default. Budget: gemini-2.5-flash-image / hero: gemini-3-pro-image
IMAGE_ASPECT=16:9                         # 16:9 | 9:16 | 1:1 | 3:2

stub creates local PNG/WAV assets without API keys for tests and offline dry runs.

IMAGE_ASPECT (default 16:9) is enforced exactly at save time with a center crop, whatever size a provider returns. Gemini and gpt-image-2 generate 16:9 natively; the gpt-image-1 family is requested at 1536x1024 (3:2) and cropped to 1536x864.

⚠ The default model is gpt-image-2. If you pick an older model, mind the shutdowns: gpt-image-1 retires 2026-10-23, gpt-image-1-mini/gpt-image-1.5 on 2026-12-01. Changing the model/provider changes cache keys, so the next weft images run regenerates (re-bills) existing shots.

Recurring character: put a CHARACTER.png next to CONTI.md (or inside generated_project) and write @char in a shot prompt — the sheet is sent as a reference image (OpenAI images.edit / Gemini image input) and the marker becomes "the recurring channel character exactly as shown in the reference sheet". Use CHARACTER_SHEET= for a custom path. Without a sheet or on providers without reference support (comfyui/stub), the marker is stripped with a single warning. The weft pick UI's regeneration panel also offers per-shot provider/model dropdowns.

comfyui generates images on a local ComfyUI server (COMFYUI_URL, default http://127.0.0.1:8188). Point COMFYUI_WORKFLOW at a workflow JSON exported from ComfyUI with Save (API Format) (a path on the machine running weft), and put __WEFT_PROMPT__ where the positive prompt text goes — Weft substitutes the shot prompt there (JSON-escape safe). (Optional: __WEFT_SEED__ gets a fresh seed per candidate; without it, "seed" keys are randomized so the N candidates are real variations.) Have the workflow output a near-16:9 resolution (e.g. 1344×768, 1920×1088) to avoid crop loss. @char character sheets are not supported on comfyui (the marker is stripped — wire IP-Adapter into the workflow yourself).

Remote ComfyUI over SSH — for ComfyUI on another machine, an SSH port-forward to a local port is the safe option (ComfyUI has no auth). If it runs on the remote default (127.0.0.1:8188), open a tunnel from the machine running weft and keep COMFYUI_URL at the default:

ssh -N -L 8188:localhost:8188 homegpu   # this PC's 8188 → remote 8188 (leave running)
IMAGE_PROVIDER=comfyui
COMFYUI_URL=http://127.0.0.1:8188        # the tunnel endpoint — do not put the SSH alias in the URL
COMFYUI_WORKFLOW=/path/to/workflow_api.json

(The SSH alias homegpu is known only to ssh, not to HTTP. Build the workflow via the remote ComfyUI in a browser, then save the JSON onto this PC.)

AI agent note: do not decide whether an API key is real from its string shape. A Typecast key may start with __ and still be valid. Treat TTS as stub/placeholder only when TYPECAST_API_KEY or TYPECAST_VOICE is empty, or when TTS_PROVIDER=stub is set. Otherwise verify by running weft tts and checking the Typecast HTTP result, generated WAVs, or sidecar metadata with provider=typecast. Never print secret values in logs or responses.

Use another project folder — just cd into it:

cd that-folder
weft all

Set image candidate count:

weft images --n 3

Script-To-Conti Skill

Use the script-to-conti skill when you want an AI assistant to convert a script into a Weft CONTI.md. Claude reads .claude/skills/script-to-conti/; Codex reads .agents/skills/script-to-conti/. AI agents should normally run the CLI step by step instead of using weft all: choose image for static illustration/metaphor shots, remotion or hyperframe for charts/equations/process animation, and clip for existing video.

Recommended order when an AI agent drives weft via the CLI: once weft conti passes with zero violations, run the conti-qa skill once before weft tts/weft images (which bill API usage) — fixing the conti before billing is the cheapest point. TTS-friendly pronunciation in the narration column is applied by script-to-conti at conversion time.

Dedicated skills also cover the stages after the conti (weft whereisskill prints the paths):

  • conti-qa: quality review after weft conti passes — rhythm, image prompts, text cards, fact/speculation lint
  • visual-qa: vision review of candidate PNGs and the final MP4 after weft images/weft ffmpeg, with pick fixes and regeneration suggestions
  • animation-render: takes the SPEC.md from weft animate and renders remotion/hyperframe shots into clip.mp4

The skill outputs:

  • narration beat rows
  • visual shot directives: , , , , , ,
  • shot prompts
  • motion notes
  • subtitles

Then, from inside the project folder, run:

weft conti

Image Style

Every generated image receives:

shot-specific prompt + shared Style suffix

Override the shared style by placing a STYLE.txt file in the project folder (next to CONTI.md). If the file is missing, the first image-generation run materializes the default 3b1b-style sentence into STYLE.txt:

  • STYLE.txt (in the project folder, next to CONTI.md)
  • or generated_project/STYLE.txt

The generated default matches DEFAULT_STYLE in weft/assets.py. Then, from inside the project folder, regenerate images:

weft images

See STYLE_GUIDE.md for templates and examples.

Notes

  • Re-running conti rebuilds generated_project and resets picks.
  • After using the picker, run capcut directly unless you intentionally want to regenerate the project.
  • weft capcut builds a CapCut-openable draft (project) and registers it in CapCut's project list. Registration edits CapCut's project-list file (root_meta_info.json), and if CapCut is running it overwrites that file from memory on quit, so a freshly registered draft disappears. Build with CapCut closed. (If it's running, registration is skipped and only the files are written — close CapCut and run weft capcut again to register it in the project list.)

License

MIT © 2026 Minwoo Park. See LICENSE.

About

Dual-track workflow that turns a script into an editable CapCut draft — narration beats + visual shots

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors