入力と対応形式
本ツールは最大30MiBまでの一つの動画ファイルを入力として受け付けます.ブラウザのローカル環境における標準的なデコード処理に依存しており,MIMEタイプとしてはvideo/mp4およびvideo/webm(または拡張子が.mp4,.webmのもの)に限定して対応しています.
これ以外の形式や制限サイズを超えるファイルが選択された場合,処理は即座に拒否され,エラーメッセージがユーザーに対して表示される仕組みになっています.ユーザーが条件を満たす動画ファイルを選択すると,ツール内部のown関数によって提供されたBlobオブジェクトに対するObject URLが生成され,このURLがHTML5の<video>要素のsrc属性にマッピングされます.
このファイルの読み込みと動画データの解析処理はすべてブラウザ内のコンテキストで実行されるという事実があり,入力された動画データの変換処理自体はクライアント側で完結します.動画のメタデータが読み込まれる際,handleMetadataLoaded関数が呼び出され,動画の実時間(duration)やネイティブ解像度(videoWidth,videoHeight)が取得されます.
これらの値が正常に取得できない場合や,非有限の値が含まれている場合は,デコード不能なファイルと判断して処理を直ちにエラーで停止し,未対応形式の強引な偽装や処理の続行は行いません.また,ユーザーのお手元に適切なテスト用動画ファイルが存在しない場合でもツールの挙動を確認できるように,動作確認用として2秒間のサンプル動画を生成する機能が組み込まれています.
これはMediaRecorder APIを用いて一時的なWebM動画を動的に作成する仕組みであり,Canvas上に幅160px,高さ90pxの背景を描画し,時間の経過に合わせて動く矩形を実時間で録画することでファイルを読み込んだ状態を擬似的に再現し,入力から変換までのフロー全体を体験できるように設計されています.
これにより,実際の動画ファイルを用意する手間を省き,ツールの操作感や変換設定の効果を素早く試すことが可能となっています.
開始終了の設定
動画のメタデータが正常に取得され,全体の再生時間が判明すると,ユーザーは2つのスライダーコンポーネントを通じて動画から抽出したい区間の開始位置と終了位置を精密に指定できるようになります.このスライダーコンポーネントは0.1秒単位での細かな調整が可能となっており,開始位置は終了位置を超えないよう,逆に終了位置は開始位置を下回らないよう,onChangeイベントハンドラ内でその値が常に厳密に検証されます.
現在の指定位置はformatTime関数を通じて「分:秒.1桁の小数秒」の形式(例:1:23.4)にフォーマットされ,ユーザーインターフェース上に分かりやすく表示されます.ここでの時間はミリ秒表示ではありません.指定された抽出範囲が確定した後,実際のフレーム抽出に向けた正確なタイムスタンプの計算はcreateVideoFrameTimes関数が担います.
この関数は,指定された開始時間と終了時間,およびセレクトボックスから選択されたFPS(フレームレート)に基づき,区間内で必要となるフレーム数を正確に計算し,均等な時間間隔で抽出ポイントの配列を生成します.抽出プロセスにおいて,動画を指定のタイムスタンプへ移動させる役割はseekVideoFrame関数が担当します.
ブラウザにおける動画のシーク操作は非同期的に行われるため,単に動画要素のcurrentTimeプロパティを変更するだけでは,描画の準備が整う前に次の処理に進んでしまう可能性があります.そのため,内部でHAVE_CURRENT_DATA = 2という定数を用い,動画のreadyStateがこの値以上であること,かつ現在位置が目標とするタイムスタンプの0.001秒の許容誤差内に収まっているかを判定します.
シークの完了を検知するために,動画要素に対してseekedイベントやloadeddataイベントのリスナーを登録し,最大10秒間のタイムアウト期間を設定して待機するアプローチをとっています.これにより,フレームが未ロードのまま描画処理へ進んでしまう不具合を防ぎ,安定した画像抽出を支えています.
幅・FPSと処理上限
動画フレームの抽出処理において,Canvas要素を用いた描画とピクセルデータの操作は,ブラウザのメモリとCPUリソースを著しく消費する重い処理です.そのため,実際の変換処理を開始する前に,validateMediaExport関数による入力パラメータの厳格な検証と制限の適用が行われます.
ユーザーが入力可能な出力幅のピクセル数は,極端な高解像度によるブラウザのクラッシュを防ぐため,100ピクセルから640ピクセルの範囲に制限されています.出力の高さは,入力された元動画のアスペクト比(縦横比)を維持する形で自動的に算定されます.ここで算出された幅と高さの積である1フレームあたりの総画素数は,どのような条件を指定した場合でも必ず100万画素以下に収まる必要があり,これを超過する場合はエラーとなります.
フレームレート(FPS)については,セレクトボックスから5(軽量な動き),10(標準的な動き),15(なめらかな動き),24(高画質な動き)のいずれかを選択する方式を採用しています.また,一度の処理で抽出対象となる動画の長さは30秒以内であることが求められます.
これらの基本条件に加え,最終的な出力形式としてGIF形式を選択した場合は,フォーマットの特性上さらなる制約が課せられます.具体的には,生成されるGIFアニメーションの総フレーム数は最大300フレームまでに制限され,さらに「出力幅×出力高さ×総フレーム数」の計算式で求められる総処理画素数が5000万画素以下でなければならないという厳しいルールが存在します.
これらの判定プロセスにはNumber.isFiniteやNumber.isIntegerを用いた型の正当性チェックが含まれており,一つでも制限に違反するパラメータが存在した場合は直ちに例外がスローされ,処理が拒否されます.このように,実際にエンコードのループを回す前に上限の超過を確定させることで,実行不可能なタスクを未然に遮断する堅牢な設計が貫かれています.
GIFのフレームと減色
GIFアニメーションへのエクスポート処理では,独自のLZW圧縮アルゴリズムを実装する代わりに,JavaScriptで実装された実績のあるgifencライブラリを使用しています.処理が開始されると,事前の検証を通過した出力幅と高さを持つCanvas要素が作成され,頻繁なピクセル読み取り操作に最適化するためのwillReadFrequently: true属性を指定して2Dコンテキストが取得されます.
その後,算定された各フレームのタイムスタンプに従ってseekVideoFrameを順次呼び出し,動画要素を特定の再生位置で停止させた上で,ctx.drawImageメソッドを用いて現在の動画フレームをCanvasへと描画します.続いてctx.getImageDataを実行し,CanvasからRGBA形式の一次元ピクセル配列を抽出します.
GIFフォーマットの仕様上,1フレームにつき最大256色のインデックスカラーしか扱うことができないため,抽出されたピクセルデータはgifencライブラリが提供するquantize関数に渡されます.この関数により,そのフレームの画像内容に最も適した最大256色のパレットが計算されます.
次にapplyPalette関数を用いて,元のRGBAピクセル配列が計算されたパレットのインデックス番号へとマッピングされ,減色処理が行われます.最終的に,エンコーダーのwriteFrameメソッドによってフレームデータがGIFストリームに書き込まれます.
このとき,フレームの表示間隔は指定されたFPSから逆算された遅延値として設定されますが,GIFの仕様により遅延値は100分の1秒単位へ丸められるため,指定したFPSと実際の再生時間が完全には一致せず,色の変化や時間的なズレが生じることがあります.また,ループのインデックスが4の倍数になるたびにsetTimeoutを介して処理を休止させますが,端末のUIフリーズの回避を保証するものではありません.
出力されるGIFは完全に無音となります.
WebMの実時間・無音
WebM動画の出力プロセスは,GIF出力時のように動画を停止させながらシークを繰り返す個別フレーム抽出の手法ではなく,Canvas上で実時間で再生されている映像をそのまま録画するという別のアプローチを採用しています.この機能はブラウザ側に実装されているMediaRecorder APIのサポートに依存しており,処理開始時にまずvideo/webm;codecs=vp9,video/webm;codecs=vp8,あるいはベースとなるvideo/webmというMIMEタイプのサポート状況を順番に確認します.
ブラウザがいずれの形式にも対応していない場合は,録画不可としてエラーを投げて処理を中断します.録画が可能な環境であれば,元動画を指定された抽出区間の開始位置にシークした後,動画要素の音声をミュート状態(無音)に設定し,等倍速での再生を開始します.その後,recordCanvasVideo関数内においてCanvas要素のcaptureStreamメソッドを呼び出して映像ストリームを取得し,映像のビットレートを5,000,000 bpsに設定したMediaRecorderのインスタンスを起動して録画を開始します.
録画プロセス自体は,ブラウザのタイマー機能であるsetTimeoutを用いた再帰的な呼び出しによって細かく管理されます.録画開始からの経過時間をミリ秒単位で正確に計測し,指定された抽出秒数に達するまでの間,定期的にctx.drawImageを実行して動画要素の現在フレームをCanvasに転写し続けます.
経過時間が目標とする動画長に到達した時点で明示的に録画の停止メソッドが呼ばれ,dataavailableイベントを通じてWebMのBlobオブジェクトが生成されます.この実時間キャプチャの性質上,処理には実際の抽出動画長と同等の待機時間がかかり,正確なフレーム単位での編集や元動画のメタデータの保持は保証されません.
また,描画されるのは映像のみであるため,出力されるWebM動画も完全に無音となります.
保存と再選択・環境差
GIFアニメーションまたはWebM動画のエンコード処理が完了すると,生成されたバイナリデータはブラウザ内でBlobオブジェクトとして一つにまとめられ,画面でのプレビュー表示用およびローカルへのダウンロード用のObject URLが動的に生成されます.生成されたファイルのサイズは,データ量が1024×1024バイトを超える場合はMiB単位で計算されて表示され,それに満たない場合はKiB単位で表示されることで,ユーザーが最終的なファイルサイズを把握しやすくなっています.
出力されたファイルは,設定された形式に応じて「trimmed_animation.gif」または「trimmed_video.webm」というデフォルトのファイル名でユーザーの端末へ直接ダウンロードすることが可能です.本ツールでは連続した動画変換操作におけるメモリ管理の一環として,新しいファイルの再選択時や画面上のクリアボタンの押下時,あるいはアプリケーションのコンポーネントからのページ離脱時(アンマウント時)において,revokeObjectURL関数を通じて登録されている既存のObject URLを解放する実装が行われています.
さらに,generationという整数値を保持する参照などを用いることで,非同期処理の実行世代を管理しています.エンコード処理の開始時に取得した世代番号と,シークなどの非同期待機から復帰した時点での世代番号が一致しない場合,ユーザーが既に別の動画を選択するなどの操作を行ったと判断し,後続の描画・変換処理をキャンセルして,古いデータが現在の画面のUI状態を不当に上書きしてしまうことを防いでいます.
また,これと並行してAbortControllerを利用したシグナル制御も適用されており,不要になった処理の速やかな中断をサポートしています.出力されるWebM動画のエンコード品質や,MediaRecorder APIの利用可否,Canvas要素に対するハードウェアアクセラレーションのパフォーマンスなどは,実行するオペレーティングシステム(OS)やウェブブラウザのバージョンといったユーザーのローカル環境の違いに大きく依存して変動する性質を持っています.