Image::MagickとFFmpegで文字を動画にする。
FFmpegの -i オプションで連番の画像ファイルを動画に変換できるらしいです。
http://opensourceaki.blogspot.com/2007/10/ffmpeg_19.html
となれば話は簡単で、先ほどのtext2imageで連番の画像ファイルを生成してFFmpegで連結してあげればいいということになります。Image::Magickでα値を徐々に変化させて、フェードイン・フェードアウトを付けることもできます。
こんな感じで、
> perl text2movie.pl Usage: text2movie.pl -t text [option(s)] outfile Options: -t, --text text to convert to movie -g, --size size (default: 640x480) -b, --bgcolor background color (default: black) -c, --fgcolor foreground color (default: white) --font font (default: C:/Windows/Fonts/meiryo.ttc) --pointsize font size (default: 56) -r, --rate frame rate [fps] (default: 30) --fadein fade-in [sec] (default: 0.7) --hold hold [sec] (default: 0.7) --fadeout fade-out [sec] (default: 0.6) > perl text2movie.pl -t "Image::Magickと\nFFmpegで\n文字を動画にする\nテスト" out.avi
こんな感じの、ムービー編集ソフトで作るようなタイトル動画が生成できます。あとはタイトル動画と撮影した動画をMEncoderで連結してあげればGUIに頼ることなく見栄えのよい動画クリップを作成することができます。
プラットフォームに依存する部分はたぶんないので、ImageMagickとFFmpegとPerlがあればどこでも動くと思います。動画をバッチ処理で作成したいちょっとマニアックな方はお試しあれ。
コードはこちら。無保証、自己責任でよろしくです。
#!/usr/bin/perl # text2movie use strict; use warnings; use Image::Magick; use Getopt::Long; use Jcode; my $DEBUG = $ENV{DEBUG} || 0; # FFmpegの場所 my $ffmpeg = "C:\\Path\\To\\ffmpeg.exe"; # 一時ファイルの作成場所 my $tmpdir = "tmp_text2movie_$$"; # 一時ファイルの名前 my $tmpfile = "$tmpdir\\tmp%06d.jpg"; # オプションのデフォルト値 my %default = ( size => '640x480', bgcolor => 'black', fgcolor => 'white', font => 'C:/Windows/Fonts/meiryo.ttc', pointsize => 56, rate => 30, fadein => 0.7, hold => 0.7, fadeout => 0.6, ); # オプション my %opts; # 出力ファイル名 my $outfile; # usage sub usage { print <<EOM; Usage: $0 -t text [option(s)] outfile Options: -t, --text text to convert to movie -g, --size size (default: $default{size}) -b, --bgcolor background color (default: $default{bgcolor}) -c, --fgcolor foreground color (default: $default{fgcolor}) --font font (default: $default{font}) --pointsize font size (default: $default{pointsize}) -r, --rate frame rate [fps] (default: $default{rate}) --fadein fade-in [sec] (default: $default{fadein}) --hold hold [sec] (default: $default{hold}) --fadeout fade-out [sec] (default: $default{fadeout}) EOM exit 1; } Main: { # コマンドラインオプション取得 GetOptions( "t|text=s" => \$opts{text}, "g|size=s" => \$opts{size}, "b|bgcolor=s" => \$opts{bgcolor}, "c|fgcolor=s" => \$opts{fgcolor}, "font=s" => \$opts{font}, "pointsize=i" => \$opts{pointsize}, "r|rate=i" => \$opts{rate}, "fadein=i" => \$opts{fadein}, "hold=i" => \$opts{hold}, "fadeout=i" => \$opts{fadeout}, ) or usage(); $outfile = shift; usage() if ! defined $outfile; usage() if ! defined $opts{text}; # 文字コードをUTF-8に変換 my $text = Jcode->new( $opts{text} )->utf8; # 連続した画像を生成する seqimage( { filename => $tmpfile, text => $text, size => $opts{size} || $default{size}, bgcolor => $opts{bgcolor} || $default{bgcolor}, fgcolor => $opts{fgcolor} || $default{fgcolor}, font => $opts{font} || $default{font}, pointsize => $opts{pointsize} || $default{pointsize}, rate => $opts{rate} || $default{rate}, fadein => $opts{fadein} || $default{fadein}, hold => $opts{hold} || $default{hold}, fadeout => $opts{fadeout} || $default{fadeout}, } ); # 画像を連結して動画にする system qq($ffmpeg -r 30 -i $tmpfile) . qq( -vcodec mjpeg -sameq -y $outfile); # 一時ファイル削除 while ( my $f = glob "$tmpdir/*" ) { unlink $f } rmdir $tmpdir; exit 0; } # 連続した画像を出力する sub seqimage { my $arg = shift; # 一時ディレクトリ作成 mkdir $tmpdir; # 文字色を取得する my $image = Image::Magick->new(); my @fgcolor = $image->QueryColor( $arg->{fgcolor} ); # α値を変化させて画像を生成する my $max = $arg->{rate} * ( $arg->{fadein} + $arg->{hold} + $arg->{fadeout} ); for ( my $i = 0; $i < $max; $i++ ) { my $file = sprintf $arg->{filename}, $i; # α値計算 my $alpha; if ( $i < $arg->{rate} * $arg->{fadein} ) { $alpha = 1 / ( $arg->{rate} * $arg->{fadein} ) * $i; print "fade-in: $file, $alpha\n" if $DEBUG; } elsif ( $i < $arg->{rate} * ( $arg->{fadein} + $arg->{hold} ) ) { $alpha = 1; print "hold: $file, $alpha\n" if $DEBUG; } else { $alpha = 1 / ( $arg->{rate} * $arg->{fadeout} ) * ( $max - $i ); print "fadeout: $file, $alpha\n" if $DEBUG; } # 文字色 my $fgcolor = "rgba(" . join(",", @fgcolor[0, 1, 2], $alpha) . ")"; # 画像生成 text2image( { file => $file, text => $arg->{text}, size => $arg->{size}, bgcolor => $arg->{bgcolor}, fgcolor => $fgcolor, font => $arg->{font}, pointsize => $arg->{pointsize}, } ); } } # 文字を画像に変換する sub text2image { my $arg = shift; # オブジェクト生成 my $image = Image::Magick->new( size => $arg->{size} ); # キャンバス生成 $image->ReadImage( 'xc:' . $arg->{bgcolor} ); # キャンバスサイズ取得 my ( $width, $height ) = $arg->{size} =~ /(\d+)x(\d+)/g; # 文字描画位置取得 my ( $char_width, $char_height, $ascender, $descender, $text_width, $text_height, $max_advance, $bounds_x1, $bounds_y1, $bounds_x2, $bounds_y2, $origin_x, $origin_y ) = $image->QueryFontMetrics( encoding => 'UTF-8', text => $arg->{text}, font => $arg->{font}, fill => $arg->{fgcolor}, pointsize => $arg->{pointsize}, align => 'Center', ); # 文字描画位置計算(画面中央) my $lines = ( $arg->{text} =~ s/\\n|\n/\\n/g ); my $x = $width / 2; my $y = $height / 2 - $char_height * $lines / 2; # 文字描画 $image->Annotate( encoding => 'UTF-8', text => $arg->{text}, font => $arg->{font}, fill => $arg->{fgcolor}, pointsize => $arg->{pointsize}, align => 'Center', x => $x, y => $y, ); # ファイル出力 $image->Write($arg->{file}); }