움직임은 비싸다 — 모든 컷을 영상으로 만들지 않는 법

모든 컷을 영상으로 만들기 전에

기존 파이프라인은 캐릭터 이미지를 생성한 뒤 확대·이동 효과인 Ken Burns를 적용해 영상을 만들었다. 여기에 이미지로부터 동영상을 만드는 image-to-video를 넣으면 캐릭터나 배경 자체에 움직임을 줄 수 있다. 나는 Veo를 이용해 도입부 일부를 영상화했다.

문제는 생성 개수였다. 한 편에는 수십 개의 서브컷이 들어간다. 모든 컷을 영상으로 만들면 이미지 생성과 음성 합성 비용에 영상 생성 비용까지 더해진다. 도입할 수 있는지만 따지기보다 어디에 쓸지를 먼저 정해야 했다.

당시 설계 문서와 스크립트에는 8초 클립당 1.04달러라는 예상 단가가 들어 있었다. 이 가정이면 30개는 31.20달러, 하루 한 편씩 30일 제작하면 영상 생성분만 936달러다. 계산 자체는 맞지만 이 단가를 Veo의 확정 요금으로 제시할 수는 없다. 코드의 예상액과 실제 청구 내역을 대조한 기록이 없기 때문이다.

모델과 생성 옵션에 따라 요금이 달라지므로 재사용할 때는 적용할 모델의 공식 가격표를 확인해야 한다. 이 글의 1.04달러는 당시 예산 계산에 넣었던 가정으로만 사용한다.

초반 이탈을 줄이려는 가설

당시 설계 문서에는 시청자의 50~70%가 영상 초반 5%, 약 24초 안에 이탈한다는 관측이 정리돼 있었다. 나는 이를 바탕으로 도입부인 Hook을 먼저 개선하기로 했다. 이 수치는 당시 채널에 관한 기록이며 모든 영상에 적용되는 기준은 아니다. 영상 길이가 달라지면 첫 5%에 해당하는 시간도 달라진다.

초반 이탈이 크다는 관측만으로 정적 이미지가 원인이라고 단정할 수는 없다. 주제나 대본, 목소리도 영향을 줄 수 있다. 도입부에 움직임을 넣으면 도움이 될 것이라는 가설 아래 첫 4개 서브컷을 영상화 대상으로 정했다. 뒤쪽 컷은 기존 이미지 방식으로 남겨 생성량을 제한했다.

원문에서는 24초를 덮기 위해 8초 클립 4개가 필요하다고 설명했지만, 단순히 이어 붙인다면 3개로 24초를 채울 수 있다. 4개는 선택한 제작 범위였으며 산술적으로 필요한 최소 개수가 아니다.

더 중요한 차이는 생성 길이와 편집 길이다. 스크립트는 클립당 8초를 요청하지만 조립기는 나레이션 길이를 서브컷에 나눠 배정한다. 클립을 자르거나 속도를 조정할 수 있으므로 첫 4개를 골랐다고 완성 영상의 첫 32초가 반드시 영상으로 채워지는 것은 아니다. 나는 시간 구간을 정확히 잘라 선택한 것이 아니라 앞에서부터 컷 개수를 제한했다.

구현: 기존 메타데이터에 영상 경로를 더하다

앞선 글에서 다룬 조립 구조를 유지하면서 이미지 생성과 조립 사이에 영상화 단계를 추가했다. 이 단계는 프롬프트 목록의 앞 N개를 선택하고, 대응하는 이미지가 있으면 영상 생성을 요청한다. 생성 파일을 저장한 뒤 같은 메타데이터 항목에 videoPath를 추가한다.

조립기는 영상 경로와 파일의 존재 여부를 확인한다. 다음은 현재 소스의 선택 로직을 떼어낸 예제다. 실제 세그먼트를 만드는 코드와 길이 조정은 생략했다.

import os


def select_segment_kind(entry):
    video_path = entry.get("videoPath")
    if video_path and os.path.exists(video_path):
        return "video", video_path
    return "image", entry["imagePath"]

영상 파일이 없으면 기존 이미지 경로를 사용한다. 다만 파일이 존재한다는 확인은 정상적으로 재생된다는 검증과 다르다. 뒤 단계에서 영상 길이를 읽고 배정된 길이에 맞춰야 한다. 현재 구현에는 너무 짧은 클립을 이미지로 대체하는 처리와 생성 영상의 소리를 끄는 처리도 있다.

따라서 분기 한 줄이 통합의 전부는 아니다. 영상 생성 단계와 조립기의 수정이 모두 필요했다. 이미지 메타데이터에 선택적인 영상 경로를 더해 기존 입력 형식을 확장한 것이 핵심이었다. 별도의 영상 목록과 이미지 목록을 조립 단계마다 맞추는 작업은 피할 수 있었다.

현재 생성 스크립트는 한 클립의 생성 완료를 주기적으로 조회하고 파일을 받은 뒤 다음 클립으로 넘어간다. API가 비동기 작업을 반환하더라도 스크립트가 여러 클립을 병렬 생성하는 구조는 아니다. 다른 단계를 함께 진행하는 오케스트레이션까지 이 코드가 보장하지는 않는다.

개수 제한과 비용 상한은 다르다

생성 대상의 수는 max_clips로 정했다. 기본값은 4이며 0 이하이면 영상 생성 단계를 건너뛴다. 다음 코드는 대상 선택과 예상액 계산을 함수로 재구성한 것이다. 단가는 당시 가정을 표시하기 위해 인자로 분리했다.

from decimal import Decimal


def plan_hook_generation(prompts, max_clips=4,
                         assumed_clip_cost=Decimal("1.04")):
    if max_clips <= 0:
        return [], Decimal("0")

    selected = prompts[:max_clips]
    estimated_cost = len(selected) * assumed_clip_cost
    return selected, estimated_cost

이 가정에서 4개를 선택하면 예상액은 4.16달러다. 영상 생성분만의 계산이며 이미지 생성과 음성 합성 등 나머지 제작비는 포함하지 않는다. 코드에 있는 예상액 출력도 청구를 감시하거나 초과 사용을 차단하는 기능은 아니다. 다시 실행해 클립을 생성하거나 단가가 달라지면 전체 지출은 달라진다.

max_clips=0의 의미도 구분해야 한다. 새 영상 생성을 생략할 뿐, 이미 기록된 videoPath를 지우지는 않는다. 같은 작업 결과를 다시 조립하면 기존 영상 파일이 선택될 수 있다. 전부 이미지로 만들려면 조립에 넘길 메타데이터나 영상 사용 조건도 별도로 바꿔야 한다. 이미지 생성 등 다른 단계의 비용 역시 남는다.

생성 개수를 노출한 것은 운영자가 투입량을 조절할 수 있게 한 선택이다. 클립이 많을수록 품질이 좋아진다는 뜻은 아니다. 컷별 필요와 비용을 보고 범위를 정하고, 금액 자체를 제한하려면 별도의 사용량 집계와 예산 관리가 필요하다.

캐릭터 이미지를 입력으로 유지하다

이번 제품의 이미지는 Flux와 캐릭터용 LoRA로 생성했다. LoRA는 모델을 적은 수의 학습 파라미터로 조정하는 방식이다. 3편에서 다룬 첫 제품의 레퍼런스 등록 방식과는 구분해야 한다. 여기서는 이미 만들어 둔 캐릭터 이미지를 영상 생성의 입력으로 사용했다.

입력 이미지는 영상의 외형 기준을 제공하지만, 모든 프레임에서 캐릭터가 그대로 유지된다는 보장은 없다. 움직임을 주는 과정에서 얼굴이나 형태가 달라질 수 있으므로 결과를 확인해야 한다. 아래 클립은 기존 이미지를 입력으로 사용해 얻은 한 사례다.

도입부에 사용한 image-to-video 예시. 기존 캐릭터 이미지에 움직임을 더한 8초 클립이며, 무음으로 반복 재생한다.

결과와 남은 측정

영상화는 실험용 샘플에 그치지 않고 반복 제작 경로에 적용했다. 통합이 한 번에 됐다는 것이 당시의 회고다. 병행하던 숏폼 프로젝트에서 Veo를 사용하며 겪은 시행착오가 이번 작업에 도움이 됐다. 다만 그 경험이 이후의 생성 실패나 외형 변형까지 없애 준 것은 아니다.

비용 측면에서 구현한 것은 생성 개수 제한과 예상액 표시다. 실제 편당 청구액을 약 4달러로 고정했다는 성과와는 다르다. 이미지를 유지할 수 있는 조립 경로를 남겨 두고 영상화 범위를 조절할 수 있게 됐다는 데 의미가 있었다.

시청 유지율이 영상화 덕분에 나아졌는지는 분리하지 못했다. 같은 시기에 나레이션 음성, 배경음악, 도입부 대본도 함께 바꿨기 때문이다. 유지율이 달라졌더라도 어느 변경의 효과인지는 이 기록만으로 알 수 없다.

당시에는 여러 개선을 함께 적용해 반복 제작을 이어 갔다. 이는 영상화의 단독 효과를 검증한 실험과 다르다. 다음에 효과를 비교한다면 먼저 비교할 지표와 변경 범위를 정하고, 주제와 시청자 구성 같은 조건도 함께 기록해야 한다. 영상만 바꿨다는 이유로 서로 다른 업로드의 성과 차이를 곧바로 인과로 해석할 수는 없다.

학습

← 전체 글 목록