Rejeitar deslocamentos inválidos sem perder legendas

RustBeginner
Pratique Agora

Introdução

Uma transformação correta de legendas precisa proteger o arquivo inteiro, incluindo as legendas depois do primeiro exemplo válido. Um bloco malformado mais adiante não pode deixar um resultado parcial, e uma saída existente não pode ser sobrescrita só porque a nova execução parece bem-sucedida.

Você implementará o limite de arquivo em Rust em torno da transformação completa dos blocos e depois exercitará Unicode, texto de várias linhas, sobreposições válidas, deslocamentos inválidos, caminhos ausentes e conflitos de saída. Este ambiente independente fornece as funções completas de milissegundos e deslocamento do laboratório anterior. Nenhum arquivo da VM anterior é necessário. O reprodutor preparado lê a saída real que você selecionou; ele não reconstrói legendas a partir da fonte original.

Você deve compreender as regras de formato e tempo do laboratório anterior, além dos pré-requisitos de Rust nele declarados. Este laboratório acrescenta o limite de criação de uma nova saída; ele não ensina codificação de vídeo nem afirma oferecer suporte a toda a sintaxe WebVTT. A estimativa do autor é de 50 minutos, incluindo leitura e recuperação de erros comuns, não um tempo de conclusão medido com iniciantes.

Validar tudo antes de criar a saída

Nesta etapa, você implementará a sequência completa de leitura, validação e criação de um novo arquivo. Abra Terminal. Adicione o Cargo ao caminho deste shell, escolha a localidade UTF-8 disponível para a sessão e entre no projeto independente:

export PATH=/home/labex/.cargo/bin:$PATH
export LANG=C.UTF-8 LC_ALL=C.UTF-8
cd /home/labex/project/subtitle-sync

Repita as exportações em qualquer novo terminal e o comando de diretório se perder sua localização. Elas não editam arquivos de inicialização. Os caracteres Unicode ainda podem aparecer como símbolos substitutos no terminal; os bytes reais do arquivo UTF-8 e as legendas no navegador preservam o texto.

Edite apenas src/export.rs, mantendo transform(input: &Path, output: &Path, delta: i64) -> Result<usize, String>. Mantenha inalterados todos os outros arquivos-fonte, Cargo.toml, rust-toolchain.toml e os arquivos originais em data/. As funções de tempo completas já estão em src/time.rs; suas precondições públicas continuam sendo campos de tempo não negativos validados pelo analisador e objetos Cue válidos. Você pode implementar uma sequência interna equivalente, mas toda a validação precisa terminar antes da criação da saída. Os arquivos preparados que não devem mudar precisam manter bytes, permissões, proprietário/grupo e horário de modificação; o horário de acesso decorrente de leituras comuns é excluído.

O subconjunto controlado de WebVTT em texto simples do laboratório anterior também se aplica aqui. Ele exige o cabeçalho exato WEBVTT e uma linha seguinte vazia. As linhas de tempo usam HH:MM:SS.mmm --> HH:MM:SS.mmm com os espaços exatos mostrados, dígitos ASCII, pelo menos dois dígitos de hora, dois dígitos de minuto/segundo no intervalo 00–59 e três de milissegundos. Os tempos completos devem caber em milissegundos i64 não negativos. Os inícios não podem diminuir; inícios iguais e sobreposições são válidos; os fins são posteriores aos inícios. Um bloco tem uma ou mais linhas lógicas de conteúdo não vazias. Unicode, tabulações, texto de várias linhas e espaços no início e no fim são preservados, incluindo conteúdos compostos inteiramente de espaços em branco. Uma linha realmente vazia encerra o bloco; texto solto depois dela é rejeitado. LF/CRLF, uma quebra de linha final opcional e separadores vazios adicionais são permitidos na entrada. A saída preparada usa LF, dois dígitos de hora para 0–99 horas (somente os dígitos adicionais necessários para horas maiores), uma linha vazia após o cabeçalho e uma após cada bloco, inclusive o último. Ela normaliza a grafia dos timestamps e a quantidade de separadores, preservando o texto lógico do conteúdo. Retornos de carro isolados, BOM/metadados de cabeçalho, IDs/configurações de blocos, NOTE/STYLE/REGION, marcação/entidades, <, > ou & no conteúdo e caracteres de controle diferentes de tabulações não são suportados neste curso e são rejeitados, não descartados silenciosamente.

Examine a mesma fonte real e depois tente deliberadamente a exportação ainda incompleta:

cargo run --quiet -- inspect data/original.vtt
cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000

O segundo comando deve retornar um status diferente de zero com transform is unfinished; ele não pode criar um arquivo. A legenda original começa em 4000–7000 ms e a cena verde do vídeo editado começa em 2000 ms. O deslocamento exigido continua sendo -2000 em cada extremo de cada bloco.

Antes de ler a explicação, escreva sua própria ordem segura para estas ações deliberadamente desordenadas: criar um novo destino, renderizar texto, analisar todos os blocos, escrever bytes, ler a entrada indicada, deslocar cada bloco. Leia o arquivo de teste com erro posterior e preveja se um destino deve existir caso seu segundo bloco falhe:

cat data/bad-late.vtt

Agora compare sua ordem com o limite dos efeitos sobre arquivos. O limite de arquivo deve seguir esta ordem. Primeiro, leia a entrada realmente indicada como texto UTF-8. Analise todos os blocos e rejeite conteúdo não suportado ou malformado com contexto do bloco. Depois, desloque toda a coleção com aritmética que verifica overflow, rejeitando tempos negativos ou que excedam o intervalo. Renderize o resultado completo na memória. Somente depois de todas essas operações terem sucesso, crie uma nova saída e escreva os bytes preparados.

Criar a saída antes de concluir a análise, o deslocamento e a renderização poderia deixar um arquivo vazio ou parcial após uma falha em um bloco posterior. Escrever com substituição poderia destruir um resultado anterior útil. OpenOptions configura como um arquivo é aberto. .write(true).create_new(true) solicita um arquivo recém-criado que permite escrita; se qualquer objeto já ocupar o caminho, incluindo um link simbólico cujo destino não existe, a abertura falha sem truncá-lo. Essa decisão atômica de criar um novo arquivo evita uma corrida entre verificar a existência e criar. Ela não é uma garantia geral de transação de armazenamento diante de uma falha de E/S não relacionada após a criação.

Path é um caminho de sistema de arquivos emprestado. fs::read_to_string retorna a String UTF-8 completa ou um erro de E/S. map_err acrescenta o caminho real que falhou e mantém a mensagem; ? retorna esse erro imediatamente. As funções fornecidas parse, shift_all e render trabalham com dados completos na memória. Write é a trait padrão que fornece write_all, que tenta escrever cada byte do buffer preparado. Importá-la disponibiliza esse método; não é necessário implementar uma trait. as_bytes toma emprestados os bytes UTF-8 existentes da String sem recortá-la em fatias do tamanho de caracteres. shifted.len() é a quantidade de blocos, retornada somente após a escrita ter sucesso.

Leia o código inicial real, incluindo suas importações:

cat src/export.rs

Ele atualmente importa apenas Path. Não suponha que os outros nomes estejam disponíveis. Acrescente use std::fs::{self, OpenOptions}; para o módulo de sistema de arquivos e o construtor, use std::io::Write; para a trait que disponibiliza write_all e use crate::{parse, render, shift_all}; para as funções preparadas da biblioteca. Mantenha use std::path::Path;. Uma ordem clara é fs/OpenOptions, Write, Path e depois as funções do crate, como na referência opcional; a ordem em si não é avaliada. As chaves em um grupo use importam vários nomes; elas não são o corpo de uma função.

Antes de editar, crie um ponto de restauração apenas do código-fonte permitido. mktemp -d cria um diretório único substituindo os seis caracteres X; ele recusa um nome existente. $() captura esse caminho em uma variável desta sessão do shell. As aspas mantêm o caminho unido, e && copia somente após a criação do diretório ter sucesso. cp -p copia o arquivo preservando suas permissões e seu horário de modificação. Este é um ponto de restauração do código-fonte, não um backup de qualquer legenda de saída. Imprima a variável e guarde o nome real; não adivinhe o sufixo.

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 imprime esse nome de diretório seguido de uma quebra de linha. Abra o código-fonte existente com nano; -c mostra a posição do cursor:

nano -c src/export.rs

Pesquise com Ctrl+W por pub fn transform e pressione Enter. Mantenha seus tipos e sua assinatura de retorno. Renomeie _input, _output e _delta removendo os sublinhados iniciais. O corpo fica entre a chave de abertura dessa função e a última chave de fechamento; altere esse corpo, não o módulo de tempo fornecido.

Implemente sua sequência segura como uma função completa antes de executá-la. Leia a entrada real input em raw, analise-a em cues, desloque todos os blocos para shifted e renderize complete. Para cada operação de entrada que pode falhar, acrescente o caminho real da entrada com .map_err(|error| format!("{}: {error}", input.display()))?. A renderização em si retorna uma String, não Result. Depois configure um arquivo de saída mutável por meio de OpenOptions::new().write(true).create_new(true).open(output), acrescentando um erro com caminho de saída se falhar. Por fim, file.write_all(complete.as_bytes()) precisa ter sucesso antes de Ok(shifted.len()). Explique para si qual é a primeira expressão capaz de criar um destino e por que todos os blocos já estão verificados nesse ponto. Implementações equivalentes com o mesmo comportamento público são permitidas.

Use Ctrl+O, Enter, Ctrl+X para salvar e sair do nano. Leia o código-fonte salvo:

cat src/export.rs

Processe a fonte real por meio desse limite:

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

Os três blocos estão presentes, incluindo ambas as linhas de texto e os intervalos sobrepostos. Se a saída já existir após uma tentativa anterior, preserve-a: a próxima etapa fornece uma recuperação sem sobrescrita antes de recriar esse nome de arquivo exato.

Se ainda não salvou uma edição acidental, pressione Ctrl+X e depois N na pergunta sobre salvar para deixar o arquivo inalterado. Para um erro já salvo, leia o arquivo e a linha indicados pelo Cargo, reabra esse código-fonte e corrija a expressão local. Como alternativa, restaure seu próprio ponto de restauração. O ponto de restauração export contém o estado anterior a esta fase; restaurá-lo significa que você precisará refazer esta fase e quaisquer funções posteriores.

Se abriu um novo Terminal, suas variáveis e seu diretório de trabalho não são transferidos. Primeiro repita os comandos visíveis de ambiente e diretório:

export PATH=/home/labex/.cargo/bin:$PATH
export LANG=C.UTF-8 LC_ALL=C.UTF-8
cd /home/labex/project/subtitle-sync

Os comandos de recuperação seguintes são condicionais: use-os apenas para desfazer um erro salvo. ls -d lista os próprios nomes de diretórios; * corresponde aos seus sufixos gerados. Se nenhum ponto de restauração existir, leia esse erro e corrija o código-fonte em vez de inventar um caminho.

ls -d .edit-before-export.*

read espera uma linha e a armazena em uma variável da sessão. Execute-o, depois digite ou cole um nome de diretório completo real impresso acima e pressione Enter. Não digite um sufixo de exemplo nem o sinal de dólar:

read rs01_restore

Confirme que o arquivo contém o código-fonte anterior esperado antes de restaurá-lo:

cat "$rs01_restore/export.rs"
cp -p "$rs01_restore/export.rs" src/export.rs

Depois reabra o nano, refaça a implementação que falta e execute novamente o comando que falhou. Não execute o setup novamente nem altere os arquivos de entrada protegidos. Corrigir o código não substitui um VTT de saída existente nem o arquivo já carregado em um Blob do navegador; preserve o resultado anterior antes de gerar novamente seu caminho exigido, depois selecione essa saída real e clique em Load actual files novamente.

Referência completa opcional de export.rs para comparação e recuperação

Tente editar as funções e examine os resultados antes de abrir esta referência. Ela mostra uma implementação completa, não uma sequência obrigatória de comandos. Algoritmos equivalentes são permitidos pelo contrato público. Compare a função afetada em vez de substituir desnecessariamente o trabalho já concluído.

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())
}

Recuperar-se de erros preservando as evidências

Nesta etapa, você distinguirá conflitos de arquivo, blocos malformados e deslocamentos inválidos e depois confirmará a reprodução real do vídeo. Continue em Terminal, no diretório do projeto. As falhas abaixo são intencionais e devem retornar um status diferente de zero; elas não podem imprimir uma mensagem de sucesso nem criar a nova saída solicitada.

Primeiro repita o comando bem-sucedido contra seu resultado existente:

cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000

Espere um erro que indique o arquivo existente output/aligned.vtt. Ele permanece inalterado. Para preservar um resultado anterior antes de reconstruir o caminho exigido, crie um novo diretório de backup. mktemp -d substitui os seis caracteres X por um sufixo único e se recusa a reutilizar um diretório existente. $() captura esse caminho em uma variável da sessão, e as aspas o mantêm unido. && executa mv somente quando o diretório foi criado com sucesso; mv move o resultado anterior para esse novo diretório vazio, de modo que outro backup não seja sobrescrito.

rs01_backup_dir=$(mktemp -d output/aligned-backup.XXXXXX) &&
mv output/aligned.vtt "$rs01_backup_dir/aligned.vtt"

Se a criação do diretório falhar, a movimentação será ignorada e o resultado anterior permanecerá. Leia esse erro antes de tentar novamente. Gere novamente a saída exata exigida com sua implementação atual:

cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000

Agora leia o arquivo de teste com um bloco malformado mais adiante:

cat data/bad-late.vtt

Seu primeiro bloco é válido, mas o bloco 2 termina antes de começar. Um prefixo parcial não é uma exportação utilizável. O próximo comando deve retornar um status diferente de zero e identificar a fonte real, o bloco 2 e sua linha de tempo:

cargo run --quiet -- shift data/bad-late.vtt output/bad-result.vtt 0

Use ls, que lista um caminho indicado, para verificar que nenhuma saída apareceu. Este comando também deve retornar um status diferente de zero, pois o resultado não existe:

ls output/bad-result.vtt

Recupere-se escolhendo a fonte válida e uma nova saída separada:

cargo run --quiet -- shift data/original.vtt output/after-bad-input.vtt -2000

Um caminho incorreto é um erro diferente. Use um erro de digitação intencional no plural:

cargo run --quiet -- shift data/originals.vtt output/after-path-error.vtt -2000

Ele deve indicar o caminho de entrada ausente, sem inventar um erro de bloco. Corrija o caminho:

cargo run --quiet -- shift data/original.vtt output/after-path-error.vtt -2000

Um deslocamento de -4001 levaria o primeiro início, de 4000 ms, para abaixo de zero. O próximo comando precisa rejeitar esse deslocamento em vez de convertê-lo, por retorno ao início do intervalo numérico, em um enorme número positivo ou limitá-lo a zero:

cargo run --quiet -- shift data/original.vtt output/negative-result.vtt -4001

Um deslocamento enorme, mas válido em i64, pode causar overflow ao ser somado a um extremo. Essa falha também deve retornar um erro com contexto, sem criar a saída:

cargo run --quiet -- shift data/original.vtt output/overflow-result.vtt 9223372036854775807

Um número fora de i64 é rejeitado ainda antes, durante a análise dos argumentos. Isso continua sendo um erro recuperável, não um panic:

cargo run --quiet -- shift data/original.vtt output/invalid-offset.vtt 9223372036854775808

Recupere-se com o deslocamento sabidamente válido e outro novo nome de arquivo:

cargo run --quiet -- shift data/original.vtt output/after-offset-errors.vtt -2000

Todas essas falhas deixaram as legendas e o vídeo originais disponíveis. Os casos extremos de i64 verificam a aritmética, não a reprodução no navegador com precisão de milissegundos em tempos enormes; as observações no navegador aqui dizem respeito a este curta.

Examine outra fonte válida, com finais de linha CRLF, blocos sobrepostos com o mesmo início, conteúdo composto apenas de espaços em branco e texto Unicode:

cargo run --quiet -- inspect data/whitespace.vtt

Ela contém uma linha de conteúdo composta apenas de espaços e uma tabulação. Essa linha não é vazia e, portanto, pertence ao seu bloco. Uma linha realmente vazia, por outro lado, separa blocos; um conteúdo vazio ou uma continuação solta de texto não é suportado. O leitor preparado mantém essa distinção e nunca filtra um bloco silenciosamente. Exporte o caso válido atravessando uma passagem de minuto:

cargo run --quiet -- shift data/whitespace.vtt output/whitespace-shifted.vtt 58001

Mude para Preview. Mantenha Video em data/edited.webm, selecione Caption file output/aligned.vtt e clique em Load actual files. Em Time (seconds), use 2.5, clique em Go to time e procure a cena verde e a legenda Unicode completa de duas linhas. Em 4.5, ambos os blocos sobrepostos devem estar visíveis; em 60.5, a cena âmbar deve mostrar o bloco que atravessou a passagem de minuto. Clique em Play para confirmar a reprodução real e depois pause. O nome carregado identifica seu resultado real. Os botões de download salvam os arquivos carregados sem reconstruí-los.

Você também pode selecionar output/whitespace-shifted.vtt e examinar 60.5. Ambos os blocos com o mesmo início são carregados; a legenda composta inteiramente de espaços em branco pode parecer vazia. O arquivo salvo e o texto do bloco carregado preservam os espaços iniciais/finais e a segunda linha do outro bloco, embora o layout do navegador não precise exibir cada espaço ou tabulação com uma largura distinguível. O texto lógico exato é verificado no arquivo, não deduzido do espaçamento visual. A ordem dos blocos ativos no navegador pode diferir para inícios iguais; a ordem dos blocos no arquivo de saída continua preservada.

Volte a Terminal e examine o principal resultado aceito uma última vez:

cargo run --quiet -- inspect output/aligned.vtt

Se um comando tiver um erro de grafia ou de caminho, corrija o comando em vez de editar os dados protegidos. Se seu código Rust estiver errado, use a confirmação do ponto de restauração apenas do código-fonte ou a referência opcional de comparação da etapa 1 para corrigir a função afetada e depois execute novamente. Ao recriar uma saída existente depois de corrigir o código, preserve-a primeiro com a operação de backup em um diretório único. Agora você tem um resultado completo e as evidências anteriores, em vez de um arquivo parcial que apenas parece bem-sucedido.

Resumo

Você implementou o limite dos efeitos sobre arquivos após a validação e a transformação completas dos blocos. Caminhos de saída existentes foram recusados sem substituição, dados inválidos mais adiante não produziram uma exportação parcial e erros comuns de caminho, tempo negativo e overflow continuaram recuperáveis. Você preservou trabalhos anteriores em diretórios de backup únicos e verificou legendas Unicode reais, de várias linhas e sobrepostas com o reprodutor real. O desafio combinará esses limites já ensinados para outro curta, com um deslocamento público diferente.