LinuxでMPEG2 TSから字幕情報を抽出してFFmpegで動画に埋め込む方法
最終更新日:2016/05/22
LinuxでMPEG2 TSから字幕情報を抽出してFFmpegで動画に埋め込む方法について説明します。
生のMPEG2 TS
Linux録画サーバのChinachuやepgrec UNAで録画した場合、通常はMPEG2 TSファイルに字幕情報は含まれていません。理由は、recpt1コマンドのオプションで”–sid”を指定しているため、字幕情報などが省略されているためです。
そのため、字幕情報を抽出したい場合、省略されていない生(なま)のTSを録画する必要があります。ただし問題なのは、生TSはマルチ編成の番組(1チャンネルでニュースと野球を放送等)やEPG、ワンセグを全てまとめた1つのファイルに録画してしまうことです。そのため、ファイルサイズが”–sid”オプション指定時よりも大きくなります。
また、マルチ編成の番組の場合、PCでは現状はメインの番組の字幕情報しか抽出できなさそうです。私が調べた限りでは方法が見つかりませんでした。これは、マルチ編成で放送されることがあまり無い地デジではほとんど問題になりませんが、BSやCSで生のTSを録画すると他のチャンネルと一緒に録画されてしまうため注意が必要です。
生のTSを録画する方法は、epgrec UNAでは録画予約時、録画MODEに”Full TS”を選択するだけで良いです。Chinachuでは番組個別に録画方法を指定する方法がありません。そのため、Chinachuの環境ではrecpt1コマンドを直接実行して録画するしか方法はないでしょう(多分)。
字幕の抽出方法
Linuxで生TSから字幕情報を抽出するにはassdumperコマンドを使用します。調べた限り、Linuxで使い勝手の良いコマンドはこれ以外に見つかりませんでした。このコマンドはASS形式の字幕情報を出力します。
Ubuntu・Debianの場合) ※ Debianでは sudo を抜いてrootユーザーで実行して下さい $ sudo apt-get install subversion ruby $ svn export https: //github .com /eagletmt/eagletmt-recutils/trunk/assdumper $ cd assdumper $ make $ strip assdumper $ sudo cp -p assdumper assadjust.rb /usr/local/bin/ $ sudo chown root:root /usr/local/bin/ass * CentOS 7の場合) # yum install subversion ruby # cd assdumper # make # strip assdumper # cp -p assdumper assadjust.rb /usr/local/bin/ # chown root:root /usr/local/bin/ass* |
サーバー用の最小環境しかインストールしていない場合、日本語フォントをインストールする必要があります。
Ubuntu・Debianの場合) $ sudo apt-get install fontconfig fonts-takao その他の日本語フォントを探したい場合は以下を実行 $ apt-cache search fonts | grep -i japan あるいは $ apt-cache search fonts | grep -i 日本語 CentOS 7の場合) # yum install fontconfig ipa-pgothic-fonts その他の日本語フォントを探したい場合は以下を実行 $ yum search fonts | grep -i japan |
コマンドの使用方法は、生TSファイルを引数に指定し、リダイレクトでファイルに出力します。ただし、このままだと字幕の時間情報が録画時の時刻のままなので、録画開始時の時刻を0秒としてassadjust.rbスクリプトで相対時刻に直します。それらの手順を実行したものが以下となります。
$ assdumper input.ts > output.raw.ass $ assadjust.rb 19:00:00 output.raw.ass > output.ass |
assdumperコマンドを実行しただけのASSファイルの内容は以下になります。
1 2 3 4 5 6 7 8 9 | [Script Info] ScriptType: v4.00+ Collisions: Normal ScaledBorderAndShadow: yes Timer: 100.0000 [Events] Dialogue: 0,21:27:17.92,21:27:21.85,Default,,,,,, 下落率はともに6年連続で縮小しました。 Dialogue: 0,21:27:21.76,21:27:25.46,Default,,,,,, 中東やアフリカからドイツなどを目指す難民や移民。 |
assadjust.rbスクリプトで録画開始時刻を”21:27:17”に指定して実行した結果が以下になります。時刻がずれていることが分かります。
1 2 3 4 5 6 7 8 9 | [Script Info] ScriptType: v4.00+ Collisions: Normal ScaledBorderAndShadow: yes Timer: 100.0000 [Events] Dialogue: 0,00:00:00.92,00:00:04.85,Default,,,,,, 下落率はともに6年連続で縮小しました。 Dialogue: 0,00:00:04.76,00:00:08.46,Default,,,,,, 中東やアフリカからドイツなどを目指す難民や移民。 |
assadjust.rbスクリプトを使うのでも良いのですが、フォント情報が入っていないため、このままではFFmpeg用には使用できません。また、epgrec UNAでは録画ファイル名に録画開始時刻が含まれているため、ファイル名を元に時刻をずらすことが出来れば使い勝手が良さそうです。
と言う訳で、スクリプトを自作してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #!/usr/bin/perl use strict; use warnings; use utf8; use open ":utf8" ; use Getopt::Long; use File::Basename qw /basename dirname /; use Time::Local; # ************************ # ***** ユーザー設定 ***** # ************************ # assdumperの実行ファイル名 my $assdumper_cmd= "/usr/local/bin/assdumper" ; # 先頭のマージン秒 (59秒以内) my $margin_sec = 0; # ************************ # ***** 設定ここまで ***** # ************************ #binmode(STDOUT, ":utf8"); # 関数: ヘルプ sub usage { no utf8; print <<END; Usage: assdumperでMPEG2-TSファイルから字幕情報を抽出し、録画開始時刻を基準にして出力する 書式 assconv.pl -i|-input < file > -o|-output < file > [-h|-help] [-m|-margin <int>] [-s|-start <hh:mm:ss>] -h, -help 本内容の表示 -i, -input < file > 入力 MPEG2-TS ファイル名(必須) -o, -output < file > 出力 ASS ファイル名(必須) -m, -margin <int> 入力ファイルの先頭のマージン秒(59秒以内) -s, -start <hh:mm:ss> 入力 MPEG2-TS ファイルの録画開始時刻 注意事項 - 録画開始時刻の形式は "01:02:34" の様な8文字厳守. "1:2:34" の省略形式は不可. END exit (0); } my %opts = (); GetOptions(\%opts, qw(help|h input|i=s output|o=s start|s=s margin|m=s)) or die "Error(GetOptions)" ; unless (exists $opts{ 'input' } && exists $opts{ 'output' }) { &usage(); } elsif (exists $opts{ 'help' }) { &usage(); } my $ts_file = $opts{ 'input' }; my $ass_file = $opts{ 'output' }; my $start_time = "" ; # マージン秒指定 if (exists $opts{ 'margin' } && $opts{ 'margin' } < 60) { $margin_sec = $opts{ 'margin' }; } sub time2sec { my($time1) = @_; my($hour, $min, $sec, $msec) = split (/[:\.]/, $time1); return timegm($sec, $min, $hour, 1, 0, 0); } sub sec2time { my($sec_time) = @_; my($sec, $min, $hour, @tmp) = gmtime($sec_time); return sprintf( "%02d:%02d:%02d" , $hour, $min, $sec); } sub time_subtract { my($std_time, $rec_time) = @_; return &sec2time(&time2sec($rec_time) - &time2sec($std_time) + $margin_sec); } my $base_name = basename $ts_file; if (exists $opts{ 'start' }) { if ($opts{ 'start' } =~ /^(\d{2}:\d{2}:\d{2})/) { $start_time = $1; } else { &usage(); } } elsif ($base_name =~ /^20\d{6}_(\d{2})(\d{2})(\d{2})/) { $start_time = sprintf( "%02d:%02d:%02d" , $1, $2, $3) } elsif ($base_name =~ /^20\d{6}_(\d{2})(\d{2})/) { $start_time = sprintf( "%02d:%02d:00" , $1, $2) } else { die "not specific the start time" ; } my @result = `$assdumper_cmd $ts_file 2> /dev/null `; open (my $ass_handle, ">" , $ass_file) or die "can't open $ass_file: $!" ; foreach my $line (@result) { if ($line =~ /^Dialogue: 0,([0-9:]+)\.\d+,([0-9:]+)/) { my ($time1, $time2) = ($1, $2); my ($mod_time1, $mod_time2); $mod_time1 = &time_subtract($start_time, $time1); $mod_time2 = &time_subtract($start_time, $time2); if (&time2sec($time2) - &time2sec($time1) > 5) { $mod_time2 = &sec2time(&time2sec($mod_time1) + 5) } $line =~ s/${time1}/${mod_time1}/; $line =~ s/${time2}/${mod_time2}/; } print $ass_handle $line; } print $ass_handle <<END; [V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding Style: Default,TakaoPGothic,20,&H00FFFFFF,&H0000FFFF,&H00000000,&H80000000,0,0,0,0,90,90,0,0,1,1,1,2,20,20,20,128 END close($ass_handle); |
ダブルクリックするとスクリプト全体を選択した状態になるのでCtrl+Cでコピーして下さい。/usr/local/bin/ディレクトリなどに”assconv.pl”として保存して下さい(UTF8の改行コードはLF)。
# vi /usr/local/bin/assconv.pl # chmod +x /usr/local/bin/assconv.pl |
デフォルトのフォント指定は”TakaoPGothic”としています(スクリプトの132行目)。CentOS 7の場合、”IPAPGothic”として下さい。他のフォントに変える場合、fc-listコマンドでフォント名を確認して変更します。下記はコマンドの実行例です。日本語名の後ろにASSで指定可能な英語フォント名があります。
$ fc-list | grep -i takao /usr/share/fonts/truetype/fonts-japanese-gothic .ttf: Takao Pゴシック,TakaoPGothic:style=Regular /usr/share/fonts/truetype/takao-gothic/TakaoExGothic .ttf: TakaoExゴシック,TakaoExGothic:style=Regular /usr/share/fonts/truetype/takao-gothic/TakaoPGothic .ttf: Takao Pゴシック,TakaoPGothic:style=Regular /usr/share/fonts/truetype/takao-gothic/TakaoGothic .ttf: Takaoゴシック,TakaoGothic:style=Regular |
assconv.plスクリプトはファイル名の先頭が録画開始時刻(YYYYMMDD_hhmm[ss])の形式に対応しています。この形式のファイル名ならば、ファイル名から取得した録画開始時刻を元にASSファイルの時刻をずらして出力します。
epgrec UNAの環境で使用する場合、ファイル名の形式を以下とすると利用できます。
- %YEAR%%MONTH%%DAY%_%HOUR%%MIN%%SEC%_%TYPE%%CH%_%TITLE%
録画開始時刻の引数指定や録画開始前のマージン秒を指定することも可能です。下記は実行例です。詳細は”-h”オプションで表示される内容を確認して下さい。
$ assconv.pl -i 20151125_1900_GR27_NHKニュース.ts -o output.ass $ assconv.pl -i 20151125_190003_GR27_NHKニュース.ts -o output.ass -m 2 |
ASSファイルはVLC media playerでも使用可能なはずなのですが、ASSファイルを読み込むと再生できませんでした(字幕が表示できないのではなく、再生が始まりません)。Windowsで試したので、フォント名をWindowsのものに変更したり、色々と試したのですが駄目でした。少なくともFFmpegでは読み込むことができるので、今のところ良しとしています。
FFmpegで字幕を動画に埋め込む方法
FFmpegで字幕を動画に埋め込むには、libassに対応したFFmpegが必要です。本ブログではFFmpegのビルド方法を紹介しています。そちらの記事でlibassに対応したFFmpegをビルドする手順を説明しています。詳細は「LinuxでFFmpegをほぼ全自動でビルドする(CentOS、Ubuntu等に対応)」を参照して下さい。
下記はFFmpegのASSファイルの指定例です。
$ ffmpeg -i input.ts -vcodec libx264 -vf "subtitles=output.ass" output.mp4 下記のオプションでASSファイルを指定する。 -vf "subtitles=ASSファイル名" |

FFmpegによる字幕埋め込み例
上の画像は実際にFFmpegで作成した動画のスクリーンショットです。赤で囲った箇所がTakao Pゴシックで埋め込んだ字幕です。ASSではフォントサイズや位置など細かい指定ができます。詳細は参考ページを確認して下さい。