홈 › 8장 영상 AI 기술 실무

8.1 AI에 넣기 전 준비 - 오디오·프레임 추출과 시간 모델

음성·화면 분석에 맞는 입력을 FFmpeg로 만들고, AI 결과를 밀리초 정수 구간으로 저장해 타임코드와 프레임으로 오차 없이 오가는 방법을 코드와 측정값으로 설명합니다.

최종 수정 2026-10-28 · 읽는 시간 약 5분 · 운영 프리즘(Prism) · 편집 원칙

#AI는 "시간이 붙은 결과"를 만든다

6장에서 본 것처럼 AI 분석의 결과는 "이 구간에 이런 내용이 있다"입니다. 결과가 영상의 어느 시점인지 정확히 붙어 있지 않으면 MAM에서 쓸 수 없습니다. 검색 결과를 눌렀을 때 재생기가 엉뚱한 곳으로 가면, 분석이 아무리 정확해도 사용자는 그 기능을 믿지 않습니다.

그래서 AI 파이프라인에서 가장 먼저 정해야 하는 것은 모델이 아니라 두 가지입니다.

  1. 모델에 무엇을 넣을 것인가(입력의 형식과 품질)
  2. 결과를 어떤 시간 단위로 저장할 것인가

#입력 만들기: 오디오

음성 인식 모델은 대개 16kHz 모노 오디오를 기준으로 학습됩니다. 방송 소재의 48kHz 다채널 오디오를 그대로 넣기보다 변환해서 넣는 것이 일반적입니다.

ffmpeg -i input.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le audio16k.wav

10분(600초) 길이의 합성 영상으로 실행한 결과입니다.

항목 결과
출력 크기 19,200,078바이트 (16,000샘플 × 2바이트 × 600초 = 19,200,000 + 헤더 78바이트)
소요 시간 0.21초 (실시간의 약 2,800배속)

PCM 16비트 모노 16kHz는 1초에 32,000바이트라서 용량을 미리 계산할 수 있습니다. 1시간이면 약 115MB입니다. 변환 자체는 사실상 비용이 없고, 비용은 모델 쪽에서 듭니다.

#입력 만들기: 프레임

화면 분석에는 영상 전체가 아니라 샘플링한 프레임을 넣습니다. 이때 프레임을 뽑는 방법에 따라 그 프레임이 영상의 몇 초 지점인지가 달라지므로, 시각까지 함께 기록해야 합니다.

# 10초마다 1장(원본의 0, 10, 20초 ... 프레임), 가로 640으로 줄여서 JPEG 저장, 실제 PTS는 로그로 기록
ffmpeg -i input.mp4 -vf "select='isnan(prev_selected_t)+gte(t-prev_selected_t,10)',scale=640:-2,showinfo" \
  -fps_mode passthrough -q:v 3 frames/f_%05d.jpg 2> showinfo.log

10분(600초) 25fps 합성 영상에서 60장이 0.68초에 만들어졌고, showinfo가 기록한 원본 PTS는 0, 10, 20, ... 590초였습니다. showinfo.log의 pts_time: 값을 파일 번호와 함께 저장합니다.

샘플링 간격과 해상도는 9장 2편의 비용 계산에 직접 영향을 주므로, 글자를 읽을 목적(작은 자막)이면 해상도를 높이고 장면 분류 목적이면 낮추는 식으로 목적별로 정합니다.

작업 권장 입력 이유
음성 인식 16kHz 모노 PCM 모델이 기대하는 형식. 불필요한 변환을 줄임
화면 문자 인식 가로 1280 안팎 JPEG 작은 글자가 뭉개지지 않아야 함
장면·객체 분류 가로 640 안팎 JPEG 큰 구조만 보면 되고 처리 속도가 중요
텍스트 요약·분류 시간이 붙은 대본 텍스트 앞 단계(STT, OCR)의 결과

#시간 모델: 밀리초 정수 구간

모델은 시간을 12.34초 같은 실수로 돌려줍니다. 이 값을 그대로 저장하면 1장·3장에서 본 29.97 문제가 다시 생깁니다. 권장하는 방식은 경계에서 한 번만 정수 밀리초로 바꿔 저장하는 것입니다.

CREATE TABLE ai_segment (
  id         INTEGER PRIMARY KEY,
  asset_id   TEXT NOT NULL,
  kind       TEXT NOT NULL CHECK (kind IN ('speech','ocr','scene','face','object')),
  start_ms   INTEGER NOT NULL,
  end_ms     INTEGER NOT NULL CHECK (end_ms > start_ms),
  text       TEXT,
  confidence REAL,
  model      TEXT
);
CREATE INDEX ix_seg ON ai_segment(asset_id, kind, start_ms);
  • 시간은 파일의 시작을 0으로 하는 상대 시간으로 저장합니다. 원본의 시작 타임코드(예: 01:00:00;00)는 파일 레코드(5장 4편)에 있으므로, 화면에 보여 줄 때만 더합니다.
  • CHECK (end_ms > start_ms)로 길이가 0이거나 뒤집힌 구간을 입구에서 막습니다. 실제로 start = end인 행을 넣어 보면 CHECK constraint failed: end_ms > start_ms로 거부됩니다.

"재생 위치 10초에 걸쳐 있는 모든 결과"와 "20~25초와 겹치는 결과"는 부등호 두 개면 됩니다.

-- t = 10초에 걸친 구간 (start <= t < end)
SELECT kind, text FROM ai_segment
WHERE asset_id = 'a1' AND start_ms <= 10000 AND end_ms > 10000;

-- 20~25초와 겹치는 구간 (start < 25초 AND end > 20초)
SELECT kind FROM ai_segment
WHERE asset_id = 'a1' AND start_ms < 25000 AND end_ms > 20000;

음성 2건, 화면 문자 1건, 장면 1건을 넣고 실행했더니 첫 질의는 ocr, scene, speech 세 건을, 둘째 질의는 ocr, scene 두 건을 돌려주었습니다(음성 구간은 18초에 끝나므로 제외).

#프레임과 밀리초를 오가기

밀리초는 프레임에서 계산한 값입니다. 29.97fps의 프레임 간격은 약 33.4ms라서, 밀리초 정수 하나로 프레임 위치를 충분히 구별할 수 있지만 되돌릴 때 어떻게 반올림하느냐에 따라 결과가 달라집니다. 목적에 따라 함수를 둘로 나눕니다.

from fractions import Fraction

FPS = Fraction(30000, 1001)


def frame_to_ms(frame, fps=FPS):
    return round(Fraction(frame) / fps * 1000)       # 프레임 -> 밀리초(가장 가까운 정수)


def ms_to_frame(ms, fps=FPS):
    return round(Fraction(ms, 1000) * fps)           # 프레임에서 만든 값을 되돌릴 때: 가장 가까운 프레임


def frame_at(ms, fps=FPS):
    return int(Fraction(ms, 1000) * fps)             # 재생 위치 ms에 화면에 보이는 프레임(내림)
frame_to_ms(1800)   = 60060 ms
ms_to_frame(60060)  = 1800
frame_at(60059)     = 1799      ← 재생 위치가 1ms 앞이면 이전 프레임이 보임
frame_to_ms(107892) = 3599996 ms (1시간은 정확히 107,892.1프레임이라 정수 프레임에서는 4ms 모자란 값)

프레임 → 밀리초 → 프레임 왕복을 프레임 0~107,999 전체에 대해 실행한 결과입니다.

되돌리는 방식 원래 프레임과 다른 경우
가장 가까운 프레임(ms_to_frame, 반올림) 0 / 108,000
내림(frame_at) 52,200 / 108,000 (약 48%)

내림으로 되돌리면 프레임 1이 33ms로 저장된 뒤 프레임 0으로 돌아옵니다. 그래서 두 질문을 구분합니다.

  • "이 결과가 어느 프레임에서 만들어진 값인가": 가장 가까운 프레임(ms_to_frame)
  • "이 재생 위치에서 어느 프레임이 보이는가": 내림(frame_at)
  • 프레임 → 타임코드 변환은 1장의 정수 기반 드롭 프레임 함수를 씁니다. ms → 프레임 → 타임코드로 나누면 각 단계를 따로 테스트할 수 있습니다.

#정리

  • AI 결과는 시간에 붙어 있어야 쓸 수 있습니다. 모델보다 입력 형식과 시간 모델을 먼저 정합니다.
  • 오디오는 16kHz 모노 PCM(1초 32,000바이트), 프레임은 목적별 해상도로 샘플링합니다. 이 합성 영상에서 추출 시간은 매우 짧았습니다(코덱·해상도·저장 장치에 따라 달라집니다).
  • 시간은 파일 기준 상대 밀리초 정수 구간으로 저장하고, 프레임 변환은 분수 계산으로 하되 "만들어진 프레임"(반올림)과 "보이는 프레임"(내림)을 구분합니다. 프레임 정확도가 필요한 값은 프레임 번호나 PTS로 저장합니다.

#참고 자료

링크는 2026-10-28에 열람이 되는 것을 확인했습니다. 규격과 도구 문서는 개정될 수 있으므로 적용하기 전에 최신 판을 확인하세요.