feat: implementa painel de debug para exibir o comando gerado pelo FFmpeg/mkvmerge na ExecutionPanel, com opção de cópia

This commit is contained in:
2026-03-01 22:16:28 -03:00
parent e347202e95
commit d86135ad6b
5 changed files with 75 additions and 13 deletions
+2 -2
View File
@@ -393,7 +393,7 @@ src/
| 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 | Planejado — painel colapsável com o comando gerado na `ExecutionPanel` (modo debug) |
| 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 |
---
@@ -402,7 +402,6 @@ src/
- 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)
- Painel colapsável com o comando FFmpeg/mkvmerge gerado (modo debug, DT-10)
- 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
@@ -431,6 +430,7 @@ src/
- [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)
---
+10 -10
View File
@@ -1,7 +1,7 @@
# Implementação — DT-10: Painel de Debug do Comando Gerado
**Data:** 01/03/2026
**Status:** Planejado
**Status:** ✅ Implementado
**Prioridade:** Baixa — melhoria de DX para usuário técnico
**Arquivos afetados:**
@@ -151,12 +151,12 @@ Ao concluir a implementação:
## Critério de aceitação
- [ ] O painel "🛠 Comando gerado" aparece na `ExecutionPanel` após a primeira geração
- [ ] O painel está colapsado por padrão
- [ ] O conteúdo exibe o binário (`ffmpeg` ou `mkvmerge`) seguido de todos os argumentos
- [ ] Argumentos com espaços internos são envolvidos em aspas duplas
- [ ] O botão "📋 Copiar" coloca o texto no clipboard
- [ ] O comando é exibido também para gerações canceladas
- [ ] O painel **não** aparece antes da primeira geração (estado `Idle` inicial)
- [ ] Modo Lote exibe o comando do último item processado
- [ ] `cargo test`33 testes passando sem regressão
- [x] O painel "🛠 Comando gerado" aparece na `ExecutionPanel` após a primeira geração
- [x] O painel está colapsado por padrão
- [x] O conteúdo exibe o binário (`ffmpeg` ou `mkvmerge`) seguido de todos os argumentos
- [x] Argumentos com espaços internos são envolvidos em aspas duplas
- [x] O botão "📋 Copiar" coloca o texto no clipboard
- [x] O comando é exibido também para gerações canceladas
- [x] O painel **não** aparece antes da primeira geração (estado `Idle` inicial)
- [x] Modo Lote exibe o comando do último item processado
- [x] `cargo test`68 testes passando sem regressão
+26
View File
@@ -245,6 +245,19 @@ impl App {
} else {
FfmpegCommandBuilder::build(&project)
};
// Formatar comando legível para o painel de debug
let binary = if uses_mkvmerge { "mkvmerge" } else { "ffmpeg" };
let cmd_str = format!(
"{} {}",
binary,
args.iter()
.map(|a| if a.contains(' ') { format!("\"{}\"", a) } else { a.clone() })
.collect::<Vec<_>>()
.join(" ")
);
self.execution_panel.last_command = Some(cmd_str);
let (tx, rx) = mpsc::channel::<BackgroundMsg>();
self.bg_rx = Some(rx);
self.execution_panel.state = ExecutionState::Running;
@@ -311,6 +324,19 @@ impl App {
} else {
FfmpegCommandBuilder::build(&project)
};
// Formatar comando legível para o painel de debug do item de lote
let binary = if uses_mkvmerge { "mkvmerge" } else { "ffmpeg" };
let cmd_str = format!(
"{} {}",
binary,
args.iter()
.map(|a| if a.contains(' ') { format!("\"{}\"", a) } else { a.clone() })
.collect::<Vec<_>>()
.join(" ")
);
self.batch_items[index].last_command = Some(cmd_str);
let (tx, rx) = mpsc::channel::<BackgroundMsg>();
self.bg_rx = Some(rx);
+16
View File
@@ -17,6 +17,8 @@ pub struct BatchItem {
pub project: Project,
pub state: ExecutionState,
pub log_lines: Vec<String>,
/// Comando completo enviado ao FFmpeg/mkvmerge na última geração deste item.
pub last_command: Option<String>,
}
impl BatchItem {
@@ -25,6 +27,7 @@ impl BatchItem {
project,
state: ExecutionState::Idle,
log_lines: Vec::new(),
last_command: None,
}
}
}
@@ -254,7 +257,20 @@ impl BatchPanel {
});
});
}
if let Some(cmd) = &item.last_command {
ui.collapsing("🛠 Comando gerado", |ui| {
let mut cmd_str = cmd.as_str();
ui.add(
egui::TextEdit::multiline(&mut cmd_str)
.desired_rows(3)
.desired_width(f32::INFINITY)
.font(egui::TextStyle::Monospace),
);
if ui.small_button("📋 Copiar").clicked() {
ui.output_mut(|o| o.copied_text = cmd.clone());
}
});
} });
}); // push_id
ui.add_space(4.0);
}
+20
View File
@@ -15,6 +15,9 @@ pub enum ExecutionState {
pub struct ExecutionPanel {
pub state: ExecutionState,
pub log_lines: Vec<String>,
/// Comando completo enviado ao FFmpeg/mkvmerge na última geração.
/// Exemplo: "ffmpeg -i input.mkv -c copy ... output.mkv"
pub last_command: Option<String>,
}
impl ExecutionPanel {
@@ -22,6 +25,7 @@ impl ExecutionPanel {
ExecutionPanel {
state: ExecutionState::Idle,
log_lines: Vec::new(),
last_command: None,
}
}
@@ -103,6 +107,22 @@ impl ExecutionPanel {
});
});
}
// Painel de debug: comando gerado
if let Some(cmd) = &self.last_command {
ui.collapsing("🛠 Comando gerado", |ui| {
let mut cmd_str = cmd.as_str();
ui.add(
egui::TextEdit::multiline(&mut cmd_str)
.desired_rows(3)
.desired_width(f32::INFINITY)
.font(egui::TextStyle::Monospace),
);
if ui.small_button("📋 Copiar").clicked() {
ui.output_mut(|o| o.copied_text = cmd.clone());
}
});
}
});
(generate_clicked, cancel_clicked)