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 と指定することで実行可能。