No description
  • Python 65.3%
  • JavaScript 14.8%
  • CSS 13.4%
  • HTML 5.1%
  • Shell 1.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jaewon Kim 6b0d67b207 Add emphasis: AI-picked zoom/highlight overlays for segments
New STEP 2.5/3.5 in the pipeline: segment.py now emits a per-frame
diff map that emphasis.py turns into emphasis candidates (coordinates
+ strength), and emphasis_director.py has the AI pick which candidate
to highlight per segment (zoom, colored box, or both). Coordinates
stay program-computed and normalized (0-1); the AI only selects among
verified candidates, same pattern as segment timing. Render, workfile,
editor, and web UI updated to store/apply/edit emphasis alongside
subtitles and speed.
2026-08-07 01:28:39 +00:00
app Add emphasis: AI-picked zoom/highlight overlays for segments 2026-08-07 01:28:39 +00:00
web Add emphasis: AI-picked zoom/highlight overlays for segments 2026-08-07 01:28:39 +00:00
.env.example Add ROUGHCUT: AI-assisted rough-cut editor for screen recordings 2026-08-07 00:06:13 +00:00
.gitignore Add ROUGHCUT: AI-assisted rough-cut editor for screen recordings 2026-08-07 00:06:13 +00:00
README.md Add emphasis: AI-picked zoom/highlight overlays for segments 2026-08-07 01:28:39 +00:00
requirements.txt Add ROUGHCUT: AI-assisted rough-cut editor for screen recordings 2026-08-07 00:06:13 +00:00
run.sh Add ROUGHCUT: AI-assisted rough-cut editor for screen recordings 2026-08-07 00:06:13 +00:00

ROUGHCUT — 기능 소개 영상 자동 편집

화면 녹화 영상과 기능 설명 문서를 올리면, 구간을 나누고 화면을 읽고 자막을 쓰고 구간별 배속까지 정해 초안 영상(rough cut)을 만듭니다. 만든 뒤에는 말로 고칩니다 — "로딩 구간은 8배속으로", "3번 자막 더 짧게".

Video_Maker 파일럿(CLI)의 4단계 파이프라인을 그대로 잇되, 배속 판단을 규칙에서 AI로 옮기고 웹 화면과 편집 대화를 붙였습니다.

STEP 1    구간 나누기   segment.py           화면 변화 감지 → 구간 경계 + 변화량 + 변화 지도
STEP 2    화면 읽기     frames.py            구간별 대표 프레임 여러 장 (+ 선택적 OCR)
STEP 2.5  강조 후보     emphasis.py          변화 지도 → 강조할 만한 영역 후보 (좌표)
STEP 3    자막·배속     director.py          AI가 자막을 쓰고 배속을 정함
STEP 3.5  강조 감독     emphasis_director.py AI가 후보 중 어디를 강조할지 고름
STEP 4    영상 만들기   render.py            배속 + 강조 + 자막 합성 → MP4

설계 원칙 — 시간은 프로그램이, 말과 속도는 AI가

원본 파일럿의 원칙을 그대로 지킵니다.

시간 정보(구간 경계)는 프로그램이 계산해서 확정하고, AI에게는 이미 나뉜 구간에 들어갈 내용만 맡긴다.

그래서 같은 영상은 항상 같은 구간으로 나뉩니다(결정적). AI는 구간의 start/end를 절대 건드리지 못하고, 자막·배속·사용 여부만 정합니다. 편집 대화도 마찬가지로 허용된 편집 연산(op) 만 JSON으로 돌려받아 프로그램이 검증한 뒤 적용합니다.

배속 판단을 AI에게 넘긴 이유는 근거가 갖춰져 있기 때문입니다.

누가 무엇을
프로그램 구간 경계 · 길이 · 화면 변화량(0~255) · 대표 프레임 여러 장 · 강조 후보 좌표
사람 편집 지시문 (“로딩은 빠르게, 입력 장면은 천천히”)
AI 구간별 자막 · 배속 · (허용 시) 잘라낼 구간 · 어느 후보를 강조할지

같은 원칙이 공간에도 적용됩니다. 좌표는 프로그램이 계산해 후보로 내놓고, AI는 그중 어디를 강조할지만 고릅니다. 자세한 내용은 아래 「강조」를 보세요.

구간을 어떻게 나누는가

되감기(seek)는 h264에서 비싸므로 순차 두 패스로 끝낸다.

  1. 훑기 — 0.1초 간격으로 프레임을 디코딩해 변화 신호를 만들고, 신호 자체의 통계(중앙값 + 2.2σ)로 적응 임계를 잡는다. 임계를 넘은 지점 중 주변에서 가장 큰 값만 경계로 삼는다(전환 하나가 0.1초짜리 조각 여럿으로 쪼개지는 것을 막는다).
  2. 다시 재기 — 각 경계 후보의 앞뒤 0.35초를 프레임 단위로 훑어 실제 전환 프레임을 찾아 경계를 옮긴다. 이게 없으면 경계가 최대 샘플 간격만큼 밀리고, 구간 첫 화면이 아직 이전 화면이라 대표 프레임과 자막이 한 박자씩 어긋난다.

그 뒤 두 가지를 정리한다.

  • 너무 짧은 조각은 합친다. 빠른 클릭 연속은 1~2초짜리 조각 십수 개로 잡히는데, 1.5초짜리 자막은 읽히기 전에 사라진다. 2.5초에 못 미치는 조각은 이웃과 묶되 한 덩이가 9초를 넘지 않게 한다.
  • 너무 긴 구간은 진짜 변화 지점에서 쪼갠다. 다만 화면이 사실상 멈춘 구간(변화량 0.08 미만)은 아무리 길어도 그대로 둔다. 86초 로딩을 17초씩 다섯 토막 내어 같은 자막을 다섯 번 띄우느니, 한 구간으로 두고 8배속을 거는 편이 낫다.

자막을 위해 무엇을 보여주는가

구간 하나를 화면 한 장으로 보여주면 "무슨 화면인지"는 알아도 "무엇이 어떻게 바뀌었는지"는 못 쓴다. 그래서 이렇게 보낸다.

  • 구간마다 길이에 따라 1~4장을 시작·중간·끝에서 뽑아, [구간 7 · 01:23.4 · 중간] 같은 라벨과 함께 시간 순서대로 보낸다.
  • 배치 앞뒤로 이웃 구간의 화면을 한 장씩 참고용으로 덧붙인다. (흐름 파악용이며 그 구간의 자막은 쓰지 않게 한다.)
  • 앞 배치에서 쓴 자막도 함께 넘겨 말투와 흐름을 잇는다.

한 요청에 실을 양은 구간 수가 아니라 이미지 수(기본 26장)로 끊는다. 22초짜리 구간 넷과 2초짜리 구간 열은 이미지 수가 전혀 다르기 때문이다.

실행

git clone <이 저장소> && cd video-ai-studio
cp .env.example .env          # GENAI_API_KEY 를 채웁니다
./run.sh                      # → http://localhost:8000

run.sh 하나로 끝납니다. .venv를 만들고 requirements.txt를 설치한 뒤 서버를 띄웁니다(두 번째 실행부터는 requirements가 바뀌었을 때만 다시 설치).

PORT=9000 ./run.sh            # 포트 변경 (셸 값이 .env 보다 우선)
./run.sh --reload             # uvicorn 인자는 그대로 전달됩니다
SKIP_VENV=1 ./run.sh          # 가상환경 없이 현재 파이썬으로
SKIP_INSTALL=1 ./run.sh       # 의존성 설치 건너뛰기

FFmpeg는 imageio-ffmpeg가 함께 설치하므로 따로 깔지 않아도 됩니다. 시스템에 ffmpeg가 있으면 그쪽을 먼저 씁니다.

한글 자막용 Pretendard 폰트를 app/assets/fonts/에 함께 담았고, 렌더 시 fontsdir로 직접 지정합니다. 폰트가 하나도 없는 서버에서도 자막이 깨지지 않습니다.

모델

OpenAI 호환 게이트웨이(/v1/chat/completions)를 씁니다. 기본값은 사내 GenAI Shared Service이며 .env로 바꿀 수 있습니다.

GENAI_API_KEY=sk-...                                  # 필수, 기본값 없음
GENAI_BASE_URL=https://genai-sharedservice-apac.pwc.com
GENAI_MODEL=bedrock.anthropic.claude-sonnet-4-6       # 기본 모델

API 키는 코드에 두지 않습니다. .env.gitignore에 있고, 양식은 .env.example에 있습니다. 우선순위는 셸 환경변수 > .env > 코드 기본값 이며, 키가 비어 있으면 서버는 그대로 뜨되 분석 단계에서 안내 메시지를 냅니다.

모델 쓰임
bedrock.anthropic.claude-sonnet-4-6 기본값. 자막 품질이 가장 안정적
vertex_ai.gemini-3-flash-preview 구간이 많은 긴 영상에서 빠름

화면 드롭다운의 기본 목록은 app/config.pyDEFAULT_MODEL_CHOICES입니다. 게이트웨이가 /v1/models를 막아 두어(403) 목록을 고정해 뒀습니다. 다른 게이트웨이를 쓴다면 코드를 고치는 대신 .env에서 통째로 갈아끼웁니다.

# 아이디|이름|설명 을 ; 로 이어 붙입니다. 이름·설명은 생략 가능합니다.
GENAI_MODELS=bedrock.anthropic.claude-sonnet-4-6|Claude Sonnet 4.6|자막 품질이 안정적;vertex_ai.gemini-3-flash-preview|Gemini 3 Flash|긴 영상에서 빠름

형식이 깨진 항목은 조용히 건너뜁니다 — .env 오타 하나로 서버가 죽지 않게 했습니다. GENAI_MODELS를 채울 때는 GENAI_MODEL도 그 목록에 넣어 두는 편이 좋습니다(빠뜨리면 이름 없이 맨 앞에 끼워 넣습니다).

GENAI_MODEL에 적은 기본 모델은 목록에 없어도 항상 고를 수 있습니다 (없으면 목록 맨 앞에 자동으로 끼워 넣습니다). 목록에 없는 모델은 서버가 기본 모델로 되돌리므로, 이 보정이 없으면 .envGENAI_MODEL이 조용히 무시됩니다.

두 모델 모두 프레임 이미지를 직접 읽습니다(vision). 그래서 PaddleOCR가 없어도 화면 글자를 파악합니다. 설치돼 있으면 OCR 결과를 프롬프트에 함께 넣습니다.

쓰는 순서

  1. 원본 — 화면 녹화(mp4/mov/mkv/webm)와 설명 문서(md/txt/docx)를 올립니다. 문서는 없어도 되지만, 있으면 자막이 문서 내용에 근거하게 됩니다.
  2. 편집 지시 — 원하는 방향을 한국어로 씁니다. 이 지시가 배속 기준보다 우선합니다.
  3. 분석 시작 — 구간 나누기 → 화면 읽기 → 자막·배속 (3분 영상 기준 약 45초).
  4. 타임라인에서 확인 — 블록 폭은 편집본에서 차지하는 길이, 색은 배속입니다. 느릴수록 차갑고 빠를수록 뜨겁습니다.
  5. 고치기 — 구간 목록에서 자막·배속·강조를 직접 고치거나, 오른쪽 대화창에 말합니다.
  6. 영상 만들기 — MP4 · SRT · 작업 파일 JSON을 내려받습니다.

프로젝트 다루기

프로젝트 하나가 영상 하나입니다. 화면 위쪽 가운데의 프로젝트 이름을 누르면 목록이 열리고, 맨 위 + 새 프로젝트 로 다른 영상을 시작합니다. (이미 분석된 프로젝트에 다른 영상을 끌어다 놓아도 새 프로젝트로 넘어갑니다. 이전 프로젝트는 목록에 그대로 남습니다.)

제목은 올린 파일명에서 따오므로 대개 고치게 됩니다. 이름 옆 연필 버튼을 누르거나 제목을 두 번 누르면 바로 고칠 수 있습니다. Enter로 저장, Esc로 취소합니다.

산출물

프로젝트 하나가 폴더 하나입니다. 통째로 복사해 넘길 수 있습니다.

data/<프로젝트 id>/
  source.mp4          올린 원본
  document.txt        설명 문서에서 뽑은 텍스트
  workfile.json       작업 파일 ← 원본(source of truth)
  thumbs/seg-N.jpg    구간 썸네일
  out/*_draft.mp4     영상 초안
  out/*.srt           자막
  project.json        제목·상태·대화 기록

작업 파일이 원본이고 SRT·MP4는 파생물입니다. 자막이나 배속만 고친 뒤 STEP 4만 다시 돌리면 되고, JSON을 직접 편집해 되돌려 넣어도 됩니다.

{
  "구간": 9, "시작": 64.09, "종료": 86.31,
  "제목": "브리핑 AI 분석 중",
  "자막": "AI가 핵심 내용을 분석하여 브리핑을 생성 중입니다",
  "배속": 8.0, "배속_이유": "AI 처리 대기 구간, 길이 22초",
  "화면_변화량": 0.02, "확신도": "높음", "사용": true,
  "강조": {
    "종류": "확대+박스",
    "영역": [0.428, 0.811, 0.216, 0.167],
    "배율": 2.4, "색": "#FF3B30",
    "시작": 1.6, "확대": 0.45, "유지": 1.4, "원복": 0.45,
    "이유": "번역 완료 알림", "근거": "A"
  },
  "강조_후보": [
    {"라벨": "A", "영역": [0.428, 0.811, 0.216, 0.167],
     "시각": 100.5, "세기": 159.4, "면적비": 0.028}
  ]
}

강조의 **영역은 정규화 좌표(0~1)**입니다. 원본이 4K여도 렌더는 1080p일 수 있고, UI는 320px 썸네일 위에 같은 값을 얹기 때문입니다. 타이밍은 출력 초로, 배속이 적용된 뒤 그 구간이 최종 영상에서 시작하는 시점 기준입니다. 강조_후보를 함께 저장해 두어야 나중에 대화로 고칠 때 영상을 다시 분석하지 않습니다.

강조 — 확대와 붉은 박스

중요한 부분을 확대하거나 색 테두리로 짚어 줍니다. 둘 다 걸 수도 있습니다. 확대는 부드럽게 들어갔다가 원래대로 돌아옵니다.

좌표는 AI에게 묻지 않습니다

시간 경계와 같은 이유입니다. 모델에 보내는 프레임은 900px로 줄인 JPEG이라 좌표계가 모호하고, 한 구간에 여러 장을 보내면 장마다 답이 달라져 박스가 떨립니다. 무엇보다 프로그램이 검증할 수 없습니다(0.42, 0.31)이 정말 저장 버튼인지 확인할 방법이 없으니까요.

그래서 이렇게 나눕니다.

  1. 프로그램이 후보를 찾습니다. STEP 1이 만드는 프레임 차이 지도를 구간별로 누적해, 화면 일부만 바뀐 자리를 찾습니다. 변화량이 큰 지점은 후보가 아닙니다 — 그건 전부 화면 전체 전환이고 이미 구간 경계로 쓰고 있습니다. 기준은 크기가 아니라 공간적 집중도입니다.
  2. 후보를 화면에 그려서 보여줍니다. A, B 같은 라벨을 붙인 네모를 대표 프레임 위에 그려 AI에게 보냅니다.
  3. AI는 라벨만 고릅니다. 답이 프로그램이 계산한 값 중 하나로 제한되므로 검증할 수 있고, 좌표가 흔들리지 않습니다.

프로그램은 "어디가 바뀌었나"까지만 알 수 있고 "그래서 뭐가 중요한가"는 모릅니다. 실제로 어떤 화면에서 국소 변화 1등은 작업표시줄 미리보기 팝업이었고, 정작 사람 눈에 띄는 다운로드 알림은 순위 밖이었습니다. 그 판단이 AI 몫이고, 이게 후보를 여럿 내놓는 이유입니다.

기본은 「강조 없음」

강조는 아껴 써야 하는 장치입니다. 모든 구간에 박스가 뜨면 아무것도 강조되지 않은 것과 같습니다. 그래서 AI에게 고르지 않는 쪽을 기본으로 알려주고, 전체 구간의 40%를 상한으로 둡니다.

렌더 — 필터 순서가 전부입니다

setpts(배속) → fps → drawbox(박스) → zoompan(확대) → subtitles → scale → setsar
  • 강조는 배속 뒤에. 앞에 두면 8배속 구간에서 확대가 순식간에 지나갑니다. 뒤에 두면 타이밍이 최종 영상 기준이 되어 배속과 무관하게 일정합니다. 그래서 작업 파일의 강조 타이밍도 출력 초로 저장합니다.
  • fps를 끼우는 이유. 확대 곡선은 프레임 번호로 움직입니다. setpts만 써서는 프레임이 인코더 단에서 버려져 곡선과 실제 출력이 어긋납니다.
  • 자막보다 앞에. 뒤에 두면 자막이 함께 확대되거나 잘립니다.
  • 확대는 zoompan으로 하고 출력 크기를 못박습니다. 조각들의 해상도가 항상 같아야 concat -c copy가 안전합니다. 해상도가 다르면 concat은 경고 한 줄 없이 통과시킨 뒤 조용히 깨진 영상을 만듭니다.

배율은 1.0~2.5배입니다. 1080p에서 2.5배를 넘기면 눈에 띄게 뭉개지므로, 그보다 작은 영역은 확대 대신 박스로 갑니다. 화면 폭을 꽉 채우는 띠 같은 영역도 확대해 봐야 티가 안 나서 자동으로 박스로 낮춥니다.

고치기

구간 목록에서 강조 방식·위치·색을 직접 고르거나, 대화로 시킵니다. 썸네일 위에 강조 영역이 그대로 표시되므로 렌더 전에 눈으로 확인할 수 있습니다.

"3번 구간 저장 버튼에 박스 쳐줘"
"5번은 확대까지 해줘"
"강조 색을 파란색으로"
"2번 강조 빼줘"

배속

AI가 고를 수 있는 값은 0.5 / 0.75 / 1 / 1.25 / 1.5 / 2 / 3 / 4 / 6 / 8 / 12 / 16 입니다. 그 밖의 값은 가장 가까운 눈금으로 스냅합니다(사람이 직접 지정할 때는 스냅 없이 0.25~20 사이 아무 값이나 됩니다).

  • 5초 이하 구간은 원속 — 더 빠르면 눈으로 따라갈 수 없습니다.
  • 2배속을 넘는 구간은 소리를 죽입니다(빨리감기 소리가 거슬립니다).
  • 「AI가 배속 결정」을 끄면 화면 변화량 규칙만 씁니다.

렌더 방식

거대한 filter_complex 하나 대신 구간별로 짧게 인코딩한 뒤 concat 합니다. 구간이 수십 개인 영상에서 입력 분기가 폭증하는 것을 피하고, 구간 단위로 진행률을 보고하며, 실패 지점이 바로 드러납니다. 자막은 구간 조각에 곧바로 입혀 전체를 두 번 인코딩하지 않습니다.

3분(구간 14개) 영상 기준 분석 약 45초, 렌더 약 20초입니다.

API

GET /api/bootstrap 모델·자막 스타일·배속 눈금·프로젝트 목록
POST /api/projects 영상·문서 업로드 후 분석 시작 (multipart)
GET /api/projects/{id} 전체 상태 (프로젝트·작업 파일·작업·결과물)
GET /api/projects/{id}/events 진행 상황 SSE
PATCH /api/projects/{id}/segments/{n} 자막·배속·사용 여부·강조 수정
POST /api/projects/{id}/chat 대화형 편집
POST /api/projects/{id}/render 영상 만들기
POST /api/projects/{id}/reanalyze 편집 지시를 바꿔 다시 분석
GET /api/projects/{id}/download/{mp4|srt|workfile} 내려받기

한계

  • 구간 경계는 프로그램이 정합니다. 대화로 구간을 새로 만들거나 합칠 수 없습니다. 필요 없는 구간은 끄는 방식으로 뺍니다.
  • 강조 위치도 프로그램이 찾은 후보 중에서만 고를 수 있습니다. 화면에서 변화가 없었던 자리는 후보로 잡히지 않아 지정할 수 없습니다. (임의 영역을 직접 그리는 UI는 아직 없고, API로는 region에 정규화 좌표를 직접 넣을 수 있습니다.)
  • 진행 상황은 메모리에 있습니다. 작업 중 서버를 다시 띄우면 그 작업은 끊깁니다(이미 저장된 결과물은 그대로입니다).
  • 동시 편집은 고려하지 않았습니다. 프로젝트 하나에 작업 하나만 돕니다.