adiciona diretrizes de geração de código e workflow ao repositório

This commit is contained in:
2026-02-28 13:05:17 -03:00
commit eff779a1df
9 changed files with 839 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
# AI Code Generation Guidelines
## Princípios Fundamentais
Você deve priorizar:
- Simplicidade
- Reutilização
- Clareza
- Manutenibilidade
- Aderência ao projeto existente
Evite complexidade desnecessária.
---
## 1. Evitar Overengineering
Antes de implementar qualquer solução, verifique:
- Existe uma forma mais simples?
- Esta solução resolve apenas o problema atual?
- Estou adicionando abstrações desnecessárias?
Regras:
- NÃO crie abstrações "para o futuro"
- NÃO use padrões complexos sem necessidade clara
- Prefira soluções diretas e legíveis
---
## 2. Não Reinventar a Roda
Antes de implementar algo novo:
Verifique, nesta ordem:
1. Código existente no projeto
2. Bibliotecas já instaladas no projeto
3. Bibliotecas populares e consolidadas
4. Recursos nativos da linguagem
Regras:
- Prefira bibliotecas consolidadas
- NÃO reimplemente funcionalidades comuns
- Use soluções padrão da comunidade
---
## 3. Consultar Documentação
Antes de usar qualquer biblioteca, framework ou API:
- Consulte a documentação oficial
- Use a forma recomendada
- Evite hacks e workarounds
Se não souber algo:
- NÃO invente
- Admita que não sabe
- Peça mais informações
---
## 4. Não Duplicar Código
Antes de escrever uma função:
Verifique:
- Já existe algo similar?
- Pode ser reutilizado?
- Pode ser refatorado?
Regras:
- NÃO copie e cole código
- Reutilize funções existentes
- Extraia código comum para funções reutilizáveis
---
## 5. Separação de Responsabilidades
Não coloque tudo em um único arquivo.
Organize por responsabilidade:
Exemplo:
- controllers/
- services/
- repositories/
- utils/
- models/
Regras:
- Uma função deve ter uma única responsabilidade
- Um arquivo deve ter um propósito claro
- Evite arquivos grandes demais
---
## 6. Seguir o Padrão do Projeto
Antes de criar código novo:
Analise:
- Estrutura de pastas
- Convenções de nomes
- Estilo do código
- Arquitetura existente
Regras:
- Siga o padrão existente
- NÃO introduza novos padrões sem necessidade
---
## 7. Código Legível
Priorize:
- Nomes claros
- Código simples
- Facilidade de entendimento
Evite:
- Código "inteligente demais"
- Truques desnecessários
---
## 8. Pensamento Crítico Obrigatório
Antes de gerar código, você deve se perguntar:
- Isso já existe?
- Isso é a forma mais simples?
- Isso é consistente com o projeto?
- Isso é necessário?
Se a resposta for não clara, investigue antes.
---
## 9. Em Caso de Dúvida
Pare e pergunte.
Não assuma.
Não invente.
Não improvise.
+16
View File
@@ -0,0 +1,16 @@
# Workflow obrigatório
Antes de escrever código:
1. Leia o projeto
2. Entenda a arquitetura
3. Procure código existente
4. Procure bibliotecas existentes
Depois disso:
5. Proponha a solução
6. Explique por que é a melhor opção
7. Só então escreva o código
Nunca escreva código imediatamente sem análise.
+1
View File
@@ -0,0 +1 @@
/target
Generated
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "simple-multimidia-track-audio-editor"
version = "0.1.0"
+6
View File
@@ -0,0 +1,6 @@
[package]
name = "simple-multimidia-track-audio-editor"
version = "0.1.0"
edition = "2024"
[dependencies]
+264
View File
@@ -0,0 +1,264 @@
# Plano de Desenvolvimento — Simple Multimedia Track Audio Editor
**Versão:** 1.0
**Data:** 28/02/2026
**Referência:** PRD v1.1
---
## Estratégia Geral
O desenvolvimento segue a **Clean Architecture** de dentro para fora: as camadas mais internas (domínio) são implementadas e testadas primeiro, sem qualquer dependência de I/O, frameworks ou processos externos. A UI é sempre a última camada a ser construída.
```
Fase 1: Setup
└── Fase 2: Domain (testável, sem I/O)
└── Fase 3: Application (testável com mocks)
└── Fase 4: Adapters (integração com FFmpeg)
└── Fase 5: Infrastructure (processo real)
└── Fase 6: UI (montagem final)
```
Cada fase deve estar **compilando e com testes passando** antes de avançar para a próxima.
---
## Fase 1 — Setup do Projeto
**Objetivo:** Preparar o ambiente antes de escrever qualquer lógica de negócio.
### Tarefas
- [ ] Atualizar `Cargo.toml` com as dependências:
```toml
eframe = "0.27"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["process", "rt-multi-thread", "macros"] }
serde_json = "1"
```
- [ ] Criar a estrutura de pastas:
```
src/
├── domain/
│ ├── entities/
│ └── value_objects/
├── application/
│ ├── use_cases/
│ └── ports/
├── adapters/
│ ├── ffmpeg/
│ └── filesystem/
├── infrastructure/
│ └── process/
└── ui/
├── components/
└── app.rs
```
- [ ] Declarar os módulos em `src/main.rs`
- [ ] Verificar que o projeto compila (`cargo check`)
---
## Fase 2 — Domain (núcleo puro)
**Objetivo:** Modelar o problema de negócio sem nenhum acoplamento a frameworks, I/O ou processos externos.
> Regra: nenhum `use std::process`, nenhum `use eframe`, nenhuma chamada de rede ou filesystem nessa camada.
### Value Objects (`src/domain/value_objects/`)
| Tipo | Implementação |
|---|---|
| `FilePath` | Newtype sobre `PathBuf`; derivar `Clone`, `Debug`, `Serialize`, `Deserialize` |
| `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` |
| `TrackLanguage` | Newtype sobre `String` (ex: `"por"`, `"eng"`); validar formato ISO 639-2 |
### Entities (`src/domain/entities/`)
| Tipo | Campos principais |
|---|---|
| `VideoFile` | `path: FilePath` |
| `AudioTrack` | `id: TrackId`, `path: FilePath`, `offset: SyncOffset`, `language: TrackLanguage` |
| `SubtitleTrack` | `id: TrackId`, `path: FilePath`, `offset: SyncOffset`, `language: TrackLanguage` |
| `MediaTrackInfo` | `id: TrackId`, `kind: TrackKind` (enum: Video/Audio/Subtitle), `codec: String`, `language: Option<TrackLanguage>` |
| `MkvOutput` | `path: FilePath` |
| `Project` | Entidade raiz — ver abaixo |
#### Estrutura de `Project`
```rust
pub struct Project {
pub source: VideoFile,
pub tracks: Vec<Track>, // faixas externas adicionadas pelo usuário
pub existing_tracks: Vec<MediaTrackInfo>, // faixas lidas do arquivo via ffprobe
pub output: MkvOutput,
}
```
### Testes obrigatórios
- [ ] `SyncOffset::from_seconds_str("1.2")` → `SyncOffset(1200)`
- [ ] `SyncOffset::from_seconds_str("-0.5")` → `SyncOffset(-500)`
- [ ] `Project` não aceita `output.path` igual a `source.path`
- [ ] `TrackId` é opaco (não expõe indexação interna)
---
## Fase 3 — Application (casos de uso e ports)
**Objetivo:** Definir o que o sistema faz sem saber como. Ports são traits; use cases orquestram entidades.
### Ports (`src/application/ports/`)
```rust
// MediaInfoPort: inspeciona faixas de um arquivo de mídia
pub trait MediaInfoPort {
fn probe(&self, path: &FilePath) -> Result<Vec<MediaTrackInfo>>;
}
// MediaProcessorPort: executa o processamento final
pub trait MediaProcessorPort {
fn execute(&self, args: Vec<String>) -> Result<()>;
}
// FileSystemPort: abstrai acesso ao sistema de arquivos
pub trait FileSystemPort {
fn exists(&self, path: &FilePath) -> bool;
}
```
### Use Cases (`src/application/use_cases/`)
Implementar nesta ordem (dependência crescente):
| # | Use Case | Descrição |
|---|---|---|
| 1 | `LoadMediaInfo` | Usa `MediaInfoPort` para popular `Project::existing_tracks` |
| 2 | `AddAudioTrack` | Adiciona `AudioTrack` externo ao `Project::tracks` |
| 3 | `AddSubtitle` | Adiciona `SubtitleTrack` externo ao `Project::tracks` |
| 4 | `AdjustSync` | Altera `SyncOffset` de uma faixa existente pelo `TrackId` |
| 5 | `EditExistingTrackSync` | Ajusta offset de faixa já presente no arquivo original |
| 6 | `SetTrackLanguage` | Altera idioma de uma faixa pelo `TrackId` |
| 7 | `GenerateOutput` | Constrói o comando final e delega ao `MediaProcessorPort` |
### Testes obrigatórios
- [ ] Usar mocks dos ports (sem chamar nenhum processo externo)
- [ ] `GenerateOutput` sempre produz comando com `-c copy` (verificado via mock)
- [ ] `AdjustSync` com `TrackId` inexistente retorna erro
- [ ] `AddAudioTrack` em `Project` sem `source` retorna erro
---
## Fase 4 — Adapters (implementações concretas)
**Objetivo:** Conectar o domínio ao FFmpeg e ao filesystem real.
### FFmpeg (`src/adapters/ffmpeg/`)
#### `FfmpegCommandBuilder`
Responsabilidade única: converter `Project` em `Vec<String>` de argumentos para o FFmpeg.
Regras invariantes:
- `-c copy` **sempre presente** (RNF-01 — nunca opcional, nunca configurável)
- `SyncOffset(i64 ms)` → `-itsoffset 1.200` (conversão feita **somente aqui**)
- `TrackId` → `-map 0:a:N` (mapeamento de índice feito **somente aqui**)
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
```
- [ ] Implementar `FfmpegCommandBuilder::build(project: &Project) -> Vec<String>`
- [ ] Testes: verificar presença de `-c copy`, ordem dos `-map`, formato de `-itsoffset`
#### `FfprobeGateway`
Implementa `MediaInfoPort`. Executa:
```
ffprobe -v quiet -print_format json -show_streams <path>
```
e mapeia a saída JSON para `Vec<MediaTrackInfo>`.
- [ ] Implementar parsing de JSON via `serde_json`
- [ ] Mapear `codec_type` para `TrackKind`
- [ ] Mapear `tags.language` para `Option<TrackLanguage>`
#### `FfmpegGateway`
Implementa `MediaProcessorPort`. Executa o processo real do FFmpeg e captura stderr.
- [ ] Capturar stderr para exibição de erros (RF-07)
- [ ] Retornar erro com mensagem legível em caso de código de saída não-zero
### Filesystem (`src/adapters/filesystem/`)
- [ ] `FilePickerAdapter`: abstrai seleção de arquivo via diálogo nativo (usar crate `rfd`)
---
## Fase 5 — Infrastructure
**Objetivo:** Execução real e não-bloqueante de processos externos.
### Módulo `src/infrastructure/process/`
- [ ] Implementar execução via `tokio::process::Command` (assíncrono, RNF-05)
- [ ] Capturar stdout e stderr em stream (para exibir progresso em tempo real — RF-07)
### Validação na inicialização
- [ ] Verificar se `ffmpeg` está disponível no `PATH` (DT-06)
- [ ] Verificar se `ffprobe` está disponível no `PATH`
- [ ] Exibir mensagem clara ao usuário se algum dos dois estiver ausente
---
## Fase 6 — UI (camada mais externa)
**Objetivo:** Interface gráfica que conecta o usuário ao `Project` via use cases.
> Todo estado da aplicação vive no `Project`. A UI apenas lê e dispara use cases.
> Termos técnicos do FFmpeg **nunca aparecem na interface** (ver PRD seção 13).
### Componentes (`src/ui/components/`), em ordem de construção
| # | Componente | Descrição |
|---|---|---|
| 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 |
| 3 | `AddAudioTrackForm` | Formulário para adicionar faixa de áudio externa (arquivo, idioma, offset) |
| 4 | `AddSubtitleForm` | Formulário para adicionar legenda externa (arquivo, idioma, offset) |
| 5 | `SyncOffsetField` | Campo de offset em segundos (ex: `-1.2s`); converte para `SyncOffset(ms)` internamente |
| 6 | `LanguageField` | Seletor/input de idioma (ex: `por`, `eng`) |
| 7 | `OutputSelector` | Campo de caminho de saída + extensão `.mkv` forçada |
| 8 | `ExecutionPanel` | Botão "Gerar MKV", exibição de progresso e erros legíveis |
### App (`src/ui/app.rs`)
- [ ] Implementar `eframe::App` para `App`
- [ ] `App` contém `project: Project` como única fonte de verdade do estado
- [ ] Despachar eventos de UI para os use cases correspondentes
- [ ] Integrar execução assíncrona (tokio) com o loop de UI do eframe
---
## Critérios de Conclusão (v1.0)
Alinhados com o PRD seção 12:
- [ ] É 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 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` no arquivo de saída)
- [ ] O flag `-c copy` está sempre presente no comando gerado (verificável em modo debug)
+380
View File
@@ -0,0 +1,380 @@
# 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
```toml
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.
+2
View File
@@ -0,0 +1,2 @@
[tools]
rust = "latest"
+3
View File
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}