Communitygithub.com

카운트다운 비디오

지정된 시간에 정확히 종료되는 카운트다운 비디오를 만듭니다. 프레임 기반의 정확한 시간 계산을 통해 setInterval 드리프트를 방지합니다.

카운트다운 비디오란 무엇인가요?

이 스킬은 사용자가 Remotion 또는 웹 환경에서 정확한 프레임 단위 카운트다운을 생성해야 할 때 사용합니다. 코어 아이디어는 타이머 값을 내부 상태로 관리하지 않고, 매 프레임마다 신뢰할 수 있는 클록(Remotion의 `useCurrentFrame()` 또는 웹의 `performance.now()`)에서 남은 시간을 순수 함수로 계산하는 것입니다. 이렇게 하면 setInterval이 탭이 제한되거나 렌더 프레임이 느려질 때 누적되는 드리프트 오류(분당 약 1초 손실)가 완전히 제거됩니다. 라이브 스트리밍 '곧 시작합니다' 화면, 제품 출시/플래시 세일 카운트다운, 또는 무한 루프 배경과 함께 사용되며, 자릿수 롤링/플립 전환과 시맨리스 루핑 배경 패턴을 포함합니다. Remotion 렌더링의 경우 프레임 N이 정확히 `N/fps` 초에 해당하므로 타이머가 프레임 단위로 정확하고, 웹에서는 `requestAnimationFrame`을 사용하여 표시되는 초가 변경될 때만 다시 그려 효율적입니다. 최종 출력물은 MP4 비디오로 제공되며, 시작, 중간, 마지막 프레임의 정지 이미지를 검사하여 타이머가 의도한 시간에 정확히 00:00에 도달하는지 확인하는 검증 단계가 포함되어 있습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/iart-ai/tiktok-video-skills/tree/main/skills/countdown-video

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

카운트다운 비디오은(는) 무엇을 하나요?

정확히 정해진 시간에 종료되는 카운트다운을 만듭니다. 전체 제작의 핵심은 한 가지 아이디어입니다: 프레임(또는 단조 증가하는 타임스탬프)에서 남은 시간을 계산하고, 그 숫자를 포맷하고 애니메이션 처리하십시오 — 절대 setInterval로 카운트다운하지 마십시오. 라이브 스트리밍 "곧 시작합니다" 화면, 출시/세일 카운트다운, 루핑 배경을 다룹니다.

사용 시기

  • "곧 시작합니다" / "잠시 후 돌아옵니다" 라이브 스트리밍 화면 (5-10분 타이머).
  • 출시 및 플래시 세일 카운트다운 (목표 날짜까지 dd:hh:mm:ss; 긴급함을 위해 mm:ss).
  • 숫자가 정확한 프레임에 0에 도달해야 하는 모든 애니메이션 카운터.

하나의 규칙: 클록에서 숫자를 도출하고, 틱(tick)을 사용하지 마십시오

setInterval(fn, 1000) 카운트다운은 드리프트됩니다 — 콜백은 적어도 1000ms 후에 실행되며, 정확히 실행되지 않고, 탭이 제한되거나 렌더 프레임이 느려지면 누적됩니다. 분당 약 1초 손실에 대한 보고가 일반적입니다. 해결책은 렌더링된 비디오와 라이브 웹 모두 동일합니다: 권위 있는 클록의 순수 함수로 남은 시간을 계산하여, 늦은 프레임이 오류를 누적하는 대신 다음 프레임에서 자체 수정되도록 합니다.

컨텍스트권위 있는 클록남은 시간
Remotion 렌더useCurrentFrame()total − frame/fps
웹 (라이브 페이지)performance.now() 또는 Date.now()targetMs − now
고정된 날짜까지Date.now()targetTimestamp − Date.now()

절대 남은 시간을 상태에 저장하고 감소시키지 마십시오. 매 프레임마다 다시 계산하십시오.

프레임 정확한 카운트다운 (Remotion → MP4)

useCurrentFrame()은 결정론적입니다: 프레임 N은 항상 정확히 N/fps 초에 렌더링됩니다. 전체에서 빼면 타이머는 설계상 드리프트가 없습니다.

import { useCurrentFrame, useVideoConfig } from "remotion";

export const Countdown: React.FC<{ durationInSeconds: number }> = ({
  durationInSeconds,
}) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  // 남은 초, 0에서 클램프 — ceil을 사용하여 "1"이 전체 1초 동안 표시됨
  const elapsed = frame / fps;
  const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));

  return <div style={{ fontVariantNumeric: "tabular-nums" }}>{formatMMSS(remaining)}</div>;
};

durationInSeconds는 **속성(prop)**이며, 상수가 아닙니다 — 동일한 컴포지션이 하나의 입력 변경만으로 5분 스트림 타이머나 30초 세일 타이머를 렌더링합니다. 컴포지션의 durationInFrames를 durationInSeconds * fps로 설정하여 비디오가 카운트다운과 정확히 같은 길이가 되도록 하십시오.

웹 타이머 (라이브 페이지, requestAnimationFrame)

라이브 페이지의 경우 performance.now()에서 rAF 루프를 구동하고 표시되는 초가 변경될 때만 다시 그리십시오 (1Hz 숫자에 대해 60회/초 작업 없음):

const start = performance.now();
let lastShown = -1;
function tick(now) {
  const remaining = Math.max(0, Math.ceil(duration - (now - start) / 1000));
  if (remaining !== lastShown) { render(remaining); lastShown = remaining; }
  if (remaining > 0) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);

고정된 날짜까지 카운트다운하는 것은 Date.now()를 사용한 동일한 아이디어입니다: Math.max(0, target - Date.now()). 시간대 처리를 포함한 전체 날짜 대상 버전은 references/countdown-cookbook.md를 참조하십시오.

시간 포맷팅

나눗셈 + 모듈로 연산으로 남은 초 정수를 필드로 분해한 다음, 0으로 채웁니다. 지속 시간에 맞는 형식을 선택하십시오 — 분만 중요한데 00d 00h 05m을 절대 표시하지 마십시오.

const pad = (n) => String(n).padStart(2, "0");
const formatMMSS = (s) => `${pad(Math.floor(s / 60))}:${pad(s % 60)}`;
const formatDHMS = (s) => {
  const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600);
  const m = Math.floor((s % 3600) / 60), sec = s % 60;
  return `${pad(d)}:${pad(h)}:${pad(m)}:${pad(sec)}`;
};
형식사용필드 생략 시기
mm:ss스트림 "곧 시작합니다", 짧은 타이머 (<1시간)—
hh:mm:ss플래시 세일, 당일 이벤트dd가 0이면 숨김
dd:hh:mm:ss출시 / 여러 날 카운트다운더 깔끔하게 읽기 위해 선행 0 단위 숨김

항상 font-variant-numeric: tabular-nums (또는 고정폭 숫자 폰트)을 설정하여 숫자가 동일한 너비를 유지하고 숫자가 변경될 때 레이아웃이 흔들리지 않도록 하십시오.

숫자 롤 / 플립 전환

평면적인 숫자 교체는 저렴해 보입니다; 구르는 "주행 기록계" 모양이 카운트다운을 돋보이게 합니다. 각 숫자를 0-9의 수직 스트립으로 렌더링하고 -digit × digitHeight만큼 변환(translate)하십시오. 텍스트 콘텐츠가 아닌 위치를 애니메이션 처리하여 변경이 튀는 것이 아니라 미끄러지듯 움직이도록 하십시오.

.reel { height: 1em; overflow: hidden; }
.reel .col { transition: transform .45s cubic-bezier(.22,1,.36,1); }
/* col holds stacked 0..9; shift by -value em */

Remotion에서는 CSS 전환 대신 프레임에서 스트립 오프셋을 구동하십시오 (결정론적이도록). 플립 카드 스타일 (위쪽 절반이 아래쪽 위로 접힘)과 숫자별 릴 모두 실행 가능한 컴포넌트와 함께 references/digit-flip.md에 자세히 설명되어 있습니다.

루핑 배경

스트림 화면은 몇 분 동안 지속되므로 배경이 끊김 없이 반복되어야 합니다. 타임라인에 균등하게 나누어지는 주기 (예: 주기가 loopFrames인 그라데이션 또는 입자 드리프트)로 모션을 만들고, frame % loopFrames로 읽어 랩에서 눈에 띄는 이음새가 없도록 하십시오. 대비를 낮추고 느리게 유지하십시오 — 숫자가 주제이고 배경은 분위기입니다. 레시피는 references/countdown-cookbook.md에 있습니다.

출력 체크리스트

  • 남은 시간이 매 프레임 useCurrentFrame() / performance.now()에서 다시 계산됨 — setInterval 감소 없음.
  • 타이머가 의도된 정확한 프레임에 00:00에 도달함; durationInFrames === durationInSeconds × fps.
  • 지속 시간은 하드코딩된 상수가 아닌 입력 속성임.
  • 숫자는 tabular-nums를 사용; 숫자가 변경되어도 레이아웃이 변하지 않음.
  • 형식이 범위와 일치함 (mm:ss vs dd:hh:mm:ss); 방해가 될 때 선행 0 단위는 생략.
  • 배경 루프 주기가 타임라인을 균등하게 나눔; 랩에 이음새 없음.

전달 및 검증 (렌더링된 정지 이미지 → MP4)

패키지된 도우미 (scripts/): scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png로 정지 이미지를 타일링한 다음, scripts/probe-mp4.sh out.mp4 [WxH] [fps]로 인코딩을 확인하십시오. scripts/README.md를 참조하십시오.

카운트다운은 Remotion 컴포지션 (<Composition> + zod schema + defaultProps, durationInSeconds는 속성)으로 제공됩니다 — 타이머는 useCurrentFrame()의 순수 함수이며, setInterval / Date.now() / Math.random()을 사용하지 않습니다. durationInFrames = durationInSeconds * fps로 설정하십시오. 전달물 = out/*.mp4 + 프로젝트. 9:16 세로 (1080×1920)가 기본값입니다.

루프 검증 — 정지 이미지 렌더링 → 검사 → 인코딩. 전체 목적은 프레임 정확도이므로, 인코딩 전에 정확한 프레임에서 숫자 값을 확인하십시오.

# 시작 / 중간 / 끝 정지 이미지 — 제공된 속성(렌더링할 실제 지속 시간) 사용
npx remotion still Countdown out/f-start.png --frame=0   --props='{"durationInSeconds":300}'
npx remotion still Countdown out/f-mid.png   --frame=N   --props='{"durationInSeconds":300}'
npx remotion still Countdown out/f-end.png   --frame=L   --props='{"durationInSeconds":300}'  # L = durationInFrames-1

# 각 PNG 검사 — 숫자는 정확히 프레임/fps 계산을 읽어야 함:
#  - 프레임 N에서 표시 = ceil(durationInSeconds - N/fps)  (예: 30fps에서 300초, 프레임 4500 → 150s → "02:30")
#  - 마지막 프레임은 00:00 (또는 종료 상태 유지)을 읽음; 드리프트 없음, 1초 오차 없음
#  - tabular-nums: 프레임 간 레이아웃이 흔들리지 않음
#  - 9:16 안전 영역: 타이머 + 브랜드 블록이 상단 ~12% 및 하단 ~20-35% UI 및 오른쪽 액션 레일에서 벗어남

npx remotion render Countdown out/countdown.mp4 --props='{"durationInSeconds":300}'   # 숫자가 확인되면 인코딩
npx remotion render Countdown out/demo.gif --codec=gif                                 # README 증명 클립

npx remotion compositions를 사용하여 durationInFrames/fps를 확인한 다음, 확인하려는 모든 초에 대한 프레임을 계산하십시오.

마치기 전에:

  1. 정지 이미지가 프레임 0, 중간, 마지막에서 오류 없이 깔끔하게 렌더링됩니다.
  2. 프레임 N의 숫자 값이 ceil(durationInSeconds - N/fps)와 정확히 일치합니다; 마지막 프레임이 의도된 프레임에서 00:00에 도달합니다 (드리프트/1초 오차 없음).
  3. 타이머 + 브랜드가 확인된 모든 프레임에서 9:16 안전 영역 내에 있습니다.
  4. 프레임 기반만 — setInterval / Date.now() / 타이머 없음; tabular-nums로 레이아웃 유지.
  5. 제공된 durationInSeconds (단순 defaultProps가 아님)가 올바르게 렌더링됩니다; MP4 인코딩, GIF는 선택 사항입니다.

참조 파일

  • references/countdown-cookbook.md — 완전히 실행 가능한 Remotion <Countdown> (구성 가능한 지속 시간, mm:ss / dd:hh:mm:ss, 종료 상태 유지), 시간대 처리가 포함된 고정 날짜 카운트다운 웹 컴포넌트, "곧 시작합니다" 스트림 화면 레이아웃 (브랜드 블록 + 소셜 핸들 + 루핑 그라데이션 배경), 그리고 끊김 없는 루프 배경 패턴.
  • references/digit-flip.md — 두 가지 결정론적 숫자 애니메이션 컴포넌트: 주행 기록계 릴 (프레임에 의해 구동되는 수직 0-9 스트립) 및 플립 카드 (위쪽 절반 접힘) 효과, 그리고 각각이 출시, 세일, 스트림 중 어느 상황에 적합한지.

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

관련 스킬