ffmpegで動画ファイルの静止画サムネイルを作成する
はじめに
「動画ファイルが多量に存在するけど,ファイル名が適当すぎて目的のファイルをなかなか見つけられない」状況が生じたのでメモ.
方法をざっくり言うと,特定ディレクトリ内のすべての動画ファイルについて,N x Nマスの静止画サムネイルを作成*1する.
やりかた
#!/bin/sh dir_path="." #1 tiles_h=8 #4 tiles_v=8 #4 tiles=$(($tiles_h * $tiles_v)) for file in `find $dir_path -type f \( -name "*.flv" -o -name "*.mp4" -o -name "*.avi" \)` do frames=`ffmpeg -i "$file" 2>&1 | awk '/totalframes/{print $3}'` if [ "$frames" = "" ]; then continue; else fps=$(($frames / $tiles)); #3 if [ $fps -gt 1000 ]; then #2 fps=1000; fi ffmpeg -y -i "$file" -vf thumbnail=${fps},tile=${tiles_h}x${tiles_v},scale=1920:-1 "${file%.*}_thumb.jpg"; #3 fi done
#1: サムネイル作成対象のディレクトリ.
#2: ffmpegのバグ?か分からないが,-vfオプションのthumbnail値であまりにも大きな値(例えば5000等)を設定するとエラーで落ちるため,1000を上限閾値とした.
#3: ここで静止画サムネイルを作成.出力サイズはscale値で設定し,-1の場合はソースから自動決定する.
#4: サムネイルの分割数.
ワンラインで書くと次の通り.コピペで便利.
$ for file in `find . -type f \( -name "*.flv" -o -name "*.mp4" -o -name "*.avi" \)`; do frames=`ffmpeg -i "$file" 2>&1 | awk '/totalframes/{print $3}'`; if [ "$frames" = "" ]; then continue; else (fps=$(($frames/64)); if [ $fps -gt 1000 ]; then fps=1000; fi; ffmpeg -y -i "$file" -vf thumbnail=$fps,tile=8x8,scale=1920:-1 "${file%.*}_thumb.jpg") fi; done
補足
定期的に実行する場合は,下記内容を「/home/ni66ling/local/bin/mk_thumbnail.sh」とかに保存して,
#!/bin/sh dir_path="." tiles_h=8 tiles_v=8 tiles=$(($tiles_h * $tiles_v)) for file in `find $dir_path -type f \( -name "*.flv" -o -name "*.mp4" -o -name "*.avi" \)` do frames=`ffmpeg -i "$file" 2>&1 | awk '/totalframes/{print $3}'` if [ "$frames" = "" ]; then continue; else fps=$(($frames / $tiles)); if [ $fps -gt 1000 ]; then fps=1000; fi ffmpeg -y -i "$file" -vf thumbnail=${fps},tile=${tiles_h}x${tiles_v},scale=1920:-1 "${file%.*}_thumb.jpg"; fi done
cronで定期実行するように設定してあげれば良さそう.
00 3 * * * /home/ni66ling/local/bin/mk_thumbnail.sh
まだ試してないけど,あとで試す予定.*2
追記 2014/10/12
上記でうまくいかないケースがあった*3ので,その対処を行った.
全フレーム数の取得を,totalframesから取得するのではなく,durationとfpsから算出するように変更した.
それと,ログ出力と,不要サムネイルの削除*4を追加した.
#!/bin/sh input_dir_path="." # 入力ディレクトリパス output_dir_path="${input_dir_path}/thumbs/" # 出力ディレクトリパス log_file_path="${output_dir_path}/mk_thumbnail_`date '+%Y%m%d%H%M%S'`.log" # ログファイルパス ffmpeg="./ffmpeg" # ffmpegバイナリパス find="/bin/find" # findバイナリパス tiles_h=8 # サムネイルタイルの水平方向数 tiles_v=8 # サムネイルタイルの垂直方向数 # 出力ディレクトリ生成 if [ ! -e "$output_dir_path" ]; then mkdir -p "$output_dir_path" fi # サムネイル作成対象のファイル数の取得 IFS=$'\n'; input_files_cmd='$find $input_dir_path -maxdepth 1 -type f \( -name "*.flv" -o -name "*.mp4" -o -name "*.avi" -o -name "*.ts" -o -name "*.wmv" \)' nof_files=`eval "$input_files_cmd" | wc -l` echo "#input files: $nof_files" | tee $log_file_path counter=1 for file in `eval "$input_files_cmd"`; do IFS=$' \t\n' printf "processing:$file ($counter/$nof_files) is started... " | tee -a $log_file_path counter=$(($counter + 1)) # 既にサムネイルがあるならスキップ input_filename="${file##*/}" output_filepath="${output_dir_path}${input_filename%.*}_thumb.jpg" if [ -e "$output_filepath" ]; then printf "already existing.\n" | tee -a $log_file_path continue fi # 動画フレーム数の計算(Frames = Duration * FPS) hms=(`echo \`$ffmpeg -i "$file" 2>&1 | awk '/Duration/{print $2}' | sed 's/\..*//g' | tr -s ':' ' ' \` `) if [ ${#hms[*]} -eq 0 ]; then printf "error (duration is not defined).\n" | tee -a $log_file_path continue fi duration_sec=`expr ${hms[0]} \* 3600 + ${hms[1]} \* 60 + ${hms[2]} \* 1` src_fps=`$ffmpeg -i "$file" 2>&1 | awk '/fps/{print $0}' | sed 's/\ fps.*//' | sed 's/.*\ //'` if [ "$src_fps" = "" ]; then printf "error (fps is not defined).\n" | tee -a $log_file_path continue fi frames=`echo "$duration_sec * $src_fps" | bc | sed 's/\..*//g'` # サムネイル作成処理 tiles=$(($tiles_h * $tiles_v)) thumb_fps=`expr $frames \/ $tiles` if [ $thumb_fps -gt 1000 ]; then thumb_fps=1000 fi ffmpeg_msg=`$ffmpeg -y -i "$file" -vf thumbnail=${thumb_fps},tile=${tiles_h}x${tiles_v},scale=1920:-1 "$output_filepath" \ 2>&1 > /dev/null | grep -E 'error|failed' | tr -d '\n'` if [ "$ffmpeg_msg" = "" ]; then printf "succeeded.\n" | tee -a $log_file_path else printf "error (ffmpeg:$ffmpeg_msg)\n" | tee -a $log_file_path fi done printf "making thumbnail is done !!\n\n" printf "================================================================================\n\n" # 出力ディレクトリにおけるサムネイルについて,対応するソースの動画が存在しないなら,サムネイルを削除 echo "checking existing thumbnails." IFS=$'\n'; input_files_cmd='$find $output_dir_path -maxdepth 1 -mindepth 1 -regex ".*_thumb\.jpg$"' nof_files=`eval "$input_files_cmd" | wc -l` echo "#thumbnail files: $nof_files" | tee -a $log_file_path counter=1 for file in `eval "$input_files_cmd"`; do counter=$(($counter + 1)) video_file_name=`echo $file | sed 's/.*\/\([^\/]*\)_thumb\.jpg/\1/' | sed 's/\(\[\|\]\|\*\|\?\)/\\\\\1/g'` find_video_cmd_result=`$find $input_dir_path -maxdepth 1 -mindepth 1 -name "${video_file_name}.*"` if [ "" = "$find_video_cmd_result" ]; then rm -f "$file" printf "source video file of thumbnail:${file} is missing. therefore, this thumbnail is removed.\n" | tee -a $log_file_path fi done printf "complete !!"
ソースコードはGitHubにおいています. https://github.com/KenshoFujisaki/MakeVideoThumbnail