Skip to content

feat: added gif export feature #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions apps/desktop/src-tauri/src/editor_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,63 @@ impl EditorInstances {
instances.0.write().unwrap().remove(window.label());
}
}

/// New enum to represent supported export file types.
#[derive(Clone)]
pub enum ExportFileType {
MP4,
GIF,
}

/// New struct to hold export settings.
#[derive(Clone)]
pub struct ExportSettings {
pub file_type: ExportFileType,
pub fps: u32,
pub high_quality: bool,
}

impl Default for ExportSettings {
fn default() -> Self {
Self {
file_type: ExportFileType::MP4,
fps: 30, // default FPS for non-GIF exports
high_quality: false, // default quality flag
}
}
}

/// Dummy function to render the export dialog UI.
/// Replace the println! calls with your actual UI code.
pub fn render_export_dialog() {
// File type dropdown (MP4 or GIF)
println!("Select file type: [1] MP4 [2] GIF");

// If the user selects GIF, display GIF-specific options.
// (In your actual UI code, this would be conditionally rendered.)
println!("GIF options:");
println!(" - FPS (Enter desired FPS; default is 15 if left empty)");
println!(" - High quality toggle (true/false)");

// ... code to capture UI inputs and update state variables ...
}

/// Function called when the export button is clicked.
/// It processes the export request based on the export settings.
pub fn handle_export_request(settings: ExportSettings) {
match settings.file_type {
ExportFileType::MP4 => {
println!("Exporting as MP4 with {} fps.", settings.fps);
// ... existing MP4 export logic ...
}
ExportFileType::GIF => {
// Apply GIF-specific defaults if needed. For example, if the FPS is not set or is zero:
let fps = if settings.fps == 0 { 15 } else { settings.fps };
println!(
"Exporting as GIF at {} fps. High quality: {}",
fps, settings.high_quality
);
// ... add GIF export processing logic here ...
}
}
}
69 changes: 66 additions & 3 deletions apps/desktop/src-tauri/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
use cap_project::{ProjectConfiguration, XY};
use std::path::PathBuf;
use tauri::AppHandle;
use async_trait::async_trait; // add this dependency in Cargo.toml if needed

#[tauri::command]
#[specta::specta]
Expand All @@ -16,6 +17,8 @@ pub async fn export_video(
force: bool,
fps: u32,
resolution_base: XY<u32>,
export_format: ExportFormat, // new parameter for format
high_quality: Option<bool>, // new parameter for GIF quality; None if not provided
) -> Result<PathBuf, String> {
let editor_instance = create_editor_instance_impl(&app, &video_id).await?;

Expand Down Expand Up @@ -51,8 +54,8 @@ pub async fn export_video(

let output_path = editor_instance.meta().output_path();

// If the file exists and we're not forcing a re-render, return it
if output_path.exists() && !force {
// If the file exists and we're not forcing a re-render, return it (MP4)
if output_path.exists() && !force && matches!(export_format, ExportFormat::MP4) {
return Ok(output_path);
}

Expand All @@ -77,6 +80,7 @@ pub async fn export_video(
.map(|auth| auth.is_upgraded())
.unwrap_or(false);

// Create the exporter instance with common parameters
let exporter = cap_export::Exporter::new(
modified_project,
output_path.clone(),
Expand All @@ -101,7 +105,24 @@ pub async fn export_video(
e.to_string()
})?;

let result = exporter.export_with_custom_muxer().await;
// Decide export logic based on selected format.
let result = match export_format {
ExportFormat::MP4 => {
// Use your existing export logic (using custom muxer)
exporter.export_with_custom_muxer().await
}
ExportFormat::GIF => {
// For GIF, apply GIF-specific defaults.
let gif_fps = if fps == 0 { 15 } else { fps };
let quality = high_quality.unwrap_or(false);
println!(
"Exporting as GIF at {} fps. High quality: {}",
gif_fps, quality
);
// Call a new function to export GIF. (You'll need to implement the gif encoding using the gif crate.)
exporter.export_gif(gif_fps, quality).await
}
};

match result {
Ok(_) => {
Expand All @@ -122,6 +143,12 @@ pub struct ExportEstimates {
pub estimated_size_mb: f64,
}

#[derive(Debug, serde::Serialize, specta::Type, Clone)]
pub enum ExportFormat {
MP4,
GIF,
}

// This will need to be refactored at some point to be more accurate.
#[tauri::command]
#[specta::specta]
Expand Down Expand Up @@ -197,3 +224,39 @@ pub async fn get_export_estimates(
estimated_size_mb,
})
}

#[async_trait]
pub trait ExporterExt {
async fn export_gif(&self, fps: u32, high_quality: bool) -> Result<(), Box<dyn std::error::Error>>;
}

#[async_trait]
impl ExporterExt for cap_export::Exporter {
async fn export_gif(&self, fps: u32, high_quality: bool) -> Result<(), Box<dyn std::error::Error>> {
// Initialize GIF encoder using the gif crate.
// This is a stub implementation. Replace it with your actual logic.
//
// For example:
//
// use std::fs::File;
// use gif::{Encoder, Frame, Repeat, SetParameter};
//
// let file = File::create(&self.output_path)?;
// let mut encoder = Encoder::new(file, self.resolution_base.x as u16, self.resolution_base.y as u16, &[])?;
// encoder.set(Repeat::Infinite)?;
//
// for frame in self.generate_frames(fps).await? {
// let gif_frame = Frame::from_rgba_speed(
// self.resolution_base.x as u16,
// self.resolution_base.y as u16,
// &mut frame.into_vec(),
// if high_quality { 5 } else { 10 },
// );
// encoder.write_frame(&gif_frame)?;
// }
//
// Ok(())
println!("GIF export simulation complete.");
Ok(())
}
}
21 changes: 21 additions & 0 deletions apps/desktop/src-tauri/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,24 @@ impl<T> Resource<T> {
self.value.borrow().as_ref()
}
}

// UI labels and constants for export dialogs.
pub mod export_ui {
/// Title for the export dialog.
pub const EXPORT_DIALOG_TITLE: &str = "Export Options";

/// Label for the file type selection dropdown.
pub const FILE_TYPE_LABEL: &str = "Select Export Format:";

/// Option value for MP4.
pub const MP4_OPTION: &str = "MP4";

/// Option value for GIF.
pub const GIF_OPTION: &str = "GIF";

/// Label for GIF FPS input.
pub const GIF_FPS_LABEL: &str = "Enter desired FPS (default is 15):";

/// Label for the high quality GIF toggle.
pub const GIF_QUALITY_LABEL: &str = "High Quality GIF:";
}
Loading