-
Seedance 2.0 fast가 만든 영상의 입 모양이 어색했다 — lip-sync가 production 디폴트가 된 이유IT 2026. 5. 22. 22:00
📡 이 글은 BytePlus Seedance 2.0 fast 모델을 API로 직접 호출해서 영상을 만드는 사용자를 위한 글입니다.
generate_audio토글과reference_audiorole을 직접 다루는 시나리오는 API 사용자만 해당됩니다. ModelArk 콘솔에서 음성을 같이 업로드해 보신 분이라면 결과는 비슷하게 보이지만, 본문에서 다루는 ffmpeg post-mux와의 trade-off 결정은 API 자동화 환경에서 의미가 있습니다."AI로는 무음 영상만 생성하고 음성은 내가 mux한다" — 처음의 가정
처음 영상 파이프라인을 설계할 때 자연스럽게 떠오른 패턴이 하나 있었습니다.
- Seedance는 시각만 잘하니까 영상은 무음으로 생성시키자(
generate_audio: false). - 그 다음 사용자가 녹음한 음성 mp3를 ffmpeg로 내가 직접 mux하자.
- 그렇게 하면 모델이 멋대로 만들어 넣는 이상한 음성도 없고, 음성 정체성도 100% 사용자 것이고, 비용도 아낀다.
논리적으로 깔끔합니다. 그리고 한 두 클립까지는 실제로 잘 작동합니다. 문제는 close-up 인물 발화 클립을 만들 때부터였습니다. AI가 만든 영상 속 입 모양이 음성과 묘하게 어긋났고, 시청자가 즉시 알아챘습니다. "왜 이렇게 천천히 말해?" "영상이 음성보다 살짝 빠른가?" 같은 인상이 남았죠.
ffmpeg adelay로 음성 timing을 1.3초씩 보정해 봐도, 발화 중간중간의 비트가 안 맞는 건 후처리로 도저히 못 막습니다. 입 모양은 phoneme 단위로 정렬되어야 하는데, 영상 측은 phoneme을 모르고 음성 측은 영상 모션을 모릅니다. 두 stream이 처음부터 같이 만들어졌어야 하는 정보였던 거죠.
모델이 처음부터 audio·video를 같이 만들게 하자 — lip-sync
해법은 의외로 간단했습니다. Seedance 2.0 fast 자체가 audio·video를 joint로 생성하도록 모드 자체를 바꾸는 것.
generate_audio: true로 켜고, 사용자 음성 mp3를 reference_audio role로 같이 넣어 보내면, 모델이 audio와 video를 cross-modal하게 동시에 만들어 줍니다. 입 모양은 자기가 만든 audio의 phoneme에 맞춰 정렬되니, 둘 사이 sync는 모델 안에서 끝납니다.이 동작이 가능한 이유는 최근 영상 모델의 architecture에 있습니다. Seedance 2.0 fast 같은 모델은 Dual-Branch DiT(4.5B parameter) 구조라, audio branch와 video branch가 병렬로 존재하고 중간에 cross-modal joint module이 둘을 묶어줍니다. millisecond 단위로 phoneme → mouth shape 매핑이 이뤄지죠.
다이어그램이 강조하는 건 두 가지입니다. (1) audio·video sync는 모델 밖에서 mux로 풀 문제가 아니라, 처음부터 같이 생성될 때만 풀리는 문제다. (2) 사용자 음성은 reference_audio로 들어가서 timbre·prosody의 ground truth가 되고, 모델은 그걸 흉내내며 영상과 sync된 새 audio track을 만들어 낸다.
코드로 보면 — 토글 한 줄 차이
이 모드 전환을 코드에 녹이려면 클립 정의에 토글 하나만 추가하면 됩니다.
# 클립 정의 yaml에서 - id: i2v_son_living_room type: i2v prompt: "..." voice_mp3: "voices/son_living_room.mp3" lip_sync: true # ← 이 한 줄 차이 # 코드 측에서는 def build_request(clip) -> dict: body = { "model": "seedance-2.0-fast", "content": [...], # prompt + first_frame "duration": clip.duration, } if clip.lip_sync: body["generate_audio"] = True body["content"].append({ "type": "audio_url", "audio_url": {"url": _data_uri(clip.voice_mp3)}, "role": "reference_audio", }) else: body["generate_audio"] = False # 영상은 무음으로 받고, 후속 단계에서 ffmpeg mux return bodylip_sync토글이 켜진 클립은 audio를 reference로 함께 전송하고 모델에 audio·video joint를 맡깁니다. 꺼진 클립은 silent로 받아 ffmpeg로 mux하는 기존 fallback 경로를 탑니다. 한 시스템 안에 두 흐름이 공존하지만, 클립별로 명시적으로 결정하니 디버깅이 쉽습니다.fallback이 여전히 필요한 이유
lip-sync가 디폴트가 됐다고 ffmpeg mux 경로를 완전히 버릴 수는 없습니다. trade-off가 있기 때문입니다.
- lip-sync 정확도 ↑ ↔ 음성 정체성 보존 ↓. 모델이 reference 음성의 timbre·pitch를 흉내내긴 하지만 100% 동일하지는 않습니다. 어린이 본인 목소리처럼 정체성 자체가 작품의 본질인 경우, 약간의 음색 변형도 손해입니다.
- reference 충실도가 비결정적입니다. 같은 reference_audio로 같은 모델을 호출해도 어떤 호출에선 거의 그대로, 어떤 호출에선 "TTS같은" 단조로운 음성이 나옵니다(이 비결정성은 따로 다룰 만큼 깊은 주제입니다).
이 두 한계가 critical할 때의 대응이 두 가지 있습니다.
- silent + ffmpeg mux로 fallback. 입 모양 sync는 포기하고 voice 정체성을 우선.
- 입 안 보이는 구도로 우회. wide shot, 뒷모습, voiceover로 가면 sync 자체가 문제가 안 됩니다. 시나리오 단계에서 결정해야 하는 일.
결과 — 디폴트가 뒤집힌 이야기
처음 시작했을 때 "silent + 후합성이 reliable한 패턴"이라고 적었던 메모를, 두 달 뒤에 다시 보니 정 반대 결론을 적게 됐습니다.
- lip-sync = production 디폴트. 인물 발화 클립은 거의 항상 lip-sync로 갑니다.
- silent + post-mux = fallback. 음성 정체성이 critical한 클립에서만 의도적으로 쓰는 경로.
- per-clip 토글로 두 흐름을 한 시스템에서 처리. lip-sync는 클립의 capability이지, 시스템 전체의 default 정책이 아닙니다.
뒤집힌 디폴트가 알려주는 더 큰 교훈은 이겁니다 — 모델이 더 많은 결정을 자체적으로 처리하는 모드가 새로 생기면, "내가 후처리로 한다"는 기존 패턴을 다시 검토해야 한다. 후처리로 풀고 있던 문제가 사실 모델 안에서 더 잘 풀리는 문제일 수 있고, 그 경우 후처리 layer는 fallback으로 강등됩니다. 영상이든 음성이든 텍스트든 같은 원리입니다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
- Seedance는 시각만 잘하니까 영상은 무음으로 생성시키자(