[이 모듈이 하는 일] 설계도의 장면별 발화(ttsText)로 한국어 내레이션 음성을 만들고, 발화마다 영상 전체 기준의 시작·끝 시각을 계산해 시간표(timeline)를 만든다. 이 시간표는 자막·영상 길이·최종 렌더링이 모두 기준으로 쓴다. [입력] 이전 단계 JSON(vidia.doc@1). - blueprint.scenes[]: id, utterances[{id, speakerId, ttsText, pauseAfter}], durationSec, tailSec - blueprint.speakers[]: {id, voiceId, tempo} - blueprint.models.tts: {defaultVoiceId, tempo} - blueprint.audio.sceneGapMs: 장면 사이 쉼(기본 200ms) - assets: 이미 만든 음성("tts:<장면id>" 키)이 있으면 다시 만들지 않는다. - timeline: 이전 시간표(재실행일 때) [할 일] 1. 준비 - 화자 표: speakerId → voiceId, tempo. 화자의 tempo 가 없으면 models.tts.tempo, 그것도 없으면 1.0. 발화에 speakerId 가 없으면 첫 화자. - 장면 사이 쉼 gapSec = (audio.sceneGapMs 또는 200) ÷ 1000. 2. 장면마다 설계도 순서대로 음성을 만든다. - 발화가 없는 장면은 음성을 만들지 않는다. - 한 장면의 발화를 순서대로 훑어 "같은 화자가 이어지는 묶음(part)"으로 나눈다. 화자가 한 명이면 장면 전체가 part 하나다. - part 마다 tts_generate 를 부른다. voice_id = 그 화자의 voiceId, tempo = 그 화자의 tempo, utterances = [{"id": 발화 id, "text": ttsText, "pause_after": pauseAfter(있을 때만)}], label = "<장면id> 내레이션"(part 가 2개 이상이면 "<장면id> 내레이션 2" 처럼 번호). - 결과의 asset_id·duration_sec·utterances[{id,start_sec,end_sec}] 를 기록한다. - 자산 키: 첫 part 는 "tts:<장면id>", 두 번째부터 "tts:<장면id>:p2", "tts:<장면id>:p3" … - assets 에 그 키가 이미 있고 timeline.narration 에 같은 키의 기록이 있으면 도구를 다시 부르지 않고 그 기록을 쓴다. - ttsText 에 숫자·영문·기호가 남아 있어도 고치지 않는다(대본 수정은 이 단계의 일이 아니다). 도구가 거절하면 "error" 에 발화 id 와 이유를 적는다. 3. 시간표를 계산한다(초 단위, 소수 셋째 자리까지 반올림). - 첫 장면 startSec = 0, 그 뒤 장면은 startSec = 앞 장면 startSec + 앞 장면 durationSec (장면은 빈틈 없이 이어진다). - 장면 안 내레이션 시작 여유 leadSec: 첫 장면은 0.1, 그 뒤 장면은 gapSec. - part 는 장면 안에서 앞 part 가 끝나는 곳에 이어 붙인다: part 시작(장면 기준) = leadSec + 앞 part 들의 duration_sec 합. - 발화 절대 시각 = 장면 startSec + part 시작 + 도구가 준 start_sec / end_sec. - 장면 길이 durationSec = max(마지막 part 끝(장면 기준) + tailSec, 설계도 durationSec). tailSec 은 장면 값, 없으면 0.5. 발화가 없는 장면은 설계도 durationSec(없으면 3). - totalSec = 마지막 장면 startSec + durationSec. 4. timeline 을 채운다. - timeline.scenes["<장면id>"] = {"startSec": …, "durationSec": …, "narrationStartSec": 장면 startSec + leadSec, "narrationEndSec": 마지막 발화 절대 끝} - timeline.utterances["<발화id>"] = {"sceneId": "<장면id>", "startSec": …, "endSec": …} - timeline.narration = [{"key": "tts:<장면id>", "sceneId": "<장면id>", "assetId": 음성 asset_id, "startSec": part 절대 시작, "durationSec": duration_sec}, …] (영상 순서) - timeline.totalSec = 전체 길이 - timeline.order = 장면 id 배열(설계도 순서) [출력] 다음 최상위 필드만 담은 JSON 객체 하나(적지 않은 최상위 필드는 입력값이 그대로 이어지므로 다시 적지 않는다. 바꾸는 필드는 값 전체를 적는다.) - assets: 기존 값에 "tts:<장면id>"(와 "tts:<장면id>:p2" …) 키를 더한다. - timeline: 위에서 만든 {utterances, scenes, narration, totalSec, order} 예: "assets":{"tts:s01":41,"tts:s02":42}, "timeline":{"utterances":{"s01_u1":{"sceneId":"s01","startSec":0.1,"endSec":2.84}},"scenes":{"s01":{"startSec":0,"durationSec":6.12,"narrationStartSec":0.1,"narrationEndSec":5.72}},"narration":[{"key":"tts:s01","sceneId":"s01","assetId":41,"startSec":0.1,"durationSec":5.62}],"totalSec":58.4,"order":["s01","s02"]} [주의] - tts_generate 는 글자 수에 비례해 비용이 드는 도구다. 장면(또는 part)마다 한 번만 부르고, 이미 만든 음성은 다시 만들지 않는다. - 도구가 준 발화 시각을 그대로 쓴다. 시각을 추정해 지어내지 않는다. - 설계도(blueprint)는 바꾸지 않는다. - 설명·마크다운 없이 JSON 객체 하나만 답한다.