Чтение заголовка CR2 (Raw Canon Image) с помощью Python
Я пытаюсь извлечь дату / время, когда снимок был сделан из CR2 (Canon format for raw pictures).
Я знаю спецификацию CR2, и я знаю, что могу использовать модуль Python struct для извлечения фрагментов из двоичного буфера.
Вкратце, спецификация говорит, что в теге0x0132 / 306
я могу найти строку длиной 20 - дата и время.
Я попытался получить этот тег, используя:
struct.unpack_from(20*'s', buffer, 0x0132)
Но я получаю
('x00', 'x00', "'", 'x88, ...[and more crap])
Есть идеи?
Edit
Большое спасибо за тщательные усилия! Ответы феноменальны, и я узнал много нового об обработке двоичных данных.3 ответа:
Вы приняли во внимание заголовок, который должен (согласно спецификации) предшествовать блоку IFD, о котором вы говорите?
Я просмотрел спецификацию, и она говорит, что первый блок IFD следует за 16-байтовым заголовком. Таким образом, если мы читаем байты 16 и 17 (при смещении 0x10 hex), мы должны получить количество записей в первом блоке IFD. Затем мы просто должны искать каждую запись, пока не найдем соответствующий идентификатор тега, который (как я читаю) дает нам смещение байта вашей даты / времени строка.
Это работает для меня:
from struct import * def FindDateTimeOffsetFromCR2( buffer, ifd_offset ): # Read the number of entries in IFD #0 (num_of_entries,) = unpack_from('H', buffer, ifd_offset) print "ifd #0 contains %d entries"%num_of_entries # Work out where the date time is stored datetime_offset = -1 for entry_num in range(0,num_of_entries-1): (tag_id, tag_type, num_of_value, value) = unpack_from('HHLL', buffer, ifd_offset+2+entry_num*12) if tag_id == 0x0132: print "found datetime at offset %d"%value datetime_offset = value return datetime_offset if __name__ == '__main__': with open("IMG_6113.CR2", "rb") as f: buffer = f.read(1024) # read the first 1kb of the file should be enough to find the date / time datetime_offset = FindDateTimeOffsetFromCR2(buffer, 0x10) print unpack_from(20*'s', buffer, datetime_offset)
Вывод для моего примера файла:
ifd #0 contains 14 entries found datetime at offset 250 ('2', '0', '1', '0', ':', '0', '8', ':', '0', '1', ' ', '2', '3', ':', '4', '5', ':', '4', '6', '\x00')
[edit] - исправленный / более подробный пример
from struct import * recognised_tags = { 0x0100 : 'imageWidth', 0x0101 : 'imageLength', 0x0102 : 'bitsPerSample', 0x0103 : 'compression', 0x010f : 'make', 0x0110 : 'model', 0x0111 : 'stripOffset', 0x0112 : 'orientation', 0x0117 : 'stripByteCounts', 0x011a : 'xResolution', 0x011b : 'yResolution', 0x0128 : 'resolutionUnit', 0x0132 : 'dateTime', 0x8769 : 'EXIF', 0x8825 : 'GPS data'}; def GetHeaderFromCR2( buffer ): # Unpack the header into a tuple header = unpack_from('HHLHBBL', buffer) print "\nbyte_order = 0x%04X"%header[0] print "tiff_magic_word = %d"%header[1] print "tiff_offset = 0x%08X"%header[2] print "cr2_magic_word = %d"%header[3] print "cr2_major_version = %d"%header[4] print "cr2_minor_version = %d"%header[5] print "raw_ifd_offset = 0x%08X\n"%header[6] return header def FindDateTimeOffsetFromCR2( buffer, ifd_offset, endian_flag ): # Read the number of entries in IFD #0 (num_of_entries,) = unpack_from(endian_flag+'H', buffer, ifd_offset) print "Image File Directory #0 contains %d entries\n"%num_of_entries # Work out where the date time is stored datetime_offset = -1 # Go through all the entries looking for the datetime field print " id | type | number | value " for entry_num in range(0,num_of_entries): # Grab this IFD entry (tag_id, tag_type, num_of_value, value) = unpack_from(endian_flag+'HHLL', buffer, ifd_offset+2+entry_num*12) # Print out the entry for information print "%04X | %04X | %08X | %08X "%(tag_id, tag_type, num_of_value, value), if tag_id in recognised_tags: print recognised_tags[tag_id] # If this is the datetime one we're looking for, make a note of the offset if tag_id == 0x0132: assert tag_type == 2 assert num_of_value == 20 datetime_offset = value return datetime_offset if __name__ == '__main__': with open("IMG_6113.CR2", "rb") as f: # read the first 1kb of the file should be enough to find the date/time buffer = f.read(1024) # Grab the various parts of the header (byte_order, tiff_magic_word, tiff_offset, cr2_magic_word, cr2_major_version, cr2_minor_version, raw_ifd_offset) = GetHeaderFromCR2(buffer) # Set the endian flag endian_flag = '@' if byte_order == 0x4D4D: # motorola format endian_flag = '>' elif byte_order == 0x4949: # intel format endian_flag = '<' # Search for the datetime entry offset datetime_offset = FindDateTimeOffsetFromCR2(buffer, 0x10, endian_flag) datetime_string = unpack_from(20*'s', buffer, datetime_offset) print "\nDatetime: "+"".join(datetime_string)+"\n"
0x0132-это не смещение, это номер тега даты. CR2 или TIFF, соответственно, это формат, основанный на каталоге. Вы должны найти запись, заданную вашим (известным) тегом, который вы ищете.
Edit : Хорошо, прежде всего, вы должны прочитать, если данные файла сохраняются в формате little или big-endian. Первые восемь байт определяют заголовок, а первые два байта этого заголовка определяют конечность. Модуль struct Python позволяет обрабатывать небольшие и большие конечные данные путем префикса строки формата с помощью ''. Итак, предполагая, что
data
является буфером, содержащим ваш образ CR2, вы можете обрабатывать endianness черезСпецификация формата указывает, что первый каталог файла изображения начинается со смещения относительно начала файла, причем смещение указывается в последних 4 байтах заголовка. Таким образом, чтобы получить смещение к первому IFD, вы можете использовать строку, подобную этой:header = data[:8] endian_flag = "<" if header[:2] == "II" else ">"
Теперь вы можете пойти дальше и прочитать первый IFD. Вы найдете количество записей в каталоге с указанным смещением в файле, которое составляет два байта в ширину. Таким образом, вы бы прочитали количество записей в первом IFD, используя:ifd_offset = struct.unpack("{0}I".format(endian_flag), header[4:])[0]
Запись в поле имеет длину 12 байт, поэтому вы можете вычислить длину IFD. После number_of_entries * 12 байт появится еще одно смещение длиной 4 байта, указывающее, где искать следующий каталог. Это в основном то, как вы работаете с изображениями TIFF и CR2.number_of_entries = struct.unpack("{0}H".format(endian_flag), data[ifd_offset:ifd_offset+2])[0]
"магия" здесь заключается в том, чтобы обратите внимание, что в каждой из 12 байтовых записей поля первые два байта будут идентификатором тега. И именно там вы ищете свой тег 0x0132. Итак, если вы знаете, что первый IFD начинается с ifd_offset в файле, вы можете сканировать первый каталог через:
current_position = ifd_offset + 2 for field_offset in xrange(current_position, number_of_entries*12, 12): field_tag = struct.unpack("{0}H".format(endian_flag), data[field_offset:field_offset+2])[0] field_type = struct.unpack("{0}H".format(endian_flag), data[field_offset+2:field_offset+4])[0] value_count = struct.unpack("{0}I".format(endian_flag), data[field_offset+4:field_offset+8])[0] value_offset = struct.unpack("{0}I".format(endian_flag), data[field_offset+8:field_offset+12])[0] if field_tag == 0x0132: # You are now reading a field entry containing the date and time assert field_type == 2 # Type 2 is ASCII assert value_count == 20 # You would expect a string length of 20 here date_time = struct.unpack("20s", data[value_offset:value_offset+20]) print date_time
Очевидно, что вы хотите рефакторировать эту распаковку в общую функцию и, вероятно, обернуть весь формат в хороший класс, но это выходит за рамки данного примера. Вы также можете сократить время распаковки, объединив несколько форматируйте строки в один, получая более крупный кортеж, содержащий все поля, которые вы можете распаковать в отдельные переменные, которые я оставил для ясности.
Я обнаружил, что EXIF.py от https://github.com/ianare/exif-py считывает данные EXIF из файлов. CR2. Похоже, что потому, что файлы. CR2 основаны на .Файлы TIFF EXIF.py совместим.
import EXIF import time # Change the filename to be suitable for you f = open('../DCIM/100CANON/IMG_3432.CR2', 'rb') data = EXIF.process_file(f) f.close() date_str = data['EXIF DateTimeOriginal'].values # We have the raw data print date_str # We can now convert it date = time.strptime(date_str, '%Y:%m:%d %H:%M:%S') print date
И это печатает:
2011:04:30 11:08:44 (2011, 4, 30, 11, 8, 44, 5, 120, -1)