TL;DR
clusterで撮影した画像ファイルの一括ダウンロードで得られるzipファイル内のPNGファイル名を cluster_yyyy-MM-dd_HH-mm-ss_XXX.png
という形式にリネームするツールを作った(yyyyMMddHHmmss
は撮影日時の年月日時分秒,撮影日時=ファイル作成日時=ファイル最終更新時刻).
ついでに,PNGファイルにメタ情報として,ファイル撮影日時を埋め込むようにした.
背景
clusterのPNG画像はGUID形式のもので整理するのに不便という話がいつもたむろしているDiscordのチャットにあった. 複数選択して一括ダウンロードしたPNG画像はタイムスタンプが保持されていることは知っていたので,zipファイルを突っ込んだら中のファイルをリネームして,新しいzipファイルを作るCLIツールを作成した. また,画像のメタデータとして日付とかを持てるといいかもという話もあったので,ついでにそれも追加するようにした.
(ファイル名がGUIDであるのは,DBに突っ込む上での都合なのかどうかという想像が出来て楽しいところ)
PNGファイルの構造
PNGファイルの構造は非常に単純であり,先頭8バイトにシグネチャがあり,そこから下記の形式のチャンクが続くだけである. ただし,データ長やCRC-32といった数値はビッグエンディアンであることに注意しなければならない.
サイズ | 意味 |
---|---|
4 Bytes | チャンクデータ長(N) |
4 Bytes | チャンク種別を示すASCIIテキスト4文字(IHDR , IDAT , tEXt など) |
N Bytes | チャンクに応じたデータ部 |
4 Bytes | チャンク種別とデータ部のCRC-32 |
最初にIHDR,最後にIEND,IDATチャンクが複数ある場合はIDATチャンクの間に他のチャンクが存在してはならず,データ順になるように連続していなければならない等の制約があるが,全体的な構造を示す部分はなくただチャンクが並んでいるだけなので,チャンクの追加は容易である.
チャンク種別のASCII文字の何文字目が大文字か小文字かによって,チャンクの特性を表現している.
文字 | 意味 |
---|---|
1文字目 | 大文字であれば必須チャンク |
2文字目 | 大文字であれば仕様が公開・定義されているもの |
3文字目 | 将来のために予約されているが,現在は常に大文字 |
4文字目 | 大文字の場合,他の必須チャンクの影響を受けるので,そのままコピーはできない |
tEXtチャンク
tEXtチャンクの構造は下記の通り. データ部がASCII文字でキーと値がNULL文字で区切られている構造となっている. 仕様としては,キー部は80文字以内という但し書きがある.
サイズ | 意味 |
---|---|
4 Bytes | データ長(M + 1 + N) |
4 Bytes | tEXt |
M Bytes | キー文字列 |
1 Byte | \0 (NULL文字) |
N Bytes | 値となる文字列 |
4 Bytes | CRC-32 |
Creation Time
WindowsのエクスプローラではPNGファイルにキー:Creation Time,値:yyyy:MM:dd HH:mm 形式の時刻文字列のtEXtチャンクが存在する場合,それを撮影日時として表示する仕様となっているようだ. そのため,PNGファイルにキー:Creation Time,値:ファイル最終更新時刻をyyyy:MM:dd HH:mm:ss形式にした文字列 を埋め込む機能を入れた. これにより,ファイル名を手動で再度リネームしても更新時刻は失われなくなる. なお,ファイル1つにつき,31 Bytesサイズが増加するが微々たるものであるため,良しとした.
PNGの仕様としては時刻文字列としては RFC 1123 が推奨されるようだが,利便性を考え,Windowsのエクスプローラ形式を採用した.
Title
ファイルをリネームするにあたって,もともとのファイル名もメタデータとして残すことにした. というのも,clusterのPNGファイル名はGUID形式のものであり,重複は基本的にない一意のものであると見做せるためだ. だからといって何かになるわけではないが,今後何かあったときに役立てることが出来る可能性がある.
tIMEチャンク
PNGファイルの仕様として,最終更新時刻を保持するためのチャンクも用意されている. これを設定したからといって,エクスプローラの表示に影響を与えるわけではないが,ついでなので追加することにした. UTC(あるいはGMT)の時刻が推奨されるため,UTCに変更して保存するようにした. そのため,値だけ見ればCreation Timeに保存した時刻と9時間のズレがある.
サイズ | 意味 |
---|---|
4 Bytes | データ長(7) |
4 Bytes | tIME |
2 Bytes | 年 |
1 Byte | 月 |
1 Byte | 日 |
1 Byte | 時 |
1 Byte | 分 |
1 Byte | 秒 |
4 Bytes | CRC-32 |
zipファイルの時刻精度について
zipファイルの仕様上,zipファイル内のファイルの時刻精度は2秒刻みである. そのため,時刻を含むだけの形式では,ファイル名重複が容易に起こりうるため,連番部分を追加している. 敢えてミリ秒っぽく3桁分を用意しているが,clusterではどう努力しても2秒以内に100枚以上のファイルどころか10枚以上のファイルの保存はできないと思われるので,1桁でもよいのではないかと思ってはいる.
あえてやらなかったこと
clusterの写真ファイルは保存速度のため,圧縮率レベルは1にして保存しているらしい(これはVRChatの写真も同様). (PNGの画像データはzlib形式のDeflate圧縮を採用しているので,実際に伸長して再圧縮して確かめてみた感じだと,zlibの最低の圧縮レベル圧縮率を指定したときと同一の結果となった) なので,ある程度の圧縮レベルにして,再圧縮するようにすれば,追加したチャンク分以上にデータサイズを削ることが出来ると考えた. だが,あくまでリネームツールのため,再圧縮は範疇外として採用しなかった. (再圧縮の処理時間が1秒だとしても対象ファイルが600枚あれば10分はかかってしまう)
同様の理由でIDATチャンクの結合も行わないことにしている(clusterのPNG画像はIDATのデータ部を 8192 Bytes 毎になるように分割している.IDATを結合することで,IDAT1つあたり 12 Bytes(データ長 + チャンク種別 + CRC-32) のファイルサイズを削減することが出来る).
再圧縮については,別で作成したPNGファイルの超圧縮ツールの役割であると思う.