adiciona diretrizes de geração de código e workflow ao repositório
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+7
@@ -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"
|
||||
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "simple-multimidia-track-audio-editor"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
Reference in New Issue
Block a user