feat: atualiza versão do projeto para 1.3 e revisa o plano de desenvolvimento e PRD com novas funcionalidades e requisitos
This commit is contained in:
+114
-7
@@ -1,9 +1,9 @@
|
|||||||
# Plano de Desenvolvimento — Simple Multimedia Track Audio Editor
|
# Plano de Desenvolvimento — Simple Multimedia Track Audio Editor
|
||||||
|
|
||||||
**Versão:** 1.1
|
**Versão:** 1.3
|
||||||
**Data:** 28/02/2026
|
**Data:** 28/02/2026
|
||||||
**Status:** Concluído — todas as fases implementadas, 33 testes passando
|
**Status:** Fases 1–6 concluídas; Fase 7 (Persistência) e Fase 8 (Modo Lote) em planejamento
|
||||||
**Referência:** PRD v1.1
|
**Referência:** PRD v1.2
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ Cada fase deve estar **compilando e com testes passando** antes de avançar para
|
|||||||
### Value Objects (`src/domain/value_objects/`)
|
### Value Objects (`src/domain/value_objects/`)
|
||||||
|
|
||||||
| Tipo | Implementação |
|
| Tipo | Implementação |
|
||||||
|---|---|
|
| --------------- | ----------------------------------------------------------------------------------------------------- |
|
||||||
| `FilePath` | Newtype sobre `PathBuf`; derivar `Clone`, `Debug`, `Serialize`, `Deserialize` |
|
| `FilePath` | Newtype sobre `PathBuf`; derivar `Clone`, `Debug`, `Serialize`, `Deserialize` |
|
||||||
| `TrackId` | Newtype opaco sobre `u32`; derivar `Clone`, `Copy`, `Debug`, `PartialEq`, `Eq`, `Hash` |
|
| `TrackId` | Newtype opaco sobre `u32`; derivar `Clone`, `Copy`, `Debug`, `PartialEq`, `Eq`, `Hash` |
|
||||||
| `SyncOffset` | Newtype sobre `i64` (milissegundos, **nunca `f64`**); implementar método `from_seconds_str` e `as_ms` |
|
| `SyncOffset` | Newtype sobre `i64` (milissegundos, **nunca `f64`**); implementar método `from_seconds_str` e `as_ms` |
|
||||||
@@ -55,7 +55,7 @@ Cada fase deve estar **compilando e com testes passando** antes de avançar para
|
|||||||
### Entities (`src/domain/entities/`)
|
### Entities (`src/domain/entities/`)
|
||||||
|
|
||||||
| Tipo | Campos principais |
|
| Tipo | Campos principais |
|
||||||
|---|---|
|
| ---------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
| `VideoFile` | `path: FilePath` |
|
| `VideoFile` | `path: FilePath` |
|
||||||
| `AudioTrack` | `id: TrackId`, `path: FilePath`, `offset: SyncOffset`, `language: TrackLanguage` |
|
| `AudioTrack` | `id: TrackId`, `path: FilePath`, `offset: SyncOffset`, `language: TrackLanguage` |
|
||||||
| `SubtitleTrack` | `id: TrackId`, `path: FilePath`, `offset: SyncOffset`, `language: TrackLanguage` |
|
| `SubtitleTrack` | `id: TrackId`, `path: FilePath`, `offset: SyncOffset`, `language: TrackLanguage` |
|
||||||
@@ -111,7 +111,7 @@ pub trait FileSystemPort {
|
|||||||
Implementar nesta ordem (dependência crescente):
|
Implementar nesta ordem (dependência crescente):
|
||||||
|
|
||||||
| # | Use Case | Descrição |
|
| # | Use Case | Descrição |
|
||||||
|---|---|---|
|
| --- | ----------------------- | ----------------------------------------------------------- |
|
||||||
| 1 | `LoadMediaInfo` | Usa `MediaInfoPort` para popular `Project::existing_tracks` |
|
| 1 | `LoadMediaInfo` | Usa `MediaInfoPort` para popular `Project::existing_tracks` |
|
||||||
| 2 | `AddAudioTrack` | Adiciona `AudioTrack` externo ao `Project::tracks` |
|
| 2 | `AddAudioTrack` | Adiciona `AudioTrack` externo ao `Project::tracks` |
|
||||||
| 3 | `AddSubtitle` | Adiciona `SubtitleTrack` externo ao `Project::tracks` |
|
| 3 | `AddSubtitle` | Adiciona `SubtitleTrack` externo ao `Project::tracks` |
|
||||||
@@ -140,11 +140,13 @@ Implementar nesta ordem (dependência crescente):
|
|||||||
Responsabilidade única: converter `Project` em `Vec<String>` de argumentos para o FFmpeg.
|
Responsabilidade única: converter `Project` em `Vec<String>` de argumentos para o FFmpeg.
|
||||||
|
|
||||||
Regras invariantes:
|
Regras invariantes:
|
||||||
|
|
||||||
- `-c copy` **sempre presente** (RNF-01 — nunca opcional, nunca configurável)
|
- `-c copy` **sempre presente** (RNF-01 — nunca opcional, nunca configurável)
|
||||||
- `SyncOffset(i64 ms)` → `-itsoffset 1.200` (conversão feita **somente aqui**)
|
- `SyncOffset(i64 ms)` → `-itsoffset 1.200` (conversão feita **somente aqui**)
|
||||||
- `TrackId` → `-map 0:a:N` (mapeamento de índice feito **somente aqui**)
|
- `TrackId` → `-map 0:a:N` (mapeamento de índice feito **somente aqui**)
|
||||||
|
|
||||||
Exemplo de saída esperada:
|
Exemplo de saída esperada:
|
||||||
|
|
||||||
```
|
```
|
||||||
ffmpeg -i input.mkv -itsoffset 1.200 -i audio_pt.aac -map 0:v -map 0:a -map 1:a -c copy -metadata:s:a:1 language=por output.mkv
|
ffmpeg -i input.mkv -itsoffset 1.200 -i audio_pt.aac -map 0:v -map 0:a -map 1:a -c copy -metadata:s:a:1 language=por output.mkv
|
||||||
```
|
```
|
||||||
@@ -156,9 +158,11 @@ ffmpeg -i input.mkv -itsoffset 1.200 -i audio_pt.aac -map 0:v -map 0:a -map 1:a
|
|||||||
#### `FfprobeGateway`
|
#### `FfprobeGateway`
|
||||||
|
|
||||||
Implementa `MediaInfoPort`. Executa:
|
Implementa `MediaInfoPort`. Executa:
|
||||||
|
|
||||||
```
|
```
|
||||||
ffprobe -v quiet -print_format json -show_streams <path>
|
ffprobe -v quiet -print_format json -show_streams <path>
|
||||||
```
|
```
|
||||||
|
|
||||||
e mapeia a saída JSON para `Vec<MediaTrackInfo>`.
|
e mapeia a saída JSON para `Vec<MediaTrackInfo>`.
|
||||||
|
|
||||||
- [x] Implementar parsing de JSON via `serde_json`
|
- [x] Implementar parsing de JSON via `serde_json`
|
||||||
@@ -206,7 +210,7 @@ Implementa `MediaProcessorPort`. Executa o processo real do FFmpeg e captura std
|
|||||||
### Componentes (`src/ui/components/`), em ordem de construção
|
### Componentes (`src/ui/components/`), em ordem de construção
|
||||||
|
|
||||||
| # | Componente | Descrição |
|
| # | Componente | Descrição |
|
||||||
|---|---|---|
|
| --- | ------------------- | -------------------------------------------------------------------------------------- |
|
||||||
| 1 | `VideoSelector` | Seletor de arquivo de vídeo; dispara `LoadMediaInfo` ao confirmar |
|
| 1 | `VideoSelector` | Seletor de arquivo de vídeo; dispara `LoadMediaInfo` ao confirmar |
|
||||||
| 2 | `ExistingTrackList` | Lista faixas detectadas (áudio/legenda); permite editar offset de cada uma |
|
| 2 | `ExistingTrackList` | Lista faixas detectadas (áudio/legenda); permite editar offset de cada uma |
|
||||||
| 3 | `AddAudioTrackForm` | Formulário para adicionar faixa de áudio externa (arquivo, idioma, offset) |
|
| 3 | `AddAudioTrackForm` | Formulário para adicionar faixa de áudio externa (arquivo, idioma, offset) |
|
||||||
@@ -227,6 +231,109 @@ Implementa `MediaProcessorPort`. Executa o processo real do FFmpeg e captura std
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Fase 7 — Persistência do Estado do Projeto
|
||||||
|
|
||||||
|
**Objetivo:** Salvar e carregar o estado do `Project` em disco, permitindo que o usuário retome uma sessão anterior sem precisar reconfigurar tudo.
|
||||||
|
|
||||||
|
**Pré-requisito para a Fase 8:** o modo lote se beneficia diretamente da persistência — um carrinho salvo pode ser retomado após fechar a aplicação.
|
||||||
|
|
||||||
|
**Restrição:** a serialização deve viver exclusivamente na camada de infraestrutura/UI. O `domain/` não deve depender de `serde` diretamente, mas as entidades **já derivam** `Serialize`/`Deserialize` — nenhuma mudança no domínio é necessária.
|
||||||
|
|
||||||
|
### Formato e local do arquivo
|
||||||
|
|
||||||
|
- Formato: **JSON** via `serde_json` (já disponível no `Cargo.toml`)
|
||||||
|
- Local: diretório de configuração do usuário (`~/.config/simple-mkv-editor/session.json` no Linux; `AppData` no Windows)
|
||||||
|
- Um único arquivo de sessão por vez (sobrescreve ao salvar)
|
||||||
|
|
||||||
|
### Quando salvar / carregar
|
||||||
|
|
||||||
|
| Evento | Ação |
|
||||||
|
| ------------------------------- | ---------------------------------------------------------------- |
|
||||||
|
| Usuário clica "Salvar sessão" | Serializa `Project` para disco |
|
||||||
|
| Inicialização da aplicação | Verifica se existe arquivo de sessão; oferece opção de restaurar |
|
||||||
|
| Usuário clica "Carregar sessão" | Desserializa e substitui `App::project` |
|
||||||
|
|
||||||
|
> Salvamento automático fica fora do escopo desta fase para evitar escritas freqüentes em disco.
|
||||||
|
|
||||||
|
### Mudanças necessárias
|
||||||
|
|
||||||
|
| Arquivo | O que muda |
|
||||||
|
| --------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||||
|
| `src/infrastructure/persistence/mod.rs` | Novo módulo: `save_session(project)` e `load_session() -> Result<Project>` |
|
||||||
|
| `src/infrastructure/mod.rs` | Expor `persistence` |
|
||||||
|
| `src/ui/app.rs` | Botões "Salvar sessão" / "Carregar sessão" no header; chamar o módulo de persistência |
|
||||||
|
|
||||||
|
### Tarefas
|
||||||
|
|
||||||
|
- [ ] Criar `src/infrastructure/persistence/mod.rs` com `save_session` e `load_session`
|
||||||
|
- [ ] Resolver caminho do arquivo via `dirs` crate (ou `std::env`)
|
||||||
|
- [ ] Adicionar botões no header da UI
|
||||||
|
- [ ] Tratar erros de desserialização (arquivo corrompido ou versão incompatível) com mensagem amigável
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fase 8 — Modo Lote via Abas
|
||||||
|
|
||||||
|
**Objetivo:** Permitir que o usuário configure e processe múltiplos projetos sequencialmente, um por vez, sem sobrecarga de I/O.
|
||||||
|
|
||||||
|
**Modelo mental — carrinho:** o usuário adiciona itens livremente, na ordem que quiser, e só inicia o processamento quando clicar "Processar Tudo". Itens podem ser removidos do carrinho a qualquer momento antes de processar.
|
||||||
|
|
||||||
|
**Restrição:** Nenhuma camada abaixo da UI (`domain`, `application`, `adapters`, `infrastructure`) precisa ser alterada — `Project` já é a unidade de trabalho reutilizável.
|
||||||
|
|
||||||
|
### Seleção de arquivos
|
||||||
|
|
||||||
|
Igual ao modo Projeto Único — **diálogos nativos via `FilePickerAdapter` / `rfd`**. Nenhum path é digitado manualmente pelo usuário. O formulário de cada item do lote expõe os mesmos botões "Escolher..." já existentes.
|
||||||
|
|
||||||
|
### Formulário — Opção A (inline na aba Lote)
|
||||||
|
|
||||||
|
O formulário de configuração de um novo item **expande inline** na própria aba Lote, abaixo da lista. Não altera nem reutiliza a aba Projeto Único. Ao confirmar, o item é adicionado ao carrinho e o formulário é limpo.
|
||||||
|
|
||||||
|
```
|
||||||
|
[ + Adicionar item ] ← clique expande o formulário abaixo
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Vídeo: [ep03.mkv ] [Escolher...] │
|
||||||
|
│ Saída: [ep03_pt.mkv ] [Escolher...] │
|
||||||
|
│ Faixas: [+ Áudio] [+ Legenda] │
|
||||||
|
│ [Cancelar] [Adicionar ✓] │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mudanças na UI
|
||||||
|
|
||||||
|
| Arquivo | O que muda |
|
||||||
|
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `src/ui/app.rs` | Adicionar `enum ActiveTab { Single, Batch }` e `active_tab: ActiveTab`; adicionar `batch_items: Vec<BatchItem>` |
|
||||||
|
| `src/ui/components/batch_panel.rs` | Novo componente: carrinho de itens, formulário inline de adição, botão "Processar Tudo" |
|
||||||
|
| `src/ui/components/mod.rs` | Expor `batch_panel` |
|
||||||
|
|
||||||
|
### Estrutura de `BatchItem`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct BatchItem {
|
||||||
|
project: Project,
|
||||||
|
state: ExecutionState, // reutiliza o enum já existente
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fluxo de execução do lote
|
||||||
|
|
||||||
|
1. Usuário adiciona itens ao carrinho (zero ou mais), configura cada um com diálogo nativo
|
||||||
|
2. Itens podem ser removidos do carrinho enquanto nenhum processamento estiver em curso
|
||||||
|
3. Ao clicar "Processar Tudo", o lote é bloqueado (sem mais adições/remoções)
|
||||||
|
4. Para cada item em ordem: `state → Running` → `run_ffmpeg_async` → aguarda `BackgroundMsg::Done | Error` → `state → Success | Error` → próximo item
|
||||||
|
5. "Cancelar" interrompe o item atual via `cancel_tx`; os demais permanecem no carrinho com estado `Idle`
|
||||||
|
|
||||||
|
### Tarefas
|
||||||
|
|
||||||
|
- [ ] Criar `enum ActiveTab` e barra de abas no `update()` de `App`
|
||||||
|
- [ ] Criar `BatchItem` e `batch_items: Vec<BatchItem>` em `App`
|
||||||
|
- [ ] Criar componente `BatchPanel` com carrinho e formulário inline (Opção A)
|
||||||
|
- [ ] Implementar loop sequencial de execução em `App::process_batch()`
|
||||||
|
- [ ] Exibir estado individual por item (`Aguardando | Processando | Concluído | Erro`)
|
||||||
|
- [ ] Bloquear adição/remoção de itens durante processamento
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Critérios de Conclusão (v1.0)
|
## Critérios de Conclusão (v1.0)
|
||||||
|
|
||||||
Alinhados com o PRD seção 12:
|
Alinhados com o PRD seção 12:
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# PRD — Simple Multimedia Track Audio Editor
|
# PRD — Simple Multimedia Track Audio Editor
|
||||||
|
|
||||||
**Versão:** 1.1
|
**Versão:** 1.2
|
||||||
**Data:** 28/02/2026
|
**Data:** 28/02/2026
|
||||||
**Status:** Em desenvolvimento
|
**Status:** Em desenvolvimento
|
||||||
**Revisão:** Incorporados feedbacks de arquitetura — modelo de sessão, ffprobe, tipos fortes e regra de no-reencode
|
**Revisão:** v1.2 — Adicionado RF-09 (Modo Lote); batch promovido de backlog para requisito planejado
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -105,6 +105,17 @@ As ferramentas existentes são complexas (ex: interface direta do FFmpeg via CLI
|
|||||||
- A faixa original é remapeada com o novo timestamp via `ffmpeg -itsoffset` combinado com `-map`
|
- A faixa original é remapeada com o novo timestamp via `ffmpeg -itsoffset` combinado com `-map`
|
||||||
- Útil para corrigir atrasos em faixas de dublagem ou legenda já incorporadas ao arquivo
|
- Útil para corrigir atrasos em faixas de dublagem ou legenda já incorporadas ao arquivo
|
||||||
|
|
||||||
|
### RF-09 — Processamento em lote
|
||||||
|
|
||||||
|
- A interface oferece duas abas: **Projeto Único** (fluxo atual) e **Lote**
|
||||||
|
- A aba Lote funciona como um **carrinho**: o usuário adiciona, revisa e remove itens livremente antes de processar
|
||||||
|
- Cada item é um projeto completo e independente (vídeo, faixas e saída próprios), configurado via **formulário inline** na própria aba Lote — sem navegação para outra tela
|
||||||
|
- A seleção de arquivos usa **diálogos nativos** (igual ao modo Projeto Único) — nenhum path é digitado manualmente
|
||||||
|
- O processamento é **sequencial** — um projeto por vez, sem paralelismo, para evitar sobrecarga de I/O
|
||||||
|
- Durante o processamento, o carrinho é bloqueado: não é possível adicionar nem remover itens
|
||||||
|
- Cada item exibe seu estado individual: `Aguardando | Processando | Concluído | Erro`
|
||||||
|
- É possível cancelar o item em execução; os demais permanecem no carrinho com estado `Aguardando`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Requisitos Não Funcionais
|
## 7. Requisitos Não Funcionais
|
||||||
@@ -159,7 +170,7 @@ Arquivo de saída (.mkv)
|
|||||||
### Stack
|
### Stack
|
||||||
|
|
||||||
| Camada | Tecnologia | Motivo |
|
| Camada | Tecnologia | Motivo |
|
||||||
|--------------|-------------------------------|---------------------------------------------|
|
| ------------ | ------------------------------ | ------------------------------------ |
|
||||||
| Interface | `egui` / `eframe` | Nativo, simples, multiplataforma |
|
| Interface | `egui` / `eframe` | Nativo, simples, multiplataforma |
|
||||||
| Backend | Rust (`std::process::Command`) | Estável, sem dependências externas |
|
| Backend | Rust (`std::process::Command`) | Estável, sem dependências externas |
|
||||||
| Erros | `anyhow` | Propagação de erros simplificada |
|
| Erros | `anyhow` | Propagação de erros simplificada |
|
||||||
@@ -209,7 +220,7 @@ As dependências sempre apontam de fora para dentro. Camadas internas não conhe
|
|||||||
Contém as entidades e objetos de valor do negócio. Não depende de nada externo.
|
Contém as entidades e objetos de valor do negócio. Não depende de nada externo.
|
||||||
|
|
||||||
| Tipo | Exemplos |
|
| Tipo | Exemplos |
|
||||||
|----------------|-----------------------------------------------------------------------------------|
|
| ------------- | ------------------------------------------------------------------------------------ |
|
||||||
| Entities | `Project`, `VideoFile`, `AudioTrack`, `SubtitleTrack`, `MkvOutput`, `MediaTrackInfo` |
|
| Entities | `Project`, `VideoFile`, `AudioTrack`, `SubtitleTrack`, `MkvOutput`, `MediaTrackInfo` |
|
||||||
| Value Objects | `TrackId`, `SyncOffset`, `TrackLanguage`, `FilePath` |
|
| Value Objects | `TrackId`, `SyncOffset`, `TrackLanguage`, `FilePath` |
|
||||||
|
|
||||||
@@ -233,7 +244,7 @@ Contém as regras de negócio da aplicação. Orquestra as entidades do domínio
|
|||||||
Define traits (ports) que as camadas externas devem implementar.
|
Define traits (ports) que as camadas externas devem implementar.
|
||||||
|
|
||||||
| Tipo | Exemplos |
|
| Tipo | Exemplos |
|
||||||
|------------|-------------------------------------------------------------------------------------------------------------------|
|
| --------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Use Cases | `LoadMediaInfo`, `AddAudioTrack`, `AddSubtitle`, `AdjustSync`, `EditExistingTrackSync`, `SetTrackLanguage`, `GenerateOutput` |
|
| Use Cases | `LoadMediaInfo`, `AddAudioTrack`, `AddSubtitle`, `AdjustSync`, `EditExistingTrackSync`, `SetTrackLanguage`, `GenerateOutput` |
|
||||||
| Ports | `MediaProcessorPort`, `MediaInfoPort`, `FileSystemPort` |
|
| Ports | `MediaProcessorPort`, `MediaInfoPort`, `FileSystemPort` |
|
||||||
|
|
||||||
@@ -254,7 +265,7 @@ Define traits (ports) que as camadas externas devem implementar.
|
|||||||
Implementam os ports definidos na camada de Application. Traduzem dados entre o domínio e o mundo externo.
|
Implementam os ports definidos na camada de Application. Traduzem dados entre o domínio e o mundo externo.
|
||||||
|
|
||||||
| Tipo | Exemplos |
|
| Tipo | Exemplos |
|
||||||
|--------------------|---------------------------------------------------------------------------|
|
| ------------ | --------------------------------------------------------- |
|
||||||
| Gateway | `FfmpegCommandBuilder`, `FfmpegGateway`, `FfprobeGateway` |
|
| Gateway | `FfmpegCommandBuilder`, `FfmpegGateway`, `FfprobeGateway` |
|
||||||
| Presenter | `ErrorPresenter` (formata stderr do FFmpeg) |
|
| Presenter | `ErrorPresenter` (formata stderr do FFmpeg) |
|
||||||
| File Adapter | `FilePickerAdapter` |
|
| File Adapter | `FilePickerAdapter` |
|
||||||
@@ -268,7 +279,7 @@ Implementam os ports definidos na camada de Application. Traduzem dados entre o
|
|||||||
Camada mais externa. Contém o framework de UI e a execução real de processos.
|
Camada mais externa. Contém o framework de UI e a execução real de processos.
|
||||||
|
|
||||||
| Tipo | Exemplos |
|
| Tipo | Exemplos |
|
||||||
|--------------|-------------------------------------------------------|
|
| ---------- | ------------------------------------------------------ |
|
||||||
| UI | `App` (eframe), componentes egui |
|
| UI | `App` (eframe), componentes egui |
|
||||||
| Process | Execução de `std::process::Command` / `tokio::process` |
|
| Process | Execução de `std::process::Command` / `tokio::process` |
|
||||||
| Filesystem | Leitura e escrita de arquivos |
|
| Filesystem | Leitura e escrita de arquivos |
|
||||||
@@ -301,7 +312,7 @@ src/
|
|||||||
## 10. Débitos Técnicos
|
## 10. Débitos Técnicos
|
||||||
|
|
||||||
| ID | Descrição | Impacto | Mitigação |
|
| ID | Descrição | Impacto | Mitigação |
|
||||||
|-----|--------------------------------------|------------------|--------------------------------------|
|
| ----- | ----------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------ |
|
||||||
| DT-01 | FFmpeg não embutido | Usuário precisa instalar | Distribuir junto com a aplicação |
|
| DT-01 | FFmpeg não embutido | Usuário precisa instalar | Distribuir junto com a aplicação |
|
||||||
| DT-02 | Dependência de processo externo | Menor controle interno | Encapsular via módulo de serviço |
|
| DT-02 | Dependência de processo externo | Menor controle interno | Encapsular via módulo de serviço |
|
||||||
| DT-03 | Parsing de erros do FFmpeg (stderr) | Necessário tratamento manual | Parsear saída e exibir mensagem amigável |
|
| DT-03 | Parsing de erros do FFmpeg (stderr) | Necessário tratamento manual | Parsear saída e exibir mensagem amigável |
|
||||||
@@ -318,7 +329,6 @@ src/
|
|||||||
- Seleção de faixa padrão no container MKV
|
- Seleção de faixa padrão no container MKV
|
||||||
- Remoção de faixas existentes do arquivo original
|
- Remoção de faixas existentes do arquivo original
|
||||||
- Suporte a perfis de configuração salvos
|
- Suporte a perfis de configuração salvos
|
||||||
- Modo de processamento em lote (batch)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -345,17 +355,17 @@ src/
|
|||||||
|
|
||||||
O FFmpeg é uma ferramenta de poder absurdo — e opacidade equivalente. O usuário não entende `track index`, `stream mapping` ou `itsoffset`. O usuário entende:
|
O FFmpeg é uma ferramenta de poder absurdo — e opacidade equivalente. O usuário não entende `track index`, `stream mapping` ou `itsoffset`. O usuário entende:
|
||||||
|
|
||||||
- *"Áudio em português está 1.2 segundos atrasado"*
|
- _"Áudio em português está 1.2 segundos atrasado"_
|
||||||
- *"Quero adicionar a legenda em inglês"*
|
- _"Quero adicionar a legenda em inglês"_
|
||||||
- *"Gerar o arquivo final"*
|
- _"Gerar o arquivo final"_
|
||||||
|
|
||||||
Toda decisão de UX deve partir dessa perspectiva. Termos técnicos do FFmpeg nunca devem aparecer na interface.
|
Toda decisão de UX deve partir dessa perspectiva. Termos técnicos do FFmpeg nunca devem aparecer na interface.
|
||||||
|
|
||||||
| FFmpeg (interno) | Interface (usuário) |
|
| FFmpeg (interno) | Interface (usuário) |
|
||||||
|-----------------------|--------------------------------------|
|
| -------------------- | ------------------------------ |
|
||||||
| `-itsoffset -1200ms` | "Adiantar 1.2s" |
|
| `-itsoffset -1200ms` | "Adiantar 1.2s" |
|
||||||
| `stream 0:a:1` | "Faixa de áudio 2 — Português" |
|
| `stream 0:a:1` | "Faixa de áudio 2 — Português" |
|
||||||
| `-c copy` | *(invisível — nunca exposto)* |
|
| `-c copy` | _(invisível — nunca exposto)_ |
|
||||||
| `ffprobe output` | Lista de faixas detectadas |
|
| `ffprobe output` | Lista de faixas detectadas |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -373,6 +383,7 @@ O MKV (Matroska) é estruturalmente um banco de dados multimídia. Suas operaç
|
|||||||
O arquivo de saída não é uma criação nova — é uma nova **visão** do container original com faixas adicionadas e metadados ajustados, sem tocar nos bytes de mídia.
|
O arquivo de saída não é uma criação nova — é uma nova **visão** do container original com faixas adicionadas e metadados ajustados, sem tocar nos bytes de mídia.
|
||||||
|
|
||||||
Esse modelo mental simplifica a implementação:
|
Esse modelo mental simplifica a implementação:
|
||||||
|
|
||||||
- Sem buffers de vídeo
|
- Sem buffers de vídeo
|
||||||
- Sem pipelines de transcoding
|
- Sem pipelines de transcoding
|
||||||
- Apenas mapeamento de streams e metadados
|
- Apenas mapeamento de streams e metadados
|
||||||
|
|||||||
+20
-8
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Data:** 28/02/2026
|
**Data:** 28/02/2026
|
||||||
**Status:** Fases 1–6 concluídas + exportação de faixa + exibição/remoção de faixas adicionadas + layout responsivo + progresso em tempo real + cancelamento de geração — compilando, 33 testes passando, zero warnings do projeto, aplicação executável
|
**Status:** Fases 1–6 concluídas + exportação de faixa + exibição/remoção de faixas adicionadas + layout responsivo + progresso em tempo real + cancelamento de geração — compilando, 33 testes passando, zero warnings do projeto, aplicação executável
|
||||||
**Referência:** DEVELOPMENT_PLAN.md v1.1
|
**Referência:** DEVELOPMENT_PLAN.md v1.3
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ Todas as 6 fases do plano de desenvolvimento foram implementadas. O projeto comp
|
|||||||
Todos os 14 warnings de `unused`/`dead_code` foram resolvidos: assignment morto em `command_builder.rs` removido, lifetime explícito em `FilePath::to_string_lossy` corrigido, e APIs arquiteturais não chamadas pela UI suprimidas com `#[allow(dead_code)]`.
|
Todos os 14 warnings de `unused`/`dead_code` foram resolvidos: assignment morto em `command_builder.rs` removido, lifetime explícito em `FilePath::to_string_lossy` corrigido, e APIs arquiteturais não chamadas pela UI suprimidas com `#[allow(dead_code)]`.
|
||||||
|
|
||||||
Após a conclusão das fases, foram implementadas funcionalidades adicionais:
|
Após a conclusão das fases, foram implementadas funcionalidades adicionais:
|
||||||
|
|
||||||
- Exportação de faixa de áudio ou legenda diretamente da lista de faixas existentes
|
- Exportação de faixa de áudio ou legenda diretamente da lista de faixas existentes
|
||||||
- Exibição das faixas externas adicionadas com opção de remoção individual
|
- Exibição das faixas externas adicionadas com opção de remoção individual
|
||||||
- Layout responsivo com painéis fixos (cabeçalho, rodapé, barra lateral) e área central com scroll
|
- Layout responsivo com painéis fixos (cabeçalho, rodapé, barra lateral) e área central com scroll
|
||||||
@@ -25,7 +26,7 @@ Após a conclusão das fases, foram implementadas funcionalidades adicionais:
|
|||||||
## Status por Fase
|
## Status por Fase
|
||||||
|
|
||||||
| Fase | Descrição | Status |
|
| Fase | Descrição | Status |
|
||||||
|------|----------------------------------|-------------|
|
| ---- | ------------------------------- | ------------ |
|
||||||
| 1 | Setup do projeto | ✅ Concluída |
|
| 1 | Setup do projeto | ✅ Concluída |
|
||||||
| 2 | Domain (núcleo puro) | ✅ Concluída |
|
| 2 | Domain (núcleo puro) | ✅ Concluída |
|
||||||
| 3 | Application (use cases + ports) | ✅ Concluída |
|
| 3 | Application (use cases + ports) | ✅ Concluída |
|
||||||
@@ -110,8 +111,9 @@ src/
|
|||||||
## Testes Unitários (33/33 passando)
|
## Testes Unitários (33/33 passando)
|
||||||
|
|
||||||
### Domain — Value Objects
|
### Domain — Value Objects
|
||||||
|
|
||||||
| Teste | Resultado |
|
| Teste | Resultado |
|
||||||
|-------|-----------|
|
| ------------------------------------------------------------------------ | --------- |
|
||||||
| `sync_offset::from_seconds_str_positivo` — `"1.2"` → `SyncOffset(1200)` | ✅ |
|
| `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_negativo` — `"-0.5"` → `SyncOffset(-500)` | ✅ |
|
||||||
| `sync_offset::from_seconds_str_com_sufixo_s` | ✅ |
|
| `sync_offset::from_seconds_str_com_sufixo_s` | ✅ |
|
||||||
@@ -123,15 +125,17 @@ src/
|
|||||||
| `track_language::idioma_invalido_longo` — `"port"` | ✅ |
|
| `track_language::idioma_invalido_longo` — `"port"` | ✅ |
|
||||||
|
|
||||||
### Domain — Entities
|
### Domain — Entities
|
||||||
|
|
||||||
| Teste | Resultado |
|
| Teste | Resultado |
|
||||||
|-------|-----------|
|
| ------------------------------------------------------------------------- | --------- |
|
||||||
| `project::projeto_valido` | ✅ |
|
| `project::projeto_valido` | ✅ |
|
||||||
| `project::projeto_invalido_mesmo_caminho` — output == source retorna erro | ✅ |
|
| `project::projeto_invalido_mesmo_caminho` — output == source retorna erro | ✅ |
|
||||||
| `project::track_id_opaco` — TrackId não expõe indexação interna | ✅ |
|
| `project::track_id_opaco` — TrackId não expõe indexação interna | ✅ |
|
||||||
|
|
||||||
### Application — Use Cases (todos com mocks, sem I/O real)
|
### Application — Use Cases (todos com mocks, sem I/O real)
|
||||||
|
|
||||||
| Teste | Resultado |
|
| Teste | Resultado |
|
||||||
|-------|-----------|
|
| ------------------------------------------------------------------ | --------- |
|
||||||
| `load_media_info::carrega_faixas_no_projeto` | ✅ |
|
| `load_media_info::carrega_faixas_no_projeto` | ✅ |
|
||||||
| `add_audio_track::adiciona_audio_no_projeto` | ✅ |
|
| `add_audio_track::adiciona_audio_no_projeto` | ✅ |
|
||||||
| `add_subtitle::adiciona_legenda_no_projeto` | ✅ |
|
| `add_subtitle::adiciona_legenda_no_projeto` | ✅ |
|
||||||
@@ -143,8 +147,9 @@ src/
|
|||||||
| `generate_output::sempre_inclui_c_copy` | ✅ |
|
| `generate_output::sempre_inclui_c_copy` | ✅ |
|
||||||
|
|
||||||
### Adapters — FfmpegCommandBuilder
|
### Adapters — FfmpegCommandBuilder
|
||||||
|
|
||||||
| Teste | Resultado |
|
| Teste | Resultado |
|
||||||
|-------|-----------|
|
| -------------------------------------------------------- | --------- |
|
||||||
| `sempre_contem_c_copy` — invariante RNF-01 | ✅ |
|
| `sempre_contem_c_copy` — invariante RNF-01 | ✅ |
|
||||||
| `output_e_o_ultimo_argumento` | ✅ |
|
| `output_e_o_ultimo_argumento` | ✅ |
|
||||||
| `itsoffset_formato_correto` — `1200ms` → `"1.200"` | ✅ |
|
| `itsoffset_formato_correto` — `1200ms` → `"1.200"` | ✅ |
|
||||||
@@ -152,8 +157,9 @@ src/
|
|||||||
| `metadata_idioma_audio` — `-metadata:s:a:0 language=por` | ✅ |
|
| `metadata_idioma_audio` — `-metadata:s:a:0 language=por` | ✅ |
|
||||||
|
|
||||||
### Application — ExportTrack
|
### Application — ExportTrack
|
||||||
|
|
||||||
| Teste | Resultado |
|
| Teste | Resultado |
|
||||||
|-------|-----------|
|
| ----------------------------------------------------------------------------------------- | --------- |
|
||||||
| `export_track::exporta_audio_com_c_a_copy` — `-c:a copy` presente, sem `-c copy` genérico | ✅ |
|
| `export_track::exporta_audio_com_c_a_copy` — `-c:a copy` presente, sem `-c copy` genérico | ✅ |
|
||||||
| `export_track::exporta_audio_mapeia_stream_correto` — `0:stream_index` correto | ✅ |
|
| `export_track::exporta_audio_mapeia_stream_correto` — `0:stream_index` correto | ✅ |
|
||||||
| `export_track::exporta_legenda_sem_c_copy` — nenhuma forma de `-c` para legendas | ✅ |
|
| `export_track::exporta_legenda_sem_c_copy` — nenhuma forma de `-c` para legendas | ✅ |
|
||||||
@@ -161,8 +167,9 @@ src/
|
|||||||
| `export_track::rejeita_faixa_de_video` — retorna erro para `TrackKind::Video` | ✅ |
|
| `export_track::rejeita_faixa_de_video` — retorna erro para `TrackKind::Video` | ✅ |
|
||||||
|
|
||||||
### Application — RemoveTrack
|
### Application — RemoveTrack
|
||||||
|
|
||||||
| Teste | Resultado |
|
| Teste | Resultado |
|
||||||
|-------|-----------|
|
| ---------------------------------------------------------------------- | --------- |
|
||||||
| `remove_track::remove_faixa_existente` — faixa removida do Vec | ✅ |
|
| `remove_track::remove_faixa_existente` — faixa removida do Vec | ✅ |
|
||||||
| `remove_track::erro_se_id_inexistente` — retorna erro se id não existe | ✅ |
|
| `remove_track::erro_se_id_inexistente` — retorna erro se id não existe | ✅ |
|
||||||
|
|
||||||
@@ -195,12 +202,16 @@ rfd = "0.14"
|
|||||||
## O Que Falta (Backlog de Refinamento)
|
## O Que Falta (Backlog de Refinamento)
|
||||||
|
|
||||||
### Funcional
|
### Funcional
|
||||||
|
|
||||||
- [x] Exportar faixa de áudio ou legenda existente para arquivo separado
|
- [x] Exportar faixa de áudio ou legenda existente para arquivo separado
|
||||||
- [x] Exibição de faixas externas adicionadas com opção de remover
|
- [x] Exibição de faixas externas adicionadas com opção de remover
|
||||||
- [x] Progresso em tempo real via `run_ffmpeg_async` — `run_ffmpeg_async` usa `std::sync::mpsc::Sender<String>`; `start_generation` cria tokio Runtime + thread encaminhadora; cada linha de stderr do FFmpeg aparece no log antes do término
|
- [x] Progresso em tempo real via `run_ffmpeg_async` — `run_ffmpeg_async` usa `std::sync::mpsc::Sender<String>`; `start_generation` cria tokio Runtime + thread encaminhadora; cada linha de stderr do FFmpeg aparece no log antes do término
|
||||||
- [x] Cancelamento de geração — botão "Cancelar" visível durante `Running`; sinal via `tokio::sync::oneshot`; `child.kill().await` no `tokio::select!`; estado `ExecutionState::Cancelled` exibido em amarelo
|
- [x] Cancelamento de geração — botão "Cancelar" visível durante `Running`; sinal via `tokio::sync::oneshot`; `child.kill().await` no `tokio::select!`; estado `ExecutionState::Cancelled` exibido em amarelo
|
||||||
|
- [ ] **Fase 7 — Persistência do Estado** — salvar/carregar `Project` em JSON (`~/.config/...`); botões "Salvar sessão" / "Carregar sessão" no header; novo módulo `infrastructure/persistence` — ver DEVELOPMENT_PLAN.md Fase 7
|
||||||
|
- [ ] **Fase 8 — Modo Lote via Abas** — navegação por abas (`Projeto Único` / `Lote`); `Vec<BatchItem>` em `App`; processamento sequencial (um por vez); novo componente `BatchPanel` — ver DEVELOPMENT_PLAN.md Fase 8
|
||||||
|
|
||||||
### Qualidade
|
### Qualidade
|
||||||
|
|
||||||
- [x] Corrigir 14 warnings de `unused`/`dead_code` identificados por `cargo build`:
|
- [x] Corrigir 14 warnings de `unused`/`dead_code` identificados por `cargo build`:
|
||||||
- `next_input_idx += 1` removido do loop externo em `command_builder.rs` (assignment nunca lido)
|
- `next_input_idx += 1` removido do loop externo em `command_builder.rs` (assignment nunca lido)
|
||||||
- Lifetime explícito `Cow<'_, str>` adicionado em `FilePath::to_string_lossy`
|
- Lifetime explícito `Cow<'_, str>` adicionado em `FilePath::to_string_lossy`
|
||||||
@@ -210,6 +221,7 @@ rfd = "0.14"
|
|||||||
- [ ] Testes de snapshot para o `FfmpegCommandBuilder` com projetos mais complexos
|
- [ ] Testes de snapshot para o `FfmpegCommandBuilder` com projetos mais complexos
|
||||||
|
|
||||||
### UI
|
### UI
|
||||||
|
|
||||||
- [x] Layout responsivo mais refinado
|
- [x] Layout responsivo mais refinado
|
||||||
- [ ] Exibição do comando FFmpeg gerado em modo debug (ex: painel colapsável na `ExecutionPanel`)
|
- [ ] Exibição do comando FFmpeg gerado em modo debug (ex: painel colapsável na `ExecutionPanel`)
|
||||||
- [ ] Persistência do estado do projeto em disco (`serde` já disponível)
|
- [ ] Persistência do estado do projeto em disco (`serde` já disponível)
|
||||||
|
|||||||
Reference in New Issue
Block a user