Finish encode
This commit is contained in:
4 files changed
+122
-31
No files matched your search
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub async fn decode(path: PathBuf) {
|
pub fn decode(path: PathBuf) {
|
||||||
println!("{}", path.display());
|
println!("{}", path.display());
|
||||||
}
|
}
|
||||||
+57
-10
@@ -1,24 +1,27 @@
|
|||||||
use image::{ImageBuffer, Rgb};
|
use image::{ImageBuffer, Rgb};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
pub fn encode(path: PathBuf) {
|
pub fn encode(path: PathBuf, output_path: PathBuf) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new()?;
|
||||||
let temp_path = temp_dir.path();
|
let temp_path = temp_dir.path();
|
||||||
let size = 300;
|
let width = 3840;
|
||||||
|
let height = 2160;
|
||||||
|
println!("Temp directory: {}", temp_path.display());
|
||||||
|
|
||||||
let file_data = std::fs::read(&path).unwrap();
|
let file_data = std::fs::read(&path)?;
|
||||||
|
|
||||||
let mut frame_num = 0;
|
let mut frame_num = 0;
|
||||||
let mut byte_index = 0;
|
let mut byte_index = 0;
|
||||||
let mut bit_index = 0;
|
let mut bit_index = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut img = ImageBuffer::<Rgb<u8>, Vec<u8>>::new(size, size);
|
let mut img = ImageBuffer::<Rgb<u8>, Vec<u8>>::new(width, height);
|
||||||
|
|
||||||
for pixel_idx in 0..(size * size) {
|
for pixel_idx in 0..(width * height) {
|
||||||
let x = (pixel_idx % size) as u32;
|
let x = (pixel_idx % width) as u32;
|
||||||
let y = (pixel_idx / size) as u32;
|
let y = (pixel_idx / width) as u32;
|
||||||
|
|
||||||
if byte_index < file_data.len() {
|
if byte_index < file_data.len() {
|
||||||
let byte = file_data[byte_index];
|
let byte = file_data[byte_index];
|
||||||
@@ -41,8 +44,8 @@ pub fn encode(path: PathBuf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame_path = temp_path.join(format!("frame_{}.png", frame_num));
|
let frame_path = temp_path.join(format!("frame_{:06}.png", frame_num));
|
||||||
img.save(&frame_path).unwrap();
|
img.save(&frame_path)?;
|
||||||
|
|
||||||
frame_num += 1;
|
frame_num += 1;
|
||||||
|
|
||||||
@@ -50,4 +53,48 @@ pub fn encode(path: PathBuf) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("Generated {} frames", frame_num);
|
||||||
|
|
||||||
|
combine_frames_to_video(temp_path, &output_path)?;
|
||||||
|
|
||||||
|
Ok(output_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine_frames_to_video(
|
||||||
|
frames_dir: &std::path::Path,
|
||||||
|
output_path: &PathBuf,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let input_pattern = frames_dir.join("frame_%06d.png");
|
||||||
|
let input_pattern_str = input_pattern.to_str().ok_or("Invalid path")?;
|
||||||
|
|
||||||
|
let mut child = Command::new("ffmpeg")
|
||||||
|
.arg("-framerate")
|
||||||
|
.arg("60")
|
||||||
|
.arg("-i")
|
||||||
|
.arg(input_pattern_str)
|
||||||
|
.arg("-c:v")
|
||||||
|
.arg("ffv1")
|
||||||
|
.arg("-level")
|
||||||
|
.arg("3")
|
||||||
|
.arg("-coder")
|
||||||
|
.arg("1")
|
||||||
|
.arg("-context")
|
||||||
|
.arg("1")
|
||||||
|
.arg("-slices")
|
||||||
|
.arg("4")
|
||||||
|
.arg("-slicecrc")
|
||||||
|
.arg("1")
|
||||||
|
.arg("-y")
|
||||||
|
.arg(output_path)
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let status = child.wait()?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err(format!("ffmpeg failed with status: {}", status).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Video saved to: {}", output_path.display());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
+20
-14
@@ -7,7 +7,7 @@ mod encode;
|
|||||||
mod util;
|
mod util;
|
||||||
use decode::decode;
|
use decode::decode;
|
||||||
use encode::encode;
|
use encode::encode;
|
||||||
use util::get_user_selected_file;
|
use util::{get_user_selected_file, save_mkv_output};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let app = Application::builder()
|
let app = Application::builder()
|
||||||
@@ -40,21 +40,27 @@ fn main() {
|
|||||||
|
|
||||||
encodebtn.connect_clicked({
|
encodebtn.connect_clicked({
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
|
let encodebtn = encodebtn.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
get_user_selected_file(&window, |file_path| {
|
encodebtn.set_sensitive(false);
|
||||||
if let Some(path) = file_path {
|
let encodebtn = encodebtn.clone();
|
||||||
encode(path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
decodebtn.connect_clicked({
|
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
move |_| {
|
let value = window.clone();
|
||||||
get_user_selected_file(&window, |file_path| {
|
get_user_selected_file(&window, move |file_path| {
|
||||||
if let Some(path) = file_path {
|
if let Some(input_path) = file_path {
|
||||||
spawn_future_local(decode(path));
|
let window = value.clone();
|
||||||
|
let encodebtn = encodebtn.clone();
|
||||||
|
save_mkv_output(&window, move |output_path| {
|
||||||
|
if let Some(output) = output_path {
|
||||||
|
match encode(input_path.clone(), output) {
|
||||||
|
Ok(_path) => println!("Encoding completed!"),
|
||||||
|
Err(e) => eprintln!("Encoding error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encodebtn.set_sensitive(true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
encodebtn.set_sensitive(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-6
@@ -4,9 +4,9 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
pub fn get_user_selected_file<F>(parent: &ApplicationWindow, callback: F)
|
pub fn get_user_selected_file<F>(parent: &ApplicationWindow, callback: F)
|
||||||
where
|
where
|
||||||
F: FnOnce(Option<PathBuf>) + 'static,
|
F: Fn(Option<PathBuf>) + 'static,
|
||||||
{
|
{
|
||||||
let callback = std::cell::RefCell::new(Some(callback));
|
let callback = std::rc::Rc::new(callback);
|
||||||
|
|
||||||
let dialog = FileChooserDialog::new(
|
let dialog = FileChooserDialog::new(
|
||||||
Some("Choose a File"),
|
Some("Choose a File"),
|
||||||
@@ -28,10 +28,48 @@ where
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(cb) = callback.borrow_mut().take() {
|
callback(result);
|
||||||
cb(result);
|
dialog.close();
|
||||||
}
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_mkv_output<F>(parent: &ApplicationWindow, callback: F)
|
||||||
|
where
|
||||||
|
F: Fn(Option<PathBuf>) + 'static,
|
||||||
|
{
|
||||||
|
let callback = std::rc::Rc::new(callback);
|
||||||
|
|
||||||
|
let dialog = FileChooserDialog::new(
|
||||||
|
Some("Save MKV File"),
|
||||||
|
Some(parent),
|
||||||
|
gtk4::FileChooserAction::Save,
|
||||||
|
&[
|
||||||
|
("Cancel", ResponseType::Cancel),
|
||||||
|
("Save", ResponseType::Accept),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let filter = gtk4::FileFilter::new();
|
||||||
|
filter.set_name(Some("Matroska Video"));
|
||||||
|
filter.add_pattern("*.mkv");
|
||||||
|
dialog.add_filter(&filter);
|
||||||
|
dialog.set_current_name("output.mkv");
|
||||||
|
|
||||||
|
dialog.connect_response(move |dialog, response| {
|
||||||
|
let result = if response == ResponseType::Accept {
|
||||||
|
dialog.file().and_then(|file| file.path()).map(|mut path| {
|
||||||
|
if path.extension().is_none() || path.extension().unwrap() != "mkv" {
|
||||||
|
path.set_extension("mkv");
|
||||||
|
}
|
||||||
|
path
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
callback(result);
|
||||||
dialog.close();
|
dialog.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user