Introducción
Un cortometraje ha perdido su título inicial de dos segundos, pero sus subtítulos siguen usando la línea temporal original. La primera escena verde empieza ahora en el segundo 2, mientras que su subtítulo empieza en el segundo 4. Los rótulos visibles de las escenas indican los tiempos; no necesitas adivinarlos escuchando.
Representarás los tiempos de los subtítulos con milisegundos enteros e implementarás una transformación en Rust que desplace todos los bloques, conservando el texto Unicode completo y sus múltiples líneas. Después cargarás el archivo realmente exportado como pista de subtítulos de un vídeo en el navegador. El siguiente laboratorio se centra en el límite de escritura protegida y en recuperarse de errores habituales. Las películas y los subtítulos son material sintético original del curso, no grabaciones de hechos reales.
Debes conocer las estructuras, enumeraciones, vectores, Result, cadenas, operaciones con archivos y pruebas y bucles básicos de Rust. Completar el curso existente de CLI de Rust resulta útil, pero es opcional; este laboratorio proporciona el análisis de argumentos y no exige conocimientos de clap. El autor estima 45 minutos, incluida la lectura y las correcciones habituales; no es un tiempo de finalización medido con principiantes.
Observar la sincronización y modelar milisegundos
En este paso verás una muestra de subtítulos que funciona, examinarás su relación con la línea temporal original e implementarás la conversión numérica del tiempo.
Abre la pestaña Preview. El reproductor preparado selecciona inicialmente data/edited.webm y data/reference.vtt. La referencia es un ejemplo que ya funciona, no el resultado que debes entregar. Haz clic en Load actual files y después en Play en el vídeo. Deberías ver el rótulo de una escena verde y el subtítulo de dos líneas que empieza por Café 林地 🌿. Pausa el vídeo. En Time (seconds), introduce 2.5 y pulsa Go to time para examinar directamente esa escena. Este control posiciona el vídeo real; los subtítulos los muestra su pista de subtítulos real.
La película editada ha eliminado un título de 2000 ms. El primer bloque original abarca 4000–7000 ms; el bloque sincronizado debe abarcar 2000–5000 ms. Aplicar -2000 a ambos extremos lo adelanta sin cambiar su duración. Los desplazamientos positivos retrasan los subtítulos. La escena azul del segundo 4.5 mostrará después un solapamiento intencionado, y la escena ámbar del segundo 60.5 comprobará un cambio de minuto. Estos puntos de observación son públicos; no hay ningún acertijo que resolver escuchando.
Cambia a Terminal. El proyecto está en /home/labex/project/subtitle-sync. El entorno preparado incluye Rust 1.99.1. Añade el directorio de Cargo a la ruta de búsqueda de comandos de este shell y selecciona una configuración regional UTF-8 disponible para esta sesión:
export PATH=/home/labex/.cargo/bin:$PATH
export LANG=C.UTF-8 LC_ALL=C.UTF-8
$PATH conserva los directorios de búsqueda existentes. Estas exportaciones duran durante la sesión de terminal y no modifican los archivos de inicio del shell; repítelas en cualquier terminal nuevo. La representación de glifos del terminal aún puede mostrar símbolos de sustitución para algunos caracteres Unicode. Aquí las comprobaciones fiables del texto son los bytes guardados y los subtítulos reales del navegador.
Entra en el proyecto. Todos los comandos posteriores usan este directorio; repite el comando si pierdes tu ubicación:
cd /home/labex/project/subtitle-sync
El proyecto preparado separa responsabilidades. src/cues.rs define Cue, valida nuestro formato WebVTT controlado, extrae campos temporales numéricos y da formato a la salida. src/main.rs interpreta comandos y muestra el éxito o los errores recuperables. src/export.rs lee la entrada realmente solicitada y crea una salida nueva tras completar la transformación. El siguiente laboratorio enseña ese límite de archivos. Editarás únicamente src/time.rs, conservando los nombres y las firmas de las funciones públicas. Mantén sin cambios los demás archivos fuente, Cargo.toml, rust-toolchain.toml y todos los originales bajo data/. Se permiten implementaciones internas equivalentes. Los archivos preparados que no se modifican deben conservar sus bytes, permisos, propietario/grupo y fecha de modificación; las lecturas normales pueden actualizar la fecha de acceso, que no forma parte de este requisito.
Un bloque tiene un inicio, un final y una o varias líneas de texto. Su campo de diagnóstico line registra la posición de la línea de tiempos en el archivo original; no forma parte del texto del subtítulo. La estructura proporcionada es:
pub struct Cue {
pub start: i64,
pub end: i64,
pub text: String,
pub line: usize,
}
i64 es un entero de 64 bits con signo. Guardamos milisegundos enteros, por lo que cruzar un minuto requiere aritmética entera en vez de editar la puntuación de una marca de tiempo. El inicio y el final son no negativos, el final es posterior al inicio y el analizador valida esas relaciones. String posee el texto completo; no todos sus caracteres Unicode ocupan un solo byte.
Examina los subtítulos originales reales con cat, que lee un archivo sin modificarlo:
cat data/original.vtt
WEBVTT
00:00:04.000 --> 00:00:07.000
Café 林地 🌿
Second line intact
00:00:06.000 --> 00:00:09.000
Overlap stays
00:01:01.750 --> 00:01:04.250
Across a minute
Este curso admite deliberadamente un subconjunto pequeño de WebVTT de texto plano:
- La primera línea es exactamente
WEBVTT, seguida de una línea vacía. Este curso no admite metadatos de cabecera ni una marca de orden de bytes. - Cada línea de tiempos tiene el formato
HH:MM:SS.mmm --> HH:MM:SS.mmm, con exactamente un espacio a cada lado de la flecha. Todos los dígitos son ASCII. Las horas tienen al menos dos dígitos decimales, los minutos y los segundos exactamente dos dentro de 00–59, y los milisegundos exactamente tres. Cada marca de tiempo completa debe caber en un valor i64 no negativo de milisegundos. - Los inicios de los bloques son no decrecientes en el orden del archivo; se permiten inicios iguales e intervalos solapados. El final debe superar al inicio. El programa conserva el orden del archivo.
- Cada bloque tiene al menos una línea lógica de contenido no vacía. Se conservan Unicode, tabulaciones, espacios iniciales/finales y varias líneas de texto. Incluso se permite contenido formado solo por espacios o tabulaciones, aunque pueda parecer en blanco. Una línea verdaderamente vacía termina un bloque; no puede representar una línea vacía interna del contenido. El texto posterior a ese límite debe iniciar otro bloque válido, no ser una continuación huérfana.
- La entrada admite finales de línea LF y CRLF, un salto de línea final opcional y varios separadores vacíos entre bloques. El formato de salida preparado usa LF, dos dígitos de hora para 0–99 horas (solo los dígitos adicionales necesarios para horas mayores), una línea vacía después de la cabecera y otra después de cada bloque, incluido el último. Normaliza la escritura de las marcas temporales y el número de separadores; el texto lógico del contenido no cambia. No se admiten retornos de carro aislados.
- No se admiten identificadores ni ajustes de bloque, NOTE, STYLE, REGION, marcado, referencias de caracteres ni los caracteres de contenido
<,>o&. Tampoco se admiten otros caracteres de control salvo las tabulaciones, incluido NUL. El contenido no compatible se rechaza en vez de eliminarse silenciosamente.
Estas restricciones describen el formato del curso, no todo el estándar WebVTT. El analizador preparado aplica las reglas de estructura y texto. No realiza la conversión a milisegundos que te falta implementar. Su función auxiliar time_fields comprueba la escritura y los rangos de los campos temporales y devuelve [hours, minutes, seconds, milliseconds] como cuatro valores i64 no negativos. Tu función milliseconds recibe esos campos ya validados, no números negativos arbitrarios ni minutos fuera de rango. Aun así, debe rechazar el desbordamiento al combinarlos.
La primera implementación convierte cada campo con la escala de su unidad. El array scales contiene los milisegundos por hora, minuto, segundo y milisegundo. El rango 0..4 incluye de 0 a 3; las posiciones correspondientes de los arrays emparejan cada campo con su escala. Los guiones bajos facilitan la lectura de literales numéricos grandes. 0_i64 selecciona explícitamente el tipo del acumulador.
checked_mul y checked_add devuelven Some(value) cuando el resultado cabe, o None si se desborda el entero. ok_or convierte ese Option en Result con un mensaje de error guardado en un String que posee el texto. ? continúa con el valor correcto o devuelve inmediatamente el error. El desbordamiento es una entrada rechazada de forma normal, no un panic ni un valor temporal que da la vuelta por desbordamiento.
Antes de programar, predice el total de 00:01:01.750: ¿cuánto aporta cada campo no nulo? Anota el entero esperado y después usa la ejecución siguiente para comprobar tu predicción.
Lee primero el código inicial real:
cat src/time.rs
use crate::Cue importa el tipo preparado de esta biblioteca. Consérvalo. Hay tres funciones; edita ahora únicamente milliseconds. Su { de apertura está en la línea de la firma y su } de cierre correspondiente está justo antes de shift_one. Las dos funciones de desplazamiento deben conservar sus cuerpos incompletos en esta fase.
Antes de editar, crea un punto de recuperación de solo el código fuente permitido. mktemp -d crea un directorio único sustituyendo los seis caracteres X; rechaza un nombre existente. $() captura esa ruta en una variable para esta sesión del shell. Las comillas mantienen unida la ruta y && copia solo si se crea correctamente el directorio. cp -p copia el archivo conservando sus permisos y fecha de modificación. Es un punto de recuperación del código fuente, no una copia de ningún subtítulo de salida. Muestra la variable y conserva el nombre real; no adivines el sufijo.
rs01_before_milliseconds=$(mktemp -d .edit-before-milliseconds.XXXXXX) && cp -p src/time.rs "$rs01_before_milliseconds/time.rs"
printf '%s\n' "$rs01_before_milliseconds"
printf muestra este nombre de directorio seguido de un salto de línea. Abre el código existente con nano; -c muestra la posición del cursor:
nano -c src/time.rs
Pulsa Ctrl+W, escribe pub fn milliseconds y pulsa Enter para buscar. Las flechas permiten moverte por el archivo. En esta firma, elimina el guion bajo inicial de _parts para que el cuerpo pueda usar parts; conserva [i64; 4] y el tipo de retorno. Sustituye únicamente la línea Err(...) de esta función por tu lógica de conversión, dentro de sus llaves existentes. No elimines la función siguiente.
Construye las cuatro escalas en el mismo orden que los campos validados. Empieza un total i64 mutable en cero. Para cada índice de 0..4, multiplica ese campo por su escala con comprobación de desbordamiento y después suma la parte al total con la misma comprobación. Convierte cualquiera de los dos desbordamientos en un error con ok_or(String::from("time integer overflow"))?. Por ejemplo, la primera parte del bucle calcula una contribución:
let piece = parts[i]
.checked_mul(scales[i])
.ok_or(String::from("time integer overflow"))?;
Usa let scales = [3_600_000, 60_000, 1_000, 1]; y let mut total = 0_i64; antes de ese bucle. La suma sigue el mismo patrón comprobado con total.checked_add(piece). Después del bucle, devuelve Ok(total) como expresión final sin punto y coma. Deja intactas las otras dos funciones incompletas.
Pulsa Ctrl+O para escribir, Enter para confirmar el nombre de archivo existente y después Ctrl+X para salir. Si nano pregunta si quieres guardar al salir, responde Y y confirma el nombre de archivo. Lee lo guardado antes de ejecutarlo:
cat src/time.rs
Ejecuta una conversión. cargo run compila e inicia el programa preparado, --quiet oculta el progreso de Cargo y -- pasa los argumentos siguientes al programa. Su comando time usa tu conversión:
cargo run --quiet -- time 00:01:01.750
00:01:01.750 = 61750 ms
Sesenta segundos más un segundo y 750 milisegundos dan 61750. Ahora examina todos los valores de los bloques originales; inspect lee el archivo realmente indicado y llama al analizador:
cargo run --quiet -- inspect data/original.vtt
Cue 1: 4000 --> 7000 ms
Café 林地 🌿
Second line intact
Cue 2: 6000 --> 9000 ms
Overlap stays
Cue 3: 61750 --> 64250 ms
Across a minute
Se conservan los tres registros y las dos líneas de texto del primer bloque. Compara la conversión con tu predicción.
Si no has guardado una edición accidental, pulsa Ctrl+X y después N ante la pregunta de guardar para dejar el archivo sin cambios. Para un error guardado, lee el archivo y la línea indicados por Cargo, vuelve a abrir este código y corrige esa expresión local. Otra opción es restaurar tu propio punto de recuperación. El punto de recuperación milliseconds contiene el estado anterior a esta fase; restaurarlo significa que debes rehacer esta fase y cualquier función posterior.
Si has abierto un Terminal nuevo, sus variables y su directorio de trabajo no se trasladan a él. Repite primero los comandos visibles de entorno y directorio:
export PATH=/home/labex/.cargo/bin:$PATH
export LANG=C.UTF-8 LC_ALL=C.UTF-8
cd /home/labex/project/subtitle-sync
Los comandos de recuperación siguientes son condicionales: úsalos solo al deshacer un error guardado. ls -d enumera los propios nombres de directorio; * coincide con sus sufijos generados. Si no existe ningún punto de recuperación, lee ese error y repara el código en vez de inventar una ruta.
ls -d .edit-before-milliseconds.*
read espera una línea y la guarda en una variable de sesión. Ejecútalo, escribe o pega un nombre de directorio completo real mostrado arriba y pulsa Enter. No escribas un sufijo de ejemplo ni el signo del dólar:
read rs01_restore
Confirma que el archivo sea el código anterior esperado antes de restaurarlo:
cat "$rs01_restore/time.rs"
cp -p "$rs01_restore/time.rs" src/time.rs
Después vuelve a abrir nano, rehaz la implementación que falta y repite el comando fallido. No vuelvas a ejecutar la preparación ni cambies archivos de entrada protegidos. Corregir el código no sustituye un VTT de salida existente ni el archivo ya cargado en un Blob del navegador; conserva el resultado antiguo antes de regenerar su ruta requerida, después selecciona esa salida real y pulsa Load actual files de nuevo.
Desplazar toda la colección de subtítulos
En este paso desplazarás un bloque con tipo definido, aplicarás la operación a todos los bloques y verás la salida realmente guardada. Continúa en Terminal, en /home/labex/project/subtitle-sync.
shift_one toma prestado con &Cue un bloque validado por el analizador; debe devolver uno nuevo sin modificar el original. Añade el desplazamiento con signo de manera independiente a ambos extremos con checked_add y después rechaza los resultados negativos. No conviertas valores negativos en enteros sin signo, no tomes su valor absoluto ni los recortes a cero. Esos atajos cambian los tiempos solicitados.
La estructura devuelta conserva los tiempos nuevos, clona todo el texto del que es propietaria y copia la línea de diagnóstico. .clone() conserva el String completo, incluido Unicode y cada línea de texto. La forma abreviada de los campos start, end recibe las variables de igual nombre. || significa que basta un extremo negativo para rechazar el desplazamiento.
Antes de editar, crea un punto de recuperación de solo el código fuente permitido. mktemp -d crea un directorio único sustituyendo los seis caracteres X; rechaza un nombre existente. $() captura esa ruta en una variable para esta sesión del shell. Las comillas mantienen unida la ruta y && copia solo si se crea correctamente el directorio. cp -p copia el archivo conservando sus permisos y fecha de modificación. Es un punto de recuperación del código fuente, no una copia de ningún subtítulo de salida. Muestra la variable y conserva el nombre real; no adivines el sufijo.
rs01_before_one_cue=$(mktemp -d .edit-before-one-cue.XXXXXX) && cp -p src/time.rs "$rs01_before_one_cue/time.rs"
printf '%s\n' "$rs01_before_one_cue"
printf muestra este nombre de directorio seguido de un salto de línea. Abre el código existente con nano; -c muestra la posición del cursor:
nano -c src/time.rs
Busca con Ctrl+W pub fn shift_one y después pulsa Enter. Su cuerpo termina en la } de cierre de nivel superior justo antes de pub fn shift_all, no en una llave interna de if. Conserva milliseconds sin cambios. Elimina los guiones bajos iniciales de _cue y _delta, conservando los tipos y la firma de retorno. Implementa ahora solo esta función; deja shift_all sin terminar.
Usa un start local obtenido de cue.start.checked_add(delta) y un end de la operación correspondiente con cue.end. Asigna a cada desbordamiento su propio error String mediante ok_or(...)?. Comprueba ambos valores nuevos con if start < 0 || end < 0; devuelve un error desde esa rama. De lo contrario, construye Ok(Cue { start, end, text: cue.text.clone(), line: cue.line }). Esto conserva el bloque original y devuelve su copia desplazada. Guarda con Ctrl+O, Enter y después sal con Ctrl+X.
Predice el bloque individual preparado antes de ejecutarlo: 4000–7000 ms, -2000, texto A green scene:
cargo run --quiet -- cue -2000
2000 --> 5000 ms | A green scene
Esto comprueba un bloque, no toda la película. El siguiente intento deliberado aún devuelve un estado no nulo porque shift_all está sin terminar. No debe imprimir un mensaje de creación correcta ni crear el archivo indicado:
cargo run --quiet -- shift data/original.vtt output/unfinished-collection.vtt -2000
ls enumera una ruta indicada. Aquí también debe devolver un estado no nulo porque no debe existir ninguna salida parcial:
ls output/unfinished-collection.vtt
Construye ahora la operación de colección sin sustituir ninguna de las funciones completadas. Un slice prestado &[Cue] da acceso a los registros de entrada; Vec<Cue> posee el resultado nuevo. Vec::new crea un vector vacío y push añade un bloque completo. iter().enumerate() recorre cada bloque prestado con un índice desde cero. map_err transforma un mensaje de error; su cierre |error| ... recibe el error anterior y format! construye un String con la posición del bloque desde uno y la línea de diagnóstico original.
Antes de editar, crea un punto de recuperación de solo el código fuente permitido. mktemp -d crea un directorio único sustituyendo los seis caracteres X; rechaza un nombre existente. $() captura esa ruta en una variable para esta sesión del shell. Las comillas mantienen unida la ruta y && copia solo si se crea correctamente el directorio. cp -p copia el archivo conservando sus permisos y fecha de modificación. Es un punto de recuperación del código fuente, no una copia de ningún subtítulo de salida. Muestra la variable y conserva el nombre real; no adivines el sufijo.
rs01_before_all_cues=$(mktemp -d .edit-before-all-cues.XXXXXX) && cp -p src/time.rs "$rs01_before_all_cues/time.rs"
printf '%s\n' "$rs01_before_all_cues"
printf muestra este nombre de directorio seguido de un salto de línea. Abre el código existente con nano; -c muestra la posición del cursor:
nano -c src/time.rs
Busca pub fn shift_all. Es la última función del archivo; su cuerpo termina en la última } correspondiente de nivel superior. Los bloques internos for e if tienen sus propias llaves de cierre; no confundas una con el límite de la función. Elimina los guiones bajos de _cues y _delta. Sustituye únicamente su cuerpo incompleto Err(...). Empieza con let mut shifted = Vec::new();, recorre for (index, cue) in cues.iter().enumerate() y llama a la función que acabas de completar. Añade contexto a los fallos:
let next = shift_one(cue, delta)
.map_err(|error| format!("cue {} at line {}: {error}", index + 1, cue.line))?;
Añade next dentro del bucle con shifted.push(next);. Devuelve Ok(shifted) después del bucle. El ? debe devolver un error en lugar de omitir un bloque incorrecto o conservar un prefijo correcto. Guarda, confirma el nombre de archivo y sal de nano. La lógica anterior de milliseconds y shift_one permanece en su sitio.
cat src/time.rs
Repite la prueba de regresión de un solo bloque con el módulo ya completo:
cargo run --quiet -- cue -2000
Usa ahora el comando shift con la ruta de entrada real, una ruta de salida nueva y el desplazamiento en milisegundos con signo:
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
Created output/aligned.vtt with 3 cues (offset -2000 ms)
El límite de archivos preparado lee, analiza y desplaza toda la colección antes de crear un archivo nuevo. Observa su rechazo repitiendo deliberadamente el mismo comando. El siguiente comando debe devolver un estado no nulo e indicar la salida existente:
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
Un archivo existente puede ser una ejecución anterior correcta o un resultado incorrecto de un cambio previo. Corregir el código no lo reescribe. Conserva ese trabajo antes de volver a intentar la ruta de salida requerida. El siguiente comando crea un directorio de copia de seguridad nuevo y mueve a él el archivo existente:
mktemp -d crea un directorio nuevo, sustituyendo los seis caracteres X por un sufijo único; no reutiliza un directorio de copia existente. $() captura la ruta impresa en la variable de sesión rs01_backup_dir. Las comillas alrededor de la variable mantienen unida la ruta. && ejecuta mv solo si se crea correctamente el directorio; mv mueve el archivo existente al directorio vacío recién creado. Así no se sobrescribe una copia anterior. Si falla la creación del directorio, se omite el movimiento y la salida original permanece donde estaba; lee el error antes de repetirlo. Si estás recuperándote de un primer intento fallido, aplica esta misma operación cuando ya exista una salida.
rs01_backup_dir=$(mktemp -d output/aligned-backup.XXXXXX) &&
mv output/aligned.vtt "$rs01_backup_dir/aligned.vtt"
Genera ahora de nuevo la ruta requerida con tu código actual:
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
Debe crear tres bloques. El resultado antiguo sigue disponible dentro del directorio de copia único. El siguiente laboratorio enseña e implementa el propio límite de escritura protegida.
Examina el archivo realmente guardado:
cat output/aligned.vtt
WEBVTT
00:00:02.000 --> 00:00:05.000
Café 林地 🌿
Second line intact
00:00:04.000 --> 00:00:07.000
Overlap stays
00:00:59.750 --> 00:01:02.250
Across a minute
Los dos primeros bloques siguen solapándose. El tercero ha pasado del minuto 1 al minuto anterior y se ha conservado cada línea completa de texto. El programa ha desplazado toda la colección; no basta con que el primer bloque sea correcto.
Cambia a Preview. Mantén Video como data/edited.webm, cambia Caption file a output/aligned.vtt y pulsa Load actual files. El nombre seleccionado que aparece en el estado de carga debe identificar tu salida. La vista previa lee los bytes reales de ese archivo; no vuelve a crear los subtítulos ni recurre a la muestra.
Introduce 2.5 en Time (seconds) y pulsa Go to time: deben coincidir la escena verde y el subtítulo Unicode de dos líneas. Examina después 4.5: la escena azul debe mostrar tanto el primer bloque como Overlap stays. En 60.5, la escena ámbar debe mostrar Across a minute. Un navegador puede ordenar los bloques activos con igual inicio de forma distinta al archivo; deben seguir presentes todos sus textos completos. Pulsa Play para confirmar la reproducción real y después pausa. Download captions guarda el archivo de salida cargado y Download video la película cargada; ninguno de los botones transforma el contenido.
El ejemplo siguiente muestra el archivo realmente guardado en la escena azul. Busca el bloque Unicode completo de dos líneas, el otro bloque solapado independiente y el nombre del archivo de salida cargado.

Vuelve a Terminal. Para provocar un error de ruta intencionado, usa el nombre de archivo singular que no existe:
cargo run --quiet -- inspect data/caption.vtt
Debe devolver un estado no nulo e indicar la ruta que falta. Un archivo ausente es distinto de un bloque mal formado dentro de un archivo existente. Recupérate indicando tu salida real:
cargo run --quiet -- inspect output/aligned.vtt
Deberías volver a ver los tres bloques desplazados y su texto completo. Conserva los subtítulos y el vídeo originales; modificarlos ocultaría el efecto de la transformación.
Si no has guardado una edición accidental, pulsa Ctrl+X y después N ante la pregunta de guardar para dejar el archivo sin cambios. Para un error guardado, lee el archivo y la línea indicados por Cargo, vuelve a abrir este código y corrige esa expresión local. Otra opción es restaurar tu propio punto de recuperación. El punto de recuperación one-cue contiene el estado anterior a esta fase; restaurarlo significa que debes rehacer esta fase y cualquier función posterior.
Si has abierto un Terminal nuevo, sus variables y su directorio de trabajo no se trasladan a él. Repite primero los comandos visibles de entorno y directorio:
export PATH=/home/labex/.cargo/bin:$PATH
export LANG=C.UTF-8 LC_ALL=C.UTF-8
cd /home/labex/project/subtitle-sync
Los comandos de recuperación siguientes son condicionales: úsalos solo al deshacer un error guardado. ls -d enumera los propios nombres de directorio; * coincide con sus sufijos generados. Si no existe ningún punto de recuperación, lee ese error y repara el código en vez de inventar una ruta.
ls -d .edit-before-one-cue.*
read espera una línea y la guarda en una variable de sesión. Ejecútalo, escribe o pega un nombre de directorio completo real mostrado arriba y pulsa Enter. No escribas un sufijo de ejemplo ni el signo del dólar:
read rs01_restore
Confirma que el archivo sea el código anterior esperado antes de restaurarlo:
cat "$rs01_restore/time.rs"
cp -p "$rs01_restore/time.rs" src/time.rs
Después vuelve a abrir nano, rehaz la implementación que falta y repite el comando fallido. No vuelvas a ejecutar la preparación ni cambies archivos de entrada protegidos. Corregir el código no sustituye un VTT de salida existente ni el archivo ya cargado en un Blob del navegador; conserva el resultado antiguo antes de regenerar su ruta requerida, después selecciona esa salida real y pulsa Load actual files de nuevo.
El punto de recuperación .edit-before-one-cue.* tiene completados solo los milisegundos. Para un error limitado a la colección, usa el mismo procedimiento de confirmación con .edit-before-all-cues.*: ese punto también conserva la función de un solo bloque completada. Ninguno es una implementación final completa.
Referencia completa opcional de time.rs para comparación y recuperación
Intenta editar las funciones y examina sus resultados antes de abrir esta referencia. Muestra una implementación completa, no una secuencia obligatoria de comandos. Se permiten algoritmos equivalentes bajo el contrato público. Compara la función afectada en lugar de sustituir innecesariamente el trabajo ya completado.
use crate::Cue;
pub fn milliseconds(parts: [i64; 4]) -> Result<i64, String> {
let scales = [3_600_000, 60_000, 1_000, 1];
let mut total = 0_i64;
for i in 0..4 {
let piece = parts[i]
.checked_mul(scales[i])
.ok_or(String::from("time integer overflow"))?;
total = total
.checked_add(piece)
.ok_or(String::from("time integer overflow"))?;
}
Ok(total)
}
pub fn shift_one(cue: &Cue, delta: i64) -> Result<Cue, String> {
let start = cue.start
.checked_add(delta)
.ok_or(String::from("start integer overflow"))?;
let end = cue.end
.checked_add(delta)
.ok_or(String::from("end integer overflow"))?;
if start < 0 || end < 0 {
return Err(String::from("shift would produce negative time"));
}
Ok(Cue { start, end, text: cue.text.clone(), line: cue.line })
}
pub fn shift_all(cues: &[Cue], delta: i64) -> Result<Vec<Cue>, String> {
let mut shifted = Vec::new();
for (index, cue) in cues.iter().enumerate() {
let next = shift_one(cue, delta)
.map_err(|error| format!("cue {} at line {}: {error}", index + 1, cue.line))?;
shifted.push(next);
}
Ok(shifted)
}
Resumen
Has convertido campos temporales validados en milisegundos enteros con comprobación de desbordamiento, desplazado un bloque y una colección completa, y conservado el texto, el orden, la duración y el solapamiento legítimo. Después has cargado el archivo nuevo real como pista de subtítulos de vídeo, examinado los puntos públicos de sincronización visual y corregido una ruta de entrada errónea. El siguiente laboratorio enseña el límite de archivos que rechaza entradas incorrectas y rutas de salida existentes antes de modificar un archivo de resultado.



