Remotion으로 나만의 동적 자막 비디오 시스템 만들기
– ArtXTech
요즘 영상 편집에 부쩍 관심이 많아졌는데, 특히 자막 넣는 게 은근 노가다이다.
그래서 “이거 좀 자동으로 할 수 없을까?” 하다가 Whisper 모델 디펜던시가 오픈소스로 풀린 걸 최근 발견했다. 여기에 혹시 Remotion을 접목하면 어떨까? 프론트엔드 개발자로서 이건 못 참지! 싶어서 바로 프로젝트에 뛰어들었다.
SRT 파일만 딱 던져주면 알아서 타이핑 애니메이션 뙇! 그리드 효과 뙇! 들어간 키네틱 타이포 애니메이션을 구현해보고자 했다 일명 ‘동적 자막 비디오 생성 시스템’이라고나 할까?
1단계: 일단 핵심 부품부터 🛠️
모든 프로젝트가 그렇듯, 가장 중요한 건 핵심 기능을 하는 부품들, 그러니까 컴포넌트들부터 만들어보자.
이런 컴포넌트들이 잘 돌아가려면 뒤에서 궂은일 해주는 헬퍼 함수들도 필요하다.
-
SRT 파서:
.srt자막 파일, 이걸 시스템이 알아먹을 수 있게 파싱하고 처리하는 모듈을 만들었다. - subtitleHelpers: 자막이 정확한 타이밍에 뿅! 하고 나타났다가 사라져야 하는데, 그 계산을 도와주는 유틸리티 함수들이다.
그래서 이 컴포넌트랑 유틸리티들을 가지고 실제로 프로젝트를 진행해 영상을 만들어 보았다.
- YT 비디오 프로젝트 (HowIProgressed): 이건 주로 22-24년 동안 내가 어떻게 성장했는지 뭐 그런 회고복기 영상이다.
2단계: 만들다 보니 점점 확고해지는 방향성
몇 날 며칠 키보드 두들기다 보니, 나름대로 아키텍처에 대한 감이 잡힌다.
- 컴포넌트 기반 설계: 역시 React의 꽃은 컴포넌트! 재사용 가능한 모듈식 구조로 만드니까 여기저기 갖다 쓰기도 편하고, 관리도 쉬워졌다. 레고 블록 쌓는 느낌.
-
타입 안전성: TypeScript를 썼는데, 사실…생산성이란 측면에서 여전히 현 내 프로젝트에 이걸 고집하는 게 맞는가 의문이 들지만…뭐 나중에 미디어 파싱 때 타입 시스템 덕분에 개발 단계에서 오류를 미리 잡는다 생각하며 계속 유지구축 중이다., 코드도 더 안정적으로 만들 수 있으리라. (물론
any파티를 안 했다는 건 아니지만… 읍읍!)
기술적으로도 몇 가지 배운 점:
- 프레임 기반 애니메이션: Remotion은 프레임 단위로 애니메이션을 제어한다. 처음엔 좀 낯설었고 영상 비디오툴처럼 여러 비디오를 동적으로 시시 때때로 내가 원하는 프레임 타이밍에 재생 길이를 넣으며 총 프레임수를 칼 같이 맞추는 덴 아직 무리가 있을지도.
-
상태 관리: 애니메이션 상태나 프레임 같은 걸 추적할 때는
useRef가 은근 했다. 리렌더링 없이 값을 유지해야 할 때.
3단계: 개발 중 맞닥뜨린 예상치 못한 이슈들 🤬
[주요 이슈들 - 현재 위치]
개발 과정이 항상 순탄하지만은 않았다. 왜 한번도 순탄한 적이 없을까? 이럴 땐 개발자 그만두길 잘 했다…싶기도. 해결하느라 머리 좀 뜯었다.
- 컴포넌트 재생성 문제: 자막 내용이 바뀔 때마다 해당 자막 컴포넌트가 새로 뿅! 하고 나타나야 하는데, 이놈이 꿈쩍도 안 한다. 분명 props는 바뀌었는데 왜 리렌더링이 안 되냐고!
[해결책들 - 현재 위치]
그래서 결국 이렇게 해결했다! 휴… 역시 난 좀 쩌는 듯?
-
자막 애니메이션 재시작 해결:
TypewriterSubtitle컴포넌트에subtitleIndex라는 prop을 새로 추가했다. 그리고useRef를 써서 이전subtitleIndex랑 현재subtitleIndex를 비교.
// [메모 A: subtitleIndex prop 추가]
const TypewriterSubtitle = ({ subtitleIndex, text, startFrame, endFrame, ...rest }) => {
const frame = useCurrentFrame();
// [메모 B: 시작 프레임 추적용 ref]
const startFrameRef = useRef(frame);
// [메모 C: 이전 자막 인덱스 추적용 ref]
const prevSubtitleIndexRef = useRef(subtitleIndex);
// [메모 D: 자막 변경 감지 로직]
// 자막 인덱스가 바뀌었다? 그럼 이건 새로운 자막
// 애니메이션 시작 프레임을 현재 프레임으로 리셋.
if (subtitleIndex !== prevSubtitleIndexRef.current) {
startFrameRef.current = frame; // 여기서 리셋
prevSubtitleIndexRef.current = subtitleIndex; // 현재 인덱스로 업데이트
}
// 실제 애니메이션 로직은 startFrameRef.current 기준으로 돌아감
const typingProgress = Math.max(0, frame - startFrameRef.current) / (endFrame - startFrameRef.current);
// (이하 타이핑 애니메이션 로직 생략)
return (
<div style={{ opacity: typingProgress > 0 ? 1 : 0 }}>
{/* 타이핑 효과가 적용된 텍스트 */}
</div>
);
};
4단계: 그래서 내가 만들려는 궁극적인 시스템
[최종 목표 > 시스템 목표 - 현재 위치]
이런 해결 과정을 거쳐서 내가 꿈꾸는 시스템은 이런 모습이다.
- 동적 자막 비디오 생성: 그냥 SRT 파일 던져주고, 몇 가지 옵션만 설정하면 자막 애니메이션이 위스퍼를 통해 추출한 자막 타이밍 지시에 따라 자동으로 영상으로 렌더링
- 다양한 컨텐츠 지원: 교육 영상, 댄스 커버 같은 엔터테인먼트 영상, 심지어 인터뷰나 토론 같은 대화형 컨텐츠까지 어떤 종류의 영상이든 착붙 자막을 제공
- 시각적 매력도: 단순히 글자만 나오는 자막대신 추가로 타이핑 애니메이션, 키네틱 그리드 효과, 하프톤 셰이더 같은 시각적인 효과들까지 추가할 예정이다.
[최종 목표 > 기술적 목표 - 현재 위치]
기술적으로도 몇 가지 아쉬운 점은…
- 성능 최적화: 프레임 드롭 없이 부드럽게 돌아가는, 최적화된 시스템은 아직 갈 길이 멀다.
- 확장성: 나중에 또 새로운 컨텐츠 타입이나 자막 효과를 추가하고 싶을 때, 큰 공수 안 들이고 쉽게 확장할 수 있도록 유연한 구조 갖추기
- 유지보수성: 코드는 내가 짜고 내가 까먹는 게 국룰 (ㅋㅋ) 그러니 명확하게 컴포넌트 분리하고, TypeScript로 타입 챙겨서 나중에 봐도 “아, 내가 이땐 이런 생각으로 짰구나!” 하고 이해하기 쉽게 만들기…하지만 언제 이걸 다 챙길까나.
5단계: 시스템 구조
SRT 파일이 들어오면 parseSrt가 자막 객체 배열로 만들고, 컴포넌트에 넘겨주는 흐름이다. 오디오/비디오 원본이랑 합쳐져서 Remotion Composition을 통해 최종 비디오가 렌더링 된다.
데이터가 대충 어떻게 흘러가는지 ASCII 버젼으로 대충 그려보았다.
[시스템 구조 다이어그램 > 프로젝트 파일 구조 - 현재 위치]
궁금해할 사람들을 위해 (사실은 나중에 내가 보려고..크흠) 프로젝트 파일 구조도 여기 남겨둔다.
VideoEditPlayground/
├── src/remotion/
│ ├── _Shared/ # 여러 프로젝트에서 같이 쓰는 공용 모듈들
│ │ ├── Typography/
│ │ ├── Layout/
│ │ ├── Helper/
│ │ │ ├── parseSrt.ts # SRT 파싱 로직
│ │ │ └── subtitleHelpers.ts # 자막 관련 헬퍼 함수들
│ ├── _YT/ # 유튜브 컨텐츠용 프로젝트 폴더
│ │ ├── HowIProgressed/ # '내가 어떻게 성장했나' 시리즈 (예시)
│ │ │ ├── YTep1.tsx # 에피소드 1 컴포지션
│ │ │ ├── core/contents-srt.tsx # 자막 데이터 정의
│ │ │ └── contents/contentSrt.tsx # (구조 리팩토링 중...)
│ └── schema.ts # Remotion 입력 props 타입 정의
` ‘Shared’ 폴더에 재사용 가능한 컴포넌트, 훅, 헬퍼들을 몰아넣고, 각 프로젝트 타입 별로 폴더를 나눠서 관리하고 있다.
아무튼 이렇게 Remotion 가지고 동적 자막 시스템을 만들어보았다.
처음엔 “이거 금방 만들겠는데?” 싶었다. 그도 그럴게 리모션에서 틱톡 팟캐스트라며 위스퍼 모듈을 끌어와 자동 자막을 생성하는 예제가 있어 걔만 손 대면 어케 잘 될거라 생각해서이다. 그런데 그 별도 리포지토리만 돌릴 땐 잘 돌아가는 예제가 내 소스코드 플레이 그라운드에 접목시키면 희안하게 안 돌아가더라. 그 샘플 예제로는 뭔가 미흡한 점이 많아 여러모로 커스텀을 하려 했건만…그래서 결국 별도로 내가 따로 자막 시스템을 빌드하게 되었다.
앞으로 이 시스템을 어떻게 더 발전시켜 나갈지, 또 어떤 새로운 포맷팅을 컨텐츠 제작을 위해 개발할 지 기대…는 사실 안되지만 여튼, 이렇게 리모션을 통해 유튜브 비디오를 만들게 되어 참 감회가 새롭다.
다른 리모션 포스팅을 보고 싶다면
2025-01-16-implementing-canvas-animation-to-remotion-kr
2025-03-18-making-my-own-video-editor-in-progress
KR-no-more-adobe-starting-remotion-kr
KR-remotion-gsap
KR-making-chart-data-journalism-storytelling-motion-setup-with-remotion
KR-figma-to-jitter-and-remotion
KR-converting-bodymovin-animation-to-remotion-template
KR-how-i-make-a-youtube-series-with-remotion-dynamic-srt-script-setup-exp-likejennie
KR-how-i-made-podcast-interview-with-ai-virtual-idol
KR-remotion-and-shader
KR-thepain-of-animation-debugging