2010-09-12 16 views
10

Estoy tratando de extraer la fecha/hora cuando se tomó una foto del CR2 (formato de Canon para imágenes sin formato).Lectura de un encabezado CR2 (Imagen en bruto de Canon) usando Python

Sé el CR2 specification, y sé que puedo usar el módulo Python struct para extraer piezas de un búfer binario.

En pocas palabras, la especificación dice que en la etiqueta 0x0132/306 puedo encontrar una cadena de longitud 20, la fecha y la hora.

Me trataron de conseguir que la etiqueta mediante el uso de:

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

pero consigo

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

¿Alguna idea?

Edición

Muchas gracias por el esfuerzo a fondo! Las respuestas son fenomenales y aprendí mucho sobre el manejo de datos binarios.

+0

Esto es un poco viejo, lo sé, pero si usted no desea leer los datos usted mismo, escribí una biblioteca para analizar archivos CR2 llamado simplemente [rawphoto ] (https://github.com/photoshell/rawphoto). También está en [PyPi] (https://pypi.python.org/pypi/rawphoto). Espero que te sea útil. –

Respuesta

7

¿Ha tenido en cuenta el encabezado que debería (de acuerdo con la especificación) preceder al bloque IFD del que está hablando?

Miré a través de la especificación y dice que el primer bloque IFD sigue al encabezado de 16 bytes. Entonces, si leemos los bytes 16 y 17 (en offset 0x10 hex) deberíamos obtener el número de entradas en el primer bloque IFD. Luego solo tenemos que buscar a través de cada entrada hasta que encontremos una identificación de etiqueta coincidente que (a medida que la leo) nos da el desplazamiento de bytes de su cadena de fecha/hora.

Esto funciona para mí:

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) 

de salida para mi ejemplo del archivo es:

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

[editar] - a/ejemplo más a fondo revisada

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

Gracias @Jon Cage. Me temo que no sé cómo hacer eso. ¿Cómo puedo saber qué bloque precede? – Escualo

+0

Valdría la pena hacer el control endian según la respuesta de Jim para una solución más robusta, pero mi muestra funcionó bien en una máquina AMD Windows 7 :-) –

+0

Hombre esto es genial - ¡Gracias! – Escualo

6

0x0132 no es el desplazamiento, es el número de etiqueta de la fecha. CR2 o TIFF, respectivamente, es un formato basado en directorio. Tienes que buscar la entrada dada tu etiqueta (conocida) que estás buscando.

Editar: Ok, antes que nada, tiene que leer si los datos del archivo se guardan con formato pequeño o grande. El primer byte de ocho especifica el encabezado, y los primeros dos bytes de ese encabezado especifican el endianness. El módulo struct de Python le permite manejar datos pequeños y grandes de endian prefijando una cadena de formato con '<' o '>'. Por lo tanto, asumiendo data es un tampón que contiene su imagen CR2, que puede manejar endianness través

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

La especificación de formato indica que el primer directorio de archivo de imagen comienza en un desplazamiento relativo al principio del archivo, con el desplazamiento siendo especificado en los últimos 4 bytes del encabezado. Por lo tanto, para obtener la compensación a la primera IFD, se puede utilizar una línea similar a la siguiente:

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

Ahora puede seguir adelante y leer el primer IFD. Encontrará el número de entradas en el directorio en el desplazamiento especificado en el archivo, que tiene dos bytes de ancho. Por lo tanto, debería leer el número de entradas en la primera IFD usando:

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

Una entrada de campo es de 12 bytes de longitud, por lo que se puede calcular la longitud de la IFD. Después de number_of_entries * 12 bytes, habrá otra compensación de 4 bytes de longitud, que le indicará dónde buscar el siguiente directorio. Eso es básicamente cómo trabajas con imágenes TIFF y CR2.

La "magia" aquí es observar que con cada una de las entradas de campo de 12 bytes, los primeros dos bytes serán la identificación de la etiqueta. Y ahí es donde buscas tu etiqueta 0x0132. Por lo tanto, dado que sabe que la primera IFD comienza en ifd_offset en el archivo, puede escanear el primer directorio a través de:

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 

Usted sería obvio que quiere refactor que desembalar en una función común y probablemente envolver todo el formato en una buena clase, pero eso está más allá del alcance de este ejemplo.También puede acortar el desembalaje combinando varias cadenas de formato en una, produciendo una tupla más grande que contiene todos los campos que puede descomprimir en distintas variables, que dejé fuera para mayor claridad.

+0

¿Puedes dar un ejemplo? Estoy completamente perdido aquí ... ¡Gracias! – Escualo

+0

+1: para el control de endian era demasiado perezoso para implementar :-) –

+0

Genial, desearía poder votar más. – Escualo

4

I encontró que EXIF.py de https://github.com/ianare/exif-py lee los datos EXIF ​​de .CR2 fi les. Parece que debido a que los archivos .CR2 están basados ​​en archivos .TIFF, EXIF.py es compatible.

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 

Y esta impresiones:

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