labunix's blog

labunixのラボUnix

debian stretch+ffmpegで字幕ファイルsrt/assをなるべく簡単に作成してみる。

■debian stretch+ffmpegで字幕ファイルsrt/assをなるべく簡単に作成してみる。

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 9.1 (stretch)
Release:	9.1
Codename:	stretch

■assよりもsrtの方が書きやすそうだと感じたので、srtをベースにする。

 No行の単位で、表示文字について、
 表示開始時間と表示終了時間を除くと以下で書ける。
 サブリミナル効果のような感知出来ない文字は必要としていないので、
 秒以下の単位はすべて「,000」で共通とする。

No
表示開始時間(HH:MM:SS,000) --> 表示終了時間(HH:MM:SS,000)
表示文字
<改行>

■srtの形式について

 字幕ファイル (.srt) の書き方
 http://d.hatena.ne.jp/uogiawi/20150319/1426738108

■assの形式について

 ASSの仕組みと書き方
 http://www.geocities.co.jp/bancodesrt/tool/ass.html

■変換に関してエディタを必要としないようにする。
 なるべく簡単にという条件を満たすように、
 等間隔時間の表示を前提条件としているので、
 歌詞やコード、翻訳字幕の埋め込みなどには多くの修正が必要となる。
 srtからassに変換する方式を方針として、なんとなく表示出来れば良いというレベルまで。
 気になればそれに応じて修正するという方針で。(例えば改行コードはソースではなくsrtやassを編集して行う)
 結果としてゼロから書くより、修正すれば良いというだけでやる気になったという点が大きい。

■ffmpegのバージョンは以下。

$ ffmpeg -version | sed -e 's/ --/\n\t&/g'
ffmpeg version 3.2.5-1 Copyright (c) 2000-2017 the FFmpeg developers
built with gcc 6.3.0 (Debian 6.3.0-18) 20170516
configuration:
	 --prefix=/usr
	 --extra-version=1
	 --toolchain=hardened
	 --libdir=/usr/lib/x86_64-linux-gnu
	 --incdir=/usr/include/x86_64-linux-gnu
	 --enable-gpl
	 --disable-stripping
	 --enable-avresample
	 --enable-avisynth
	 --enable-gnutls
	 --enable-ladspa
	 --enable-libass
	 --enable-libbluray
	 --enable-libbs2b
	 --enable-libcaca
	 --enable-libcdio
	 --enable-libebur128
	 --enable-libflite
	 --enable-libfontconfig
	 --enable-libfreetype
	 --enable-libfribidi
	 --enable-libgme
	 --enable-libgsm
	 --enable-libmp3lame
	 --enable-libopenjpeg
	 --enable-libopenmpt
	 --enable-libopus
	 --enable-libpulse
	 --enable-librubberband
	 --enable-libshine
	 --enable-libsnappy
	 --enable-libsoxr
	 --enable-libspeex
	 --enable-libssh
	 --enable-libtheora
	 --enable-libtwolame
	 --enable-libvorbis
	 --enable-libvpx
	 --enable-libwavpack
	 --enable-libwebp
	 --enable-libx265
	 --enable-libxvid
	 --enable-libzmq
	 --enable-libzvbi
	 --enable-omx
	 --enable-openal
	 --enable-opengl
	 --enable-sdl2
	 --enable-libdc1394
	 --enable-libiec61883
	 --enable-chromaprint
	 --enable-frei0r
	 --enable-libopencv
	 --enable-libx264
	 --enable-shared
libavutil      55. 34.101 / 55. 34.101
libavcodec     57. 64.101 / 57. 64.101
libavformat    57. 56.101 / 57. 56.101
libavdevice    57.  1.100 / 57.  1.100
libavfilter     6. 65.100 /  6. 65.100
libavresample   3.  1.  0 /  3.  1.  0
libswscale      4.  2.100 /  4.  2.100
libswresample   2.  3.100 /  2.  3.100
libpostproc    54.  1.100 / 54.  1.100

■動画の全体再生時間の取得
 以下のうち「-show_entries」を使う。
 再生時間=字幕表示時間とすると、平均8秒間隔で埋め込んでいけば良さそう。

$ ffprobe -hide_banner target.mp4 2>&1 | awk -F\. '/Duration/{gsub(",","",$2);print $2}'
00:06:17.90

$ ffprobe -hide_banner target.mp4 2>&1 | awk -F\. '/Duration/{gsub("  Duration: ","",$1);print $1}'
00:06:17

$ ffprobe -hide_banner -show_entries format=duration target.mp4 2>&1 | awk -F = '/duration/{print $2}'
377.903333

$ echo $(ffprobe -hide_banner -show_entries format=duration target.mp4 2>&1 | awk -F = '/duration/{print $2}') \
  $(awk 'BEGIN{count=0}/./{count+=1}END{print count}' source.txt) | awk '{print $1/$2}'
8.78845

■マージ用ファイルを作成してsrtを出力、assに変換
 ここでは単純にsrt形式が使用出来るかの確認を目的にしているが、
 出来たassを編集しても良い。

$ t=$(ffprobe -hide_banner target.mp4 2>&1 | awk -F\. '/Duration/{gsub("  Duration: ","",$1);print $1}')
  d="$(date +%F) $t"; \
  alltime=$(ffprobe -hide_banner -show_entries format=duration target.mp4 2>&1 | awk -F = '/duration/{print $2}'); \
  line=$(awk '/./{cnt+=1}END{print cnt}' source.txt); \
  interval=$(echo $alltime $line | awk '{print $1/$2}'); \
  for n in $(seq 1 $line);do \
    echo $d;d=$(date '+%F %H:%M:%S' -d "$d $interval seconds ago"); \
  done | sort -n | \
  awk 'BEGIN{a="00:00:00"}($2!="00:00:00"){print a",000 --> "$2",000";a=$2}' | \
  awk 'BEGIN{count=0}/./{count+=1;print count,$0}' > mergetime.txt

$ awk 'BEGIN{count=0}/./{count+=1;print count,$0}' source.txt > mergesource.txt

$ join -j 1 mergetime.txt mergesource.txt | \
    sed -e 's/^\([0-9]*\) /\n\1\n/' -e 's/\(--> .*,000\) /\1\n/' | \
    awk '(NR!=1){print}END{print ""}' > target.srt

$ ffmpeg -i target.srt output.ass 2>&1 | awk '!/built with|configuration/'
ffmpeg version 3.2.5-1 Copyright (c) 2000-2017 the FFmpeg developers
  libavutil      55. 34.101 / 55. 34.101
  libavcodec     57. 64.101 / 57. 64.101
  libavformat    57. 56.101 / 57. 56.101
  libavdevice    57.  1.100 / 57.  1.100
  libavfilter     6. 65.100 /  6. 65.100
  libavresample   3.  1.  0 /  3.  1.  0
  libswscale      4.  2.100 /  4.  2.100
  libswresample   2.  3.100 /  2.  3.100
  libpostproc    54.  1.100 / 54.  1.100
Input #0, srt, from 'target.srt':
  Duration: N/A, bitrate: N/A
    Stream #0:0: Subtitle: subrip

■動画と字幕をマージする。
 以下2つはsrt、assの変換。動画変換も行われるので特にassファイルの統合に時間がかかる。
 3つ目は動画の変換は不要という場合はsrtなら以下で出来る。ただし、assではエラーになって字幕の無い動画が出来てしまう。

$ ffmpeg -i target.mp4 -vf subtitles=target.srt output.mp4
$ ffmpeg -i target.mp4 -vf ass=output.ass output_ass.mp4
$ ffmpeg -i target.mp4 -i target.srt -c copy -c:s mov_text  output_cp.mp4

■assで作ってしまった人は以下でsrtに変換出来る。

$ ffmpeg -i output.ass output.srt 2>&1 | awk '!/built with|configuration/'
ffmpeg version 3.2.5-1 Copyright (c) 2000-2017 the FFmpeg developers
  libavutil      55. 34.101 / 55. 34.101
  libavcodec     57. 64.101 / 57. 64.101
  libavformat    57. 56.101 / 57. 56.101
  libavdevice    57.  1.100 / 57.  1.100
  libavfilter     6. 65.100 /  6. 65.100
  libavresample   3.  1.  0 /  3.  1.  0
  libswscale      4.  2.100 /  4.  2.100
  libswresample   2.  3.100 /  2.  3.100
  libpostproc    54.  1.100 / 54.  1.100
Input #0, ass, from 'output.ass':
  Duration: N/A, bitrate: N/A
    Stream #0:0: Subtitle: ass
Output #0, srt, to 'output.srt':
  Metadata:
    encoder         : Lavf57.56.101
    Stream #0:0: Subtitle: subrip (srt)
    Metadata:
      encoder         : Lavc57.64.101 srt
Stream mapping:
  Stream #0:0 -> #0:0 (ass (ssa) -> subrip (srt))
Press [q] to stop, [?] for help
size=       3kB time=00:06:09.00 bitrate=   0.1kbits/s speed=6.65e+04x    
video:0kB audio:0kB subtitle:2kB other streams:0kB global headers:0kB muxing overhead: 72.375420%

■3つ目の「output_cp.mp4」がすぐに出来て楽なので、後は動画を見ながらエディタ編集で調整する。
 target.srtから表示開始や終了時間と、1行内の文字列の修正の両方を修正した。

$ test -f target.srt.org || cp target.{srt,srt.org};vlc output_cp.mp4 &
$ ffmpeg -i target.mp4 -i target.srt -c copy -c:s mov_text  output_cp.mp4

■ハードサブ
 「-c copy」では動画再生時にフォント読み込みが行われるので「-vf」オプションで行う。
 このための処理に時間がかかってたのか。

$ ffmpeg -i target.srt output.ass 2>&1 | awk '!/built with|configuration/'
$ ffmpeg -i target.mp4 -vf ass=output.ass output_ass.mp4
...
[Parsed_ass_0 @ 0x5613be5abda0] fontselect: (Arial, 400, 0) -> /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf, 0, LiberationSans
[Parsed_ass_0 @ 0x5613be5abda0] Glyph 0x30D4 not found, selecting one more font for (Arial, 400, 0)
[Parsed_ass_0 @ 0x5613be5abda0] fontselect: (Arial, 400, 0) -> /usr/share/fonts/truetype/vlgothic/VL-Gothic-Regular.ttf, 0, VL-Gothic-Regular
...

$ ffmpeg -i target.mp4 -vf subtitles=target.srt output.mp4
...
[Parsed_subtitles_0 @ 0x556737f2ada0] fontselect: (Arial, 400, 0) -> /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf, 0, LiberationSans
[Parsed_subtitles_0 @ 0x556737f2ada0] Glyph 0x30D4 not found, selecting one more font for (Arial, 400, 0)
[Parsed_subtitles_0 @ 0x556737f2ada0] fontselect: (Arial, 400, 0) -> /usr/share/fonts/truetype/vlgothic/VL-Gothic-Regular.ttf, 0, VL-Gothic-Regular
...

■埋め込みフォントの分、元のファイルよりも大きくなる。
 srtの追加だけなら微々たる変化。
 srtとassにファイルサイズの違いは無い。

$ du *.mp4
53508	output.mp4
53508	output_ass.mp4
34236	output_cp.mp4
34068	target.mp4