Skip to content

Commit

Permalink
Merge branch 'main' into fix-windows-inner-size
Browse files Browse the repository at this point in the history
  • Loading branch information
ItsEeleeya committed Jan 9, 2025
2 parents e5e98eb + 0b1baa4 commit 347a9e1
Show file tree
Hide file tree
Showing 25 changed files with 508 additions and 563 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions apps/desktop/src-tauri/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
get_video_metadata, upsert_editor_instance, windows::ShowCapWindow, RenderProgress,
VideoRecordingMetadata, VideoType,
general_settings::GeneralSettingsStore, get_video_metadata, upsert_editor_instance,
windows::ShowCapWindow, RenderProgress, VideoRecordingMetadata, VideoType,
};
use cap_project::ProjectConfiguration;
use std::path::PathBuf;
Expand Down Expand Up @@ -44,10 +44,8 @@ pub async fn export_video(
.unwrap_or(screen_metadata.duration),
);

// Calculate total frames with ceiling to ensure we don't exceed 100%
let total_frames = ((duration * 30.0).ceil() as u32).max(1);

let editor_instance = upsert_editor_instance(&app, video_id.clone()).await;
let total_frames = editor_instance.get_total_frames();

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

Expand All @@ -72,6 +70,7 @@ pub async fn export_video(
}

let exporter = cap_export::Exporter::new(
&app,
modified_project,
output_path.clone(),
move |frame_index| {
Expand Down
39 changes: 39 additions & 0 deletions apps/desktop/src-tauri/src/general_settings.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use cap_project::Resolution;
use serde::{Deserialize, Serialize};
use serde_json::json;
use specta::Type;
Expand Down Expand Up @@ -25,6 +26,8 @@ pub struct GeneralSettingsStore {
pub has_completed_startup: bool,
#[serde(default)]
pub theme: AppTheme,
#[serde(default)]
pub recording_config: Option<RecordingConfig>,
}

#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize, Type)]
Expand All @@ -36,6 +39,25 @@ pub enum AppTheme {
Dark,
}

#[derive(Debug, Clone, Serialize, Deserialize, Type)]
#[serde(rename_all = "camelCase")]
pub struct RecordingConfig {
pub fps: u32,
pub resolution: Resolution,
}

impl Default for RecordingConfig {
fn default() -> Self {
Self {
fps: 30,
resolution: Resolution {
width: 1920,
height: 1080,
},
}
}
}

fn true_b() -> bool {
true
}
Expand Down Expand Up @@ -78,3 +100,20 @@ pub fn init(app: &AppHandle) {
app.manage(GeneralSettingsState::new(store));
println!("GeneralSettingsState managed");
}

#[tauri::command]
#[specta::specta]
pub async fn get_recording_config(app: AppHandle) -> Result<RecordingConfig, String> {
let settings = GeneralSettingsStore::get(&app)?;
Ok(settings
.and_then(|s| s.recording_config)
.unwrap_or_default())
}

#[tauri::command]
#[specta::specta]
pub async fn set_recording_config(app: AppHandle, config: RecordingConfig) -> Result<(), String> {
GeneralSettingsStore::update(&app, |settings| {
settings.recording_config = Some(config);
})
}
51 changes: 45 additions & 6 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ use cap_media::feeds::{AudioInputFeed, AudioInputSamplesSender};
use cap_media::frame_ws::WSFrame;
use cap_media::sources::CaptureScreen;
use cap_media::{feeds::CameraFeed, sources::ScreenCaptureTarget};
use cap_project::{Content, ProjectConfiguration, RecordingMeta, SharingMeta};
use cap_project::{Content, ProjectConfiguration, RecordingMeta, Resolution, SharingMeta};
use cap_recording::RecordingOptions;
use cap_rendering::ProjectRecordings;
use clipboard_rs::common::RustImage;
use clipboard_rs::{Clipboard, ClipboardContext};
use general_settings::GeneralSettingsStore;
use general_settings::{GeneralSettingsStore, RecordingConfig};
use mp4::Mp4Reader;
// use display::{list_capture_windows, Bounds, CaptureTarget, FPS};
use notifications::NotificationType;
Expand Down Expand Up @@ -383,9 +383,20 @@ type MutableState<'a, T> = State<'a, Arc<RwLock<T>>>;

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

// Load settings from disk if they exist
if let Ok(Some(settings)) = GeneralSettingsStore::get(&app) {
if let Some(config) = settings.recording_config {
state.start_recording_options.fps = config.fps;
state.start_recording_options.output_resolution = Some(config.resolution);
}
}

// If there's a saved audio input but no feed, initialize it
if let Some(audio_input_name) = state.start_recording_options.audio_input_name() {
if state.audio_input_feed.is_none() {
Expand All @@ -407,15 +418,28 @@ async fn get_recording_options(state: MutableState<'_, App>) -> Result<Recording
#[tauri::command]
#[specta::specta]
async fn set_recording_options(
app: AppHandle,
state: MutableState<'_, App>,
options: RecordingOptions,
) -> Result<(), String> {
// Update in-memory state
state
.write()
.await
.set_start_recording_options(options)
.set_start_recording_options(options.clone())
.await?;

// Update persistent settings
GeneralSettingsStore::update(&app, |settings| {
settings.recording_config = Some(RecordingConfig {
fps: options.fps,
resolution: options.output_resolution.unwrap_or_else(|| Resolution {
width: 1920,
height: 1080,
}),
});
})?;

Ok(())
}

Expand Down Expand Up @@ -1856,6 +1880,7 @@ pub async fn run() {
global_message_dialog,
show_window,
write_clipboard_string,
get_editor_total_frames,
])
.events(tauri_specta::collect_events![
RecordingOptionsChanged,
Expand Down Expand Up @@ -1989,11 +2014,13 @@ pub async fn run() {
audio_input_feed: None,
start_recording_options: RecordingOptions {
capture_target: ScreenCaptureTarget::Screen(CaptureScreen {
id: 1,
name: "Default".to_string(),
id: 0,
name: String::new(),
}),
camera_label: None,
audio_input_name: None,
fps: 30,
output_resolution: None,
},
current_recording: None,
pre_created_video: None,
Expand Down Expand Up @@ -2353,3 +2380,15 @@ trait EventExt: tauri_specta::Event {
}

impl<T: tauri_specta::Event> EventExt for T {}

#[tauri::command(async)]
#[specta::specta]
async fn get_editor_total_frames(app: AppHandle, video_id: String) -> Result<u32, String> {
let editor_instances = app.state::<EditorInstancesState>();
let instances = editor_instances.lock().await;

let instance = instances
.get(&video_id)
.ok_or_else(|| "Editor instance not found".to_string())?;
Ok(instance.get_total_frames())
}
8 changes: 8 additions & 0 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ pub fn list_cameras() -> Vec<String> {
pub async fn start_recording(app: AppHandle, state: MutableState<'_, App>) -> Result<(), String> {
let mut state = state.write().await;

// Get the recording config
let config = GeneralSettingsStore::get(&app)?
.and_then(|s| s.recording_config)
.unwrap_or_default();

// Update the recording options with the configured FPS
state.start_recording_options.fps = config.fps;

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

let recording_dir = app
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/routes/(window-chrome)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function Settings(props: RouteSectionProps) {
<For
each={[
{ href: "general", name: "General", icon: IconCapSettings },
{ href: "config", name: "Config", icon: IconLucideVideo },
{ href: "hotkeys", name: "Shortcuts", icon: IconCapHotkeys },
{
href: "recordings",
Expand All @@ -31,7 +32,7 @@ export default function Settings(props: RouteSectionProps) {
name: "Previous Screenshots",
icon: IconLucideCamera,
},
window.FLAGS.customS3 && {
{
href: "apps",
name: "Cap Apps",
icon: IconLucideLayoutGrid,
Expand Down
89 changes: 89 additions & 0 deletions apps/desktop/src/routes/(window-chrome)/settings/config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createQuery } from "@tanstack/solid-query";
import { For, Show } from "solid-js";
import { commands } from "~/utils/tauri";

const RESOLUTION_OPTIONS = [
{ label: "720p (1280x720)", value: "720p", width: 1280, height: 720 },
{ label: "1080p (1920x1080)", value: "1080p", width: 1920, height: 1080 },
{ label: "4K (3840x2160)", value: "4k", width: 3840, height: 2160 },
] as const;

const FPS_OPTIONS = [
{ label: "30 FPS", value: 30 },
{ label: "60 FPS", value: 60 },
] as const;

export default function Config() {
const config = createQuery(() => ({
queryKey: ["recording-config"],
queryFn: () => commands.getRecordingOptions(),
}));

const updateConfig = async (updates: {
fps?: number;
outputResolution?: { width: number; height: number };
}) => {
if (!config.data) return;

await commands.setRecordingOptions({
...config.data,
...updates,
});

config.refetch();
};

return (
<div class="flex flex-col w-full h-full divide-y divide-[--gray-200] pt-1 pb-12">
<div class="p-4">
<div class="mb-6">
<label class="text-sm font-medium mb-2 block">
Output Resolution
</label>
<select
class="w-full p-2 border rounded"
value={
RESOLUTION_OPTIONS.find(
(opt) =>
opt.width === config.data?.outputResolution?.width &&
opt.height === config.data?.outputResolution?.height
)?.value
}
onChange={(e) => {
const option = RESOLUTION_OPTIONS.find(
(opt) => opt.value === e.currentTarget.value
);
if (option) {
updateConfig({
outputResolution: {
width: option.width,
height: option.height,
},
});
}
}}
>
<For each={RESOLUTION_OPTIONS}>
{(option) => <option value={option.value}>{option.label}</option>}
</For>
</select>
</div>

<div class="mb-6">
<label class="text-sm font-medium mb-2 block">Frame Rate</label>
<select
class="w-full p-2 border rounded"
value={config.data?.fps}
onChange={(e) => {
updateConfig({ fps: Number(e.currentTarget.value) });
}}
>
<For each={FPS_OPTIONS}>
{(option) => <option value={option.value}>{option.label}</option>}
</For>
</select>
</div>
</div>
</div>
);
}
Loading

0 comments on commit 347a9e1

Please sign in to comment.