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

17 KiB

PRD — Simple Multimedia Track Audio Editor

Versão: 1.1
Data: 28/02/2026
Status: Em desenvolvimento
Revisão: Incorporados feedbacks de arquitetura — modelo de sessão, ffprobe, tipos fortes e regra de no-reencode


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
  • Remoção de faixas existentes do arquivo original
  • Seleção de faixa padrão no container
  • Edição de corte ou splice de vídeo

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-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

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ência externa

FFmpeg deve estar instalado no sistema. A aplicação não o embute por padrão, porém pode ser distribuída junto.

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 Serialização de perfis e histórico
Async tokio (opcional) Execução não bloqueante da interface
Mídia FFmpeg (externo) Maduro, estável, amplamente testado
Container MKV Melhor suporte a múltiplas faixas

Dependências Cargo

eframe = "0.27"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["process"] }   # opcional

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, 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

TrackId abstrai os índices de stream do FFmpeg. Internamente é um identificador opaco (u32 ou String). 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).

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, EditExistingTrackSync, SetTrackLanguage, GenerateOutput
Ports MediaProcessorPort, MediaInfoPort, FileSystemPort

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
Presenter ErrorPresenter (formata stderr do FFmpeg)
File Adapter FilePickerAdapter

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.

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), TrackLanguage, FilePath
├── application/
│   ├── use_cases/       # LoadMediaInfo, AddAudioTrack, AddSubtitle, AdjustSync, GenerateOutput
│   └── ports/           # Traits: MediaProcessorPort, MediaInfoPort, FileSystemPort
├── adapters/
│   ├── ffmpeg/          # FfmpegCommandBuilder, FfmpegGateway, FfprobeGateway
│   └── filesystem/      # FilePickerAdapter
├── infrastructure/
│   └── process/         # Execução real do FFmpeg / ffprobe
├── ui/
│   ├── app.rs           # eframe App (ponto de entrada da interface)
│   └── components/      # Componentes egui reutilizáveis
└── main.rs

10. Débitos Técnicos

ID Descrição Impacto Mitigaçã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-03 Parsing de erros do FFmpeg (stderr) Necessário tratamento manual Parsear saída e exibir mensagem amigável
DT-04 Compatibilidade de codecs Alguns codecs podem não ser aceitos Usar MKV como container padrão
DT-05 Performance de processo externo Pequeno overhead Aceitável — mux é rápido
DT-06 ffprobe como dependência adicional Parsing de JSON da saída do ffprobe Validar presença de ffprobe na inicialização; exibir mensagem clara se ausente

11. Funcionalidades Futuras (Backlog)

  • Preview de vídeo embutido na interface
  • Detecção automática de sincronização entre faixas
  • Seleção de faixa padrão no container MKV
  • Remoção de faixas existentes do arquivo original
  • Suporte a perfis de configuração salvos
  • Modo de processamento em lote (batch)

12. Critérios de Aceitação (v1.0)

  • É possível selecionar um arquivo de vídeo base
  • Ao selecionar o vídeo, as faixas existentes são listadas automaticamente (via ffprobe)
  • É possível adicionar uma ou mais faixas de áudio externas
  • É possível adicionar uma ou mais faixas de legenda
  • É possível definir offset de sincronização por faixa ao adicionar uma nova faixa
  • É possível editar o offset de sincronização de uma faixa de áudio ou legenda já existente no arquivo
  • É possível atribuir idioma a cada faixa
  • O arquivo MKV é gerado corretamente ao confirmar
  • Erros do FFmpeg são exibidos de forma legível
  • A interface não trava durante o processamento
  • Nenhum reencoding ocorre (verificável via ffprobe)
  • O flag -c copy está sempre presente no comando gerado (verificável em modo debug)

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.