RustでExifのMakerNoteからPanasonicのカメラのレンズ情報を雑に取得する
Pnaasonicのカメラを使ってるのですが、Exifからレンズ情報が取れない...のですよね。
問題
通常、ExifのLensModelにレンズ名が入ってくるのだと思うのですが、Panasonicのカメラでは、入ってなさそうです(フルサイズ系(S系)は入っているかも)。少なくとも、僕の使ったことのあるカメラ、GM5, GX8, G9等では入ってなかったです。
ですが、ExifのMakerNoteの中に、データはあるようなので、それを使えばいけそうです。
ExifをParseするのに、rexif
を使う
ExifをRustでParseするのには、rexif
というものがありますので、これを使いました。
rexifでparseすると、entriesというメソッドで、各エントリーが取得できるので、MakerNoteの場合に、別の関数でentryの、ifd.ext_data
をparseします。下記のような感じですね。
struct Exif { lens_model: String, // 他にも適当に } let mut data = Exif{lens_model = "".to_string() }; let exif_data = rexif::parse_file(file_path); for e in exif_data.unwrap().entries { match e.tag { rexif::ExifTag::MakerNote => { let d = get_lens_from_maker_note(e.ifd.ext_data); if d != "" { data.LensModel = d; } }, // 他にも適当に } }
get_lens_from_maker_note でやっていること
この関数では、rexif
から取ってきたe.ifd_ext_data
をparseします。この型はVec<u8>
のバイト列です。Panasonicのカメラの場合、先頭の12バイトは、80, 97, 110, 97, 115, 111, 110, 105, 99, 0, 0, 0
となりますが、これはPanasonic\0\0\0
になります。
先頭がPanasonic\0\0\0
の場合に、レンズ情報がどこにあるかですが、固定長というわけではなさそうだったので、先頭から適当にそれっぽい文字がでるまで探すことにしました。
確認できたところでは、LUMIX
, LEICA
, OLYMPUS
, SIGMA
等の文字の後に、レンズ情報が来ていました。LEICA
は、LEICA認証の通ったPanasonicのレンズ(例: LEICA DG 100-400/F4.0-6.3)の場合になります。
なので、下記のような感じで取るようにしました。
- 最初に
Pansonic\0\0\0
があるか判定 - 制御文字以外を
std::char::from_u32
で変換して連結していき、最後の10文字だけ取っておく - レンズのプレフィックス用の正規表現にあたるかチェック
- そこから制御文字の手前までをレンズ情報とする
という、とても雑な感じです(正規表現にマッチするレンズ情報がない場合、最後まで読んでしまうので、効率が悪い)。
コードは以下のようになります。
// currently only for Panasonic camera fn get_lens_from_maker_note(data: Vec<u8>) -> String { if data.len() < 9 { return "".to_string(); } // Panasonic let panasonic: [u8; 12] = [80, 97, 110, 97, 115, 111, 110, 105, 99, 0, 0, 0]; let first12chars = &data[0..12]; // return when first 12 char is not "Panasonic\0\0\0" if first12chars != &panasonic { return "".to_string(); } // Lens name prefix regex(I only confirmed LUMIX, LEICA, OLYNMUS, SIGMA) let re = regex::Regex::new("(?i)(LUMIX|LEICA|OLYMPUS|SIGMA|TAMRON|KOWA|COSINA|VOIGT|VENUS)$") .unwrap(); let mut i = 12; let mut str = " ".to_string(); // dummy 9 chars while i < data.len() { let d = data[i]; if d < 32 || 126 < d { i += 1; continue; } // enough length for regex str = str[str.len() - 9..str.len()].to_string(); str.push(std::char::from_u32(data[i].into()).unwrap()); let captures = re.captures(&str); if captures.is_some() { let cap = captures.unwrap(); let mut lens = cap[0].to_string(); let mut i2 = i; while i2 < data.len() { i2 += 1; if data[i2] < 32 || 126 < data[i2] { return lens.to_string(); } lens.push(std::char::from_u32(data[i2].into()).unwrap()); } } i += 1; } return "".to_string(); }
雑すぎやしないか?
もっと頑張ってMakerNoteを解析するという手段もありなのですが、MakerNoteの仕様は、各社バラバラです。まともに対応すると、PerlのExifToolsのコードを見るとかする必要がありそうなのですが、そこまで時間を取れないので、一旦これで手を打ちました。
※ExifToolsは下記です。 metacpan.org
ですが、12バイト目がエントリー数で、そこから、12バイト単位でエントリーが並んでおり、そこにはレンズ情報はないということはわかったので、せめて下記のような追加を行いました。
下記の用に12から始めているところを
let mut i = 12;
コメント通りですが、(data[12] のエントリ数 + 1 ("Panasonic\0\0\0"(12byte)) ) x 12byte
分をスキップすることにしました。
// // safely skip 12byte x (data[12](num of entries) + "Panasonic\0\0\0") let mut i: usize = usize::from(data[12] + 1) * 12;
まぁ、大した効果はないとは思います。
後は、正規表現でやるより、スライスでチェックしたほうが速いかも...とか思いましたが、正直速度はなんの問題もないレベルなので、一旦これで良しとします。
おわり
というわけでかなり雑にではありますが、レンズ情報を取得できるようになりました。
めでたし。