¿Cómo monto mis páginas web de viajes? (II)
Hace tiempo expliqué cómo montaba mis páginas webs de viajes. Hay algunos cambios en el año y medio transcurrido:
-
Subo las fotografías por WebDAV con el siguiente script en Python:
El funcionamiento del programa es bien sencillo. Lo más interesante es que mientras se está subiendo una fotografía, se está pidiendo al servidor ZOPE que genere la preview de la fotografía anterior. También nos aseguramos de que las fotos se suben en orden, algo crítico en mi entorno. El programa tiene mucho cuidado en no sobreescribir ficheros.
No hay control de errores porque el proceso se ejecuta de forma manual. Si hay problemas, lo volvemos a ejecutar.
-
La creación de previews de las fotografías se realizaba de forma separada, manual, y carpeta a carpeta. Lento y tenía timeouts del proxy con frecuencia. El script del punto anterior pide generar las previews foto a foto, en paralelo con la subida de las mismas. El proceso se solapa y no consume tiempo adicional, podemos ir viendo cómo queda la página web a medida que se suben fotos, etc.
He tenido que modificar el script ZOPE Generar_preview para contemplar esta posibilidad:
Si indicamos una imagen, genera la preview de dicha imagen. Si le indicamos un directorio, genera las previews de todas las imágenes en ese directorio.
-
Ahora extraemos mucha más información EXIF de las fotografías. Tenemos un External Method de ZOPE con el siguiente contenido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
import exifread # Los "str()" son por: # http://www.joonis.de/en/zope/dtml-mixed-encoding def generar_datetimeoriginal(self) : f = open(self._get_fsname(self.filename), "rb") tags = exifread.process_file(f, details=False) datetimeoriginal = "" if "EXIF DateTimeOriginal" in tags : datetimeoriginal = tags["EXIF DateTimeOriginal"].values datetimeoriginal = str(datetimeoriginal.replace(":", "-", 2)) if not self.hasProperty('exif_datetimeoriginal') : self.manage_addProperty('exif_datetimeoriginal', "", 'string') if datetimeoriginal == "" : self.manage_delProperties(("exif_datetimeoriginal",)) else : self.manage_changeProperties({"exif_datetimeoriginal":datetimeoriginal}) return datetimeoriginal 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}) focal = "" if "EXIF FocalLength" in tags : focal = tags["EXIF FocalLength"].values[0] focal = float(focal.num)/focal.den # Equivalencia a 35mm. Crop Factor # http://www.devicespecifications.com/en/model/5d342ce2 if model == "Canon PowerShot SX60 HS" : focal = focal * 5.61 elif model == "bq Aquaris E5" : focal = focal * 7.61 elif model == "SAMSUNG GT-I9100" : focal = focal * 7.61 elif model == "Apple iPhone 6" : focal = focal * 7.21 elif model == "Apple iPhone 5s" : focal = focal * 7.08 elif model == "Apple iPhone 4": focal = focal * 7.61 else : focal = '' if focal != '' : focal = "%.1f" %focal if not self.hasProperty('exif_focal') : self.manage_addProperty('exif_focal', "", 'string') if focal == "" : self.manage_delProperties(("exif_focal",)) else : self.manage_changeProperties({"exif_focal":focal}) 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+" "+focal+"mm "+hdr def generar_gps(self) : f = open(self._get_fsname(self.filename), "rb") tags = exifread.process_file(f, details=False) latitud = longitud = "" if "GPS GPSLongitude" in tags : lat = tags["GPS GPSLatitude"].values lon = tags["GPS GPSLongitude"].values if (lat[0].den==1) and (lon[0].den==1) : minseg = lat[2].num/60.0/lat[2].den if lat[2].num else 0 latitud = "%s %d° %.3f" %(tags["GPS GPSLatitudeRef"].values, lat[0].num, lat[1].num/float(lat[1].den)+minseg) minseg = lon[2].num/60.0/lon[2].den if lon[2].num else 0 longitud = "%s %d° %.3f" %(tags["GPS GPSLongitudeRef"].values, lon[0].num, lon[1].num/float(lon[1].den)+minseg) else : longitud = latitud = "XXXXXXXXX" latitud = str(latitud) longitud = str(longitud) if not self.hasProperty('longitud') : self.manage_addProperty('longitud', "", 'string') self.manage_addProperty('latitud', "", 'string') self.manage_changeProperties({"longitud":longitud,"latitud":latitud}) if latitud == "" : self.manage_delProperties(("longitud", "latitud")) return latitud+" "+longitud
Hay una rutina nueva, generar_metadata(), que proporciona información como el valor ISO, tiempo de exposición, nivel de zoom, modelo de la cámara, HDR, etc.
Si estamos procesando una fotografía de una cámara conocida, nos proporcionará la distancia focal de la fotografía, equivalente a una cámara de 35mm [1].
[1] En el caso de usar la lente frontal del teléfono (por ejemplo, un selfie), el valor será incorrecto. Es algo a mejorar en el futuro.
-
Voy añadiendo metadatos adicionales de vez en cuando y me gusta que su efecto sea retroactivo. Así, tengo el siguiente script ZOPE Generar_metadata_recursivo para hacer eso: repasar todas las fotografías bajo un directorio dado -de forma recursiva- y regenerar sus metadatos:
-
La gestión de la navegación dentro de un viaje dado ha cambiado mucho. La idea es que sea mucho más simple navegar dentro de un viaje de varios días, con fotografías de diferentes personas, y que mejoras futuras en el sistema se puedan aplicar de forma retroactiva.
Ahora el index_html de ZOPE contendrá lo siguiente:
Aquí se imprime el texto de entrada y los menús de navegación, se visualizan las fotos de toda la gente, etc. Como siempre en ZOPE, si ese fichero no existe, se usará el del directorio padre. [2]
[2] Para que este código fuera general tendría que tener la lista de personas en otro lugar. Es una mejora futura.
En el fichero texto ponemos el texto que queremos incluir al principio de la página web. Si ese fichero no existe, se usará el del directorio padre. Aquí, típicamente, doy contexto sobre el viaje en general o sobre qué pasó ese día concreto.
La visualización en sí de una foto se realiza a través del fichero foto. De nuevo, si no existe ese fichero, se usará la versión en el directorio padre. El contenido actual es:
Obsérvese que ahora cada foto tiene un permalink para poder enlazarla.
Cada directorio puede contener un fichero menu_entries. Si no lo tiene, se busca en el directorio padre (útil para gestionar los viajes largos por días o por actividad). Un contenido posible es:
Ahí aparece el título del viaje y las diferentes entradas del menú de navegación.
El menú en sí se gestiona con el script ZOPE menu2 (tengo un script ZOPE menu, incompatible):
La gran ventaja de trabajar con ficheros separados es que se pueden sobrecargar en directorios hijos con facilidad. Además, cuando esté satisfecho con el resultado, puedo mover esos ficheros al directorio superior con el efecto de a) no repetir esa información en todos los directorios de viajes y b) actualizando alguno de esos ficheros, se actualizan TODOS los viajes.
-
La funcionalidad de usar Google Maps para mostrar un fichero KML hospedado en mi propio servidor web ha desaparecido. He estado una temporada buscando alternativas y, finalmente, me he decidido por la biblioteca Javascript Leaflet. Esto es tema de otro artículo. Lo podéis ver funcionando en https://www.jcea.es/pics/italia2015, más concretamente en https://www.jcea.es/pics/italia2015/italia2015.kml/mapa.
Actualización 20160205: He publicado el artículo: Mapas y capas de información.
-
Actualización 20160128: Artículo sobre cómo geoetiquetar las fotografías de mi cámara digital WIFI desde el móvil: Geolocalización en una cámara de fotos WIFI (¿Cómo monto mis páginas web de viajes? (II bis)).
Año y medio de evolución resumido en diez minutos de lectura :-).