Modelar e deslocar blocos de legendas

RustBeginner
Pratique Agora

Introdução

Um curta perdeu seu título de abertura de dois segundos, mas as legendas ainda usam a linha do tempo original. A primeira cena verde agora começa aos 2 segundos, enquanto sua legenda começa aos 4 segundos. Os cartões visíveis das cenas revelam os tempos; você não precisa adivinhar ouvindo.

Você representará os tempos das legendas como milissegundos inteiros e implementará uma transformação em Rust que desloca todos os blocos, preservando integralmente o texto Unicode e suas várias linhas. Depois, carregará o arquivo realmente exportado como uma faixa de legendas de vídeo no navegador. O próximo laboratório se concentra no limite de escrita que protege a saída e na recuperação de falhas comuns. Os vídeos e as legendas são dados sintéticos originais deste curso, não gravações de acontecimentos reais.

Você já deve entender estruturas, enumerações, vetores, Result, strings, operações com arquivos, testes básicos e laços em Rust. Concluir o curso existente de CLI em Rust é útil, mas opcional; este laboratório fornece a análise dos argumentos e não exige conhecimento de clap. A estimativa do autor é de 45 minutos, incluindo leitura e correções comuns; não é um tempo de conclusão medido com iniciantes.

Observar a sincronização e modelar milissegundos

Nesta etapa, você assistirá a uma amostra de legendas que funciona, examinará sua relação com a linha do tempo original e implementará a conversão numérica dos tempos.

Abra a aba Preview. O reprodutor preparado seleciona inicialmente data/edited.webm e data/reference.vtt. A referência é um exemplo que já funciona, não a saída que você entregará. Clique em Load actual files e depois em Play no vídeo. Você deverá ver um cartão de cena verde e a legenda de duas linhas que começa com Café 林地 🌿. Pause o vídeo. Em Time (seconds), digite 2.5 e clique em Go to time para examinar essa cena diretamente. Esse controle busca o ponto no vídeo real; a legenda é exibida pela faixa de legendas realmente carregada.

O vídeo editado teve um título de 2000 ms removido. O primeiro bloco original vai de 4000 a 7000 ms; o bloco sincronizado deve ir de 2000 a 5000 ms. Aplicar -2000 aos dois extremos o antecipa sem mudar sua duração. Deslocamentos positivos atrasam as legendas. Mais adiante, a cena azul aos 4,5 segundos mostrará uma sobreposição intencional, e a cena âmbar aos 60,5 segundos exercitará a passagem de minuto. Esses pontos de observação são públicos; não há um enigma de áudio.

Mude para Terminal. O projeto está em /home/labex/project/subtitle-sync. O ambiente preparado inclui Rust 1.99.1. Adicione o diretório do Cargo ao caminho de busca de comandos deste shell e selecione uma localidade UTF-8 disponível para esta sessão:

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

$PATH mantém os diretórios de busca existentes. Essas exportações duram apenas nesta sessão de terminal e não editam arquivos de inicialização do shell; repita-as em qualquer novo terminal. A renderização da fonte do terminal ainda pode mostrar símbolos substitutos para alguns caracteres Unicode. Aqui, as verificações confiáveis do texto são os bytes salvos e as legendas reais no navegador.

Entre no projeto. Todos os comandos seguintes usam esse diretório; repita o comando se perder sua localização:

cd /home/labex/project/subtitle-sync

O projeto preparado separa as responsabilidades. src/cues.rs define Cue, valida nosso formato WebVTT controlado, extrai os campos numéricos de tempo e formata a saída. src/main.rs analisa comandos e exibe sucesso ou erros recuperáveis. src/export.rs lê a entrada realmente solicitada e cria uma nova saída após a transformação completa. O próximo laboratório ensina esse limite de arquivo. Você editará apenas src/time.rs, mantendo os nomes e as assinaturas das funções públicas. Mantenha inalterados todos os outros arquivos-fonte, Cargo.toml, rust-toolchain.toml e cada arquivo original em data/. Implementações internas equivalentes são permitidas. Os arquivos preparados que não devem mudar precisam manter bytes, permissões, proprietário/grupo e horário de modificação; leituras comuns podem atualizar o horário de acesso, que não faz parte desse requisito.

Um bloco de legenda tem um tempo inicial, um tempo final e uma ou mais linhas de texto. Seu campo de diagnóstico line registra a linha de tempo original; ele não faz parte do texto da legenda. A estrutura fornecida é:

pub struct Cue {
    pub start: i64,
    pub end: i64,
    pub text: String,
    pub line: usize,
}

i64 é um inteiro de 64 bits com sinal. Armazenamos milissegundos inteiros para que atravessar um minuto seja uma operação aritmética, em vez de uma edição da pontuação de um timestamp. O início e o fim são não negativos, o fim é posterior ao início e o analisador valida essas relações. String possui o texto completo; seus caracteres Unicode não ocupam todos um único byte.

Examine as legendas originais reais com cat, que lê sem alterar um arquivo:

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 oferece suporte a um subconjunto deliberadamente pequeno de WebVTT em texto simples:

  • A primeira linha é exatamente WEBVTT, seguida de uma linha vazia. Metadados de cabeçalho e uma marca de ordem de bytes não são suportados neste curso.
  • Cada linha de tempo segue HH:MM:SS.mmm --> HH:MM:SS.mmm, com exatamente um espaço de cada lado da seta. Todos os dígitos são ASCII. As horas têm pelo menos dois dígitos decimais, os minutos e segundos têm exatamente dois no intervalo 00–59, e os milissegundos têm exatamente três. Cada timestamp completo deve caber em um valor i64 não negativo de milissegundos.
  • Os inícios dos blocos não podem diminuir na ordem do arquivo; inícios iguais e intervalos sobrepostos são válidos. O fim deve ser maior que o início. O programa preserva a ordem do arquivo.
  • Cada bloco tem pelo menos uma linha lógica de conteúdo não vazia. Unicode, tabulações, espaços no início e no fim e várias linhas de texto são preservados. Mesmo um conteúdo composto apenas de espaços ou tabulações é permitido, embora possa parecer vazio. Uma linha realmente vazia encerra um bloco; ela não pode representar uma linha vazia interna do conteúdo. O texto após esse limite deve iniciar outro bloco válido, não uma continuação sem linha de tempo.
  • Na entrada, são permitidos finais de linha LF e CRLF, uma quebra de linha final opcional e várias linhas vazias entre blocos. O formato de saída preparado 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. Ele normaliza a grafia dos timestamps e a quantidade de separadores; o texto lógico do conteúdo não muda. Retornos de carro isolados não são suportados.
  • IDs de blocos, configurações, NOTE, STYLE, REGION, marcação, referências de caracteres e os caracteres <, > ou & no conteúdo não são suportados. Os demais caracteres de controle, incluindo NUL, também não são suportados; somente as tabulações são uma exceção. Conteúdo não suportado é rejeitado, não removido silenciosamente.

Essas restrições descrevem o formato deste curso, não o padrão WebVTT completo. O analisador preparado aplica as regras de estrutura e texto. Ele não realiza a conversão de milissegundos que falta implementar. Sua função auxiliar time_fields verifica a grafia do timestamp e os intervalos dos campos e retorna [hours, minutes, seconds, milliseconds] como quatro valores i64 não negativos. Sua função milliseconds recebe esses campos já validados, não números negativos arbitrários nem minutos fora do intervalo. Ainda assim, ela precisa rejeitar overflow ao combiná-los.

A primeira implementação converte cada campo usando a escala de sua unidade. O array scales contém os milissegundos por hora, minuto, segundo e milissegundo. O intervalo 0..4 inclui de 0 a 3; as posições correspondentes dos arrays associam cada campo à sua escala. Os sublinhados facilitam a leitura de literais numéricos grandes. 0_i64 escolhe explicitamente o tipo do acumulador.

checked_mul e checked_add retornam Some(value) quando o resultado cabe ou None em caso de overflow de inteiro. ok_or converte esse Option em Result com uma mensagem de erro armazenada em uma String que possui o texto. ? continua com o valor de sucesso ou retorna o erro imediatamente. Overflow é uma rejeição normal da entrada, não um panic nem um tempo que dá a volta no intervalo numérico.

Antes de programar, preveja o total de 00:01:01.750: quanto vem de cada campo diferente de zero? Escreva o inteiro esperado e depois use a execução abaixo para testar sua previsão.

Primeiro leia o código inicial real:

cat src/time.rs

use crate::Cue importa o tipo preparado desta biblioteca. Mantenha-o no lugar. Há três funções; edite apenas milliseconds agora. Sua chave de abertura { está na linha da assinatura, e a chave de fechamento correspondente } fica logo antes de shift_one. As duas funções de deslocamento devem manter seus corpos incompletos nesta fase.

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_milliseconds=$(mktemp -d .edit-before-milliseconds.XXXXXX) && cp -p src/time.rs "$rs01_before_milliseconds/time.rs"
printf '%s\n' "$rs01_before_milliseconds"

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/time.rs

Pressione Ctrl+W, digite pub fn milliseconds e pressione Enter para pesquisar. As setas movem o cursor pelo arquivo. Nessa assinatura, remova o sublinhado inicial de _parts para que o corpo possa usar parts; mantenha [i64; 4] e o tipo de retorno inalterados. Substitua apenas a linha Err(...) dessa função por sua lógica de conversão, dentro das chaves existentes. Não remova a próxima função.

Construa as quatro escalas na mesma ordem dos campos validados. Inicie um total i64 mutável em zero. Para cada índice em 0..4, multiplique o campo por sua escala com verificação de overflow e depois some a parcela ao total também com verificação. Converta qualquer overflow em erro com ok_or(String::from("time integer overflow"))?. Por exemplo, a primeira parte do laço calcula uma única contribuição:

let piece = parts[i]
    .checked_mul(scales[i])
    .ok_or(String::from("time integer overflow"))?;

Use let scales = [3_600_000, 60_000, 1_000, 1]; e let mut total = 0_i64; antes desse laço. A soma segue o mesmo padrão com verificação usando total.checked_add(piece). Depois do laço, retorne Ok(total) como expressão final sem ponto e vírgula. Deixe os dois corpos incompletos restantes intactos.

Pressione Ctrl+O para gravar, Enter para confirmar o nome de arquivo existente e Ctrl+X para sair. Se o nano perguntar sobre salvar ao sair, responda Y e confirme o nome do arquivo. Leia o que salvou antes de executar:

cat src/time.rs

Execute uma única conversão. cargo run compila e inicia o programa preparado, --quiet oculta o progresso do Cargo, e -- passa os argumentos seguintes ao programa. O comando time dele usa sua conversão:

cargo run --quiet -- time 00:01:01.750
00:01:01.750 = 61750 ms

Sessenta segundos mais um segundo e 750 milissegundos resultam em 61750. Agora examine todos os valores dos blocos originais; inspect lê o arquivo realmente indicado e chama o analisador:

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

Os três registros e as duas linhas de texto do primeiro bloco permanecem. Compare a conversão com sua previsão.

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 milliseconds 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-milliseconds.*

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/time.rs"
cp -p "$rs01_restore/time.rs" src/time.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.

Deslocar toda a coleção de legendas

Nesta etapa, você deslocará um bloco tipado, aplicará essa operação a todos os blocos e assistirá à saída realmente salva. Continue em Terminal, em /home/labex/project/subtitle-sync.

shift_one toma emprestado um bloco validado pelo analisador com &Cue; ele deve retornar um novo bloco sem mudar o original. Adicione o deslocamento com sinal separadamente aos dois extremos usando checked_add e depois rejeite resultados negativos. Não converta valores negativos em inteiros sem sinal, não tome seu valor absoluto nem os limite a zero. Esses atalhos mudam os tempos solicitados.

A estrutura retornada mantém os novos tempos, clona o texto completo que pertence ao bloco original e copia sua linha de diagnóstico. .clone() preserva a String inteira, incluindo Unicode e cada linha de texto. Os campos abreviados da estrutura start, end recebem variáveis com os mesmos nomes. || significa que basta um dos extremos ser negativo para rejeitar o deslocamento.

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_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 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/time.rs

Pesquise com Ctrl+W por pub fn shift_one e pressione Enter. Seu corpo termina na chave de fechamento } do nível superior logo antes de pub fn shift_all, não em uma chave interna de if. Mantenha milliseconds inalterada. Remova os sublinhados iniciais de _cue e _delta, preservando os tipos e a assinatura de retorno. Implemente somente esta função agora; deixe shift_all incompleta.

Use um start local obtido de cue.start.checked_add(delta) e um end obtido da operação correspondente em cue.end. Dê a cada overflow seu próprio erro String por meio de ok_or(...)?. Verifique os dois novos valores com if start < 0 || end < 0; retorne um erro nesse ramo. Caso contrário, construa Ok(Cue { start, end, text: cue.text.clone(), line: cue.line }). Isso preserva o bloco original e retorna sua cópia deslocada. Salve com Ctrl+O, Enter e saia com Ctrl+X.

Preveja o resultado do bloco único preparado antes de executá-lo: 4000–7000 ms, -2000, texto A green scene:

cargo run --quiet -- cue -2000
2000 --> 5000 ms | A green scene

Isso demonstra um bloco, não o vídeo inteiro. A tentativa deliberada seguinte ainda retorna um status diferente de zero porque shift_all está incompleta. Ela não deve imprimir sucesso de criação nem criar o arquivo indicado:

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

ls lista um caminho indicado. Aqui ele também deve retornar um status diferente de zero porque nenhuma saída parcial deve existir:

ls output/unfinished-collection.vtt

Agora construa a operação da coleção sem substituir nenhuma das funções concluídas. Uma fatia emprestada &[Cue] expõe os registros de entrada; Vec<Cue> possui o novo resultado. Vec::new cria um vetor vazio e push acrescenta um bloco completo. iter().enumerate() visita cada bloco emprestado com um índice iniciado em zero. map_err transforma uma mensagem de erro; sua closure |error| ... recebe o erro anterior, e format! constrói uma String com a posição do bloco contada a partir de um e a linha de diagnóstico original.

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_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 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/time.rs

Pesquise pub fn shift_all. Esta é a última função do arquivo; seu corpo termina na última chave correspondente } do nível superior. Blocos internos de for e if têm suas próprias chaves de fechamento; não confunda uma delas com o limite da função. Remova os sublinhados de _cues e _delta. Substitua apenas seu corpo Err(...) incompleto. Comece com let mut shifted = Vec::new();, percorra for (index, cue) in cues.iter().enumerate() e chame a função que acabou de concluir. Acrescente contexto às falhas:

let next = shift_one(cue, delta)
    .map_err(|error| format!("cue {} at line {}: {error}", index + 1, cue.line))?;

Acrescente next dentro do laço com shifted.push(next);. Retorne Ok(shifted) após o laço. O ? deve retornar um erro em vez de pular um bloco inválido ou manter um prefixo bem-sucedido. Salve, confirme o nome do arquivo e saia do nano. Sua lógica anterior de milliseconds e shift_one permanece no lugar.

cat src/time.rs

Repita o teste de regressão de um único bloco com o módulo agora completo:

cargo run --quiet -- cue -2000

Agora use o comando shift com o caminho real de entrada, um novo caminho de saída e o deslocamento em milissegundos com sinal:

cargo run --quiet -- shift data/original.vtt output/aligned.vtt -2000
Created output/aligned.vtt with 3 cues (offset -2000 ms)

O limite de arquivo preparado lê, analisa e desloca toda a coleção antes de criar um novo arquivo. Observe sua recusa repetindo intencionalmente o mesmo comando. O próximo comando deve retornar um status diferente de zero e indicar a saída existente:

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

Um arquivo existente pode ser o resultado correto de uma execução anterior ou um resultado incorreto de uma edição anterior. Corrigir o código não o reescreve. Preserve esse trabalho antes de tentar novamente o caminho de saída exigido. O comando seguinte cria um novo diretório de backup e move o arquivo existente para ele:

mktemp -d cria um novo diretório, substituindo os seis caracteres X por um sufixo único; ele não reutiliza um diretório de backup existente. $() captura o caminho impresso na variável de sessão rs01_backup_dir. A variável entre aspas mantém esse caminho unido. && executa mv somente se a criação do diretório tiver sucesso; mv move o arquivo existente para o novo diretório vazio. Assim, um backup anterior não é sobrescrito. Se a criação do diretório falhar, a movimentação será ignorada e a saída original permanecerá onde estava; leia o erro antes de tentar novamente. Se estiver se recuperando de uma primeira tentativa anterior que falhou, use a mesma operação de backup quando houver uma saída existente.

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

Agora gere novamente o caminho exigido com seu código atual:

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

Ele deve criar três blocos. O resultado anterior continua disponível dentro do diretório de backup único. O próximo laboratório ensina e implementa o próprio limite de escrita protegida.

Examine o arquivo realmente salvo:

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

Os dois primeiros blocos ainda se sobrepõem. O início do terceiro passou de 1 minuto para o minuto anterior, e todas as linhas completas de texto foram preservadas. O programa deslocou toda a coleção; acertar apenas o primeiro bloco não basta.

Mude para Preview. Mantenha Video em data/edited.webm, altere Caption file para output/aligned.vtt e clique em Load actual files. O nome selecionado no estado de carregamento deve identificar sua saída. A prévia lê os bytes reais desse arquivo; ela não recria as legendas nem volta à amostra como alternativa.

Digite 2.5 em Time (seconds) e clique em Go to time: a cena verde e a legenda Unicode de duas linhas devem coincidir. Depois examine 4.5: a cena azul deve mostrar tanto o primeiro bloco quanto Overlap stays. Em 60.5, a cena âmbar deve mostrar Across a minute. Um navegador pode ordenar blocos ativos com o mesmo início de modo diferente da ordem do arquivo; todos os seus textos completos devem permanecer presentes. Clique em Play para confirmar a reprodução real e depois pause. Download captions salva o arquivo de saída carregado, e Download video salva o vídeo carregado; nenhum botão transforma o conteúdo.

O exemplo abaixo mostra o arquivo realmente salvo na cena azul. Procure tanto o bloco Unicode completo de duas linhas quanto o bloco separado que se sobrepõe a ele, além do nome do arquivo de saída carregado.

O arquivo realmente sincronizado preserva integralmente os dois blocos sobrepostos

Volte a Terminal. Para provocar intencionalmente um erro de caminho, use o nome singular inexistente:

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

Isso deve retornar um status diferente de zero e indicar o caminho ausente. Um arquivo ausente é diferente de um bloco malformado dentro de um arquivo existente. Recupere-se indicando sua saída real:

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

Você deverá ver novamente os três blocos deslocados e seu texto completo. Preserve as legendas e o vídeo originais; alterá-los esconderia o efeito da transformação.

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 one-cue 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-one-cue.*

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/time.rs"
cp -p "$rs01_restore/time.rs" src/time.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.

O ponto de restauração .edit-before-one-cue.* tem apenas a função de milissegundos concluída. Para um erro limitado à coleção, use o mesmo procedimento de confirmação com .edit-before-all-cues.*: esse ponto também mantém a função de bloco único concluída. Nenhum deles é uma implementação final completa.

Referência completa opcional de time.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 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)
}

Resumo

Você converteu campos de tempo validados em milissegundos inteiros com verificação de overflow, deslocou um bloco e uma coleção completa e preservou texto, ordem, duração e sobreposições válidas. Depois carregou o novo arquivo real como uma faixa de legendas de vídeo, examinou os pontos visuais de sincronização publicados e se recuperou de um caminho de entrada incorreto. O próximo laboratório ensina o limite de arquivo que rejeita entradas inválidas e caminhos de saída existentes antes de alterar um arquivo de resultado.