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

Support multiple segments in recording meta #177

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 0 additions & 1 deletion apps/desktop/src-tauri/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

use serde::{Deserialize, Serialize};
use serde_json::json;
use specta::Type;
Expand Down
3 changes: 1 addition & 2 deletions apps/desktop/src-tauri/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ pub async fn export_video(
.ok();
},
&editor_instance.project_path,
editor_instance.audio.clone(),
editor_instance.meta(),
editor_instance.render_constants.clone(),
editor_instance.cursor.clone(),
&editor_instance.segments,
)
.await
.map_err(|e| {
Expand Down
21 changes: 12 additions & 9 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ mod windows;

use audio::AppSounds;
use auth::{AuthStore, AuthenticationInvalid};
use cap_editor::{EditorInstance, FRAMES_WS_PATH};
use cap_editor::{EditorState, ProjectRecordings};
use cap_editor::{EditorInstance, ProjectRecordings, FRAMES_WS_PATH};
use cap_editor::EditorState;
use cap_media::feeds::{AudioInputFeed, AudioInputSamplesSender};
use cap_media::sources::CaptureScreen;
use cap_media::{
Expand Down Expand Up @@ -735,7 +735,7 @@ async fn create_editor_instance(
let project_config = editor_instance.project_config.1.borrow();
project_config.clone()
},
recordings: editor_instance.recordings,
recordings: editor_instance.recordings.clone(),
path: editor_instance.project_path.clone(),
pretty_name: meta.pretty_name,
})
Expand Down Expand Up @@ -1302,13 +1302,16 @@ async fn take_screenshot(app: AppHandle, _state: MutableState<'_, App>) -> Resul
project_path: recording_dir.clone(),
sharing: None,
pretty_name: screenshot_name,
display: Display {
path: screenshot_path.clone(),
content: cap_project::Content::SingleSegment {
segment: cap_project::SingleSegment {
display: Display {
path: screenshot_path.clone(),
},
camera: None,
audio: None,
cursor: None,
},
},
camera: None,
audio: None,
segments: vec![],
cursor: None,
}
.save_for_project();

Expand Down
114 changes: 60 additions & 54 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ use crate::{
use cap_editor::ProjectRecordings;
use cap_media::feeds::CameraFeed;
use cap_media::sources::{AVFrameCapture, CaptureScreen, CaptureWindow, ScreenCaptureSource};
use cap_project::{ProjectConfiguration, TimelineConfiguration, TimelineSegment, ZoomSegment};
use cap_rendering::ZOOM_DURATION;
use cap_project::{
Content, ProjectConfiguration, TimelineConfiguration, TimelineSegment,
};
use std::time::Instant;
use tauri::{AppHandle, Manager};
use tauri_specta::Event;
Expand Down Expand Up @@ -120,22 +121,24 @@ pub async fn start_recording(app: AppHandle, state: MutableState<'_, App>) -> Re
#[tauri::command]
#[specta::specta]
pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> {
let state = state.write().await;
let mut state = state.write().await;

if let Some(recording) = state.current_recording.as_mut() {
recording.pause().await.map_err(|e| e.to_string())?;
}

// if let Some(recording) = &mut state.current_recording {
// recording.pause().await.map_err(|e| e.to_string())?;
// }
Ok(())
}

#[tauri::command]
#[specta::specta]
pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> {
let state = state.write().await;
let mut state = state.write().await;

if let Some(recording) = state.current_recording.as_mut() {
recording.resume().await.map_err(|e| e.to_string())?;
}

// if let Some(recording) = &mut state.current_recording {
// recording.resume().await.map_err(|e| e.to_string())?;
// }
Ok(())
}

Expand Down Expand Up @@ -166,14 +169,16 @@ pub async fn stop_recording(app: AppHandle, state: MutableState<'_, App>) -> Res
let screenshots_dir = recording.recording_dir.join("screenshots");
std::fs::create_dir_all(&screenshots_dir).ok();

let display_output_path = match &recording.meta.content {
Content::SingleSegment { segment } => segment.path(&recording.meta, &segment.display.path),
Content::MultipleSegments { inner } => {
inner.path(&recording.meta, &inner.segments[0].display.path)
}
};

let display_screenshot = screenshots_dir.join("display.jpg");
let now = Instant::now();
create_screenshot(
recording.display_output_path.clone(),
display_screenshot.clone(),
None,
)
.await?;
create_screenshot(display_output_path, display_screenshot.clone(), None).await?;
println!("created screenshot in {:?}", now.elapsed());

// let thumbnail = screenshots_dir.join("thumbnail.png");
Expand Down Expand Up @@ -209,6 +214,7 @@ pub async fn stop_recording(app: AppHandle, state: MutableState<'_, App>) -> Res
let start = passed_duration;
passed_duration += recording.segments[i + 1] - recording.segments[i];
segments.push(TimelineSegment {
recording_segment: None,
start,
end: passed_duration.min(recordings.duration()),
timescale: 1.0,
Expand All @@ -217,48 +223,48 @@ pub async fn stop_recording(app: AppHandle, state: MutableState<'_, App>) -> Res
segments
};

let zoom_segments = {
let mut segments = vec![];

const ZOOM_SEGMENT_AFTER_CLICK_PADDING: f64 = 1.5;

for click in &recording.cursor_data.clicks {
let time = click.process_time_ms / 1000.0;

if segments.last().is_none() {
segments.push(ZoomSegment {
start: (click.process_time_ms / 1000.0 - (ZOOM_DURATION + 0.2)).max(0.0),
end: click.process_time_ms / 1000.0 + ZOOM_SEGMENT_AFTER_CLICK_PADDING,
amount: 2.0,
});
} else {
let last_segment = segments.last_mut().unwrap();

if click.down {
if last_segment.end > time {
last_segment.end = (time + ZOOM_SEGMENT_AFTER_CLICK_PADDING)
.min(recordings.duration());
} else if time < max_duration - ZOOM_DURATION {
segments.push(ZoomSegment {
start: (time - ZOOM_DURATION).max(0.0),
end: time + ZOOM_SEGMENT_AFTER_CLICK_PADDING,
amount: 2.0,
});
}
} else {
last_segment.end =
(time + ZOOM_SEGMENT_AFTER_CLICK_PADDING).min(recordings.duration());
}
}
}

segments
};
// let zoom_segments = {
// let mut segments = vec![];

// const ZOOM_SEGMENT_AFTER_CLICK_PADDING: f64 = 1.5;

// for click in &recording.cursor_data.clicks {
// let time = click.process_time_ms / 1000.0;

// if segments.last().is_none() {
// segments.push(ZoomSegment {
// start: (click.process_time_ms / 1000.0 - (ZOOM_DURATION + 0.2)).max(0.0),
// end: click.process_time_ms / 1000.0 + ZOOM_SEGMENT_AFTER_CLICK_PADDING,
// amount: 2.0,
// });
// } else {
// let last_segment = segments.last_mut().unwrap();

// if click.down {
// if last_segment.end > time {
// last_segment.end = (time + ZOOM_SEGMENT_AFTER_CLICK_PADDING)
// .min(recordings.duration());
// } else if time < max_duration - ZOOM_DURATION {
// segments.push(ZoomSegment {
// start: (time - ZOOM_DURATION).max(0.0),
// end: time + ZOOM_SEGMENT_AFTER_CLICK_PADDING,
// amount: 2.0,
// });
// }
// } else {
// last_segment.end =
// (time + ZOOM_SEGMENT_AFTER_CLICK_PADDING).min(recordings.duration());
// }
// }
// }

// segments
// };

ProjectConfiguration {
timeline: Some(TimelineConfiguration {
segments,
zoom_segments,
zoom_segments: vec![],
}),
..Default::default()
}
Expand Down
11 changes: 7 additions & 4 deletions apps/desktop/src/utils/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ export type HotkeyAction = "startRecording" | "stopRecording" | "restartRecordin
export type HotkeysConfiguration = { show: boolean }
export type HotkeysStore = { hotkeys: { [key in HotkeyAction]: Hotkey } }
export type JsonValue<T> = [T]
export type MultipleSegment = { display: Display; camera?: CameraMeta | null; audio?: AudioMeta | null; cursor: string | null }
export type MultipleSegments = { segments: MultipleSegment[] }
export type NewNotification = { title: string; body: string; is_error: boolean }
export type NewRecordingAdded = { path: string }
export type NewScreenshotAdded = { path: string }
Expand All @@ -246,13 +248,12 @@ export type OSPermissionsCheck = { screenRecording: OSPermissionStatus; micropho
export type Plan = { upgraded: boolean; last_checked: number }
export type PreCreatedVideo = { id: string; link: string; config: S3UploadMeta }
export type ProjectConfiguration = { aspectRatio: AspectRatio | null; background: BackgroundConfiguration; camera: Camera; audio: AudioConfiguration; cursor: CursorConfiguration; hotkeys: HotkeysConfiguration; timeline?: TimelineConfiguration | null; motionBlur: number | null }
export type ProjectRecordings = { display: Video; camera: Video | null; audio: Audio | null }
export type ProjectRecordings = { segments: SegmentRecordings[] }
export type RecordingInfo = { captureTarget: ScreenCaptureTarget }
export type RecordingMeta = { pretty_name: string; sharing?: SharingMeta | null; display: Display; camera?: CameraMeta | null; audio?: AudioMeta | null; segments?: RecordingSegment[]; cursor: string | null }
export type RecordingMeta = ({ segment: SingleSegment } | { inner: MultipleSegments }) & { pretty_name: string; sharing?: SharingMeta | null }
export type RecordingMetaChanged = { id: string }
export type RecordingOptions = { captureTarget: ScreenCaptureTarget; cameraLabel: string | null; audioInputName: string | null }
export type RecordingOptionsChanged = null
export type RecordingSegment = { start: number; end: number }
export type RecordingStarted = null
export type RecordingStopped = { path: string }
export type RenderFrameEvent = { frame_number: number }
Expand All @@ -264,11 +265,13 @@ export type RequestStartRecording = null
export type RequestStopRecording = null
export type S3UploadMeta = { id: string; user_id: string; aws_region?: string; aws_bucket?: string }
export type ScreenCaptureTarget = ({ variant: "window" } & CaptureWindow) | ({ variant: "screen" } & CaptureScreen)
export type SegmentRecordings = { display: Video; camera: Video | null; audio: Audio | null }
export type SerializedEditorInstance = { framesSocketUrl: string; recordingDuration: number; savedProjectConfig: ProjectConfiguration; recordings: ProjectRecordings; path: string; prettyName: string }
export type SharingMeta = { id: string; link: string }
export type ShowCapWindow = "Setup" | "Main" | { Settings: { page: string | null } } | { Editor: { project_id: string } } | "PrevRecordings" | "WindowCaptureOccluder" | { Camera: { ws_port: number } } | { InProgressRecording: { position: [number, number] | null } } | "Upgrade"
export type SingleSegment = { display: Display; camera?: CameraMeta | null; audio?: AudioMeta | null; cursor: string | null }
export type TimelineConfiguration = { segments: TimelineSegment[]; zoomSegments?: ZoomSegment[] }
export type TimelineSegment = { timescale: number; start: number; end: number }
export type TimelineSegment = { recordingSegment: number | null; timescale: number; start: number; end: number }
export type UploadMode = { Initial: { pre_created_video: PreCreatedVideo | null } } | "Reupload"
export type UploadProgress = { stage: string; progress: number; message: string }
export type UploadResult = { Success: string } | "NotAuthenticated" | "PlanCheckFailed" | "UpgradeRequired"
Expand Down
Loading
Loading