subtitulador en processing

publicado el 2018-10-07

parte del proyecto: tortilla-stories


la idea de la función de tortilla stories es que las tortillan traen historias sonoras que se pueden escuchar al ponerlas en las tornamesas.

todas las personas que hablan en las grabaciones lo hacen en español, pero como el evento se presentaría en brooklyn era un buen gesto subtitularlo al inglés.

al final mel y yo lo logramos. en este post describo el programa que hice y cómo lo usamos.

foto de mel en el escenario, colocando una tortilla en una tornamesa. a la izquierda se ve una pantalla con un texto que dice 'and that the teamwork... the union, the acceptance...'

mel en escena mezclando tortillas, nótese el subtítulo en acción

formato de archivo: .srt

mel se encargó de hacer las traducciones de los audios.

para colocar los tiempos, decidimos que sería buena idea usar el formato subrip (.srt) que además sigue siendo compatible con el que se usa al parecer ahora en web, webvtt.

aquí hay un ejemplo:

1
00:01:59,500 --> 00:02:01,400
If tortillas talked about me...

2
00:02:01,500 --> 00:02:04,300
... They would say that I am grateful

por cada texto del subtítulo:

  • la primera línea lleva un identificador
  • la segunda línea tiene el tiempo inicial y el tiempo final en el que debe aparecer dicho subtítulo
  • la tercera línea (y más, opcionalmente) tienen los textos a mostrar en ese tiempo
  • la última línea ha de estar en blanco

subtitulador

todo los pedazos de código que están a continuación son componentes de un mismo sketch de processing.

el programa en su totalidad tiene un caso de uso específico para la función de tortilla stories, pero los bloques como las clases Subtitulo y Subtitulos creo que pueden usarse para otras cosas también.

¿luego podremos hacer una library?

protocolo osc

el programa completo funciona usando un pequeño protocolo osc.

letsparty, el programa de processing principal que hice, controla a las tornamesas y también envía al programa de puredata mensajes para iniciar o terminar cada audio - estos mensajes indican qué audio y en qué tornamesa.

lo adapté para que también le enviara al subtitulador cada vez que iniciaba algún audio, sin importar en qué tornamesa (asumiendo que solo se reproduciría un audio con voz/texto a la vez).

el mensaje tiene esta forma de dirección: /tortillas/subtitulo e incluye un número entero que corresponde al número de audio.

en este caso, el subtitulador escucha en el puerto 7046.

archivos con datos

el sketch, dentro del directorio de data, tiene un archivo lista.txt y una serie de archivos con terminación .srt.

el archivo lista.txt tiene líneas que indican el número de tortilla (“disco”) al que corresponden, y después de una coma el nombre del archivo .srt correspondiente. por ejemplo:

12,ts_timestamp_trackHistorias.srt

indica que a la tortilla / audio número 12 le corresponde el archivo de subtítulos ts_timestamp_trackHistorias.srt

cada archivo .srt tiene los textos y tiempos correspondientes con respecto al audio de cada disco, en el formato descrito arriba

código fuente

descarga el archivo .pde del código fuente

pensaba copiarlo abajo pero algunas partes no caben. mejor aquí describo a grandes razgos cómo funciona.

clase ArchivoSubtitulo

esta clase simplemente es la estructura de datos que se obtiene al leer una línea de lista.txt, es decir un número y un nombre de archivo.

clase Subtitulo

esta clase funciona como estructura de datos para lo que llamo un “subtítulo”: un identificador numérico, un texto de una o más líneas, un tiempo de inicio en milisegundos, y un tiempo final en milisegundos.

clase Subtitulos

esta es la clase que hace el trabajo principal.

es una estructura de datos que tiene un arreglo de todos los subtítulos que están en un archivo (es decir, todos los subtítulos que le corresponden a un audio), y además se encarga de cargarlos de un archivo y hacer el parsing (¿decodificación?) del formato, entre otras cosas.

el método cargaSubtitulos() es el que carga el archivo de texto y hace el arreglo de tipo Subtitulo, decodificando cada línea de acuerdo al formato subrip.

para ayudarse, llama a parseTimestamp() -quien se encarga de decodificar la línea con los tiempos de inicio y fin- y que a su vez llama dos veces a parseTime(), una por cada tiempo.

por otro lado, esta clase tiene el método textoParaTiempo() que devuelve el texto que corresponde a un tiempo correspondiente, y el método textoActual() que es un caso particular en el que aprovecha la función de processing millis()para conocer el tiempo actual.

el tiempo actual en ese método se compara con un tiempoinicial que es reseteado cada vez que se llama iniciaTiempo().

sketch principal

el sketch principal tiene varias funciones:

  • cargar y traducir el archivo lista.txt para crear los objetos de tipo ArchivoSubtitulo y saber qué identificadores de tortilla sí tienen un archivo de subtítulos (en setup() y con cargaLista()).
  • manejar el protocolo de mensajes osc que le indica cada vez que se inicia la reproducción de una tortilla (oscEvent()).
  • responder adecuadamente al protocolo: cada vez que se inició una tortilla con subtítulo, entonces hay que cargar dicho archivo de subtítulos e iniciar su “tiempo” (cargaEinicia()).
  • dibujar el subtítulo en la pantalla, con un tamaño, proporción y posición adecuados (método draw() junto con textWidthWithNewLine()).

cierre

este programa lo hice separado del otro tanto por manejo de complejidad como para poder ejecutarlo fácilmente en otra pantalla.

debido a su protocolo osc, podría de hecho funcionar en otra computadora conectada a la misma red que la computadora principal, después de configurar la red adecuadamente. esto implica que en un escenario mayor con proyector o algo así sería relativamente fácil de mantenerlo funcionando.

curiosamente, presenta los subtítulos de una manera que nos pareció más adecuada y precisa que vlc (o al menos en un caso específico en el que necesitábamos “quemar” los subtítulos a imagen/video, en vlc notamos errores y con este programa no (lo malo es que este programa llegó después de cuando se necesitó para eso)).

veo con motivación el seguirlo adecuando y generalizando para otras aplicaciones - de nuevo, de inicio como library de processing, ¿tal vez?.

y por más que lo probamos muchas veces, verlo en acción sin errores durante las funciones fue genial :)