exifreadを使って得られるタグの値は、どれもexifread.classes.IfdTagクラスで、str()を使って大体のところまでわかりやすい状態で表示することができます。さらに細かな表示指定をするためにデータの定式を調べてみます。
タグの値から.valuesで得られるものは、str(文字列)またはlistです。 listの場合、大抵の場合要素は1つだけで、要素の型はintかexifread.utils.Ratioです。Ratioはfractionsまたはsympy.Rationalに似た仕組みで有理数(分数)を保持する仕組みです。
まだよくわかっていませんが、type()や.__class__などを使ってデータの形式を探ってみます。
with open(file, 'rb') as f: tags = exifread.process_file(f) print(f.name) key = "EXIF DateTimeOriginal" ifd = tags.get(key,'') print(key,":",ifd,type(ifd)) print(ifd.__class__, ifd.__class__.__name__, ) print(ifd.__class__.__bases__, type(ifd.__class__.__bases__)) print(ifd.__class__.__bases__[0],type(ifd.__class__.__bases__[0])) print('values:',tags.get(key,'').values.__class__) for itm in tags.get(key,'').values: print(type(itm), itm, itm.__class__.__bases__) print('---end of ---',key)
実行結果です
adachi@fumita:/media/adachi/S1T/python3$ python3 withExifread02.py /home/adachi/Downloads/sample.jpg EXIF DateTimeOriginal : 2018:07:24 12:05:34 <class 'exifread.classes.IfdTag'> <class 'exifread.classes.IfdTag'> IfdTag (<class 'object'>,) <class 'tuple'> <class 'object'> <class 'type'> values: <class 'str'> <class 'str'> 2 (<class 'object'>,) <class 'str'> 0 (<class 'object'>,) <class 'str'> 1 (<class 'object'>,) <class 'str'> 8 (<class 'object'>,) <class 'str'> : (<class 'object'>,) <class 'str'> 0 (<class 'object'>,) <class 'str'> 7 (<class 'object'>,) <class 'str'> : (<class 'object'>,) <class 'str'> 2 (<class 'object'>,) <class 'str'> 4 (<class 'object'>,) <class 'str'> (<class 'object'>,) <class 'str'> 1 (<class 'object'>,) <class 'str'> 2 (<class 'object'>,) <class 'str'> : (<class 'object'>,) <class 'str'> 0 (<class 'object'>,) <class 'str'> 5 (<class 'object'>,) <class 'str'> : (<class 'object'>,) <class 'str'> 3 (<class 'object'>,) <class 'str'> 4 (<class 'object'>,) ---end of --- EXIF DateTimeOriginal
tags.get('EXIF DateTimeOriginal')は'exifread.classes.IfdTag'というクラスです。
type(ifd)でもifd.__class__でも求めることができます。
.__name__で名前を調べるとIfdTagとだけ答えます。
.__bases__で親クラスを調べるとタプルのデータが返ってきます。タプルですが、項目は一つしかありません[1]を問い合わせるとエラーになります。タプルの中身はobjectというクラス(データタイプ)だというだけです。
tags.get('EXIF DateTimeOriginal').valuesはstrです。イテレータブルなのでforにかけると、個々の文字がもこれもstrとして個別に出てきます。
.__bases__はあまり役に立ちません。
exifreadで得られる個々のタグの値はIfdTagクラスという仮面をかぶっていますが、中身は.valuesで取り出すことができて、日付の場合は文字列という結論です。
tags.get('EXIF DateTimeOriginal').values は str。
シャッター速度は'EXIF ExposureTime'です。
key = "EXIF ExposureTime" ifd = tags.get(key,'') print(key,":",ifd,type(ifd)) print(ifd.__class__, ifd.__class__.__name__, ) print(ifd.__class__.__bases__, type(ifd.__class__.__bases__)) print(ifd.__class__.__bases__[0],type(ifd.__class__.__bases__[0])) print('values:',tags.get(key,'').values.__class__) for itm in tags.get(key,'').values: print(type(itm), itm, itm.__class__.__bases__) #print(type(itm)) #print(itm) #print(itm.__class__.__bases__) print('---end of ---',key)
実行結果です
EXIF ExposureTime : 1/750 <class 'exifread.classes.IfdTag'> <class 'exifread.classes.IfdTag'> IfdTag (<class 'object'>,) <class 'tuple'> <class 'object'> <class 'type'> values: <class 'list'> <class 'exifread.utils.Ratio'> 1/750 (<class 'object'>,) ---end of --- EXIF ExposureTime
tags.get('EXIF ExposureTime').valuesはlistです。イテレータブルなのでforにかけると、要素数は1で、そのtypeはclass 'exifread.utils.Ratio'です。
クラスは同様なので、簡略化して示しておきます。
key = "EXIF ISOSpeedRatings" ifd = tags.get(key,'') print(key,":",ifd) print('values:',ifd.values.__class__) for itm in ifd.values: print(type(itm), itm) print('---end of ---',key,'\n') key = "Image Orientation" ifd = tags.get(key,'') print(key,":",ifd) print('values:',ifd.values.__class__) for itm in ifd.values: print(type(itm), itm) print('---end of ---',key,'\n')
実行結果です
EXIF ISOSpeedRatings : 800 values: <class 'list'> <class 'int'> 800 ---end of --- EXIF ISOSpeedRatings Image Orientation : Horizontal (normal) values: <class 'list'> <class 'int'> 1 ---end of --- Image Orientation
intですが要素数1のlistに入れられています。
ISOは整数そのままですが、Orientationの整数はcodeなので、printでは対応した'状態'を表す言葉に変換されています。
tags.get('EXIF ISOSpeedRatings').valuesとtags.get('Image Orientation').valuesはlistです。イテレータブルなのでforにかけると、要素数は1で、そのtypeはclass 'int'です。
このタグは値のないファイルもありますから、そのときには.valuesを求めないようにする必要があります。
key = "MakerNote LensMinMaxFocalMaxAperture" ifd = tags.get(key,'') print(key,":",ifd) if ifd != '': print('values:',ifd.values.__class__) for itm in ifd.values: print(type(itm), itm) print('---end of ---',key,'\n')
実行結果です
MakerNote LensMinMaxFocalMaxAperture : [40, 40, 14/5, 14/5] values: <class 'list'> <class 'exifread.utils.Ratio'> 40 <class 'exifread.utils.Ratio'> 40 <class 'exifread.utils.Ratio'> 14/5 <class 'exifread.utils.Ratio'> 14/5 ---end of --- MakerNote LensMinMaxFocalMaxAperture
tags.get('MakerNote LensMinMaxFocalMaxAperture').valuesはlistです。イテレータブルなのでforにかけると、要素数は4で、そのtypeはclass 'exifread.utils.Ratio'です。
ruby時代に使っていたタグとlens関係の値です。関数を定義して統一しました。
#!/usr/bin/python3 import sys import exifread def printValues(key): ifd = tags.get(key,'') print(key,":",ifd) if ifd != '': print(' values:',ifd.values.__class__) if ifd.values.__class__ != str: for itm in ifd.values: print(' ',type(itm), itm) files = { '1':"/home/adachi/Downloads/sample.jpg", '2':"/home/adachi/Downloads/DSC_1414.JPG", '3':"/home/adachi/Downloads/P3300108.JPG", '4':"/home/adachi/Downloads/IMG_20190224_111208.jpg", '5':"/home/adachi/Downloads/from_google200326.png", } file = files.get(sys.argv[1],files['5']) if len(sys.argv)>1 else files['1'] with open(file, 'rb') as f: tags = exifread.process_file(f) print(f.name) key = "Image Model" printValues(key) key = "Image Orientation" printValues(key) key = "EXIF DateTimeOriginal" printValues(key) key = "EXIF ExposureTime" printValues(key) key = "EXIF FNumber" printValues(key) key = "EXIF ISOSpeedRatings" printValues(key) key = "EXIF FocalLengthIn35mmFilm" printValues(key) key = "EXIF FocalLength" printValues(key) key = "EXIF ExifImageWidth" printValues(key) key = "EXIF LensSpecification" printValues(key) key = "EXIF LensModel" printValues(key) key = "MakerNote LensMinMaxFocalMaxAperture" printValues(key)
実行結果です。
adachi@fumita:/media/adachi/S1T/python3$ python3 withExifread03.py 2 /home/adachi/Downloads/DSC_1414.JPG Image Model : NIKON D500 values: <class 'str'> Image Orientation : Horizontal (normal) values: <class 'list'> <class 'int'> 1 EXIF DateTimeOriginal : 2020:02:24 10:33:22 values: <class 'str'> EXIF ExposureTime : 1/50 values: <class 'list'> <class 'exifread.utils.Ratio'> 1/50 EXIF FNumber : 11 values: <class 'list'> <class 'exifread.utils.Ratio'> 11 EXIF ISOSpeedRatings : 800 values: <class 'list'> <class 'int'> 800 EXIF FocalLengthIn35mmFilm : 60 values: <class 'list'> <class 'int'> 60 EXIF FocalLength : 40 values: <class 'list'> <class 'exifread.utils.Ratio'> 40 EXIF ExifImageWidth : 5568 values: <class 'list'> <class 'int'> 5568 EXIF LensSpecification : EXIF LensModel : MakerNote LensMinMaxFocalMaxAperture : [40, 40, 14/5, 14/5] values: <class 'list'> <class 'exifread.utils.Ratio'> 40 <class 'exifread.utils.Ratio'> 40 <class 'exifread.utils.Ratio'> 14/5 <class 'exifread.utils.Ratio'> 14/5
機種によりデータがない場合がありますが、typeは共通です。
strとintはいいとして、小数や分数で表す必要がある数値はRatioになっています。
シャッター速度の1/50などはいいですが、f値の28/5は5.6となってほしいところです。
一つ気になるのは、FocalLengthIn35mmFilm(int)とFocalLength(Ratio)のtypeの違いです。35mmフイルム用のレンズの焦点距離は確かに必ず整数だとは思いますが。
タグ | type | 要素数 | 例(printによる表示例) |
---|---|---|---|
Image Model | str | 任意 | NIKON D500 |
EXIF DateTimeOriginal | 2020:02:24 10:33:22 | ||
EXIF LensModel | OLYMPUS M.14-42mm F3.5-5.6 EZ | ||
Image Orientation | intのlist | 1 | 1 |
EXIF ISOSpeedRatings | 400 | ||
EXIF FocalLengthIn35mmFilm | 60 | ||
EXIF ExifImageWidth | 5568 | ||
EXIF ExposureTime | Ratioのlist | 1 | 1/50 |
EXIF FNumber | 28/5 | ||
EXIF FocalLength | 40 | ||
EXIF LensSpecification | 4 | [14, 42, 7/2, 28/5] | |
MakerNote LensMinMaxFocalMaxAperture | [40, 40, 14/5, 14/5] |
スマートフォンの写真でポピュラーになりました。
GPSに限らずexifデータにはタグの文字列は含まれておらず、codeになっていて、これをツール側で文字列にしています。PIL.ExifTagsではそのためのdict(=TAGSなど)を用意していました。dictにないものはcodeをそのままにかえしていました。どうやらPIL.ExifTagsではGPSデータは別の括りになっています。
exifreadでは今の所codeのまま出てくることはないのですが、今の所網羅されているのか、文字列にできないものを無視しているのかは不明です。GPSデータもフラットに他のタグと一緒に並んで出てきます。
まだ、一種類のスマホの写真しか扱っていないので、出てきたタグで全てかどうかはわかりません。
タグ | type | 要素数 | 例(printによる表示例) |
---|---|---|---|
GPS GPSLatitudeRef | str | 1 | N |
GPS GPSLongitudeRef | E | ||
GPS GPSDate | 2019:02:24 | ||
GPS GPSVersionID | intのlist | 4 | [2, 2, 0, 0] |
GPS GPSAltitudeRef | 1 | 1 | |
GPS GPSProcessingMethod | 7 | [67, 69, 76, 76, 73, 68, 0] | |
GPS GPSLatitude | Ratioのlist | 3 | [43, 8, 16307373/1000000] |
GPS GPSLongitude | [141, 1, 5402069/125000] | ||
GPS GPSTimeStamp | [2, 12, 8] | ||
GPS GPSAltitude | 1 | 0 |
[43, 8, 16307373/1000000]は43°8′16.3″ということです。
タグの出現順(dictではあまり意味のないことですが)
GPS GPSVersionID : [2, 2, 0, 0] GPS GPSLatitudeRef : N GPS GPSLatitude : [43, 8, 16307373/1000000] GPS GPSLongitudeRef : E GPS GPSLongitude : [141, 1, 5402069/125000] GPS GPSAltitudeRef : 1 GPS GPSAltitude : 0 GPS GPSTimeStamp : [2, 12, 8] GPS GPSProcessingMethod : [67, 69, 76, 76, 73, 68, 0] GPS GPSDate : 2019:02:24
Altitudeは高さですが、信用できる値ではありません。たぶん地球を回転楕円体とみなしての測定なので、緯度と軽度はそこそこ正確なものの、高さについては地形の情報が足りなくて不正確なのでしょう。