Files
simple-multimidia-track-aud…/PRD.md
T

477 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PRD — Simple Multimedia Track Audio Editor
**Versão:** 1.4
**Data:** 01/03/2026
**Status:** v1.0 funcional — Fases 19 concluídas; 33 testes passando; aplicação executável
**Revisão:** v1.4 — Adicionado RF-15 (Excluir faixa existente do output)
---
## 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)
---
## 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] 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.