Moviendo las imágenes de Flickr a Digital Ocean Spaces

Cyberhades Digital Ocean Spaces

*Cyberhades Digital Ocean Spaces*

En Cyberhades hemos estado usando Flickr como repositorio de imágenes desde el 2008. Incluso en los años 2015 y 2016 pagamos por una cuenta profesional, que honestamente no recuerdo la differencia entre la cuenta pro y la gratuita free, cuyo coste era de $24.95 al año. A partir del año 2017 volvimos a usar la cuenta free, porque no le estábamos sacando ningún beneficio a la cuenta de pago. Nuestra experiencia con Flickr siempre ha sido muy positiva, nunca hemos tenido problemas con el servicio, pero el año pasado Flickr fue adquirida por SmugMug, y recientemente anunciaron que las cuentas gratuitas de Flickr estarían limitadas a un máximo de 1000 imágenes, y el resto serían borradas, a menos que pagues por una cuenta pro, $50 al año, o $6 al mes. En nuestro caso, tenemos en Flickr 3.997 imágenes, con lo cual si Flickr nos borra 2.997 imágenes, tendría un gran impacto en el blog. La cuenta pro de Flickr provee de otros beneficios más allá del número ilimitado de fotos, y si eres fotógrafo lo mismo te interesa, pero en nuestro caso, quitando el CDN, no nos aporta ningún valor extra que beneficie el blog.

El motivo de esta entreda no es publicar el hecho que hemos movido las fotos de sitio, sino el compartir cómo lo hicimos.

Lo primero era decidir dónde migrar, y después de mirar varios proveedores en la nube, decidimos usar el servicio Spaces de Digital Ocean. Hace unos 3 años que nos movimos a este proveedor y estamos muy satisfechos con sus servicios. Spaces, además también tiene un CDN y es compatible con S3 de Amazon, es decir, puedes usar cualquier cliente o herramienta diseñada para S3 con Spaces, y otra cosa que nos gusta mucho de Digital Ocean, es la política de precios, puedes contratar servicios a precio fijo, y no por uso. Personalmente no me gustan las sorpresas, sobre todo al final de mes cuando te llega la factura para pagar :)

Lo siguiente que necesitamos hacer es descargar las fotos de Flickr. Por suerte, Flickr nos permite descargar todos nuestros datos, incluidas las imágenes. Para ello debes ir a la página de datos de tu cuenta, y requerir tus datos. Despueés de un rato (depende del número de fotos que tengas), tendrás disponibles los ficheros para descarga, como puede verse en la siguiente imagen:

Flickr Data

*Flickr Data*

Cada uno de esos archivos .zip contiene 500 imágenes. Después de bajarnos todos los zips y extraer el contenido, nos encontramos con el primer problema. El nombre de los ficheros no es exactamente el nombre original de la imagen. Flickr le añade un número (identificador único), más _o, supongo que esa ‘o’ correspende a original. Es decir, cuando tu subes una imagen a Flickr, éste le asgina al nombre del fichero id_o, además Flickr crea varias imágenes (distintos tamaños) derivadas de la original. Si el fichero original es: libro-microhistorias-informatica--nuevo0xword.jpg, Flickr lo almacena como libro-microhistorias-informatica--nuevo0xword_8768892888_o.jpg. Ese número 8768892888, sería el identificador único. El problema con el que nos encontramos aquí, es que los enlaces a las imágenes en Flickr, tienen esta pinta: https://farm4.staticflickr.com/3803/8768892888_8932423465.jpg. Como vemos, nuestro identificador is parte de la URL, por lo que tenemos que extraerlo y mapear el mismo con el nombre de la foto, para así poder reemplazar en enlace de Flickr por el nuevo en Digital Ocean.

Para ello escribi un script en Python. La función que mapea los identificadores a cada fichero, lee todos los ficheros, extrae el numero del nombre del fichero, y lo pone en un diccionario junto al nombre. Me encontré con algunas excepciones en el que número que búscamos no esta el tercera posición (después de dividir el nombre por el carácter ‘_’), y algunas veces se encontraba en la cuarta posición.

def loadFilenames(picspath):

    dict = {}
    
    onlyfiles = [f for f in listdir(picspath) if isfile(join(picspath, f))]
    for entry in onlyfiles:
        tokens = entry.split("_")
        if tokens[-3].isdigit():
            dict[tokens[-3]] = entry
        if len(tokens) > 3 and tokens[-4].isdigit():
            dict[tokens[-4]] = entry

    return dict

Lo siguiente que tenemos que hacer es encontrar los enlaces de las imágenes a Flickr en todas las entradas, y reemplazar dichos enlaces con los nuevos en Digital Ocean. Aquí nos encontramos con otro problema. Durante todos estos años, el formato de los enlaces en Flickr han cambiado ligeramente, por lo que tenemos que encontrar las distintas formas que hemos usado. Para ello usé las siguintes expresiones regulares:

matches = re.findall("http[s]?://farm?.\.static\.*flickr\.com/\d+/\d+_\w+\.[a-z]{3,4}", c)
matches = matches + re.findall("http[s]?://www\.flickr\.com/photos/cyberhades/\d+/*", c)
matches = matches + re.findall("http[s]?://c?.\.staticflickr\.com/\d+/\d+/\d+_\w+\.[a-z]{3,4}", c)

Una vez tenemos nuestras expresiones regulares, tenemos que ir por todas las entradas del blog, buscar si existe algún enlace que corresponde con alguna de las expresiones regulares y sustituirlo con el nuevo enlace de la imagen correspondiente. Pero antes de hacer esto, necesitamos subir nuestras imágenes a nuestro Spaces.

Spaces cuesta $5 al mes y nos ofrece 250gb de espacio. A la hora de crear el space asegúrate activar el CDN, una vez creado, todo lo que tienes que hacer es subir las imágenes. Lo puedes hacer usando el propio panel de control de Digital Ocean, o usando algún cliente. En nuestro caso, escribí una aplicación en Go. Para ello necesitas crear un token desde el panel de control de Digital Ocean, para poder acceder a tu space desde tu aplicación. Aquí puedes ver el código de dicho fichero. Un par de detalles a tener en cuenta, es que cuando subes un fichero, tienes que especificar el Content-Type:

func GetFileContentType(out *os.File) (string, error) {

  // Only the first 512 bytes are used to sniff the content type.
  buffer := make([]byte, 512)

  _, err := out.Read(buffer)
  if err != nil {
    return "", err
  }
  contentType := http.DetectContentType(buffer)

  return contentType, nil
}

Y asegúrate que el fichero es accesible de forma pública:

...
// Make the file public
userMetaData := map[string]string{"x-amz-acl": "public-read"}

// Upload the file with FPutObject
n, err := client.FPutObject(spaceName, objectName+strings.Replace(path, dirPath, "", 1), path, minio.PutObjectOptions{ContentType: contentType, CacheControl: cacheControl, UserMetadata: userMetaData})
if err != nil {
  log.Fatalln(err)
}
...

Antes de subir nuestras imáges, hay una cosa más que hacer. Las imágenes que nos bajamos desde Flickr, son las imágenes originales, la mayoría son bastante grandes de tamaño y poco aconsejables para el blog, así lo suguiente que hicimos fue bajar la resolución de las imágenes un máximo de 600 pixeles de ancho, guardando el aspecto y sólo modificando las imágenes cuya resolución es mayor que los 600 pixeles horizontales, es decir, las imágenes pequeñas no las queremos modificar. Para ello hicimos uso del extraordinario ffmpeg:

for i in *; do ffmpeg -i $i -vf "scale='min(600,iw)':-1'" ${i%.*}_opt.${i#*.}; done

Con scale='min(600,iw)' nos aseguramos que sólo las imágenes con una resolución mayor de 600 pixeles horizontales (width) son modificadas, y con :-1 guardamos el aspecto de la misma.

Este comando no sobreescribe el fichero original, sino que genera un fichero con el mismo nombre para añade _opt al final (antes de la extensión).

Una vez tenemos nuestras imágenes ya las podemos subir a nuetro space.

Imágenes Cyberhades

*Imágenes Cyberhades*

Como se puede ver, cada imagen tiene dos enlaces: Origin y Edge, este último es el que queremos usar, ya que es el que hace uso del CDN.

Ya lo último que nos queda por hacer es el reemplazar los enlaces de Flickr por los nuevos en Digital Ocean:

...
for match in matches:
    if '_' in match:
        k = match.split('/')[-1].split('_')[0]
    else:
        tokens = match.split('/')
        if tokens[-1].isdigit():
            k = tokens[-1]
        else:
            k = tokens[-2]

    if k in dict:
        c = c.replace(match, "https://cyberhades.ams3.cdn.digitaloceanspaces.com/imagenes/" + dict[k])

print(entry)
o = open(entry, "w", encoding = "ISO-8859-1")
o.write(c)
o.close()
...

El script de Python completo los puedes encontrar aquí.

Para acabar decir que Digital Ocean Spaces no es más barato que el haber pagado por una cuenta pro en Flickr, pero Spaces nos da mucho más juego.