Synctoon — 스크립트 한 장으로 2D 애니메이션 영상을 뽑아내는 AI 파이프라인
Gemini가 감정·동작·배경을 연출하고, Gentle이 립싱크를 맞추고, Pillow가 프레임을 합성한다
나레이션 오디오와 스크립트 텍스트 파일 두 개를 던지면 2D 토킹헤드 애니메이션 영상이 나온다. 캐릭터가 말에 맞춰 입을 움직이고, 감정에 따라 표정이 바뀌고, 장면에 맞는 배경이 깔린다.
Synctoon은 이걸 전부 자동으로 처리한다.
3단계 파이프라인
전체 흐름은 단순하다. 분석 → 합성 → 영상화.
Stage 1 — AI 분석 + 오디오 정렬 (core.py)
Gentle(Docker 기반 forced aligner)에 오디오와 텍스트를 넘기면 단어별 시작/종료 타임스탬프가 나온다. "Hello"가 0.5초~0.8초 구간이라는 식.
그 다음 Gemini 2.0 Flash에 텍스트를 8번 보낸다. 각각 다른 연출 요소를 분석한다:
머리 방향(좌/우/정면), 눈 방향(좌/우/정면, 90% 정면 바이어스), 캐릭터 할당(누가 말하는지), 감정(14종 — happy, sad, angry, shock, evil_laugh 등), 바디 포즈(47종 — dancing, kung_fu, meditation 등), 강도(보통/강조), 줌 레벨(0/1/2), 배경(31종 — office, forest, bedroom, park 등)
AI 응답은 marshmallow 스키마로 검증한다. 검증 실패하면 에러 메시지를 프롬프트에 붙여서 재시도한다. 최대 3회. 자기교정 루프.
그 다음 g2p_en이 각 영어 단어를 음소(phoneme)로 변환한다. "Hello" → HH, AH, L, OW. 단어의 시간 구간을 음소 수로 균등 배분해서 프레임별 입 모양을 결정한다.
마지막으로 24fps 기준 프레임별 CSV를 생성한다. 각 프레임에 캐릭터, 감정, 바디포즈, 머리방향, 눈방향, 배경, 입모양, 줌, 눈깜빡임 정보가 들어간다. 80프레임마다 3프레임짜리 눈깜빡임 시퀀스도 자동 삽입.
Stage 2 — 프레임 합성 (frame_generator.py)
CSV를 한 줄씩 읽으면서 5개 레이어를 합성한다. 배경 → 바디 → 머리 → 눈 → 입. Pillow(PIL)로 PNG 이미지를 겹겹이 쌓는다.
metadata.json에 각 레이어의 위치와 크기가 픽셀 단위로 정의되어 있다. 머리 위에 눈이 (x, y) 좌표에 오고, 입이 (x, y) 좌표에 온다.
똑똑한 부분은 중복 프레임 캐싱이다. 캐릭터+감정+포즈+머리+눈+입+배경+줌 조합이 같으면 이전에 합성한 PNG를 재사용한다. 대사 중간에 입만 바뀌지 않는 구간이 연속되면 프레임 하나로 수십 프레임을 커버한다.
Stage 3 — 영상 컴파일 (frame_to_video.py)
OpenCV의 VideoWriter가 PNG 시퀀스를 24fps MP4로 묶는다. FFmpeg로 원본 오디오를 합쳐서 최종 영상 완성.
음소 → 입 모양 매핑
mouth_image.json이 각 음소를 특정 입 이미지에 매핑한다. 음소 "AH" → m_a_e_ah_h(행복 표정용), m_a_e_ah_s(슬픈 표정용). 감정 상태에 따라 같은 발음이라도 다른 입 모양 에셋을 쓴다.
45개 음소가 17개 입 모양(viseme)으로 그룹핑된다. 한 입 모양이 여러 비슷한 음소를 커버하는 구조.
에셋 시스템
캐릭터 에셋은 계층적이다:
images/characters/character_1/ 아래에 body/, head/, eyes/, mouth/, background/ 폴더가 있다.
바디 포즈 47종. 감정 14종. 배경 31종. 2D 퍼펫 애니메이션치고 조합 수가 꽤 많다. 각 폴더 안에 변형(variant) 이미지가 여러 장 있어서, 같은 포즈라도 매번 다른 이미지가 랜덤 선택된다.
현재 한계
프로토타입 단계라는 게 코드 곳곳에서 보인다. 파일 경로가 하드코딩되어 있고(/home/oye/Downloads/...), API 키가 코드에 노출되어 있다. 캐릭터도 현재 1개만 활성화되어 있다(멀티캐릭터 구조는 갖추고 있지만).
Gentle Docker 컨테이너가 반드시 떠 있어야 하고, Gemini API 호출 간 6초 sleep이 들어간다(rate limit 회피). 8번 호출하니까 AI 분석 단계만 최소 48초.
웹 UI는 없다. 전부 CLI.
그래도 접근법 자체는 흥미롭다. LLM을 "애니메이션 디렉터"로 쓰는 패턴. 텍스트를 읽고 감정, 포즈, 카메라, 배경을 결정하는 건 사람이 하던 일인데, 그걸 프롬프트 8개로 자동화했다.
근데 솔직히 말하면
코드를 까보면 "AI 기반 애니메이션 파이프라인"이라는 타이틀에 비해 AI가 하는 일이 생각보다 얕다.
프롬프트를 보면 안다. Carefully review the text below and create head movement instructions. Left (L), Right (R), Mid (M). — 이게 전부다. 텍스트를 던지고 L/R/M 중 하나 골라달라는 것. 감정도 마찬가지. 14개 감정 딕셔너리를 통째로 프롬프트에 넣고 번호를 고르라는 구조. 프롬프트 엔지니어링이라고 부르기 민망한 수준.
더 근본적인 문제는, 오디오를 전혀 분석하지 않는다는 점이다. 오디오는 Gentle의 립싱크 타이밍에만 쓰인다. 감정 분석? 텍스트만 본다. 바디 포즈? 텍스트만 본다. 머리 방향? 텍스트만 본다. 화자의 어조, 목소리 떨림, 말 빠르기, 감정적 뉘앙스 — 오디오에 담긴 이 모든 정보가 버려진다.
"I\'m fine"을 담담하게 말하는 것과 울면서 말하는 건 같은 텍스트인데 완전히 다른 연출이어야 한다. 이 시스템은 그 차이를 모른다.
8번의 API 호출이 각각 독립적이라는 것도 문제다. 감정이 angry인데 바디 포즈가 meditation으로 나올 수 있다. 배경이 bedroom인데 줌이 2(클로즈업)로 나올 수도 있다. 각 호출이 서로의 결과를 모르니까. 사람이라면 전체 장면을 보고 일관된 연출을 하겠지만, 여기서는 8개의 독립된 판단이 조합될 뿐이다.
rate limit 회피를 위한 6초 sleep도 아키텍처 문제를 드러낸다. 8개 호출을 순차적으로 보내는 건 batch API나 function calling으로 한 번에 처리하면 되는 일이다. Gemini의 structured output을 쓰면 marshmallow 검증 루프도 필요 없다.
그리고 영어 전용이다. g2p_en은 영어 음소만 지원한다. 한국어나 일본어 나레이션은 립싱크가 안 된다.
정리하면:
오디오 무시 — 텍스트만 보고 연출 결정. 어조와 감정 정보 소실.
8개 호출 독립 — 감정/포즈/배경 간 일관성 보장 없음. angry + meditation 같은 모순 가능.
프롬프트가 너무 단순 — few-shot 예시도 없고, 연출 의도에 대한 컨텍스트도 없음. L/R/M 고르라는 게 끝.
API 비효율 — 8번 순차 호출 + 6초 sleep = 최소 48초. 한 번의 structured output으로 대체 가능.
영어 전용 립싱크 — g2p_en 의존. 다국어 확장 불가.
그럼에도 이 프로젝트가 보여주는 건, LLM + forced aligner + 레이어 합성이라는 조합만으로도 "돌아가는" 애니메이션이 나온다는 사실이다. 완성도가 아니라 가능성을 증명한 프로토타입. 위 한계들은 전부 개선 가능한 문제이기도 하다.
리포지토리 현황 (2026-03 기준)
Star 25개, Fork 3개. 사실상 1인 개발 프로젝트다. 컨트리뷰터는 oyekamal 1명(커밋 45개) + google-labs-jules bot 1개(README 자동 생성).
main 브랜치 마지막 커밋이 2025년 9월이다. 6개월 넘게 main에 아무것도 안 들어갔다.
PR이 7개 있는데, 머지된 건 딱 1개(#2 — README 추가, bot이 작성). 나머지 6개 오픈 PR은 전부 2025-10-13 같은 날, 6분 간격으로 올라왔다. 라벨이 codex다. AI 코딩 에이전트가 한꺼번에 생성한 리팩토링 PR인데, 아직 리뷰도 안 됐다.
Issue는 1개. "request i want to work on this project" — 본문 없음. 참여 요청인데 응답도 없다.
커밋 메시지를 보면 개발 타임라인이 보인다. 2024년 8~9월에 핵심 기능(에셋 합성, CSV, 영상 컴파일, 눈깜빡임)을 2주 만에 구현했고, 2025년 7월에 README를 추가하고, 9월에 create_animation.py(통합 엔트리포인트)를 만든 게 마지막이다.
라이선스는 GPL-3.0. 상업적 사용 시 소스 공개 의무가 있다.
파이프라인 코드 탐색
각 카드를 클릭하면 해당 부분의 실제 소스 코드가 펼쳐집니다
Docker 컨테이너(port 49153)에 오디오 + 텍스트를 POST하면 단어별 시작/종료 타임스탬프 JSON이 돌아온다.
6초 간격으로 Gemini에 텍스트를 보내서 머리방향, 눈방향, 캐릭터, 감정, 포즈, 강도, 줌, 배경을 분석한다. 결과는 update_values()로 단어별 데이터에 매핑.
┗ 8개 호출의 상세 흐름 — 각 항목을 클릭하면 호출 체인과 프롬프트가 펼쳐집니다
▶
1
get_head_movement_instructions(transcript)
머리 방향
호출 체인
get_head_movement_instructions(text)
→
prompts.py
::
head_movement_instructions
프롬프트 핵심
HeadDirectionSchemaL R MM
▶
2
get_eyes_movement_instructions(transcript)
눈 방향
프롬프트 핵심
EyeDirectionSchemaL R M (90%)M
▶
3
get_character(transcript, characters)
캐릭터 할당
프롬프트 핵심
CharacterSchema@retry(max_attempts=3, delay=1)1
▶
4
get_emotion(transcript, emotions)
감정 (14종)
프롬프트 핵심
EmotionSchema1 (happy)
▶
5
get_body_action(transcript, body_actions)
바디 포즈 (47종)
constants.py에서 전달되는 포즈 목록 (일부)
achieve
explain
dancing
kung_fu
meditation
running
singing
thinking
jumping
... 외 38종
BodyActionSchema3 (explain)
▶
6
get_intensity(transcript)
강도
2 → 에셋 파일명에 _2 suffix가 붙어 강한 표정 변형 사용.
값: 1 (보통) / 2 (강조)
▶
7
get_zoom(transcript)
줌 레벨
0 (기본) / 1 (중간) / 2 (클로즈업)
▶
8
get_screen_mode(transcript, screen_mode)
배경 (31종)
프롬프트 핵심
ScreenModeSchema1 (office)
AI 응답을 marshmallow 스키마로 검증하고, 실패하면 에러 메시지를 프롬프트에 붙여서 재시도. 최대 3회.
텍스트 + 단어 수 + JSON 포맷 지정. 감정은 enum dict를, 배경은 scene list를 프롬프트에 직접 넣는다.
"Hello" → HH, AH, L, OW. 단어의 시간 구간(0.5초~0.8초)을 음소 수로 균등 나눠서 프레임별 입 모양 결정.
24fps 기준 프레임별 메타데이터 CSV. 80프레임 간격으로 3프레임짜리 blink 시퀀스를 자동 삽입.
metadata.json의 픽셀 좌표로 눈과 입을 머리에 붙이고, 머리를 바디에, 캐릭터를 배경 위에 합성.
캐릭터+감정+포즈+머리+눈+입+배경+줌 조합을 문자열 키로 만들어서 이미 합성한 PNG는 건너뛴다.
Gemini가 반환한 "단어 0~4번은 감정 happy" 같은 범위 지시를 각 단어 객체에 하나씩 매핑.
Gemini API 8회 호출 — 각각의 역할
머리 방향
L / R / M
눈 방향
L / R / M (90% M)
캐릭터
누가 말하는지
감정
14종
바디 포즈
47종
강도
보통 / 강조
줌 레벨
0 / 1 / 2
배경
31종
에셋 레이어 합성 순서
metadata.json의 픽셀 좌표에 따라 각 레이어가 정확한 위치에 배치된다
GitHub 리포지토리 현황
▶
Pull Requests 분석 — 7개 중 1개만 머지됨
| # | 제목 | 상태 | 날짜 |
|---|---|---|---|
| #2 | Add comprehensive README.md | merged | 2025-07-02 |
| #3 | Improve frame metadata handling in video converter | open codex | 2025-10-13 |
| #4 | Refactor character asset updater | open codex | 2025-10-13 |
| #5 | Ensure NLTK resources are loaded on demand | open codex | 2025-10-13 |
| #6 | Add CLI configuration and validation (중복) | open codex | 2025-10-13 |
| #7 | Improve blink parsing for frame generation | open codex | 2025-10-13 |
| #8 | Add CLI configuration and validation (중복) | open codex | 2025-10-13 |
주목할 점: 6개 오픈 PR 전부 같은 날(2025-10-13) 6분 간격으로 생성, codex 라벨. AI 코딩 에이전트가 일괄 생성한 리팩토링 PR인데, 5개월째 리뷰 없이 방치 중. #6과 #8은 제목까지 동일한 중복 PR.
▶
커밋 타임라인 — 2주 스프린트 후 6개월 침묵
2024-08-17 ~ 09-26 — 핵심 기능 구현 (2주 스프린트)
에셋 매핑, 이미지 합성, CSV 생성, 줌, 배경, 눈깜빡임, 영상 컴파일 등 전체 파이프라인 완성. 커밋 메시지: eyes access done → blinking added → video making added
2024-10 ~ 2025-06 — 9개월 공백
2025-07-02 — README 추가 (bot)
google-labs-jules bot이 PR #2로 README 자동 생성. 유일하게 머지된 PR.
2025-09-06 ~ 09-10 — 통합 엔트리포인트 + 정리
create_animation.py 생성, requirements.txt 추가, README 업데이트. main 브랜치 마지막 활동.
2025-10-13 — codex PR 6개 일괄 생성 (미머지)
AI 에이전트가 리팩토링 PR 6개를 6분 만에 생성. 이후 활동 없음.
컨트리뷰터
실전 순서
스크립트(.txt)와 나레이션 오디오(.mp3) 준비
Gentle Docker 컨테이너 실행 → 오디오-텍스트 강제 정렬(단어별 타임스탬프)
Gemini API 8회 호출 → 감정·포즈·배경·카메라 등 연출 지시 자동 생성
g2p_en으로 단어 → 음소 변환, 프레임별 입 모양 CSV 생성
Pillow로 5레이어(배경→바디→머리→눈→입) 합성, 중복 프레임 캐싱
OpenCV로 PNG → 24fps MP4 컴파일, FFmpeg로 오디오 병합
장점
- ✓ 완전 오픈소스 — 코드·에셋 전체 공개, 커스터마이징 자유
- ✓ LLM 기반 자동 연출 — 감정 14종, 포즈 47종, 배경 31종 조합을 AI가 결정
- ✓ 음소 단위 립싱크 — Gentle 강제 정렬 + g2p로 입 모양이 자연스럽게 맞음
- ✓ 프레임 중복 캐싱으로 합성 시간 대폭 절감
단점
- ✗ 초기 프로토타입 — 하드코딩 경로, API 키 노출 등 프로덕션 수준 아님
- ✗ 영어 전용 — g2p_en이 영어 음소만 지원, 한국어/일본어 불가
- ✗ Gemini API 8회 호출 + 6초 간격 → AI 분석만 최소 48초 소요
- ✗ 캐릭터 에셋 1개만 활성화 — 멀티캐릭터는 구조만 있고 에셋 부족