VidTSX 2D 비디오 생성기은(는) 무엇을 하나요?
샷 또는 비디오 설명으로부터 VidTSX용 프로덕션 준비 완료된 2D TSX 비디오 파일을 생성합니다. 이 파일들은 VidTSX가 프레임별로 렌더링하는 Remotion 컴포지션입니다. 따라서 독창성보다 정확성이 더 중요합니다. 단일 비단조 interpolate 범위나 잘못된 useState 하나가 렌더를 중단시킬 수 있습니다.
이 스킬은 2D 모션 그래픽을 다룹니다. 3D 작업(@remotion/three / React Three Fiber)은 범위를 벗어납니다.
이 스킬 사용 방법
- 명확한 단일 샷 →
.tsx파일을 직접 생성합니다. - 복잡하거나, 여러 샷이 있거나, 시각적으로 모호한 요청 → 먼저 짧은 메시지 하나로 샷 분할이나 시각적 방향을 확인한 후 생성합니다. 지나치게 묻지 않으며, 한 줄 확인으로 충분합니다.
- 인라인 코드가 아닌 실제
.tsx파일로 전달합니다. 이러한 TSX 구성 요소는 항상 20줄을 초과하며, 사용자는 이 파일을 직접 편집합니다. 사용자가 단일 결합 전달물을 요청하지 않는 한, 기본적으로 파일당 하나의 샷 (원자적이고 편집하기 쉬움)으로 제공합니다. - 파일은 컴포넌트 코드만 포함합니다. 아래 구조적 섹션 주석은 환영하지만, 파일 내부에 마크다운 펜스나 산문 설명은 포함하지 않습니다.
- 렌더링을 수행하고 완료되었다고 말하기 전에 확인합니다 — 렌더 검증을 참조하세요. 추론만 한 샷은 완료된 것이 아닙니다. 스틸 이미지(그리고 공개/스크롤의 경우 각 큐에서의 프레임)가 기준입니다.
출력 사양
화면 비율 프리셋
| 형식 | 너비 | 높이 | 사용 사례 |
|---|---|---|---|
| 가로 | 1920 | 1080 | YouTube, 프레젠테이션 |
| 세로 | 1080 | 1920 | TikTok, Reels, Shorts |
| 정사각형 | 1080 | 1080 | Instagram 피드 |
기본값
- 형식: 가로 (1920×1080)
- 길이: 5초
- FPS: 30
- 스타일: 미니멀리스트 (
references/style-presets.md참조)
필수 파일 구조
모든 생성된 파일은 다음 골격을 따릅니다. 레이블이 지정된 섹션 주석은 의도적인 것으로, 대용량 파일을 탐색 가능하게 유지합니다.
import React from 'react';
import {
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
AbsoluteFill,
Sequence,
} from 'remotion';
// =============================================================================
// 컴포지션 설정
// =============================================================================
export const compositionConfig = {
id: 'ComponentName', // PascalCase만 — 하이픈이나 밑줄 없음
durationInSeconds: 5,
fps: 30,
width: 1920,
height: 1080,
};
// =============================================================================
// 스타일 상수
// =============================================================================
const COLORS = {
primary: '#6366f1',
secondary: '#8b5cf6',
accent: '#06b6d4',
background: '#0f0f23',
text: '#ffffff',
} as const;
const TYPOGRAPHY = {
fontFamily: 'Inter, system-ui, sans-serif',
} as const;
const EASINGS = {
easeOut: Easing.bezier(0.33, 1, 0.68, 1),
easeIn: Easing.bezier(0.32, 0, 0.67, 0),
easeInOut: Easing.bezier(0.37, 0, 0.63, 1),
overshoot: Easing.bezier(0.34, 1.56, 0.64, 1),
} as const;
// =============================================================================
// 사전 생성 데이터 (모듈 수준에서 한 번 계산, 렌더 중에는 계산 안 함)
// =============================================================================
const seededRandom = (seed: number): number => {
const x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
};
// =============================================================================
// 메인 컴포넌트
// =============================================================================
const ComponentName: React.FC = () => {
const frame = useCurrentFrame();
const { fps, durationInFrames, width, height } = useVideoConfig();
return (
<AbsoluteFill style={{ backgroundColor: COLORS.background }}>
{/* 콘텐츠 */}
</AbsoluteFill>
);
};
export default ComponentName;
ComponentName을 샷을 설명하는 PascalCase 이름으로 바꾸고, 선택한 스타일 프리셋에서 COLORS 블록을 설정하고, 요청에 따라 width/height/durationInSeconds/fps를 설정합니다.
정확성 규칙
이것들은 스타일 선호도가 아닙니다. 렌더가 성공하도록 하는 요소입니다. VidTSX(Remotion을 통해)는 각 프레임을 고정된 프레임 번호에서 컴포넌트를 호출하여 렌더링합니다. 이벤트 루프, 프레임 간 지속성, 또는 벽시계 시간이 없습니다. 이러한 것을 가정하는 모든 것은 실패합니다.
애니메이션은 항상 프레임 기반입니다.
- 모든 모션을
useCurrentFrame()+interpolate()로 구동합니다. useState,useEffect,setTimeout,setInterval또는 CSS 애니메이션/전환을 절대 사용하지 마세요. 이들은 프레임별 모델에 맞지 않으며 깨지거나 비결정적인 출력을 생성합니다.- 무작위 또는 절차적 작업에는
seededRandom(또는 이와 동등한 시드 함수)을 사용하여 주어진 프레임의 모든 렌더가 동일하도록 합니다. 파티클/랜덤 배열을 모듈 수준에서 사전 생성하고, 렌더 함수 내부에서는 절대 생성하지 마십시오. 프레임당 재계산하면 깜빡임이 발생합니다. - 요소가 한꺼번에 나타나는 대신 순차적으로 들어오도록 애니메이션 간격을 조정하세요. 더 좋게 보이며 예상되는 모양입니다.
interpolate — 입력 범위는 반드시 엄격하게 증가해야 함
interpolate의 입력 범위는 엄격하게 단조 증가해야 합니다. 중복되거나 내림차순 값은 런타임에 오류를 발생시킵니다.
// ✅ 올바름
interpolate(frame, [0, 30, 60], [0, 1, 0]);
// ❌ 오류 발생 — 입력 범위가 내림차순
interpolate(frame, [60, 30, 0], [0, 1, 0]);
매핑을 반전시키려면 입력 범위가 아닌 출력 범위를 뒤집으세요:
// ✅ 올바름 — 0→100, 1→0 매핑
interpolate(value, [0, 1], [100, 0]);
// ❌ 잘못됨
interpolate(value, [1, 0], [100, 0]);
인덱스 기반 타이밍의 경우 startFrame < endFrame인지 확인하고 항상 클램프하세요:
const startFrame = index * 30;
const endFrame = startFrame + 30;
interpolate(frame, [startFrame, endFrame], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
항상 바인딩되지 않은 값이 정말로 필요한 경우가 아니라면 extrapolateLeft: 'clamp' 및 extrapolateRight: 'clamp'를 전달하세요. 이 값이 없으면 값이 키프레임 전후에 범위를 벗어납니다.
Easing — Easing.bezier()를 사용하고 래퍼 구문을 사용하지 마세요
Easing.out(Easing.cubic)과 같은 래퍼 형식은 충돌을 일으킵니다. 명명된 베지어를 한 번 정의하고(골격의 EASINGS 블록 참조) 참조하세요:
// ❌ 충돌
Easing.out(Easing.cubic);
Easing.in(Easing.quad);
// ✅ 올바름
interpolate(frame, [0, 30], [0, 1], {
easing: EASINGS.easeOut,
extrapolateRight: 'clamp',
});
chroma-js — 네임스페이스 가져오기만 가능
// ✅ 올바름
import * as chroma from 'chroma-js';
const color = chroma('#00bfff').brighten(0.5).hex();
// ❌ 잘못됨 — "chroma is not a function"
import chroma from 'chroma-js';
@remotion/paths — 실제 함수만 존재함
다음 헬퍼는 존재하지 않으며 절대 사용해서는 안 됩니다:
makeCircle(), makeRect(), makeTriangle(), makeLine(), makePie(), makePolygon(), makeEllipse(), makeStar().
다음 가져오기만 유효합니다:
import { evolvePath, getLength, getPointAtLength, getTangentAtLength } from '@remotion/paths';
SVG 경로 문자열을 직접 작성하고 evolvePath로 애니메이션화하세요:
const circlePath = 'M 50 10 A 40 40 0 1 1 49.99 10 Z';
const rectPath = 'M 0 0 L 100 0 L 100 50 L 0 50 Z';
const linePath = 'M 0 0 L 100 100';
const { strokeDasharray, strokeDashoffset } = evolvePath(progress, rectPath);
컴포지션 ID
compositionConfig.id는 PascalCase만 가능합니다 — 하이픈이나 밑줄 없음 (예: ProductReveal, product-reveal 또는 product_reveal 안 됨).
레이아웃
안전 영역
- 상단 10%: 플랫폼 UI용으로 예약.
- 하단 15%: 캡션/버튼용으로 예약.
- 기본 콘텐츠는 세로로 25%–75% 사이에 유지하세요.
중앙 정렬 헬퍼
const centered: React.CSSProperties = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
};
타이포그래피
| 요소 | 크기 | 두께 |
|---|---|---|
| 제목 | 72–120px | 700–900 |
| 부제목 | 36–48px | 500–700 |
| 본문 | 28–36px | 400–500 |
텍스트 요소에는 항상 margin: 0을 설정하세요. 브라우저 기본값이 레이아웃을 중앙에서 벗어나게 밀어냅니다.
스타일 프리셋
이 저장소에서 기본값은 브랜드 스타일이며 프리셋이 아닙니다. 샷은 골격의 로컬 COLORS/TYPOGRAPHY 블록 대신 remotion/src/brand.ts에서 COLORS, GRADIENT, RADIUS, SHADOW, EASINGS를 가져오고 remotion/src/fonts.ts에서 FONT_HEADLINE, FONT_DISPLAY, FONT_BODY, FONT_MONO를 가져오며 brand.md를 따릅니다. FONT_HEADLINE(Instrument Serif)에는 한 가지 두께만 있습니다. 절대 fontWeight를 주지 마세요. 샷은 compositionConfig에 ground: 'night'라고 명시되지 않는 한 낮 바탕에 위치합니다. 키트 구성 요소(lib/shorts.tsx, BrandBg)는 여기서 색상을 가져오므로, 직접 설정하는 모든 색상에 대해 brand.ts에서 useGround()를 읽으세요.
사용자가 스타일을 지정하면 references/style-presets.md 에서 해당 팔레트와 특성을 로드하고 COLORS 객체를 골격에 넣습니다. 여섯 가지 프리셋은: 미니멀리스트, 멤피스, 네오 브루탈리즘, 글래스모피즘, 네온/사이버펑크, 코퍼레이트입니다. 스타일이 지정되지 않은 경우 위의 브랜드 스타일을 사용하세요.
렌더 검증 (건너뛰지 마세요)
샷은 직접 확인하기 전까지 완료된 것이 아닙니다. .tsx를 작성한 후 적어도 하나의 스틸 이미지를 렌더링하고 열어보세요. 추론만 한 샷은 절대 전달하지 마십시오. 렌더링이 성공하고 숫자가 올바르게 보이지만 텍스트가 카드를 넘치거나, 이미지가 잘못된 크롭으로 잘리거나, 요소가 화면 밖에 있거나, 두 요소가 겹칠 수 있습니다. 스크린샷만이 이러한 문제를 잡아냅니다.
- 스틸 이미지 렌더링(예:
node scripts/render-all.mjs --still --scale=1 <ShotId>)하고 PNG를 읽으세요. - 시간 기반 공개(큐에 들어오는 요소, 스크롤, 단계적 페이즈)의 경우, 단일 지속 시간의 60% 스틸 이미지로는 충분하지 않습니다. 전체 클립을 렌더링하고 각 주요 순간에서 프레임을 추출하거나(
ffmpeg -ss <t> -i out/<id>.mp4 -frames:v 1 f.jpg), 여러 오프셋에서 스틸 이미지를 렌더링하세요. 하나의 프레임이 아닌 각 큐에서의 상태를 확인하세요. - 특히 다음을 확인하세요: 이미지가 슬롯에 맞는지(중요한 콘텐츠가 잘리지 않았는지; 이질적인 실제 이미지의 경우 일치하는 배경에
objectFit: contain선호), 텍스트가 잘리거나 넘치지 않는지, 안전 영역을 벗어난 것이 없는지, 각 애니메이션 요소가 표시되어야 할 때 실제로 보이는지. - 다른 푸티지 위에 합성된 경우, 고립된 샷뿐만 아니라 최종 출력 프레임도 교차 확인하세요.
스크린샷이 드러내는 것을 수정한 후 다시 렌더링하고 다시 확인하세요. "렌더링했고 올바르게 보입니다" — 표시된 프레임과 함께 — 를 완료 기준으로 삼으세요.
최종 출력
완전한 .tsx 파일과 그 외에는 아무것도 생성하지 마세요. 파일 자체에 마크다운을 감싸거나 코드 앞뒤에 설명을 넣지 마십시오. 파일을 전달할 때 채팅에 짧은 한 줄 메모를 남기는 것은 괜찮습니다.