heic2jpg

git clone git://archive.git.mtrnord.blog/MTRNord/heic2jpg.git
Log | Files | Refs | README | LICENSE

commit fa12b068886107bb42ea3ca2674cd8539d284fa6
parent 0047eb9494f60479a0b16bd0c80fe2ad56c43253
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sun, 10 Nov 2024 01:20:07 +0100

Prepare worker

Diffstat:
Msrc/app.rs | 30+++++++++++++++++++++++++++---
Asrc/conversion_worker.rs | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 1+
Msrc/select_folder.rs | 2+-
4 files changed, 143 insertions(+), 4 deletions(-)

diff --git a/src/app.rs b/src/app.rs @@ -1,4 +1,5 @@ use crate::config::{APP_ID, PROFILE}; +use crate::conversion_worker::{ConversionWorker, ConversionWorkerInputMsg, ConversionWorkerMsg}; use crate::modals::about::AboutDialog; use crate::select_folder::{InOut, SelectFolder, SelectFolderOut}; use gtk::prelude::*; @@ -6,7 +7,7 @@ use gtk::{gio, glib}; use relm4::{ actions::{RelmAction, RelmActionGroup}, adw, gtk, main_application, Component, ComponentController, ComponentParts, ComponentSender, - Controller, SimpleComponent, + Controller, SimpleComponent, WorkerController, }; use std::path::PathBuf; @@ -16,6 +17,7 @@ pub(super) struct App { output_folder_selector: Controller<SelectFolder>, input_folder: Option<PathBuf>, output_folder: Option<PathBuf>, + conversion_worker: WorkerController<ConversionWorker>, } #[derive(Debug)] @@ -24,6 +26,7 @@ pub(super) enum AppMsg { OutputFolderSelected(PathBuf), DeselectInputFolder, DeselectOutputFolder, + Convert, Quit, Noop, } @@ -105,8 +108,7 @@ impl SimpleComponent for App { gtk::Button { set_label: "Convert", connect_clicked[sender] => move |_| { - // TODO: Implement conversion using worker and imagick - sender.input(AppMsg::Quit); + sender.input(AppMsg::Convert); } } } @@ -153,11 +155,21 @@ impl SimpleComponent for App { SelectFolderOut::FolderSelected(path) => AppMsg::OutputFolderSelected(path), SelectFolderOut::AbortLast => AppMsg::DeselectInputFolder, }); + let conversion_worker = + ConversionWorker::builder() + .detach_worker(()) + .forward(sender.input_sender(), |msg| match msg { + ConversionWorkerMsg::ConversionStarted => AppMsg::Noop, + ConversionWorkerMsg::ProgressUpdate(_) => AppMsg::Noop, + ConversionWorkerMsg::ConversionComplete => AppMsg::Noop, + ConversionWorkerMsg::ConversionFailed(_) => AppMsg::Noop, + }); let model = Self { about_dialog, input_folder_selector, output_folder_selector, + conversion_worker, input_folder: None, output_folder: None, }; @@ -196,6 +208,18 @@ impl SimpleComponent for App { AppMsg::OutputFolderSelected(path) => self.output_folder = Some(path), AppMsg::DeselectOutputFolder => self.output_folder = None, AppMsg::Quit => main_application().quit(), + AppMsg::Convert => { + if let (Some(input_folder), Some(output_folder)) = + (&self.input_folder, &self.output_folder) + { + let _ = self.conversion_worker.sender().send( + ConversionWorkerInputMsg::ConvertFolder( + input_folder.clone(), + output_folder.clone(), + ), + ); + } + } AppMsg::Noop => {} } } diff --git a/src/conversion_worker.rs b/src/conversion_worker.rs @@ -0,0 +1,114 @@ +use magick_rust::{MagickError, MagickWand}; +use std::path::PathBuf; +use std::result::Result; +use walkdir::WalkDir; + +use relm4::{ComponentSender, Worker}; + +#[derive(Debug)] +pub enum ConversionWorkerInputMsg { + ConvertFolder(PathBuf, PathBuf), +} + +#[derive(Debug)] +pub enum ConversionWorkerMsg { + ConversionStarted, + ProgressUpdate(f64), + ConversionComplete, + ConversionFailed(String), +} + +pub struct ConversionWorker; + +impl Worker for ConversionWorker { + type Init = (); + type Input = ConversionWorkerInputMsg; + type Output = ConversionWorkerMsg; + + fn init(_init: Self::Init, _sender: ComponentSender<Self>) -> Self { + Self + } + + fn update(&mut self, msg: ConversionWorkerInputMsg, sender: ComponentSender<Self>) { + match msg { + ConversionWorkerInputMsg::ConvertFolder(input_path, output_path) => { + // Start the conversion + sender + .output(ConversionWorkerMsg::ConversionStarted) + .unwrap(); + + // Walk directory, find all heic files, convert them to jpg and update progress + let result = self.convert_folder(input_path, output_path, |progress| { + sender + .output(ConversionWorkerMsg::ProgressUpdate(progress)) + .unwrap(); + }); + + // Send the result of the conversion back + match result { + Ok(_) => sender + .output(ConversionWorkerMsg::ConversionComplete) + .unwrap(), + Err(e) => sender + .output(ConversionWorkerMsg::ConversionFailed(e.to_string())) + .unwrap(), + } + } + } + } +} + +impl ConversionWorker { + fn convert_folder<F: Fn(f64)>( + &self, + input_path: PathBuf, + output_path: PathBuf, + progress_callback: F, + ) -> Result<(), MagickError> { + // Use walkdir to find all heic files in the input directory + let heic_files: Vec<PathBuf> = WalkDir::new(input_path) + .into_iter() + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.extension().map_or(false, |ext| ext == "heic") { + Some(path.to_path_buf()) + } else { + None + } + }) + .collect(); + + // Convert each heic file to jpg + for (index, heic_file) in heic_files.iter().enumerate() { + let output_file = output_path + .join(heic_file.file_stem().unwrap()) + .with_extension("jpg"); + + // Convert the file + self.convert_heic_to_jpg(heic_file.to_path_buf(), output_file)?; + + // Update the progress + progress_callback((index + 1) as f64 / heic_files.len() as f64); + } + Ok(()) + } + + fn convert_heic_to_jpg( + &self, + input_file: PathBuf, + output_file: PathBuf, + ) -> Result<(), MagickError> { + // Create a MagickWand + let mut wand = MagickWand::new(); + + // Read the input file + wand.read_image(input_file.to_str().unwrap())?; + + // Convert the image to jpg + wand.set_image_format("jpg")?; + wand.write_image(output_file.to_str().unwrap())?; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs @@ -1,6 +1,7 @@ #[rustfmt::skip] mod config; mod app; +mod conversion_worker; mod modals; mod select_folder; diff --git a/src/select_folder.rs b/src/select_folder.rs @@ -3,7 +3,7 @@ use relm4::{ adw, gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller, SimpleComponent, }; -use relm4_components::{open_dialog::*, save_dialog::*}; +use relm4_components::open_dialog::*; use std::path::PathBuf; #[derive(Debug, PartialEq)]