비디오 클립 생성 (정적 이미지 → 클립 → 립싱크)
개요
완성된 정적 이미지가 이미지-투-비디오 모델을 통해 4-9초 클립이 됩니다. 그런 다음, 캐릭터가 화면에서 말을 하면 립싱크 모델이 입 모양을 사용자의 대화 오디오에 맞춰 다시 조정합니다. 결과물을 직접 볼 수는 없습니다. 추출된 프레임(scripts/take_sheet.py 및 viewing-videos 스킬 사용)으로 모든 클립을 평가하고, 움직임에 대한 최종 결정은 사람이 내리도록 합니다.
요구 사항: Python 3 (stdlib만) 및 ffmpeg/ffprobe. 키는 환경 변수에 저장: GEMINI_API_KEY (Google AI Studio 키, 이미지 범위 키도 작동하며 GEMINI_IMAGE_API_KEY를 대체로 읽음)는 Omni용, FAL_KEY (fal에 표시된 정확한 전체 키)는 립싱크용. 절대 출력하지 마십시오.
모델 선택
| 모델 | 접근 방법 | 용도 | 참고 사항 |
|---|---|---|---|
Gemini Omni Flash 1.1 (gemini-omni-1.1-flash) | Google Interactions API, AI Studio 키 | 기본값, 일러스트, 애니메이션 및 만화 스틸컷용 (한 번의 사실적인 스틸컷 테스트에서도 작동) | 아트 스타일과 얼굴 유지, 모든 입력 비율 지원(2:3 스틸컷이 깔끔한 9:16으로 반환되므로 사전 아웃페인트나 크롭 불필요); 720p 9:16 또는 16:9; 동기식, 클립당 약 25-35초; 자체 오디오 추가(음소거 필요) |
| Veo 3.1 / Veo 3.1 Fast | Gemini API, 시작 프레임으로 이미지 사용 | 사실적인 스틸컷 | 당사 실행에서 사용되지 않음; 현재 문서 먼저 확인 |
| Seedance 2.5 | ElevenLabs Flows 또는 ByteDance 직접 | 대체 스타일 | 시도 시 계정 승인 필요 |
| ElevenLabs Flows 내 모든 모델 | ElevenLabs API | 모든 것에 하나의 키 | API 접근은 Pro 플랜(402 paid_plan_required)과 키의 image_video_generation 권한(없으면 401) 필요; Omni는 앱 전용 |
립싱크의 경우, fal의 Sync 3 (fal-ai/sync-lipsync/v3)가 기본값입니다. 비디오-투-비디오 방식으로 작동하므로 클립의 움직임은 유지하고 입만 다시 조정합니다. 비교 및 가격: references/providers.md.
스틸컷 애니메이션
python3 <this-skill-dir>/scripts/omni_generate.py jobs.json clips/ [--takes 3] [--only S05]
{"base": "이 스틸컷을 그대로 살려 움직이게 하세요: 동일한 아트 스타일, 팔레트, 조명 및 레이아웃, 그리고 모든 캐릭터가 가지고 있는 얼굴, 머리카락, 체격 및 옷을 유지합니다. 자연스럽고 부드러운 움직임. 화면에 쓰여진 것 없음, 말풍선 없음, 새로운 인물 없음, 하나의 연속된 쇼트.",
"jobs": {"S05": {"image": "panels/p05.png",
"prompt": "[0-3초] 빨간 재킷을 입은 여성이 전화기를 들고 흥분하여 말합니다. [3-6초] 초록 후드를 입은 친구가 소파에 기대어 웃습니다. 느린 카메라 푸시인."}}}
프롬프트 작성 방법 (references/gemini-omni.md에 전체 가이드와 예제 포함):
- 초별 타임라인, 각 괄호 안에 하나의 비트. 클립은 타임라인 길이만큼 실행됩니다.
- 사람들을 입은 옷으로 식별, 절대 이름으로 부르지 마십시오: 모델은 "다나"가 누군지 모릅니다.
- 립싱크할 캐릭터에 대해 **"말한다"**라고 지정하여 입이 이미 움직이도록 합니다.
- 변경되지 말아야 할 사항을 고정: "카메라는 고정", "왼쪽 벽에 창문 하나", "정확히 한 명의 후드 인물", "그녀의 손은 테이블 위에 평평하게". 각 고정 사항은
references/failure-modes.md에 실제 실패 사례에서 비롯되었습니다.
모든 테이크 사용 전 검토
python3 <this-skill-dir>/scripts/take_sheet.py sheet review/S09.jpg clips/S09_t1.mp4 clips/S09_t2.mp4 clips/S09_t3.mp4 --times 0.3,1,2,3,4,5,6
시트를 읽으십시오: 행은 테이크, 열은 시간입니다. 복제된 소품, 나타나는 문이나 사람, 몸을 가로지르는 손, 변형되는 얼굴, 두 번 반복되는 동작을 찾으십시오. 각 문제의 정확한 초를 명시하십시오. 수정 방법은 문제 위치에 따라 다릅니다.
샘플링된 프레임 사이에서 하드 컷을 놓치기 쉽습니다. 직접 나열하십시오. 출력이 없으면 하나의 연속 샷을 의미합니다:
ffmpeg -i clips/S09_t1.mp4 -vf "select='gt(scene,0.3)',showinfo" -an -f null - 2>&1 | grep pts_time
샷에 맞게 클립 조정
편집은 샷을 정확히 채우는 클립이 필요하며, 편집의 프레임 속도와 일치해야 합니다:
| 문제 | 해결 방법 |
|---|---|
| 나쁜 시작 (대사 시작 전 동작, 첫 1초 안에 복제된 소품) | 나중에 시작: --off 1.5. |
| 트리밍 후 너무 짧음 | --speed 1.22로 느리게 (약 1.25까지 읽기 가능); 마지막 프레임 유지. |
| 나쁜 중간 | 샷 분할: 첫 번째 대사를 문제를 프레임 밖으로 유지하는 크롭에서 재생한 후 문제가 지나간 후 다시 컷. |
| 수정 불가 (문 나타남, 두 번째 사람 들어옴) | 고정된 프롬프트로 재생성, 2-3 테이크, 시트에서 선택. |
lipsync_parts.py normalize clip.mp4 shot.mp4 --frames N --off S --speed K는 오프셋, 속도, FPS, 스케일(늘리지 않고 커버 및 중앙 크롭), 마지막 프레임 유지를 한 단계로 수행하며 항상 정확히 N 프레임을 반환합니다.
대화 립싱크
- 클립을 샷에 맞게 정규화:
python3 <this-skill-dir>/scripts/lipsync_parts.py normalize clip.mp4 shot.mp4 --frames 132 --off 1.5 --speed 1.22. - 대사 경계에서 분할하여 각 부분에 한 명의 화자가 있도록 합니다.
shot.json작성: 각 대사는 샷 내 시작/종료 시간(초)과 화자 정보를 포함;frames는 정규화한--frames값과 동일. 뒤에서 보이거나, 화면 밖이거나, 군중 함성인 화자는"skip": true추가:{"id": "S05", "video": "shot.mp4", "fps": 24, "frames": 132, "lines": [{"id": "05-1", "audio": "lines/05-1.mp3", "start": 0.72, "end": 2.31, "speaker": "maya"}, {"id": "05-2", "audio": "lines/05-2.mp3", "start": 2.43, "end": 4.10, "speaker": "ido"}]}lipsync_parts.py split shot.json parts/실행. 파트 외에도parts/S05_lipsync_jobs.json작성: 각 비-스킵 파트에 대한 항목, 절대 경로 사용,out은 join이 예상하는 이름으로 이미 지정됨 (synced/S05_<k>.mp4),face: null.
- 각 파트의 화자 지점 지정하고 해당 jobs 파일에
"face": "x,y@<mid>"로 작성 (mid는 각 항목에 있음).- 파트의 중간 프레임에서 얼굴의 한 픽셀 선택.
- 확인:
take_sheet.py point parts/S05_1.mp4 check.jpg --frame 30 --xy 410,560실행 후 이미지 확인. - 얼굴 감지는 그려진 얼굴에 신뢰할 수 없으므로 눈으로 선택하고 확인. 얼굴이 하나만 보일 때만
null로 남겨 둠.
- 동기화:
lipsync_fal.py --jobs parts/S05_lipsync_jobs.json. 초당 약 $0.13, 파트당 약 70-150초 예상; 6개 병렬 실행. 기존 출력은 건너뜀. 얼굴 수정 후 하나의 파트를 다시 실행하려면:lipsync_fal.py parts/S05_1.mp4 parts/S05_1.wav synced/S05_1.mp4 --face 410,560@30 --force(--force를--jobs와 함께 사용하면 모든 파트 다시 실행). - 조인:
lipsync_parts.py join parts/S05_plan.json synced/ final/S05.mp4. 각 파트는 정확한 프레임 수로 강제 조정, 스킵된 파트는 원래 프레임 유지, 동기화된 파일이 없는 파트는 보고됨 (--strict는 오류로 처리), 총 프레임 수 확인.
세부 사항, 모델 선택 및 실패 사례: references/lipsync.md.
비용 및 시간 (2026-10 측정)
| 단계 | 수량 | 비용 | 시간 |
|---|---|---|---|
| Omni 클립 | 출력 비디오 초당 (6초 클립 약 $0.60; 리테이크 포함 샷당 약 1.5회 호출 계획) | ~$0.10 | 호출당 약 30초, 4개 병렬 |
| Sync 3 립싱크 | 출력 초당 (파트는 일반적으로 3-6초) | ~$0.13 | 파트당 약 70-150초, 6개 병렬 |
흔한 실수
| 실수 | 결과 | 해결 방법 |
|---|---|---|
| 비디오 프롬프트에서 캐릭터 이름 지정 | 잘못된 사람이 움직임 | 옷으로 설명 |
| Omni의 오디오 유지 | 대화 아래에 가상의 목소리와 음악 생성 | 음소거 (-an) |
| 시트 없이 테이크 사용 | 복제된 소품과 새로운 문 배송 | 모든 테이크 시트 작성 |
| 첫 1초가 나쁘다고 전체 클립 다시 생성 | 다른 곳에서 새로운 문제 발생 | 오프셋 및 느리게 하기 |
| 두 명의 화자가 있는 전체 샷을 립싱크 | 한 입이 두 대사 모두에 움직임 | 대사 경계에서 분할, 파트당 한 명의 화자 |
| 모델이 화자를 선택하도록 함 | 잘못된 얼굴이 말함 | --face X,Y@FRAME |
| fal 키 단축 | 401 오류 | fal에 표시된 대로 키 정확히 전달 |
| 프레임 수 수정 없이 파트 조인 | 샷이 편집과 동기화 어긋남 | lipsync_parts.py join 사용 |
관련 스킬
- 이미지 생성: 스틸컷 (캐릭터 카드, 패널).
- elevenlabs-tts: 입이 동기화할 대화.
- 비디오 보기: 클립 검토용 프레임 추출.
- 애니메이션 에피소드 제작: 이 클립들을 사용하는 전체 에피소드 워크플로우.