Lập Trình App Nén Video MP4 Bằng Rust Và GPU Windows
Lập Trình Hệ Thống • Xử Lý Đa Phương Tiện & GPU Windows
Lập Trình App Nén Video MP4 Bằng Rust Và GPU Windows
•
Thời gian đọc: 22 phút
•
Tác giả: Chuyên Gia Kỹ Thuật Hệ Thống & Rust Developer
Mô hình ứng dụng nén video native bằng Rust: Giao diện Slint UI render bằng GPU tiêu thụ dưới 30MB RAM, tự động nhận diện phần cứng NVENC/AMF/QSV và không làm nghẽn CPU.
Tóm tắt thông tin cho công cụ tìm kiếm AI (AI Overview & Key Takeaways):
Hướng dẫn kỹ thuật toàn diện về việc kiến tạo một phần mềm máy tính (Desktop App) độc lập trên hệ điều hành Windows bằng ngôn ngữ Rust, chuyên trách phân tích và nén hàng loạt tệp video .mp4 dung lượng lớn mà không làm giảm tốc độ khung hình (FPS) hay kích thước độ phân giải hiển thị:
- Lợi thế công nghệ Rust & Slint: Thay thế hoàn toàn các framework nặng nề như Electron hay Python Tkinter. Slint biên dịch trực tiếp sang mã máy native, render giao diện thông qua DirectX/Vulkan, tiêu thụ dưới 30MB RAM và triệt tiêu độ trễ tương tác.
- Tăng tốc phần cứng GPU chuyên dụng (Hardware Encoding): Tự động nhận diện chip mã hóa độc lập gồm NVIDIA NVENC (hevc_nvenc), Intel QuickSync (hevc_qsv), và AMD AMF (hevc_amf). Việc mã hóa diễn ra trên ASIC chuyên biệt của card đồ họa, giữ mức tải CPU và GPU 3D core tiệm cận 0%, máy tính hoàn toàn không giật lag.
- Tiền phân tích thông minh (Pre-analysis): Trích xuất siêu dữ liệu qua
ffprobeđể kiểm tra codec hiện tại và bitrate. Hệ thống phân tích tỷ lệ nén khả thi trước khi người dùng nhấn nút Compress More (chuyển đổi H.264 sang HEVC/H.265 với container taghvc1, sao chép nguyên vẹn luồng âm thanh-c:a copy). - Cơ chế Async đa luồng & Hủy an toàn: Ứng dụng chạy trên nền tảng
tokiongầm, sử dụngCancellationTokenđể dừng khẩn cấp tiến trình, tiêu diệt sub-process FFmpeg và dọn dẹp các tệp tin nén dở dang. - Đóng gói Single Executable (.exe duy nhất): Sử dụng macro
include_bytes!nhúng thẳngffmpeg.exevàffprobe.exevào file thực thi, kết hợp cấu hình ẩn màn hình dòng lệnh console (windows_subsystem) và tối ưu LTO siêu nhẹ.
1. Đánh Giá Kiến Trúc: Tại Sao Rust & Slint Là Lựa Chọn Tối Thượng Cho Windows?
Xử lý video đa phương tiện là một tác vụ tiêu tốn tài nguyên phần cứng nghiêm trọng nhất trên máy tính. Nhiều nhà phát triển thường tìm đến Python (PyQt, Tkinter) hoặc JavaScript (Electron, Tauri) vì tốc độ viết code nhanh. Tuy nhiên, các giải pháp này bộc lộ những nhược điểm chí mạng: Electron tiêu tốn từ 200MB – 400MB RAM chỉ để dựng cửa sổ giao diện rỗng, trong khi Python vướng phải cơ chế GIL (Global Interpreter Lock) khiến ứng dụng dễ rơi vào trạng thái “Not Responding” khi đang đọc ghi ổ đĩa hoặc xử lý luồng phụ.
Ngược lại, Rust kết hợp với framework đồ họa Slint UI mang lại hiệu năng ở cấp độ phần cứng hệ thống (Bare-metal performance):
| Tiêu Chí Kỹ Thuật | Rust + Slint UI (Khuyên Dùng) | Go + Fyne Framework | Python + Tkinter / PyQt |
|---|---|---|---|
| Bộ nhớ RAM lúc chạy rảnh | Dưới 25MB – 30MB RAM | Khoảng 45MB – 70MB RAM | 120MB – 350MB RAM |
| Cơ chế Render giao diện | Render trực tiếp qua GPU (DirectX 11/12 hoặc Vulkan) cực mượt. | Dùng OpenGL qua cgo, phụ thuộc driver OpenGL của Windows. | Vẽ bằng CPU phần mềm (Tkinter) hoặc bộ wrapper Qt đồ sộ. |
| Phụ thuộc môi trường Runtime | Không Runtime, không máy ảo GC, biên dịch 100% mã máy. | Kèm Go Runtime và Garbage Collector (GC) trong file .exe. | Bắt buộc cài Python hoặc đóng gói PyInstaller cồng kềnh. |
| Kiểm soát an toàn luồng & I/O | Cơ chế Borrow Checker bảo đảm không bao giờ bị Data Race khi xử lý ngầm. | Dùng Goroutine nhẹ nhàng nhưng dễ rò rỉ bộ nhớ nếu không huỷ channel. | Khó kiểm soát hoàn toàn tín hiệu tắt tiến trình khi FFmpeg chạy lỗi. |
2. Giải Mã Kỹ Thuật: Tăng Tốc Phần Cứng & Tự Động Nhận Diện GPU
Yêu cầu quan trọng nhất của người dùng là: “Tối ưu ở mức độ thấp nhất, chạy mượt mà không lag và không ngốn CPU/GPU”. Nhiều người lầm tưởng rằng nén bằng GPU sẽ làm card màn hình nóng ran và không thể chơi game hay làm việc khác được.
Trong kiến trúc card đồ họa hiện đại, các nhà sản xuất tích hợp một vi mạch độc lập chuyên biệt (Dedicated Hardware ASIC) hoàn toàn tách rời với nhân đồ họa 3D Core (CUDA Core hay Compute Units):
- NVIDIA NVENC (NVIDIA Encoder): Bộ xử lý độc lập có mặt trên dòng GeForce GTX/RTX. Khi gọi encoder
hevc_nvenc, toàn bộ việc mã hóa diễn ra trên chip NVENC. Nhân CUDA Core và CPU Core giữ nguyên mức tiêu thụ 0–2%, quạt tản nhiệt không cần gầm rú. - Intel QuickSync Video (QSV): Bộ giải mã phần cứng tích hợp sẵn trong vi xử lý Intel Core i. Gọi encoder
hevc_qsvgiúp giải phóng hoàn toàn các luồng xử lý số học của CPU. - AMD Advanced Media Framework (AMF): Khối xử lý chuyên trách trên card đồ họa Radeon RX thông qua encoder
hevc_amf.
3. Thuật Toán Tiền Phân Tích & Nguyên Lý Nén “Giữ Nguyên Chất Lượng”
Làm thế nào để giảm kích thước tệp video từ 30% đến 60% nhưng vẫn giữ nguyên tốc độ khung hình (FPS) và kích thước khung hình (Resolution)? Câu trả lời nằm ở bước chuyển đổi chuẩn nén từ H.264 (AVC) sang H.265 (HEVC):
Quy Trình 3 Bước Tiền Phân Tích Trước Khi Nén:
- Lọc tệp tin theo dung lượng ngưỡng: Người dùng nhập dung lượng mong muốn (ví dụ 1000MB). Ứng dụng quét đệ quy các tệp
.mp4và chỉ đưa vào hàng đợi những tệp vượt ngưỡng cấu hình. - Phân tích luồng (Stream Inspection): Gọi
ffprobetrích xuất codec video (H.264 hay H.265), bitrate (kbps) và thời lượng tổng (duration). Nếu tệp tin đang ở chuẩn H.264 có bitrate cao (>8.000 kbps), hệ thống dự báo: “Có thể nén giảm 35% – 50% dung lượng” và bật sáng nút Compress More. Nếu tệp đã là H.265 tối ưu, hệ thống sẽ đưa ra cảnh báo để tránh nén trùng lặp. - Mã hóa bảo toàn luồng âm thanh: Khi thực thi nén, thêm tham số
-c:a copyđể giữ nguyên vẹn chất lượng âm thanh gốc (AAC/MP3) mà không cần decode rồi encode lại âm thanh, giúp tăng tốc độ xử lý thêm 15%. Đồng thời thêm cờ-vtag hvc1để đảm bảo tệp MP4 tương thích 100% với trình phát mặc định của Windows (Media Player / Films & TV) cũng như các thiết bị của Apple.
4. Chuẩn Bị Môi Trường Biên Dịch Rust & Bộ Công Cụ Windows
Để biên dịch mã nguồn Rust thành tệp thực thi .exe native trên Windows, bạn cần chuẩn bị 3 công cụ nền tảng sau:
Rust trên Windows sử dụng linker MSVC của Microsoft. Tải công cụ Visual Studio Build Tools, chọn gói Desktop development with C++ với các thành phần MSVC v143 và Windows 10/11 SDK.
Truy cập trang chủ rustup.rs tải rustup-init.exe. Khởi chạy và chọn tùy chọn 1 (Default) để tự động cài đặt rustc, trình quản lý gói cargo và chuỗi công cụ chuẩn.
Cài đặt Visual Studio Code cùng tiện ích mở rộng rust-analyzer và Slint để nhận diện cú pháp, tự động hoàn thành mã nguồn và bắt lỗi trực tiếp trong quá trình lập trình.
5. Toàn Bộ Mã Nguồn Dự Án (Bản Hoàn Thiện Single-Executable)
Dưới đây là cấu trúc dự án và toàn bộ mã nguồn của ứng dụng hoàn thiện, tích hợp đầy đủ tính năng nén GPU, thanh Progress Bar chạy mượt mà theo từng %, nút Hủy an toàn (Cancel) và hộp kiểm tự động xóa tệp video gốc sau khi nén thành công:
5.1. Cấu Trúc Cây Thư Mục Dự Án (Directory Structure)
video_compressor/
├── bin/
│ ├── ffmpeg.exe <-- File thực thi FFmpeg cho Windows (Tải bản Essentials)
│ └── ffprobe.exe <-- File thực thi FFprobe để đọc thông số video
├── ui/
│ └── appwindow.slint <-- Tệp thiết kế giao diện đồ họa Slint UI
├── src/
│ └── main.rs <-- Mã nguồn logic xử lý đa luồng và GPU bằng Rust
├── build.rs <-- Script cấu hình biên dịch giao diện Slint
└── Cargo.toml <-- Danh sách thư viện và tối ưu hóa Release
5.2. File Cấu Hình Dự Án & Thư Viện (Cargo.toml)
[package]
name = "video_compressor"
version = "0.1.0"
edition = "2021"
[dependencies]
# Framework UI native render bang GPU DirectX/Vulkan
slint = "1.3"
# Runtime xu ly bat dong bo da luong ngam de khong bi treo UI
tokio = { version = "1.0", features = ["full"] }
# Thu vien ho tro CancellationToken de huy tien trinh an toan
tokio-util = { version = "0.7", features = ["sync"] }
# Quet thu muc de quy sieu toc
walkdir = "2.4"
[build-dependencies]
# Compile file giao dien .slint thanh ma may native khi build
slint-build = "1.3"
# Cau hinh toi uu hoa kich thuoc file .exe va toc do chay cuc dai
[profile.release]
opt-level = "z" # Toi uu hoa kich thuoc file nhi phan nho nhat
lto = true # Link-Time Optimization giup don dep ma nguon thua
codegen-units = 1 # Tang hieu qua nen ma may cua trinh bien dich
panic = "abort" # Loai bo bang unwinding de giam dung luong file
5.3. File Biên Dịch Giao Diện (build.rs)
fn main() {
// Thong bao cho rustc biet vi tri file giao dien can compile truoc
slint_build::compile("ui/appwindow.slint").expect("Khong the bien dich file giao dien Slint!");
}
5.4. Giao Diện Đồ Họa Trực Quan (ui/appwindow.slint)
import { Button, LineEdit, ScrollView, CheckBox } from "std-widgets.slint";
export component AppWindow inherits Window {
title: "Bộ Nén Video GPU Siêu Tốc - Single Exe";
width: 750px;
height: 590px;
background: #1e1e24;
in-out property <string> folder_path: "Chưa chọn thư mục...";
in-out property <string> size_threshold: "1000";
in-out property <string> status_text: "Sẵn sàng.";
in-out property <string> log_text: "";
in-out property <bool> compress_enabled: false;
in-out property <bool> cancel_enabled: false;
in-out property <bool> delete_original: false;
in-out property <float> progress_value: 0.0;
callback select_folder_clicked();
callback compress_more_clicked();
callback cancel_clicked();
VerticalLayout {
padding: 20px;
spacing: 15px;
// Vung chon thu muc
HorizontalLayout {
spacing: 10px;
Text { text: "Thư mục:"; color: #ffffff; vertical-alignment: center; }
LineEdit { text: root.folder_path; enabled: false; width: 400px; }
Button { text: "Chọn Thư Mục"; clicked => { root.select_folder_clicked(); } }
}
// Vung nhap dung luong nguong (MB)
HorizontalLayout {
spacing: 10px;
Text { text: "Dung lượng tối thiểu (MB):"; color: #ffffff; vertical-alignment: center; }
LineEdit {
text: root.size_threshold;
edited(text) => { root.size_threshold = text; }
width: 100px;
}
}
// Tuy chon xoa file goc sau khi nen thanh cong
HorizontalLayout {
spacing: 10px;
alignment: start;
CheckBox {
text: "Tự động xóa file video gốc sau khi nén thành công";
checked: root.delete_original;
toggled => { root.delete_original = !root.delete_original; }
}
}
// Cac nut dieu khien: Bat dau va Huy
HorizontalLayout {
spacing: 20px;
alignment: center;
Button {
text: "Compress More (Bắt đầu)";
enabled: root.compress_enabled;
width: 210px;
clicked => { root.compress_more_clicked(); }
}
Button {
text: "Hủy Tiến Trình (Cancel)";
enabled: root.cancel_enabled;
width: 210px;
clicked => { root.cancel_clicked(); }
}
}
// Thanh Progress Bar do hoa sieu muot
VerticalLayout {
spacing: 5px;
Text {
text: "Tiến trình hiện tại: " + Math.round(root.progress_value * 100) + "%";
color: #00ffcc;
}
Rectangle {
background: #3a3a45;
height: 14px;
border-radius: 7px;
clip: true;
Rectangle {
background: #00ffcc;
x: 0px;
width: root.progress_value * parent.width;
height: parent.height;
animate width { duration: 150ms; easing: ease-out; }
}
}
}
// Status Bar hien thi trang thai he thong
Rectangle {
background: #2a2a35;
height: 35px;
border-radius: 4px;
Text {
text: root.status_text;
color: #ffffff;
x: 10px;
vertical-alignment: center;
}
}
// Hop thoai nhat ky hoat dong (Log Text Area)
Text { text: "Nhật ký hoạt động (Log & Error):"; color: #aaaaaa; }
Rectangle {
background: #0f0f12;
border-width: 1px;
border-color: #3a3a45;
border-radius: 4px;
ScrollView {
Text {
text: root.log_text;
color: #dcdcdc;
font-family: "Consolas";
wrap: word-wrap;
}
}
}
}
}
5.5. Mã Nguồn Xử Lý Đa Luồng & Trích Xuất GPU (src/main.rs)
// An cua so Console mau den tren Windows khi mo file .exe
#![windows_subsystem = "windows"]
slint::include_modules!();
use std::process::{Command, Stdio};
use std::path::{Path, PathBuf};
use std::io::{BufRead, BufReader};
use std::fs;
use std::sync::{Arc, Mutex};
use walkdir::WalkDir;
use tokio_util::sync::CancellationToken;
// 1. Nhúng trực tiếp 2 file exe vào bên trong mã nguồn khi biên dịch Release
const FFMPEG_EXE_BYTES: &[u8] = include_bytes!("../bin/ffmpeg.exe");
const FFPROBE_EXE_BYTES: &[u8] = include_bytes!("../bin/ffprobe.exe");
// Giải nén ngầm các file nhúng ra thư mục Temp nếu chưa tồn tại
fn prepare_embedded_binaries() -> (PathBuf, PathBuf) {
let temp_dir = std::env::temp_dir().join("vid_compressor_bin");
let _ = fs::create_dir_all(&temp_dir);
let ffmpeg_path = temp_dir.join("ffmpeg.exe");
let ffprobe_path = temp_dir.join("ffprobe.exe");
if !ffmpeg_path.exists() { let _ = fs::write(&ffmpeg_path, FFMPEG_EXE_BYTES); }
if !ffprobe_path.exists() { let _ = fs::write(&ffprobe_path, FFPROBE_EXE_BYTES); }
(ffmpeg_path, ffprobe_path)
}
// Lấy tổng thời lượng video (tính theo giây) qua ffprobe
fn get_video_duration(ffprobe_path: &Path, file_path: &Path) -> f32 {
let output = Command::new(ffprobe_path)
.args([
"-v", "error",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nocey=1",
file_path.to_str().unwrap()
])
.output();
if let Ok(out) = output {
let result = String::from_utf8_lossy(&out.stdout).trim().to_string();
if let Some(val) = result.split('=').last() {
return val.trim().parse::<f32>().unwrap_or(0.0);
}
}
0.0
}
// Tự động nhận diện GPU có sẵn trên Windows để chọn Encoder tối ưu nhất
fn detect_gpu_encoder(ffmpeg_path: &Path) -> &'static str {
let check = Command::new(ffmpeg_path).args(["-encoders"]).output();
if let Ok(output) = check {
let text = String::from_utf8_lossy(&output.stdout);
if text.contains("hevc_nvenc") { return "hevc_nvenc"; } // NVIDIA HEVC H.265
if text.contains("hevc_qsv") { return "hevc_qsv"; } // Intel QuickSync
if text.contains("hevc_amf") { return "hevc_amf"; } // AMD AMF
}
"libx265" // Fallback sang CPU nén chất lượng cao nếu không có GPU rời
}
// Hộp thoại chọn thư mục Native của Windows qua PowerShell
fn select_folder_windows() -> Option<String> {
let script = "Add-Type -AssemblyName System.Windows.Forms; $dialog = New-Object System.Windows.Forms.FolderBrowserDialog; if ($dialog.ShowDialog() -eq 'OK') { $dialog.SelectedPath }";
let output = Command::new("powershell").args(["-NoProfile", "-Command", script]).output().ok()?;
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
if path.is_empty() { None } else { Some(path) }
}
#[tokio::main]
async fn main() -> Result<(), slint::PlatformError> {
let (ffmpeg_path, ffprobe_path) = prepare_embedded_binaries();
let ffmpeg_path_arc = Arc::new(ffmpeg_path);
let ffprobe_path_arc = Arc::new(ffprobe_path);
let ui = AppWindow::new()?;
let current_cancel_token: Arc<Mutex<Option<CancellationToken>>> = Arc::new(Mutex::new(None));
// SỰ KIỆN 1: Quét thư mục và phân tích danh sách file
let ui_handle = ui.as_weak();
let ffprobe_clone = ffprobe_path_arc.clone();
ui.on_select_folder_clicked(move || {
let ui = ui_handle.unwrap();
if let Some(path) = select_folder_windows() {
ui.set_folder_path(path.clone().into());
let mut log = ui.get_log_text().to_string();
let threshold_mb: u64 = ui.get_size_threshold().to_string().parse().unwrap_or(1000);
log.push_str(&format!("[INFO] Đã quét thư mục: {}n", path));
let mut match_count = 0;
for entry in WalkDir::new(&path).into_iter().filter_map(|e| e.ok()) {
if entry.path().extension().map_or(false, |ext| ext == "mp4") && !entry.file_name().to_string_lossy().contains("_compressed") {
if let Ok(metadata) = entry.metadata() {
let size_mb = metadata.len() / (1024 * 1024);
if size_mb >= threshold_mb {
match_count += 1;
let duration = get_video_duration(&ffprobe_clone, entry.path());
log.push_str(&format!(" -> Thỏa mãn: {} ({} MB - {}s)n", entry.file_name().to_string_lossy(), size_mb, duration as u32));
}
}
}
}
if match_count > 0 {
ui.set_status_text(format!("Tìm thấy {} file đủ tiêu chuẩn nén.", match_count).into());
ui.set_compress_enabled(true);
} else {
ui.set_status_text("Không tìm thấy file nào vượt ngưỡng.".into());
ui.set_compress_enabled(false);
}
ui.set_log_text(log.into());
}
});
// SỰ KIỆN 2: Bấm nút Compress More để tiến hành nén ngầm bằng GPU
let ui_handle_compress = ui.as_weak();
let ffmpeg_clone = ffmpeg_path_arc.clone();
let ffprobe_compress_clone = ffprobe_path_arc.clone();
let token_compress_clone = current_cancel_token.clone();
ui.on_compress_more_clicked(move || {
let ui = ui_handle_compress.unwrap();
let folder = ui.get_folder_path().to_string();
let threshold_mb: u64 = ui.get_size_threshold().to_string().parse().unwrap_or(1000);
let should_delete_orig = ui.get_delete_original();
let encoder = detect_gpu_encoder(&ffmpeg_clone);
let mut files_to_compress = Vec::new();
for entry in WalkDir::new(&folder).into_iter().filter_map(|e| e.ok()) {
if entry.path().extension().map_or(false, |ext| ext == "mp4") && !entry.file_name().to_string_lossy().contains("_compressed") {
if let Ok(metadata) = entry.metadata() {
if metadata.len() / (1024 * 1024) >= threshold_mb {
files_to_compress.push(entry.path().to_path_buf());
}
}
}
}
let token = CancellationToken::new();
*token_compress_clone.lock().unwrap() = Some(token.clone());
ui.set_compress_enabled(false);
ui.set_cancel_enabled(true);
let ui_clone = ui_handle_compress.clone();
let ffmpeg_path_exec = ffmpeg_clone.clone();
let ffprobe_path_exec = ffprobe_compress_clone.clone();
tokio::spawn(async move {
let total_files = files_to_compress.len();
let mut is_cancelled_global = false;
for (index, file_path) in files_to_compress.iter().enumerate() {
if token.is_cancelled() {
is_cancelled_global = true;
break;
}
let file_name = file_path.file_name().unwrap().to_string_lossy().to_string();
let duration = get_video_duration(&ffprobe_path_exec, file_path);
let _ = slint::invoke_from_event_loop({
let ui_clone = ui_clone.clone();
let file_name = file_name.clone();
move || {
let ui = ui_clone.unwrap();
ui.set_status_text(format!("Đang nén ({}/{}): {}", index + 1, total_files, file_name).into());
ui.set_progress_value(0.0);
}
});
let output_str = format!("{}_compressed.mp4", file_path.file_stem().unwrap().to_string_lossy());
let final_output = file_path.with_file_name(output_str);
// Khởi chạy FFmpeg và lắng nghe tiến trình thời gian thực
let mut child = Command::new(&*ffmpeg_path_exec)
.args([
"-y", "-i", file_path.to_str().unwrap(),
"-c:v", encoder, "-vtag", "hvc1", "-c:a", "copy", "-preset", "fast",
"-progress", "pipe:1",
final_output.to_str().unwrap()
])
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("Khong the khoi chay FFmpeg");
if let Some(stdout) = child.stdout.take() {
let mut reader = BufReader::new(stdout);
loop {
if token.is_cancelled() {
let _ = child.kill();
is_cancelled_global = true;
break;
}
let mut line = String::new();
if let Ok(bytes) = reader.read_line(&mut line) {
if bytes == 0 { break; }
if line.starts_with("out_time_us=") {
let us_str = line.trim().trim_start_matches("out_time_us=");
if let Ok(us) = us_str.parse::<f64>() {
let current_seconds = us / 1_000_000.0;
if duration > 0.0 {
let percentage = (current_seconds / duration as f64).min(1.0) as f32;
let _ = slint::invoke_from_event_loop({
let ui_clone = ui_clone.clone();
move || { ui_clone.unwrap().set_progress_value(percentage); }
});
}
}
}
}
}
}
let status = child.wait();
if is_cancelled_global {
if final_output.exists() { let _ = fs::remove_file(&final_output); }
break;
}
let _ = slint::invoke_from_event_loop({
let ui_clone = ui_clone.clone();
let file_name = file_name.clone();
let file_path_clone = file_path.clone();
move || {
let ui = ui_clone.unwrap();
let mut l = ui.get_log_text().to_string();
if status.map_or(false, |s| s.success()) {
l.push_str(&format!("[OK] Đã nén xong: {}n", file_name));
ui.set_progress_value(1.0);
if should_delete_orig {
if let Err(e) = fs::remove_file(&file_path_clone) {
l.push_str(&format!("[CANH BAO] Khong the xoa file goc: {}n", e));
} else {
l.push_str("[DON DEP] Da xoa file goc de tiet kiem o cung.n");
}
}
} else {
l.push_str(&format!("[ERROR] Loi xu ly file: {}n", file_name));
}
ui.set_log_text(l.into());
}
});
}
let _ = slint::invoke_from_event_loop(move || {
let ui = ui_clone.unwrap();
if is_cancelled_global {
ui.set_status_text("Tiến trình đã bị người dùng hủy bỏ!".into());
let mut l = ui.get_log_text().to_string();
l.push_str("[CANCELLED] Đã dừng toàn bộ tác vụ nén.n");
ui.set_log_text(l.into());
} else {
ui.set_status_text("Đã xử lý xong toàn bộ danh sách!".into());
}
ui.set_compress_enabled(false);
ui.set_cancel_enabled(false);
});
});
});
// SỰ KIỆN 3: Nút Cancel dừng khẩn cấp
let token_cancel_action = current_cancel_token.clone();
ui.on_cancel_clicked(move || {
if let Some(token) = token_cancel_action.lock().unwrap().take() {
token.cancel();
}
});
ui.run()
}
6. Quy Trình Biên Dịch Thành File .EXE Thương Mại Siêu Nhẹ
Sau khi tổ chức các tệp tin theo đúng cấu trúc thư mục, bạn mở Terminal tại thư mục gốc của dự án và chạy duy nhất lệnh sau:
# Bien dich ban thuong mai toi uu hoa toi da
cargo build --release
Tệp tin thành phẩm duy nhất được tạo ra tại targetreleasevideo_compressor.exe. Tệp này sở hữu toàn bộ các ưu điểm vượt trội:
- Độc lập 100%: Đã nhúng sẵn bộ giải mã FFmpeg và FFprobe bên trong, người dùng không cần cài đặt bất kỳ phần mềm bên ngoài nào.
- Giao diện chuyên nghiệp: Cửa sổ đen cmd tự động bị ẩn nhờ thuộc tính
#![windows_subsystem = "windows"]. - An toàn tuyệt đối: Tiến trình hủy bỏ dọn dẹp sạch sẽ ổ cứng, không để lại rác tệp tin hỏng trên máy người dùng.
Trang Bị Phần Cứng & Ổ Cứng Tốc Độ Cao Cho Xử Lý Video Trên Shopee
Nâng cấp ổ cứng SSD NVMe tốc độ cao và card đồ họa chuyên dụng để tối ưu hóa thời gian đọc ghi tệp video 4K/8K:
Nguồn Dữ Liệu Tham Khảo (Plain Text Reference Sources):
– Slint Official Documentation: Native GUI toolkit for desktop applications (https://slint.dev/docs)
– Tokio Asynchronous Runtime for Rust: Multi-threaded background tasks and cancellation tokens (https://tokio.rs)
– FFmpeg Hardware Acceleration Guide: NVIDIA NVENC, Intel QSV, AMD AMF encoders (https://trac.ffmpeg.org/wiki/HWAccelIntro)
– Rust Windows Subsystem Reference: Eliminating console window allocation (https://doc.rust-lang.org/reference/attributes/codegen.html)
– Cargo Profile Optimization Guide: Minimizing binary sizes with LTO and opt-level z (https://doc.rust-lang.org/cargo/reference/profiles.html)