Files
simple-multimidia-track-aud…/.github/copilot-instructions.md
T

5.2 KiB

Copilot Instructions — Simple Multimedia Track Audio Editor

Visão do Produto

Editor gráfico (egui/eframe) que combina vídeo, áudio e legendas em um único arquivo MKV sem reencoding — apenas mux e ajuste de timestamps via FFmpeg. Pense no MKV como um banco de dados: as operações são INSERT de faixas e UPDATE de timestamps; nenhum byte de mídia é reprocessado.

Arquitetura — Clean Architecture (de dentro para fora)

domain/          ← núcleo puro; zero I/O, zero frameworks
application/     ← casos de uso + ports (traits); testável com mocks
adapters/        ← implementações concretas dos ports (FFmpeg, filesystem)
infrastructure/  ← execução assíncrona de processo real (tokio)
ui/              ← egui/eframe; única camada que lida com estado visual
  • Project (src/domain/entities/project.rs) é a única fonte de verdade do estado em memória. Toda mutação passa por métodos do Project ou pelos casos de uso.
  • App (src/ui/app.rs) detém um Option<Project> e orquestra os componentes de UI.
  • Nenhuma camada interna (domain, application) deve importar eframe, tokio, ou qualquer crate de I/O.

Regras de Negócio Críticas

  1. -c copy é invarianteFfmpegCommandBuilder sempre emite -c copy. Nunca remova nem torne opcional.
  2. SyncOffset é sempre i64 em milissegundos — nunca use f64 para representar offset internamente. A conversão para segundos ocorre apenas em FfmpegCommandBuilder::build() e na exibição (Display).
  3. output.path != source.pathProject::new() retorna Err se forem iguais.
  4. Faixas externas adicionadas pelo usuário ficam em project.tracks: Vec<Track>; faixas detectadas via ffprobe ficam em project.existing_tracks: Vec<MediaTrackInfo> — nunca misture os dois vetores.

Convenções de Código

  • Newtypes para todos os value objects: FilePath(PathBuf), TrackId(u32), SyncOffset(i64), TrackLanguage(String). Não use os tipos primitivos diretamente nos casos de uso ou entidades.
  • Ports são traits em src/application/ports/mod.rs. Toda comunicação cross-layer usa Result<T> de anyhow.
  • Casos de uso em src/application/use_cases/ recebem ports por referência (&impl Port) e o Project por &mut — nunca acessam FFmpeg diretamente.
  • Componentes de UI (src/ui/components/) têm estado próprio (formulários, campos) mas não detêm o Project — recebem referências ou emitem valores de volta para App.

Comunicação Assíncrona (UI ↔ Background Thread)

A geração do MKV roda em thread separada (tokio). O padrão é:

UI → spawn(tokio) → run_ffmpeg_async(args, progress_tx, cancel_rx)
                              ↓ BackgroundMsg (LogLine | Done | Error)
              bg_rx (mpsc::Receiver) lido no loop update() do egui
  • App::bg_rx recebe mensagens; App::cancel_tx é um oneshot::Sender para cancelamento.
  • O loop update() do egui drena bg_rx a cada frame para atualizar ExecutionPanel.

Fluxo de Dados Principal

Usuário seleciona vídeo
  → FilePickerAdapter → FilePath
  → LoadMediaInfo (use case) → FfprobeGateway → Vec<MediaTrackInfo>
  → App::try_build_project() → Project

Usuário adiciona faixa
  → AddAudioTrack / AddSubtitle (use case) → Project.tracks.push(Track)

Usuário clica "Gerar MKV"
  → FfmpegCommandBuilder::build(&project) → Vec<String>
  → run_ffmpeg_async(args, tx, cancel_rx) (infrastructure)
  → FfmpegGateway::execute() (adapter, modo síncrono — apenas em testes/uso direto)

Comandos de Desenvolvimento

cargo check          # verificar compilação sem produzir binário
cargo test           # rodar todos os testes (33 testes, todos devem passar)
cargo run            # executar a aplicação (requer ffmpeg e ffprobe no PATH)

Dependências externas necessárias em runtime: ffmpeg e ffprobe disponíveis no PATH. A inicialização do App chama validate_dependencies() e exibe erro global se ausentes.

Arquivos-Chave

Arquivo Papel
src/domain/entities/project.rs Entidade raiz; todas as mutações de estado
src/domain/value_objects/sync_offset.rs Conversão ms ↔ segundos; testes unitários de parsing
src/adapters/ffmpeg/command_builder.rs Tradução Project → argumentos FFmpeg; única responsável por -itsoffset e -map
src/adapters/ffmpeg/ffprobe_gateway.rs Parser JSON do ffprobe → Vec<MediaTrackInfo>
src/infrastructure/process/mod.rs run_ffmpeg_async com streaming de stderr e cancelamento
src/ui/app.rs Orquestrador da UI; gerencia Project, canal de background e componentes
src/application/ports/mod.rs Traits MediaInfoPort, MediaProcessorPort, FileSystemPort