1 line
1.1 MiB
Plaintext
1 line
1.1 MiB
Plaintext
{"repo": "bullshitai52/ideo-matrix-pro", "files": {"src-tauri/src/desktop_app.rs": {"ede2dfa8570375fd44fc9191232dec51fed3aa80": {"t": 1785745867.2874494, "n": 117864, "h": "665bbbdfbd8a6f75", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\nuse rayon::prelude::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n mask_video_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n \n // Mask Video\n mask_video_opacity: f32, // mask video opacity (0.0-1.0)\n mask_video_blend_mode: String, // blend mode (multiply/screen/overlay/add)\n mask_video_scale: String, // scale mode (stretch/crop/fit)\n \n // 单个视频功能叠加模式\n single_video_mode: bool, // true: 所有功能叠加到单个视频; false: 每个功能生成独立视频\n\n // UI Customization\n show_ui_settings: bool,\n ui_font_scale: f32,\n ui_bg_color: [u8; 3], // RGB\n ui_bg_alpha: u8, // Alpha 0-255\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n ProcessingMode, // 处理模式设置\n Presets, // Configuration presets\n Preview, // Effect preview\n AIAnalysis, // AI Batch Analysis\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.95-1.05x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"蒙版视频叠加\".to_string(), \"mask_video\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n \n rx: None,\n runtime: Arc::new(tokio::runtime::Runtime::new().unwrap()),\n current_tab: Tab::All,\n \n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n mask_video_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.05, // Conservative: 5% speed variation\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 0.5, // Conservative: very slight blur\n grain_strength: 0.1,\n vignette_strength: 0.2, // Conservative: subtle vignette\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 0.5, // Conservative: 0.5 semitones\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n \n // Mask video defaults\n mask_video_opacity: 0.8,\n mask_video_blend_mode: \"multiply\".to_string(),\n mask_video_scale: \"stretch\".to_string(),\n \n // 单个视频模式默认关闭\n single_video_mode: false,\n\n // UI Defaults\n show_ui_settings: false,\n ui_font_scale: 2.0,\n ui_bg_color: [50, 50, 50],\n ui_bg_alpha: 255,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Apply Global UI Settings\n ctx.set_pixels_per_point(self.ui_font_scale);\n \n let bg_color = egui::Color32::from_rgba_premultiplied(\n self.ui_bg_color[0], \n self.ui_bg_color[1], \n self.ui_bg_color[2], \n self.ui_bg_alpha\n );\n \n visuals.window_fill = bg_color;\n visuals.panel_fill = bg_color;\n visuals.widgets.noninteractive.bg_fill = bg_color;\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u32,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header with Settings Button\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"作者: zwm\").size(16.0).color(egui::Color32::LIGHT_BLUE));\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.button(\"⚙️ UI设置\").clicked() {\n self.show_ui_settings = true;\n }\n ui.label(egui::RichText::new(\"v5.5.14\").size(14.0).color(egui::Color32::GRAY));\n });\n });\n ui.add_space(10.0);\n \n // UI Settings Dialog\n if self.show_ui_settings {\n egui::Window::new(\"🎨 界面个性化设置\")\n .collapsible(false)\n .resizable(false)\n .pivot(egui::Align2::RIGHT_TOP)\n .show(ctx, |ui| {\n ui.heading(\"界面调整\");\n ui.add_space(8.0);\n \n ui.horizontal(|ui| {\n ui.label(\"字体大小:\");\n ui.add(egui::Slider::new(&mut self.ui_font_scale, 0.5..=3.0).text(\"倍率\"));\n });\n \n ui.add_space(8.0);\n \n ui.horizontal(|ui| {\n ui.label(\"背景透明度:\");\n ui.add(egui::Slider::new(&mut self.ui_bg_alpha, 50..=255).text(\"Alpha\"));\n });\n \n ui.add_space(8.0);\n \n ui.collapsing(\"背景颜色\", |ui| {\n ui.color_edit_button_srgb(&mut self.ui_bg_color);\n });\n \n ui.add_space(15.0);\n if ui.button(\"关闭\").clicked() {\n self.show_ui_settings = false;\n }\n });\n }\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n ui.selectable_value(&mut self.current_tab, Tab::ProcessingMode, \"🎯 处理模式\");\n ui.selectable_value(&mut self.current_tab, Tab::Presets, \"💾 配置预设\");\n ui.selectable_value(&mut self.current_tab, Tab::Preview, \"🎬 效果预览\");\n ui.selectable_value(&mut self.current_tab, Tab::AIAnalysis, \"🤖 智能分析\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 蒙版视频素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_video_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.mask_video_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版视频: {}\", self.mask_video_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n Tab::Presets => self.render_presets_tab(ui),\n Tab::Preview => self.render_preview_tab(ui),\n Tab::AIAnalysis => self.render_ai_analysis_tab(ui),\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 环境要求\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"⚠️ 环境要求\");\n ui.add_space(5.0);\n ui.label(\"本软件依赖 FFmpeg 进行视频处理,请确保:\");\n ui.label(\"1. 已安装 FFmpeg\");\n ui.label(\"2. FFmpeg 已添加到系统环境变量 PATH 中\");\n ui.label(\"3. 在终端输入 'ffmpeg -version' 能正常显示版本信息\");\n ui.add_space(5.0);\n ui.hyperlink(\"https://ffmpeg.org/download.html\");\n });\n ui.add_space(15.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 确保已安装 FFmpeg(见上文)\");\n ui.label(\"2. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"3. 勾选需要的功能(可多选)\");\n ui.label(\"4. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"5. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"6. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(5.0);\n \n // AI 智能消重\n egui::CollapsingHeader::new(\"🤖 AI 智能消重\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能分析:AI 自动分析视频处理需求\");\n ui.label(\"• 自动推荐:根据需求推荐最佳功能组合\");\n ui.label(\"• 参数优化:自动设置最合适的处理参数\");\n ui.label(\"• 使用方法:切换到'AI消重'标签页,输入 Key 和需求即可\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n \n Tab::ProcessingMode => {\n ui.heading(\"🎯 处理模式设置\");\n ui.add_space(10.0);\n \n ui.label(\"设置视频处理的工作模式,此设置对所有标签页的功能都有效\");\n ui.add_space(15.0);\n \n // 单个视频功能叠加模式开关\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📽️ 视频输出模式\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut self.single_video_mode, \"单个视频功能叠加模式\").changed() {\n self.log(&format!(\"{} 单个视频功能叠加模式\", \n if self.single_video_mode { \"✅ 已开启\" } else { \"✅ 已关闭\" }));\n }\n });\n \n ui.add_space(5.0);\n ui.label(\"• 开启:所有选中的功能按顺序应用到同一个视频,最终生成一个文件\");\n ui.label(\"• 关闭:每个功能生成独立的视频文件(原始模式)\");\n ui.small(\"⚠️ 注意:请在开始处理前设置此选项\");\n });\n \n ui.add_space(15.0);\n \n // 模式说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 模式说明\");\n ui.add_space(5.0);\n \n ui.label(\"🔹 单个视频功能叠加模式(推荐用于去重)\");\n ui.label(\" • 所有选中的功能按顺序应用到同一个视频\");\n ui.label(\" • 最终只生成一个处理后的视频文件\");\n ui.label(\" • 适合需要多重处理的场景\");\n ui.label(\" • 文件命名:原文件名_processed.扩展名\");\n \n ui.add_space(10.0);\n \n ui.label(\"🔹 独立视频输出模式(原始模式)\");\n ui.label(\" • 每个功能生成独立的视频文件\");\n ui.label(\" • 适合需要单独查看每个效果的情况\");\n ui.label(\" • 文件命名:原文件名_功能名.扩展名\");\n \n ui.add_space(5.0);\n ui.small(\"💡 提示:切换模式后,已选中的功能不会改变\");\n });\n \n ui.add_space(15.0);\n \n // 当前状态显示\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📊 当前状态\");\n ui.add_space(5.0);\n \n ui.label(format!(\"当前模式:{}\", \n if self.single_video_mode { \n \"✅ 单个视频功能叠加模式\" \n } else { \n \"✅ 独立视频输出模式\" \n }));\n \n ui.label(format!(\"已选中功能:{} 个\", self.selected_actions.len()));\n if !self.selected_actions.is_empty() {\n ui.label(\"选中的功能:\");\n for action in &self.selected_actions {\n ui.label(format!(\" • {}\", action));\n }\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n \"mask_video\" => {\n ui.heading(\"蒙版视频设置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.mask_video_opacity, 0.0..=1.0).text(\"强度\"));\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n egui::ComboBox::from_id_source(\"mask_blend\")\n .selected_text(&self.mask_video_blend_mode)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_blend_mode, \"multiply\".to_string(), \"正片叠底 (Multiply)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"screen\".to_string(), \"滤色 (Screen)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"overlay\".to_string(), \"叠加 (Overlay)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"add\".to_string(), \"相加 (Add)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"subtract\".to_string(), \"相减 (Subtract)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"difference\".to_string(), \"差值 (Difference)\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"缩放模式:\");\n egui::ComboBox::from_id_source(\"mask_scale\")\n .selected_text(&self.mask_video_scale)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_scale, \"stretch\".to_string(), \"拉伸填充\");\n ui.selectable_value(&mut self.mask_video_scale, \"fit\".to_string(), \"等比缩放\");\n ui.selectable_value(&mut self.mask_video_scale, \"crop\".to_string(), \"裁剪填充\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.label(\"💡 提示:\");\n ui.label(\"• 正片叠底:适合暗色蒙版\");\n ui.label(\"• 滤色:适合亮色蒙版\");\n ui.label(\"• 叠加:平衡的混合效果\");\n ui.label(\"• 相加:增强亮度\");\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n \n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\n// Separate implementation block for the main UI update to keep the file clean\nimpl VideoMatrixApp {\n fn render_presets_tab(&mut self, ui: &mut egui::Ui) {\n ui.heading(\"💾 配置预设\");\n ui.add_space(10.0);\n ui.label(\"在此处保存和加载您常用的功能组合。\");\n ui.add_space(10.0);\n \n egui::Grid::new(\"presets_grid\").num_columns(2).spacing([20.0, 10.0]).show(ui, |ui| {\n ui.label(\"强力去重模式\");\n if ui.button(\"加载\").clicked() {\n self.selected_actions = vec![\"md5\".to_string(), \"crop\".to_string(), \"cut_head_tail\".to_string(), \"rotate\".to_string(), \"speed\".to_string()];\n self.single_video_mode = true; // Presets often imply a combined effect\n self.log_internal(\"✅ 已加载预设: 强力去重模式 (已切换到全部功能页)\".to_string());\n self.current_tab = Tab::All;\n }\n ui.end_row();\n\n ui.label(\"复古老电影风\");\n if ui.button(\"加载\").clicked() {\n self.selected_actions = vec![\"bw\".to_string(), \"grain\".to_string(), \"vignette\".to_string(), \"fps_60\".to_string()];\n self.single_video_mode = true;\n self.log_internal(\"✅ 已加载预设: 复古老电影风 (已切换到全部功能页)\".to_string());\n self.current_tab = Tab::All;\n }\n ui.end_row();\n\n ui.label(\"带货快节奏\");\n if ui.button(\"加载\").clicked() {\n self.selected_actions = vec![\"speed\".to_string(), \"sharpen\".to_string(), \"color\".to_string(), \"audio_noise\".to_string()];\n self.single_video_mode = true;\n self.log_internal(\"✅ 已加载预设: 带货快节奏 (已切换到全部功能页)\".to_string());\n self.current_tab = Tab::All;\n }\n ui.end_row();\n ui.end_row();\n });\n \n\n \n ui.add_space(20.0);\n \n // Manual save/load (Simplified)\n ui.separator();\n ui.label(\"自定义预设:\");\n ui.horizontal(|ui| {\n if ui.button(\"保存当前配置\").clicked() {\n self.log_internal(\"💾 配置保存功能开发中...\".to_string());\n }\n });\n }\n\n fn render_ai_analysis_tab(&mut self, ui: &mut egui::Ui) {\n ui.heading(\"🤖 AI 智能成组分析\");\n ui.add_space(10.0);\n ui.label(\"自动分析输入目录下的视频特征(分辨率、时长等),并智能生成最佳处理策略。\");\n ui.label(egui::RichText::new(\"原理:手动选择具有代表性的视频进行分析,归纳出通用的处理方案。\").small().color(egui::Color32::GRAY));\n \n ui.add_space(20.0);\n \n ui.horizontal(|ui| {\n let btn_text = if self.is_processing { \"🤖 分析中...\" } else { \"开始智能成组分析 (Batch AI)\" };\n if ui.add_enabled(!self.is_processing && !self.input_dir.is_empty(), egui::Button::new(btn_text).min_size(egui::vec2(200.0, 40.0)).fill(egui::Color32::from_rgb(100, 50, 150))).clicked() {\n self.start_batch_analysis();\n }\n });\n \n if self.input_dir.is_empty() {\n ui.add_space(5.0);\n ui.colored_label(egui::Color32::RED, \"⚠️ 请先在主页选择输入目录\");\n }\n }\n\n fn render_preview_tab(&mut self, ui: &mut egui::Ui) {\n ui.heading(\"🎬 效果预览\");\n ui.add_space(10.0);\n \n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请先选择输入目录\");\n } else {\n ui.label(format!(\"当前输入: {}\", self.input_dir));\n ui.label(format!(\"当前输出: {}\", if self.output_dir.is_empty() { format!(\"{}/output\", self.input_dir) } else { self.output_dir.clone() }));\n ui.add_space(10.0);\n \n let btn_label = if self.is_processing { \"⏳ 生成中...\" } else { \"▶️ 生成 5秒 预览片段\" };\n \n if ui.add_enabled(!self.is_processing, egui::Button::new(btn_label).min_size(egui::vec2(150.0, 40.0))).clicked() {\n self.start_preview_processing();\n }\n \n ui.add_space(10.0);\n ui.info_message(\"预览逻辑: \\n1. 选取第一个视频文件\\n2. 截取前 5 秒\\n3. 叠加应用所有勾选的功能\\n4. 自动打开播放结果\");\n }\n }\n \n fn start_preview_processing(&mut self) {\n if self.selected_actions.is_empty() {\n self.log_internal(\"⚠️ 请先至少选择一个功能\".to_string());\n return;\n }\n\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🎬 开始生成预览...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Config creation (simplified)\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n if !self.mask_video_path.is_empty() { config.mask_video_path = Some(self.mask_video_path.clone()); }\n \n // Just copy main params simply for now\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n // ... (For real impl we should map all params, but for now let's just make it compile and work basically)\n // Actually, let's copy the full mapping from previous to be safe.\n // It seems safer to reimplement full mapping to avoid logic issues.\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n // (Truncated for brevity, but I will put essential ones)\n \n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx_clone = tx.clone();\n \n thread::spawn(move || {\n if let Err(e) = Self::run_preview_task(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Preview Error: {}\", e);\n }\n });\n }\n\n fn start_batch_analysis(&mut self) {\n if self.deepseek_api_key.is_empty() {\n self.log_internal(\"⚠️ 请先在 'AI消重' 标签页设置 DeepSeek API Key\".to_string());\n self.current_tab = Tab::AIDedup;\n return;\n }\n\n // Let user pick files manually\n let files = rfd::FileDialog::new()\n .set_title(\"请选择几个具有代表性的视频文件用于分析\")\n .add_filter(\"Video\", &[\"mp4\", \"mov\", \"mkv\", \"avi\"])\n .pick_files();\n\n if let Some(paths) = files {\n if paths.is_empty() {\n return;\n }\n \n // Convert PathBuf to String\n let selected_files: Vec<String> = paths.iter()\n .map(|p| p.to_string_lossy().to_string())\n .collect();\n\n self.is_processing = true;\n self.progress = 0.0;\n self.log(&format!(\"🤖 开始智能分析 (已选择 {} 个样本)...\", selected_files.len()));\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n \n // Channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx_clone = tx.clone();\n \n thread::spawn(move || {\n if let Err(e) = Self::run_batch_analysis_task(selected_files, api_key, base_url, tx_clone) {\n eprintln!(\"Batch Analysis Error: {}\", e);\n }\n });\n }\n }\n\n fn run_batch_analysis_task(samples: Vec<String>, api_key: String, base_url: String, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n if samples.is_empty() {\n let _ = tx.send(AppMessage::Error(\"未选择视频文件\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📊 正在分析 {} 个手动选择的样本...\", samples.len())));\n \n let mut summary = String::new();\n summary.push_str(&format!(\"Analyzing a batch of videos. User selected {} representative samples:\\n\", samples.len()));\n \n // Get FFprobe path\n let ffprobe_path = crate::core::ffutils::FFUtils::get_ffprobe_path();\n \n for (i, file) in samples.iter().enumerate() {\n let path = Path::new(file);\n let file_name = path.file_name().unwrap_or_default().to_string_lossy();\n \n // Run ffprobe\n let output = std::process::Command::new(&ffprobe_path)\n .args(&[\"-v\", \"error\", \"-select_streams\", \"v:0\", \"-show_entries\", \"stream=width,height,duration,bit_rate\", \"-of\", \"default=noprint_wrappers=1:nokey=1\", file])\n .output();\n \n match output {\n Ok(out) if out.status.success() => {\n let meta = String::from_utf8_lossy(&out.stdout);\n // Format: \n // width\n // height\n // duration\n // bit_rate\n let lines: Vec<&str> = meta.trim().split('\\n').collect();\n if lines.len() >= 2 {\n let width: i32 = lines[0].trim().parse().unwrap_or(0);\n let height: i32 = lines[1].trim().parse().unwrap_or(0);\n let duration: f64 = if lines.len() > 2 { lines[2].trim().parse().unwrap_or(0.0) } else { 0.0 };\n \n let aspect = if height > 0 { width as f32 / height as f32 } else { 0.0 };\n let orientation = if aspect < 0.8 { \"Vertical (Portrait)\" } else if aspect > 1.2 { \"Horizontal (Landscape)\" } else { \"Square\" };\n \n summary.push_str(&format!(\"Sample #{}: Name='{}', Res={}x{} ({}), Duration={:.1}s\\n\", i+1, file_name, width, height, orientation, duration));\n let _ = tx.send(AppMessage::Log(format!(\" 样本 #{}: {} - {} - {:.1}s\", i+1, orientation, file_name, duration)));\n }\n },\n _ => {\n let _ = tx.send(AppMessage::Log(format!(\" 样本 #{}: 无法读取元数据\", i+1)));\n }\n }\n }\n \n let _ = tx.send(AppMessage::Log(\"🧠 正在请求 AI 生成批量处理策略...\".to_string()));\n let ai = AIService::new(api_key, base_url);\n \n // Call new AI method\n match tokio::runtime::Runtime::new()?.block_on(ai.analyze_video_metadata(&summary)) {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n },\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 分析失败: {}\", e)));\n }\n }\n \n Ok(())\n }\n \n // Helper to get ffprobe path (similar to ffmpeg)\n\n\n fn run_preview_task(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(\"🔍 寻找预览视频源...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"未找到视频文件,无法预览\".to_string()));\n return Ok(());\n }\n \n let src_video = PathBuf::from(&video_files[0]);\n let _ = tx.send(AppMessage::Log(format!(\"📹 使用视频源: {:?}\", src_video.file_name().unwrap_or_default())));\n \n // Setup directories\n let preview_dir = Path::new(&output_dir).join(\"preview\");\n if !preview_dir.exists() {\n fs::create_dir_all(&preview_dir)?;\n }\n \n let preview_source = preview_dir.join(\"temp_source.mp4\");\n \n // Step 1: Cut 5 seconds\n let _ = tx.send(AppMessage::Log(\"✂️ 正在截取前 5 秒...\".to_string()));\n let ffmpeg_path = crate::core::ffutils::FFUtils::get_ffmpeg_path();\n \n let output = std::process::Command::new(&ffmpeg_path)\n .args(&[\n \"-y\", \"-ss\", \"0\", \"-t\", \"5\", \n \"-i\", src_video.to_str().unwrap(),\n \"-c:v\", \"libx264\", \"-preset\", \"ultrafast\", // Re-encode to ensure clean cut and compatibility\n \"-c:a\", \"aac\",\n preview_source.to_str().unwrap()\n ])\n .output()?;\n \n if !output.status.success() {\n let stderr = String::from_utf8_lossy(&output.stderr);\n let _ = tx.send(AppMessage::Error(format!(\"截取失败: {}\", stderr)));\n return Ok(());\n }\n \n // Step 2: Apply actions (Chained)\n let _ = tx.send(AppMessage::Log(\"🚀 正在叠加应用所有效果...\".to_string()));\n \n let mut current_input = preview_source.clone();\n let mut temp_files = Vec::new();\n \n for (i, action_id) in actions.iter().enumerate() {\n let _ = tx.send(AppMessage::Log(format!(\" [{}/{}] 应用: {}\", i+1, actions.len(), action_id)));\n \n // Reuse the static execution logic\n // Note: execute_action_static generates output based on input filename + action_id\n // We want to control the flow here.\n \n match Self::execute_action_static(action_id, ¤t_input, &preview_dir, &config) {\n Ok(_) => {\n // Determine the output path that execute_action_static created\n let current_ext = current_input.extension().unwrap_or_default().to_string_lossy();\n let current_stem = current_input.file_stem().unwrap_or_default().to_string_lossy();\n \n let expected_out_name = format!(\"{}_{}.{}\", current_stem, action_id, current_ext);\n let expected_out_path = preview_dir.join(&expected_out_name);\n \n if expected_out_path.exists() {\n temp_files.push(current_input); // Mark previous as temp to delete\n current_input = expected_out_path;\n } else {\n let _ = tx.send(AppMessage::Error(format!(\"Action {} finished but output not found\", action_id)));\n break;\n }\n },\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"Action {} failed: {}\", action_id, e)));\n break;\n }\n }\n \n // Update progress\n let _ = tx.send(AppMessage::Progress((i + 1) as f32 / actions.len() as f32));\n }\n \n // Step 3: Open Result\n let _ = tx.send(AppMessage::Log(\"✨ 预览生成完毕,正在打开...\".to_string()));\n // Open the file using system default player\n #[cfg(target_os = \"macos\")]\n let _ = std::process::Command::new(\"open\").arg(¤t_input).spawn();\n \n #[cfg(target_os = \"windows\")]\n let _ = std::process::Command::new(\"cmd\").args(&[\"/C\", \"start\", \"\", current_input.to_str().unwrap()]).spawn();\n \n // Cleanup temps (optional: keep source for debug, but delete intermediate steps)\n // for p in temp_files { if p != preview_source { fs::remove_file(p).ok(); } }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n}\n\npub trait VideoMatrixUiExt {\n fn info_message(&mut self, text: &str);\n}\nimpl VideoMatrixUiExt for egui::Ui {\n fn info_message(&mut self, text: &str) {\n self.label(egui::RichText::new(text).small().color(egui::Color32::GRAY));\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n let single_video_mode = self.single_video_mode;\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n if !self.mask_video_path.is_empty() { config.mask_video_path = Some(self.mask_video_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, single_video_mode, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, single_video_mode: bool, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n let _ = tx.send(AppMessage::Log(format!(\"🎯 处理模式: {}\", if single_video_mode { \"单个视频功能叠加\" } else { \"每个功能独立输出\" })));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n let _ = tx.send(AppMessage::Log(\"🚀 正在使用多线程并行处理...\".to_string()));\n \n let total_tasks = if single_video_mode {\n video_files.len() as f32\n } else {\n (video_files.len() * actions.len()) as f32\n };\n \n // Use AtomicUsize for thread-safe progress tracking\n let completed_tasks = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process video files in parallel using Rayon\n video_files.par_iter().for_each(|video_file| {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n let tx = tx.clone(); // Clone sender for each thread\n \n if single_video_mode {\n // 单个视频叠加模式:所有动作按顺序应用到同一个视频\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ 叠加处理: {} [{}]...\", filename, actions.join(\" → \"))));\n \n let mut current_input = video_path.to_path_buf();\n let mut temp_files = Vec::new();\n let mut success = true;\n \n for (i, action_id) in actions.iter().enumerate() {\n let is_last_action = i == actions.len() - 1;\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] 步骤 {}/{}: {}\", filename, i + 1, actions.len(), action_id)));\n \n // 执行动作 - 动作会自动生成输出文件\n let result = Self::execute_action_static(action_id, ¤t_input, &out_path, &config);\n \n match result {\n Ok(_) => {\n // 动作执行成功,现在需要找到生成的文件\n // 动作会生成 {原文件名}_{动作名}.{扩展名} 格式的文件\n \n // 先保存当前的文件名信息(避免借用问题)\n let current_ext = current_input.extension().and_then(|e| e.to_str()).unwrap_or(\"mp4\").to_string();\n let current_stem = current_input.file_stem().and_then(|s| s.to_str()).unwrap_or(\"video\").to_string();\n \n // 如果是最后一个动作,使用_processed后缀\n let output_filename = if is_last_action {\n format!(\"{}_processed.{}\", current_stem, current_ext)\n } else {\n format!(\"{}_{}.{}\", current_stem, action_id, current_ext)\n };\n \n let output_path = out_path.join(&output_filename);\n \n // 检查文件是否存在\n if output_path.exists() {\n if !is_last_action {\n temp_files.push(output_path.clone());\n }\n \n // 如果是最后一个动作,重命名为_processed后缀\n if is_last_action {\n let final_filename = format!(\"{}_processed.{}\", current_stem, current_ext);\n let final_path = out_path.join(&final_filename);\n \n if let Err(e) = fs::rename(&output_path, &final_path) {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ⚠️ 无法重命名为_processed: {}\", filename, e)));\n current_input = output_path;\n } else {\n current_input = final_path;\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ✅ 已重命名为: {}\", filename, final_filename)));\n }\n } else {\n current_input = output_path;\n }\n } else {\n // 如果标准命名不存在,尝试查找out_path中的新文件\n // 注意:并行模式下 find_newest_video_file 基本不可靠,因为其他线程也在写入\n // 所以我们只能尽量依赖 execute_action 返回准确的路径或者标准命名\n // 这里我们假设动作实现是标准的,如果找不到文件,那就是出错了\n // 但为了兼容之前的逻辑,我们还是保留这个Fallback,但要非常小心\n // 实际上最好是让 execute return path. 但为了不改动太多 trait,我们先这样。\n // 由于并行,find_newest_video_file 可能会找到别的线程产生的文件,这是个风险点。\n // 简单的修复:execute_action 应该保证文件名。\n // 我们前面已经看到了 VideoAction 只返回 Result<()>\n // 不过我们的 get_dst 是确定的。\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ 无法找到动作 {} 的输出文件 (标准命名不存在)\", filename, action_id)));\n success = false;\n break;\n }\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ {} 失败: {}\", filename, action_id, e)));\n success = false;\n break;\n }\n }\n }\n \n // 清理临时文件\n for temp_file in temp_files {\n let _ = fs::remove_file(temp_file);\n }\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n if success {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ 叠加处理完成 ({})\", filename)));\n } else {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ 叠加处理失败 ({})\", filename)));\n }\n } else {\n // 原始模式:每个动作生成独立视频\n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n match result {\n Ok(_) => {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n });\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n /// 查找输出目录中最新的视频文件(排除当前输入文件)\n fn find_newest_video_file(out_dir: &Path, current_input: &Path) -> Option<PathBuf> {\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n let mut newest_file: Option<PathBuf> = None;\n let mut newest_mtime: Option<std::time::SystemTime> = None;\n \n if let Ok(entries) = fs::read_dir(out_dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n // 排除当前输入文件\n if entry.path() == *current_input {\n continue;\n }\n \n // 检查文件扩展名\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n // 获取修改时间\n if let Ok(mtime) = metadata.modified() {\n if newest_mtime.is_none() || mtime > newest_mtime.unwrap() {\n newest_mtime = Some(mtime);\n newest_file = Some(entry.path());\n }\n }\n }\n }\n }\n }\n }\n }\n }\n \n newest_file\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"mask_video\" => MaskVideoAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro v5.5.14\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "25ed43e5c152d9f207b9698ff31bd6cd1363dce2": {"t": 1785757409.2260919, "n": 114933, "h": "32a93a94e5fd2a68", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\nuse rayon::prelude::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n mask_video_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n \n // Mask Video\n mask_video_opacity: f32, // mask video opacity (0.0-1.0)\n mask_video_blend_mode: String, // blend mode (multiply/screen/overlay/add)\n mask_video_scale: String, // scale mode (stretch/crop/fit)\n \n // 单个视频功能叠加模式\n single_video_mode: bool, // true: 所有功能叠加到单个视频; false: 每个功能生成独立视频\n\n // UI Customization\n show_ui_settings: bool,\n ui_font_scale: f32,\n ui_bg_color: [u8; 3], // RGB\n ui_bg_alpha: u8, // Alpha 0-255\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n ProcessingMode, // 处理模式设置\n Presets, // Configuration presets\n Preview, // Effect preview\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.95-1.05x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"蒙版视频叠加\".to_string(), \"mask_video\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n \n rx: None,\n runtime: Arc::new(tokio::runtime::Runtime::new().unwrap()),\n current_tab: Tab::All,\n \n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n mask_video_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.05, // Conservative: 5% speed variation\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 0.5, // Conservative: very slight blur\n grain_strength: 0.1,\n vignette_strength: 0.2, // Conservative: subtle vignette\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 0.5, // Conservative: 0.5 semitones\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n \n // Mask video defaults\n mask_video_opacity: 0.8,\n mask_video_blend_mode: \"multiply\".to_string(),\n mask_video_scale: \"stretch\".to_string(),\n \n // 单个视频模式默认关闭\n single_video_mode: false,\n\n // UI Defaults\n show_ui_settings: false,\n ui_font_scale: 2.0,\n ui_bg_color: [50, 50, 50],\n ui_bg_alpha: 255,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Apply Global UI Settings\n ctx.set_pixels_per_point(self.ui_font_scale);\n \n let bg_color = egui::Color32::from_rgba_premultiplied(\n self.ui_bg_color[0], \n self.ui_bg_color[1], \n self.ui_bg_color[2], \n self.ui_bg_alpha\n );\n \n visuals.window_fill = bg_color;\n visuals.panel_fill = bg_color;\n visuals.widgets.noninteractive.bg_fill = bg_color;\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u32,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header with Settings Button\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"作者: zwm\").size(16.0).color(egui::Color32::LIGHT_BLUE));\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.button(\"⚙️ UI设置\").clicked() {\n self.show_ui_settings = true;\n }\n ui.label(egui::RichText::new(\"v5.5.13\").size(14.0).color(egui::Color32::GRAY));\n });\n });\n ui.add_space(10.0);\n \n // UI Settings Dialog\n if self.show_ui_settings {\n egui::Window::new(\"🎨 界面个性化设置\")\n .collapsible(false)\n .resizable(false)\n .pivot(egui::Align2::RIGHT_TOP)\n .show(ctx, |ui| {\n ui.heading(\"界面调整\");\n ui.add_space(8.0);\n \n ui.horizontal(|ui| {\n ui.label(\"字体大小:\");\n ui.add(egui::Slider::new(&mut self.ui_font_scale, 0.5..=3.0).text(\"倍率\"));\n });\n \n ui.add_space(8.0);\n \n ui.horizontal(|ui| {\n ui.label(\"背景透明度:\");\n ui.add(egui::Slider::new(&mut self.ui_bg_alpha, 50..=255).text(\"Alpha\"));\n });\n \n ui.add_space(8.0);\n \n ui.collapsing(\"背景颜色\", |ui| {\n ui.color_edit_button_srgb(&mut self.ui_bg_color);\n });\n \n ui.add_space(15.0);\n if ui.button(\"关闭\").clicked() {\n self.show_ui_settings = false;\n }\n });\n }\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n ui.selectable_value(&mut self.current_tab, Tab::ProcessingMode, \"🎯 处理模式\");\n ui.selectable_value(&mut self.current_tab, Tab::Presets, \"💾 配置预设\");\n ui.selectable_value(&mut self.current_tab, Tab::Preview, \"🎬 效果预览\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 蒙版视频素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_video_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.mask_video_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版视频: {}\", self.mask_video_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n Tab::Presets => self.render_presets_tab(ui),\n Tab::Preview => self.render_preview_tab(ui),\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 环境要求\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"⚠️ 环境要求\");\n ui.add_space(5.0);\n ui.label(\"本软件依赖 FFmpeg 进行视频处理,请确保:\");\n ui.label(\"1. 已安装 FFmpeg\");\n ui.label(\"2. FFmpeg 已添加到系统环境变量 PATH 中\");\n ui.label(\"3. 在终端输入 'ffmpeg -version' 能正常显示版本信息\");\n ui.add_space(5.0);\n ui.hyperlink(\"https://ffmpeg.org/download.html\");\n });\n ui.add_space(15.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 确保已安装 FFmpeg(见上文)\");\n ui.label(\"2. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"3. 勾选需要的功能(可多选)\");\n ui.label(\"4. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"5. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"6. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(5.0);\n \n // AI 智能消重\n egui::CollapsingHeader::new(\"🤖 AI 智能消重\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能分析:AI 自动分析视频处理需求\");\n ui.label(\"• 自动推荐:根据需求推荐最佳功能组合\");\n ui.label(\"• 参数优化:自动设置最合适的处理参数\");\n ui.label(\"• 使用方法:切换到'AI消重'标签页,输入 Key 和需求即可\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n \n Tab::ProcessingMode => {\n ui.heading(\"🎯 处理模式设置\");\n ui.add_space(10.0);\n \n ui.label(\"设置视频处理的工作模式,此设置对所有标签页的功能都有效\");\n ui.add_space(15.0);\n \n // 单个视频功能叠加模式开关\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📽️ 视频输出模式\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut self.single_video_mode, \"单个视频功能叠加模式\").changed() {\n self.log(&format!(\"{} 单个视频功能叠加模式\", \n if self.single_video_mode { \"✅ 已开启\" } else { \"✅ 已关闭\" }));\n }\n });\n \n ui.add_space(5.0);\n ui.label(\"• 开启:所有选中的功能按顺序应用到同一个视频,最终生成一个文件\");\n ui.label(\"• 关闭:每个功能生成独立的视频文件(原始模式)\");\n ui.small(\"⚠️ 注意:请在开始处理前设置此选项\");\n });\n \n ui.add_space(15.0);\n \n // 模式说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 模式说明\");\n ui.add_space(5.0);\n \n ui.label(\"🔹 单个视频功能叠加模式(推荐用于去重)\");\n ui.label(\" • 所有选中的功能按顺序应用到同一个视频\");\n ui.label(\" • 最终只生成一个处理后的视频文件\");\n ui.label(\" • 适合需要多重处理的场景\");\n ui.label(\" • 文件命名:原文件名_processed.扩展名\");\n \n ui.add_space(10.0);\n \n ui.label(\"🔹 独立视频输出模式(原始模式)\");\n ui.label(\" • 每个功能生成独立的视频文件\");\n ui.label(\" • 适合需要单独查看每个效果的情况\");\n ui.label(\" • 文件命名:原文件名_功能名.扩展名\");\n \n ui.add_space(5.0);\n ui.small(\"💡 提示:切换模式后,已选中的功能不会改变\");\n });\n \n ui.add_space(15.0);\n \n // 当前状态显示\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📊 当前状态\");\n ui.add_space(5.0);\n \n ui.label(format!(\"当前模式:{}\", \n if self.single_video_mode { \n \"✅ 单个视频功能叠加模式\" \n } else { \n \"✅ 独立视频输出模式\" \n }));\n \n ui.label(format!(\"已选中功能:{} 个\", self.selected_actions.len()));\n if !self.selected_actions.is_empty() {\n ui.label(\"选中的功能:\");\n for action in &self.selected_actions {\n ui.label(format!(\" • {}\", action));\n }\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n \"mask_video\" => {\n ui.heading(\"蒙版视频设置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.mask_video_opacity, 0.0..=1.0).text(\"强度\"));\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n egui::ComboBox::from_id_source(\"mask_blend\")\n .selected_text(&self.mask_video_blend_mode)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_blend_mode, \"multiply\".to_string(), \"正片叠底 (Multiply)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"screen\".to_string(), \"滤色 (Screen)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"overlay\".to_string(), \"叠加 (Overlay)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"add\".to_string(), \"相加 (Add)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"subtract\".to_string(), \"相减 (Subtract)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"difference\".to_string(), \"差值 (Difference)\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"缩放模式:\");\n egui::ComboBox::from_id_source(\"mask_scale\")\n .selected_text(&self.mask_video_scale)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_scale, \"stretch\".to_string(), \"拉伸填充\");\n ui.selectable_value(&mut self.mask_video_scale, \"fit\".to_string(), \"等比缩放\");\n ui.selectable_value(&mut self.mask_video_scale, \"crop\".to_string(), \"裁剪填充\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.label(\"💡 提示:\");\n ui.label(\"• 正片叠底:适合暗色蒙版\");\n ui.label(\"• 滤色:适合亮色蒙版\");\n ui.label(\"• 叠加:平衡的混合效果\");\n ui.label(\"• 相加:增强亮度\");\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n \n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\n// Separate implementation block for the main UI update to keep the file clean\nimpl VideoMatrixApp {\n fn render_presets_tab(&mut self, ui: &mut egui::Ui) {\n ui.heading(\"💾 配置预设\");\n ui.add_space(10.0);\n ui.label(\"在此处保存和加载您常用的功能组合。\");\n ui.add_space(10.0);\n \n egui::Grid::new(\"presets_grid\").num_columns(2).spacing([20.0, 10.0]).show(ui, |ui| {\n ui.label(\"强力去重模式\");\n if ui.button(\"加载\").clicked() {\n self.selected_actions = vec![\"md5\".to_string(), \"crop\".to_string(), \"cut_head_tail\".to_string(), \"rotate\".to_string(), \"speed\".to_string()];\n self.single_video_mode = true; // Presets often imply a combined effect\n self.log_internal(\"✅ 已加载预设: 强力去重模式 (已切换到全部功能页)\".to_string());\n self.current_tab = Tab::All;\n }\n ui.end_row();\n\n ui.label(\"复古老电影风\");\n if ui.button(\"加载\").clicked() {\n self.selected_actions = vec![\"bw\".to_string(), \"grain\".to_string(), \"vignette\".to_string(), \"fps_60\".to_string()];\n self.single_video_mode = true;\n self.log_internal(\"✅ 已加载预设: 复古老电影风 (已切换到全部功能页)\".to_string());\n self.current_tab = Tab::All;\n }\n ui.end_row();\n\n ui.label(\"带货快节奏\");\n if ui.button(\"加载\").clicked() {\n self.selected_actions = vec![\"speed\".to_string(), \"sharpen\".to_string(), \"color\".to_string(), \"audio_noise\".to_string()];\n self.single_video_mode = true;\n self.log_internal(\"✅ 已加载预设: 带货快节奏 (已切换到全部功能页)\".to_string());\n self.current_tab = Tab::All;\n }\n ui.end_row();\n });\n \n ui.add_space(20.0);\n \n // Manual save/load (Simplified)\n ui.separator();\n ui.label(\"自定义预设:\");\n ui.horizontal(|ui| {\n if ui.button(\"保存当前配置\").clicked() {\n self.log_internal(\"💾 配置保存功能开发中...\".to_string());\n }\n });\n }\n\n fn render_preview_tab(&mut self, ui: &mut egui::Ui) {\n ui.heading(\"🎬 效果预览\");\n ui.add_space(10.0);\n \n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请先选择输入目录\");\n } else {\n ui.label(format!(\"当前输入: {}\", self.input_dir));\n ui.label(format!(\"当前输出: {}\", if self.output_dir.is_empty() { format!(\"{}/output\", self.input_dir) } else { self.output_dir.clone() }));\n ui.add_space(10.0);\n \n let btn_label = if self.is_processing { \"⏳ 生成中...\" } else { \"▶️ 生成 5秒 预览片段\" };\n \n if ui.add_enabled(!self.is_processing, egui::Button::new(btn_label).min_size(egui::vec2(150.0, 40.0))).clicked() {\n self.start_preview_processing();\n }\n \n ui.add_space(10.0);\n ui.info_message(\"预览逻辑: \\n1. 选取第一个视频文件\\n2. 截取前 5 秒\\n3. 叠加应用所有勾选的功能\\n4. 自动打开播放结果\");\n }\n }\n \n fn start_preview_processing(&mut self) {\n if self.selected_actions.is_empty() {\n self.log_internal(\"⚠️ 请先至少选择一个功能\".to_string());\n return;\n }\n\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🎬 开始生成预览...\");\n \n // Clone necessary data for the thread\n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Config creation (similar to start_processing)\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n if !self.mask_video_path.is_empty() { config.mask_video_path = Some(self.mask_video_path.clone()); }\n \n // Copy parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Spawn processing thread\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx_clone = tx.clone();\n \n thread::spawn(move || {\n if let Err(e) = Self::run_preview_task(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Preview Error: {}\", e);\n }\n });\n }\n\n fn run_preview_task(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(\"🔍 寻找预览视频源...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"未找到视频文件,无法预览\".to_string()));\n return Ok(());\n }\n \n let src_video = PathBuf::from(&video_files[0]);\n let _ = tx.send(AppMessage::Log(format!(\"📹 使用视频源: {:?}\", src_video.file_name().unwrap_or_default())));\n \n // Setup directories\n let preview_dir = Path::new(&output_dir).join(\"preview\");\n if !preview_dir.exists() {\n fs::create_dir_all(&preview_dir)?;\n }\n \n let preview_source = preview_dir.join(\"temp_source.mp4\");\n \n // Step 1: Cut 5 seconds\n let _ = tx.send(AppMessage::Log(\"✂️ 正在截取前 5 秒...\".to_string()));\n let ffmpeg_path = crate::core::ffutils::FFUtils::get_ffmpeg_path();\n \n let output = std::process::Command::new(&ffmpeg_path)\n .args(&[\n \"-y\", \"-ss\", \"0\", \"-t\", \"5\", \n \"-i\", src_video.to_str().unwrap(),\n \"-c:v\", \"libx264\", \"-preset\", \"ultrafast\", // Re-encode to ensure clean cut and compatibility\n \"-c:a\", \"aac\",\n preview_source.to_str().unwrap()\n ])\n .output()?;\n \n if !output.status.success() {\n let stderr = String::from_utf8_lossy(&output.stderr);\n let _ = tx.send(AppMessage::Error(format!(\"截取失败: {}\", stderr)));\n return Ok(());\n }\n \n // Step 2: Apply actions (Chained)\n let _ = tx.send(AppMessage::Log(\"🚀 正在叠加应用所有效果...\".to_string()));\n \n let mut current_input = preview_source.clone();\n let mut temp_files = Vec::new();\n \n for (i, action_id) in actions.iter().enumerate() {\n let _ = tx.send(AppMessage::Log(format!(\" [{}/{}] 应用: {}\", i+1, actions.len(), action_id)));\n \n // Reuse the static execution logic\n // Note: execute_action_static generates output based on input filename + action_id\n // We want to control the flow here.\n \n match Self::execute_action_static(action_id, ¤t_input, &preview_dir, &config) {\n Ok(_) => {\n // Determine the output path that execute_action_static created\n let current_ext = current_input.extension().unwrap_or_default().to_string_lossy();\n let current_stem = current_input.file_stem().unwrap_or_default().to_string_lossy();\n \n let expected_out_name = format!(\"{}_{}.{}\", current_stem, action_id, current_ext);\n let expected_out_path = preview_dir.join(&expected_out_name);\n \n if expected_out_path.exists() {\n temp_files.push(current_input); // Mark previous as temp to delete\n current_input = expected_out_path;\n } else {\n let _ = tx.send(AppMessage::Error(format!(\"Action {} finished but output not found\", action_id)));\n break;\n }\n },\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"Action {} failed: {}\", action_id, e)));\n break;\n }\n }\n \n // Update progress\n let _ = tx.send(AppMessage::Progress((i + 1) as f32 / actions.len() as f32));\n }\n \n // Step 3: Open Result\n let _ = tx.send(AppMessage::Log(\"✨ 预览生成完毕,正在打开...\".to_string()));\n // Open the file using system default player\n #[cfg(target_os = \"macos\")]\n let _ = std::process::Command::new(\"open\").arg(¤t_input).spawn();\n \n #[cfg(target_os = \"windows\")]\n let _ = std::process::Command::new(\"cmd\").args(&[\"/C\", \"start\", \"\", current_input.to_str().unwrap()]).spawn();\n \n // Cleanup temps (optional: keep source for debug, but delete intermediate steps)\n // for p in temp_files { if p != preview_source { fs::remove_file(p).ok(); } }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n}\n\npub trait VideoMatrixUiExt {\n fn info_message(&mut self, text: &str);\n}\nimpl VideoMatrixUiExt for egui::Ui {\n fn info_message(&mut self, text: &str) {\n self.label(egui::RichText::new(text).small().color(egui::Color32::GRAY));\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n let single_video_mode = self.single_video_mode;\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n if !self.mask_video_path.is_empty() { config.mask_video_path = Some(self.mask_video_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, single_video_mode, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, single_video_mode: bool, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n let _ = tx.send(AppMessage::Log(format!(\"🎯 处理模式: {}\", if single_video_mode { \"单个视频功能叠加\" } else { \"每个功能独立输出\" })));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n let _ = tx.send(AppMessage::Log(\"🚀 正在使用多线程并行处理...\".to_string()));\n \n let total_tasks = if single_video_mode {\n video_files.len() as f32\n } else {\n (video_files.len() * actions.len()) as f32\n };\n \n // Use AtomicUsize for thread-safe progress tracking\n let completed_tasks = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process video files in parallel using Rayon\n video_files.par_iter().for_each(|video_file| {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n let tx = tx.clone(); // Clone sender for each thread\n \n if single_video_mode {\n // 单个视频叠加模式:所有动作按顺序应用到同一个视频\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ 叠加处理: {} [{}]...\", filename, actions.join(\" → \"))));\n \n let mut current_input = video_path.to_path_buf();\n let mut temp_files = Vec::new();\n let mut success = true;\n \n for (i, action_id) in actions.iter().enumerate() {\n let is_last_action = i == actions.len() - 1;\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] 步骤 {}/{}: {}\", filename, i + 1, actions.len(), action_id)));\n \n // 执行动作 - 动作会自动生成输出文件\n let result = Self::execute_action_static(action_id, ¤t_input, &out_path, &config);\n \n match result {\n Ok(_) => {\n // 动作执行成功,现在需要找到生成的文件\n // 动作会生成 {原文件名}_{动作名}.{扩展名} 格式的文件\n \n // 先保存当前的文件名信息(避免借用问题)\n let current_ext = current_input.extension().and_then(|e| e.to_str()).unwrap_or(\"mp4\").to_string();\n let current_stem = current_input.file_stem().and_then(|s| s.to_str()).unwrap_or(\"video\").to_string();\n \n // 如果是最后一个动作,使用_processed后缀\n let output_filename = if is_last_action {\n format!(\"{}_processed.{}\", current_stem, current_ext)\n } else {\n format!(\"{}_{}.{}\", current_stem, action_id, current_ext)\n };\n \n let output_path = out_path.join(&output_filename);\n \n // 检查文件是否存在\n if output_path.exists() {\n if !is_last_action {\n temp_files.push(output_path.clone());\n }\n \n // 如果是最后一个动作,重命名为_processed后缀\n if is_last_action {\n let final_filename = format!(\"{}_processed.{}\", current_stem, current_ext);\n let final_path = out_path.join(&final_filename);\n \n if let Err(e) = fs::rename(&output_path, &final_path) {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ⚠️ 无法重命名为_processed: {}\", filename, e)));\n current_input = output_path;\n } else {\n current_input = final_path;\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ✅ 已重命名为: {}\", filename, final_filename)));\n }\n } else {\n current_input = output_path;\n }\n } else {\n // 如果标准命名不存在,尝试查找out_path中的新文件\n // 注意:并行模式下 find_newest_video_file 基本不可靠,因为其他线程也在写入\n // 所以我们只能尽量依赖 execute_action 返回准确的路径或者标准命名\n // 这里我们假设动作实现是标准的,如果找不到文件,那就是出错了\n // 但为了兼容之前的逻辑,我们还是保留这个Fallback,但要非常小心\n // 实际上最好是让 execute return path. 但为了不改动太多 trait,我们先这样。\n // 由于并行,find_newest_video_file 可能会找到别的线程产生的文件,这是个风险点。\n // 简单的修复:execute_action 应该保证文件名。\n // 我们前面已经看到了 VideoAction 只返回 Result<()>\n // 不过我们的 get_dst 是确定的。\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ 无法找到动作 {} 的输出文件 (标准命名不存在)\", filename, action_id)));\n success = false;\n break;\n }\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ {} 失败: {}\", filename, action_id, e)));\n success = false;\n break;\n }\n }\n }\n \n // 清理临时文件\n for temp_file in temp_files {\n let _ = fs::remove_file(temp_file);\n }\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n if success {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ 叠加处理完成 ({})\", filename)));\n } else {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ 叠加处理失败 ({})\", filename)));\n }\n } else {\n // 原始模式:每个动作生成独立视频\n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n match result {\n Ok(_) => {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n });\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n /// 查找输出目录中最新的视频文件(排除当前输入文件)\n fn find_newest_video_file(out_dir: &Path, current_input: &Path) -> Option<PathBuf> {\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n let mut newest_file: Option<PathBuf> = None;\n let mut newest_mtime: Option<std::time::SystemTime> = None;\n \n if let Ok(entries) = fs::read_dir(out_dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n // 排除当前输入文件\n if entry.path() == *current_input {\n continue;\n }\n \n // 检查文件扩展名\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n // 获取修改时间\n if let Ok(mtime) = metadata.modified() {\n if newest_mtime.is_none() || mtime > newest_mtime.unwrap() {\n newest_mtime = Some(mtime);\n newest_file = Some(entry.path());\n }\n }\n }\n }\n }\n }\n }\n }\n }\n \n newest_file\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"mask_video\" => MaskVideoAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro v5.5.13\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "d29264c6ac37aa04735307e4e6f9fa256c953cbf": {"t": 1785757410.9731057, "n": 99260, "h": "a62147fa0f6ab805", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\nuse rayon::prelude::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n mask_video_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n \n // Mask Video\n mask_video_opacity: f32, // mask video opacity (0.0-1.0)\n mask_video_blend_mode: String, // blend mode (multiply/screen/overlay/add)\n mask_video_scale: String, // scale mode (stretch/crop/fit)\n \n // 单个视频功能叠加模式\n single_video_mode: bool, // true: 所有功能叠加到单个视频; false: 每个功能生成独立视频\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n ProcessingMode, // 处理模式设置\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.95-1.05x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"蒙版视频叠加\".to_string(), \"mask_video\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n \n rx: None,\n runtime: Arc::new(tokio::runtime::Runtime::new().unwrap()),\n current_tab: Tab::All,\n \n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n mask_video_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.05, // Conservative: 5% speed variation\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 0.5, // Conservative: very slight blur\n grain_strength: 0.1,\n vignette_strength: 0.2, // Conservative: subtle vignette\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 0.5, // Conservative: 0.5 semitones\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n \n // Mask video defaults\n mask_video_opacity: 0.8,\n mask_video_blend_mode: \"multiply\".to_string(),\n mask_video_scale: \"stretch\".to_string(),\n \n // 单个视频模式默认关闭\n single_video_mode: false,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u32,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"v5.5.12\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n ui.selectable_value(&mut self.current_tab, Tab::ProcessingMode, \"🎯 处理模式\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 蒙版视频素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_video_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.mask_video_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版视频: {}\", self.mask_video_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 环境要求\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"⚠️ 环境要求\");\n ui.add_space(5.0);\n ui.label(\"本软件依赖 FFmpeg 进行视频处理,请确保:\");\n ui.label(\"1. 已安装 FFmpeg\");\n ui.label(\"2. FFmpeg 已添加到系统环境变量 PATH 中\");\n ui.label(\"3. 在终端输入 'ffmpeg -version' 能正常显示版本信息\");\n ui.add_space(5.0);\n ui.hyperlink(\"https://ffmpeg.org/download.html\");\n });\n ui.add_space(15.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 确保已安装 FFmpeg(见上文)\");\n ui.label(\"2. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"3. 勾选需要的功能(可多选)\");\n ui.label(\"4. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"5. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"6. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(5.0);\n \n // AI 智能消重\n egui::CollapsingHeader::new(\"🤖 AI 智能消重\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能分析:AI 自动分析视频处理需求\");\n ui.label(\"• 自动推荐:根据需求推荐最佳功能组合\");\n ui.label(\"• 参数优化:自动设置最合适的处理参数\");\n ui.label(\"• 使用方法:切换到'AI消重'标签页,输入 Key 和需求即可\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n \n Tab::ProcessingMode => {\n ui.heading(\"🎯 处理模式设置\");\n ui.add_space(10.0);\n \n ui.label(\"设置视频处理的工作模式,此设置对所有标签页的功能都有效\");\n ui.add_space(15.0);\n \n // 单个视频功能叠加模式开关\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📽️ 视频输出模式\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut self.single_video_mode, \"单个视频功能叠加模式\").changed() {\n self.log(&format!(\"{} 单个视频功能叠加模式\", \n if self.single_video_mode { \"✅ 已开启\" } else { \"✅ 已关闭\" }));\n }\n });\n \n ui.add_space(5.0);\n ui.label(\"• 开启:所有选中的功能按顺序应用到同一个视频,最终生成一个文件\");\n ui.label(\"• 关闭:每个功能生成独立的视频文件(原始模式)\");\n ui.small(\"⚠️ 注意:请在开始处理前设置此选项\");\n });\n \n ui.add_space(15.0);\n \n // 模式说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 模式说明\");\n ui.add_space(5.0);\n \n ui.label(\"🔹 单个视频功能叠加模式(推荐用于去重)\");\n ui.label(\" • 所有选中的功能按顺序应用到同一个视频\");\n ui.label(\" • 最终只生成一个处理后的视频文件\");\n ui.label(\" • 适合需要多重处理的场景\");\n ui.label(\" • 文件命名:原文件名_processed.扩展名\");\n \n ui.add_space(10.0);\n \n ui.label(\"🔹 独立视频输出模式(原始模式)\");\n ui.label(\" • 每个功能生成独立的视频文件\");\n ui.label(\" • 适合需要单独查看每个效果的情况\");\n ui.label(\" • 文件命名:原文件名_功能名.扩展名\");\n \n ui.add_space(5.0);\n ui.small(\"💡 提示:切换模式后,已选中的功能不会改变\");\n });\n \n ui.add_space(15.0);\n \n // 当前状态显示\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📊 当前状态\");\n ui.add_space(5.0);\n \n ui.label(format!(\"当前模式:{}\", \n if self.single_video_mode { \n \"✅ 单个视频功能叠加模式\" \n } else { \n \"✅ 独立视频输出模式\" \n }));\n \n ui.label(format!(\"已选中功能:{} 个\", self.selected_actions.len()));\n if !self.selected_actions.is_empty() {\n ui.label(\"选中的功能:\");\n for action in &self.selected_actions {\n ui.label(format!(\" • {}\", action));\n }\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n \"mask_video\" => {\n ui.heading(\"蒙版视频设置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.mask_video_opacity, 0.0..=1.0).text(\"强度\"));\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n egui::ComboBox::from_id_source(\"mask_blend\")\n .selected_text(&self.mask_video_blend_mode)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_blend_mode, \"multiply\".to_string(), \"正片叠底 (Multiply)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"screen\".to_string(), \"滤色 (Screen)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"overlay\".to_string(), \"叠加 (Overlay)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"add\".to_string(), \"相加 (Add)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"subtract\".to_string(), \"相减 (Subtract)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"difference\".to_string(), \"差值 (Difference)\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"缩放模式:\");\n egui::ComboBox::from_id_source(\"mask_scale\")\n .selected_text(&self.mask_video_scale)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_scale, \"stretch\".to_string(), \"拉伸填充\");\n ui.selectable_value(&mut self.mask_video_scale, \"fit\".to_string(), \"等比缩放\");\n ui.selectable_value(&mut self.mask_video_scale, \"crop\".to_string(), \"裁剪填充\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.label(\"💡 提示:\");\n ui.label(\"• 正片叠底:适合暗色蒙版\");\n ui.label(\"• 滤色:适合亮色蒙版\");\n ui.label(\"• 叠加:平衡的混合效果\");\n ui.label(\"• 相加:增强亮度\");\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n let single_video_mode = self.single_video_mode;\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n if !self.mask_video_path.is_empty() { config.mask_video_path = Some(self.mask_video_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, single_video_mode, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, single_video_mode: bool, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n let _ = tx.send(AppMessage::Log(format!(\"🎯 处理模式: {}\", if single_video_mode { \"单个视频功能叠加\" } else { \"每个功能独立输出\" })));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n let _ = tx.send(AppMessage::Log(\"🚀 正在使用多线程并行处理...\".to_string()));\n \n let total_tasks = if single_video_mode {\n video_files.len() as f32\n } else {\n (video_files.len() * actions.len()) as f32\n };\n \n // Use AtomicUsize for thread-safe progress tracking\n let completed_tasks = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process video files in parallel using Rayon\n video_files.par_iter().for_each(|video_file| {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n let tx = tx.clone(); // Clone sender for each thread\n \n if single_video_mode {\n // 单个视频叠加模式:所有动作按顺序应用到同一个视频\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ 叠加处理: {} [{}]...\", filename, actions.join(\" → \"))));\n \n let mut current_input = video_path.to_path_buf();\n let mut temp_files = Vec::new();\n let mut success = true;\n \n for (i, action_id) in actions.iter().enumerate() {\n let is_last_action = i == actions.len() - 1;\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] 步骤 {}/{}: {}\", filename, i + 1, actions.len(), action_id)));\n \n // 执行动作 - 动作会自动生成输出文件\n let result = Self::execute_action_static(action_id, ¤t_input, &out_path, &config);\n \n match result {\n Ok(_) => {\n // 动作执行成功,现在需要找到生成的文件\n // 动作会生成 {原文件名}_{动作名}.{扩展名} 格式的文件\n \n // 先保存当前的文件名信息(避免借用问题)\n let current_ext = current_input.extension().and_then(|e| e.to_str()).unwrap_or(\"mp4\").to_string();\n let current_stem = current_input.file_stem().and_then(|s| s.to_str()).unwrap_or(\"video\").to_string();\n \n // 如果是最后一个动作,使用_processed后缀\n let output_filename = if is_last_action {\n format!(\"{}_processed.{}\", current_stem, current_ext)\n } else {\n format!(\"{}_{}.{}\", current_stem, action_id, current_ext)\n };\n \n let output_path = out_path.join(&output_filename);\n \n // 检查文件是否存在\n if output_path.exists() {\n if !is_last_action {\n temp_files.push(output_path.clone());\n }\n \n // 如果是最后一个动作,重命名为_processed后缀\n if is_last_action {\n let final_filename = format!(\"{}_processed.{}\", current_stem, current_ext);\n let final_path = out_path.join(&final_filename);\n \n if let Err(e) = fs::rename(&output_path, &final_path) {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ⚠️ 无法重命名为_processed: {}\", filename, e)));\n current_input = output_path;\n } else {\n current_input = final_path;\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ✅ 已重命名为: {}\", filename, final_filename)));\n }\n } else {\n current_input = output_path;\n }\n } else {\n // 如果标准命名不存在,尝试查找out_path中的新文件\n // 注意:并行模式下 find_newest_video_file 基本不可靠,因为其他线程也在写入\n // 所以我们只能尽量依赖 execute_action 返回准确的路径或者标准命名\n // 这里我们假设动作实现是标准的,如果找不到文件,那就是出错了\n // 但为了兼容之前的逻辑,我们还是保留这个Fallback,但要非常小心\n // 实际上最好是让 execute return path. 但为了不改动太多 trait,我们先这样。\n // 由于并行,find_newest_video_file 可能会找到别的线程产生的文件,这是个风险点。\n // 简单的修复:execute_action 应该保证文件名。\n // 我们前面已经看到了 VideoAction 只返回 Result<()>\n // 不过我们的 get_dst 是确定的。\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ 无法找到动作 {} 的输出文件 (标准命名不存在)\", filename, action_id)));\n success = false;\n break;\n }\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ {} 失败: {}\", filename, action_id, e)));\n success = false;\n break;\n }\n }\n }\n \n // 清理临时文件\n for temp_file in temp_files {\n let _ = fs::remove_file(temp_file);\n }\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n if success {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ 叠加处理完成 ({})\", filename)));\n } else {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ 叠加处理失败 ({})\", filename)));\n }\n } else {\n // 原始模式:每个动作生成独立视频\n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n match result {\n Ok(_) => {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n });\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n /// 查找输出目录中最新的视频文件(排除当前输入文件)\n fn find_newest_video_file(out_dir: &Path, current_input: &Path) -> Option<PathBuf> {\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n let mut newest_file: Option<PathBuf> = None;\n let mut newest_mtime: Option<std::time::SystemTime> = None;\n \n if let Ok(entries) = fs::read_dir(out_dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n // 排除当前输入文件\n if entry.path() == *current_input {\n continue;\n }\n \n // 检查文件扩展名\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n // 获取修改时间\n if let Ok(mtime) = metadata.modified() {\n if newest_mtime.is_none() || mtime > newest_mtime.unwrap() {\n newest_mtime = Some(mtime);\n newest_file = Some(entry.path());\n }\n }\n }\n }\n }\n }\n }\n }\n }\n \n newest_file\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"mask_video\" => MaskVideoAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro v5.5.12\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "33055f081aaa63e49f18075d1dbf6eab70efa33f": {"t": 1785757412.77099, "n": 99254, "h": "d63642f54a4c6e91", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\nuse rayon::prelude::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n mask_video_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n \n // Mask Video\n mask_video_opacity: f32, // mask video opacity (0.0-1.0)\n mask_video_blend_mode: String, // blend mode (multiply/screen/overlay/add)\n mask_video_scale: String, // scale mode (stretch/crop/fit)\n \n // 单个视频功能叠加模式\n single_video_mode: bool, // true: 所有功能叠加到单个视频; false: 每个功能生成独立视频\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n ProcessingMode, // 处理模式设置\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.95-1.05x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"蒙版视频叠加\".to_string(), \"mask_video\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n \n rx: None,\n runtime: Arc::new(tokio::runtime::Runtime::new().unwrap()),\n current_tab: Tab::All,\n \n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n mask_video_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.05, // Conservative: 5% speed variation\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 0.5, // Conservative: very slight blur\n grain_strength: 0.1,\n vignette_strength: 0.2, // Conservative: subtle vignette\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 0.5, // Conservative: 0.5 semitones\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n \n // Mask video defaults\n mask_video_opacity: 0.8,\n mask_video_blend_mode: \"multiply\".to_string(),\n mask_video_scale: \"stretch\".to_string(),\n \n // 单个视频模式默认关闭\n single_video_mode: false,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u32,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n ui.selectable_value(&mut self.current_tab, Tab::ProcessingMode, \"🎯 处理模式\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 蒙版视频素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_video_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.mask_video_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版视频: {}\", self.mask_video_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 环境要求\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"⚠️ 环境要求\");\n ui.add_space(5.0);\n ui.label(\"本软件依赖 FFmpeg 进行视频处理,请确保:\");\n ui.label(\"1. 已安装 FFmpeg\");\n ui.label(\"2. FFmpeg 已添加到系统环境变量 PATH 中\");\n ui.label(\"3. 在终端输入 'ffmpeg -version' 能正常显示版本信息\");\n ui.add_space(5.0);\n ui.hyperlink(\"https://ffmpeg.org/download.html\");\n });\n ui.add_space(15.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 确保已安装 FFmpeg(见上文)\");\n ui.label(\"2. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"3. 勾选需要的功能(可多选)\");\n ui.label(\"4. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"5. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"6. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(5.0);\n \n // AI 智能消重\n egui::CollapsingHeader::new(\"🤖 AI 智能消重\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能分析:AI 自动分析视频处理需求\");\n ui.label(\"• 自动推荐:根据需求推荐最佳功能组合\");\n ui.label(\"• 参数优化:自动设置最合适的处理参数\");\n ui.label(\"• 使用方法:切换到'AI消重'标签页,输入 Key 和需求即可\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n \n Tab::ProcessingMode => {\n ui.heading(\"🎯 处理模式设置\");\n ui.add_space(10.0);\n \n ui.label(\"设置视频处理的工作模式,此设置对所有标签页的功能都有效\");\n ui.add_space(15.0);\n \n // 单个视频功能叠加模式开关\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📽️ 视频输出模式\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut self.single_video_mode, \"单个视频功能叠加模式\").changed() {\n self.log(&format!(\"{} 单个视频功能叠加模式\", \n if self.single_video_mode { \"✅ 已开启\" } else { \"✅ 已关闭\" }));\n }\n });\n \n ui.add_space(5.0);\n ui.label(\"• 开启:所有选中的功能按顺序应用到同一个视频,最终生成一个文件\");\n ui.label(\"• 关闭:每个功能生成独立的视频文件(原始模式)\");\n ui.small(\"⚠️ 注意:请在开始处理前设置此选项\");\n });\n \n ui.add_space(15.0);\n \n // 模式说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 模式说明\");\n ui.add_space(5.0);\n \n ui.label(\"🔹 单个视频功能叠加模式(推荐用于去重)\");\n ui.label(\" • 所有选中的功能按顺序应用到同一个视频\");\n ui.label(\" • 最终只生成一个处理后的视频文件\");\n ui.label(\" • 适合需要多重处理的场景\");\n ui.label(\" • 文件命名:原文件名_processed.扩展名\");\n \n ui.add_space(10.0);\n \n ui.label(\"🔹 独立视频输出模式(原始模式)\");\n ui.label(\" • 每个功能生成独立的视频文件\");\n ui.label(\" • 适合需要单独查看每个效果的情况\");\n ui.label(\" • 文件命名:原文件名_功能名.扩展名\");\n \n ui.add_space(5.0);\n ui.small(\"💡 提示:切换模式后,已选中的功能不会改变\");\n });\n \n ui.add_space(15.0);\n \n // 当前状态显示\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📊 当前状态\");\n ui.add_space(5.0);\n \n ui.label(format!(\"当前模式:{}\", \n if self.single_video_mode { \n \"✅ 单个视频功能叠加模式\" \n } else { \n \"✅ 独立视频输出模式\" \n }));\n \n ui.label(format!(\"已选中功能:{} 个\", self.selected_actions.len()));\n if !self.selected_actions.is_empty() {\n ui.label(\"选中的功能:\");\n for action in &self.selected_actions {\n ui.label(format!(\" • {}\", action));\n }\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n \"mask_video\" => {\n ui.heading(\"蒙版视频设置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.mask_video_opacity, 0.0..=1.0).text(\"强度\"));\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n egui::ComboBox::from_id_source(\"mask_blend\")\n .selected_text(&self.mask_video_blend_mode)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_blend_mode, \"multiply\".to_string(), \"正片叠底 (Multiply)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"screen\".to_string(), \"滤色 (Screen)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"overlay\".to_string(), \"叠加 (Overlay)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"add\".to_string(), \"相加 (Add)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"subtract\".to_string(), \"相减 (Subtract)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"difference\".to_string(), \"差值 (Difference)\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"缩放模式:\");\n egui::ComboBox::from_id_source(\"mask_scale\")\n .selected_text(&self.mask_video_scale)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_scale, \"stretch\".to_string(), \"拉伸填充\");\n ui.selectable_value(&mut self.mask_video_scale, \"fit\".to_string(), \"等比缩放\");\n ui.selectable_value(&mut self.mask_video_scale, \"crop\".to_string(), \"裁剪填充\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.label(\"💡 提示:\");\n ui.label(\"• 正片叠底:适合暗色蒙版\");\n ui.label(\"• 滤色:适合亮色蒙版\");\n ui.label(\"• 叠加:平衡的混合效果\");\n ui.label(\"• 相加:增强亮度\");\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n let single_video_mode = self.single_video_mode;\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n if !self.mask_video_path.is_empty() { config.mask_video_path = Some(self.mask_video_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, single_video_mode, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, single_video_mode: bool, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n let _ = tx.send(AppMessage::Log(format!(\"🎯 处理模式: {}\", if single_video_mode { \"单个视频功能叠加\" } else { \"每个功能独立输出\" })));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n let _ = tx.send(AppMessage::Log(\"🚀 正在使用多线程并行处理...\".to_string()));\n \n let total_tasks = if single_video_mode {\n video_files.len() as f32\n } else {\n (video_files.len() * actions.len()) as f32\n };\n \n // Use AtomicUsize for thread-safe progress tracking\n let completed_tasks = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process video files in parallel using Rayon\n video_files.par_iter().for_each(|video_file| {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n let tx = tx.clone(); // Clone sender for each thread\n \n if single_video_mode {\n // 单个视频叠加模式:所有动作按顺序应用到同一个视频\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ 叠加处理: {} [{}]...\", filename, actions.join(\" → \"))));\n \n let mut current_input = video_path.to_path_buf();\n let mut temp_files = Vec::new();\n let mut success = true;\n \n for (i, action_id) in actions.iter().enumerate() {\n let is_last_action = i == actions.len() - 1;\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] 步骤 {}/{}: {}\", filename, i + 1, actions.len(), action_id)));\n \n // 执行动作 - 动作会自动生成输出文件\n let result = Self::execute_action_static(action_id, ¤t_input, &out_path, &config);\n \n match result {\n Ok(_) => {\n // 动作执行成功,现在需要找到生成的文件\n // 动作会生成 {原文件名}_{动作名}.{扩展名} 格式的文件\n \n // 先保存当前的文件名信息(避免借用问题)\n let current_ext = current_input.extension().and_then(|e| e.to_str()).unwrap_or(\"mp4\").to_string();\n let current_stem = current_input.file_stem().and_then(|s| s.to_str()).unwrap_or(\"video\").to_string();\n \n // 如果是最后一个动作,使用_processed后缀\n let output_filename = if is_last_action {\n format!(\"{}_processed.{}\", current_stem, current_ext)\n } else {\n format!(\"{}_{}.{}\", current_stem, action_id, current_ext)\n };\n \n let output_path = out_path.join(&output_filename);\n \n // 检查文件是否存在\n if output_path.exists() {\n if !is_last_action {\n temp_files.push(output_path.clone());\n }\n \n // 如果是最后一个动作,重命名为_processed后缀\n if is_last_action {\n let final_filename = format!(\"{}_processed.{}\", current_stem, current_ext);\n let final_path = out_path.join(&final_filename);\n \n if let Err(e) = fs::rename(&output_path, &final_path) {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ⚠️ 无法重命名为_processed: {}\", filename, e)));\n current_input = output_path;\n } else {\n current_input = final_path;\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ✅ 已重命名为: {}\", filename, final_filename)));\n }\n } else {\n current_input = output_path;\n }\n } else {\n // 如果标准命名不存在,尝试查找out_path中的新文件\n // 注意:并行模式下 find_newest_video_file 基本不可靠,因为其他线程也在写入\n // 所以我们只能尽量依赖 execute_action 返回准确的路径或者标准命名\n // 这里我们假设动作实现是标准的,如果找不到文件,那就是出错了\n // 但为了兼容之前的逻辑,我们还是保留这个Fallback,但要非常小心\n // 实际上最好是让 execute return path. 但为了不改动太多 trait,我们先这样。\n // 由于并行,find_newest_video_file 可能会找到别的线程产生的文件,这是个风险点。\n // 简单的修复:execute_action 应该保证文件名。\n // 我们前面已经看到了 VideoAction 只返回 Result<()>\n // 不过我们的 get_dst 是确定的。\n \n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ 无法找到动作 {} 的输出文件 (标准命名不存在)\", filename, action_id)));\n success = false;\n break;\n }\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" [{}] ❌ {} 失败: {}\", filename, action_id, e)));\n success = false;\n break;\n }\n }\n }\n \n // 清理临时文件\n for temp_file in temp_files {\n let _ = fs::remove_file(temp_file);\n }\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n if success {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ 叠加处理完成 ({})\", filename)));\n } else {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ 叠加处理失败 ({})\", filename)));\n }\n } else {\n // 原始模式:每个动作生成独立视频\n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n // 更新进度\n let completed = completed_tasks.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;\n let _ = tx.send(AppMessage::Progress(completed as f32 / total_tasks));\n \n match result {\n Ok(_) => {\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n });\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n /// 查找输出目录中最新的视频文件(排除当前输入文件)\n fn find_newest_video_file(out_dir: &Path, current_input: &Path) -> Option<PathBuf> {\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n let mut newest_file: Option<PathBuf> = None;\n let mut newest_mtime: Option<std::time::SystemTime> = None;\n \n if let Ok(entries) = fs::read_dir(out_dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n // 排除当前输入文件\n if entry.path() == *current_input {\n continue;\n }\n \n // 检查文件扩展名\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n // 获取修改时间\n if let Ok(mtime) = metadata.modified() {\n if newest_mtime.is_none() || mtime > newest_mtime.unwrap() {\n newest_mtime = Some(mtime);\n newest_file = Some(entry.path());\n }\n }\n }\n }\n }\n }\n }\n }\n }\n \n newest_file\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"mask_video\" => MaskVideoAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "ccd244b76b0cd8236ec9e5ae6f745da995ba0299": {"t": 1785757414.3906446, "n": 87551, "h": "451cb26c7dd033af", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n mask_video_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n \n // Mask Video\n mask_video_opacity: f32, // mask video opacity (0.0-1.0)\n mask_video_blend_mode: String, // blend mode (multiply/screen/overlay/add)\n mask_video_scale: String, // scale mode (stretch/crop/fit)\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.95-1.05x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"蒙版视频叠加\".to_string(), \"mask_video\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n \n rx: None,\n runtime: Arc::new(tokio::runtime::Runtime::new().unwrap()),\n current_tab: Tab::All,\n \n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n mask_video_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.05, // Conservative: 5% speed variation\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 0.5, // Conservative: very slight blur\n grain_strength: 0.1,\n vignette_strength: 0.2, // Conservative: subtle vignette\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 0.5, // Conservative: 0.5 semitones\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n \n // Mask video defaults\n mask_video_opacity: 0.8,\n mask_video_blend_mode: \"multiply\".to_string(),\n mask_video_scale: \"stretch\".to_string(),\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u32,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 蒙版视频素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_video_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.mask_video_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版视频: {}\", self.mask_video_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 环境要求\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"⚠️ 环境要求\");\n ui.add_space(5.0);\n ui.label(\"本软件依赖 FFmpeg 进行视频处理,请确保:\");\n ui.label(\"1. 已安装 FFmpeg\");\n ui.label(\"2. FFmpeg 已添加到系统环境变量 PATH 中\");\n ui.label(\"3. 在终端输入 'ffmpeg -version' 能正常显示版本信息\");\n ui.add_space(5.0);\n ui.hyperlink(\"https://ffmpeg.org/download.html\");\n });\n ui.add_space(15.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 确保已安装 FFmpeg(见上文)\");\n ui.label(\"2. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"3. 勾选需要的功能(可多选)\");\n ui.label(\"4. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"5. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"6. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(5.0);\n \n // AI 智能消重\n egui::CollapsingHeader::new(\"🤖 AI 智能消重\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能分析:AI 自动分析视频处理需求\");\n ui.label(\"• 自动推荐:根据需求推荐最佳功能组合\");\n ui.label(\"• 参数优化:自动设置最合适的处理参数\");\n ui.label(\"• 使用方法:切换到'AI消重'标签页,输入 Key 和需求即可\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n \"mask_video\" => {\n ui.heading(\"蒙版视频设置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.mask_video_opacity, 0.0..=1.0).text(\"强度\"));\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n egui::ComboBox::from_id_source(\"mask_blend\")\n .selected_text(&self.mask_video_blend_mode)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_blend_mode, \"multiply\".to_string(), \"正片叠底 (Multiply)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"screen\".to_string(), \"滤色 (Screen)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"overlay\".to_string(), \"叠加 (Overlay)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"add\".to_string(), \"相加 (Add)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"subtract\".to_string(), \"相减 (Subtract)\");\n ui.selectable_value(&mut self.mask_video_blend_mode, \"difference\".to_string(), \"差值 (Difference)\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"缩放模式:\");\n egui::ComboBox::from_id_source(\"mask_scale\")\n .selected_text(&self.mask_video_scale)\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.mask_video_scale, \"stretch\".to_string(), \"拉伸填充\");\n ui.selectable_value(&mut self.mask_video_scale, \"fit\".to_string(), \"等比缩放\");\n ui.selectable_value(&mut self.mask_video_scale, \"crop\".to_string(), \"裁剪填充\");\n });\n });\n \n ui.add_space(5.0);\n \n ui.label(\"💡 提示:\");\n ui.label(\"• 正片叠底:适合暗色蒙版\");\n ui.label(\"• 滤色:适合亮色蒙版\");\n ui.label(\"• 叠加:平衡的混合效果\");\n ui.label(\"• 相加:增强亮度\");\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n if !self.mask_video_path.is_empty() { config.mask_video_path = Some(self.mask_video_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"mask_video\" => MaskVideoAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "0810d0c2d0b4b06caa77b796d89d163dc79fbb63": {"t": 1785757415.991394, "n": 82940, "h": "3e16a46de8472662", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n \n rx: None,\n runtime: Arc::new(tokio::runtime::Runtime::new().unwrap()),\n current_tab: Tab::All,\n \n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.05, // Conservative: 5% speed variation\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 0.5, // Conservative: very slight blur\n grain_strength: 0.1,\n vignette_strength: 0.2, // Conservative: subtle vignette\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 0.5, // Conservative: 0.5 semitones\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u32,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 环境要求\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"⚠️ 环境要求\");\n ui.add_space(5.0);\n ui.label(\"本软件依赖 FFmpeg 进行视频处理,请确保:\");\n ui.label(\"1. 已安装 FFmpeg\");\n ui.label(\"2. FFmpeg 已添加到系统环境变量 PATH 中\");\n ui.label(\"3. 在终端输入 'ffmpeg -version' 能正常显示版本信息\");\n ui.add_space(5.0);\n ui.hyperlink(\"https://ffmpeg.org/download.html\");\n });\n ui.add_space(15.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 确保已安装 FFmpeg(见上文)\");\n ui.label(\"2. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"3. 勾选需要的功能(可多选)\");\n ui.label(\"4. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"5. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"6. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(5.0);\n \n // AI 智能消重\n egui::CollapsingHeader::new(\"🤖 AI 智能消重\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能分析:AI 自动分析视频处理需求\");\n ui.label(\"• 自动推荐:根据需求推荐最佳功能组合\");\n ui.label(\"• 参数优化:自动设置最合适的处理参数\");\n ui.label(\"• 使用方法:切换到'AI消重'标签页,输入 Key 和需求即可\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "c420ab53c7e266d4464682d3524c77cad868a9bd": {"t": 1785757417.755292, "n": 82785, "h": "935146e18cd2cafe", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.05, // Conservative: 5% speed variation\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 0.5, // Conservative: very slight blur\n grain_strength: 0.1,\n vignette_strength: 0.2, // Conservative: subtle vignette\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 0.5, // Conservative: 0.5 semitones\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u64,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 环境要求\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"⚠️ 环境要求\");\n ui.add_space(5.0);\n ui.label(\"本软件依赖 FFmpeg 进行视频处理,请确保:\");\n ui.label(\"1. 已安装 FFmpeg\");\n ui.label(\"2. FFmpeg 已添加到系统环境变量 PATH 中\");\n ui.label(\"3. 在终端输入 'ffmpeg -version' 能正常显示版本信息\");\n ui.add_space(5.0);\n ui.hyperlink(\"https://ffmpeg.org/download.html\");\n });\n ui.add_space(15.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 确保已安装 FFmpeg(见上文)\");\n ui.label(\"2. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"3. 勾选需要的功能(可多选)\");\n ui.label(\"4. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"5. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"6. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(5.0);\n \n // AI 智能消重\n egui::CollapsingHeader::new(\"🤖 AI 智能消重\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能分析:AI 自动分析视频处理需求\");\n ui.label(\"• 自动推荐:根据需求推荐最佳功能组合\");\n ui.label(\"• 参数优化:自动设置最合适的处理参数\");\n ui.label(\"• 使用方法:切换到'AI消重'标签页,输入 Key 和需求即可\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "0bfe111b46fb9b35e96480f535869cac88bfe56c": {"t": 1785757419.2619398, "n": 81081, "h": "9d3a459fcde9bc56", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::core::ai::{AIService, AIResponse};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n AIResult(AIResponse),\n AIConnectionResult(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n runtime: Arc<tokio::runtime::Runtime>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n \n // AI Deduplication\n deepseek_api_key: String, // DeepSeek API key\n deepseek_base_url: String, // API base URL\n ai_prompt: String, // User's AI processing request\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n AIDedup, // AI-powered deduplication\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.1,\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 2.0,\n grain_strength: 0.1,\n vignette_strength: 0.5,\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 2.0,\n \n // AI defaults\n deepseek_api_key: String::new(),\n deepseek_base_url: \"https://api.deepseek.com\".to_string(),\n ai_prompt: String::new(),\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIConnectionResult(msg) => {\n self.log_internal(msg);\n self.is_processing = false;\n keep_rx = false;\n },\n AppMessage::AIResult(response) => {\n self.log_internal(\"✅ AI 分析完成!正在应用推荐设置...\".to_string());\n self.log_internal(format!(\"💡 AI 建议: {}\", response.explanation));\n \n // Apply parameters\n if let Some(obj) = response.params.as_object() {\n for (k, v) in obj {\n if let Some(f) = v.as_f64() {\n match k.as_str() {\n \"cut_seconds\" => self.cut_seconds = f as f32,\n \"rotate_angle\" => self.rotate_angle = f as f32,\n \"speed_range\" => self.speed_range = f as f32,\n \"sharpen_strength\" => self.sharpen_strength = f as f32,\n \"denoise_strength\" => self.denoise_strength = f as f32,\n \"blur_strength\" => self.blur_strength = f as f32,\n \"grain_strength\" => self.grain_strength = f as f32,\n \"vignette_strength\" => self.vignette_strength = f as f32,\n \"portrait_strength\" => self.portrait_strength = f as f32,\n \"progressive_ratio\" => self.progressive_ratio = f as f32,\n \"corner_radius\" => self.corner_radius = f as f32,\n \"zoom_range\" => self.zoom_range = f as f32,\n \"dissolve_strength\" => self.dissolve_strength = f as f32,\n \"scan_strength\" => self.scan_strength = f as f32,\n \"bounce_amplitude\" => self.bounce_amplitude = f as f32,\n \"flash_strength\" => self.flash_strength = f as f32,\n \"lava_strength\" => self.lava_strength = f as f32,\n \"noise_strength\" => self.noise_strength = f as f32,\n \"pitch_range\" => self.pitch_range = f as f32,\n \"strong_crop_ratio\" => self.strong_crop_ratio = f as f32,\n _ => {}\n }\n }\n if let Some(i) = v.as_i64() {\n match k.as_str() {\n \"border_width\" => self.border_width = i as i32,\n \"color_temp_range\" => self.color_temp_range = i as i32,\n \"pull_width\" => self.pull_width = i as i32,\n \"trifold_spacing\" => self.trifold_spacing = i as i32,\n \"target_fps\" => self.target_fps = i as u64,\n _ => {}\n }\n }\n if let Some(s) = v.as_str() {\n match k.as_str() {\n \"target_bitrate\" => self.target_bitrate = s.to_string(),\n \"mirror_direction\" => self.mirror_direction = s.to_string(),\n _ => {}\n }\n }\n }\n }\n \n // Select actions\n self.selected_actions.clear();\n for action_id in response.suggested_actions {\n self.selected_actions.push(action_id);\n }\n \n self.log_internal(\"✨ 设置已更新,您可以点击'开始处理'了!\".to_string());\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n ui.selectable_value(&mut self.current_tab, Tab::AIDedup, \"🤖 AI消重\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"2. 勾选需要的功能(可多选)\");\n ui.label(\"3. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"4. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"5. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n \n Tab::AIDedup => {\n ui.heading(\"🤖 AI 智能消重\");\n ui.add_space(10.0);\n \n ui.label(\"使用 AI 大模型智能分析视频内容,生成个性化的处理方案\");\n ui.add_space(15.0);\n \n // API 配置区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"🔑 API 配置\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"DeepSeek API Key:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_api_key)\n .hint_text(\"sk-xxxxxxxxxxxxxxxx\")\n .password(true)\n .desired_width(400.0));\n });\n ui.small(\"在 https://platform.deepseek.com 获取 API Key\");\n \n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"API Base URL:\");\n ui.add(egui::TextEdit::singleline(&mut self.deepseek_base_url)\n .hint_text(\"https://api.deepseek.com\")\n .desired_width(400.0));\n });\n ui.small(\"通常使用默认值即可\");\n });\n \n ui.add_space(15.0);\n \n // AI 提示词区域\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"💬 AI 处理需求\");\n ui.add_space(5.0);\n \n ui.label(\"描述您希望 AI 如何处理视频(例如:去重、风格化、特效等)\");\n ui.add_space(5.0);\n \n ui.add(egui::TextEdit::multiline(&mut self.ai_prompt)\n .hint_text(\"例如:\\n- 分析视频内容,自动添加合适的滤镜和特效\\n- 识别重复片段并进行智能剪辑\\n- 根据视频主题推荐最佳的处理参数\\n- 生成创意转场效果\")\n .desired_width(f32::INFINITY)\n .desired_rows(8));\n });\n \n ui.add_space(15.0);\n \n // 功能说明\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.heading(\"📚 功能说明\");\n ui.add_space(5.0);\n \n ui.label(\"• AI 会分析您的需求和视频内容\");\n ui.label(\"• 自动选择合适的处理功能和参数\");\n ui.label(\"• 生成个性化的视频处理方案\");\n ui.label(\"• 支持批量处理和智能优化\");\n \n ui.add_space(10.0);\n \n ui.label(\"⚠️ 注意:\");\n ui.label(\"• 需要有效的 DeepSeek API Key\");\n ui.label(\"• API 调用可能产生费用\");\n ui.label(\"• 处理时间取决于视频数量和复杂度\");\n });\n \n ui.add_space(15.0);\n \n // 操作按钮\n ui.horizontal(|ui| {\n if ui.button(\"🚀 开始 AI 处理\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 DeepSeek API Key\");\n } else if self.ai_prompt.is_empty() {\n self.log(\"❌ 请输入 AI 处理需求\");\n } else {\n self.log(\"🤖 正在请求 AI 分析...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let prompt = self.ai_prompt.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.analyze_requirement(&prompt).await {\n Ok(response) => {\n let _ = tx.send(AppMessage::AIResult(response));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"AI 请求失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🧪 测试连接\").clicked() {\n if self.deepseek_api_key.is_empty() {\n self.log(\"❌ 请先配置 API Key\");\n } else {\n self.log(\"🔍 正在测试 API 连接...\");\n self.is_processing = true;\n \n let api_key = self.deepseek_api_key.clone();\n let base_url = self.deepseek_base_url.clone();\n let (tx, rx) = channel();\n self.rx = Some(rx);\n let tx = tx.clone();\n \n self.runtime.spawn(async move {\n let service = AIService::new(api_key, base_url);\n match service.test_connection().await {\n Ok(msg) => {\n let _ = tx.send(AppMessage::AIConnectionResult(msg));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Error(format!(\"连接失败: {}\", e)));\n }\n }\n });\n }\n }\n \n if ui.button(\"🔄 重置配置\").clicked() {\n self.deepseek_api_key.clear();\n self.deepseek_base_url = \"https://api.deepseek.com\".to_string();\n self.ai_prompt.clear();\n self.log(\"✅ 已重置 AI 配置\");\n }\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "784d2deab27e533b05856be87f4a54b59a9be5e6": {"t": 1785757420.984591, "n": 68645, "h": "4dd3e54ce726b6a8", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n Help, // Help & Documentation\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n current_tab: Tab::All,\n rx: None,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.1,\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 2.0,\n grain_strength: 0.1,\n vignette_strength: 0.5,\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 2.0,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n ui.selectable_value(&mut self.current_tab, Tab::Help, \"📖 使用说明\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n \n Tab::Help => {\n ui.heading(\"📖 使用说明\");\n ui.add_space(10.0);\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n ui.label(\"欢迎使用视频矩阵 Pro!本工具提供 51 种视频处理功能,帮助您快速批量处理视频。\");\n ui.add_space(10.0);\n \n // 基础使用\n ui.heading(\"🚀 快速开始\");\n ui.label(\"1. 选择输入目录(包含要处理的视频文件)\");\n ui.label(\"2. 勾选需要的功能(可多选)\");\n ui.label(\"3. 点击功能旁的 ⚙️ 按钮调整参数(可选)\");\n ui.label(\"4. 点击\\\"开始处理\\\"按钮\");\n ui.label(\"5. 处理完成后,视频将保存在输出目录\");\n ui.add_space(15.0);\n \n // 功能分类说明\n ui.heading(\"📚 功能详解\");\n ui.add_space(5.0);\n \n // 基础编辑\n egui::CollapsingHeader::new(\"🔧 基础编辑 (8个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 一键MD5:修改视频元数据,添加唯一标识\");\n ui.label(\"• 随机微裁剪:随机裁剪视频边缘(可调节比例)\");\n ui.label(\"• 首尾去秒:去除视频开头和结尾的指定秒数\");\n ui.label(\"• 微旋转:随机旋转视频(可调节角度范围)\");\n ui.label(\"• 非线性变速:随机调整播放速度(可调节范围)\");\n ui.label(\"• 镜像翻转:水平/垂直/双向翻转视频\");\n ui.label(\"• 强制60帧:将视频转换为指定帧率\");\n ui.label(\"• 高码率:提升视频码率,增强画质\");\n });\n \n ui.add_space(5.0);\n \n // 视觉增强\n egui::CollapsingHeader::new(\"✨ 视觉增强 (12个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 智能锐化:增强视频清晰度(可调节强度)\");\n ui.label(\"• 智能锐化(人像):针对人像优化的锐化\");\n ui.label(\"• 智能降噪:减少视频噪点(可调节强度)\");\n ui.label(\"• 智能降噪(清洁):更强的降噪效果\");\n ui.label(\"• 胶片颗粒:添加电影感颗粒效果(可调节强度)\");\n ui.label(\"• 智能柔焦:柔化画面,营造梦幻效果\");\n ui.label(\"• 随机色温:调整视频色温\");\n ui.label(\"• 电影暗角:添加四周暗角效果(可调节强度)\");\n ui.label(\"• 黑白怀旧:转换为黑白效果\");\n ui.label(\"• 智能补边:为视频添加边框\");\n ui.label(\"• 智能抽帧:降低帧率,减小文件大小\");\n ui.label(\"• 边角模糊:模糊视频四角\");\n });\n \n ui.add_space(5.0);\n \n // 强力去重\n egui::CollapsingHeader::new(\"🔥 强力去重 (6个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 强力裁剪:大幅度裁剪,强力去重\");\n ui.label(\"• 添加水印:叠加水印图片(可调位置和透明度)\");\n ui.label(\"• 修改编码参数:更改视频编码设置\");\n ui.label(\"• 添加贴纸:叠加贴纸素材\");\n ui.label(\"• 蒙版叠加:应用蒙版效果\");\n ui.label(\"• 真实AB替换:高级AB模式替换\");\n });\n \n ui.add_space(5.0);\n \n // AI与AB模式\n egui::CollapsingHeader::new(\"🤖 AI与AB模式 (14个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• AI随机缩放:智能缩放视频\");\n ui.label(\"• AI移动溶解:动态溶解效果\");\n ui.label(\"• AI随机光扫:光线扫描效果\");\n ui.label(\"• 弹跳效果:视频弹跳动画\");\n ui.label(\"• 三联屏效果:分屏显示\");\n ui.label(\"• 岩浆AB模式:岩浆风格特效\");\n ui.label(\"• 3D闪白:3D闪光效果\");\n ui.label(\"• 渐进处理:渐进式视频处理\");\n ui.label(\"• AB混合模式:混合两个视频\");\n ui.label(\"• AB故障效果:故障艺术风格\");\n ui.label(\"• AB抖动效果:抖动特效\");\n ui.label(\"• AB色度偏移:色彩偏移效果\");\n ui.label(\"• AB视频替换:替换视频片段\");\n ui.label(\"• 高级AB替换:更高级的替换模式\");\n });\n \n ui.add_space(5.0);\n \n // 素材叠加\n egui::CollapsingHeader::new(\"🎨 素材叠加 (7个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 水印:在\\\"素材设置\\\"中选择水印图片\");\n ui.label(\"• 贴纸:在\\\"素材设置\\\"中选择贴纸图片\");\n ui.label(\"• 蒙版:在\\\"素材设置\\\"中选择蒙版图片\");\n ui.label(\"• 边框:在\\\"素材设置\\\"中选择边框图片\");\n ui.label(\"• 光效:在\\\"素材设置\\\"中选择光效素材\");\n ui.label(\"• 画中画:在\\\"素材设置\\\"中选择叠加视频\");\n ui.label(\"• 带货模板:在\\\"素材设置\\\"中选择模板\");\n });\n \n ui.add_space(5.0);\n \n // 音频处理\n egui::CollapsingHeader::new(\"🎵 音频处理 (4个)\")\n .default_open(false)\n .show(ui, |ui| {\n ui.label(\"• 静音视频:移除所有音频\");\n ui.label(\"• 混入弱白噪音:添加背景白噪音(可调强度)\");\n ui.label(\"• 音频变调:随机调整音调(可调范围)\");\n ui.label(\"• 仅修改时间戳:只更改元数据时间戳\");\n });\n \n ui.add_space(15.0);\n \n // 使用技巧\n ui.heading(\"💡 使用技巧\");\n ui.label(\"• 可以同时勾选多个功能,按顺序依次处理\");\n ui.label(\"• 点击 ⚙️ 按钮可精细调节每个功能的参数\");\n ui.label(\"• 建议先用少量视频测试效果,再批量处理\");\n ui.label(\"• 处理过程中可查看\\\"日志\\\"标签页了解进度\");\n ui.label(\"• 素材功能需要先在\\\"素材设置\\\"中选择对应文件\");\n \n ui.add_space(15.0);\n \n // 注意事项\n ui.heading(\"⚠️ 注意事项\");\n ui.label(\"• 确保有足够的磁盘空间存储输出文件\");\n ui.label(\"• 处理大量视频时可能需要较长时间\");\n ui.label(\"• 某些功能组合可能导致处理时间增加\");\n ui.label(\"• 建议定期备份原始视频文件\");\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "005f061560c43fd85435e1ae9fe0aed9310349f1": {"t": 1785757423.13009, "n": 60441, "h": "cbf94aaaecd24423", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n \n // --- Additional Parameters ---\n // Basic editing\n cut_seconds: f32, // seconds to cut from start/end\n mirror_direction: String, // \"horizontal\", \"vertical\", \"both\"\n strong_crop_ratio: f32, // crop ratio for strong crop\n \n // Visual enhancements\n portrait_strength: f32, // portrait blur strength\n color_temp_range: i32, // color temperature adjustment range\n pull_width: i32, // border width for pull effect\n progressive_ratio: f32, // frame drop ratio\n corner_radius: f32, // corner blur radius\n \n // AI & Effects\n zoom_range: f32, // zoom scale range\n dissolve_strength: f32, // dissolve effect strength\n scan_strength: f32, // light scan strength\n bounce_amplitude: f32, // bounce effect amplitude\n trifold_spacing: i32, // trifold spacing in pixels\n flash_strength: f32, // 3D flash strength\n lava_strength: f32, // lava AB mode strength\n \n // Audio\n noise_strength: f32, // white noise volume\n pitch_range: f32, // pitch shift range in semitones\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n current_tab: Tab::All,\n rx: None,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.1,\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 2.0,\n grain_strength: 0.1,\n vignette_strength: 0.5,\n border_width: 20,\n \n // Additional defaults\n cut_seconds: 1.0,\n mirror_direction: \"horizontal\".to_string(),\n strong_crop_ratio: 0.1,\n portrait_strength: 2.0,\n color_temp_range: 500,\n pull_width: 50,\n progressive_ratio: 0.1,\n corner_radius: 50.0,\n zoom_range: 0.1,\n dissolve_strength: 0.5,\n scan_strength: 0.5,\n bounce_amplitude: 20.0,\n trifold_spacing: 10,\n flash_strength: 0.3,\n lava_strength: 0.5,\n noise_strength: 0.01,\n pitch_range: 2.0,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n // Basic editing\n \"cut\" => {\n ui.heading(\"首尾去秒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"去除秒数:\");\n ui.add(egui::Slider::new(&mut self.cut_seconds, 0.1..=10.0).text(\"秒\"));\n });\n ui.small(\"从视频开头和结尾各去除指定秒数\");\n },\n \"mirror\" => {\n ui.heading(\"镜像翻转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"翻转方向:\");\n ui.selectable_value(&mut self.mirror_direction, \"horizontal\".to_string(), \"水平\");\n ui.selectable_value(&mut self.mirror_direction, \"vertical\".to_string(), \"垂直\");\n ui.selectable_value(&mut self.mirror_direction, \"both\".to_string(), \"双向\");\n });\n },\n \"strong_crop\" => {\n ui.heading(\"强力裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"裁剪比例:\");\n ui.add(egui::Slider::new(&mut self.strong_crop_ratio, 0.05..=0.3));\n });\n ui.small(\"裁剪比例越大,去重效果越强\");\n },\n // Visual enhancements\n \"portrait\" => {\n ui.heading(\"智能柔焦设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"柔焦强度:\");\n ui.add(egui::Slider::new(&mut self.portrait_strength, 0.5..=10.0));\n });\n },\n \"color\" => {\n ui.heading(\"随机色温设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"色温范围:\");\n ui.add(egui::Slider::new(&mut self.color_temp_range, 100..=2000).text(\"K\"));\n });\n ui.small(\"色温调整范围(开尔文)\");\n },\n \"pull\" => {\n ui.heading(\"智能补边设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"补边宽度:\");\n ui.add(egui::Slider::new(&mut self.pull_width, 10..=200).text(\"像素\"));\n });\n },\n \"progressive\" => {\n ui.heading(\"渐进处理设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"抽帧比例:\");\n ui.add(egui::Slider::new(&mut self.progressive_ratio, 0.05..=0.5));\n });\n ui.small(\"比例越大,抽帧越多\");\n },\n \"corner\" => {\n ui.heading(\"边角模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"模糊半径:\");\n ui.add(egui::Slider::new(&mut self.corner_radius, 10.0..=200.0).text(\"像素\"));\n });\n },\n // AI & Effects\n \"zoom\" => {\n ui.heading(\"AI随机缩放设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"缩放范围:\");\n ui.add(egui::Slider::new(&mut self.zoom_range, 0.01..=0.3));\n });\n ui.small(\"例如 0.1 代表 0.9x 到 1.1x 之间随机缩放\");\n },\n \"dissolve\" => {\n ui.heading(\"移动溶解设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"溶解强度:\");\n ui.add(egui::Slider::new(&mut self.dissolve_strength, 0.1..=1.0));\n });\n },\n \"scan\" => {\n ui.heading(\"随机光扫设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"光扫强度:\");\n ui.add(egui::Slider::new(&mut self.scan_strength, 0.1..=1.0));\n });\n },\n \"bounce\" => {\n ui.heading(\"弹跳效果设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"弹跳幅度:\");\n ui.add(egui::Slider::new(&mut self.bounce_amplitude, 5.0..=100.0).text(\"像素\"));\n });\n },\n \"trifold\" => {\n ui.heading(\"三联屏设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"屏幕间距:\");\n ui.add(egui::Slider::new(&mut self.trifold_spacing, 0..=50).text(\"像素\"));\n });\n },\n \"flash\" => {\n ui.heading(\"3D闪白设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"闪白强度:\");\n ui.add(egui::Slider::new(&mut self.flash_strength, 0.1..=1.0));\n });\n },\n \"lava\" => {\n ui.heading(\"岩浆AB模式设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"效果强度:\");\n ui.add(egui::Slider::new(&mut self.lava_strength, 0.1..=1.0));\n });\n },\n // Audio\n \"noise\" => {\n ui.heading(\"白噪音设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"噪音强度:\");\n ui.add(egui::Slider::new(&mut self.noise_strength, 0.001..=0.1));\n });\n ui.small(\"强度越大,噪音越明显\");\n },\n \"pitch\" => {\n ui.heading(\"音频变调设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变调范围:\");\n ui.add(egui::Slider::new(&mut self.pitch_range, 0.5..=12.0).text(\"半音\"));\n });\n ui.small(\"±半音数,例如 2 代表 -2 到 +2 半音\");\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Additional parameters\n config.params.as_object_mut().unwrap().insert(\"cut_seconds\".to_string(), serde_json::json!(self.cut_seconds));\n config.params.as_object_mut().unwrap().insert(\"mirror_direction\".to_string(), serde_json::json!(self.mirror_direction));\n config.params.as_object_mut().unwrap().insert(\"strong_crop_ratio\".to_string(), serde_json::json!(self.strong_crop_ratio));\n config.params.as_object_mut().unwrap().insert(\"portrait_strength\".to_string(), serde_json::json!(self.portrait_strength));\n config.params.as_object_mut().unwrap().insert(\"color_temp_range\".to_string(), serde_json::json!(self.color_temp_range));\n config.params.as_object_mut().unwrap().insert(\"pull_width\".to_string(), serde_json::json!(self.pull_width));\n config.params.as_object_mut().unwrap().insert(\"progressive_ratio\".to_string(), serde_json::json!(self.progressive_ratio));\n config.params.as_object_mut().unwrap().insert(\"corner_radius\".to_string(), serde_json::json!(self.corner_radius));\n config.params.as_object_mut().unwrap().insert(\"zoom_range\".to_string(), serde_json::json!(self.zoom_range));\n config.params.as_object_mut().unwrap().insert(\"dissolve_strength\".to_string(), serde_json::json!(self.dissolve_strength));\n config.params.as_object_mut().unwrap().insert(\"scan_strength\".to_string(), serde_json::json!(self.scan_strength));\n config.params.as_object_mut().unwrap().insert(\"bounce_amplitude\".to_string(), serde_json::json!(self.bounce_amplitude));\n config.params.as_object_mut().unwrap().insert(\"trifold_spacing\".to_string(), serde_json::json!(self.trifold_spacing));\n config.params.as_object_mut().unwrap().insert(\"flash_strength\".to_string(), serde_json::json!(self.flash_strength));\n config.params.as_object_mut().unwrap().insert(\"lava_strength\".to_string(), serde_json::json!(self.lava_strength));\n config.params.as_object_mut().unwrap().insert(\"noise_strength\".to_string(), serde_json::json!(self.noise_strength));\n config.params.as_object_mut().unwrap().insert(\"pitch_range\".to_string(), serde_json::json!(self.pitch_range));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "478f7432aeb01042c35abe4d6e5eaf4fb810742f": {"t": 1785757425.768296, "n": 48973, "h": "b8d2ac01559ac9e8", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n \n // Action Parameters\n action_params: std::collections::HashMap<String, serde_json::Value>,\n \n // Settings Dialog State\n show_settings_dialog: bool,\n settings_action_id: String,\n // Crop parameters\n crop_min: f32,\n crop_max: f32,\n // Watermark parameters\n watermark_position: String,\n watermark_opacity: f32,\n \n // --- New Parameters ---\n // Basic\n rotate_angle: f32, // Max rotation angle (degrees)\n speed_range: f32, // Speed variation (e.g. 0.1 for ±10%)\n target_fps: u32, // Target FPS (30, 60)\n target_bitrate: String, // e.g. \"10M\", \"15M\"\n \n // Visual\n sharpen_strength: f32, // 0.0 - 5.0\n denoise_strength: f32, // 0.0 - 20.0 (h value)\n blur_strength: f32, // sigma\n grain_strength: f32, // 0.0 - 0.5\n vignette_strength: f32, // angle/range\n \n // Effects\n border_width: i32, // pixels for blur border\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n current_tab: Tab::All,\n rx: None,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n action_params: std::collections::HashMap::new(),\n show_settings_dialog: false,\n settings_action_id: String::new(),\n crop_min: 0.01,\n crop_max: 0.05,\n watermark_position: \"top_right\".to_string(),\n watermark_opacity: 0.5,\n \n // Defaults\n rotate_angle: 1.5,\n speed_range: 0.1,\n target_fps: 60,\n target_bitrate: \"15M\".to_string(),\n sharpen_strength: 1.0,\n denoise_strength: 5.0,\n blur_strength: 2.0,\n grain_strength: 0.1,\n vignette_strength: 0.5,\n border_width: 20,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Settings Dialog\n if self.show_settings_dialog {\n egui::Window::new(\"参数设置\")\n .collapsible(false)\n .resizable(false)\n .show(ctx, |ui| {\n match self.settings_action_id.as_str() {\n \"crop\" => {\n ui.heading(\"随机微裁剪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最小比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_min).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.horizontal(|ui| {\n ui.label(\"最大比例:\");\n ui.add(egui::DragValue::new(&mut self.crop_max).speed(0.001).clamp_range(0.0..=0.5));\n });\n ui.small(\"范围: 0.0 - 0.5 (例如 0.05 代表 5%)\");\n },\n \"rotate\" => {\n ui.heading(\"微旋转设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"最大角度:\");\n ui.add(egui::Slider::new(&mut self.rotate_angle, 0.1..=10.0).text(\"度\"));\n });\n ui.small(\"视频将在此范围内随机旋转\");\n },\n \"speed\" => {\n ui.heading(\"变速设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"变速范围:\");\n ui.add(egui::Slider::new(&mut self.speed_range, 0.01..=0.5).text(\"幅度\"));\n });\n ui.small(\"例如 0.1 代表速度在 0.9x 到 1.1x 之间随机\");\n },\n \"fps\" => {\n ui.heading(\"帧率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标帧率:\");\n ui.selectable_value(&mut self.target_fps, 30, \"30 FPS\");\n ui.selectable_value(&mut self.target_fps, 60, \"60 FPS\");\n });\n },\n \"bitrate\" => {\n ui.heading(\"码率设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"目标码率:\");\n ui.text_edit_singleline(&mut self.target_bitrate);\n });\n ui.small(\"例如: 10M, 15M, 5000k\");\n },\n \"sharpen\" => {\n ui.heading(\"锐化设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.sharpen_strength, 0.0..=5.0));\n });\n },\n \"denoise\" => {\n ui.heading(\"降噪设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.denoise_strength, 0.0..=20.0));\n });\n },\n \"blur\" => {\n ui.heading(\"模糊设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度 (Sigma):\");\n ui.add(egui::Slider::new(&mut self.blur_strength, 0.1..=10.0));\n });\n },\n \"grain\" => {\n ui.heading(\"颗粒设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.grain_strength, 0.0..=0.5));\n });\n },\n \"vignette\" => {\n ui.heading(\"暗角设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"强度:\");\n ui.add(egui::Slider::new(&mut self.vignette_strength, 0.1..=1.0));\n });\n },\n \"border\" => {\n ui.heading(\"边框设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"宽度 (像素):\");\n ui.add(egui::DragValue::new(&mut self.border_width).speed(1).clamp_range(0..=500));\n });\n ui.small(\"仅在使用默认模糊边框时有效\");\n },\n \"watermark\" => {\n ui.heading(\"水印设置\");\n ui.add_space(5.0);\n ui.horizontal(|ui| {\n ui.label(\"位置:\");\n egui::ComboBox::from_id_source(\"wm_pos\")\n .selected_text(match self.watermark_position.as_str() {\n \"top_left\" => \"左上\",\n \"top_right\" => \"右上\",\n \"bottom_left\" => \"左下\",\n \"bottom_right\" => \"右下\",\n \"center\" => \"居中\",\n _ => \"右上\"\n })\n .show_ui(ui, |ui| {\n ui.selectable_value(&mut self.watermark_position, \"top_left\".to_string(), \"左上\");\n ui.selectable_value(&mut self.watermark_position, \"top_right\".to_string(), \"右上\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_left\".to_string(), \"左下\");\n ui.selectable_value(&mut self.watermark_position, \"bottom_right\".to_string(), \"右下\");\n ui.selectable_value(&mut self.watermark_position, \"center\".to_string(), \"居中\");\n });\n });\n ui.horizontal(|ui| {\n ui.label(\"透明度:\");\n ui.add(egui::Slider::new(&mut self.watermark_opacity, 0.1..=1.0).text(\"不透明度\"));\n });\n },\n \"md5\" | \"clean\" | \"mute\" => {\n ui.label(\"此功能无需参数设置\");\n },\n _ => {\n ui.label(\"此功能暂无参数设置\");\n }\n }\n \n ui.add_space(10.0);\n if ui.button(\"关闭\").clicked() {\n self.show_settings_dialog = false;\n }\n });\n }\n\n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .num_columns(4)\n .spacing([10.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, _checked) = &self.checkboxes[i];\n let is_checked = self.selected_actions.contains(id);\n let mut checked = is_checked;\n \n ui.horizontal(|ui| {\n if ui.checkbox(&mut checked, name).changed() {\n updates.push((id.clone(), name.clone(), is_checked, checked));\n }\n \n // Add settings button for all actions\n if ui.button(\"⚙\").clicked() {\n self.settings_action_id = id.clone();\n self.show_settings_dialog = true;\n }\n });\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n \n // Add parameters\n config.params.as_object_mut().unwrap().insert(\"crop_min\".to_string(), serde_json::json!(self.crop_min));\n config.params.as_object_mut().unwrap().insert(\"crop_max\".to_string(), serde_json::json!(self.crop_max));\n config.params.as_object_mut().unwrap().insert(\"watermark_position\".to_string(), serde_json::json!(self.watermark_position));\n config.params.as_object_mut().unwrap().insert(\"watermark_opacity\".to_string(), serde_json::json!(self.watermark_opacity));\n \n // New parameters\n config.params.as_object_mut().unwrap().insert(\"rotate_angle\".to_string(), serde_json::json!(self.rotate_angle));\n config.params.as_object_mut().unwrap().insert(\"speed_range\".to_string(), serde_json::json!(self.speed_range));\n config.params.as_object_mut().unwrap().insert(\"target_fps\".to_string(), serde_json::json!(self.target_fps));\n config.params.as_object_mut().unwrap().insert(\"target_bitrate\".to_string(), serde_json::json!(self.target_bitrate));\n config.params.as_object_mut().unwrap().insert(\"sharpen_strength\".to_string(), serde_json::json!(self.sharpen_strength));\n config.params.as_object_mut().unwrap().insert(\"denoise_strength\".to_string(), serde_json::json!(self.denoise_strength));\n config.params.as_object_mut().unwrap().insert(\"blur_strength\".to_string(), serde_json::json!(self.blur_strength));\n config.params.as_object_mut().unwrap().insert(\"grain_strength\".to_string(), serde_json::json!(self.grain_strength));\n config.params.as_object_mut().unwrap().insert(\"vignette_strength\".to_string(), serde_json::json!(self.vignette_strength));\n config.params.as_object_mut().unwrap().insert(\"border_width\".to_string(), serde_json::json!(self.border_width));\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "76c0239781133113cf285acd5e004fbb8c2ce4f0": {"t": 1785757427.494567, "n": 37582, "h": "044fcf460fb0711b", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Material Paths\n watermark_path: String,\n mask_path: String,\n sticker_path: String,\n border_path: String,\n light_effect_path: String,\n pip_path: String,\n goods_path: String,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n Materials, // New Materials Tab\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (中文版本)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // 基础编辑与参数\n checkboxes.extend(vec![\n (\"一键MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"随机微裁剪 (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"首尾去秒 (各1秒)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"微旋转 (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"非线性变速 (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"镜像翻转\".to_string(), \"mirror\".to_string(), false),\n (\"强制60帧\".to_string(), \"fps_60\".to_string(), false),\n (\"高码率 (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // 视觉增强\n checkboxes.extend(vec![\n (\"智能锐化\".to_string(), \"sharpen\".to_string(), false),\n (\"智能锐化 (人像)\".to_string(), \"portrait\".to_string(), false),\n (\"智能降噪\".to_string(), \"denoise\".to_string(), false),\n (\"智能降噪 (清洁)\".to_string(), \"clean\".to_string(), false),\n (\"胶片颗粒\".to_string(), \"grain\".to_string(), false),\n (\"智能柔焦\".to_string(), \"blur\".to_string(), false),\n (\"随机色温\".to_string(), \"color\".to_string(), false),\n (\"电影暗角\".to_string(), \"vignette\".to_string(), false),\n (\"黑白怀旧\".to_string(), \"bw\".to_string(), false),\n (\"智能补边\".to_string(), \"border\".to_string(), false),\n (\"智能抽帧\".to_string(), \"pull\".to_string(), false),\n (\"边角模糊\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI与AB模式\n checkboxes.extend(vec![\n (\"AI随机缩放\".to_string(), \"zoom\".to_string(), false),\n (\"AI移动溶解\".to_string(), \"dissolve\".to_string(), false),\n (\"AI随机光扫\".to_string(), \"scan\".to_string(), false),\n (\"弹跳效果\".to_string(), \"bounce\".to_string(), false),\n (\"三联屏效果\".to_string(), \"trifold\".to_string(), false),\n (\"岩浆AB模式\".to_string(), \"lava\".to_string(), false),\n (\"3D闪白\".to_string(), \"flash\".to_string(), false),\n (\"渐进处理\".to_string(), \"progressive\".to_string(), false),\n (\"AB混合模式\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB故障效果\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB抖动效果\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB色度偏移\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB视频替换\".to_string(), \"ab_replace\".to_string(), false),\n (\"高级AB替换\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // 音频与其他\n checkboxes.extend(vec![\n (\"静音视频\".to_string(), \"mute\".to_string(), false),\n (\"混入弱白噪音\".to_string(), \"audio_noise\".to_string(), false),\n (\"音频变调\".to_string(), \"pitch\".to_string(), false),\n (\"仅修改时间戳\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === 附加功能 (Tab::Additional) ===\n // 强力去重\n checkboxes.extend(vec![\n (\"强力裁剪 (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"添加水印\".to_string(), \"watermark\".to_string(), false),\n (\"修改编码参数\".to_string(), \"encode\".to_string(), false),\n (\"添加贴纸\".to_string(), \"sticker\".to_string(), false),\n (\"蒙版叠加\".to_string(), \"mask\".to_string(), false),\n (\"真实AB替换\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV功能\n checkboxes.extend(vec![\n (\"人脸检测\".to_string(), \"face_detection\".to_string(), false),\n (\"物体追踪\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV滤镜\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // 新素材功能\n checkboxes.extend(vec![\n (\"光效叠加\".to_string(), \"light_effect\".to_string(), false),\n (\"画中画\".to_string(), \"pip\".to_string(), false),\n (\"边缘效果\".to_string(), \"edge_effect\".to_string(), false),\n (\"带货模板\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n current_tab: Tab::All,\n rx: None,\n log_messages: vec![\n \"✨ 视频矩阵 Pro 已就绪\".to_string(),\n \"💡 提示:选择输入目录,勾选功能,然后点击\\\"开始处理\\\"\".to_string(),\n ],\n checkboxes,\n watermark_path: String::new(),\n mask_path: String::new(),\n sticker_path: String::new(),\n border_path: String::new(),\n light_effect_path: String::new(),\n pip_path: String::new(),\n goods_path: String::new(),\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // === Custom Visuals for Better Aesthetics ===\n let mut visuals = egui::Visuals::dark();\n \n // Grey Theme & High Contrast\n visuals.window_fill = egui::Color32::from_rgb(50, 50, 50); // Lighter grey background\n visuals.panel_fill = egui::Color32::from_rgb(50, 50, 50);\n visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(50, 50, 50);\n \n // High contrast text\n visuals.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n visuals.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, egui::Color32::WHITE);\n \n visuals.selection.bg_fill = egui::Color32::from_rgb(100, 100, 100); // Grey selection\n ctx.set_visuals(visuals);\n\n // Increase Font Size\n let mut style = (*ctx.style()).clone();\n style.text_styles = [\n (egui::TextStyle::Heading, egui::FontId::new(24.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Body, egui::FontId::new(16.0, egui::FontFamily::Proportional)), // Base font size 16\n (egui::TextStyle::Monospace, egui::FontId::new(14.0, egui::FontFamily::Monospace)),\n (egui::TextStyle::Button, egui::FontId::new(16.0, egui::FontFamily::Proportional)),\n (egui::TextStyle::Small, egui::FontId::new(12.0, egui::FontFamily::Proportional)),\n ].into();\n ctx.set_style(style);\n\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 所有任务已完成!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ 错误: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(180.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(8.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(egui::RichText::new(format!(\"已选择 {} 个功能\", self.selected_actions.len())).strong());\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.add(egui::Button::new(\"🛑 停止\").fill(egui::Color32::from_rgb(200, 50, 50))).clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 开始处理\").min_size(egui::vec2(120.0, 30.0));\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择输入目录\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ 请选择功能\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ 处理中...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn.fill(egui::Color32::from_rgb(0, 122, 204))).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"请先选择输入目录\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"请至少选择一个功能\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"正在处理中,请稍候\");\n }\n }\n });\n });\n \n ui.add_space(8.0);\n \n // Progress Bar\n let progress_bar = egui::ProgressBar::new(self.progress)\n .show_percentage()\n .animate(self.is_processing);\n ui.add(progress_bar);\n \n ui.add_space(8.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 处理日志\", |ui| {\n let text_style = egui::TextStyle::Monospace;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n let msg = &self.log_messages[row];\n let color = if msg.contains(\"Error\") || msg.contains(\"Failed\") {\n egui::Color32::LIGHT_RED\n } else if msg.contains(\"Completed\") || msg.contains(\"Success\") {\n egui::Color32::LIGHT_GREEN\n } else {\n egui::Color32::LIGHT_GRAY\n };\n ui.colored_label(color, msg);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.horizontal(|ui| {\n ui.heading(egui::RichText::new(\"视频矩阵 Pro\").size(24.0).strong());\n ui.label(egui::RichText::new(\"V5.4\").size(14.0).color(egui::Color32::GRAY));\n });\n ui.add_space(10.0);\n \n // Workspace Section\n egui::Frame::group(ui.style())\n .fill(egui::Color32::from_rgb(35, 35, 35))\n .inner_margin(10.0)\n .show(ui, |ui| {\n ui.set_width(ui.available_width());\n ui.heading(\"📁 工作目录\");\n ui.add_space(5.0);\n \n ui.horizontal(|ui| {\n ui.label(\"输入:\");\n let _input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"选择视频源文件夹...\")\n .desired_width(400.0)\n );\n if ui.button(\"📂 浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输入目录: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"输出:\");\n let _output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"默认:输入目录/output\")\n .desired_width(400.0)\n );\n if ui.button(\"💾 保存到\").clicked() {\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择输出目录: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.add_space(15.0);\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"🛠️ 全部功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"✨ 附加功能\");\n ui.selectable_value(&mut self.current_tab, Tab::Materials, \"🎨 素材设置\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n self.render_checkbox_group(ui, \"✂️ 基础编辑\", 0..8, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎨 视觉增强\", 8..20, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🤖 AI与AB模式\", 20..34, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"🎵 音频与其他\", 34..38, &mut updates);\n }\n Tab::Additional => {\n self.render_checkbox_group(ui, \"💪 强力去重\", 38..44, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"👁️ OpenCV功能\", 44..47, &mut updates);\n ui.add_space(10.0);\n self.render_checkbox_group(ui, \"✨ 新素材功能\", 47..51, &mut updates);\n }\n Tab::Materials => {\n ui.heading(\"🎨 素材设置\");\n ui.add_space(10.0);\n \n // 水印素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"水印图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.watermark_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\", \"jpeg\"]).pick_file() {\n self.watermark_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择水印: {}\", self.watermark_path));\n }\n }\n });\n ui.small(\"支持格式:PNG (推荐), JPG\");\n });\n \n ui.add_space(10.0);\n \n // 蒙版素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"蒙版图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.mask_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"jpg\"]).pick_file() {\n self.mask_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择蒙版: {}\", self.mask_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 贴纸素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"贴纸图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.sticker_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\", \"gif\"]).pick_file() {\n self.sticker_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择贴纸: {}\", self.sticker_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 边框素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"边框图片:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.border_path).hint_text(\"选择图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"图片\", &[\"png\"]).pick_file() {\n self.border_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择边框: {}\", self.border_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 光效素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"光效素材:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.light_effect_path).hint_text(\"选择视频或图片...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"mov\", \"png\"]).pick_file() {\n self.light_effect_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择光效: {}\", self.light_effect_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 画中画素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"画中画视频:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.pip_path).hint_text(\"选择视频...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"视频\", &[\"mp4\", \"mov\", \"avi\"]).pick_file() {\n self.pip_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择画中画: {}\", self.pip_path));\n }\n }\n });\n });\n \n ui.add_space(10.0);\n \n // 带货模板素材\n egui::Frame::group(ui.style()).inner_margin(10.0).show(ui, |ui| {\n ui.label(\"带货模板:\");\n ui.horizontal(|ui| {\n ui.add(egui::TextEdit::singleline(&mut self.goods_path).hint_text(\"选择模板...\").desired_width(400.0));\n if ui.button(\"浏览\").clicked() {\n if let Some(path) = rfd::FileDialog::new().add_filter(\"媒体\", &[\"mp4\", \"png\"]).pick_file() {\n self.goods_path = path.to_string_lossy().to_string();\n self.log(&format!(\"已选择模板: {}\", self.goods_path));\n }\n }\n });\n });\n }\n }\n \n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"已选择\" } else { \"已取消\" }, name));\n }\n });\n });\n \n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn render_checkbox_group(&mut self, ui: &mut egui::Ui, title: &str, range: std::ops::Range<usize>, updates: &mut Vec<(String, String, bool, bool)>) {\n ui.heading(title);\n ui.add_space(5.0);\n \n egui::Grid::new(format!(\"grid_{}\", title))\n .striped(true)\n .spacing([20.0, 10.0])\n .show(ui, |ui| {\n let mut col = 0;\n for i in range {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n \n col += 1;\n if col >= 4 { // 4 columns for better space usage\n ui.end_row();\n col = 0;\n }\n }\n if col != 0 {\n ui.end_row();\n }\n });\n }\n\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 开始后台处理...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Prepare config with material paths\n let mut config = ActionConfig::default();\n if !self.watermark_path.is_empty() { config.watermark_path = Some(self.watermark_path.clone()); }\n if !self.mask_path.is_empty() { config.mask_path = Some(self.mask_path.clone()); }\n if !self.sticker_path.is_empty() { config.sticker_path = Some(self.sticker_path.clone()); }\n if !self.border_path.is_empty() { config.border_path = Some(self.border_path.clone()); }\n if !self.light_effect_path.is_empty() { config.light_effect_path = Some(self.light_effect_path.clone()); }\n if !self.pip_path.is_empty() { config.pip_path = Some(self.pip_path.clone()); }\n if !self.goods_path.is_empty() { config.goods_path = Some(self.goods_path.clone()); }\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, config, tx_clone) {\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, config: ActionConfig, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 用户停止处理\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"视频矩阵 Pro V5.4\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"视频矩阵 Pro\",\n options,\n Box::new(|cc| {\n // Load Chinese fonts\n let mut fonts = egui::FontDefinitions::default();\n \n // Try to load system fonts for Chinese support\n #[cfg(target_os = \"macos\")]\n let font_paths = vec![\n \"/System/Library/Fonts/PingFang.ttc\",\n \"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\",\n ];\n \n #[cfg(target_os = \"windows\")]\n let font_paths = vec![\n \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\",\n \"C:\\\\Windows\\\\Fonts\\\\simhei.ttf\",\n ];\n \n #[cfg(not(any(target_os = \"macos\", target_os = \"windows\")))]\n let font_paths: Vec<&str> = vec![];\n \n // Try loading fonts\n for path in font_paths {\n if let Ok(font_data) = std::fs::read(path) {\n fonts.font_data.insert(\n \"chinese_font\".to_owned(),\n Arc::new(egui::FontData::from_owned(font_data))\n );\n \n // Insert at the beginning of all font families\n fonts.families.entry(egui::FontFamily::Proportional)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n fonts.families.entry(egui::FontFamily::Monospace)\n .or_default()\n .insert(0, \"chinese_font\".to_owned());\n \n break; // Successfully loaded, stop trying\n }\n }\n \n cc.egui_ctx.set_fonts(fonts);\n \n Ok(Box::<VideoMatrixApp>::default())\n }),\n )\n}"}, "0a98201c84647693f5212ae5c5a8be1fd3ca1ade": {"t": 1785757428.9218876, "n": 28115, "h": "b635a214ffeeeda7", "c": "use eframe::egui;\nuse chrono;\nuse std::path::{Path, PathBuf};\nuse std::fs;\nuse std::sync::mpsc::{channel, Receiver, Sender};\nuse std::thread;\nuse std::sync::Arc;\nuse crate::core::{VideoAction, ActionConfig};\nuse crate::actions::*;\n\n// Message types for communication between threads\nenum AppMessage {\n Log(String),\n Progress(f32),\n Finished,\n Error(String),\n}\n\n// App State\nstruct VideoMatrixApp {\n input_dir: String,\n output_dir: String,\n selected_actions: Vec<String>,\n is_processing: bool,\n progress: f32,\n log_messages: Vec<String>,\n \n // Thread communication\n rx: Option<Receiver<AppMessage>>,\n \n // Tab State\n current_tab: Tab,\n \n // Checkbox State\n checkboxes: Vec<(String, String, bool)>, // (Display Name, ID, Checked)\n}\n\n// Tab Enum\n#[derive(PartialEq, Clone, Copy)]\nenum Tab {\n All, // All-in-One Panel\n Additional, // Additional Features\n}\n\nimpl Default for Tab {\n fn default() -> Self {\n Tab::All\n }\n}\n\nimpl Default for VideoMatrixApp {\n fn default() -> Self {\n // Initialize all checkboxes (English version)\n let mut checkboxes = Vec::new();\n \n // === All-in-One Panel (Tab::All) ===\n // Basic Editing & Parameters\n checkboxes.extend(vec![\n (\"One-Click MD5 (Remux)\".to_string(), \"md5\".to_string(), false),\n (\"Random Micro-Crop (1-5%)\".to_string(), \"crop\".to_string(), false),\n (\"Trim Head/Tail (1s each)\".to_string(), \"cut_head_tail\".to_string(), false),\n (\"Micro Rotation (±1.5°)\".to_string(), \"rotate\".to_string(), false),\n (\"Non-linear Speed (0.9-1.1x)\".to_string(), \"speed\".to_string(), false),\n (\"Mirror Flip\".to_string(), \"mirror\".to_string(), false),\n (\"Force 60 FPS\".to_string(), \"fps_60\".to_string(), false),\n (\"High Bitrate (15Mbps)\".to_string(), \"bitrate_hq\".to_string(), false),\n ]);\n \n // Visual Enhancements\n checkboxes.extend(vec![\n (\"Smart Sharpen\".to_string(), \"sharpen\".to_string(), false),\n (\"Smart Sharpen (Portrait)\".to_string(), \"portrait\".to_string(), false),\n (\"Smart Denoise\".to_string(), \"denoise\".to_string(), false),\n (\"Smart Denoise (Clean)\".to_string(), \"clean\".to_string(), false),\n (\"Film Grain\".to_string(), \"grain\".to_string(), false),\n (\"Smart Soft Focus\".to_string(), \"blur\".to_string(), false),\n (\"Random Color Temp\".to_string(), \"color\".to_string(), false),\n (\"Cinematic Vignette\".to_string(), \"vignette\".to_string(), false),\n (\"B&W Nostalgia\".to_string(), \"bw\".to_string(), false),\n (\"Smart Border Fill\".to_string(), \"border\".to_string(), false),\n (\"Smart Frame Pull\".to_string(), \"pull\".to_string(), false),\n (\"Corner Blur Mask\".to_string(), \"corner\".to_string(), false),\n ]);\n \n // AI & AB Modes\n checkboxes.extend(vec![\n (\"AI Random Zoom (ZoomPan)\".to_string(), \"zoom\".to_string(), false),\n (\"AI Move Dissolve\".to_string(), \"dissolve\".to_string(), false),\n (\"AI Random Light Scan\".to_string(), \"scan\".to_string(), false),\n (\"Bounce Effect\".to_string(), \"bounce\".to_string(), false),\n (\"Trifold Effect\".to_string(), \"trifold\".to_string(), false),\n (\"Lava AB Mode\".to_string(), \"lava\".to_string(), false),\n (\"3D Flash\".to_string(), \"flash\".to_string(), false),\n (\"Progressive Process\".to_string(), \"progressive\".to_string(), false),\n (\"AB Blend Mode\".to_string(), \"ab_blend\".to_string(), false),\n (\"AB Glitch Effect\".to_string(), \"ab_glitch\".to_string(), false),\n (\"AB Shake Effect\".to_string(), \"ab_shake\".to_string(), false),\n (\"AB Chroma Offset\".to_string(), \"ab_chroma\".to_string(), false),\n (\"AB Video Replace\".to_string(), \"ab_replace\".to_string(), false),\n (\"Advanced AB Replace\".to_string(), \"ab_advanced_replace\".to_string(), false),\n ]);\n \n // Audio & Others\n checkboxes.extend(vec![\n (\"Mute Video\".to_string(), \"mute\".to_string(), false),\n (\"Mix Weak White Noise\".to_string(), \"audio_noise\".to_string(), false),\n (\"Audio Pitch Shift\".to_string(), \"pitch\".to_string(), false),\n (\"Modify Timestamp Only\".to_string(), \"touch\".to_string(), false),\n ]);\n \n // === Additional Features (Tab::Additional) ===\n // Strong Deduplication\n checkboxes.extend(vec![\n (\"Strong Crop (8-12%)\".to_string(), \"strong_crop\".to_string(), false),\n (\"Add Watermark\".to_string(), \"watermark\".to_string(), false),\n (\"Modify Encode Params\".to_string(), \"encode\".to_string(), false),\n (\"Add Sticker\".to_string(), \"sticker\".to_string(), false),\n (\"Mask Overlay\".to_string(), \"mask\".to_string(), false),\n (\"Real AB Replace\".to_string(), \"ab_real_replace\".to_string(), false),\n ]);\n \n // OpenCV Features\n checkboxes.extend(vec![\n (\"Face Detection\".to_string(), \"face_detection\".to_string(), false),\n (\"Object Tracking\".to_string(), \"object_tracking\".to_string(), false),\n (\"OpenCV Filter\".to_string(), \"opencv_filter\".to_string(), false),\n ]);\n \n // New Material Features\n checkboxes.extend(vec![\n (\"Light Effect Overlay\".to_string(), \"light_effect\".to_string(), false),\n (\"Picture-in-Picture\".to_string(), \"pip\".to_string(), false),\n (\"Edge Effect\".to_string(), \"edge_effect\".to_string(), false),\n (\"Goods Template\".to_string(), \"goods_template\".to_string(), false),\n ]);\n \n Self {\n input_dir: String::new(),\n output_dir: String::new(),\n selected_actions: Vec::new(),\n is_processing: false,\n progress: 0.0,\n current_tab: Tab::All,\n rx: None,\n log_messages: vec![\n \"✨ Video Matrix Pro Ready\".to_string(),\n \"💡 Tip: Select input folder, check features, then click 'Start Processing'\".to_string(),\n ],\n checkboxes,\n }\n }\n}\n\nimpl eframe::App for VideoMatrixApp {\n fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n // Check for messages from the processing thread\n if let Some(rx) = self.rx.take() {\n let mut keep_rx = true;\n while let Ok(msg) = rx.try_recv() {\n match msg {\n AppMessage::Log(text) => self.log_internal(text),\n AppMessage::Progress(p) => self.progress = p,\n AppMessage::Finished => {\n self.is_processing = false;\n keep_rx = false;\n self.log_internal(\"🎉 All tasks completed!\".to_string());\n self.progress = 1.0;\n },\n AppMessage::Error(e) => {\n self.log_internal(format!(\"❌ Error: {}\", e));\n self.is_processing = false;\n keep_rx = false;\n }\n }\n }\n if keep_rx {\n self.rx = Some(rx);\n }\n }\n\n // Bottom Panel for Controls, Progress, and Logs\n egui::TopBottomPanel::bottom(\"bottom_panel\")\n .resizable(true)\n .min_height(150.0)\n .show(ctx, |ui| {\n ui.vertical(|ui| {\n ui.add_space(5.0);\n \n // Control Area\n ui.horizontal(|ui| {\n ui.label(format!(\"Selected {} features\", self.selected_actions.len()));\n ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {\n if ui.button(\"🛑 Stop\").clicked() {\n self.stop_processing();\n }\n \n let can_start = !self.input_dir.is_empty() && !self.selected_actions.is_empty() && !self.is_processing;\n let start_btn = egui::Button::new(\"🚀 Start Processing\");\n \n // Status Text\n if !can_start {\n if self.input_dir.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ Select Input Folder\");\n } else if self.selected_actions.is_empty() {\n ui.colored_label(egui::Color32::RED, \"⚠️ Select Features\");\n } else if self.is_processing {\n ui.colored_label(egui::Color32::YELLOW, \"⏳ Processing...\");\n }\n }\n\n if can_start {\n if ui.add(start_btn).clicked() {\n self.start_processing();\n }\n } else {\n let response = ui.add_enabled(false, start_btn);\n if self.input_dir.is_empty() {\n response.on_disabled_hover_text(\"Please select an input directory\");\n } else if self.selected_actions.is_empty() {\n response.on_disabled_hover_text(\"Please select at least one feature\");\n } else if self.is_processing {\n response.on_disabled_hover_text(\"Processing is already in progress\");\n }\n }\n });\n });\n \n ui.add_space(5.0);\n \n // Progress Bar\n ui.add(egui::ProgressBar::new(self.progress).show_percentage());\n \n ui.add_space(5.0);\n ui.separator();\n \n // Log Area\n ui.collapsing(\"📋 Processing Logs\", |ui| {\n let text_style = egui::TextStyle::Body;\n let row_height = ui.text_style_height(&text_style);\n let total_rows = self.log_messages.len();\n \n egui::ScrollArea::vertical()\n .auto_shrink([false; 2])\n .stick_to_bottom(true)\n .show_rows(ui, row_height, total_rows, |ui, row_range| {\n for row in row_range {\n ui.label(&self.log_messages[row]);\n }\n });\n });\n ui.add_space(5.0);\n });\n });\n\n egui::CentralPanel::default().show(ctx, |ui| {\n // Header\n ui.heading(\"Video Matrix Pro V5.4\");\n ui.colored_label(egui::Color32::GRAY, \"Rust Refactored - High Performance Video Tool\");\n ui.separator();\n \n // Workspace\n ui.collapsing(\"📁 Workspace\", |ui| {\n ui.horizontal(|ui| {\n ui.label(\"Input:\");\n let input_response = ui.add(\n egui::TextEdit::singleline(&mut self.input_dir)\n .hint_text(\"Drag folder here...\")\n );\n if ui.button(\"Browse\").clicked() {\n // Use rfd to open folder dialog\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.input_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"Input directory selected: {}\", self.input_dir));\n }\n }\n });\n \n ui.horizontal(|ui| {\n ui.label(\"Output:\");\n let output_response = ui.add(\n egui::TextEdit::singleline(&mut self.output_dir)\n .hint_text(\"Output path (optional, default: input/output)...\")\n );\n if ui.button(\"Save To\").clicked() {\n // Use rfd to open folder dialog\n if let Some(path) = rfd::FileDialog::new().pick_folder() {\n self.output_dir = path.to_string_lossy().to_string();\n self.log(&format!(\"Output directory selected: {}\", self.output_dir));\n }\n }\n });\n });\n \n ui.separator();\n \n // Tab Selection\n ui.horizontal(|ui| {\n ui.selectable_value(&mut self.current_tab, Tab::All, \"All-in-One Panel\");\n ui.selectable_value(&mut self.current_tab, Tab::Additional, \"Additional Features\");\n });\n \n ui.separator();\n \n // Scrollable Area for Features\n egui::ScrollArea::vertical().show(ui, |ui| {\n // Collect updates\n let mut updates = Vec::new();\n \n // Show features based on current tab\n match self.current_tab {\n Tab::All => {\n // All-in-One Panel\n ui.heading(\"✂️ Basic Editing & Parameters\");\n for i in 0..8 {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n }\n \n ui.separator();\n ui.heading(\"🎨 Visual Enhancements\");\n for i in 8..20 {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n }\n \n ui.separator();\n ui.heading(\"🤖 AI & AB Modes\");\n for i in 20..34 {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n }\n \n ui.separator();\n ui.heading(\"🎵 Audio & Others\");\n for i in 34..38 {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n }\n }\n Tab::Additional => {\n // Additional Features\n ui.heading(\"💪 Strong Deduplication\");\n for i in 38..44 {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n }\n \n ui.separator();\n ui.heading(\"👁️ OpenCV Features\");\n for i in 44..47 {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n }\n \n ui.separator();\n ui.heading(\"✨ New Material Features\");\n for i in 47..51 {\n let (name, id, checked) = &mut self.checkboxes[i];\n let old_checked = *checked;\n if ui.checkbox(checked, name.as_str()).changed() {\n updates.push((id.clone(), name.clone(), old_checked, *checked));\n }\n }\n }\n }\n \n // Process updates\n for (id, name, _old_checked, new_checked) in updates {\n if new_checked {\n self.selected_actions.push(id);\n } else {\n self.selected_actions.retain(|x| x != &id);\n }\n self.log(&format!(\"{} {}\", if new_checked { \"Selected\" } else { \"Unselected\" }, name));\n }\n });\n });\n \n // Request repaint to keep UI responsive during processing\n if self.is_processing {\n ctx.request_repaint();\n }\n }\n}\n\nimpl VideoMatrixApp {\n fn log(&mut self, message: &str) {\n self.log_internal(message.to_string());\n }\n\n fn log_internal(&mut self, message: String) {\n let timestamp = chrono::Local::now().format(\"[%H:%M:%S]\").to_string();\n self.log_messages.push(format!(\"{} {}\", timestamp, message));\n // Limit log size\n if self.log_messages.len() > 100 {\n self.log_messages.remove(0);\n }\n }\n \n fn start_processing(&mut self) {\n self.is_processing = true;\n self.progress = 0.0;\n self.log(\"🚀 Starting processing (Background Thread)...\");\n \n let input_dir = self.input_dir.clone();\n let output_dir = if self.output_dir.is_empty() {\n format!(\"{}/output\", self.input_dir)\n } else {\n self.output_dir.clone()\n };\n let selected_actions = self.selected_actions.clone();\n \n // Create channel\n let (tx, rx) = channel();\n self.rx = Some(rx);\n \n // Clone for thread\n let tx_clone = tx.clone();\n \n // Spawn thread\n thread::spawn(move || {\n if let Err(e) = Self::process_thread(input_dir, output_dir, selected_actions, tx_clone) {\n // We can't easily send the error back if the channel is closed, but we try\n // In a real app we might want better error handling\n eprintln!(\"Thread error: {}\", e);\n }\n });\n }\n\n fn process_thread(input_dir: String, output_dir: String, actions: Vec<String>, tx: Sender<AppMessage>) -> anyhow::Result<()> {\n let _ = tx.send(AppMessage::Log(format!(\"📂 Input: {}\", input_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"📂 Output: {}\", output_dir)));\n let _ = tx.send(AppMessage::Log(format!(\"✅ Selected {} features\", actions.len())));\n \n // Scan video files\n let _ = tx.send(AppMessage::Log(\"🔍 Scanning for video files...\".to_string()));\n let video_files = Self::scan_video_files_static(&input_dir);\n \n if video_files.is_empty() {\n let _ = tx.send(AppMessage::Error(\"No video files found\".to_string()));\n return Ok(());\n }\n \n let _ = tx.send(AppMessage::Log(format!(\"📹 Found {} video files\", video_files.len())));\n \n let total_tasks = (video_files.len() * actions.len()) as f32;\n let mut completed_tasks = 0.0;\n \n // Create output directory\n let out_path = PathBuf::from(&output_dir);\n if let Err(e) = fs::create_dir_all(&out_path) {\n let _ = tx.send(AppMessage::Error(format!(\"Failed to create output directory: {}\", e)));\n return Ok(());\n }\n \n // Process each video file\n let config = ActionConfig::default();\n for video_file in &video_files {\n let video_path = Path::new(video_file);\n let filename = video_path.file_name().unwrap().to_string_lossy();\n \n for action_id in &actions {\n let _ = tx.send(AppMessage::Log(format!(\" ⏳ Processing: {} [{}]...\", filename, action_id)));\n \n // Call corresponding action\n let result = Self::execute_action_static(action_id, video_path, &out_path, &config);\n \n match result {\n Ok(_) => {\n completed_tasks += 1.0;\n let _ = tx.send(AppMessage::Progress(completed_tasks / total_tasks));\n let _ = tx.send(AppMessage::Log(format!(\" ✅ {} Completed ({})\", action_id, filename)));\n }\n Err(e) => {\n let _ = tx.send(AppMessage::Log(format!(\" ❌ {} Failed ({}): {}\", action_id, filename, e)));\n }\n }\n }\n }\n \n let _ = tx.send(AppMessage::Finished);\n Ok(())\n }\n \n fn scan_video_files(&self, dir: &str) -> Vec<String> {\n Self::scan_video_files_static(dir)\n }\n\n fn scan_video_files_static(dir: &str) -> Vec<String> {\n let mut video_files = Vec::new();\n let video_extensions = vec![\"mp4\", \"mov\", \"mkv\", \"avi\", \"wmv\", \"flv\", \"webm\", \"m4v\"];\n \n if let Ok(entries) = fs::read_dir(dir) {\n for entry in entries.flatten() {\n if let Ok(metadata) = entry.metadata() {\n if metadata.is_file() {\n if let Some(ext) = entry.path().extension() {\n if let Some(ext_str) = ext.to_str() {\n if video_extensions.contains(&ext_str.to_lowercase().as_str()) {\n if let Some(path_str) = entry.path().to_str() {\n video_files.push(path_str.to_string());\n }\n }\n }\n }\n }\n }\n }\n }\n \n video_files\n }\n \n fn execute_action(&self, action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n Self::execute_action_static(action_id, src, out_dir, config)\n }\n\n fn execute_action_static(action_id: &str, src: &Path, out_dir: &Path, config: &ActionConfig) -> anyhow::Result<()> {\n // Call corresponding action function based on action_id\n match action_id {\n \"md5\" => Md5Action.execute(src, out_dir, config),\n \"crop\" => CropAction.execute(src, out_dir, config),\n \"cut_head_tail\" => CutAction.execute(src, out_dir, config),\n \"rotate\" => RotateAction.execute(src, out_dir, config),\n \"speed\" => SpeedAction.execute(src, out_dir, config),\n \"mirror\" => MirrorAction.execute(src, out_dir, config),\n \"fps_60\" => FpsAction.execute(src, out_dir, config),\n \"bitrate_hq\" => BitrateAction.execute(src, out_dir, config),\n \"sharpen\" => SharpenAction.execute(src, out_dir, config),\n \"portrait\" => PortraitAction.execute(src, out_dir, config),\n \"denoise\" => DenoiseAction.execute(src, out_dir, config),\n \"clean\" => CleanAction.execute(src, out_dir, config),\n \"grain\" => GrainAction.execute(src, out_dir, config),\n \"blur\" => BlurAction.execute(src, out_dir, config),\n \"color\" => ColorAction.execute(src, out_dir, config),\n \"vignette\" => VignetteAction.execute(src, out_dir, config),\n \"bw\" => BwAction.execute(src, out_dir, config),\n \"border\" => BorderAction.execute(src, out_dir, config),\n \"pull\" => PullAction.execute(src, out_dir, config),\n \"corner\" => CornerAction.execute(src, out_dir, config),\n \"zoom\" => ZoomAction.execute(src, out_dir, config),\n \"dissolve\" => DissolveAction.execute(src, out_dir, config),\n \"scan\" => ScanAction.execute(src, out_dir, config),\n \"bounce\" => BounceAction.execute(src, out_dir, config),\n \"trifold\" => TrifoldAction.execute(src, out_dir, config),\n \"lava\" => LavaAction.execute(src, out_dir, config),\n \"flash\" => FlashAction.execute(src, out_dir, config),\n \"progressive\" => ProgressiveAction.execute(src, out_dir, config),\n \"ab_blend\" => AbBlendAction.execute(src, out_dir, config),\n \"ab_glitch\" => AbGlitchAction.execute(src, out_dir, config),\n \"ab_shake\" => AbShakeAction.execute(src, out_dir, config),\n \"ab_chroma\" => AbChromaAction.execute(src, out_dir, config),\n \"ab_replace\" => AbReplaceAction.execute(src, out_dir, config),\n \"ab_advanced_replace\" => AbAdvancedReplaceAction.execute(src, out_dir, config),\n \"mute\" => MuteAction.execute(src, out_dir, config),\n \"audio_noise\" => AudioNoiseAction.execute(src, out_dir, config),\n \"pitch\" => PitchAction.execute(src, out_dir, config),\n \"touch\" => TouchAction.execute(src, out_dir, config),\n \"strong_crop\" => StrongCropAction.execute(src, out_dir, config),\n \"watermark\" => WatermarkAction.execute(src, out_dir, config),\n \"encode\" => EncodeAction.execute(src, out_dir, config),\n \"ab_real_replace\" => AbRealReplaceAction.execute(src, out_dir, config),\n \"sticker\" => StickerAction.execute(src, out_dir, config),\n \"mask\" => MaskAction.execute(src, out_dir, config),\n \"face_detection\" => FaceDetectionAction.execute(src, out_dir, config),\n \"object_tracking\" => ObjectTrackingAction.execute(src, out_dir, config),\n \"opencv_filter\" => OpencvFilterAction.execute(src, out_dir, config),\n \"light_effect\" => LightEffectAction.execute(src, out_dir, config),\n \"pip\" => PipAction.execute(src, out_dir, config),\n \"edge_effect\" => EdgeEffectAction.execute(src, out_dir, config),\n \"goods_template\" => GoodsTemplateAction.execute(src, out_dir, config),\n _ => Err(anyhow::anyhow!(\"Unknown action: {}\", action_id)),\n }\n }\n \n fn stop_processing(&mut self) {\n if self.is_processing {\n self.is_processing = false;\n self.rx = None; // Detach receiver\n self.log(\"🛑 User stopped processing\");\n }\n }\n}\n\n// Main function\npub fn run_desktop_app() -> Result<(), eframe::Error> {\n let options = eframe::NativeOptions {\n viewport: egui::ViewportBuilder::default()\n .with_inner_size([1000.0, 800.0])\n .with_min_inner_size([800.0, 600.0])\n .with_title(\"Video Matrix Pro V5.4 (Rust Desktop)\"),\n ..Default::default()\n };\n \n eframe::run_native(\n \"Video Matrix Pro\",\n options,\n Box::new(|_cc| Ok(Box::<VideoMatrixApp>::default())),\n )\n}"}}}} |