mcpField
C
countdown-video

countdown-video

github.com/iart-aiクリエイティブ・メディア181.8k 回インストール18 週間前に更新
リポジトリ

countdown-video について

countdown-videoは、Remotion動画やライブWebページ向けの正確なカウントダウン制作スキルです。フレームまたは時計を基準に残り時間を計算してずれを防ぎ、数字の演出、背景ループ、出力確認も扱います。

主な機能

  1. 01Remotionのフレーム駆動カウントダウン
  2. 02時計基準のWebタイマー
  3. 03時間表示形式の設定
  4. 04オドメーターとフリップ演出
  5. 05継ぎ目のない背景ループ
  6. 06レンダリングとMP4の検証

活用シーン

  1. 01配信の開始前画面
  2. 02ローンチやフラッシュセールのタイマー
  3. 03指定日時までのカウントダウン

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/iart-ai/tiktok-video-skills --skill countdown-video

インストール後、Claude Code で/countdown-videoを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
references/
countdown-cookbook.md
digit-flip.md

詳細

カウントダウン動画

終了すべき瞬間に、正確に終了するカウントダウンを作成します。基本はひとつです。フレーム(または単調増加タイムスタンプ)から残り時間を求め、その数値をフォーマットしてアニメーションさせます。setInterval でカウントダウンしてはいけません。ライブ配信の「まもなく開始」画面、ローンチやセールのカウントダウン、ループする背景に活用できます。

使用する場面

  • 5~10 分のタイマーを表示する「まもなく開始」/「すぐに戻ります」のライブ配信画面。
  • ローンチやフラッシュセールのカウントダウン(目標日時までの dd:hh:mm:ss、緊急感を演出する mm:ss)。
  • 数字が正確にゼロになるフレームで、アニメーションする必要があるカウンター。

唯一のルール:目盛りではなく、時計を基準に数字を動かす

setInterval(fn, 1000) によるカウントダウンはずれていきます。コールバックは 1000 ms より後に実行され、正確に 1000 ms 後に実行されることはありません。また、タブの処理が間引かれたり、レンダリングフレームが遅れたりすると、ずれが積み重なります。1 分あたり約 1 秒の遅れが発生したという報告もよくあります。レンダリング動画でもライブ Web ページでも、解決方法は同じです。信頼できる時計から残り時間を純粋関数として計算すれば、遅れたフレームは次のフレームで補正され、誤差が積み重なりません。

コンテキスト信頼できる時計残り時間
Remotion のレンダリングuseCurrentFrame()total − frame/fps
Web(ライブページ)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();

  // remaining seconds, clamped at 0 — ceil so "1" shows for its full second
  const elapsed = frame / fps;
  const remaining = Math.max(0, Math.ceil(durationInSeconds - elapsed));

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

durationInSeconds は定数ではなくプロパティです。入力をひとつ変えるだけで、同じコンポジションを 5 分の配信タイマーにも 30 秒のセールタイマーにもレンダリングできます。動画の長さがカウントダウンと正確に一致するよう、コンポジションの durationInFrames を durationInSeconds * fps に設定します。

Web タイマー(ライブページ、requestAnimationFrame)

ライブページでは、performance.now() を基準に rAF ループを動かし、表示する秒数が変わったときだけ再描画します(1 Hz の数値のために毎秒 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 を参照してください。

時刻のフォーマット

残り秒数の整数を、除算と剰余で各フィールドに分けてから、ゼロ埋めします。時間に合ったフォーマットを選びます。分だけで十分な場合に 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 だけ移動させます。アニメーションさせるのはテキストの内容ではなく位置です。これにより、数字が突然切り替わらず、滑らかに動きます。

.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 のトランジションではなくフレームを基準に帯のオフセットを動かします(これにより動作が決定論的になります)。上半分が下半分にかぶさるフリップカード形式と、1 桁ずつ回転するリール形式については、実行可能なコンポーネントとともに references/digit-flip.md で詳しく説明しています。

ループする背景

配信画面は数分間表示されるため、背景はシームレスにループする必要があります。タイムラインを割り切れる周期でモーションを構築します(たとえば、周期が loopFrames のグラデーションやパーティクルの移動)。そして frame % loopFrames を使えば、ループの継ぎ目が見えません。コントラストを抑え、動きをゆっくりにします。主役は数字であり、背景は雰囲気を添えるものです。レシピは references/countdown-cookbook.md を参照してください。

出力チェックリスト

  • 残り時間は毎フレーム useCurrentFrame() / performance.now() から再計算します。setInterval で減算しません。
  • タイマーは意図した正確なフレームで 00:00 になります。durationInFrames === durationInSeconds × fps です。
  • 再生時間はハードコードされた定数ではなく、入力プロパティにします。
  • 数字には tabular-nums を使用します。数字が変わってもレイアウトはずれません。
  • 表示形式は時間の範囲に合わせます(mm:ss と dd:hh:mm:ss)。先頭のゼロ単位は、煩わしい場合は省略します。
  • 背景ループの周期はタイムラインを均等に分割し、ループの切り替わりに継ぎ目がありません。

納品と検証(レンダリングした静止画 → 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)です。

検証ループ — 静止画をレンダリング → 確認 → エンコード。 フレーム精度が最も重要なので、エンコード前に正確なフレームでの数字の値を確認してください。

# Stills at start / mid / end — WITH SHIPPED PROPS (the real duration you'll render)
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

# Inspect each PNG — the digits must read EXACTLY the frame/fps math:
#  - at frame N, displayed = ceil(durationInSeconds - N/fps)  (e.g. 300s @ 30fps, frame 4500 → 150s → "02:30")
#  - last frame reads 00:00 (or the end-state hold); no drift, no off-by-one second
#  - tabular-nums: layout does not jitter between frames
#  - 9:16 safe area: the timer + brand block sit clear of top ~12% and bottom ~20-35% UI and the right action rail

npx remotion render Countdown out/countdown.mp4 --props='{"durationInSeconds":300}'   # encode once digits verify
npx remotion render Countdown out/demo.gif --codec=gif                                 # README proof clip

npx remotion compositions を使用して durationInFrames / fps を確認し、確認したい秒数のフレームを計算します。

完了前に確認すること:

  1. フレーム 0、中間、最後の静止画がエラーなくレンダリングされること。
  2. フレーム N での数字の値が正確に ceil(durationInSeconds - N/fps) となること。最後のフレームで、意図したフレームに 00:00 になること(ずれやオフバイワンがないこと)。
  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、終了状態の保持)、タイムゾーン処理に対応した固定日時までのカウントダウン Web コンポーネント、「開始までしばらくお待ちください」の配信用画面レイアウト(ブランドブロック + SNS ハンドル + ループするグラデーション背景)、および継ぎ目のないループ背景パターン。
  • references/digit-flip.md — 決定論的な数字アニメーションコンポーネント 2 種類:フレーム駆動の縦型 0~9 ストリップを使うオドメーターリールと、上半分を折り返すフリップカード効果。ローンチ、セール、配信のどれに適しているかも説明します。

コメント