singe/docs/es/lessons/21-subtitles.adoc

561 lines
23 KiB
Text

[#_lesson_21_subtitles]
== Lección 21: Subtítulos
image::learn/21-subtitles.png[La lección terminada, 480]
Alguien que no puede oír va a jugar a tu juego.
Si tu juego tiene voces y no tiene subtítulos, no podrá. No es que «le cueste
más» -- llegará a la primera escena en la que un personaje dice lo que le
explica al jugador qué tiene que hacer, y ahí se quedará. Eso es todo, y es la
razón de que esta lección exista en lugar de ser un párrafo de la lección
diecinueve.
La otra razón es práctica. Meterle subtítulos a un juego ya terminado es un
suplicio. Tienes que encontrar cada frase hablada, averiguar en qué fotograma
está y descubrir que la mitad de los tiempos están mal porque tu vídeo se volvió
a codificar a mitad del proyecto. Si lo haces mientras escribes el juego, es
cosa de una tarde. Singe te da un sistema que hace la parte difícil, y esta
lección explica cómo usarlo.
[#_it_is_not_ordinary_text_drawing]
=== No es dibujar texto sin más
Podrías hacer los subtítulos tú mismo. Guardas una lista de frases con sus
tiempos de inicio y de fin, miras el reloj en `onOverlayUpdate` y dibujas la
actual con `fontToSprite`. Hay quien lo ha hecho. Esta es la razón por la que
tú no deberías.
Los subtítulos se sincronizan con *el disco*, no con un reloj. Cuando `srtLoad`
lee tu archivo, convierte cada marca de tiempo en un número de fotograma del
disco, en ese mismo momento, usando la tasa de fotogramas del disco que esté
cargado. A partir de entonces el motor no mira ningún reloj; en cada fotograma
dibujado pregunta qué entrada cubre el fotograma en el que está el disco.
Esa única decisión te da lo que un sistema hecho a mano nunca consigue hacer
bien: *un subtítulo sobrevive a un salto*. Manda el disco a cualquier sitio --
`discSearch`, `discSkipForward`, un paso, una ramificación a otro segmento -- y
la frase correcta está en pantalla en el siguiente fotograma dibujado, hacia
delante o hacia atrás, sin recargar nada y sin poner a cero ningún reloj. En un
juego con ramificaciones como el de la lección dieciséis, donde las decisiones
del jugador hacen saltar el disco de un lado a otro sin parar, esa es la
diferencia entre funcionar y casi funcionar.
Además se dibuja en el sitio correcto. El documento de los subtítulos está sobre
la imagen, por encima de tu overlay y por debajo de cualquier GUI en espacio de
pantalla, y sigue a la imagen allí donde la pongan los ajustes del jugador --
una imagen encogida, una desplazada, una recreativa girada, un hueco en un
bezel. No acepta entrada del ratón, así que un disparo de pistola de luz lo
atraviesa sin más. Hacer todo eso bien por tu cuenta es una semana que no
tenías prevista.
[#_write_the_file]
=== Escribe el archivo
Los subtítulos van en un archivo SubRip, que termina en `.srt` y es texto plano
que puedes escribir en cualquier editor. Es uno de los formatos más sencillos
que existen. Crea un archivo llamado `subtitles.srt` junto a tu script:
----
1
00:00:01,000 --> 00:00:04,000
They told us the cave was empty.
2
00:00:04,200 --> 00:00:08,000
They were wrong about
a great many things.
3
00:00:08,500 --> 00:00:13,000
Keep your light on the floor.
----
Una entrada son cuatro cosas. Un número en una línea propia. Una línea con dos
marcas de tiempo y, entre ellas, una flecha hecha de dos guiones y un signo de
mayor que. Una o más líneas de texto. Una línea en blanco para terminar.
Una marca de tiempo es `HH:MM:SS,mmm` -- horas, minutos y segundos separados por
dos puntos, luego una coma y luego los milisegundos. La coma no es una errata, y
un punto no sirve. `00:00:04,200` son cuatro segundos y un quinto desde el
principio.
Los números de las entradas se leen y se tiran, así que no tienen por qué estar
en orden, ni siquiera ser correctos. Numéralas de todos modos; en algún momento
estarás buscando la entrada cuarenta y uno.
La entrada dos muestra cómo es una entrada de dos líneas. Las líneas se muestran
tal como las escribiste, así que eres *tú* quien decide dónde se parte una línea
larga, lo cual importa más de lo que parece y se explica más abajo.
El analizador es tolerante a propósito con las cosas que salen mal en un archivo
editado a mano. Los finales de línea de Windows se cargan sin cambios, y los
espacios del principio y del final se quitan de cada línea. Una entrada cuyas
marcas de tiempo no se pueden analizar se salta, y el resto del archivo se sigue
cargando, lo cual es muy amable justo hasta que te preguntas dónde ha ido a
parar la línea doce. Algunas cosas no están soportadas: las etiquetas `<i>` y
`<b>` que escriben algunas herramientas se muestran como texto en lugar de
obedecerse, y las coordenadas de posición que algunas herramientas añaden a la
línea de tiempo se ignoran. La sección de subtítulos de la referencia tiene la lista
completa.
[#_load_it_and_switch_it_on]
=== Cárgalo y actívalo
Tres llamadas, y el orden importa.
[source,lua]
----
if srtLoad(DIR .. "subtitles.srt") then
srtPosition(80)
srtEnable(true)
end
----
`srtLoad` lee el archivo y responde `true` o `false`. Responde `false`, sin
haber cargado nada, en tres casos: el archivo no se puede leer, no hay nada en
él que se pueda analizar o *no hay disco*. En los tres imprime en la consola un
aviso que nombra el archivo. Es un buen diseño y deberías aprovecharlo: un
archivo de subtítulos que falta no merece que el juego muera por él, pero
publicar un juego sin subtítulos sin enterarte sí merece que lo notes.
El caso de «no hay disco» es el que te va a pillar. Las marcas de tiempo tienen
que convertirse en números de fotograma a la tasa de fotogramas del disco, y
mientras no hay un disco abierto no hay tasa de fotogramas que usar. Así que
carga los subtítulos *después* del disco, lo que en un script normal significa
desde el cuerpo del script, donde se ejecuta todo lo demás que has escrito
fuera de las funciones. De ahí se sigue que un `.srt` preparado para un vídeo a
una tasa de fotogramas cae en el sitio equivocado en un vídeo a otra.
Sincroniza tu archivo con el vídeo que de verdad vas a publicar.
`srtEnable(true)` activa las entradas cargadas. Un script empieza con los
subtítulos desactivados, siempre, así que un juego que los carga tiene que
activarlos. Desactivarlos otra vez quita lo que se esté mostrando, y volver a
activarlos pone lo que cubra el fotograma en el que está el disco ahora
mismo -- sin buscar nada, sin estado que guardar.
Tener los subtítulos activados es un ajuste, no una decisión que tomas tú por
el jugador. Ponlo en tu página de opciones de la lección veinte, guárdalo con
`saveSet` de la lección doce y pásale a `srtEnable` lo último que haya elegido
el jugador.
[#_where_they_sit]
=== Dónde se colocan
[source,lua]
----
srtPosition(70)
----
`srtPosition` es un porcentaje hacia abajo de la imagen, de `1` a `95`, y por
defecto es `80`. Se aplica a la entrada que está en pantalla ahora mismo y a
todas las que vienen después. Un número fuera del rango se ignora en lugar de
recortarse al límite, lo que significa que una errata deja la posición
exactamente donde estaba y no dice nada, así que revisa tus números.
El ochenta por ciento queda bajo, y queda bajo a propósito: la parte de abajo de
la imagen suele ser la menos interesante del plano. Súbelos cuando la parte de
abajo *no* sea aburrida -- cuando tu juego dibuja un marcador a lo largo del
borde inferior, o cuando el punto de mira de la pistola de luz del jugador vive
ahí abajo.
[#_taking_one_down]
=== Quitar uno
[source,lua]
----
srtClear()
----
`srtClear` quita de golpe lo que haya en pantalla, y deja en paz las entradas
cargadas y el estado de activación. La línea que se estaba mostrando sigue
quitada hasta que el disco llega a la siguiente entrada. Úsalo cuando una escena
termina antes de tiempo y no quieres que la última frase del diálogo se quede
colgando sobre el plano siguiente.
Hay una llamada más que comparte la maquinaria de los subtítulos, y puede que
ya la hayas usado:
[source,lua]
----
overlayBanner("Extra life!", 20)
----
`overlayBanner` muestra un mensaje corto sobre la imagen durante unos treinta
fotogramas dibujados y luego lo quita. Lo dibuja el mismo documento que los
subtítulos, así que un rótulo parece un subtítulo, y mientras está en pantalla
ocupa el lugar de cualquier subtítulo, que vuelve cuando se le acaba el tiempo
al rótulo. Su propio argumento de altura es independiente de `srtPosition` y por
defecto vale `47`, el centro de la imagen. Los mensajes de más de sesenta
caracteres se ignoran por completo, así que uno largo parece no funcionar en
absoluto.
[#_subtitles_already_inside_the_video]
=== Subtítulos que ya están dentro del vídeo
Algunos archivos de vídeo llevan sus propias pistas de subtítulos. Si el tuyo
las lleva, no necesitas ningún `.srt`:
[source,lua]
----
if discGetSubtitleTracks() > 0 then
srtLoadTrack(0)
else
srtLoad(DIR .. "subtitles.srt")
end
srtEnable(true)
----
`srtLoadTrack` recibe un número de pista de `0` a `discGetSubtitleTracks() - 1`
y todo lo que viene después de cargar es idéntico.
`discGetSubtitleLanguage(track)` te dice con qué idioma está etiquetada una
pista, como un código de tres letras del tipo `eng` o `fra`, así que un juego
puede ofrecerle al jugador lo que su vídeo resulte llevar.
Dos advertencias. Leer una pista recorre una vez todo el archivo de vídeo, sin
decodificar ninguna imagen, así que un disco tan largo como una película tarda
un momento: hazlo mientras hay otra cosa en pantalla, no entre dos fotogramas de
juego. Y las pistas de subtítulos que guardan *imágenes* de las palabras en
lugar de las palabras -- las que usan los DVD y los Blu-ray -- no se pueden
leer. Se cuentan, para que la numeración coincida con la que muestran otros
reproductores, pero `srtLoadTrack` responde `false` para ellas.
[#_the_whole_script_3]
=== El script completo
Este necesita un vídeo. Ya tienes uno: `Singe/menuBackground.mkv`, que el motor
desempaquetó en tu carpeta de trabajo la primera vez que ejecutaste algo. Dile a
Singe que lo use como disco con `--framefile`, que además enciende el disco:
----
Singe --framefile=Singe/menuBackground.mkv subtitles
----
[source,lua]
----
dofile("Singe/Framework.singe")
overlaySetResolution(discGetWidth(), discGetHeight())
local HUD_X = 16
local STEP = 5
local TOP_LIMIT = 10
local LOW_LIMIT = 95
local hudFont = fontLoad("Singe/FreeSansBold.ttf", 18)
local loaded = false
local showing = false
local height = 80
if discGetSubtitleTracks() > 0 then
loaded = srtLoadTrack(0)
end
if not loaded then
loaded = srtLoad(DIR .. "subtitles.srt")
end
if loaded then
srtPosition(height)
showing = true
srtEnable(true)
end
discPlay()
function onInputPressed(what)
if not loaded then
return
end
if what == SWITCH_BUTTON1 then
showing = not showing
srtEnable(showing)
if showing then
overlayBanner("Subtitles on", 20)
else
overlayBanner("Subtitles off", 20)
end
elseif what == SWITCH_UP then
height = math.max(TOP_LIMIT, height - STEP)
srtPosition(height)
elseif what == SWITCH_DOWN then
height = math.min(LOW_LIMIT, height + STEP)
srtPosition(height)
elseif what == SWITCH_LEFT then
srtClear()
discSearch(0)
discPlay()
end
end
function onOverlayUpdate()
overlayClear()
fontSelect(hudFont)
colorForeground(255, 255, 255)
if loaded then
if showing then
fontPrint(HUD_X, 16, "Frame " .. discGetFrame() .. " subtitles ON at " .. height .. "%")
else
fontPrint(HUD_X, 16, "Frame " .. discGetFrame() .. " subtitles OFF")
end
fontPrint(HUD_X, 40, "Fire toggles them, up and down move them, left rewinds")
else
fontPrint(HUD_X, 16, "No subtitles loaded.")
fontPrint(HUD_X, 40, "Run with --framefile and check subtitles.srt is beside the script.")
end
return OVERLAY_UPDATED
end
function onShutdown()
fontUnload(hudFont)
end
----
Pulsa el disparo para activarlos y desactivarlos, arriba y abajo para mover las
líneas, e izquierda para rebobinar hasta el principio. El rebobinado es el que
hay que mirar: el disco salta atrás al fotograma cero y la entrada correcta está
en pantalla justo en el fotograma siguiente, sin que tú hagas nada al respecto.
[#_what_just_happened_17]
=== Qué acaba de pasar
[source,lua]
----
if discGetSubtitleTracks() > 0 then
loaded = srtLoadTrack(0)
end
if not loaded then
loaded = srtLoad(DIR .. "subtitles.srt")
end
----
Usa los subtítulos del propio vídeo cuando los tiene, recurre al archivo que hay
junto al script cuando no, y termina sabiendo cuál ha funcionado -- si es que ha
funcionado alguno. Las dos llamadas responden con un booleano, así que el plan
de reserva son dos sentencias `if` y ningún caso especial.
`menuBackground.mkv` no lleva ninguna pista de subtítulos, así que lo que vas a
obtener de verdad es el archivo que escribiste.
[source,lua]
----
function onInputPressed(what)
if not loaded then
return
end
----
`return` sin nada detrás sale de la función inmediatamente. Sin subtítulos
cargados, ninguna de estas teclas tiene nada que hacer, y salir pronto es más
claro que envolver todo el cuerpo en un `if`. Llamar a `srtEnable` sin nada
cargado no hace daño, pero escribir código que solo tiene sentido cuando no
hace nada es una costumbre que más vale no coger.
[source,lua]
----
elseif what == SWITCH_LEFT then
srtClear()
discSearch(0)
discPlay()
----
El `srtClear` no es necesario. El motor pondrá la entrada correcta para el
fotograma cero en el siguiente fotograma dibujado de todos modos. Está ahí para
que puedas borrarlo y mirar qué pasa: la línea vieja se queda en pantalla una
fracción de segundo mientras el disco llega a donde va. En un salto lento ese
destello de la línea equivocada se ve, y limpiar primero es como lo evitas.
[#_making_text_readable_over_a_moving_picture]
=== Texto legible sobre una imagen en movimiento
El sistema pone las palabras en pantalla. Que alguien pueda leerlas es problema
tuyo, y vale para cada línea de texto que dibujes sobre vídeo -- una indicación
de la lección dieciocho, una puntuación, una elección de rama -- no solo para
los subtítulos.
*No te acerques a los bordes.* Los televisores llevan setenta años recortando
los bordes de la imagen, y los monitores de las recreativas son peores, no
mejores. Trabaja suponiendo que el cinco por ciento exterior de la imagen, en
cada lado, puede no estar en absoluto en la pantalla de alguien. Mantén dentro
de ese margen todo lo que el jugador tenga que leer. Por eso `srtPosition` se
detiene en `95` y no en `100`.
*El contraste no es una elección de color, es una garantía.* Un texto blanco
sobre una imagen es un texto blanco sobre lo que la imagen esté haciendo en ese
momento, y dentro de dos segundos eso puede ser un campo nevado. El estilo de
subtítulos incluido lo resuelve como lo hacen los subtítulos de cine: un panel
oscuro que se ciñe al texto, más una sombra de un píxel. Si dibujas tu propio
texto sobre vídeo, haz una cosa o la otra -- un `overlayBox` detrás o una copia
oscura del texto desplazada un píxel. No te fíes de la imagen.
*Parte tú las líneas.* Dos líneas cortas se leen más rápido que una larga, y una
línea que se parte donde tú no has elegido suele partirse por el sitio
equivocado. Apunta a unos cuarenta caracteres por línea y no más de dos líneas a
la vez. Parte en una pausa natural:
`They were wrong about / a great many things` se lee bien;
`They were wrong / about a great many things` tropieza.
*Déjalas el tiempo suficiente.* Una velocidad de lectura cómoda anda por los
quince caracteres por segundo, así que una línea de cuarenta caracteres pide
unos tres segundos. Mira la entrada uno del archivo de arriba: treinta y dos
caracteres, tres segundos. Si una frase del diálogo es corta y rápida, dejar la
entrada un momento más allá del final de lo que se dice es más amable que
quitarla de golpe.
*Ponlos donde no está la acción.* Si lo importante del plano está en la parte
de abajo del encuadre, sube los subtítulos para esa escena con `srtPosition`.
Puedes llamarla tantas veces como quieras, y el jugador nunca notará que lo has
hecho.
[#_styling_them]
=== Darles estilo
Las líneas las dibuja un documento, del mismo tipo que conociste en la lección
veinte, y dos archivos de tu carpeta `Singe` deciden qué aspecto tienen:
* `Singe/subtitle.rml` -- el documento. Tiene un solo elemento, `#slot`, en el
que el motor escribe la entrada o el rótulo actual.
* `Singe/subtitle.rcss` -- el estilo. `.cue` es el bloque que lleva la altura
que pidió `srtPosition`, y `.cue .text` es la barra de texto que hay dentro:
blanca, en negrita, sobre un panel oscuro redondeado que se ciñe al texto, con
una sombra.
Abre `subtitle.rcss` y léelo -- son unas cuarenta líneas. Luego déjalo en paz,
porque lo que edites ahí no dura. Cada vez que Singe arranca, vuelve a poner su
propia copia de todo lo que haya cambiado en la carpeta `Singe`, y lo dice:
----
>>> Updated File: Singe/subtitle.rcss
----
Esa carpeta es del motor. Tu juego trae su propia copia en su lugar. Copia los
dos archivos de `Singe` a tu carpeta `subtitles`, junto al script. Luego abre tu
copia de `subtitle.rml` y cambia la única línea de su `<head>` que enlaza el
estilo, para que quede así:
[source,html]
----
<link type="text/rcss" href="subtitle.rcss"/>
----
Un nombre dentro de un documento se cuenta desde la propia carpeta del
documento, salvo un nombre que empieza por `Singe/`, que siempre quiere decir la
carpeta del motor. El enlace tal como lo copiaste, `Singe/subtitle.rcss`, sigue
trayendo el estilo del motor. El nuevo trae el tuyo.
Cuando Singe encuentra un `subtitle.rml` junto a tu script, dibuja los
subtítulos con ese en lugar de con el suyo. Añade `-p` al comando y Singe
describe lo que va haciendo, línea a línea:
----
Singe -p --framefile=Singe/menuBackground.mkv subtitles
----
Cuando aparece la primera entrada, una de esas líneas es esta:
----
Using the game's own subtitles/subtitle.rml
----
Ahora cambia el `font-size`, el `color`, el `background-color`, el
`border-radius` o el `font-effect` en tu `subtitle.rcss`, guarda y vuelve a
ejecutarlo. El motor solo toca el contenido de `#slot` y la altura de `.cue`,
así que todo lo demás de tus dos archivos es tuyo, y nada de lo que haga Singe
al arrancar los tocará. Si `-p` dice que está usando los tuyos y el aspecto no
ha cambiado, vuelve a leer el enlace: una copia que todavía dice `Singe/` está
usando sin avisar el estilo del motor.
Como `overlayBanner` usa el mismo documento, cambiar el estilo de los subtítulos
cambia también el de tus rótulos, lo cual suele ser lo que quieres, y conviene
saberlo antes de que te preguntes por qué.
Un límite: el documento de los subtítulos es un documento, y los documentos
necesitan una GPU. En una máquina sin ella, `srtLoad` sigue leyendo tu archivo y
sigue respondiendo con la verdad, y no se dibuja nada. Un aviso lo dice una vez.
[#_try_it_21]
=== Pruébalo
. *Añade una cuarta entrada* a `subtitles.srt`, entre los segundos once y trece.
Guarda y ejecuta. Luego ponla fuera de orden, como entrada `99`, la primera
del archivo, y comprueba que sigue funcionando.
. *Dale a una entrada una marca de tiempo mala* -- escribe `00:00:05.500` con un
punto en lugar de una coma -- y ejecuta. Mira qué línea desaparece, y fíjate en
que no se dice absolutamente nada al respecto. Un archivo que se carga no es
un archivo que esté bien.
. *Cámbiales el estilo.* En tu propio `subtitle.rcss` -- la copia que está junto
a tu script, no la de `Singe` -- cambia `.cue .text` a texto amarillo sobre un
fondo totalmente transparente, y luego añade una sombra más gruesa. Después reprodúcelo sobre el vídeo y decide con sinceridad si todavía
se lee.
. *Rompe la sincronización a propósito.* Cambia todas las marcas de tiempo del
archivo para que lleguen dos segundos tarde, y mira cómo un sistema que
funciona correctamente entrega unos subtítulos inútiles. Esto es lo que te
hace un vídeo recodificado.
. *Lleva la opción a un menú.* Coge la página de pausa de la lección veinte,
añádele una casilla y conéctala a `srtEnable`. Luego guarda la elección con
`saveSet` para que siga activada la próxima vez. Esta es la versión que de
verdad quieren tus jugadores.
[#_break_it_on_purpose_20]
=== Rómpelo a propósito
Ejecuta el script sin la opción `--framefile`:
----
Singe subtitles
----
Se abre la ventana, negra, con tu mensaje de reserva, y la consola dice:
----
Warning: No subtitles were loaded from subtitles/subtitles.srt.
----
Ni un cierre inesperado ni una ventana de error, porque `srtLoad` ha respondido
`false` y tu script lo ha gestionado.
Ese es el fallo más valioso de esta lección, porque es el que ocurre sin que
nadie se dé cuenta. No hay disco, así que no hay tasa de fotogramas, así que no
hay nada en lo que convertir las marcas de tiempo, así que no se carga nada. Si
hubieras escrito `srtLoad(...)` sin más y hubieras tirado la respuesta, tu juego
habría funcionado perfectamente sin mostrar subtítulos, y te habrías enterado
cuando alguien te lo dijera.
Usa la respuesta. Siempre.
[#_what_you_learned_21]
=== Lo que has aprendido
* Un juego con voces y sin subtítulos es un juego al que algunas personas no
pueden jugar en absoluto.
* Meter los subtítulos sobre la marcha es cosa de una tarde; añadirlos al final
no.
* Los subtítulos son un archivo `.srt`: un número, dos marcas de tiempo con
`-->` entre ellas, el texto y una línea en blanco.
* `srtLoad` convierte las marcas de tiempo en números de fotograma del disco al
cargar, y por eso una entrada sobrevive a cualquier salto.
* Hay que cargarlo después del disco, y responde `false` cuando no puede cargar,
también cuando no hay disco. Comprueba la respuesta.
* `srtEnable` los activa, y un script siempre empieza con ellos desactivados.
* `srtPosition` los coloca como un porcentaje hacia abajo de la imagen, de `1` a
`95`.
* `srtClear` quita lo que se esté mostrando, y `overlayBanner` comparte el mismo
documento.
* `srtLoadTrack` lee los subtítulos del propio archivo de vídeo cuando los
lleva.
* Singe reescribe su carpeta `Singe` en cada arranque, así que para cambiar el
estilo de los subtítulos copias `subtitle.rml` y `subtitle.rcss` junto a tu
script, haces que el enlace de la copia apunte a tu propia hoja y cambias
esa.
* El texto sobre una imagen en movimiento necesita un margen de seguridad, su
propio contraste, líneas que hayas partido tú y tiempo para leerse.
[#_next_time_21]
=== La próxima vez
Hasta aquí el texto. La cuarta parte sigue sin que tengas que leerla en orden,
y la próxima lección abre una puerta que ha estado cerrada durante veinte
lecciones: `overlayGetWidth` y `overlayGetHeight` son dos números, y una escena
tiene tres. La lección veintidós es el tercero.