8.1 AI에 넣기 전 준비 - 오디오·프레임 추출과 시간 모델
음성·화면 분석에 맞는 입력을 FFmpeg로 만들고, AI 결과를 밀리초 정수 구간으로 저장해 타임코드와 프레임으로 오차 없이 오가는 방법을 코드와 측정값으로 설명합니다.
#AI는 "시간이 붙은 결과"를 만든다
6장에서 본 것처럼 AI 분석의 결과는 "이 구간에 이런 내용이 있다"입니다. 결과가 영상의 어느 시점인지 정확히 붙어 있지 않으면 MAM에서 쓸 수 없습니다. 검색 결과를 눌렀을 때 재생기가 엉뚱한 곳으로 가면, 분석이 아무리 정확해도 사용자는 그 기능을 믿지 않습니다.
그래서 AI 파이프라인에서 가장 먼저 정해야 하는 것은 모델이 아니라 두 가지입니다.
- 모델에 무엇을 넣을 것인가(입력의 형식과 품질)
- 결과를 어떤 시간 단위로 저장할 것인가
#입력 만들기: 오디오
음성 인식 모델은 대개 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에 열람이 되는 것을 확인했습니다. 규격과 도구 문서는 개정될 수 있으므로 적용하기 전에 최신 판을 확인하세요.