# APNG Lip Sync Tool 개발기 - v11: 립싱크 타이밍 동기화 ## 개요 TTS 음성의 실제 길이에 맞춰 viseme 시퀀스의 타이밍을 동기화합니다. ## 타이밍 추정 ```python def estimate_phoneme_timings(text: str, total_duration_ms: int) -> list[dict]: """Estimate phoneme timings based on text and audio duration.""" viseme_data = korean_text_to_visemes(text) total_relative = sum(item['duration'] for item in viseme_data) timings = [] current_time = 0 for item in viseme_data: actual_duration = (item['duration'] / total_relative) * total_duration_ms timings.append({ 'char': item['char'], 'viseme': item['viseme'], 'start_ms': int(current_time), 'end_ms': int(current_time + actual_duration) }) current_time += actual_duration return timings ``` ## 프레임 타임라인 생성 ```python def create_lipsync_timeline(text: str, fps: int = 24) -> list[dict]: """Create frame-by-frame lipsync timeline.""" speech_result = generate_speech_with_timing(text) timings = speech_result["timings"] frame_duration_ms = 1000 / fps timeline = [] for time_ms in range(0, speech_result["duration_ms"], frame_duration_ms): viseme = get_viseme_at_time(timings, time_ms) timeline.append({'time_ms': time_ms, 'viseme': viseme}) return timeline ``` ## 실시간 동기화 클라이언트에서 오디오 재생 시간에 맞춰 viseme를 변경: ```javascript audio.addEventListener('timeupdate', () => { const currentTime = audio.currentTime * 1000; const viseme = getVisemeAtTime(timings, currentTime); characterImage.src = `/characters/${character}/${viseme}.png`; }); ``` --- *다음: v12 - 재생 플레이어 UI*