feat: add AdjustExistingTrackDrift use case to adjust drift scale of existing tracks
- Implemented AdjustExistingTrackDrift struct with execute method to modify drift scale of existing tracks in a project. - Added tests for adjusting drift scale, handling non-existent track IDs, and invalid scale values. - Updated MediaTrackInfo and AudioTrack entities to include drift_scale field. - Enhanced Project entity with needs_mkvmerge method to determine if mkvmerge is required based on drift scale. - Integrated drift scale adjustments into existing track list UI, allowing users to modify drift values. - Updated various use cases and UI components to support drift scale functionality, including add audio/subtitle forms and batch processing. - Implemented mkvmerge availability check and integrated it into the application workflow for conditional processing.
This commit is contained in:
+43
-14
@@ -1,13 +1,15 @@
|
||||
use crate::adapters::ffmpeg::{FfmpegCommandBuilder, FfmpegGateway, FfprobeGateway};
|
||||
use crate::adapters::mkvmerge::MkvmergeCommandBuilder;
|
||||
use crate::adapters::filesystem::file_picker::FilePickerAdapter;
|
||||
use crate::application::use_cases::{
|
||||
add_audio_track::AddAudioTrack, add_subtitle::AddSubtitle,
|
||||
adjust_existing_track_drift::AdjustExistingTrackDrift,
|
||||
edit_existing_track_sync::EditExistingTrackSync, export_track::ExportTrack,
|
||||
load_media_info::LoadMediaInfo, remove_track::RemoveTrack,
|
||||
};
|
||||
use crate::domain::entities::{MkvOutput, Project, VideoFile};
|
||||
use crate::domain::value_objects::FilePath;
|
||||
use crate::infrastructure::process::run_ffmpeg_async;
|
||||
use crate::infrastructure::process::{run_ffmpeg_async, run_mkvmerge_async};
|
||||
use crate::ui::components::{
|
||||
add_audio_track_form::AddAudioTrackForm,
|
||||
add_subtitle_form::AddSubtitleForm,
|
||||
@@ -70,6 +72,8 @@ pub struct App {
|
||||
batch_panel: BatchPanel,
|
||||
// Índice do item sendo processado no lote (None = nenhum)
|
||||
batch_processing_index: Option<usize>,
|
||||
// mkvmerge disponível no PATH (verificação soft na inicialização)
|
||||
mkvmerge_available: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -109,6 +113,7 @@ impl App {
|
||||
batch_items: Vec::new(),
|
||||
batch_panel: BatchPanel::new(),
|
||||
batch_processing_index: None,
|
||||
mkvmerge_available: crate::infrastructure::process::mkvmerge_available(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,13 +226,19 @@ impl App {
|
||||
}
|
||||
|
||||
/// Inicia geração do MKV em background thread com progresso em tempo real.
|
||||
/// Despacha para FFmpeg ou mkvmerge dependendo de `project.needs_mkvmerge()`.
|
||||
fn start_generation(&mut self, ctx: egui::Context) {
|
||||
let project = match &self.project {
|
||||
Some(p) => p.clone(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let args = FfmpegCommandBuilder::build(&project);
|
||||
let uses_mkvmerge = project.needs_mkvmerge();
|
||||
let args = if uses_mkvmerge {
|
||||
MkvmergeCommandBuilder::build(&project)
|
||||
} else {
|
||||
FfmpegCommandBuilder::build(&project)
|
||||
};
|
||||
let (tx, rx) = mpsc::channel::<BackgroundMsg>();
|
||||
self.bg_rx = Some(rx);
|
||||
self.execution_panel.state = ExecutionState::Running;
|
||||
@@ -237,7 +248,7 @@ impl App {
|
||||
self.cancel_tx = Some(cancel_tx);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Canal std para receber linhas de log do run_ffmpeg_async
|
||||
// Canal std para receber linhas de log
|
||||
let (log_tx, log_rx) = std::sync::mpsc::channel::<String>();
|
||||
|
||||
// Thread auxiliar: encaminha cada linha de log para o canal da UI
|
||||
@@ -251,10 +262,12 @@ impl App {
|
||||
});
|
||||
|
||||
let rt = tokio::runtime::Runtime::new().expect("Falha ao criar runtime tokio");
|
||||
let result = rt.block_on(run_ffmpeg_async(args, log_tx, cancel_rx));
|
||||
let result = if uses_mkvmerge {
|
||||
rt.block_on(run_mkvmerge_async(args, log_tx, cancel_rx))
|
||||
} else {
|
||||
rt.block_on(run_ffmpeg_async(args, log_tx, cancel_rx))
|
||||
};
|
||||
|
||||
// Aguarda o encaminhador consumir todas as linhas pendentes
|
||||
// antes de enviar Done/Error, garantindo ordem correta no log
|
||||
let _ = fwd_handle.join();
|
||||
|
||||
match result {
|
||||
@@ -279,13 +292,19 @@ impl App {
|
||||
}
|
||||
|
||||
/// Inicia o processamento de um item específico do carrinho de lote.
|
||||
/// Despacha para FFmpeg ou mkvmerge dependendo de `project.needs_mkvmerge()`.
|
||||
fn start_batch_item(&mut self, index: usize, ctx: egui::Context) {
|
||||
let project = self.batch_items[index].project.clone();
|
||||
self.batch_items[index].state = ExecutionState::Running;
|
||||
self.batch_items[index].log_lines.clear();
|
||||
self.batch_processing_index = Some(index);
|
||||
|
||||
let args = FfmpegCommandBuilder::build(&project);
|
||||
let uses_mkvmerge = project.needs_mkvmerge();
|
||||
let args = if uses_mkvmerge {
|
||||
MkvmergeCommandBuilder::build(&project)
|
||||
} else {
|
||||
FfmpegCommandBuilder::build(&project)
|
||||
};
|
||||
let (tx, rx) = mpsc::channel::<BackgroundMsg>();
|
||||
self.bg_rx = Some(rx);
|
||||
|
||||
@@ -305,7 +324,11 @@ impl App {
|
||||
});
|
||||
|
||||
let rt = tokio::runtime::Runtime::new().expect("Falha ao criar runtime tokio");
|
||||
let result = rt.block_on(run_ffmpeg_async(args, log_tx, cancel_rx));
|
||||
let result = if uses_mkvmerge {
|
||||
rt.block_on(run_mkvmerge_async(args, log_tx, cancel_rx))
|
||||
} else {
|
||||
rt.block_on(run_ffmpeg_async(args, log_tx, cancel_rx))
|
||||
};
|
||||
let _ = fwd_handle.join();
|
||||
|
||||
match result {
|
||||
@@ -577,7 +600,7 @@ impl eframe::App for App {
|
||||
.auto_shrink([false, false])
|
||||
.show(ui, |ui| {
|
||||
let is_processing = self.batch_processing_index.is_some();
|
||||
let events = self.batch_panel.ui(ui, &self.batch_items, is_processing);
|
||||
let events = self.batch_panel.ui(ui, &self.batch_items, is_processing, self.mkvmerge_available);
|
||||
for event in events {
|
||||
match event {
|
||||
BatchPanelEvent::AddItem(mut project) => {
|
||||
@@ -621,7 +644,8 @@ impl eframe::App for App {
|
||||
.show(ui, |ui| {
|
||||
if let Some(project) = &mut self.project {
|
||||
// ── Faixas existentes ─────────────────────────────────
|
||||
let events = self.existing_track_list.ui(ui, &project.existing_tracks);
|
||||
let mkvmerge_available = self.mkvmerge_available;
|
||||
let events = self.existing_track_list.ui(ui, &project.existing_tracks, mkvmerge_available);
|
||||
for event in events {
|
||||
use crate::domain::entities::TrackKind;
|
||||
use crate::ui::components::existing_track_list::ExistingTrackEvent;
|
||||
@@ -629,6 +653,9 @@ impl eframe::App for App {
|
||||
ExistingTrackEvent::OffsetChanged(id, offset) => {
|
||||
let _ = EditExistingTrackSync::execute(project, id, offset);
|
||||
}
|
||||
ExistingTrackEvent::DriftChanged(id, scale) => {
|
||||
let _ = AdjustExistingTrackDrift::execute(project, id, scale);
|
||||
}
|
||||
ExistingTrackEvent::ExportRequested(id) => {
|
||||
if let Some(track) =
|
||||
project.existing_tracks.iter().find(|t| t.id == id).cloned()
|
||||
@@ -697,7 +724,7 @@ impl eframe::App for App {
|
||||
.allocate_ui_with_layout(
|
||||
egui::Vec2::new(col_w, 0.0),
|
||||
egui::Layout::top_down(egui::Align::Min),
|
||||
|ui| self.add_audio_form.ui(ui),
|
||||
|ui| self.add_audio_form.ui(ui, mkvmerge_available),
|
||||
)
|
||||
.inner;
|
||||
|
||||
@@ -705,15 +732,15 @@ impl eframe::App for App {
|
||||
.allocate_ui_with_layout(
|
||||
egui::Vec2::new(col_w, 0.0),
|
||||
egui::Layout::top_down(egui::Align::Min),
|
||||
|ui| self.add_subtitle_form.ui(ui),
|
||||
|ui| self.add_subtitle_form.ui(ui, mkvmerge_available),
|
||||
)
|
||||
.inner;
|
||||
|
||||
(audio_req, subtitle_req)
|
||||
} else {
|
||||
let ar = self.add_audio_form.ui(ui);
|
||||
let ar = self.add_audio_form.ui(ui, mkvmerge_available);
|
||||
ui.add_space(4.0);
|
||||
let sr = self.add_subtitle_form.ui(ui);
|
||||
let sr = self.add_subtitle_form.ui(ui, mkvmerge_available);
|
||||
(ar, sr)
|
||||
};
|
||||
|
||||
@@ -725,6 +752,7 @@ impl eframe::App for App {
|
||||
req.language,
|
||||
req.is_default,
|
||||
req.title,
|
||||
req.drift_scale,
|
||||
);
|
||||
}
|
||||
if let Some(req) = subtitle_req {
|
||||
@@ -735,6 +763,7 @@ impl eframe::App for App {
|
||||
req.language,
|
||||
req.is_default,
|
||||
req.title,
|
||||
req.drift_scale,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user