487 lines
28 KiB
Markdown
487 lines
28 KiB
Markdown
# PRD — Simple Multimedia Track Audio Editor
|
||
|
||
**Versão:** 1.5
|
||
**Data:** 02/03/2026
|
||
**Status:** v1.0 funcional — Fases 1–9 concluídas; 72 testes passando; aplicação executável
|
||
**Revisão:** v1.5 — Adicionado RF-16 (Reordenar faixas externas)
|
||
|
||
---
|
||
|
||
## 1. Visão do Produto
|
||
|
||
Um editor simples de faixas multimídia que permite ao usuário combinar vídeo, áudio e legendas em um único arquivo de saída no formato MKV, sem realizar reencoding — apenas mux e ajuste de timestamps.
|
||
|
||
---
|
||
|
||
## 2. Problema
|
||
|
||
Usuários domésticos e de automação precisam de uma ferramenta simples para:
|
||
|
||
- Adicionar faixas de áudio alternativas a um vídeo (ex: dublagem, comentários)
|
||
- Adicionar legendas
|
||
- Ajustar a sincronização entre faixas
|
||
- Gerar um arquivo final organizado e compatível
|
||
|
||
As ferramentas existentes são complexas (ex: interface direta do FFmpeg via CLI) ou pesadas demais para esse caso de uso.
|
||
|
||
---
|
||
|
||
## 3. Objetivos
|
||
|
||
- Fornecer uma interface gráfica simples e intuitiva
|
||
- Abstrair a complexidade do FFmpeg para o usuário final
|
||
- Gerar arquivos MKV com múltiplas faixas de áudio e legendas
|
||
- Permitir ajuste de sincronização por faixa
|
||
- Não realizar reencoding (apenas mux)
|
||
- Ser multiplataforma (Linux, Windows, macOS)
|
||
|
||
---
|
||
|
||
## 4. Fora do Escopo (v1.0)
|
||
|
||
- Reencoding / transcodificação de vídeo ou áudio
|
||
- Preview de vídeo embutido
|
||
- Detecção automática de sincronização
|
||
- Edição de corte ou splice de vídeo
|
||
- Salvamento automático em disco (sem intervenção do usuário)
|
||
|
||
---
|
||
|
||
## 5. Usuários-Alvo
|
||
|
||
- Usuário doméstico que deseja adicionar dublagem ou áudio alternativo a vídeos
|
||
- Operadores de automação de mídia que precisam empacotar faixas em lote
|
||
- Usuários técnicos confortáveis com instalação de dependências (FFmpeg)
|
||
|
||
---
|
||
|
||
## 6. Requisitos Funcionais
|
||
|
||
### RF-01 — Adicionar faixa de áudio
|
||
|
||
- O usuário pode selecionar um arquivo de áudio externo
|
||
- O áudio é mapeado ao container de saída via `ffmpeg -map`
|
||
- Suporta múltiplas faixas de áudio
|
||
|
||
### RF-02 — Adicionar legenda
|
||
|
||
- O usuário pode selecionar um arquivo de legenda (ex: `.srt`, `.ass`)
|
||
- A legenda é mapeada ao container via `ffmpeg -map`
|
||
- Suporta múltiplas faixas de legenda
|
||
|
||
### RF-03 — Ajustar sincronização
|
||
|
||
- O usuário pode definir um offset por faixa, expresso em **segundos com uma casa decimal** na interface (ex: `1.2s`, `-0.5s`)
|
||
- Internamente armazenado como `SyncOffset(i64)` em milissegundos — sem ponto flutuante
|
||
- Implementado via `ffmpeg -itsoffset`; a conversão ms→string é feita exclusivamente no `FfmpegCommandBuilder`
|
||
- Aceita valores positivos e negativos
|
||
|
||
### RF-04 — Definir idioma da faixa
|
||
|
||
- O usuário pode atribuir um código de idioma a cada faixa (ex: `por`, `eng`)
|
||
- Implementado via `ffmpeg -metadata:s`
|
||
|
||
### RF-10 — Definir nome da faixa
|
||
|
||
- O usuário pode preencher um título livre para cada faixa externa adicionada (ex: `Português`, `Comentários`)
|
||
- O campo aceita string vazia, que instrui o `FfmpegCommandBuilder` a emitir `title=` limpando qualquer título herdado do arquivo fonte
|
||
- Objetivo principal: sobrescrever títulos gerados automaticamente por ferramentas externas (ex: `"ISO Media file produced by Google Inc."` em arquivos M4A do Google) que confundem players como Jellyfin mobile
|
||
- Implementado via `ffmpeg -metadata:s:a:{idx} title=` ou `-metadata:s:s:{idx} title=`; sempre emitido, mesmo quando vazio
|
||
|
||
### RF-11 — Definir faixa padrão
|
||
|
||
- O usuário pode marcar uma faixa externa como “faixa padrão” do container MKV
|
||
- Quando marcada, o `FfmpegCommandBuilder` emite `-disposition:a/s:{idx} default` para a faixa selecionada e `-disposition:a/s:{orig_idx} 0` para todas as faixas existentes do mesmo tipo, removendo o flag `default=1` que o arquivo fonte carrega
|
||
- Objetivo: garantir que players como Jellyfin selecionem automaticamente a faixa preferida sem depender de configuração manual no side do player
|
||
- Implementado via `ffmpeg -disposition:a:{idx} default`
|
||
|
||
### RF-05 — Selecionar arquivo de vídeo base
|
||
|
||
- O usuário seleciona o arquivo de vídeo de entrada
|
||
- O stream de vídeo é preservado sem modificação
|
||
|
||
### RF-06 — Gerar arquivo de saída
|
||
|
||
- O usuário define o caminho e nome do arquivo de saída
|
||
- O container de saída é sempre MKV
|
||
- A geração é executada via `ffmpeg` em processo externo
|
||
|
||
### RF-07 — Feedback de execução
|
||
|
||
- A interface exibe o progresso e resultado da operação
|
||
- Erros do FFmpeg (via stderr) são exibidos ao usuário de forma legível
|
||
|
||
### RF-08 — Editar faixas existentes de áudio e legenda
|
||
|
||
- O usuário pode selecionar e editar faixas de áudio ou legenda já presentes no arquivo de vídeo de entrada
|
||
- É possível ajustar o offset de sincronização de uma faixa existente, sem necessidade de adicionar uma nova faixa
|
||
- 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
|
||
|
||
### 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 | ⊸ Cancelado | ✗ Erro`
|
||
- É possível cancelar o item em execução; os demais permanecem no carrinho com estado `Aguardando`
|
||
- O dispatch FFmpeg/mkvmerge por item é automático: se qualquer faixa do item tiver `drift_scale ≠ 1.0`, o motor mkvmerge é usado
|
||
|
||
### RF-12 — Correção de drift progressivo de sincronização
|
||
|
||
- O usuário pode especificar um fator de velocidade original da faixa em porcentagem (ex: `99.983%`)
|
||
- Internamente representado como `drift_scale: f64` em cada faixa (`AudioTrack`, `SubtitleTrack`, `MediaTrackInfo`)
|
||
- Implementado via `mkvmerge --sync TID:DELAY,NUM/DEN` — aplica escala de timestamps no nível do container, sem reencoding
|
||
- **Dispatch automático:** se qualquer faixa tiver `scale ≠ 1.0`, o projeto inteiro usa o pipeline `mkvmerge` em vez do FFmpeg
|
||
- Campo exibido na interface como **"Velocidade original (%)"** — jamais expõe termos técnicos como `scale`, `drift` ou `mkvmerge`
|
||
- O campo é desabilitado com tooltip explicativo quando `mkvmerge` não está disponível no `PATH`
|
||
- Funciona tanto no modo Projeto Único quanto no modo Lote
|
||
- Preserva a invariante RNF-01: nenhum byte de mídia é reprocessado
|
||
|
||
### RF-13 — Exportar faixa existente
|
||
|
||
- A partir da lista de faixas detectadas pelo `ffprobe`, o usuário pode exportar qualquer faixa de áudio ou legenda para um arquivo separado
|
||
- O formato do arquivo de saída é inferido a partir do codec da faixa (ex: AAC → `.aac`, SRT → `.srt`)
|
||
- Implementado via `FfmpegCommandBuilder::build_export()` com `-c:a copy` para áudio; legendas omitem `-c` (conversão de container de texto, sem processamento de mídia)
|
||
- O diálogo de salvamento usa filtro por extensão correspondente ao codec detectado
|
||
|
||
### RF-14 — Persistência de sessão
|
||
|
||
- O usuário pode salvar o estado completo do projeto em disco com o botão **"💾 Salvar sessão"**
|
||
- O estado é restaurado com o botão **"📂 Carregar sessão"**
|
||
- Formato: JSON via `serde_json`; local: `~/.config/simple-mkv-editor/session.json` (Linux) / `AppData` (Windows)
|
||
- Feedback visual no header: indicador verde (sucesso) ou vermelho (erro) com botão ✕ para dispensar
|
||
- Erros de desserialização (arquivo corrompido ou versão incompatível) são tratados com mensagem amigável
|
||
- Salvamento automático não é realizado — apenas sob demanda do usuário
|
||
|
||
### RF-15 — Excluir faixa existente do arquivo de saída
|
||
|
||
- O usuário pode marcar qualquer faixa de áudio ou legenda **já presente no arquivo de entrada** para ser omitida do arquivo de saída
|
||
- A operação é reversível: a faixa pode ser restaurada antes da geração do MKV
|
||
- Faixas marcadas como excluídas são exibidas com texto tachado e cor esmaecida na lista; seus campos de offset e drift ficam desabilitados
|
||
- Faixas de vídeo e dados **não** podem ser excluídas — somente áudio e legenda
|
||
- Implementado via campo `excluded: bool` em `MediaTrackInfo`; método `toggle_existing_track_excluded()` em `Project`
|
||
- Ambos os pipelines (FFmpeg e mkvmerge) respeitam o flag: faixas excluídas são filtradas antes da geração dos argumentos de `-map` / `--audio-tracks` / `--subtitle-tracks`
|
||
- O estado `excluded` é persistido junto com a sessão (RF-14)
|
||
|
||
### RF-16 — Reordenar faixas externas
|
||
|
||
- O usuário pode alterar a ordem das faixas externas adicionadas (áudio e legenda) usando os botões **↑** e **↓** na lista de faixas
|
||
- A ordem determina o índice de stream no container MKV final — relevante para players que selecionam faixas por posição (ex: VLC, Jellyfin)
|
||
- O botão ↑ fica desabilitado na primeira faixa da lista; o botão ↓, na última
|
||
- Implementado via método `move_track(id, delta: i8)` em `Project`, usando `Vec::swap` — ambos os pipelines (FFmpeg e mkvmerge) iteram `project.tracks` em ordem e não precisam de alteração
|
||
- Somente faixas **externas** (`project.tracks`) são reordenáveis; faixas existentes (`project.existing_tracks`, lidas via ffprobe) permanecem na ordem detectada
|
||
- A nova ordem é persistida automaticamente junto com a sessão (RF-14)
|
||
|
||
---
|
||
|
||
## 7. Requisitos Não Funcionais
|
||
|
||
### RNF-01 — Sem reencoding (regra central do domínio)
|
||
|
||
Nenhum stream de vídeo ou áudio deve ser recodificado. Apenas mux e ajuste de timestamps são permitidos.
|
||
|
||
**Esta é a propriedade mais importante do produto.** O flag `-c copy` (ou equivalente por stream) **nunca é opcional**. Deve ser emitido pelo `FfmpegCommandBuilder` de forma incondicional, independente de qualquer configuração do usuário.
|
||
|
||
O domínio deve expor uma invariante que o garanta:
|
||
|
||
```
|
||
impl FfmpegCommandBuilder {
|
||
// -c copy é sempre adicionado. Não existe API para desativá-lo.
|
||
fn build(&self, project: &Project) -> Vec<String> { ... }
|
||
}
|
||
```
|
||
|
||
Qualquer violação dessa regra invalida a proposta de valor do produto.
|
||
|
||
### RNF-02 — Desempenho
|
||
|
||
A operação de mux deve ser concluída em tempo proporcional ao tamanho do arquivo, sem bloqueio da interface.
|
||
|
||
### RNF-03 — Compatibilidade
|
||
|
||
O arquivo de saída deve ser compatível com players modernos que suportam MKV (ex: VLC, mpv, Jellyfin).
|
||
|
||
### RNF-04 — Dependências externas
|
||
|
||
FFmpeg e ffprobe devem estar instalados no sistema. A aplicação verifica a presença de ambos na inicialização e exibe erro global se ausentes.
|
||
|
||
mkvmerge (MKVToolNix) é uma dependência opcional. Quando ausente, a funcionalidade de correção de drift (RF-12) fica desabilitada visualmente; o restante do produto continua funcional. A verificação é **soft** — não bloqueia a inicialização.
|
||
|
||
### RNF-05 — Interface responsiva
|
||
|
||
A interface não deve travar durante a execução do FFmpeg. A execução deve ser assíncrona.
|
||
|
||
---
|
||
|
||
## 8. Arquitetura Técnica
|
||
|
||
```
|
||
Interface (egui/eframe)
|
||
↓
|
||
Backend Rust (validação, construção de comandos)
|
||
↓
|
||
Processo externo (FFmpeg via std::process::Command)
|
||
↓
|
||
Arquivo de saída (.mkv)
|
||
```
|
||
|
||
### Stack
|
||
|
||
| Camada | Tecnologia | Motivo |
|
||
| ------------- | ------------------------------- | ------------------------------------------- |
|
||
| Interface | `egui` / `eframe` | Nativo, simples, multiplataforma |
|
||
| Backend | Rust (`std::process::Command`) | Estável, sem dependências externas |
|
||
| Erros | `anyhow` | Propagação de erros simplificada |
|
||
| Configuração | `serde` + `serde_json` | Serialização de sessão em JSON |
|
||
| Async | `tokio` | Execução não bloqueante da interface |
|
||
| Diálogos | `rfd` | Diálogos nativos de arquivo multiplataforma |
|
||
| Mídia (mux) | FFmpeg (externo) | Mux sem reencoding; caminho padrão |
|
||
| Mídia (drift) | mkvmerge / MKVToolNix (externo) | Correção de drift sem reencoding (opcional) |
|
||
| Container | MKV | Melhor suporte a múltiplas faixas |
|
||
|
||
### Dependências Cargo
|
||
|
||
```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"
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Arquitetura de Projeto — Clean Architecture
|
||
|
||
A organização do código segue os princípios da **Clean Architecture**, garantindo separação de responsabilidades, testabilidade e baixo acoplamento entre camadas.
|
||
|
||
### Regra de dependência
|
||
|
||
As dependências sempre apontam de fora para dentro. Camadas internas não conhecem camadas externas.
|
||
|
||
```
|
||
┌──────────────────────────────────────────┐
|
||
│ Infrastructure / UI │ ← egui, FFmpeg, filesystem
|
||
│ ┌────────────────────────────────────┐ │
|
||
│ │ Adapters │ │ ← FfmpegGateway, FilePicker
|
||
│ │ ┌──────────────────────────────┐ │ │
|
||
│ │ │ Application │ │ │ ← Use Cases
|
||
│ │ │ ┌────────────────────────┐ │ │ │
|
||
│ │ │ │ Domain │ │ │ │ ← Entities, Value Objects
|
||
│ │ │ └────────────────────────┘ │ │ │
|
||
│ │ └──────────────────────────────┘ │ │
|
||
│ └────────────────────────────────────┘ │
|
||
└──────────────────────────────────────────┘
|
||
```
|
||
|
||
### Camadas
|
||
|
||
#### Domain (núcleo)
|
||
|
||
Contém as entidades e objetos de valor do negócio. Não depende de nada externo.
|
||
|
||
| Tipo | Exemplos |
|
||
| ------------- | ------------------------------------------------------------------------------------ |
|
||
| Entities | `Project`, `VideoFile`, `AudioTrack`, `SubtitleTrack`, `MkvOutput`, `MediaTrackInfo` |
|
||
| Value Objects | `TrackId`, `SyncOffset`, `SyncTransform`, `TrackLanguage`, `FilePath` |
|
||
|
||
> **`Project`** é a entidade central do domínio. Representa a sessão de edição completa do usuário e deve ser a única fonte de verdade do estado em memória.
|
||
>
|
||
> ```
|
||
> Project
|
||
> ├── source: VideoFile
|
||
> ├── tracks: Vec<Track> // faixas externas adicionadas
|
||
> ├── existing_tracks: Vec<MediaTrackInfo> // faixas lidas do arquivo via ffprobe
|
||
> └── output: MkvOutput
|
||
> ```
|
||
>
|
||
> Método relevante: `needs_mkvmerge() -> bool` — retorna `true` se qualquer faixa tiver `drift_scale ≠ 1.0`, sinalizando que o projeto deve usar o pipeline `mkvmerge` em vez do FFmpeg.
|
||
>
|
||
> **`TrackId`** abstrai os índices de stream do FFmpeg. Internamente é um identificador opaco (`u32`). Nenhuma lógica de negócio deve depender de índices FFmpeg diretamente — isso é responsabilidade do adapter.
|
||
>
|
||
> **`SyncOffset`** armazena o offset de sincronização em **milissegundos como inteiro** (`i64`). Nunca como `f64`. O uso de ponto flutuante acumula erro de precisão; o adapter é responsável por converter para o formato exigido pelo FFmpeg (`-itsoffset`).
|
||
>
|
||
> **`SyncTransform`** é o value object que combina deslocamento e escala temporal: `{ offset_ms: i64, scale: f64 }`. Usado internamente para representar a transformação completa de uma faixa no pipeline mkvmerge (RF-12). `scale = 1.0` representa identidade; `scale ≠ 1.0` ativa o correto motor de dispatch.
|
||
|
||
#### Application (casos de uso)
|
||
|
||
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.
|
||
|
||
| Tipo | Exemplos |
|
||
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| Use Cases | `LoadMediaInfo`, `AddAudioTrack`, `AddSubtitle`, `AdjustSync`, `AdjustDrift`, `AdjustExistingTrackDrift`, `EditExistingTrackSync`, `ExportTrack`, `RemoveTrack`, `SetTrackLanguage`, `GenerateOutput` |
|
||
| Ports | `MediaProcessorPort`, `MediaInfoPort`, `FileSystemPort`, `ContainerMuxPort` |
|
||
|
||
> **`MediaInfoPort`** é o port responsável por inspecionar arquivos de mídia existentes. Deve ser definido na camada Application e implementado na camada Adapters via `FfprobeGateway`.
|
||
>
|
||
> ```
|
||
> trait MediaInfoPort {
|
||
> fn probe(path: &FilePath) -> Result<Vec<MediaTrackInfo>>;
|
||
> }
|
||
> ```
|
||
>
|
||
> `MediaTrackInfo` contém: `TrackId`, tipo de stream (vídeo/áudio/legenda), codec, idioma detectado.
|
||
>
|
||
> **`LoadMediaInfo`** é o caso de uso disparado quando o usuário seleciona um arquivo de vídeo. Lê as faixas existentes e popula o `Project::existing_tracks`.
|
||
|
||
#### Adapters
|
||
|
||
Implementam os ports definidos na camada de Application. Traduzem dados entre o domínio e o mundo externo.
|
||
|
||
| Tipo | Exemplos |
|
||
| ------------ | ------------------------------------------------------------------------- |
|
||
| Gateway | `FfmpegCommandBuilder`, `FfmpegGateway`, `FfprobeGateway` |
|
||
| Gateway | `MkvmergeCommandBuilder`, `MkvmergeGateway` (motor de drift — RF-12) |
|
||
| Presenter | `ErrorPresenter` (formata stderr do FFmpeg / stdout do mkvmerge) |
|
||
| File Adapter | `FilePickerAdapter` (inclui `save_audio(codec)` e `save_subtitle(codec)`) |
|
||
|
||
> **`FfprobeGateway`** implementa `MediaInfoPort`. Executa `ffprobe -v quiet -print_format json -show_streams` e mapeia a saída para `Vec<MediaTrackInfo>`. Não conhece o domínio além das structs que está populando.
|
||
>
|
||
> **`FfmpegCommandBuilder`** é responsável por converter `TrackId` para índices `-map 0:a:N` do FFmpeg e `SyncOffset` (ms inteiro) para o formato `-itsoffset 1.200` aceito pelo binário. **Toda conversão de tipos internos para argumentos FFmpeg fica aqui e somente aqui.**
|
||
>
|
||
> Além do `-c copy`, sempre emite `-max_interleave_delta 0` (evita descarte silencioso de pacotes em streams com grande delta de timestamps) e `-avoid_negative_ts make_zero` (normaliza timestamps negativos comuns em M4A/AAC de serviços de streaming).
|
||
>
|
||
> **`MkvmergeCommandBuilder`** converte `Project` em argumentos para o `mkvmerge`. Usa `--sync TID:DELAY,NUM/DEN` para aplicar deslocamento e escala de timestamps sem reencoding. A escala `f64` é convertida para fração racionl `(NUM, DEN)` com precisão de 6 casas decimais.
|
||
|
||
#### Infrastructure / UI
|
||
|
||
Camada mais externa. Contém o framework de UI e a execução real de processos.
|
||
|
||
| Tipo | Exemplos |
|
||
| ---------- | ------------------------------------------------------ |
|
||
| UI | `App` (eframe), componentes egui |
|
||
| Process | Execução de `std::process::Command` / `tokio::process` |
|
||
| Filesystem | Leitura e escrita de arquivos |
|
||
|
||
---
|
||
|
||
### Estrutura de pastas
|
||
|
||
```
|
||
src/
|
||
├── domain/
|
||
│ ├── entities/ # Project, VideoFile, AudioTrack, SubtitleTrack, MkvOutput, MediaTrackInfo
|
||
│ └── value_objects/ # TrackId, SyncOffset (ms/i64), SyncTransform (offset+scale), TrackLanguage, FilePath
|
||
├── application/
|
||
│ ├── use_cases/ # LoadMediaInfo, AddAudioTrack, AddSubtitle, AdjustSync, AdjustDrift,
|
||
│ │ # AdjustExistingTrackDrift, EditExistingTrackSync, ExportTrack,
|
||
│ │ # RemoveTrack, SetTrackLanguage, GenerateOutput
|
||
│ └── ports/ # Traits: MediaProcessorPort, MediaInfoPort, FileSystemPort, ContainerMuxPort
|
||
├── adapters/
|
||
│ ├── ffmpeg/ # FfmpegCommandBuilder, FfmpegGateway, FfprobeGateway
|
||
│ ├── mkvmerge/ # MkvmergeCommandBuilder, MkvmergeGateway (RF-12 — drift)
|
||
│ └── filesystem/ # FilePickerAdapter
|
||
├── infrastructure/
|
||
│ ├── process/ # run_ffmpeg_async, run_mkvmerge_async, validate_dependencies, mkvmerge_available
|
||
│ └── persistence/ # save_session, load_session (RF-14)
|
||
├── ui/
|
||
│ ├── app.rs # eframe App; ActiveTab (Single/Batch); dispatch FFmpeg/mkvmerge
|
||
│ └── components/ # VideoSelector, ExistingTrackList, AddAudioTrackForm, AddSubtitleForm,
|
||
│ # SyncOffsetField (+ campo drift), OutputSelector, ExecutionPanel, BatchPanel
|
||
└── main.rs
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Débitos Técnicos
|
||
|
||
| ID | Descrição | Impacto | Mitigação / Status |
|
||
| ----- | ------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| 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 | Encapsulado via `infrastructure/process`; `validate_dependencies()` na inicialização |
|
||
| DT-03 | Parsing de erros do FFmpeg (stderr) | Mensagens brutas exibidas ao usuário | Stderr capturado linha a linha via `run_ffmpeg_async`; exibido no `ExecutionPanel`; parse amigável futuro |
|
||
| DT-04 | Compatibilidade de codecs | Alguns codecs podem não ser aceitos | MKV como container padrão; `-c copy` garante que não há transcodificação |
|
||
| DT-05 | Performance de processo externo | Pequeno overhead | Aceitável — mux é rápido; sem impacto perceptível |
|
||
| DT-06 | ffprobe como dependência adicional | Parsing de JSON da saída do ffprobe | ✅ Resolvido — `validate_dependencies()` verifica ffprobe na inicialização; erro global exibido se ausente |
|
||
| DT-07 | Compatibilidade com Jellyfin mobile | Faixas não selecionadas automaticamente | Investigando — RF-10 (`title`) e RF-11 (`disposition`) implementados; comportamento no Jellyfin mobile requer testes com arquivo gerado |
|
||
| DT-08 | mkvmerge não embutido | Usuário precisa instalar MKVToolNix | Dependência opcional; feat. de drift desabilitada visualmente se ausente; verificação soft na inicialização |
|
||
| DT-09 | Testes de integração com FFmpeg real | Regressões em comandos gerados | Pendente — requer `ffmpeg` no CI; coberto indiretamente pelos testes unitários do `FfmpegCommandBuilder` |
|
||
| DT-10 | Comando FFmpeg não visível ao usuário | Difícil de debugar manualmente | ✅ Resolvido — painel colapsável "🛠 Comando gerado" na `ExecutionPanel` e em cada item do modo Lote; botão de cópia para clipboard |
|
||
|
||
---
|
||
|
||
## 11. Funcionalidades Futuras (Backlog)
|
||
|
||
- Preview de vídeo embutido na interface
|
||
- Detecção automática de sincronização entre faixas
|
||
- Suporte a perfis de configuração reutilizáveis (presets de idioma, offset, drift)
|
||
- Mensagens de erro mais amigáveis a partir do stderr do FFmpeg
|
||
- Testes de integração com FFmpeg real no CI (DT-09)
|
||
- Testes de snapshot para o `FfmpegCommandBuilder` e `MkvmergeCommandBuilder` com projetos complexos
|
||
|
||
---
|
||
|
||
## 12. Critérios de Aceitação (v1.0)
|
||
|
||
- [x] É possível selecionar um arquivo de vídeo base
|
||
- [x] Ao selecionar o vídeo, as faixas existentes são listadas automaticamente (via `ffprobe`)
|
||
- [x] É possível adicionar uma ou mais faixas de áudio externas
|
||
- [x] É possível adicionar uma ou mais faixas de legenda
|
||
- [x] É possível definir offset de sincronização por faixa ao adicionar uma nova faixa
|
||
- [x] É possível editar o offset de sincronização de uma faixa de áudio ou legenda já existente no arquivo
|
||
- [x] É possível atribuir idioma a cada faixa
|
||
- [x] É possível definir um título/nome para cada faixa externa (RF-10)
|
||
- [x] É possível marcar uma faixa externa como faixa padrão do container (RF-11)
|
||
- [x] É possível exportar uma faixa existente para arquivo separado (RF-13)
|
||
- [x] É possível salvar e restaurar a sessão em disco (RF-14)
|
||
- [x] É possível excluir faixas de áudio ou legenda existentes do arquivo de saída, com possibilidade de restauração antes da geração (RF-15)
|
||
- [x] É possível reordenar as faixas externas adicionadas com botões ↑↓; a ordem reflete o índice no container final (RF-16)
|
||
- [x] O modo lote permite configurar e processar múltiplos projetos sequencialmente (RF-09)
|
||
- [x] É possível definir fator de correção de drift por faixa (RF-12; requer mkvmerge)
|
||
- [x] O arquivo MKV é gerado corretamente ao confirmar
|
||
- [x] Erros do FFmpeg/mkvmerge são exibidos de forma legível na `ExecutionPanel`
|
||
- [x] A interface não trava durante o processamento (execução assíncrona via `tokio`)
|
||
- [x] É possível cancelar a geração em andamento
|
||
- [x] Nenhum reencoding ocorre (verificável via `ffprobe` no arquivo de saída)
|
||
- [x] O flag `-c copy` está sempre presente no comando FFmpeg gerado (verificável via testes unitários)
|
||
- [x] O comando FFmpeg/mkvmerge gerado é exibido em painel colapsável na ExecutionPanel (DT-10)
|
||
|
||
---
|
||
|
||
## 13. Princípios de UX
|
||
|
||
> Este software é um **tradutor** entre a confusão do FFmpeg e a ordem que o usuário precisa.
|
||
|
||
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"_
|
||
- _"Quero adicionar a legenda em inglês"_
|
||
- _"Gerar o arquivo final"_
|
||
|
||
Toda decisão de UX deve partir dessa perspectiva. Termos técnicos do FFmpeg nunca devem aparecer na interface.
|
||
|
||
| FFmpeg (interno) | Interface (usuário) |
|
||
| -------------------- | ------------------------------ |
|
||
| `-itsoffset -1200ms` | "Adiantar 1.2s" |
|
||
| `stream 0:a:1` | "Faixa de áudio 2 — Português" |
|
||
| `-c copy` | _(invisível — nunca exposto)_ |
|
||
| `ffprobe output` | Lista de faixas detectadas |
|
||
|
||
---
|
||
|
||
## 14. Insight Arquitetural — MKV como Banco de Dados
|
||
|
||
> Você não está editando vídeo. Você está **editando metadados de um container**.
|
||
|
||
O MKV (Matroska) é estruturalmente um banco de dados multimídia. Suas operações são:
|
||
|
||
- **INSERT** uma faixa de áudio ou legenda
|
||
- **UPDATE** o timestamp (offset) de uma faixa existente
|
||
- **SELECT** as faixas para inspecionar via ffprobe
|
||
|
||
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:
|
||
|
||
- Sem buffers de vídeo
|
||
- Sem pipelines de transcoding
|
||
- Apenas mapeamento de streams e metadados
|
||
|
||
Quando a equipe pensa assim, o código fica limpo, as abstrações fazem sentido e os bugs diminuem.
|