2024/09/06(金)写真画像ファイルにフィルム写真風の日付を挿入するシェルスクリプト改3

3月に思いつきで作成したシェルスクリプトを改良したので公開します.
前回の記事は,説明がわかりづらく,不十分だったため,もう少し解説を充実させました.
簡単な内容ですが,誰かのお役に立てれば嬉しいです.

p20180813__IMP0221_TS.jpg
2018:08:13 14:34:15 PENTAX PENTAX Q7 / 3.8mm F5.0 0.001秒 ISO100



1 できること

本バージョン“改3”では,次の機能が実装されています.
  1. 一般的なデジタルカメラで撮影した写真ファイルに,後付けでフィルム写真風の日付を追加します.
    • 実際のフィルムカメラのデート機能による日付よりも大きく,ハッキリと表示されるように設定してあります.
    • これは,プロジェクター等で投影したときに判読しやすいことが必要と考えたからです*1.用途に応じて調整してください.
  2. 出力される文字は,オレンジ色に塗りつぶされ,指定幅でグレー(透過の薄い黒)にて縁取られた文字です.
    • 塗りつぶしは単純なものではなく,画像がわずかに透けて見えるように調整してあります(透過率設定がしてあります).
      これは,デート機能による日付写し込みの挙動をちょっとだけ再現する意図です*2
    • 縁取ってあるのは可読性を向上するためです.ただし,完全な黒で縁取ると雰囲気を損ねるので薄く縁取っています.
      濃淡は好みで変更してください.
    • 右下方向に影も出ます*3.デート機能の写し込みの挙動の滲みっぽいものを表現できればと思ったのですが,イマイチです.
  3. 写真ファイルのピクセル幅と高さを数えて,画面と日付の大きさの比率を維持して日付を挿入します.
    • 機材によって写真のピクセルサイズが異なるのは当然なので,この調整機能は必須と考えました.
    • 写真のピクセルサイズが小さすぎると破綻すると思います.撮って出しの大きなJPEGファイルを処理対象にするのが推奨です.
  4. 処理後の写真ファイルは先頭に年月日が追加されます.
    • 元ファイルの名前が“_IMP0221.JPG”だった場合,“p20180813__IMP0221_TS.jpg”のようになります.
    • 元ファイルの名前は恐らくどのような文字列・長さでも対応しています.ただし半角英数の短めのファイル名が推奨です.
    • 万が一画像処理に失敗しても元のファイルが消失することは多分ありません.
  5. 処理後の写真ファイルのEXIF情報は維持されます.写真のピクセルサイズも変わりません.画質は少し劣化します(JPEGファイルをそのまま複数回処理しているため.).
市販ソフトでも似たような機能のものはあると思いますが,それらに対する優位性は以下のようなことだと思います.
  1. 無料(ただし無保証.(一応ですが)GPLライセンスとします)
  2. 好きなフォントに差し替え可能.
  3. 日付の位置や大きさ等を,コードを改変して自由に設定できる.
  4. コマンドライン(bashシェル)からお手軽に操作できる.
  5. 写真ファイルのピクセルサイズがバラバラでも,配置やサイズの比率を維持して大量に処理できる.
今の時点での問題点は
  1. 日付を挿入するだけなのに割と時間が掛かる(ImageMagickを何度も実行しているため).
  2. パイプ入力に対応していない.
  3. 1999年以前の日付に対応していない*4
  4. 縦構図の写真を処理する際,処理前に画像を回転していると,日付の挿入位置が縦横反転してしまう*5
    挿入位置を揃えたい場合は,本シェルスクリプトの処理前に回転操作をしないようにするのが手っ取り早いですね.
私が使う分にはあまり問題ではないので,改良版は出ない可能性が高いです.

*1 : 実は本記事のシェルスクリプトは,プレゼンや宴会の場で利用することを第一目標として,書き始めたものです.

*2 : カメラ用日付写し込みデートモジュール等に原理が書いてあります.写真フィルムに重ねて日付文字の光を当てているため,条件によっては文字の下の像も薄く写り込みます.

*3 : 下のコードの数値設定ではほとんど目立ちません.必要に応じて調整してください.

*4 : 私がデジカメを使い始めたのは2000年以降なので恐らく影響はないと思っています.sedの置換処理を1行足せば良いだけだと思いますが…….

*5 : 短編側が横幅扱いになって,サイズは小さくなってしまいますが,日付文字が見たまま水平に挿入されるので見やすくなります.

2 使い方

次の2行で説明できますが,例を示して補足します.好みのやり方を見つけてください.
私は例の通り,WindowsとWSL上のLinuxを使って処理するのが好みです.
  1. 最初に何らかの手段を使って,日付を入れたいファイルのリストをテキストファイルで作成します.
  2. 作成したテキストファイルのリストを,シェルスクリプトの引数に指定して実行します.

2.1 日付を入れたいファイルのリストをテキストファイルで作成する例

一般的なデジタルカメラで撮影した写真ファイルの拡張子は,大文字で“.JPG”となっています.
この点に着目して,lsとgrepを使いリストファイルを作成します.

WSL(Windows Subsystem for Linux)ならば,写真ファイルが保存してあるフォルダへエクスプローラーで移動し,シフトキーを押しながら右クリックしてください.
表示されるコンテキストメニューで『Linux シェルをここに開く』を実行し,WSLを作業フォルダで開きます.

開かれたシェルで,例えば,“ls | grep JPG > list.txt”と入力してエンターキーを押します.
$ ls | grep JPG > list.txt
$
これは,カレントディレクトリ(作業フォルダ)内で“JPG”という文字列がファイル名に含まれたファイルを検索し,
“list.txt”というファイルへ書き込むコマンドです.
検索する文字列が“JPG”ではなく,他の文字列でも構いません.
各自のファイルの分類方法に従ってリストを作成します.
なお,この例では“list.txt”の中身は次のようなものとします.
IMGP4676.JPG
IMGP4677.JPG
_IMP0221.JPG

2.2 シェルスクリプトの実行例

本記事の下に記述してあるシェルスクリプトのコードをコピーし,
作業フォルダ内に作成した空のテキストファイル(右クリックで新規作成できる『新しいテキスト ドキュメント.txt』)に貼り付け,上書き保存します.
保存後,拡張子を“sh”としたファイルに書き換えます.この場では“magick-ts.sh”とします*6

実行する準備ができたので,“./magick-ts.sh list.txt”を実行します.
$ ./magick-ts.sh list.txt
"IMGP4676.JPG"の撮影日時の取得……
OK! '24     03     20
出力ファイル名: p20240320_IMGP4676_TS.jpg
"IMGP4677.JPG"の撮影日時の取得……
OK! '24     03     20
出力ファイル名: p20240320_IMGP4677_TS.jpg
"_IMP0221.JPG"の撮影日時の取得……
OK! '18     08     13
出力ファイル名: p20180813__IMP0221_TS.jpg
$
これで日付が挿入された写真ファイルが得られます.

*6 : この作業はシェル上でvimやnano等を使ってファイルを作成した後,chmodで実行権限を付与する方法でも作れます.ですが,Windows上でファイルを作った方が読者が慣れたエディタを使えるし,chmodが必要ない分(Windows上でファイルを作ると必ず実行権限も付与されるため.)気が楽な気がしますので,Windowsで作業する例を紹介しています.

3 実行環境について

3.1 オペレーティングシステム

解説はWSL上のDebian 12での作業を想定しています(もちろんネイティブなDebian 12でも動きます.).
WSLでDebianをインストールしてください.

3.2 ImageMagick

画像処理を行うImageMagickはバージョンによってコマンド体系が異なります.
バージョン6以前は,“convert”や“identify”というコマンドを使用します.
バージョン7以降のコマンド名は“magick”を最初に記述し,
その後にオプションのような扱いで“convert”や“identify”というコマンドを続ける仕様になっています.

それぞれのバージョンで動作するコードを用意したので,環境に合わせて選んで使ってください.

Debian 12デフォルトのImageMagickはバージョン6系なので,それをインストールします.

$ sudo su -
[sudo] password for username:
# apt install imagemagick
・
・
・
#
7系はRocky Linux 8などのRHEL系OSではすでに採用されています.
ImageMagickのバージョンが“convert -version”で表示されたら6系以前,“magick -verson”で表示されれば7系以降です.
これらを確認してからシェルスクリプトを実行してください.
$ convert --version
Version: ImageMagick 6.9.11-60 Q16 x86_64 2021-01-25 https://imagemagick.org
Copyright: (C) 1999-2021 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC Modules OpenMP(4.5)
Delegates (built-in): bzlib djvu fftw fontconfig freetype heic jbig jng jp2 jpeg lcms lqr ltdl lzma openexr pangocairo png tiff webp wmf x xml zlib
$

3.3 bc

本記事の“改3”バージョンのスクリプトでは,“bc”という任意精度計算コマンドを使用します.Debian 12では標準でインストールされていないようなのでインストールします.
$ sudo su -
# apt install bc
・
・
・
#

3.4 使用フォント

7セグ・14セグフォント 「DSEG」から,14セグメントなフォントをいただいて*7,使用させていただきます.

Windows上でフォントのあるフォルダを確認し,フォントファイルを管理者権限で“/usr/local/share/fonts/”へコピーします.
これだけで使用可能になります.
sudo cp /mnt/c/Users/user/Downloads/fonts-DSEG_v046/DSEG14-Classic/*.ttf /usr/local/share/fonts/
ネイティブなDebian 12でも,入手したフォントファイルを“/usr/local/share/fonts/”へ置けば使用できます.

*7 : 素敵なフォントをありがとうございます.

4 ソースコード

コーディングの途中からコメント文による説明が適当になってしまいました.
ご容赦願います.そのまんまな変数名を定義しているので,何をしているかはわかると思います.

4.1 ImageMagick 6以前版

#!/bin/bash
# 引数の説明
# $1: 操作対象パス(ファイル)リストが記載されたファイル.
# SEIREKI_BACKQUOTE="\`" # "`"はグレイヴアクセント.西暦の略記はアポストロフィ“'”.
SEIREKI_BACKQUOTE=`echo -e "\U0027"` # グレイヴアクセントや文字コード指定がsed中で上手くエスケープできないので変数に分けた.
while read IMAGENAME # $1の各行に記載された画像ファイルのパス.
 do
                echo "\"${IMAGENAME}\"の撮影日時の取得……"
                DateTimeOriginal=`identify -verbose ${IMAGENAME} |
                        grep "DateTimeOriginal" | #表示結果例: 『    exif:DateTimeOriginal: 2024:03:20 08:00:23』
                        sed -e "s/^.*exif:DateTimeOriginal: //g" ` #処理結果例: 『2024:03:20 08:00:23』
                FILENAME_DATETIME=`echo ${DateTimeOriginal} |
                        sed -e "s/ /_/g" | 	#処理結果例: 『2024:03:20_08:00:23』
                        sed -e "s/^/p/g" | 	#処理結果例: 『p2024:03:20_08:00:23』
                        sed -e "s/_.*$//g" |  	#処理結果例: 『p2024:03:20』
                        sed -e "s/://g" ` 	#処理結果例: 『p20240320』
                DateTimeOriginal=`echo ${DateTimeOriginal} | 
                        sed -e "s/ .*$//g" |					#処理結果例: 『2024:03:20』
                        sed -e "s/20\(..\):/${SEIREKI_BACKQUOTE}\1:/g" |	#処理結果例: 『'24:03:20』
                        sed -e "s/:/     /g" `					#処理結果例: 『'24     03     20』(これくらいスペースを空けないと体裁整わない.)
                echo "OK! ${DateTimeOriginal}"
		IMAGE_WIDTH=`identify -format "%[width]" ${IMAGENAME}`
		IMAGE_HEIGHT=`identify -format "%[height]" ${IMAGENAME}`
		DATE_POINTSIZE=`echo "${IMAGE_WIDTH} * 0.03" | bc -l | xargs printf "%.0f"`
		DATE_STROKEWIDTH=`echo "${IMAGE_WIDTH} * 0.00065" | bc -l | xargs printf "%.0f"`
		DATE_POSITION_X=`echo "${IMAGE_WIDTH} * 0.1" | bc -l | xargs printf "%.0f"`
		SHADOW_DATE_POSITION_X=`echo "${DATE_POSITION_X} - ${DATE_STROKEWIDTH}" | bc -l | xargs printf "%.0f"`
		DATE_POSITION_Y=`echo "${IMAGE_HEIGHT} * 0.075" | bc -l | xargs printf "%.0f"`
		SHADOW_DATE_POSITION_Y=`echo "${DATE_POSITION_Y} - ${DATE_STROKEWIDTH}" | bc -l | xargs printf "%.0f"`
                OUTPUT_IMAGENAME=`echo "${IMAGENAME}-tmp.jpg"`		#一時ファイルの名前
                OUTPUT_IMAGENAME2=`echo "${IMAGENAME}.jpg"`		#一時ファイルの名前2
                convert  -font DSEG14-Classic-Bold -fill "#00000010" -pointsize ${DATE_POINTSIZE} -gravity southeast -annotate 0x0+${SHADOW_DATE_POSITION_X}+${SHADOW_DATE_POSITION_Y} "${DateTimeOriginal}" ${IMAGENAME} ${OUTPUT_IMAGENAME}
		#↑黒文字で日付の陰を先に生成する.
                convert -font DSEG14-Classic-Bold -fill "#fd7e0070" -stroke "#00000030" -strokewidth ${DATE_STROKEWIDTH} -pointsize ${DATE_POINTSIZE} -gravity southeast -annotate 0x0+${DATE_POSITION_X}+${DATE_POSITION_Y} "${DateTimeOriginal}" ${OUTPUT_IMAGENAME} ${OUTPUT_IMAGENAME2}
                rm ${OUTPUT_IMAGENAME}						#一時ファイルを削除する.
                OUTPUT_IMAGENAME3=`echo ${OUTPUT_IMAGENAME2} |			#OUTPUT_IMAGENAME2を元に出力ファイル名を決定
                        sed -e "s/\.JPG\.jpg/_TS.jpg/g" |			#
                        sed -e "s/^.*_TS.jpg/${FILENAME_DATETIME}_&/g"`		#先頭に日付の文字列を追加.
		mv ${OUTPUT_IMAGENAME2} "${OUTPUT_IMAGENAME3}"
                echo "出力ファイル名: ${OUTPUT_IMAGENAME3}"
done < $1

4.2 ImageMagick 7以降版

#!/bin/bash
# 引数の説明
# $1: 操作対象パス(ファイル)リストが記載されたファイル.
# SEIREKI_BACKQUOTE="\`" # "`"はグレイヴアクセント.西暦の略記はアポストロフィ“'”.
SEIREKI_BACKQUOTE=`echo -e "\U0027"` # グレイヴアクセントや文字コード指定がsed中で上手くエスケープできないので変数に分けた.
while read IMAGENAME # $1の各行に記載された画像ファイルのパス.
 do
                echo "\"${IMAGENAME}\"の撮影日時の取得……"
                DateTimeOriginal=`magick identify -verbose ${IMAGENAME} |
                        grep "DateTimeOriginal" | #表示結果例: 『    exif:DateTimeOriginal: 2024:03:20 08:00:23』
                        sed -e "s/^.*exif:DateTimeOriginal: //g" ` #処理結果例: 『2024:03:20 08:00:23』
                FILENAME_DATETIME=`echo ${DateTimeOriginal} |
                        sed -e "s/ /_/g" | 	#処理結果例: 『2024:03:20_08:00:23』
                        sed -e "s/^/p/g" | 	#処理結果例: 『p2024:03:20_08:00:23』
                        sed -e "s/_.*$//g" |  	#処理結果例: 『p2024:03:20』
                        sed -e "s/://g" ` 	#処理結果例: 『p20240320』
                DateTimeOriginal=`echo ${DateTimeOriginal} | 
                        sed -e "s/ .*$//g" |					#処理結果例: 『2024:03:20』
                        sed -e "s/20\(..\):/${SEIREKI_BACKQUOTE}\1:/g" |	#処理結果例: 『'24:03:20』
                        sed -e "s/:/     /g" `					#処理結果例: 『'24     03     20』(これくらいスペースを空けないと体裁整わない.)
                echo "OK! ${DateTimeOriginal}"
		IMAGE_WIDTH=`magick identify -format "%[width]" ${IMAGENAME}`
		IMAGE_HEIGHT=`magick identify -format "%[height]" ${IMAGENAME}`
		DATE_POINTSIZE=`echo "${IMAGE_WIDTH} * 0.03" | bc -l | xargs printf "%.0f"`
		DATE_STROKEWIDTH=`echo "${IMAGE_WIDTH} * 0.00065" | bc -l | xargs printf "%.0f"`
		DATE_POSITION_X=`echo "${IMAGE_WIDTH} * 0.1" | bc -l | xargs printf "%.0f"`
		SHADOW_DATE_POSITION_X=`echo "${DATE_POSITION_X} - ${DATE_STROKEWIDTH}" | bc -l | xargs printf "%.0f"`
		DATE_POSITION_Y=`echo "${IMAGE_HEIGHT} * 0.075" | bc -l | xargs printf "%.0f"`
		SHADOW_DATE_POSITION_Y=`echo "${DATE_POSITION_Y} - ${DATE_STROKEWIDTH}" | bc -l | xargs printf "%.0f"`
                OUTPUT_IMAGENAME=`echo "${IMAGENAME}-tmp.jpg"`		#一時ファイルの名前
                OUTPUT_IMAGENAME2=`echo "${IMAGENAME}.jpg"`		#一時ファイルの名前2
                magick convert  -font DSEG14-Classic-Bold -fill "#00000010" -pointsize ${DATE_POINTSIZE} -gravity southeast -annotate 0x0+${SHADOW_DATE_POSITION_X}+${SHADOW_DATE_POSITION_Y} "${DateTimeOriginal}" ${IMAGENAME} ${OUTPUT_IMAGENAME}
		#↑黒文字で日付の陰を先に生成する.
                magick convert -font DSEG14-Classic-Bold -fill "#fd7e0070" -stroke "#00000030" -strokewidth ${DATE_STROKEWIDTH} -pointsize ${DATE_POINTSIZE} -gravity southeast -annotate 0x0+${DATE_POSITION_X}+${DATE_POSITION_Y} "${DateTimeOriginal}" ${OUTPUT_IMAGENAME} ${OUTPUT_IMAGENAME2}
                rm ${OUTPUT_IMAGENAME}						#一時ファイルを削除する.
                OUTPUT_IMAGENAME3=`echo ${OUTPUT_IMAGENAME2} |			#OUTPUT_IMAGENAME2を元に出力ファイル名を決定
                        sed -e "s/\.JPG\.jpg/_TS.jpg/g" |			#
                        sed -e "s/^.*_TS.jpg/${FILENAME_DATETIME}_&/g"`		#先頭に日付の文字列を追加.
		mv ${OUTPUT_IMAGENAME2} "${OUTPUT_IMAGENAME3}"
                echo "出力ファイル名: ${OUTPUT_IMAGENAME3}"
done < $1

5 撮影日時の挿入例

過去の写真に日付を挿入した例.
どの写真も日付を判読でき,写真のピクセルサイズが異なっても,日付の大きさも対応して変更できているようです.

p20231124__IMG0959_TS.jpg
2023:11:24 10:59:20 RICOH IMAGING COMPANY, LTD. PENTAX K-1 / 14mm F9.0 1/320秒 ISO200

p20230101_IMGP9154_TS.jpg
2023:01:01 14:04:52 RICOH IMAGING COMPANY, LTD. PENTAX K-3 Mark III / 48mm F9.0 1/250秒 ISO200

p20230102_IMGP9334e1_TS.jpg
2023:01:02 12:44:18 RICOH IMAGING COMPANY, LTD. PENTAX K-3 Mark III / 28mm F7.1 1/60秒 ISO400

p20230319_IMGP9598_TS.jpg
2023:03:19 10:33:22 RICOH IMAGING COMPANY, LTD. PENTAX K-3 Mark III / 85mm F9.0 1/125秒 ISO500

p20230417_IMGP1133_TS.jpg
2023:04:17 12:11:26 RICOH IMAGING COMPANY, LTD. PENTAX K-3 Mark III / 21mm F9.0 1/800秒 ISO500

p20240413_IMGP4903_TS.jpg
2024:04:13 11:31:08 RICOH IMAGING COMPANY, LTD. PENTAX K-3 Mark III / 85mm F7.1 1/640秒 ISO100

p20240815_IMGP5552_TS.jpg
2024:08:15 15:56:52 RICOH IMAGING COMPANY, LTD. PENTAX K-3 Mark III / 39mm F9.0 1/320秒 ISO320