Skip to content
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

feat: Video importer (bring your own video) #162

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
84 changes: 84 additions & 0 deletions apps/desktop/src-tauri/src/import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use cap_project::RecordingMeta;
use std::path::PathBuf;
use tauri::Manager;
use tracing::{error, info};
use uuid::Uuid;

pub fn get_projects_dir(app: &tauri::AppHandle) -> Result<PathBuf, String> {
let app_data = app.path().app_data_dir().map_err(|e| e.to_string())?;
Ok(app_data.join("recordings"))
}

#[tauri::command]
#[specta::specta]
pub async fn import_video_to_project(
app: tauri::AppHandle,
video_path: PathBuf,
) -> Result<String, String> {
info!("Attempting to import video from path: {:?}", video_path);

// Verify the video file exists and is MP4
if !video_path.exists() {
let err = format!("Video path {:?} not found!", video_path);
error!("{}", err);
return Err(err);
}

if video_path.extension().and_then(|ext| ext.to_str()) != Some("mp4") {
return Err("Only MP4 files are supported".to_string());
}

let project_id = Uuid::new_v4().to_string();
info!("Generated project ID: {}", project_id);

// Create the project directory with .cap extension
let project_dir = get_projects_dir(&app)?.join(format!("{}.cap", project_id));
info!("Project directory: {:?}", project_dir);

std::fs::create_dir_all(&project_dir).map_err(|e| {
let err = format!("Failed to create project directory: {}", e);
error!("{}", err);
err
})?;

let content_dir = project_dir.join("content");
info!("Creating content directory: {:?}", content_dir);

std::fs::create_dir_all(&content_dir).map_err(|e| {
let err = format!("Failed to create content directory: {}", e);
error!("{}", err);
err
})?;

// Always copy to display.mp4
let project_video_path = content_dir.join("display.mp4");
info!("Copying video to: {:?}", project_video_path);

std::fs::copy(&video_path, &project_video_path).map_err(|e| {
let err = format!("Failed to copy video file: {}", e);
error!("{}", err);
err
})?;

// Create project metadata
let meta = RecordingMeta {
project_path: project_dir.clone(),
sharing: None,
pretty_name: format!(
"Imported Video {}",
chrono::Local::now().format("%Y-%m-%d at %H.%M.%S")
),
display: cap_project::Display {
path: PathBuf::from("content").join("display.mp4"), // Always use display.mp4
},
camera: None,
audio: None,
segments: vec![],
cursor: None,
};

meta.save_for_project();
info!("Project metadata saved successfully");

Ok(project_id)
}
19 changes: 7 additions & 12 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod platform;
mod recording;
// mod resource;
mod cursor;
mod import;
mod tray;
mod upload;
mod web_api;
Expand All @@ -33,8 +34,9 @@ use cap_rendering::{ProjectUniforms, ZOOM_DURATION};
// use display::{list_capture_windows, Bounds, CaptureTarget, FPS};
use general_settings::GeneralSettingsStore;
use image::{ImageBuffer, Rgba};
use import::{get_projects_dir, import_video_to_project};
use mp4::Mp4Reader;
use num_traits::ToBytes;

use png::{ColorType, Encoder};
use recording::{
list_cameras, list_capture_screens, list_capture_windows, InProgressRecording, FPS,
Expand Down Expand Up @@ -306,12 +308,7 @@ async fn start_recording(app: AppHandle, state: MutableState<'_, App>) -> Result

let id = uuid::Uuid::new_v4().to_string();

let recording_dir = app
.path()
.app_data_dir()
.unwrap()
.join("recordings")
.join(format!("{id}.cap"));
let recording_dir = get_projects_dir(&app).unwrap().join(format!("{id}.cap"));

// Check if auto_create_shareable_link is true and user is upgraded
let general_settings = GeneralSettingsStore::get(&app)?;
Expand Down Expand Up @@ -1387,11 +1384,8 @@ async fn get_video_metadata(
video_id
};

let video_dir = app
.path()
.app_data_dir()
let video_dir = get_projects_dir(&app)
.unwrap()
.join("recordings")
.join(format!("{}.cap", video_id));

let screen_video_path = video_dir.join("content").join("display.mp4");
Expand Down Expand Up @@ -1904,7 +1898,7 @@ async fn upload_screenshot(
}

if !auth.is_upgraded() {
open_upgrade_window(app).await;
open_upgrade_window(app.clone()).await;
return Ok(UploadResult::UpgradeRequired);
}
}
Expand Down Expand Up @@ -2451,6 +2445,7 @@ pub async fn run() {
is_camera_window_open,
seek_to,
send_feedback_request,
import_video_to_project,
])
.events(tauri_specta::collect_events![
RecordingOptionsChanged,
Expand Down
15 changes: 15 additions & 0 deletions apps/desktop/src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum TrayItem {
OpenCap,
StartNewRecording,
TakeScreenshot,
CreateProjectFromVideo,
PreviousRecordings,
PreviousScreenshots,
OpenSettings,
Expand All @@ -30,6 +31,7 @@ impl From<TrayItem> for MenuId {
TrayItem::OpenCap => "open_cap",
TrayItem::StartNewRecording => "new_recording",
TrayItem::TakeScreenshot => "take_screenshot",
TrayItem::CreateProjectFromVideo => "create_project_from_video",
TrayItem::PreviousRecordings => "previous_recordings",
TrayItem::PreviousScreenshots => "previous_screenshots",
TrayItem::OpenSettings => "open_settings",
Expand All @@ -45,6 +47,7 @@ impl From<MenuId> for TrayItem {
"open_cap" => TrayItem::OpenCap,
"new_recording" => TrayItem::StartNewRecording,
"take_screenshot" => TrayItem::TakeScreenshot,
"create_project_from_video" => TrayItem::CreateProjectFromVideo,
"previous_recordings" => TrayItem::PreviousRecordings,
"previous_screenshots" => TrayItem::PreviousScreenshots,
"open_settings" => TrayItem::OpenSettings,
Expand Down Expand Up @@ -81,6 +84,13 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
true,
None::<&str>,
)?,
&MenuItem::with_id(
app,
TrayItem::CreateProjectFromVideo,
"Create Project from Video",
true,
None::<&str>,
)?,
&MenuItem::with_id(
app,
TrayItem::PreviousRecordings,
Expand Down Expand Up @@ -122,6 +132,11 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
TrayItem::TakeScreenshot => {
let _ = RequestNewScreenshot.emit(&app_handle);
}
TrayItem::CreateProjectFromVideo => {
if let Err(e) = CapWindow::ImportVideo.show(&app_handle) {
eprintln!("Failed to show import video window: {:?}", e);
}
}
TrayItem::PreviousRecordings => {
let _ = RequestOpenSettings {
page: "recordings".to_string(),
Expand Down
27 changes: 26 additions & 1 deletion apps/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub enum CapWindow {
Main,
Settings { page: Option<String> },
Editor { project_id: String },
ImportVideo,
Permissions,
PrevRecordings,
WindowCaptureOccluder,
Expand All @@ -23,6 +24,7 @@ pub enum CapWindowId {
Main,
Settings,
Editor { project_id: String },
ImportVideo,
Permissions,
PrevRecordings,
WindowCaptureOccluder,
Expand All @@ -36,6 +38,7 @@ impl CapWindowId {
match label {
"main" => Self::Main,
"settings" => Self::Settings,
"import-video" => Self::ImportVideo,
"camera" => Self::Camera,
"window-capture-occluder" => Self::WindowCaptureOccluder,
"in-progress-recording" => Self::InProgressRecording,
Expand All @@ -53,19 +56,21 @@ impl CapWindowId {
match self {
Self::Main => "main".to_string(),
Self::Settings => "settings".to_string(),
Self::ImportVideo => "import-video".to_string(),
Self::Camera => "camera".to_string(),
Self::WindowCaptureOccluder => "window-capture-occluder".to_string(),
Self::InProgressRecording => "in-progress-recording".to_string(),
Self::PrevRecordings => "prev-recordings".to_string(),
Self::Editor { project_id } => format!("editor-{}", project_id),
Self::Permissions => "permissions".to_string(),
Self::Upgrade => "upgrade".to_string(),
Self::Editor { project_id } => format!("editor-{}", project_id),
}
}

pub fn title(&self) -> String {
match self {
Self::Settings => "Cap Settings".to_string(),
Self::ImportVideo => "Cap Import Video".to_string(),
Self::WindowCaptureOccluder => "Cap Window Capture Occluder".to_string(),
Self::InProgressRecording => "Cap In Progress Recording".to_string(),
Self::Editor { .. } => "Cap Editor".to_string(),
Expand Down Expand Up @@ -97,6 +102,7 @@ impl CapWindowId {
| Self::WindowCaptureOccluder
| Self::PrevRecordings => None,
Self::Editor { .. } => Some(Some(LogicalPosition::new(20.0, 48.0))),
Self::ImportVideo => Some(Some(LogicalPosition::new(14.0, 22.0))),
_ => Some(None),
}
}
Expand Down Expand Up @@ -341,6 +347,24 @@ impl CapWindow {

window
}
Self::ImportVideo => {
let mut window_builder = self
.window_builder(app, "/import-video")
.inner_size(500.0, 400.0)
.resizable(false)
.maximized(false)
.shadow(true)
.transparent(true);

#[cfg(target_os = "macos")]
{
window_builder = window_builder
.hidden_title(true)
.title_bar_style(tauri::TitleBarStyle::Overlay);
}

window_builder.build()?
}
};

if let Some(position) = id.traffic_lights_position() {
Expand Down Expand Up @@ -388,6 +412,7 @@ impl CapWindow {
CapWindow::Camera { .. } => CapWindowId::Camera,
CapWindow::InProgressRecording { .. } => CapWindowId::InProgressRecording,
CapWindow::Upgrade => CapWindowId::Upgrade,
CapWindow::ImportVideo => CapWindowId::ImportVideo,
}
}
}
Expand Down
Loading
Loading