diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 0000000..c59999c --- /dev/null +++ b/PROGRESS.md @@ -0,0 +1,199 @@ +# Progresso de Implementação + +**Data:** 28/02/2026 +**Status:** Fases 1–6 concluídas — compilando, testes passando, aplicação executável +**Referência:** DEVELOPMENT_PLAN.md v1.0 + +--- + +## Resumo Executivo + +Todas as 6 fases do plano de desenvolvimento foram implementadas. O projeto compila sem erros, +26 testes unitários passam e a aplicação pode ser executada com `cargo run`. + +--- + +## Status por Fase + +| Fase | Descrição | Status | +|------|----------------------------------|-------------| +| 1 | Setup do projeto | ✅ Concluída | +| 2 | Domain (núcleo puro) | ✅ Concluída | +| 3 | Application (use cases + ports) | ✅ Concluída | +| 4 | Adapters (FFmpeg + filesystem) | ✅ Concluída | +| 5 | Infrastructure (processo async) | ✅ Concluída | +| 6 | UI (eframe/egui) | ✅ Concluída | + +--- + +## Estrutura de Arquivos Criados + +``` +src/ +├── main.rs — entrada, configuração da janela eframe +│ +├── domain/ +│ ├── mod.rs +│ ├── value_objects/ +│ │ ├── mod.rs +│ │ ├── file_path.rs — FilePath (newtype sobre PathBuf) +│ │ ├── track_id.rs — TrackId (newtype opaco sobre u32) +│ │ ├── sync_offset.rs — SyncOffset (i64 ms, nunca f64) +│ │ └── track_language.rs — TrackLanguage (ISO 639-2, validado) +│ └── entities/ +│ ├── mod.rs +│ ├── project.rs — Project (entidade raiz / fonte de verdade) +│ ├── video_file.rs — VideoFile +│ ├── audio_track.rs — AudioTrack +│ ├── subtitle_track.rs — SubtitleTrack +│ ├── media_track_info.rs — MediaTrackInfo + TrackKind +│ ├── mkv_output.rs — MkvOutput +│ └── track.rs — Track (enum: Audio | Subtitle) +│ +├── application/ +│ ├── mod.rs +│ ├── ports/ +│ │ └── mod.rs — MediaInfoPort, MediaProcessorPort, FileSystemPort +│ └── use_cases/ +│ ├── mod.rs +│ ├── load_media_info.rs — popula Project::existing_tracks via ffprobe +│ ├── add_audio_track.rs — adiciona AudioTrack ao Project +│ ├── add_subtitle.rs — adiciona SubtitleTrack ao Project +│ ├── adjust_sync.rs — altera SyncOffset de faixa externa +│ ├── edit_existing_track_sync.rs — altera SyncOffset de faixa existente +│ ├── set_track_language.rs — altera idioma de faixa externa +│ └── generate_output.rs — constrói comando e delega ao MediaProcessorPort +│ +├── adapters/ +│ ├── mod.rs +│ ├── ffmpeg/ +│ │ ├── mod.rs +│ │ ├── command_builder.rs — FfmpegCommandBuilder: Project → Vec +│ │ ├── ffprobe_gateway.rs — FfprobeGateway impl MediaInfoPort +│ │ └── ffmpeg_gateway.rs — FfmpegGateway impl MediaProcessorPort +│ └── filesystem/ +│ ├── mod.rs — RealFileSystem impl FileSystemPort +│ └── file_picker.rs — FilePickerAdapter (diálogos nativos via rfd) +│ +├── infrastructure/ +│ ├── mod.rs +│ └── process/ +│ └── mod.rs — run_ffmpeg_async (tokio), validate_dependencies() +│ +└── ui/ + ├── mod.rs + ├── app.rs — eframe::App; Project como única fonte de verdade + └── components/ + ├── mod.rs + ├── video_selector.rs — seleção do vídeo base + ├── output_selector.rs — seleção do arquivo de saída (.mkv) + ├── existing_track_list.rs — lista faixas detectadas + edição de offset + ├── add_audio_track_form.rs — formulário: áudio externo + ├── add_subtitle_form.rs — formulário: legenda externa + ├── sync_offset_field.rs — campo de atraso em segundos + ├── language_field.rs — campo de idioma ISO 639-2 + └── execution_panel.rs — botão gerar, progresso, erros +``` + +--- + +## Testes Unitários (26/26 passando) + +### Domain — Value Objects +| Teste | Resultado | +|-------|-----------| +| `sync_offset::from_seconds_str_positivo` — `"1.2"` → `SyncOffset(1200)` | ✅ | +| `sync_offset::from_seconds_str_negativo` — `"-0.5"` → `SyncOffset(-500)` | ✅ | +| `sync_offset::from_seconds_str_com_sufixo_s` | ✅ | +| `sync_offset::from_seconds_str_invalido` | ✅ | +| `sync_offset::from_seconds_str_zero` | ✅ | +| `track_language::idioma_valido` — `"por"`, `"eng"` | ✅ | +| `track_language::idioma_invalido_curto` — `"pt"` | ✅ | +| `track_language::idioma_invalido_maiusculo` — `"POR"` | ✅ | +| `track_language::idioma_invalido_longo` — `"port"` | ✅ | + +### Domain — Entities +| Teste | Resultado | +|-------|-----------| +| `project::projeto_valido` | ✅ | +| `project::projeto_invalido_mesmo_caminho` — output == source retorna erro | ✅ | +| `project::track_id_opaco` — TrackId não expõe indexação interna | ✅ | + +### Application — Use Cases (todos com mocks, sem I/O real) +| Teste | Resultado | +|-------|-----------| +| `load_media_info::carrega_faixas_no_projeto` | ✅ | +| `add_audio_track::adiciona_audio_no_projeto` | ✅ | +| `add_subtitle::adiciona_legenda_no_projeto` | ✅ | +| `adjust_sync::ajusta_offset_existente` | ✅ | +| `adjust_sync::erro_se_id_inexistente` | ✅ | +| `edit_existing_track_sync::edita_offset_de_faixa_existente` | ✅ | +| `edit_existing_track_sync::erro_se_faixa_existente_nao_encontrada` | ✅ | +| `set_track_language::altera_idioma_da_faixa` | ✅ | +| `generate_output::sempre_inclui_c_copy` | ✅ | + +### Adapters — FfmpegCommandBuilder +| Teste | Resultado | +|-------|-----------| +| `sempre_contem_c_copy` — invariante RNF-01 | ✅ | +| `output_e_o_ultimo_argumento` | ✅ | +| `itsoffset_formato_correto` — `1200ms` → `"1.200"` | ✅ | +| `mapa_faixa_externa_de_audio` — `map 1:a` | ✅ | +| `metadata_idioma_audio` — `-metadata:s:a:0 language=por` | ✅ | + +--- + +## Dependências (Cargo.toml) + +```toml +eframe = "0.27" +anyhow = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["process", "rt-multi-thread", "macros", "io-util", "sync"] } +rfd = "0.14" +``` + +--- + +## Invariantes Garantidas + +- **`-c copy` sempre presente** — `FfmpegCommandBuilder::build()` emite incondicionalmente (RNF-01) +- **`SyncOffset` nunca usa `f64`** — armazenado como `i64` ms; conversão para string FFmpeg feita exclusivamente no `FfmpegCommandBuilder` +- **`TrackId` é opaco** — não expõe índice interno; mapeamento para `-map N:tipo` feito apenas no adapter +- **`Project` rejeita `output == source`** — validado no construtor +- **Termos FFmpeg nunca aparecem na UI** — a interface usa linguagem do usuário final + +--- + +## O Que Falta (Backlog de Refinamento) + +### Funcional +- [ ] Exibição de faixas externas adicionadas com opção de remover +- [ ] Progresso em tempo real via `run_ffmpeg_async` (infrastructure já implementada, UI usa thread bloqueante por ora) +- [ ] Exibição do comando gerado em modo debug + +### Qualidade +- [ ] Testes de integração com FFmpeg real (requer ffmpeg instalado no CI) +- [ ] Testes de snapshot para o `FfmpegCommandBuilder` com projetos mais complexos +- [ ] Corrigir warnings de `unused` e `dead_code` remanescentes + +### UI +- [ ] Layout responsivo mais refinado (scroll nas listas longas) +- [ ] Persistência do estado do projeto em disco (serde já disponível) +- [ ] Mensagens de erro mais amigáveis na UI + +--- + +## Como Executar + +```bash +# Verificar compilação +cargo check + +# Rodar testes +cargo test + +# Executar a aplicação (requer ffmpeg e ffprobe no PATH) +cargo run +```