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-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-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-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 - Preview de vídeo embutido na interface
- Detecção automática de sincronização entre faixas - Detecção automática de sincronização entre faixas
- Suporte a perfis de configuração reutilizáveis (presets de idioma, offset, drift) - 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 - 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 integração com FFmpeg real no CI (DT-09)
- Testes de snapshot para o `FfmpegCommandBuilder` e `MkvmergeCommandBuilder` com projetos complexos - 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] É possível cancelar a geração em andamento
- [x] Nenhum reencoding ocorre (verificável via `ffprobe` no arquivo de saída) - [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 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 # Implementação — DT-10: Painel de Debug do Comando Gerado
**Data:** 01/03/2026 **Data:** 01/03/2026
**Status:** Planejado **Status:** ✅ Implementado
**Prioridade:** Baixa — melhoria de DX para usuário técnico **Prioridade:** Baixa — melhoria de DX para usuário técnico
**Arquivos afetados:** **Arquivos afetados:**
@@ -151,12 +151,12 @@ Ao concluir a implementação:
## Critério de aceitação ## Critério de aceitação
- [ ] O painel "🛠 Comando gerado" aparece na `ExecutionPanel` após a primeira geração - [x] O painel "🛠 Comando gerado" aparece na `ExecutionPanel` após a primeira geração
- [ ] O painel está colapsado por padrão - [x] O painel está colapsado por padrão
- [ ] O conteúdo exibe o binário (`ffmpeg` ou `mkvmerge`) seguido de todos os argumentos - [x] 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 - [x] Argumentos com espaços internos são envolvidos em aspas duplas
- [ ] O botão "📋 Copiar" coloca o texto no clipboard - [x] O botão "📋 Copiar" coloca o texto no clipboard
- [ ] O comando é exibido também para gerações canceladas - [x] O comando é exibido também para gerações canceladas
- [ ] O painel **não** aparece antes da primeira geração (estado `Idle` inicial) - [x] O painel **não** aparece antes da primeira geração (estado `Idle` inicial)
- [ ] Modo Lote exibe o comando do último item processado - [x] Modo Lote exibe o comando do último item processado
- [ ] `cargo test`33 testes passando sem regressão - [x] `cargo test`68 testes passando sem regressão
+26
View File
@@ -245,6 +245,19 @@ impl App {
} else { } else {
FfmpegCommandBuilder::build(&project) 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>(); let (tx, rx) = mpsc::channel::<BackgroundMsg>();
self.bg_rx = Some(rx); self.bg_rx = Some(rx);
self.execution_panel.state = ExecutionState::Running; self.execution_panel.state = ExecutionState::Running;
@@ -311,6 +324,19 @@ impl App {
} else { } else {
FfmpegCommandBuilder::build(&project) 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>(); let (tx, rx) = mpsc::channel::<BackgroundMsg>();
self.bg_rx = Some(rx); self.bg_rx = Some(rx);
+16
View File
@@ -17,6 +17,8 @@ pub struct BatchItem {
pub project: Project, pub project: Project,
pub state: ExecutionState, pub state: ExecutionState,
pub log_lines: Vec<String>, pub log_lines: Vec<String>,
/// Comando completo enviado ao FFmpeg/mkvmerge na última geração deste item.
pub last_command: Option<String>,
} }
impl BatchItem { impl BatchItem {
@@ -25,6 +27,7 @@ impl BatchItem {
project, project,
state: ExecutionState::Idle, state: ExecutionState::Idle,
log_lines: Vec::new(), 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 }); // push_id
ui.add_space(4.0); ui.add_space(4.0);
} }
+20
View File
@@ -15,6 +15,9 @@ pub enum ExecutionState {
pub struct ExecutionPanel { pub struct ExecutionPanel {
pub state: ExecutionState, pub state: ExecutionState,
pub log_lines: Vec<String>, 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 { impl ExecutionPanel {
@@ -22,6 +25,7 @@ impl ExecutionPanel {
ExecutionPanel { ExecutionPanel {
state: ExecutionState::Idle, state: ExecutionState::Idle,
log_lines: Vec::new(), 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) (generate_clicked, cancel_clicked)