# # JPEGファイルのヘッダ(?)部分を読み込み、画像のサイズを調べる # # JPEGファイルのフォーマットは以下のURLを参照のうえ、実際のフ # ァイル数点をサンプルとした # http://siisise.net/format.html#JPEG # http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif.html # # 2002.9.12 Exifファイルに対応 import os from string import lower SOI = 0xFFD8 APP1 = 0xFFE1 EXIF_HDR = 'Exif\0\0' TIFF_TAG_MARK = 0x002A type2bytes = { # TIFFタグのデータ形式 1: 1, # unsigned byte 2: 1, # ascii string 3: 2, # unsigned short 4: 4, # unsigned long 5: 8, # unsigned rational (分数:unsigned longが2つ、分子・分母の順) 6: 1, # signed byte 7: 1, # undefined 8: 2, # signed short 9: 4, # signed long 10: 8, # signed ratuonal (分数:signed longが2つ、分子・分母の順) 11: 4, # single float 12: 8 # double float } IFD_TAG = { # IFD0タグ 0x010e: 'ImageDescription', 0x010f: 'Make', 0x0110: 'Model', 0x0112: 'Orientation', 0x011a: 'XResolution', 0x011b: 'YResolution', 0x0128: 'ResolutionUnit', 0x0131: 'Software', 0x0132: 'DateTime', 0x013e: 'WhitePoint', 0x013f: 'PrimaryChromaticities', 0x0211: 'YCbCrCoefficients', 0x0213: 'YCbCrPositioning', 0x0214: 'ReferenceBlackWhite', 0x8298: 'Copyright', 0x8769: 'ExifIFDPointer', # SubIFDタグ 0x829a: 'ExposureTime', 0x829d: 'FNumber', 0x8822: 'ExposureProgram', 0x8827: 'ISOSpeedRatings', 0x9000: 'ExifVersion', 0x9003: 'DateTimeOriginal', 0x9004: 'DateTimeDigitized', 0x9101: 'ComponentsConfiguration', 0x9102: 'CompressedBitsPerPixel', 0x9201: 'ShutterSpeedValue', 0x9202: 'ApertureValue', 0x9203: 'BrightnessValue', 0x9204: 'ExposureBiasValue', 0x9205: 'MaxApertureValue', 0x9206: 'SubjectDistance', 0x9207: 'MeteringMode', 0x9208: 'LightSource', 0x9209: 'Flash', 0x920a: 'FocalLength', 0x927c: 'MakerNote', 0x9286: 'UserComment', 0x9290: 'SubsecTime', 0x9291: 'SubsecTimeOriginal', 0x9292: 'SubsecTimeDigitized', 0xa000: 'FlashPixVersion', 0xa001: 'ColorSpace', 0xa002: 'ExifImageWidth', 0xa003: 'ExifImageHeight', 0xa004: 'RelatedSoundFile', 0xa005: 'InteroperabilityIFDPointer', 0xa20e: 'FocalPlaneXResolution', 0xa20f: 'FocalPlaneYResolution', 0xa210: 'FocalPlaneResolutionUnit', 0xa215: 'ExposureIndex', 0xa217: 'SensingMethod', 0xa300: 'FileSource', 0xa301: 'SceneType', 0xa302: 'CFAPattern', # InteroperabilityIFDタグ 0x0001: 'InteroperabilityIndex', 0x0002: 'InteroperabilityVersion', 0x1000: 'RelatedImageFileFormat', 0x1001: 'RelatedImageWidth', 0x1002: 'RelatedImageLength', # IFD1タグ 0x0100: 'ImageWidth', 0x0101: 'ImageLength', 0x0102: 'BitsPerSample', 0x0103: 'Compression', 0x0106: 'PhotometricInterpretation', 0x0111: 'StripOffsets', 0x0112: 'Orientation', 0x0115: 'SamplesPerPixel', 0x0116: 'RowsPerStrip', 0x0117: 'StripByteConunts', 0x011a: 'XResolution', 0x011b: 'YResolution', 0x011c: 'PlanarConfiguration', 0x0128: 'ResolutionUnit', 0x0201: 'JpegInterchangeFormat', 0x0202: 'JpegInterchangeFormatLength', 0x0211: 'YCbCrCoefficients', 0x0212: 'YCbCrSubSampling', 0x0213: 'YCbCrPositioning', 0x0214: 'ReferenceBlackWhite', 0x00fe: 'NewSubfileType', # その他のタグ 0x00ff: 'SubfileType', 0x012d: 'TransferFunction', 0x013b: 'Artist', 0x013d: 'Predictor', 0x013e: 'WhitePoint', 0x013f: 'PrimaryChromaticities', 0x0142: 'TileWidth', 0x0143: 'TileLength', 0x0144: 'TileOffsets', 0x0145: 'TileByteCounts', 0x014a: 'SubIFDs', 0x015b: 'JPEGTables', 0x828d: 'CFARepeatPatternDim', 0x828e: 'CFAPattern', 0x828f: 'BatteryLevel', 0x83bb: 'IPTC/NAA', 0x8773: 'InterColorProfile', 0x8824: 'SpectralSensitivity', 0x8825: 'GPSInfo', 0x8828: 'OECF', 0x8829: 'Interlace', 0x882a: 'TimeZoneOffset', 0x882b: 'SelfTimerMode', 0x920b: 'FlashEnergy', 0x920c: 'SpatialFrequencyResponse', 0x920d: 'Noise', 0x9211: 'ImageNumber', 0x9212: 'SecurityClassification', 0x9213: 'ImageHistory', 0x9214: 'SubjectLocation', 0x9215: 'ExposureIndex', 0x9216: 'TIFF/EPStandardID', 0x9290: 'SubSecTime', 0x9291: 'SubSecTimeOriginal', 0x9292: 'SubSecTimeDigitized', 0xa20b: 'FlashEnergy', 0xa20c: 'SpatialFrequencyResponse', 0xa214: 'SubjectLocation' } class JPG_SZ: def __init__(self, filename=None): if filename != None: self.setFile(filename) self.endian = 0 # 0: Motorola, 1:Intel self.img_info = {} def setFile(self, filename): self.jpgfile = filename self.ErrMsg = '' if not os.access(self.jpgfile, os.R_OK | os.F_OK): self.ErrMsg = 'file not found!' return 1 def Read(self): data = open(self.jpgfile, 'rb').read(20000) # 20kB読めば充分? start = 0 end = 2 marker = self.bin2int(data[start:end]) if marker != SOI: self.ErrMsg = 'Not a JPEG file!' return 1 marker = self.bin2int(data[start+2:end+2]) if marker == APP1: start = end + 2 ret = self.ReadExifFile(start, data) else: start = end ret = self.ReadJfifFile(start, data) return ret def ReadJfifFile(self, start, data): ret = 0 end = start + 2 while 1: marker = lower(self.bin2str(data[start:end])) if marker == lower('FFE0'): sz = self.bin2int(data[end:end+2]) if data[end+2:end+2+4] != 'JFIF': self.ErrMsg = 'Not a JPEG file!' ret = 1 break start = end + sz end = start + 2 elif marker == lower('FFDB'): start = end + 67 end = start + 2 elif marker == lower('FFC4'): sz = self.bin2int(data[end:end+2]) start = end + sz end = start + 2 elif marker == lower('FFC0'): sz_y = self.bin2int(data[end+3:end+5]) sz_x = self.bin2int(data[end+5:end+7]) self.X, self.Y = (sz_x, sz_y) ret = 0 # start = end + 17 # end = start + 2 break else: if marker[:2] == lower('FF'): sz = self.bin2int(data[end:end+2]) start = end + sz end = start + 2 else: break return ret def ReadExifFile(self, start, data): d = self.img_info end = start + 2 sz_app1 = self.bin2int(data[start:end]) start = end end = start + 6 if data[start:end] != EXIF_HDR: self.ErrMsg = 'Not a EXIF file!' return 1 start = end end = start + 2 s = data[start:end] if s == 'II': self.endian = 1 elif s == 'MM': self.endian = 0 self.offset_base = start start = end end = start + 2 sz = self.bin2int(data[start:end]) if sz != TIFF_TAG_MARK: self.ErrMsg = 'Not a EXIF file!' return 1 start = end end = start + 4 sz = self.bin2int(data[start:end]) # start = end start = self.offset_base + sz end = start + 2 # # IFD0の読みとり # next_ifd_pos = self.readIFD(start, data) # # SubIFDの読みとり # start = d['ExifIFDPointer'] + self.offset_base p = self.readIFD(start, data) while(p != self.offset_base): start = p p = self.readIFD(start, data) # # IDF1の読みとり # start = next_ifd_pos p = self.readIFD(start, data) # # InteroperabilityIFDの読みとり if p != self.offset_base: start = p else: if d.has_key('InteroperabilityIFDPointer'): start = d['InteroperabilityIFDPointer'] + self.offset_base if start != self.offset_base: p = self.readIFD(start, data) if d.has_key('ExifImageWidth') and d.has_key('ExifImageHeight'): self.X, self.Y = d['ExifImageWidth'], d['ExifImageHeight'] else: self.X, self.Y = None, None return 0 def readIFD(self, start, data): d = self.img_info num_ifd = self.bin2int(data[start:start+2]) tag_pos = start + 2 for n in range(num_ifd): tag_nam = IFD_TAG[self.bin2int(data[tag_pos:][:2])] tagd_typ = self.bin2int(data[tag_pos+2:][:2]) tagd_siz = self.bin2int(data[tag_pos+4:][:4]) tagd = data[tag_pos+8:][:4] sz = type2bytes[tagd_typ] * tagd_siz if sz > 4: offset = self.bin2int(tagd) tagd = data[self.offset_base+offset:][:sz] if tagd_typ in (2, 7): d[tag_nam] = tagd elif tagd_typ == 5: if self.endian == 0: d1 = self.bin2int(tagd[:4]) d2 = self.bin2int(tagd[4:]) elif self.endian == 1: d2 = self.bin2int(tagd[:4]) d1 = self.bin2int(tagd[4:]) d[tag_nam] = str(d1) + '/' + str(d2) elif tagd_typ == 10: if self.endian == 0: d1 = self.bin2sint(tagd[:4]) d2 = self.bin2sint(tagd[4:]) elif self.endian == 1: d2 = self.bin2sint(tagd[:4]) d1 = self.bin2sint(tagd[4:]) d[tag_nam] = str(d1) + '/' + str(d2) elif tagd_typ in (1, 3, 4): d[tag_nam] = self.bin2int(tagd) elif tagd_typ in (6, 8, 9): d[tag_nam] = self.bin2sint(tagd) tag_pos = tag_pos + 12 next_ifd_pos = self.offset_base + self.bin2int(data[tag_pos:][:4]) return next_ifd_pos def bin2str(self, d): s = '' for n in d: ss = hex(ord(n))[2:] if len(ss) == 1: ss = '0' + ss s = s + ss return s def bin2int(self, d): m = len(d) - 1 r = 0 for n in range(len(d)): if self.endian == 0: r = r + ord(d[n]) * pow(256, m - n) elif self.endian == 1: r = r + ord(d[n]) * pow(256, n) return r def bin2sint(self, d): m = pow(256, len(d)) / 2 r = self.bin2int(d) if r >= m: r = r - m * 2 return r def getResult(self): return self.X, self.Y def getErrMsg(self): return self.ErrMsg if __name__ == '__main__': # filename = '317.jpg' filename = 'DSC00494.JPG' J = JPG_SZ(filename) if J.Read() == 0: print '%s: (x, y) = (%d, %d)' % ((filename,) + J.getResult()) else: print J.getErrMsg()