コンテンツにスキップ
quickthumb
すべてのドキュメント

アニメーションのタイミング詳説

簡潔な案内はアニメーションのタイミングを参照してください。

作成するモーション、スライド時間、保持時間、動画の配置、字幕キューには秒を使います。sample() は基準タイムラインを観測し、export() は実際にエンコードした映像のタイムラインを報告します。目的の違う測定値なので、区別して使ってください。

時間軸を選ぶ

出力・観測 時間の契約
time・fps なしの sample() Canvas・ページごとに作成時の静止フレームを 1 枚取得します。アニメーションの終点へ移動せず、ラスター出力と一致します。duration=0、フレームの time=None です。
sample(time=...) 明示した文書時刻を観測します。スライド区間は [start, end) で、境界ちょうどは次のスライドです。終了後は最終状態を表示します。
sample(fps=...) 終了前の等間隔グリッド 0, 1/fps, 2/fps, … を観測します。長さがゼロでも時刻ゼロを一度観測します。
GIF 無音で、10 ms 刻みの可変フレーム時間です。同一フレームは統合できます。既定 20 fps、対応範囲は 0 より大きく 100 fps 以下です。
MP4 / WebM / MOV 保持中の重複フレームも含む固定レートです。既定 30 fps、対応範囲は 0 より大きく 120 fps 以下です。
HTML ブラウザーが対話型キューとスライド移動を制御します。エンコード fps や固定総再生時間はなく、Deck のナレーションも再生しません。

GifOptions.fps と VideoOptions.fps はサンプリング密度を指定します。動く区間はそれぞれの境界内でサンプリングし、静的区間は保持するショットにします。動画は累積フレーム時計で重複フレームを割り当て、GIF は累積時間を 1/100 秒単位に量子化します。そのため、基準の等間隔サンプルグリッドをエンコード済みファイルのインデックスには使えません。小数の区間長、パレット・非可逆圧縮、重複フレームの統合により、フレーム位置や数が変わります。

アニメーション出力後は ExportResult.timing_metrics を確認してください。

フィールド 意味
fps 選んだサンプリングレート。GIF では保存フレーム数を再生時間で割った値ではありません。
frame_count GIF の統合や動画の保持フレーム反復後に、実際にエンコードしたフレーム数。
duration 映像の再生秒数。GIF は delay の合計、動画はフレーム数を fps で割った値です。音声コンテナーの余白とは異なる場合があります。

一方 FrameSequence.duration は、量子化前の基準タイムラインを表します。HTML の timing_metrics に固定再生時間はありません。ゼロという値は対話型アニメーションが時間を取らないという意味ではありません。

from quickthumb import AnimationSpec, Canvas, GifOptions

canvas = Canvas(160, 90).background(color="#000000").shape(
    "rectangle", (40, 20), 80, 50, "#FF0000",
    animation=AnimationSpec.fade(duration=1),
)
samples = canvas.sample(fps=7, hold=0.37)
result = canvas.export("timed.gif", animation=GifOptions(fps=7, hold=0.37))
print(samples.duration, len(samples.frames))
print(result.timing_metrics.model_dump())

時間方向の露光

Canvas または Deck の出力で、各フレームを中心としたモーションブラーを有効にできます。

from quickthumb import MotionBlur, VideoOptions

canvas.export(
    "exposure.mp4",
    animation=VideoOptions(fps=30, motion_blur=MotionBlur(samples=8, shutter_angle=180)),
)

GifOptions と ImageSequenceOptions も同じ設定を受け付けます。オフにする値は None だけです。MotionBlur はサンプル数 2〜64 と、0 より大きく 360 度以下のシャッター角度を受け付けます。シャッターは各フレームを中心に (angle / 360) / fps 秒の幅を持ちます。文書の時間外では境界状態を使います。ハードカットでは中心がある側のスライド内に露光を制限し、カットちょうどは次のスライドに属します。滑らかな遷移は開始時刻の前後も含めて露光に参加します。動画と画像シーケンスの露光中心は出力グリッド index / fps、GIF は可変時間のショット時計に従います。エンコード済み sRGB の乗算済みチャンネルを平均してからストレート RGBA に戻すため、透明な軌跡でも鮮やかな色を保ちます。

露光には時間指定の字幕や表示・非表示も含まれます。キュー境界からシャッター幅の半分以内なら寄与する場合があります。作成したキュー時刻と再生時間は変わらず、GIF の delay と動画のフレーム数は引き続き各コーデックの時計に従います。hold がゼロなら最後のフレームにもモーションの露光が含まれます。完全に静止した状態を見せるには hold を追加してください。

Canvas/Deck の GIF・動画・画像シーケンス出力に対応し、workers も使えます。各ワーカーは直列描画と同じサンプル時刻・アルファ蓄積で露光全体を計算します。直接バイト列を返すヘルパーはこの設定に対応しません。sample() と render_frame() は瞬間的な基準観測のままです。結果レポートの機能報告で要求した露光を識別できますが、ソースの検査から出力時だけの設定は推定できません。

対応範囲と結果レポート

capabilities_for(target)["motion_blur"] が露光の対応を示します。

出力先の分類 対応 意味
video full Canvas/Deck の MP4、WebM、MOV、画像シーケンスで露光を積算します。
raster partial、静的代替 GIF では積算しますが、静的ラスターファイルには露光区間がありません。
html、pptx unsupported、静的代替 露光を無視します。ブラウザー・PowerPoint 用に作成したモーションは変わりません。

VideoOptions/GifOptions はそれぞれのアニメーション形式向けで、HTML/PPTX に渡すとオプション分類のエラーになります。この表があるからといって黙って受け付けるわけではありません。inspect_motion() はソースに書かれた機能を報告し、出力時のブラーが有効だったとは主張しません。実際の出力後は result.capability_report の motion_blur 行で適用したサンプル数と角度を確認します。timing_metrics はエンコードした再生情報を保持します。画像シーケンスの結果にも同じ適用行とフレーム・時間情報があります。

動くマーカーの例を実行すると、同じシーンの OFF と 8 サンプルの GIF を保存します。同じシーンを、管理されたサンプルコストのベンチマークにも使っています。

スライド時間と遷移

レイヤーモーションと入ってくる遷移は、同じスライド時計で始まります。非対話型メディアでは on_click キューは after_previous のように順番に動き、with_previous は重なります。delay もその時計の一部です。遷移は前スライドの最終状態に重ねて再生します。未指定の場合、最初のスライドは即座に始まり、それ以降は 0.5 秒のクロスフェードです。即座に切り替えるには Cut() を使います。

スライドの終了は、該当する境界のうち最も遅い時刻で決まります。

  • 時間指定も advance_after もなければ、遷移とモーションが終わってから最終状態を hold 秒(既定 3)保持します。
  • Deck 動画の slide(duration=...) は遷移とモーションを含むスライド全体の時間枠です。未指定ならナレーションが枠を決めます。どちらも長い映像アニメーションを切り詰めず、両方を収める時間を確保します。ナレーション自体は予定枠に合わせて切り詰めるか無音を足します。
  • 遷移の advance_after は hold を置き換え、その境界は入ってくる遷移の終了後から数えます。スライド終了は、その境界、モーション終了、ナレーション・明示時間のうち最も遅いものです。たとえば 1 秒のアニメーションに Cut(advance_after=0.1) を指定すると、hold=3 でも 1 秒で終わり、後ろに 3 秒は加えません。

GIF は Deck のナレーションと slide(duration=...) を無視し、モーション、遷移、advance_after、hold を使います。Deck.sample() はナレーションを考慮する動画スケジュールなので、ナレーション付き Deck の GIF とサンプルの長さは一致しない場合があります。

エンコーダーは hold=0 でも最終状態を示します。常に保持フレームを追加するのではなく、必要なら最後のモーションショットを置き換えます。静的なスライドや 1 フレーム未満のスライドにも、表現可能な 1 フレームが必要です。そのため GIF・動画の時間は数学的なタイムラインと少し異なる場合があります。現在の Deck 動画では、ナレーションも明示的な正の時間もないスライドに正の hold が必要です。Canvas と Deck GIF ではゼロも有効です。

音声・字幕・動きの抑制

MP4/WebM/MOV は対応するクリップ音声、Deck ナレーション、任意の音楽をミックスします。ナレーションはスライドの絶対オフセットから始まります。音楽は映像の時間に切り詰め、ループも可能です。コーデックの音声余白で報告する映像時間が延びることはありません。GIF と画像シーケンスには音声がありません。動きと音楽・ナレーションを一緒に届けるには動画を使ってください。MP4 はスライドごとのナレーションを保ちながらレイヤーアニメーションと遷移を再生します。静止スライドのナレーションには、静的 Canvas と deck.transition(Cut()) を使います。

動画レイヤーの start はスライド内の時刻です。trim_start、trim_end、speed はソースのメディア時間を選びます。字幕の start/end は元ファイルではなく、配置したクリップを基準にします。字幕は [start, end) で有効となり、対応するラスター・動画出力の前景に焼き込みます。動きを抑えてクリップ映像を止めても、字幕の時間指定は残ります。

ExportPolicy(reduced_motion=True) は、作成したレイヤーモーションとスライド遷移を抑制します。保持時間、ナレーション、字幕時間は残り、Deck の既定クロスフェードもスライド間に残る場合があります。結果の時間は元のモーション時間ではなく、実際の出力レポートを参照してください。

HTML の on_click は対話型、after_previous は自動です。advance_after は入ってくる遷移の後で条件付きの自動移動を有効にしますが、固定時間の動画には変えません。HTML の動画レイヤーは静的な代替なので、再生・字幕のタイミングは再現しません。出力先について validate_export() と返された代替診断を確認してください。GIF・動画のオプション分類の不一致、未対応の透明度、コーデックのレート制限は、黙って別形式を選ばず、対処できるエラーを返します。

再生を検証する

tests/test_video_layers.py の 3 スライドの統合シーンは、実際の GIF、MP4、WebM を検査します。基準サンプルとの選択ピクセル比較、入ってくる遷移、字幕の開始・終了区間、エンコードした時間、デコードした音楽とナレーションの周波数を確認します。reduced-motion のケースは静止したクリップ映像と時間指定の字幕を検査します。FFmpeg と ffprobe をインストールして実行してください。

uv run --locked pytest tests/test_video_layers.py -k motion_package --no-cov

既存の HTML タイムラインテストは、Node のステージ検証データで JavaScript を実行します。出力先ごとのブラウザー忠実度は別の検査です。