02 April 2012

(Perl) ffmpegを使って動画のサムネイル画像jpegを作成する

Perlで動画のサムネイル画像を作る時に、ffmpegを利用する方法

■ CPAN の FFmpeg::Thumbnail がインストールできない…

cpanでパッケージをインストールすると


# BEGIN failed--compilation aborted at /root/.cpan/build/FFmpeg-Thumbnail-0.02-9EZ3ZW/blib/lib/FFmpeg/Thumbnail.pm line 9.
# Compilation failed in require at (eval 4) line 2.
# BEGIN failed--compilation aborted at (eval 4) line 2.
Use of uninitialized value $FFmpeg::Thumbnail::VERSION in concatenation (.) or string at t/00-load.t line 10.
# Testing FFmpeg::Thumbnail , Perl 5.010001, /usr/bin/perl
# Looks like you failed 1 test of 1.
FAILED--Further testing stopped.

とエラーが出てインストール不可。で、ソースコードを眺めてみると、単に ffmpeg コマンドをバックグラウンドで実行して、標準出力文字列をキャプチャしているだけ…。

このレベルなら、大量の依存パッケージまみれにしなくても、自分でも作れる。

■ 外部コマンドを実行して、標準出力を取り込んで、特定の文字列を抜き出す

ffmpegコマンドを実行し、『Duration: 00:00:00.00』 という文字列を含む行を特定し、時刻データを変数に取り込む例


my @out = `ffmpeg -i $filename 2>&1`;
my ($hour, $min, $sec) = (0,0,0);
foreach my $line(@out){
$line =~ /Duration:\s+(\d+):(\d+):(\d+)(?:\.\d+)?\s*,/s;
if($1){
$hour = $1;
$min = $2;
$sec = $3;
last;
}

CPANのFFmpeg::Commandライブラリは、このような作業を内部でしている。


■ 動画のサムネイルを作成するサブルーチン


# 動画ファイルからサムネイル画像を切り出す
# $filename: 入力ファイル名
# $output_filename: 出力jpegファイル名
# $time_percent: 切り出す画像の位置(0から100%で指定)
# $pixcel_long: サムネイル長辺のピクセル数
sub sub_make_thumbnail_image {
my ($filename, $output_filename, $time_percent, $pixcel_long) = @_;

# 切り出すフレームの動画先頭からの経過秒数を求める
my $sec = sub_get_movie_duration($filename); # 動画の総秒数
if($sec <= 0){ return(0); }
$sec = int($sec * $time_percent / 100);

# 作成するサムネイルjpegのサイズを求める(アスペクト比保存)
my ($width_org, $height_org) = sub_get_movie_framesize($filename); # 動画のXYサイズ
if($width_org <= 0 || $height_org <=0){ return(0); }
my ($width, $height);
# 長辺側を基準にアスペクト比を考慮して出力サイズを求める
if($width_org > $height_org){ ($width, $height) = ($pixcel_long, int($pixcel_long*$height_org/$width_org)); }
else{ ($width, $height) = (int($pixcel_long*$width_org/$height_org), $pixcel_long); }
# 出力サイズは偶数(ffmpeg制限)
if($width % 2){ $width++; }
if($height % 2){ $height++; }

# サムネイルjpeg作成
my $cmd = "ffmpeg -i $filename -f image2 -an -y -vframes 1 -ss $sec -s ".$width."x".$height." $output_filename";
my @out = `$cmd 2>&1`;

unless(-f $output_filename){ return(0); }
return(1);
}

# 動画の縦横サイズ(ピクセル数)を得る
sub sub_get_movie_framesize {
my ($filename) = @_;
my @out = `ffmpeg -i $filename 2>&1`;
my ($width, $height) = (0,0);
foreach my $line(@out){
$line =~ /Stream .+ Video: .+ (\d+)x(\d+)/s;
if($1){
$width = $1;
$height = $2;
last;
}
}
return ($width, $height);
}

# 動画の長さ(秒数)を得る
sub sub_get_movie_duration {
my ($filename) = @_;
my @out = `ffmpeg -i $filename 2>&1`;
my ($hour, $min, $sec) = (0,0,0);
foreach my $line(@out){
$line =~ /Duration:\s+(\d+):(\d+):(\d+)(?:\.\d+)?\s*,/s;
if($1){
$hour = $1;
$min = $2;
$sec = $3;
last;
}
}
return ($hour*3600 + $min*60 + $sec);
}

※ Windowsの場合は ffmpeg.exe と指定することで実行可能。