From 7c7f86b1312ffd82238237f01144edf65e613663 Mon Sep 17 00:00:00 2001 From: Felipe Canin Novaes Date: Sat, 28 Feb 2026 16:11:12 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adiciona=20instru=C3=A7=C3=B5es=20para?= =?UTF-8?q?=20uso=20do=20Copilot=20no=20editor=20de=20=C3=A1udio=20multim?= =?UTF-8?q?=C3=ADdia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 85 +++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..3ee107b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,85 @@ +# 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` 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` é invariante** — `FfmpegCommandBuilder` 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.path`** — `Project::new()` retorna `Err` se forem iguais. +4. Faixas externas adicionadas pelo usuário ficam em `project.tracks: Vec`; faixas detectadas via ffprobe ficam em `project.existing_tracks: Vec` — 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` 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 + → 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 + → run_ffmpeg_async(args, tx, cancel_rx) (infrastructure) + → FfmpegGateway::execute() (adapter, modo síncrono — apenas em testes/uso direto) +``` + +## Comandos de Desenvolvimento + +```bash +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` | +| `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` |