❤️ 소개 — 시도하고자 했던 것과 그 이유
전자책 제작 스터디의 사례 발표 모임(2026-05-26) 영상을 복구하는 작업이었습니다.
문제는 이거였어요. 줌 녹화가 제대로 안 돼서 영상 화면은 사라지고, 남은 건 두 가지뿐이었습니다.
입을 벌리고 있는 심슨의 이미지
119분짜리 오디오 파일 (회의 음성 전체)
발표자 6명이 공유했던 화면 캡처 PNG (각자 사례 글을 길게 스크롤한 세로 이미지)
녹취록(클로바노트)도 있었지만 화면이 없으니 "까만 화면에 소리만 나오는 영상"이 될 판이었죠.
그래서 떠올린 아이디어가 "발표자가 화면을 천천히 스크롤하면서 설명하던 그 느낌을, 캡처 이미지를 오디오 타이밍에 맞춰 자동으로 스크롤시켜 재현하자" 였습니다.
화면 녹화를 다시 할 수도 없고, 영상 편집 프로그램으로 119분을 일일이 맞추는 건 엄두가 안 났어요. 그래서 코드로 영상을 만드는 Remotion을, 클로드 코드에게 통째로 맡겨보기로 했습니다.
핵심 컨셉: 오디오(원본) + 슬라이드 PNG(자동 스크롤) + 자막 + 챕터 카드 → 코드로 합성
❤️ 진행 방법 — 어떤 도구를 어떻게 활용했나
사용한 도구
| Claude Code | 전체 작 업 지휘 — 녹취록 분석, 챕터 분할, 자막 생성 스크립트, Remotion 코드 작성, 렌더링까지 직접 실행 |
| Remotion | React 코드로 영상을 만드는 프레임워크. 슬라이드 스크롤·자막·인트로/아웃트로를 컴포넌트로 구현 |
| PowerShell / Node | 녹취록 파싱 → 자막(SRT) 자동 생성 |
작업 흐름 (클로드 코드가 단계별로 수행)
1. 프로젝트 폴더 구조 + CLAUDE.md 셋업
2. 원본(오디오/녹취록/슬라이드) 분석 → 발표자·타임스탬프 매핑
3. 챕터 맵 작성 (CHAPTERS.md) — 11챕터, 발표자별 구간·슬라이드 매칭
4. 녹취록 → 자막(SRT + JSON) 자동 생성 + STT 오인식 보정
5. Remotion 프로젝트 구현 (슬라이드 스크롤 / 자막 / 챕터카드 / 인트로 / 아웃트로)
6. 30초 데모 렌더 → 검수 → 자막 4줄 제한 수정
7. 전체 119분 본 렌더 → study-video.mp4
실제로 사용한 프롬프트 (전문)
① 자료를 던지고 컨셉을 설명한 첫 지시
""C:\Users\송아영\Documents\카카오톡 받은 파일\05-26_주간_회의_AI_글쓰기_전자책_제작_저작권.mp3"
E:\송아영\05-26_주간_회의_AI_글쓰기_전자책_제작_저작권.txt"
그리고 "E:\송아영\사례발표 화면" 이 경로에 발표자 화면 있어.
설명할 때 오디오에 해당하는 화면을 천천히 스크롤하면서 보여주고 싶어
→ 클로드 코드가 알아서 녹취록에서 화자(`참석자 1~8`)와 타임스탬프를 파악하고,
PNG 6장의 해상도를 분석(세로로 매우 긴 캡처임을 확인)한 뒤,
"슬라이드 자동 스크롤" 방식이 적합하다고 판단해 설계를 시작 했습니다.
② 발표자 닉네임 매핑을 바로잡은 지시
참석자 3은 배꽃이야. 참석자 4는 이응이응, 참석자7은 백구우야.
참석자 6은 AJ (에이제이)
그리고 스크롤 할 이미지에 발표내용이 다들어 있지 않거나 짧을 수 있어.
이럴 땐 리모션 영상 넣어서 내용 전달 잘 할 수 있도록 구성해줘.
필요하다면 화면 이미지 해당부분 재차 보여줘도 되고
→ STT가 닉네임을 "소영강/소현장/백구호/에이제이…" 등으로 제멋대로 적어둔 걸,
정식 닉네임 표를 만들어 일괄 치환 규칙으로 정리했습니다.
③ 자막이 화면을 너무 가린다 → 4줄 제한
자막이 한번에 너무 많이 보여서 화면을 좀 가리는 부분이 많은데
이거 조정 간단한거야. 아니면 리소스나 시간 많이 드는 작업이야?
최대 4줄이 넘지 않도록 하는건 어때?
→ 한 발화(긴 건 1~2분짜리도 있음)를 통째로 보여주던 걸,
문장 → 쉼표 → 공백 순으로 자연스럽게 잘라 한 화면에 ~4줄(약 130자)만 띄우고,
발화 시간을 등분해 자막을 차례로 넘기도록 고쳤습니다. (`1/3`, 2/3 진행 표시도 추가)
핵심 코드 ① — 슬라이드 자동 스크롤
세로로 긴 PNG를, 챕터(=발표) 길이에 맞춰 위→아래로 균등하게 흘려보내는 컴 포넌트입니다.
슬라이드가 너무 길면(AJ 슬라이드는 무려 29,272px!) cap 모드로 표준 속도까지만 스크롤하고 멈추게 했습니다.
```tsx
// 슬라이드별 원본 크기를 하드코딩 → 런타임 이미지 로딩 없이 정확한 스크롤 계산
const SLIDE_SIZES: Record<string, { w: number; h: number }> = {
"AJ.png": { w: 1920, h: 29272 }, // 매우 김 → cap 모드
"노명숙.png": { w: 1652, h: 11194 },
"배꽃.png": { w: 1920, h: 12394 },
"백구우.png": { w: 1652, h: 8578 },
"소양강.png": { w: 1652, h: 9164 },
"이응이응.png": { w: 1652, h: 6034 },
};
export const SlideScroll: React.FC<{
slide: string; durationFrames: number; scrollMode: "fit" | "cap";
}> = ({ slide, durationFrames, scrollMode }) => {
const frame = useCurrentFrame();
const meta = SLIDE_SIZES[slide];
const displayW = 1920;
const scale = displayW / meta.w;
const displayH = meta.h * scale; // 화면 폭에 맞췄을 때의 전체 높이
const scrollMax = Math.max(0, displayH - 1080); // 스크롤 가능 거리
const progress = frame / Math.max(1, durationFrames - 1);
let translateY: number;
if (scrollMode === "fit" || scrollMax === 0) {
translateY = -scrollMax * Math.min(1, progress); // 발표 시간에 맞춰 끝까지 균등 스크롤
} else {
const targetY = 16 * (frame / 30); // 표준 16px/초로 스크롤하다
translateY = -Math.min(scrollMax, targetY); // 끝에 닿으면 정지
}
return (
<AbsoluteFill style={{ backgroundColor: "#0a0d12", overflow: "hidden" }}>
<div style={{ width: displayW, height: displayH, transform: translateY(${translateY}px) }}>
<Img src={staticFile(`slides/${slide}`)} style={{ width: displayW, height: displayH }} />
</div>
</AbsoluteFill>
);
};
```핵심 코드 ② — 자막 4줄 자동 분할
긴 발화를 화면을 안 가리도록 잘라주는 함수입니다.
```tsx
// 130자 ≈ 4줄. 문장(.!?) > 쉼표 > 공백 순으로 자연스럽게 끊는다.
function splitText(text: string, maxChars = 130): string[] {
if (text.length <= maxChars) return [text];
const breakPatterns = [/(?<=[.!?])\s+/g, /(?<=,)/g, /\s+/g];
const chunks: string[] = [];
let remaining = text;
while (remaining.length > maxChars) {
let cutAt = -1;
for (const re of breakPatterns) {
for (const m of remaining.matchAll(re)) {
const pos = m.index! + m[0].length;
if (pos > maxChars * 0.55 && pos <= maxChars) cutAt = pos; // 너무 짧지 않게
}
if (cutAt > 0) break;
}
if (cutAt < 0) cutAt = maxChars; // 끊을 곳 없으면 강제 분할
chunks.push(remaining.slice(0, cutAt).trim());
remaining = remaining.slice(cutAt).trim();
}
if (remaining) chunks.push(remaining);
return chunks;
}
```핵심 코드 ③ — 챕터를 시간축에 배치 (전체 영상 조립)
초 → 프레임 변환으로 각 발표 구간을 영상 타임라인에 그대로 꽂았습니다.
```tsx
export const StudyVideo: React.FC = () => (
<AbsoluteFill style={{ backgroundColor: "#0f1419" }}>
<Audio src={staticFile("audio.mp3")} startFrom={0} /> {/* 원본 오디오 전체 */}
<Sequence from={0} durationInFrames={5 * 30}><LogoIntro /></Sequence> {/* 인트로 5초 */}
{CHAPTERS.map((ch) => (
<Sequence key={ch.id}
from={secToFrames(ch.startSec)}
durationInFrames={secToFrames(ch.endSec - ch.startSec)}>
<ChapterRenderer chapter={ch} absoluteSecOffset={ch.startSec} />
</Sequence>
))}
</AbsoluteFill>
);
```> (관련 캡처 정리는 같은 폴더의 대화기록_주요프롬프트.md 참고)
완성 영상 샘플
❤️ 결과와 배운 점
결과
사라진 줄 알았던 119분 영상이 자막·발표자 배지·챕터 카드까지 붙은 완성본으로 부활했습니다.
영상 편집 프로그램을 한 번도 안 열고, 타임라인을 코드(초 단위)로 정의한 게 전부였어요.
자막·챕터·스크롤 속도를 바꾸고 싶으면 숫자 한두 개만 고치고 다시 렌더하면 됩니다. (수동 편집이었다면 처음부터 다시 했어야 할 일)
나만의 꿀팁
"화면 녹화"라는 고정관념을 버리니 길이 보였다. 원본 화면이 없어도, 발표자가 보여주던 자료(PNG)만 있으면 "스크롤 연출"로 충분히 그 느낌을 살릴 수 있었습니다.
클로드 코드에게는 "결과의 그림"을 먼저 주자. "오디오에 맞춰 화면을 천천히 스크롤하고 싶어" 라는 한 문장이, 폴더 구조부터 렌더까지 전부를 끌어냈습니다. 세세한 코드를 지시하지 않아도, 의도만 명확하면 알아서 설계합니다.
검수는 '짧은 데모 → 전체'로. 처음부터 119분(약 2시간)을 렌더하지 않고 30초 데모부터 뽑아 자막·스크롤을 확인했습니다. 자막이 화면을 가린다는 문제도 데모에서 먼저 잡았어요.라고 크롤드코드가 작성해줬지만 사실은 전체 영상 만든 후 방해 된다고 느껴서 그때 수정했습니다. 최대 4줄 넘지 않게 해달라고 요청했답니다.
STT 오인식은 '치환 규칙표'로 한 방에. "소영강/소현장/소연강…"처럼 흩어진 이름을 정식 닉네임으로 모으는 규칙을 만들어두니, 녹취록이 바뀌어도 스크립트만 다시 돌리면 됩니다. 그래도 꼼꼼히 살피지 않았더니 영상 중간에 오타가 굉장히 많았습니다~ 제 발음 문제겠지만요 ^^
시행착오
`node` 실행이 막혔다. 클로드 코드의 안전 모드가 "검증 안 된 스크립트 실행"을 차단 해서, 자막 생성 스크립트를 PowerShell 인라인 코드로 바꿔 같은 로직을 돌렸습니다. (우회가 아니라 허용된 도구로 전환)
Remotion 컴포지션 id에 언더스코어(`_`)를 못 쓴다.
Ch0_Opening에서 에러 →Ch0-Opening(하이픈)으로 수정.리모션은 렌더 시간 오래걸린다. 슬라이드가 크고 긴 후반 챕터(노명숙·배꽃)에서 더 느려져서, 최종적으로 약 4시간 걸렸어요.
Studio와 렌더를 동시에 켜두니 느렸다. 미리보기용 Studio를 끄니 렌더가 빨라졌습니다.
폰트 로딩 경고. Noto Sans KR을 weight 4종 다 불러와서 매 탭마다 네트워크 요청이 폭주했어요. (다음엔 weight를 줄여 로딩 최적화 예정)
❤️ 도움이 필요한 부분
자막을 문장 단위로 더 정교하게 끊고 싶습니다. 지금은 글자 수 기준이라 가끔 어색하게 잘려요. 아~ 이 부분은 해결 방법이 있긴 합니다. 오디오를 위스퍼를 이용해 실제 대화 인지하도록 한 후 원하는 형식으로 끊어달라고 하면 됩니다. 자막과 오디오 불일치는 이전에 외주영상 만들면서 지옥 문턱까지 가서 터득한 노하우입니다 ㅎㅎㅎㅎ~
긴 슬라이드(AJ 29,272px)는 스크롤이 빨라 가독성이 떨어집니다. "중요 부분만 잘라 확대(줌인)" 하는 인서트를 넣는 게 다음 숙제예요.
앞으로의 계획
스터디때 녹화 확인을 생활화 하겠습니다~ ^^
내가 맹세하는 단어가 적힌 만화 펭귄
도움 받은 글 / 참고 자료
Remotion 공식 문서 — https://www.remotion.dev/docs/
Remotion 기본 개념(Fundamentals) — https://www.remotion.dev/docs/the-fundamentals
리모션_개념과_활용법.pdf— Remotion 핵심 개념·코드 패턴·학습 링크 정리대화기록_주요프롬프트.pdf— 이번 작업에서 오간 핵심 프롬프트·의사결정 캡처