2010-09-12 11 views
10

Ich versuche, das Datum/die Uhrzeit zu extrahieren, wenn ein Bild von der CR2 (Canon-Format für RAW-Bilder) gemacht wurde.Lesen eines CR2 (Raw Canon Image) Header mit Python

Ich kenne die CR2 specification, und ich weiß, ich kann Python struct Modul verwenden, um Stücke aus einem binären Puffer zu extrahieren.

Kurz gesagt, die Spezifikation sagt, dass in Tag 0x0132/306 ich eine Zeichenfolge der Länge 20 finden kann - das Datum und die Uhrzeit.

Ich habe versucht, diesen Tag zu erhalten, indem mit: Irgendwelche Ideen

struct.unpack_from(20*'s', buffer, 0x0132) 

aber ich bekomme

('\x00', '\x00', "'", '\x88, ...[and more crap]) 

?

bearbeiten

Vielen Dank für die gründliche Mühe! Die Antworten sind phänomenal und ich habe viel über den Umgang mit binären Daten gelernt.

+0

Dies ist etwas alt, ich weiß, aber wenn Sie die Daten nicht selbst lesen wollen, schrieb ich einfach eine Bibliothek für das Parsen von CR2-Dateien namens [rawphoto ] (https://github.com/photoshell/rawphoto). Es ist auch auf [PyPi] (https://pypi.python.org/pypi/rawphoto). Ich hoffe, es ist hilfreich für Sie. –

Antwort

7

Haben Sie den Header berücksichtigt, der (gemäß der Spezifikation) dem IFD-Block vorangehen sollte, von dem Sie sprechen?

Ich schaute durch die Spezifikation und es sagt der erste IFD-Block folgt dem 16-Byte-Header. Wenn wir also die Bytes 16 und 17 (bei Offset 0x10 hex) lesen, sollten wir die Anzahl der Einträge im ersten IFD-Block erhalten. Dann müssen wir nur jeden Eintrag durchsuchen, bis wir eine passende Tag-ID gefunden haben, die (wie ich es gelesen habe) uns den Byte-Offset Ihrer Datums-/Zeit-Zeichenkette gibt.

Dies funktioniert für mich:

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) 

Ausgang für meine Beispieldatei:

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') 

[Bearbeiten] - ein überarbeitetes/gründlichere Beispiel

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" 
+0

Danke @Jon Cage. Ich fürchte, ich weiß nicht, wie ich das machen soll. Wie kann ich herausfinden, welcher Block vorangeht? – Escualo

+0

Es würde sich lohnen, den Endian-Check nach Jims Antwort für eine robustere Lösung zu machen, aber mein Beispiel funktionierte gut auf einem AMD Windows 7 Rechner :-) –

+0

Man das ist toll - Danke! – Escualo

6

0x0132 ist nicht der Offset, es ist die Tag-Nummer des Datums. CR2 bzw. TIFF ist ein verzeichnisbasiertes Format. Sie müssen den Eintrag nach Ihrem (bekannten) Tag suchen, nach dem Sie suchen.

bearbeiten: Ok, zunächst einmal müssen Sie lesen, wenn die Datendatei gespeichert wird wenig oder Big-Endian-Format. Die ersten acht Bytes geben den Header an, und die ersten zwei Bytes dieses Headers spezifizieren die Endianess. Pythons struct-Modul ermöglicht die Verarbeitung von Little- und Big-Endian-Daten durch Voranstellen einer Formatzeichenfolge mit '<' oder '>'. Also, data Annahme, daß ein Puffer Ihre CR2 Bild enthält, Sie endianness über

header = data[:8] 
endian_flag = "<" if header[:2] == "II" else ">" 

Die Formatangabe besagt, dass das erste Bilddateiverzeichnis beginnt mit einem Offset relativ zum Anfang der Datei verarbeiten kann, mit wobei der Versatz in den letzten 4 Bytes des Headers angegeben. Also, um den Offset zu dem ersten IFD Sie eine Zeile ähnlich wie diese verwenden:

ifd_offset = struct.unpack("{0}I".format(endian_flag), header[4:])[0] 

Sie können nun voran gehen und die erste IFD lesen. Sie finden die Anzahl der Einträge im Verzeichnis am angegebenen Offset in der Datei, die zwei Byte breit ist. Somit würde die Anzahl der Einträge in der ersten IFD gelesen werden:

number_of_entries = struct.unpack("{0}H".format(endian_flag), data[ifd_offset:ifd_offset+2])[0] 

Ein Feldeintrag 12 Byte lang ist, so können Sie die Länge des IFD berechnen. Nach number_of_entries * 12 Bytes gibt es einen weiteren Offset von 4 Byte, der Ihnen mitteilt, wo Sie nach dem nächsten Verzeichnis suchen müssen. Das ist im Grunde, wie Sie mit TIFF- und CR2-Bildern arbeiten.

Die "Magie" hier ist zu beachten, dass mit jedem der 12 Byte Feldeinträge, die ersten zwei Bytes die Tag-ID sein wird. Und hier suchen Sie nach Ihrem Tag 0x0132. Also, da Sie wissen, dass die erste IFD beginnt bei ifd_offset in der Datei, können Sie das erste Verzeichnis über scannen:

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 

Sie würden offensichtlich wollen, Refactoring, dass in einer gemeinsamen Funktion Auspacken und wahrscheinlich das ganze Format wickeln in eine nette Klasse, aber das ist außerhalb des Rahmens dieses Beispiels.Sie können das Entpacken auch verkürzen, indem Sie mehrere Formatstrings zu einem zusammenfassen, wodurch ein größeres Tupel entsteht, das alle Felder enthält, die Sie in verschiedene Variablen entpacken können, die ich aus Gründen der Übersichtlichkeit weggelassen habe.

+0

Können Sie ein Beispiel geben? Ich bin hier völlig am Ende ... Danke! – Escualo

+0

+1: für den Endian-Check war ich zu faul zum Implementieren :-) –

+0

Toll - ich wünschte, ich könnte mehr upvoten. – Escualo

4

I festgestellt, dass EXIF.py von https://github.com/ianare/exif-py liest die EXIF-Daten von .CR2 fi les. Es scheint, dass, da .CR2-Dateien auf TIFF-Dateien basieren, EXIF.py kompatibel ist.

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 

Und dieser Druck:

2011:04:30 11:08:44 
    (2011, 4, 30, 11, 8, 44, 5, 120, -1)