RubyでJPEG画像のEXIF情報を抽出してNoteAttributeに設定

前回の予告通り、今回はJPEG画像のEXIF情報を取得して色々と設定してみます。

今回のサンプル画像も前回と同じこれ。

JPEG画像のEXIF情報を取得する

まずはEXIF情報を取得しなきゃ始まらないので、EDAMTest.rbを弄る前にその辺りの収集関連を。 Rubyだとexifrを使うと楽らしい。

とりあえず取得した情報を表示(pp)するだけのスクリプトを作って、それで何がとれるのかを確認してみることに。

#!/usr/bin/ruby
require 'rubygems'
require 'exifr'
require 'pp'

ARGV.each { |filename|
  pp EXIFR::JPEG.new(filename).exif.to_hash
}

これを ExifrTest.rb という名前で保存して、先ほどのサンプルファイルを確認してみる。

$ sudo gem install exifr # exifrをインストールしていなければ
$ ruby ExifrTest.rb IMG_0074.jpg
{:date_time=>Fri Nov 04 22:13:41 +0900 2011,
 :focal_length=>Rational(77, 20),
 :gps_longitude_ref=>"E",
 :iso_speed_ratings=>160,
 :scene_capture_type=>0,
 :orientation=>#<EXIFR::TIFF::Orientation:TopLeft(1)>,
 :subject_area=>[1295, 967, 699, 696],
 :x_resolution=>Rational(72, 1),
 :gps_longitude=>[Rational(139, 1), Rational(2091, 50), Rational(0, 1)],
 :shutter_speed_value=>Rational(4889, 1250),
 :exposure_mode=>0,
 :exposure_time=>Rational(1, 15),
 :host_computer=>"Mac OS X 10.7.2",
 :resolution_unit=>2,
 :gps_img_direction_ref=>"T",
 :gps_altitude_ref=>"\000",
 :metering_mode=>5,
 :sensing_method=>2,
 :color_space=>1,
 :y_resolution=>Rational(72, 1),
 :f_number=>Rational(14, 5),
 :white_balance=>0,
 :aperture_value=>Rational(4281, 1441),
 :make=>"Apple",
 :gps_img_direction=>Rational(38081, 109),
 :gps_altitude=>Rational(8457, 815),
 :date_time_original=>Fri Nov 04 20:55:29 +0900 2011,
 :pixel_x_dimension=>373,
 :gps_latitude_ref=>"N",
 :exposure_program=>2,
 :brightness_value=>Rational(3709, 2373),
 :model=>"iPhone 4",
 :gps_time_stamp=>[Rational(12, 1), Rational(42, 1), Rational(1300, 1)],
 :date_time_digitized=>Fri Nov 04 20:55:29 +0900 2011,
 :pixel_y_dimension=>500,
 :sharpness=>2,
 :flash=>16,
 :software=>"QuickTime 7.7.1",
 :gps_latitude=>[Rational(35, 1), Rational(3189, 100), Rational(0, 1)]}

なんだか色々とれました(^_^;)
ちなみに、元画像はiPhone4で撮影して、iPhotoで取り込んだものをPreview.appでサイズを変更しています(ややこしいな...)

ここで日付関連の date_xxx に注目してみると...

 {:date_time=>Fri Nov 04 22:13:41 +0900 2011,
 :date_time_original=>Fri Nov 04 20:55:29 +0900 2011,
 :date_time_digitized=>Fri Nov 04 20:55:29 +0900 2011,

date_timeはPreview.appでリサイズしたときの時刻っぽい
date_time_originalは撮影した時刻。
date_time_digitizedも撮影した時刻っぽい。普通のデジカメでRAW撮影とかしたら変わりそう。
なので、使えるとしたら date_time と date_time_original かなと。

ということで、とりあえずこの辺りの時刻を前回設定したファイルの時刻の代わりに使ってみる。

require "exifr"

exifrを使うためにライブラリの読み込みを追加して...

e = EXIFR::JPEG.new(filename).exif
if !e.nil? then
  date_time_original = e[:date_time_original]
  if !date_time_original.nil? then
    note.created = date_time_original.to_i * 1000
  end
  date_time = e[:date_time]
  if !date_time.nil? then
    note.updated = date_time.to_i * 1000
  end
end

それぞれ、date_time_original / date_time で取得できた値があれば設定するように修正。
※この辺りで取得した情報をNote内容にも設定しようと思ったので、前回追加したstat周りと一緒にnote.title/note.contentの設定前に処理を移動しています(^_^;)

この時点で一回動作確認兼ねて動かしてみると、前回とほとんど同じで作成日と変更日だけ違うノートが出来上がる(ちょっと紛らわしいw)

取得した情報をEvernoteの属性として設定する

ここまでは前回とほぼ変わらないので、せっかくEXIFから取得できた情報を組み込むべく、NoteAttributeの設定に入ります。

ひとまずNoteAttributeの中で、GPS関連の latitude(緯度)/longitude(経度)/altitude(標高) だけ設定することに。

  def dms2d(negate, d, m, s)
    ddd = d + m/60 + s/3600
    return negate ? -ddd : ddd
  end

  na = Evernote::EDAM::Type::NoteAttributes.new()
  lat = e[:gps_latitude]
  latR = e[:gps_latitude_ref]
  if !lat.nil? and !latR.nil? then
    na.latitude = dms2d(latR != "N", lat[0], lat[1], lat[2])
  end
  lng = e[:gps_longitude]
  lngR = e[:gps_longitude_ref]
  if !lng.nil? and !lngR.nil? then
    na.longitude = dms2d(lngR != "E", lng[0], lng[1], lng[2])
  end
  na.altitude = e[:gps_altitude]

  if !na.latitude.nil? or !na.longitude.nil? or !na.altitude.nil? then
    note.attributes = na
  end

緯度と経度はDMS形式で設定されているらしいので、適当にその辺りを数値に変換するようにして、それぞれ設定する。
高度はそのままで良いぽいのでそのままに。
これで取得した情報を設定出来るようになっているはず...

note.content = '<?xml version="1.0" encoding="UTF-8"?>' +
  '<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">' +
  '<en-note>FileName: ' + base_name +
  '<br/>MimeType: ' + mime_type +
  '<br/>MD5Digest: ' + hashHex + 
  '<br/>' +
  '<en-media type="' + mime_type +'" hash="' + hashHex + '"/>' +
  '<br/>note.created : ' + Time.at(note.created/1000).to_s +
  '<br/>note.updated : ' + Time.at(note.updated/1000).to_s

if !note.attributes.nil? then
  note.content << "<br/>GPS latitude : #{note.attributes.latitude.to_f}"
  note.content << "<br/>GPS longitude: #{note.attributes.longitude.to_f}"
  note.content << "<br/>GPS altitude : #{note.attributes.altitude.to_f}"
end

note.content << '</en-note>'

実際にSandbox環境のWebで見たときにもわかりづらいので、今回取得した情報はノートの本体にも記載するように若干変更。
これでアップロードを試してみる...

ちゃんと日付も位置情報あたりも設定されてる( ´ ▽ ` )g
位置情報の所からGoogleMapで確認すると、きちんと川崎駅前を指している!

ということで、ここまでの変更一覧Diffはこちら

--- EDAMTest.rb.orig3   2011-11-05 21:26:03.000000000 +0900
+++ EDAMTest.rb 2011-11-06 01:14:41.000000000 +0900
@@ -13,6 +13,7 @@
 require "digest/md5"
 require "rubygems"
 require "mime/types"
+require "exifr"

 # Add the Thrift & Evernote Ruby libraries to the load path.
 # This will only work if you run this application from the ruby/sample/client
@@ -166,6 +167,45 @@
 resource.attributes.fileName = base_name

 note = Evernote::EDAM::Type::Note.new()
+
+s = File.stat(filename)
+note.created = s.ctime.to_i * 1000
+note.updated = s.mtime.to_i * 1000
+
+e = EXIFR::JPEG.new(filename).exif
+if !e.nil? then
+  date_time_original = e[:date_time_original]
+  if !date_time_original.nil? then
+    note.created = date_time_original.to_i * 1000
+  end
+  date_time = e[:date_time]
+  if !date_time.nil? then
+    note.updated = date_time.to_i * 1000
+  end
+
+  def dms2d(negate, d, m, s)
+    ddd = d + m/60 + s/3600
+    return negate ? -ddd : ddd
+  end
+
+  na = Evernote::EDAM::Type::NoteAttributes.new()
+  lat = e[:gps_latitude]
+  latR = e[:gps_latitude_ref]
+  if !lat.nil? and !latR.nil? then
+    na.latitude = dms2d(latR != "N", lat[0], lat[1], lat[2])
+  end
+  lng = e[:gps_longitude]
+  lngR = e[:gps_longitude_ref]
+  if !lng.nil? and !lngR.nil? then
+    na.longitude = dms2d(lngR != "E", lng[0], lng[1], lng[2])
+  end
+  na.altitude = e[:gps_altitude]
+
+  if !na.latitude.nil? or !na.longitude.nil? or !na.altitude.nil? then
+    note.attributes = na
+  end
+end
+
 note.title = "Test note from ENTest.rb (#{base_name})"
 note.content = '<?xml version="1.0" encoding="UTF-8"?>' +
   '<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">' +
@@ -174,7 +214,16 @@
   '<br/>MD5Digest: ' + hashHex + 
   '<br/>' +
   '<en-media type="' + mime_type +'" hash="' + hashHex + '"/>' +
-  '</en-note>'
+  '<br/>note.created : ' + Time.at(note.created/1000).to_s +
+  '<br/>note.updated : ' + Time.at(note.updated/1000).to_s
+
+if !note.attributes.nil? then
+  note.content << "<br/>GPS latitude : #{note.attributes.latitude.to_f}"
+  note.content << "<br/>GPS longitude: #{note.attributes.longitude.to_f}"
+  note.content << "<br/>GPS altitude : #{note.attributes.altitude.to_f}"
+end
+
+note.content << '</en-note>'
 note.resources = [ resource ]
 if !selectedNotebook.nil? then
   note.notebookGuid = selectedNotebook.guid
@@ -183,9 +232,6 @@
   note.tagGuids = [ selectedTag.guid ]
 end

-s = File.stat(filename)
-note.created = s.ctime.to_i * 1000
-note.updated = s.mtime.to_i * 1000

 createdNote = noteStore.createNote(authToken, note)

さて...そろそろ終わり(ネタ切れ)が見えてきたぞ...orz