Generando imágenes dinámicas para la agenda de los Juegos Olímpicos de París 2024 con node

Miniaturas de los deportes de los JJOO.
Miniaturas de los deportes de los JJOO.

Con la inminente llegada de los Juegos Olímpicos de París, me propuse la tarea de crear una agenda completa con todos los deportes que se celebrarán, para poder agregarlos a eventgarden.io y que cualquiera pudiera consultar el calendario completo de un vistazo.

Como indico en el título, voy a centrarme en la generación de las imágenes de cada entrada. Y aunque me centre en el tema concreto de los juegos olímpicos, esta técnica se puede extrapolar a cualquier tipo de aplicación que necesite generar una cantidad masiva, y repetitiva, de información.

Generar contenido en el navegador es muy sencillo. Con unas pocas líneas de HTML y CSS, se pueden crear diseños con una infinidad de variaciones. Sin embargo, en el oscuro y complejo backend, la creatividad se restringe naturalmente a lo que se puede hacer con caracteres… ¿O no?

Mi objetivo es poder generar una miniatura para cada deporte, permitiendo identificarlos de un vistazo.

Identificando los deportes

Los deportes, junto a la información de cada evento, vienen codificados de este modo:

"disciplineId": "TRI-------------------------------",
"disciplineId": "BDM-------------------------------",
"disciplineId": "VBV-------------------------------",
"disciplineId": "HBL-------------------------------",

Naturalmente, no tenía ni idea de qué significaban o cuál representaba cada uno, así que una rápida búsqueda me aclaró las ideas. En los JJOO se sigue una nomenclatura estándar llamada “Código de Nomenclatura Deportiva”, que permite identificar no solo el deporte, sino también la modalidad o el evento específico, entre otros.

En el ejemplo anterior, basándonnos en la documentación, esos códigos representarían triatlón, bádminton, voleybol playa y balonmano respectivamente.

Con la lista completa, el siguiente paso era generar un diccionario para asociar cada deporte a un emoji. Y eso es algo que la IA hace genial.

I need generate a list of emojis for every sport of JJOO 2024 paris. I will give you the list of all sports. You will respond me in a typescript list of objects with this format:

<sport_code>: <emoji_code> // <Sport_name>

const SPORT_EMOJIS: { [key: string]: string } = {
  AQU: '🏊‍♂️', // Aquatics 🏊‍♂️
  ARC: '🏹', // Archery 🏹
  ATH: '🏃‍♂️', // Athletics 🏃‍♂️
  BDM: '🏸', // Badminton 🏸
  BK3: '⛹️‍♂️', // 3x3 Basketball ⛹️‍♂️
  ...

Ya os podeis hacer una idea del rollo que hubiera sido tipear a mano las 44 disciplinas que existen…

Creando las imágenes

En el backend hay varias formas de generar imágenes. Desde mi punto de vista, los dos métodos más sencillos son:

  • Renderizar una web y tomar una captura.
  • Renderizar en un canvas y extraer el contenido.

Por sencillez y congruencia con el resto del proyecto elijo la primera opción, que consiste en renderizar un html en un navegador headless (puppeteer se puede encargar de ello por ejemplo). Un paquete que nos ayuda a abstraernos de todo eso es html-to-image.

Perfecto, pero necesitamos el html. ¿Cómo lo creamos dinámicamente? Utilizando Handlebars. Nuevamente, hay infinidad de alternativas, pero esta es una de las favoritas.

Handlebars nos permite utilizar <div>{{emoji}}</div> de tal modo que {{emoji}} se convertirá en el valor que le pasemos al generador. Por tanto lo que toca ahora es hacer un diseño. No me complico demasiado:

<!-- template.hbs -->
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      html,
      body {
        align-items: center;
        background-color: white;
        display: flex;
        flex-direction: column;
        gap: 8px;
        height: 256px;
        justify-content: center;
        overflow: hidden;
        position: relative;
        width: 256px;
      }
      h1 {
        font-size: 7em;
        margin: 0;
      }
      .olimpic {
        bottom: 0;
        display: flex;
        left: 0;
        position: absolute;
        right: 0;
      }
      . .olimpic * {
        height: 30px;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <h1 class="emoji">{{emoji}}</h1>
    <div class="olimpic">
      <div style="background: #0081C8"></div>
      <div style="background: #FCB131"></div>
      <div style="background: #000"></div>
      <div style="background: #00A651"></div>
      <div style="background: #EE334E"></div>
    </div>
  </body>
</html>

Con el diseño creado ya podremos usarlo como template en node.

const sportEmoji = SPORT_EMOJIS[sportCode];
const templateFile = await readFile(`${__dirname}/template.hbs`, "utf8");
const template = Handlebars.compile(templateFile);
const html = template({ emoji: sportEmoji });

En este punto ya tenemos el html generado. Ahora renderizaremos la imagen con ayuda de la biblioteca mencionada antes:

const image = await(
  nodeHtmlToImage({
    html,
    type: "jpeg",
    waitUntil: "networkidle2", // Espera a que la página cargue por completo.
  }),
);

const base64 = "data:image/jpeg;base64," + image.toString("base64");

Con esto ya tendríamos la imagen generada a partir de nuestra plantilla en formato base64. Aunque este no sea el mejor formato para almacenar imágenes por algunas de sus desventajas, en este caso, al ser miniaturas, es más que válido.

Algunos eventos deportivos de la ageda de los juegos olímpicos


Para terminar, si te interesa el tema, te dejo por aquí el enlace para que puedas echarle un ojo a la Agenda completa de los juegos olimpicos 2024.