[ffmpeg] image.png X soundonly.mp4 -> combined.mp4

[ffmpeg] image.png X soundonly.mp4 -> combined.mp4

暇つぶしに ffmpeg で遊びます

音声のみの mp4 ファイルがいくつか手元にあります。mp3 や wav から変換しただけの代物ゆえ、当然映像情報は入っていません。しかしメディアプレーヤーで再生中、音声だけでは寂しいので何らかの映像も伴うちゃんとした?ビデオコンテンツに仕立てたいと思いたちました
とはいえ商用 PV のように立派である必要はありません。適当な静止画がプレースホルダーみたいな感じで再生中に表示されていればよいという程度です

前提条件

sudo apt install -y ffmpeg mediainfo

基本のコマンドは次のようになります

ffmpeg -i placeholder.png -i soundonly.mp4 output.mp4

これで placeholder.png が統合され、冒頭の画像のように再生中に表示されるようになります

要件

これを実際は複数の soundonly*.mp4 に対して適用させたいのですが、"映像あり*.mp4" と同じフォルダに混在させてしまっており、このため目的物だけを抽出しなければいけません。あとさっき静止画は適当でいいやと言いましたが、同じ画像を使いまわして金太郎飴みたいになるのもつまらないので一捻りしたいところ

要件を整理すると次のようになります

  • soundonly*.mp4 だけを、映像あり*.mp4 と混在している中から抽出したい
  • 複数の素材を対象にしたい
  • 同じ画像を使いまわしたくない

設計

前掲のような要件に柔軟に対応できるのが CLI の醍醐味です。これだから黒い画面はやめられませんな(笑)
さいごに一つのシェルスクリプトとして仕上げますが、まずは個別にスニペットにして解決していきましょう

soundonly*.mp4 だけを抽出したい

ls "${playlist}"/*.mp4 | while read m; do if ! mediainfo "$m" | grep -Ei '^Video$' > /dev/null; then echo "$m"; fi; done

${playlist} ディレクトリ内にある mp4 ファイル群の中から、映像情報を有しない、つまり音声のみの mp4 を抽出しています

複数の素材を対象にしたい

前掲と同様の while loop の中で基本のコマンドを実行すればよいです

ls "${playlist}"/*.mp4 | while read m; do
  if mediainfo "$m" | grep -Ei '^Video$' > /dev/null; then
    continue
  fi
  image=[PATH_TO_IMAGE].png
  ffmpeg                                    \
    -nostdin                                \
    -i $image                               \
    -i $m $DESTDIR/`basename ${m%.*}`.mp4
done

ffmpeg に -nostdin を付けないと while loop が意図せず break して しまいましたのでこうしています

同じ画像を使いまわしたくない

予め静止画素材専用のディレクトリを作成し、その中に適用させたい複数の画像ファイルを置いておき、ランダムに選択させることにします。ランダムにする大した意味はございません。この音声にはこの画像だ、などの判断をいちいち自分でするのが面倒くさいからです(笑)。「不真面目だ、けしからん」などとお叱りをいただくかも知れませんがお遊びなのでこれで充分なのです。もしクリエィティブセンスに優れた AI が実用可能ならそいつに選定作業をやってもらいたいところですが将来課題にします
画像選定部分だけに注目すると次のような感じになります

image_assets=[WHERE_IMAGE_ASSETS_RESIDE]
image_expired=`mktemp`

ls "${playlist}"/*.mp4 | while read m; do
  image=$(
    cat $image_expired <(ls $image_assets/*.{jpg,png,JPG,PNG} 2>/dev/null) |
    sort                                                                   |
    uniq -u                                                                |
    sort -R                                                                |
    head -n1
  )
  echo $image | tee -a $image_expired
done

一度選んだ画像が重複しないように配慮しています

実装・試用

前掲をもとに作成したスクリプト batch_combine.sh と用意した素材は次のように配置しています

% tree
.
├── batch_combine.sh
├── image_assets
│   ├── placeholder1.png
│   ├── placeholder2.png
│   ├── placeholder3.png
│   └── placeholder4.png
└── playlist
    ├── non-soundonly-should-be-out-of-sight.mp4
    ├── soundonly_1.mp4
    ├── soundonly_2.mp4
    ├── soundonly_3.mp4
    └── soundonly_4.mp4

実行すると

% ./batch_combine.sh $PWD/playlist $PWD/image_assets
ffmpeg version 4.3.2-0+deb11u2 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 10 (Debian 10.2.1-6)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[libx264 @ 0x56399bd44580] kb/s:2647.80
[aac @ 0x56399bd43380] Qavg: 1569.309
/tmp/tmp.aL0whwndXO/soundonly_1.mp4  /tmp/tmp.aL0whwndXO/soundonly_3.mp4
/tmp/tmp.aL0whwndXO/soundonly_2.mp4  /tmp/tmp.aL0whwndXO/soundonly_4.mp4
%ls /tmp/tmp.aL0whwndXO/*.mp4 | while read m; do mv $m `echo $m | sed -e 's/soundonly/combined/'`; done

できたようです。アウトプット先のファイル名が soundonly のままになってしまっており紛らわしいのでリネームしています
mediainfo で検査すると Video のセクションが出来ています

これは soundonly のときにはありませんでした。抽出工程でもこの特徴を利用していたわけですね

再生して確かめると

OK. 色違いになっているので異なるプレースホルダー画像が適用されているのがわかります。4つとも確認しましたがそれぞれ異なっていました。期待通りです

備考

基本的な部分は こちら を参考にしました

今回は png X mp4 -> mp4 でしたが、その他の拡張子でもいける模様。 ffmpeg は難解な点もありますが懐の深いツールだと思います。これに限らず伝統的なコマンドラインツールで音声や画像ファイルを扱うのは楽でいいものです