IfdTagクラスの内容(exifreadを使う)

概要

exifreadを使って得られるタグの値は、どれもexifread.classes.IfdTagクラスで、str()を使って大体のところまでわかりやすい状態で表示することができます。さらに細かな表示指定をするためにデータの定式を調べてみます。

タグの値から.valuesで得られるものは、str(文字列)またはlistです。 listの場合、大抵の場合要素は1つだけで、要素の型はintかexifread.utils.Ratioです。Ratioはfractionsまたはsympy.Rationalに似た仕組みで有理数(分数)を保持する仕組みです。

日付のデータ形式

まだよくわかっていませんが、type()や.__class__などを使ってデータの形式を探ってみます。

withExifread02.pyの一部
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'です。

PilExifTag01.pyの一部
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'です。

ISO感度,Orientationの値

クラスは同様なので、簡略化して示しておきます。

PilExifTag01.pyの一部
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'です。

MakerNote LensMinMaxFocalMaxApertureの値

このタグは値のないファイルもありますから、そのときには.valuesを求めないようにする必要があります。

PilExifTag01.pyの一部
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関係の値です。関数を定義して統一しました。

PilExifTag03.py
#!/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 Modelstr任意NIKON D500
EXIF DateTimeOriginal2020:02:24 10:33:22
EXIF LensModelOLYMPUS M.14-42mm F3.5-5.6 EZ
Image Orientationintのlist11
EXIF ISOSpeedRatings400
EXIF FocalLengthIn35mmFilm60
EXIF ExifImageWidth5568
EXIF ExposureTime Ratioのlist11/50
EXIF FNumber28/5
EXIF FocalLength40
EXIF LensSpecification4[14, 42, 7/2, 28/5]
MakerNote LensMinMaxFocalMaxAperture[40, 40, 14/5, 14/5]

GPSデータ

スマートフォンの写真でポピュラーになりました。

GPSに限らずexifデータにはタグの文字列は含まれておらず、codeになっていて、これをツール側で文字列にしています。PIL.ExifTagsではそのためのdict(=TAGSなど)を用意していました。dictにないものはcodeをそのままにかえしていました。どうやらPIL.ExifTagsではGPSデータは別の括りになっています。

exifreadでは今の所codeのまま出てくることはないのですが、今の所網羅されているのか、文字列にできないものを無視しているのかは不明です。GPSデータもフラットに他のタグと一緒に並んで出てきます。

まだ、一種類のスマホの写真しか扱っていないので、出てきたタグで全てかどうかはわかりません。

タグtype要素数例(printによる表示例)
GPS GPSLatitudeRefstr1N
GPS GPSLongitudeRefE
GPS GPSDate2019:02:24
GPS GPSVersionIDintのlist4[2, 2, 0, 0]
GPS GPSAltitudeRef11
GPS GPSProcessingMethod7[67, 69, 76, 76, 73, 68, 0]
GPS GPSLatitudeRatioのlist3[43, 8, 16307373/1000000]
GPS GPSLongitude[141, 1, 5402069/125000]
GPS GPSTimeStamp[2, 12, 8]
GPS GPSAltitude10

[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は高さですが、信用できる値ではありません。たぶん地球を回転楕円体とみなしての測定なので、緯度と軽度はそこそこ正確なものの、高さについては地形の情報が足りなくて不正確なのでしょう。