Más sobre el etiquetado EXIF de fotos en mi web: HDR

En mi artículo ¿Cómo monto mis páginas web de viajes? explicaba cómo creo los álbumes fotográficos de mis viajes. Pero si sois asiduos a mi página web habréis visto que últimamente estoy mostrando más información sobre las fotos, como detalles High Dynamic Range. Por ejemplo, en las últimas fotografías de Italia.

La mayor dificultad es el propio formato EXIF. Cada cámara incluye sus propias extensiones propietarias completamente indocumentadas. Intentar descifrarlas supone una tarea de chinos, complicada además porque EXIF es un formato barroco que ha envejecido muy mal. Es muy de los 80 y las extensiones se han hecho siempre de forma descoordinada, ad-hoc y de mala manera. Incluso un mismo fabricante puede usar extensiones propietarias diferentes para distintos modelos de sus propias cámaras... para indicar lo mismo. Un auténtico batiburrillo infumable.

Estuve preguntando aquí y allí sin mucho éxito. Al final tuve que invertir un día entero aprendiendo sobre EXIF más de lo recomendable para mi salud mental y escribir los parches yo mismo.

Las preguntas:

Mis parches para la librería Python ExifRead:

  1. Correcty process the Makernote of some Canon models #49.

    Este parche gestiona adecuadamente los MakerNote de las últimas cámaras Canon.

  2. Support HDR in Canon cameras #50.

    Tras el parche anterior tenemos la infraestructura para ser capaces de detectar las imágenes HDR de mi Canon.

  3. Support Apple Makernote and Apple HDR details #51.

    Las fotos sacadas desde iOS también contienen información HDR. Naturalmente con sus propias extensiones propietarias, como todo el mundo.

Espero que la próxima actualización de la librería Python ExifRead incluya estas mejoras. Como he dicho, parecen una chorrada, pero invertí un día entero en investigar esa amalgama inconexa que es EXIF y varias horas más en entender ExifRead y escribir los parches correspondientes.

Todavía queda trabajo por hacer. Mi cámara Canon tienen varias configuraciones HDR diferentes y ahora mismo no sé cómo distinguirlas en el EXIF. También guarda datos tan interesantes como la temperatura ambiental en el momento de sacar la foto. O maravillas tales como el ángulo de visión, la distancia hiperfocal para los reglajes de la foto, etc. Habrá que seguir investigando. Por su parte iOS añade metadatos adicionales en las ráfagas de fotografías, campo que no he explorado y que no he necesitado hasta ahora.

Tanto por hacer, tan poco tiempo...

Como explico en ¿Cómo monto mis páginas web de viajes? mi servidor web es ZOPE. Recomiendo leer ese artículo para entender lo que sigue:

  1. Mi método Python Generar_preview ha pasado a contener lo siguiente:

      # Example code:
    
      # Import a standard function, and get the HTML request and response objects.
      from Products.PythonScripts.standard import html_quote
      request = container.REQUEST
      RESPONSE =  request.RESPONSE
    
      j=context.objectValues(["ExtImage"])
      for i in j :
       scale = max(i.height(), i.width())/461.0
       h,w=round(i.height()/scale),round(i.width()/scale)
       i.manage_create_prev(maxx=w, maxy=h, ratio=True)
       print "<br>"+i.Generar_metadata()+" "+i.Generar_gps()+ " <font size=-2>"+i.Generar_datetimeoriginal()+"</font>"
    
      print "<br><br>Convertimos %d im&aacute;genes" %len(j)
      return printed
    
  2. Generar_metadata es un método externo Python:

      import exifread
    
      # Los "str()" son por:
      # http://www.joonis.de/en/zope/dtml-mixed-encoding
    
      def generar_metadata(self) :
        f = open(self._get_fsname(self.filename), "rb")
        # Si usamos "details=False" no vemos los tags de HDR.
        tags = exifread.process_file(f, details=True)
    
        ISO = ""
        if "EXIF ISOSpeedRatings" in tags :
          ISO = str(tags["EXIF ISOSpeedRatings"].values[0])
    
        if not self.hasProperty('exif_ISO') :
          self.manage_addProperty('exif_ISO', "", 'string')
        if ISO == "" :
          self.manage_delProperties(("exif_ISO",))
        else :
          self.manage_changeProperties({"exif_ISO":ISO})
    
        exposure = ""
        if "EXIF ExposureTime" in tags :
          exposure = str(tags["EXIF ExposureTime"].values[0])
    
        if not self.hasProperty('exif_exposure') :
          self.manage_addProperty('exif_exposure', "", 'string')
        if exposure == "" :
          self.manage_delProperties(("exif_exposure",))
        else :
          self.manage_changeProperties({"exif_exposure":exposure})
    
        aperture = ""
        if "EXIF FNumber" in tags :
          aperture = tags["EXIF FNumber"].values[0]
          aperture = "%.1f" %(float(aperture.num)/aperture.den)
    
        if not self.hasProperty('exif_aperture') :
          self.manage_addProperty('exif_aperture', "", 'string')
        if aperture == "" :
          self.manage_delProperties(("exif_aperture",))
        else :
          self.manage_changeProperties({"exif_aperture":aperture})
    
        model = ""
        maker = ""
        if "Image Model" in tags :
          model = str(tags["Image Model"].values)
        if "Image Make" in tags :
          maker = str(tags["Image Make"].values)
          if not model.startswith(maker) :
            model = maker+" "+model
    
        if not self.hasProperty('exif_model') :
          self.manage_addProperty('exif_model', "", 'string')
        if model == "" :
          self.manage_delProperties(("exif_model",))
        else :
          self.manage_changeProperties({"exif_model":model})
    
        hdr = ""
        if maker == "Canon" :
          if "MakerNote EasyShootingMode" in tags :
            if tags["MakerNote EasyShootingMode"].printable == "High Dynamic Range" :
              hdr = "HDR"
        if maker == "Apple" :
          if "MakerNote HDRImageType" in tags :
            if tags["MakerNote HDRImageType"].printable == "HDR Image" :
              hdr = "HDR"
    
        if not self.hasProperty('exif_hdr') :
          self.manage_addProperty('exif_hdr', "", 'string')
        if hdr == "" :
          self.manage_delProperties(("exif_hdr",))
        else :
          self.manage_changeProperties({"exif_hdr":hdr})
    
        return model+" ISO "+ISO+ " "+exposure+" sec "+"f/"+aperture+" "+hdr
    

    Con este código extraemos todos los datos EXIF que nos interesan. Los metemos en los atributos ZOPE de las imágenes que se van procesando. Todo el rollo con str() es porque mi web está en ISO-8859-15 cuando hoy en día debería ser UTF-8, pero cambiarlo es completamente inabordable.

    (Sí, ya sé que debería refactorizar todo ese código)

    Obsérvese que cada tipo de cámara requiere una gestión diferente.

  3. A la hora de visualizar las fotos empleo el siguiente código DTML:

      <font size=+1><dtml-var title_or_id></font>
      <br><dtml-var "size()">
      <dtml-if exif_datetimeoriginal><br><dtml-var exif_datetimeoriginal></dtml-if>
      <dtml-if latitud><br><i><dtml-var latitud> <dtml-var longitud></i></dtml-if>
      <p><dtml-if exif_model><font size="-2"><dtml-var exif_model></font></dtml-if>
      <dtml-if exif_ISO><font size="-2">, ISO <dtml-var exif_ISO></font></dtml-if>
      <dtml-if exif_aperture><font size="-2">, f/<dtml-var exif_aperture></font></dtml-if>
      <dtml-if exif_exposure><font size="-2">, <dtml-var exif_exposure> sec</font></dtml-if>
      <dtml-if exif_hdr><font size="-2">, HDR</font></dtml-if>
      <p><dtml-var descr>
      <br clear="all">
    

    (Sí, ya sé que debería refactorizar TAMBIÉN todo esto)

Espero que mis fotos de Italia os gusten :).