INDEX
-
- 前回のあらすじ
-
- 目的
-
- やろうず!
-
- あとがき
1. 前回のあらすじ
②の続き。今回は②と独立した記事なので読まなくても大丈夫。
https://qiita.com/neonemo/items/d512e9fce2d47bcca0ab
PCの環境設定なんかは①の方を参考にしてね。こっちは必須。読み終わったら戻ってきて!
https://qiita.com/neonemo/items/3f178915bf3f9de23f9c
2. 目的
- カレントフォルダに入っているpngファイルを一括で良い感じの品質かつサイズのjpgに変換する
- 4k画質のpngとか扱いにく過ぎるので、具体的には次のような処理をする
- 品質70%程度のjpgに変換する
- 画像サイズの長辺が1920pxを超える場合は、縦横比を維持したまま1920pxに縮小する(フルHD相当)
- 4k画質のpngとか扱いにく過ぎるので、具体的には次のような処理をする
3. やろうず!
今回は外部ツールとしてffmpegを組み込んで目的を達成するので、こんな流れになる。
- ffmpegをセットアップする
- バッチコマンドを作る
- 動作確認をする
3.1. コマンドの仕様を決める
今回は、若干動きが複雑なのでこんな感じでバッチファイルを2つ作り内部的に2段階の処理をする。
青い箱が今回作成するバッチコマンドになる。
で、実際にコマンドを使う時はこう!
想定状況にぶち当たった!画像がでかいぞ!
> dir
ドライブ D のボリューム ラベルは Application です
ボリューム シリアル番号は xxxx-xxxx です
D:\tmp のディレクトリ
2021/01/05 04:30 <DIR> .
2021/01/05 04:30 <DIR> ..
2021/01/05 04:30 1,000,000 0001.png
2021/01/05 04:30 1,000,000 0002.png
2021/01/05 04:30 1,000,000 0003.png
3 個のファイル 3,000,000 バイト
2 個のディレクトリ 1,737,524,916,224 バイトの空き領域
よし、変換しよう!
> convjpg start
再びdirで確認。jpgに変換して小さくなった。やったね!
> dir
ドライブ D のボリューム ラベルは Application です
ボリューム シリアル番号は xxxx-xxxx です
D:\tmp のディレクトリ
2021/01/05 04:30 <DIR> .
2021/01/05 04:30 <DIR> ..
2021/01/05 04:35 100,000 0001.png
2021/01/05 04:35 100,000 0002.png
2021/01/05 04:35 100,000 0003.png
3 個のファイル 300,000 バイト
2 個のディレクトリ 1,737,525,616,224 バイトの空き領域
3.2. ffmpegをセットアップする
3.2.1. セットアップ
- 公式(https://ffmpeg.org/)からffmpegを入手してくる。
- 自分は (CODEX FFMPEG)のページからrelease版のこれを入手、任意のフォルダに展開してWindowsのパスを通した。
-
- ちなみに自分は、配置場所を
D:\opt\ffmpeg\ffmpeg-4.3.1-2020-11-19-full_build
にして、シンボリックリンクでD:\opt\ffmpeg\latest
から実フォルダを辿れるようにした。
> cd /d d:\opt\ffmpeg
> mklink /d latest ffmpeg-4.3.1-2020-11-19-full_build
latest <<===>> ffmpeg-4.3.1-2020-11-19-full_build のシンボリック リンクが作成されました
> dir
ドライブ D のボリューム ラベルは Application です
ボリューム シリアル番号は xxxx-xxxx です
d:\opt\ffmpeg のディレクトリ
2020/12/03 01:17 <DIR> .
2020/12/03 01:17 <DIR> ..
2020/11/22 19:57 <DIR> ffmpeg-4.3.1-2020-11-19-full_build
2020/12/03 01:17 <SYMLINKD> latest [ffmpeg-4.3.1-2020-11-19-full_build]
0 個のファイル 0 バイト
4 個のディレクトリ 551,372,156,928 バイトの空き領域
>
3.2.2. 確認
コマンドプロンプトで次のような応答が得られればOK
> where ffmpeg
D:\opt\ffmpeg\latest\bin\ffmpeg.exe
> where ffprobe
D:\opt\ffmpeg\latest\bin\ffprobe.exe
>
3.3. バッチコマンドを作る
3.3.1. ソース
- 今回はソースコードが長くなってしまったのでGitHubにアップした。
3.3.2. ピックアップ解説
画像の解像度取得にはffprobeコマンドを使う
ffprobe -v [ログ出力レベル] -select_streams [標準出力したい] -show_entries [出力したいパラメータ] -of [出力形式] [出力ファイル]
rem メイン処理
set CMD_FFPROBE=ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of default=nw=1 "%~1"
- 後でfor文で実行する為にffprobeコマンドを一旦変数に格納している
-
%~1
のようにチルダを付けると、括ったダブルコーテーションを除去した状態になる-
echo %1
だと"hello!"
というような時にも… -
echo %~1
とするとhello!
と出力するようになる
-
-
ffprobe
はffmpeg
に含まれるコマンドだ。これを使うと画像以外にも動画サイズやコーデック情報なんかも標準出力してくれる。 -
ffprobe
コマンドをオプション無しでファイルだけ指定するとものすごい量の出力がある。コマンド単独で実行して、どんなパラメータが取れるのかを確認すると理解も深まると思う。- 全部解説する気力は無いのでお願いします。マジやばいですアレ。
for /f "usebackq" %%a in (`!CMD_FFPROBE!`) do (
set REPLY=%%a
echo "!REPLY!" | findstr "width" > nul 2>&1
set IS_WIDTH=!ERRORLEVEL!
echo "!REPLY!" | findstr "height" > nul 2>&1
set IS_HEIGHT=!ERRORLEVEL!
if "!IS_WIDTH!"=="0" (
set _AL_WIDTH=!REPLY:~6!
) else if "!IS_HEIGHT!"=="0" (
set _AL_HEIGHT=!REPLY:~7!
)
)
- 27行目は標準出力した解像度を拾う処理になっている
-
REPLY
にはwidth=xxx
かheight=xxx
というffprobeの実行結果が入ってくる-
findstr
はキーワードがヒットすると実行結果コードが0
となる性質があるので、どっちの値が入っているのかをecho
とfindstr
コマンドの合わせ技で判断している。
-
- 最後に
%環境変数名:~m,n%
という指定方法で他言語でいう部分文字列取得(substr()
のような)処理が出来ので、数字部分のみを取得している-
width=
で6文字だから!REPLY:~6!
だと【6文字目より後ろから最後まで】を取得する -
m
は正の数で指定し、先頭からの文字数。省略時は最初から -
n
は負の数で指定し、末尾からの文字数を指定する。省略時は末尾から - プログラム的には違和感があるけど、負数のみを指定するという第1引数のmを省略する仕掛けがある
-
echo !_AL_WIDTH! !_AL_HEIGHT!
- これは単純に呼び出し元で拾うための標準出力である。1行で出している理由もありそれは後述する。
標準出力のコマンド結果はfor /f "usebackq"
で拾う
for /f "usebackq" %%a in (`dir /b`) do (
set HOGE=%%a
)
- 標準出力を1行ずつ回してしまうので、内容を保持したい場合は上書いてしまわない様に変数の持ち方に気を付けよう!
- ついでにHOGEを
%HOGE%
で呼び出してしまうと最初の代入以降値が変わらなくなる。そこで遅延環境変数
という仕組みを使う必要が出てくる。ビックリマークで括っているアイツだ。- 参考: 遅延環境変数 - 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
さらに標準出力のコマンド結果を分割したい時はfor /f "tokens=1,2,…"
を使う
for /f "usebackq tokens=1,2" %%b in (`get-resolution "!TARGET_FILE!"`) do (
set _AL_WIDTH=%%b // ← tokens=1の内容
set _AL_HEIGHT=%%c // ← tokens=2の内容
)
- 最初の例の
usebackq
だけだと、%%aには1行分丸ごと入っていた。tokensを使うとスペースで区切った時の何番目を割り当てるかという動きをする。割り当て用変数%%a
はアルファベット1文字限定で、a, b, c, …といった具合に順番にtokenの番号を割り当てられる。- ちなみに
get-resolution
コマンドは、解像度を3840 2160
の形式で1行で返すように実装しているので、tokens
で分割するのに向いている。 - あれだ。Linuxの
awk
に似てる。for /f "usebackq tokens=1,3"
と書いたらawk '{print $1 $3}'
みたいな感じ。
- ちなみに
サポートファイル(pngとjpg)の振り分けを拡張子で行う
call :IS_SUPPERTED_EXT "!FILE_EXT!"
rem ------------------------------
rem サポートする拡張子かどうかを返す
rem [0] 拡張子
rem ret: 0: サポートしない / 1: ~する
rem ------------------------------
:IS_SUPPERTED_EXT
setlocal enabledelayedexpansion
set ret=0
if "%~1"==".jpg" (
set ret=1
) else if "%~1"==".png" (
set ret=1
)
endlocal && set ret=%ret%
exit /b %ret%
- 見出し通り。
環境変数名:~x
みたいな感じにすると変数全体のダブルコーテーションを取り除いた上に拡張子が取れる。- forループの割り当て変数の場合、例えば
%%a
を使っている時、拡張子は%%~xa
で取れる
- forループの割り当て変数の場合、例えば
- この性質を使って疑似サブルーチンを作ってファイル単位で判定している
-
endlocal && set ret=%ret%
は疑似サブルーチンから戻り値を返す仕掛け。endlocal
をした時点で遅延環境変数が使えなくなってしまうが&&
でつないだ時はその行だけ使える。- この性質を使うと
for
の遅延環境変数からsetlocat
の外に遅延環境変数が持ってこられるようになる
- この性質を使うと
画像変換用コマンドを組み立てて実行する
ffmpeg -i [入力ファイル] -vf [スケールオプション] -loglevel [ログレベル] -q [jpgの品質] [出力ファイル]
rem 変換コマンドを組み立てて実行する
set CONV_CMD=ffmpeg -i "!TARGET_FILE!" !CONV_SCALE! -loglevel warning -q 5 "!CONV_FILE!"
echo !CONV_CMD!
!CONV_CMD!
-
ffmpeg
コマンドを組み立てて実行するだけ- コマンドを環境変数
CONV_CMD
に格納しているのは進捗代わりにecho
したかったから
- コマンドを環境変数
3.4. 動作確認をする
3.4.1. 素材
- テストデータ用に高解像度でクッソでかいPNGファイルをいくつか用意した。必要ならどうぞ。(テスト用PNG)
- GoogleDriveなんでその内消すかもしれないのでそん時はゴメン!
- テストデータは好きな場所においてください。自分は
d:\tmp
に格納した。
3.4.2. コマンド
> cd /d d:\tmp
> dir
> convjpg
> dir
実際にやったところ
> dir
ドライブ D のボリューム ラベルは Application です
ボリューム シリアル番号は xxxx-xxxx です
d:\tmp のディレクトリ
2021/01/05 07:59 <DIR> .
2021/01/05 07:59 <DIR> ..
2021/01/05 05:50 7,429,541 0001.png
2021/01/05 05:50 7,404,285 0002.png
2021/01/05 05:51 7,415,862 0003.png
2021/01/05 07:59 22,229,569 testdatas.zip
4 個のファイル 44,479,257 バイト
2 個のディレクトリ 551,269,953,536 バイトの空き領域
> convjpg start
ffmpeg -i "0001.png" -vf "scale=1920:-1" -loglevel warning -q 5 "convjpg_0001.jpg"
[png_pipe @ 0000024dc9b6dfc0] Stream #0: not enough frames to estimate rate; consider increasing probesize
[swscaler @ 0000024dc9ba64c0] deprecated pixel format used, make sure you did set range correctly
ffmpeg -i "0002.png" -vf "scale=1920:-1" -loglevel warning -q 5 "convjpg_0002.jpg"
[png_pipe @ 0000025d7676dfc0] Stream #0: not enough frames to estimate rate; consider increasing probesize
[swscaler @ 0000025d767a6400] deprecated pixel format used, make sure you did set range correctly
ffmpeg -i "0003.png" -vf "scale=1920:-1" -loglevel warning -q 5 "convjpg_0003.jpg"
[png_pipe @ 0000017b86eadfc0] Stream #0: not enough frames to estimate rate; consider increasing probesize
[swscaler @ 0000017b86ee6d40] deprecated pixel format used, make sure you did set range correctly
skipped. / file=[testdatas.zip]
> dir
ドライブ D のボリューム ラベルは Application です
ボリューム シリアル番号は xxxx-xxxx です
d:\tmp のディレクトリ
2021/01/05 08:05 <DIR> .
2021/01/05 08:05 <DIR> ..
2021/01/05 08:05 257,887 0001.jpg
2021/01/05 08:05 259,294 0002.jpg
2021/01/05 08:05 259,871 0003.jpg
2021/01/05 07:59 22,229,569 testdatas.zip
4 個のファイル 23,006,621 バイト
2 個のディレクトリ 551,291,424,768 バイトの空き領域
3.4.3. 変換前後の画像の違い
- 正直画質的な違いがあるようには見えない…耄碌したかな…。なお容量圧縮率は約96.5%!やりすぎw
- 変換前(フルカラーPNG: 4K:3860x2160/容量:7,429,541byte)
- 変換後(劣化が目立たない程度のJPG: フルHD:1920x1080/容量:257,887byte)
4. あとがき
3回の連続記事はこれで終わりだ。ぶっちゃけ解説よりもソースを見てください。お願いします(えー
今回はやりたい事が簡単な割に細かい部分で結構苦労した。ラクをするという観点だと矛盾すると思われそうだが、自分はシステム屋さんなので、他人をラクさせても組織の利益になるので良いのである。
組織内の誰かがハズレくじを引くだけの話だ。まあこの場合は切り込み隊長をする自分だな。orz
まあ、それは冗談にしても自動化、半自動化は属人化する部分をツールに外だしする事で人の差を消せる部分もあるので、過去に自動化できないと思っていた作業を再びやる事があったらもう一度自動化出来ないかは考えてみた方が良いと思う。
人は成長するんだから、過去の自分は閃いてなく今の自分なら閃く可能性があるしね!
最後に、GitHubに上げたソースコードは、私的改造もするつもりマンマンなのでこの記事との差分が出るかもしれないがその時はごめんなさい。なるべくタグで切るようにします><
以上!