실패한 영상에도 이미 완성된 음성과 조각이 있다

영상 합성은 TTS, 아바타 립싱크, 페이지별 인코딩, 최종 결합으로 이어진다. 마지막 단계에서 실패했다고 앞의 결과까지 모두 버리면 외부 합성과 인코딩을 반복한다. 반대로 파일이 있다는 이유만으로 재사용하면 대본이나 음성 설정이 바뀐 결과, 쓰다 중단된 파일을 가져올 수 있다.

성공한 외부 립싱크 작업도 S3의 결과 리포트가 정리됐으면 새로 제출하고, 아직 실행 중인 작업은 리포트를 요구하지 않고 이어받도록 고쳤다. 이후 TTS·립싱크·세그먼트의 입력 지문과 완료 기록을 추가했다. 후속 보완에서는 재사용 TTS의 합성 시작 로그와 중단된 아바타 준비의 완료 판정을 바로잡았다.

AI Commons에서는 각 중간 산출물에 두 가지 조건을 붙였다. 해당 단계가 검증을 마쳐 완성됐고, 지금 요청의 입력이 그때 입력과 같아야 한다. 재시도 시작 지점은 하나의 페이지 번호가 아니라 이 조건을 만족하는 산출물들의 집합이 된다.

완료 기록은 파일을 확정한 뒤에 남긴다

산출물 옆의 X.done.json에는 기록 형식 버전, 입력 지문, 파일 크기, SHA-256과 단계별 메타데이터가 들어간다. 음성에는 길이와 표시 본문 기준의 alignment도 저장한다. 파일만 남거나 기록이 깨졌다면 재사용하지 않는다.

최종 파일을 교체할 때 옛 완료 기록이 새 바이트를 보증하지 않게 하고, 검증을 마친 뒤 새 기록을 남기는 것이 기본 의도다. 다만 파일을 확정하는 순서와 실패 처리는 단계마다 다르다.

단계파일 교체 순서새 완료 기록의 조건
TTS옛 기록 무효화 → 같은 디렉터리의 임시 파일 쓰기 → rename음성 길이를 확인하고 표시 본문 기준 alignment가 있을 때 기록
세그먼트임시 파일 인코딩 성공 → 옛 기록 무효화 → rename최종 경로로 확정한 뒤 기록
립싱크옛 기록 무효화 시도 → 최종 로컬 경로에 직접 다운로드다운로드 바이트의 리포트 해시 대조와 품질 검사를 통과하고, 기록 무효화도 성공했을 때 기록

세그먼트 인코딩이 실패하면 최종 파일을 교체하지 않았으므로 이전 파일과 기록이 남을 수 있다. TTS는 표시 본문에 투영한 alignment가 없으면 이번 음성을 사용하되 재사용 기록을 남기지 않는다. 립싱크는 임시 파일 rename을 사용하지 않으며, AWS 어댑터가 다운로드한 파일의 SHA-256을 리포트와 대조한 뒤 호출부가 품질을 확인한다.

기록 쓰기 실패는 이번 생성 결과를 버리는 이유로 삼지 않는다. 옛 기록 무효화는 성공했지만 새 기록 쓰기만 실패한 경로에서는 경고를 남기고 지금 결과를 사용한다. 다음 실행에는 완료 증거가 없으므로 다시 생성한다. 현재 요청의 성공과 다음 요청의 재사용 가능성을 분리한 선택이다.

립싱크의 옛 기록 무효화 자체가 실패하면 경고 후 다운로드를 계속하고 새 기록 쓰기는 생략한다. 외부에서 이미 완성한 결과를 버리지 않기 위한 처리지만, 이 예외 경로에는 옛 완료 기록이 남을 수 있다.

한 페이지의 대본 수정이 다음 페이지 음성에도 영향을 준다

TTS 지문에는 발화 본문뿐 아니라 표시 본문, 앞 페이지의 previousText, 보이스·모델·설정·출력 포맷과 청킹 상수가 들어간다. 표시 본문도 alignment를 어느 문자 위치에 연결할지 결정한다. 소리 나는 텍스트가 같아도 표시 본문이 달라지면 재합성이 필요하다.

앞 페이지 텍스트는 다음 합성의 문맥이다. 그래서 1페이지 대본을 고치면 1페이지 음성뿐 아니라 2페이지 음성의 지문도 달라진다. 재사용 검사를 “그 페이지가 편집됐는가”로 끝내면 이 의존성을 놓친다. 아래 표는 해당 입력을 사용하는 단계에 변경이 전파되는 범위다.

변경TTS립싱크·세그먼트
한 페이지 대본해당 페이지와 다음 페이지바뀐 음성을 입력으로 쓰는 결과
한 페이지 이미지재사용 가능해당 이미지의 세그먼트
워터마크 설정재사용 가능설정을 사용하는 세그먼트
립싱크 크롭·추론 설정재사용 가능해당 립싱크와 이를 포함한 세그먼트

음성 해시는 립싱크와 세그먼트의 입력 지문으로 이어진다. 세그먼트에는 이미지 바이트의 해시, 음성 해시, 순서가 있는 립싱크 해시와 화면 크기·fps·패딩·워터마크·PIP·코덱 제작법을 넣는다. CPU 배정과 임시 경로처럼 결과의 의미를 바꾸지 않는 실행 정보는 제외한다.

코드의 합성 방식이 바뀌어도 입력값은 같을 수 있다. 이런 변경은 제작법 버전인 recipe를 직접 올려야 한다. 지문은 입력을 충분히 선택하고 버전을 관리할 때만 재사용 계약이 된다.

저장한 해시와 재사용 때 검증하는 값은 다르다

공통 재사용 함수의 핵심 조건을 줄이면 다음과 같다. 실제 구현에는 JSON 형태와 필드 타입 검사도 있다.

if (rec.v !== RECORD_VERSION || rec.fingerprint !== fingerprint) return null;
const st = await stat(artifactPath);
if (!st.isFile() || st.size !== rec.size) return null;
return rec; // 기록의 sha256과 meta를 후속 단계에 전달

readReusableArtifact는 저장된 SHA-256을 다시 계산해 비교하지 않는다. 파일 크기로 잘림을 거르고, 기록의 해시를 후속 의존성 연결에 사용한다. 따라서 외부에서 같은 크기의 다른 바이트로 파일을 바꾸면 이 함수만으로 탐지하지 못한다. 파일 교체 전 옛 기록을 무효화하는 이유다. 립싱크의 무효화 실패 경로에서 옛 기록이 남은 채 파일이 같은 크기로 교체됐다면, 이 크기 검사만으로 교체 사실을 판별하지 못한다.

외부 립싱크 작업 식별자에도 입력 해시의 앞 12자리를 붙여 다른 음성을 옛 작업에 연결하지 않게 한다. 이 값은 충돌이 불가능한 식별자가 아니다. 최종 이미지 요청의 비교용 해시와도 역할이 다르다. 프롬프트 추적 해시는 입력을 비교할 뿐 결과를 재사용하는 키로 쓰지 않는다.

생략되는 작업과 계속 필요한 검사를 함께 확인한다

TTS와 세그먼트 테스트는 실제 임시 파일을 만들고 외부 합성과 FFmpeg 호출을 모킹한다. 같은 입력의 두 번째 실행에서 TTS 또는 세그먼트 인코딩 호출이 0건인지, 대본 변경으로 해당 페이지와 다음 페이지를 다시 합성하는지, 이미지·음성·워터마크 변경 범위가 맞는지 확인한다. 재사용 세그먼트도 최종 결합의 호환성 검사를 거친다.

파일 잘림, 기록 누락·손상, 기록 쓰기 실패도 테스트에 포함한다. 립싱크 테스트는 모킹한 어댑터로 재사용 시 정규화·오디오 추출·외부 제출을 생략하는지와 품질 검사 실패 시 완료 기록을 남기지 않는지를 확인한다. 기록 무효화 실패를 주입해 외부 결과 사용은 계속하고 새 기록은 쓰지 않는 분기도 확인한다. 이 호출 수는 테스트 조건의 결과이며 실제 공급자 비용이나 처리 시간 절감 측정은 아니다.

재사용에는 다음 실행에서도 접근 가능한 같은 파일시스템의 중간 결과가 필요하다. 실패 때 보존하고 최종 완료나 삭제 때 정리하는 정책은 저장 공간을 소비한다. 영상별 고정 경로와 임시 파일 정리는 여러 워커가 같은 영상을 동시에 처리할 때의 소유권 프로토콜까지 제공하지 않는다. 입력 의존성에 따른 재사용과 실행별 파일 격리는 각각 필요한 조건이 다르다.