なのでC#でJPEGの撮影日時(DateTimeOriginal)のみを高速に取得するコードを書いた。
ただし次の制約があります。
- 簡単のため撮影日時はJPEGファイルの先頭から1000バイト以内にあることを前提としている
- 例外処理は行っていない
- 基本的に動くことだけ確認して細かいところは見直していない
public static string ReadExifDateTime(string path) { byte[] jpegBytes = new byte[1000]; using (BinaryReader reader = new BinaryReader(File.OpenRead(path))) { reader.Read(jpegBytes, 0, 1000); } bool isLittleEndian = false; if (jpegBytes[12] == 'I') { isLittleEndian = true; } int ifd0Offset = ReadInt(jpegBytes, 16, 4, isLittleEndian) + 12; int ifd0DirCount = ReadInt(jpegBytes, ifd0Offset, 2, isLittleEndian); int subIfdOffset = 0; for (int i = 0; i < ifd0DirCount; i++) { int ifd0Tag = ReadInt(jpegBytes, ifd0Offset + 2 + 12 * i, 2, isLittleEndian); if (ifd0Tag == 0x8769) { subIfdOffset = ReadInt(jpegBytes, ifd0Offset + 2 + 12 * i + 8, 4, isLittleEndian) + 12; break; } } if (subIfdOffset == 0) { return null; } int subIfdDirCount = ReadInt(jpegBytes, subIfdOffset, 2, isLittleEndian); int dateTimeOffset = 0; for (int i = 0; i < subIfdDirCount; i++) { int ifd0Tag = ReadInt(jpegBytes, subIfdOffset + 2 + 12 * i, 2, isLittleEndian); if (ifd0Tag == 0x9003) { dateTimeOffset = ReadInt(jpegBytes, subIfdOffset + 2 + 12 * i + 8, 4, isLittleEndian) + 12; break; } } if (dateTimeOffset == 0) { return null; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < 20; i++) { sb.Append((char)jpegBytes[dateTimeOffset + i]); } return sb.ToString(); } private static int ReadInt(byte[] bytes, int offset, int count, bool isLittleEndian) { int retVal = 0; if (isLittleEndian) { for (int i = offset + count - 1; i >= offset; i--) { retVal = (retVal << 8) + bytes[i]; } } else { for (int i = offset; i < offset + count; i++) { retVal = (retVal << 8) + bytes[i]; } } return retVal; }
上記コードをPropertyItemsを使って読み込む場合と速度の比較のためにHDD上のJPEG(1,590,029 バイト)の撮影日時を100回読み込むのにかかる時間を計測した結果
上記コードの場合: 15ms
PropertyItemsを使用した場合: 29395ms
とPropertyItemsを使用した場合に比べて2000倍くらい速いことが確認できる。EXIFの解析って大変なんですね。 なお、PropertyItemsを使用して取得するコードとしては以下を使用した。
public static string ReadExifDateTimeUsingPropertyItems(string path) { //画像を読み込む System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(path); for (int i = 0; i < bmp.PropertyItems.Length; i++) { System.Drawing.Imaging.PropertyItem pi = bmp.PropertyItems[i]; if (pi.Id == 36867 && pi.Type == 2) { return Encoding.ASCII.GetString(pi.Value); break; } } return null; }
0 件のコメント:
コメントを投稿