ffmpegで動画からサムネイル画像を作成してみます。
これをfluent-ffmpegというnpmライブラリを使うことでNode.jsから操作できます。
今回は動画からサムネイル画像を作成します。
↓↓↓↓ こういうどこかのフレームを切り出したpng画像を出力
ffmpegのインストールなど
Macを使ってますが事前にインストールしておきます。
$ brew install ffmpeg
インストール確認
$ which ffmpeg
/opt/homebrew/bin/ffmpeg
ちなみにNode.jsはv24です。
利用する動画
こちらで作った動画からサムネイルを取得します。
動画からフレームを切り出す
- ライブラリのインストール
$ npm i fluent-ffmpeg
- コード
import ffmpeg from 'fluent-ffmpeg';
import { basename, extname, dirname, join } from 'node:path';
async function createThumbnail(videoPath: string): Promise<string> {
const videoName = basename(videoPath, extname(videoPath));
const thumbnailPath = join(dirname(videoPath), `${videoName}_thumbnail.png`);
console.log(`🎬 ${videoName} からサムネイル生成中...`);
return new Promise((resolve, reject) => {
ffmpeg(videoPath)
.screenshots({
timestamps: [1], //[1] => 1秒目 ['10%'] => 10%の長さの箇所 [1,5,10] => 1,5,10秒で複数画像
filename: basename(thumbnailPath),
folder: dirname(thumbnailPath),
size: '1280x720'
})
.on('end', () => {
console.log(`✅ 完了: ${thumbnailPath}`);
resolve(thumbnailPath);
})
.on('error', reject);
});
}
// 実行
const videoPath = process.argv[2];
if (!videoPath) {
console.log('使用方法: node simple-thumbnail.ts <動画パス>');
process.exit(1);
}
createThumbnail(videoPath).catch(console.error);
.screenshotsのtimestampsの値を[1]
にしてますが、これは1秒目のフレームを画像化する指定です。
[1,5,10]
などにすると1秒目、5秒目、10秒目で3枚のスクリーンショットが抽出できます。
.screenshots({
timestamps: [1,5,10],
filename: basename(thumbnailPath),
folder: dirname(thumbnailPath),
size: '1280x720'
})
- 使ってみる
$ node simple-thumbnail.ts ./tmp/IMG_0588.MOV
こんな感じでpng画像が出力されます。
再生ボタンを入れた画像を作る
ちゃんと調べてないですが、drawtextのオプションでテキストを入れることができる模様です。
折角なので動画のサムネイルということで再生ボタンを入れた画像を作ってみます。
import ffmpeg from 'fluent-ffmpeg';
import { basename, extname, dirname, join } from 'node:path';
import { unlink } from 'node:fs/promises';
async function createThumbnailWithPlayButton(videoPath: string): Promise<string> {
const videoName = basename(videoPath, extname(videoPath));
const baseThumbnailPath = join(dirname(videoPath), `${videoName}_base.png`);
const thumbnailPath = join(dirname(videoPath), `${videoName}_thumbnail.png`);
console.log(`🎬 ${videoName} から再生ボタン付きサムネイル生成中...`);
return new Promise((resolve, reject) => {
// 1. 基本サムネイル生成
ffmpeg(videoPath)
.screenshots({
timestamps: [1], // 1秒目
filename: basename(baseThumbnailPath),
folder: dirname(baseThumbnailPath),
size: '1280x720'
})
.on('end', () => {
// 2. 再生ボタンを追加
ffmpeg(baseThumbnailPath)
.outputOptions([
'-vf',
'drawtext=text=▶️:fontsize=150:fontcolor=white:x=(w-tw)/2:y=(h-th)/2:borderw=5:bordercolor=black@0.9'
])
.output(thumbnailPath)
.on('end', async () => {
console.log(`✅ 完了: ${thumbnailPath}`);
await unlink(baseThumbnailPath); // 中間ファイル削除
resolve(thumbnailPath);
})
.on('error', () => {
// 再生ボタン追加失敗時は基本サムネイルを使用
console.log(`✅ 完了(再生ボタンなし): ${baseThumbnailPath}`);
resolve(baseThumbnailPath);
})
.run();
})
.on('error', reject);
});
}
// 実行
const videoPath = process.argv[2];
if (!videoPath) {
console.log('使用方法: node simple_image.ts <動画パス>');
process.exit(1);
}
createThumbnailWithPlayButton(videoPath).catch(console.error);
これはべんり
所感など
終わってみると割とサクッと画像切り出しができて便利ですね。
GPTのAPIなどに画像をあげつつ、盛り上がってる箇所を探してほしい選定などもやりたい