Introducción
Una transformación correcta de subtítulos debe proteger todo el archivo, incluidos los bloques posteriores al primer ejemplo correcto. Un bloque tardío mal formado no debe dejar un resultado parcial, y no se debe sobrescribir una salida existente solo porque la nueva ejecución parezca correcta.
Implementarás el límite de archivos en Rust alrededor de la transformación completa de bloques y después probarás Unicode, texto de varias líneas, solapamientos legítimos, desplazamientos no válidos, rutas ausentes y conflictos de salida. Este entorno independiente proporciona las funciones completas de milisegundos y desplazamiento del laboratorio anterior. No necesita archivos de aquella VM. El reproductor preparado lee la salida que hayas seleccionado realmente; no reconstruye subtítulos a partir de la fuente original.
Debes comprender el formato y las reglas temporales del laboratorio anterior, además de sus requisitos previos de Rust. Este laboratorio añade el límite de crear una salida nueva; no enseña codificación de vídeo ni afirma admitir toda la sintaxis de WebVTT. El autor estima 50 minutos, incluida la lectura y la recuperación habitual, no un tiempo de finalización medido con principiantes.
Validar todo antes de crear la salida
En este paso implementarás la secuencia completa de lectura, validación y creación de un archivo nuevo. Abre Terminal. Añade Cargo a la ruta de este shell, elige la configuración regional UTF-8 disponible para la sesión y entra en el proyecto independiente:
export PATH=/home/labex/.cargo/bin:$PATH
export LANG=C.UTF-8 LC_ALL=C.UTF-8
cd /home/labex/project/subtitle-sync
Repite las exportaciones en cualquier terminal nuevo y el comando de directorio si pierdes tu ubicación. No modifican archivos de inicio. Los glifos Unicode del terminal aún pueden aparecer como símbolos de sustitución; los bytes UTF-8 reales del archivo y los subtítulos del navegador conservan el texto.
Edita únicamente src/export.rs, conservando transform(input: &Path, output: &Path, delta: i64) -> Result<usize, String>. Mantén sin cambios los demás archivos fuente, Cargo.toml, rust-toolchain.toml y los originales bajo data/. Las funciones temporales completas ya están en src/time.rs; sus precondiciones públicas siguen siendo campos temporales no negativos validados por el analizador y objetos Cue válidos. Puedes implementar una secuencia interna equivalente, pero toda validación debe terminar antes de crear la salida. Los archivos preparados no modificados conservan bytes, permisos, propietario/grupo y fecha de modificación; se excluye la fecha de acceso de las lecturas normales.
Aquí se aplica el subconjunto controlado de WebVTT de texto plano del laboratorio anterior. Exige la cabecera exacta WEBVTT y una línea siguiente vacía. Las líneas de tiempos usan HH:MM:SS.mmm --> HH:MM:SS.mmm con los espacios exactos mostrados, dígitos ASCII, al menos dos dígitos de hora, dos de minutos/segundos en 00–59 y tres de milisegundos. Los tiempos completos deben caber en milisegundos i64 no negativos. Los inicios son no decrecientes; se permiten inicios iguales y solapamientos; los finales superan a los inicios. Un bloque tiene una o varias líneas lógicas de contenido no vacías. Se conservan Unicode, tabulaciones, varias líneas y espacios iniciales/finales, incluido el contenido formado enteramente por espacios en blanco. Una línea verdaderamente vacía termina el bloque; se rechaza el texto huérfano posterior. La entrada admite LF/CRLF, un salto de línea final opcional y separadores vacíos adicionales. La salida preparada usa LF, dos dígitos de hora para 0–99 horas (solo los 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, conservando el texto lógico del contenido. Este curso no admite retornos de carro aislados, BOM/metadatos de cabecera, identificadores/ajustes de bloque, NOTE/STYLE/REGION, marcado/entidades, los caracteres de contenido <, > o &, ni caracteres de control distintos de las tabulaciones; los rechaza en vez de descartarlos silenciosamente.
Examina la misma fuente real y después prueba deliberadamente la exportación sin terminar:
cargo run --quiet -- inspect data/original.vtt
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
El segundo comando debe devolver un estado no nulo con transform is unfinished; no debe crear ningún archivo. El subtítulo original empieza en 4000–7000 ms y la escena verde de la película editada empieza en 2000 ms. El desplazamiento requerido sigue siendo -2000 para cada extremo de cada bloque.
Antes de leer la explicación, escribe tu propio orden seguro para estas acciones deliberadamente desordenadas: crear un destino nuevo, formatear texto, analizar todos los bloques, escribir bytes, leer la entrada indicada, desplazar cada bloque. Lee el archivo de prueba con un bloque tardío no válido y predice si debería existir un destino cuando falle el segundo bloque:
cat data/bad-late.vtt
Compara ahora tu orden con el límite de efectos sobre archivos. El límite de archivos debe seguir este orden. Primero lee la entrada realmente indicada como texto UTF-8. Analiza todos los bloques y rechaza el contenido no compatible o mal formado con contexto del bloque. Después desplaza toda la colección con aritmética comprobada, rechazando tiempos negativos o desbordados. Genera el resultado completo en memoria. Solo después de que todas esas operaciones tengan éxito, crea una salida nueva y escribe esos bytes preparados.
Crear la salida antes de completar el análisis, el desplazamiento y el formato podría dejar un archivo vacío o parcial tras un fallo tardío. Escribir con semántica de sustitución podría destruir un resultado anterior útil. OpenOptions configura cómo se abre un archivo. .write(true).create_new(true) solicita un archivo nuevo y escribible; si ya hay cualquier objeto en la ruta, incluido un enlace simbólico colgante, la apertura falla sin truncarlo. Esta decisión atómica de crear un archivo nuevo evita una carrera entre comprobar la existencia y crearlo por separado. No garantiza una transacción general de almacenamiento ante un fallo de E/S ajeno posterior a la creación.
Path es una ruta prestada del sistema de archivos. fs::read_to_string devuelve el String UTF-8 completo o un error de E/S. map_err añade la ruta que realmente falla conservando su mensaje; ? devuelve ese error inmediatamente. Las funciones proporcionadas parse, shift_all y render trabajan con datos completos en memoria. Write es el trait estándar que proporciona write_all, que intenta escribir todos los bytes del búfer preparado. Importarlo habilita ese método; no hace falta implementar ningún trait. as_bytes toma prestados los bytes UTF-8 existentes del String sin dividirlos en fragmentos del tamaño de un carácter. shifted.len() es el número de bloques y solo se devuelve después de escribir correctamente.
Lee el código inicial real, incluidas sus importaciones:
cat src/export.rs
Actualmente solo importa Path. No supongas que los demás nombres estén disponibles. Añade use std::fs::{self, OpenOptions}; para el módulo de sistema de archivos y el constructor, use std::io::Write; para el trait que habilita write_all y use crate::{parse, render, shift_all}; para las funciones preparadas de la biblioteca. Conserva use std::path::Path;. Un orden claro de importación es fs/OpenOptions, Write, Path y después las funciones de crate, como en la referencia opcional; el orden en sí no se evalúa. Las llaves de un grupo use importan varios nombres; no son el cuerpo de una función.
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_export=$(mktemp -d .edit-before-export.XXXXXX) && cp -p src/export.rs "$rs01_before_export/export.rs"
printf '%s\n' "$rs01_before_export"
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/export.rs
Busca con Ctrl+W pub fn transform y después pulsa Enter. Conserva sus tipos y firma de retorno. Renombra _input, _output y _delta quitando los guiones bajos iniciales. El cuerpo está entre la llave de apertura de esta función y la última llave de cierre; cambia ese cuerpo, no el módulo temporal suministrado.
Implementa tu secuencia segura como una función completa antes de ejecutarla. Lee el input real en raw, analízalo en cues, desplaza todos los bloques a shifted y formatea complete. Para cada operación de entrada que pueda fallar, añade la ruta real de entrada con .map_err(|error| format!("{}: {error}", input.display()))?. El formateo en sí devuelve un String, no Result. Después configura un archivo de salida mutable mediante OpenOptions::new().write(true).create_new(true).open(output), añadiendo un error con la ruta de salida si falla. Por último, file.write_all(complete.as_bytes()) debe tener éxito antes de Ok(shifted.len()). Explícate cuál es la primera expresión que puede crear un destino y por qué todos los bloques ya están comprobados en ese punto. Se permiten implementaciones equivalentes con el mismo comportamiento público.
Usa Ctrl+O, Enter, Ctrl+X para guardar y salir de nano. Lee el código guardado:
cat src/export.rs
Procesa la fuente real a través de este límite:
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
Created output/aligned.vtt with 3 cues (offset -2000 ms)
cargo run --quiet -- inspect output/aligned.vtt
Cue 1: 2000 --> 5000 ms
Café 林地 🌿
Second line intact
Cue 2: 4000 --> 7000 ms
Overlap stays
Cue 3: 59750 --> 62250 ms
Across a minute
Están presentes los tres bloques, incluidas ambas líneas de texto y los intervalos solapados. Si ya existe la salida de un intento anterior, consérvala: el siguiente paso ofrece una recuperación sin sobrescrituras antes de recrear este nombre exacto.
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 export 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-export.*
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/export.rs"
cp -p "$rs01_restore/export.rs" src/export.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.
Referencia completa opcional de export.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 std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::Path;
use crate::{parse, render, shift_all};
pub fn transform(input: &Path, output: &Path, delta: i64) -> Result<usize, String> {
let raw = fs::read_to_string(input)
.map_err(|error| format!("{}: {error}", input.display()))?;
let cues = parse(&raw)
.map_err(|error| format!("{}: {error}", input.display()))?;
let shifted = shift_all(&cues, delta)
.map_err(|error| format!("{}: {error}", input.display()))?;
let complete = render(&shifted);
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(output)
.map_err(|error| format!("{}: cannot create new output: {error}", output.display()))?;
file.write_all(complete.as_bytes())
.map_err(|error| format!("{}: write failed: {error}", output.display()))?;
Ok(shifted.len())
}
Recuperarse de errores conservando la evidencia
En este paso distinguirás los conflictos de archivos, los bloques mal formados y los desplazamientos no válidos; después confirmarás la reproducción real del vídeo. Continúa en Terminal, en el directorio del proyecto. Los fallos siguientes son intencionados y deben devolver un estado no nulo; no deben imprimir un mensaje de éxito ni crear la nueva salida solicitada.
Primero repite el comando que funcionó contra su resultado existente:
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
Espera un error que indique el archivo existente output/aligned.vtt. Se mantiene sin cambios. Para conservar un resultado anterior antes de reconstruir la ruta requerida, crea un directorio de copia nuevo. mktemp -d sustituye los seis caracteres X por un sufijo único y se niega a reutilizar un directorio existente. $() captura esa ruta en una variable de sesión y las comillas la mantienen unida. && ejecuta mv solo si se crea correctamente el directorio; mv mueve el resultado antiguo al directorio vacío nuevo, de modo que no se sobrescribe otra copia.
rs01_backup_dir=$(mktemp -d output/aligned-backup.XXXXXX) &&
mv output/aligned.vtt "$rs01_backup_dir/aligned.vtt"
Si falla la creación del directorio, se omite el movimiento y el resultado antiguo permanece. Lee ese error antes de repetirlo. Regenera la salida exacta requerida con tu implementación actual:
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
Lee ahora el archivo de prueba que contiene un bloque tardío mal formado:
cat data/bad-late.vtt
Su primer bloque es correcto, pero el bloque 2 termina antes de empezar. Un prefijo parcial no es una exportación utilizable. El siguiente comando debe devolver un estado no nulo e identificar la fuente real, el bloque 2 y su línea de tiempos:
cargo run --quiet -- shift data/bad-late.vtt output/bad-result.vtt 0
Usa ls, que lista una ruta indicada, para comprobar que no apareció ninguna salida. También se espera un estado no nulo de este comando porque el resultado no existe:
ls output/bad-result.vtt
Recupérate eligiendo la fuente válida y otra salida nueva:
cargo run --quiet -- shift data/original.vtt output/after-bad-input.vtt -2000
Una ruta errónea es un error distinto. Introduce una errata intencionada en plural:
cargo run --quiet -- shift data/originals.vtt output/after-path-error.vtt -2000
Debe indicar la ruta de entrada que falta, sin inventar un error de bloque. Corrige la ruta:
cargo run --quiet -- shift data/original.vtt output/after-path-error.vtt -2000
Un desplazamiento -4001 haría que el primer inicio de 4000 ms bajara de cero. El siguiente comando debe rechazarlo en vez de producir por desbordamiento un número positivo enorme o recortarlo:
cargo run --quiet -- shift data/original.vtt output/negative-result.vtt -4001
Un desplazamiento enorme pero válido como i64 puede desbordarse al sumarlo a un extremo. Este fallo también debe devolver un error atribuible sin crear la salida:
cargo run --quiet -- shift data/original.vtt output/overflow-result.vtt 9223372036854775807
El análisis de argumentos rechaza aún antes un número fuera de i64. Sigue siendo un error recuperable, no un panic:
cargo run --quiet -- shift data/original.vtt output/invalid-offset.vtt 9223372036854775808
Recupérate con el desplazamiento válido conocido y otro nombre de archivo nuevo:
cargo run --quiet -- shift data/original.vtt output/after-offset-errors.vtt -2000
Todos esos fallos han dejado disponibles los subtítulos y la película originales. Los casos extremos de i64 verifican aritmética, no reproducción del navegador con precisión de milisegundos a tiempos enormes; las observaciones del navegador aquí se refieren a este cortometraje.
Examina otra fuente válida con finales de línea CRLF, bloques solapados de igual inicio, contenido formado solo por espacios en blanco y texto Unicode:
cargo run --quiet -- inspect data/whitespace.vtt
Contiene una línea de contenido formada solo por espacios y una tabulación. Esa línea no está vacía y, por tanto, pertenece a su bloque. Una línea verdaderamente vacía separa bloques; no se admite un contenido vacío ni una continuación de texto huérfana. El lector preparado conserva esta distinción y nunca filtra silenciosamente un bloque. Exporta el caso válido cruzando un límite de minuto:
cargo run --quiet -- shift data/whitespace.vtt output/whitespace-shifted.vtt 58001
Cambia a Preview. Mantén Video como data/edited.webm, selecciona Caption file output/aligned.vtt y pulsa Load actual files. En Time (seconds), introduce 2.5, pulsa Go to time y busca la escena verde y el subtítulo Unicode completo de dos líneas. En 4.5 deben verse ambos bloques solapados; en 60.5, la escena ámbar debe mostrar el bloque del cambio de minuto. Pulsa Play para confirmar la reproducción real y después pausa. El nombre cargado identifica tu resultado real. Los botones de descarga guardan los archivos cargados sin reconstruirlos.
También puedes seleccionar output/whitespace-shifted.vtt y examinar 60.5. Se cargan ambos bloques de igual inicio; el subtítulo formado totalmente por espacios en blanco puede no tener texto visible. El archivo guardado y el texto de bloque cargado conservan los espacios iniciales/finales y la segunda línea del otro bloque, aunque la composición del navegador no tenga por qué mostrar cada espacio o tabulación con una anchura distinguible. El texto lógico exacto se comprueba en el archivo, en vez de deducirlo del espaciado visual. El orden de los bloques activos del navegador puede variar para inicios iguales; se sigue conservando el orden de los bloques del archivo de salida.
Vuelve a Terminal y examina por última vez el resultado principal aceptado:
cargo run --quiet -- inspect output/aligned.vtt
Si un comando tiene un error de escritura o de ruta, corrige el comando en vez de editar los datos protegidos. Si tu código Rust es incorrecto, usa la confirmación del punto de recuperación exclusivo del código fuente o la referencia opcional de comparación del paso 1 para reparar la función afectada y después vuelve a ejecutar. Para recrear una salida existente después de corregir el código, consérvala primero con la copia en un directorio único. Ahora tienes un resultado completo más la evidencia anterior, en lugar de un archivo parcial que solo aparenta funcionar.
Resumen
Has implementado el límite de efectos sobre archivos después de validar y transformar todos los bloques. Se han rechazado las rutas de salida existentes sin sustituirlas, los datos incorrectos tardíos no han generado una exportación parcial y los errores habituales de ruta, tiempo negativo y desbordamiento han seguido siendo recuperables. Has conservado el trabajo anterior en directorios de copia únicos y verificado subtítulos Unicode, multilínea y solapados reales con el reproductor real. El desafío combinará estos límites aprendidos para otro cortometraje con un desplazamiento público diferente.



