Merge branch 'siddharthvaddem:main' into main
This commit is contained in:
@@ -25,21 +25,20 @@ Screen Studio is an awesome product and this is definitely not a 1:1 clone. Open
|
||||
OpenScreen is 100% free for personal and commercial use. Use it, modify it, distribute it. (Just be cool 😁 and give a shoutout if you feel like it !)
|
||||
|
||||
<p align="center">
|
||||
<img src="public/preview3.png" alt="OpenScreen App Preview 3" style="height: 320px; margin-right: 12px;" />
|
||||
<img src="public/preview4.png" alt="OpenScreen App Preview 4" style="height: 320px; margin-right: 12px;" />
|
||||
<img src="public/preview3.png" alt="OpenScreen App Preview 3" style="height: 0.2467; margin-right: 12px;" />
|
||||
<img src="public/preview4.png" alt="OpenScreen App Preview 4" style="height: 0.1678; margin-right: 12px;" />
|
||||
</p>
|
||||
|
||||
## Core Features
|
||||
- Record your whole screen or specific windows.
|
||||
- Add Automatic zooms or manual zooms (customizable depth levels).
|
||||
- Record microphone audio and system audio capture.
|
||||
- Customize the duration and position of zooms however you please.
|
||||
- Record specific windows or your whole screen.
|
||||
- Add automatic or manual zooms (adjustable depth levels) and customize their durarion and position.
|
||||
- Record microphone and system audio.
|
||||
- Crop video recordings to hide parts.
|
||||
- Choose between wallpapers, solid colors, gradients or a custom background.
|
||||
- Motion blur for smoother pan and zoom effects.
|
||||
- Add annotations (text, arrows, images).
|
||||
- Trim sections of the clip.
|
||||
- Customize speed at different segments.
|
||||
- Customize the speed of different segments.
|
||||
- Export in different aspect ratios and resolutions.
|
||||
|
||||
## Installation
|
||||
@@ -78,9 +77,9 @@ You may need to grant screen recording permissions depending on your desktop env
|
||||
|
||||
System audio capture relies on Electron's [desktopCapturer](https://www.electronjs.org/docs/latest/api/desktop-capturer) and has some platform-specific quirks:
|
||||
|
||||
- **macOS**: Requires macOS 13+. On macOS 14.2+ you'll be prompted to grant audio capture permission. macOS 12 and below does not support system audio (mic still work).
|
||||
- **macOS**: Requires macOS 13+. On macOS 14.2+ you'll be prompted to grant audio capture permission. macOS 12 and below does not support system audio (mic still works).
|
||||
- **Windows**: Works out of the box.
|
||||
- **Linux**: Needs PipeWire (default on Ubuntu 22.04+, Fedora 34+). Older PulseAudio-only setups may not support system audio (mic should still works).
|
||||
- **Linux**: Needs PipeWire (default on Ubuntu 22.04+, Fedora 34+). Older PulseAudio-only setups may not support system audio (mic should still work).
|
||||
|
||||
## Built with
|
||||
- Electron
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { computeFrameStepTime } from "@/lib/frameStep";
|
||||
import type { ProjectMedia } from "@/lib/recordingSession";
|
||||
import { matchesShortcut } from "@/lib/shortcuts";
|
||||
import { loadUserPreferences, saveUserPreferences } from "@/lib/userPreferences";
|
||||
import {
|
||||
getAspectRatioValue,
|
||||
getNativeAspectRatioValue,
|
||||
@@ -366,6 +367,28 @@ export default function VideoEditor() {
|
||||
loadInitialData();
|
||||
}, [applyLoadedProject]);
|
||||
|
||||
// Track whether user preferences have been loaded to avoid
|
||||
// overwriting saved prefs with defaults on the first render
|
||||
const [prefsHydrated, setPrefsHydrated] = useState(false);
|
||||
|
||||
// Load persisted user preferences on mount (intentionally runs once)
|
||||
useEffect(() => {
|
||||
const prefs = loadUserPreferences();
|
||||
updateState({
|
||||
padding: prefs.padding,
|
||||
aspectRatio: prefs.aspectRatio,
|
||||
});
|
||||
setExportQuality(prefs.exportQuality);
|
||||
setExportFormat(prefs.exportFormat);
|
||||
setPrefsHydrated(true);
|
||||
}, [updateState]);
|
||||
|
||||
// Auto-save user preferences when settings change
|
||||
useEffect(() => {
|
||||
if (!prefsHydrated) return;
|
||||
saveUserPreferences({ padding, aspectRatio, exportQuality, exportFormat });
|
||||
}, [prefsHydrated, padding, aspectRatio, exportQuality, exportFormat]);
|
||||
|
||||
const saveProject = useCallback(
|
||||
async (forceSaveAs: boolean) => {
|
||||
if (!videoPath) {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import type { ExportFormat, ExportQuality } from "@/lib/exporter";
|
||||
import type { AspectRatio } from "@/utils/aspectRatioUtils";
|
||||
|
||||
const PREFS_KEY = "openscreen_user_preferences";
|
||||
|
||||
const VALID_ASPECT_RATIOS: readonly string[] = [
|
||||
"16:9",
|
||||
"9:16",
|
||||
"1:1",
|
||||
"4:3",
|
||||
"4:5",
|
||||
"16:10",
|
||||
"10:16",
|
||||
"native",
|
||||
];
|
||||
|
||||
export interface UserPreferences {
|
||||
/** Default padding % */
|
||||
padding: number;
|
||||
/** Default aspect ratio */
|
||||
aspectRatio: AspectRatio;
|
||||
/** Default export quality */
|
||||
exportQuality: ExportQuality;
|
||||
/** Default export format */
|
||||
exportFormat: ExportFormat;
|
||||
}
|
||||
|
||||
const DEFAULT_PREFS: UserPreferences = {
|
||||
padding: 50,
|
||||
aspectRatio: "16:9",
|
||||
exportQuality: "good",
|
||||
exportFormat: "mp4",
|
||||
};
|
||||
|
||||
function safeJsonParse(text: string | null): Record<string, unknown> | null {
|
||||
if (!text) return null;
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load persisted user preferences from localStorage.
|
||||
* Returns defaults for any missing or invalid fields.
|
||||
*/
|
||||
export function loadUserPreferences(): UserPreferences {
|
||||
let raw: Record<string, unknown> | null = null;
|
||||
try {
|
||||
raw = safeJsonParse(localStorage.getItem(PREFS_KEY));
|
||||
} catch {
|
||||
return { ...DEFAULT_PREFS };
|
||||
}
|
||||
if (!raw || typeof raw !== "object") return { ...DEFAULT_PREFS };
|
||||
|
||||
return {
|
||||
padding:
|
||||
typeof raw.padding === "number" &&
|
||||
Number.isFinite(raw.padding) &&
|
||||
raw.padding >= 0 &&
|
||||
raw.padding <= 100
|
||||
? raw.padding
|
||||
: DEFAULT_PREFS.padding,
|
||||
aspectRatio:
|
||||
typeof raw.aspectRatio === "string" && VALID_ASPECT_RATIOS.includes(raw.aspectRatio)
|
||||
? (raw.aspectRatio as AspectRatio)
|
||||
: DEFAULT_PREFS.aspectRatio,
|
||||
exportQuality:
|
||||
raw.exportQuality === "medium" ||
|
||||
raw.exportQuality === "good" ||
|
||||
raw.exportQuality === "source"
|
||||
? (raw.exportQuality as ExportQuality)
|
||||
: DEFAULT_PREFS.exportQuality,
|
||||
exportFormat:
|
||||
raw.exportFormat === "gif" || raw.exportFormat === "mp4"
|
||||
? (raw.exportFormat as ExportFormat)
|
||||
: DEFAULT_PREFS.exportFormat,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist user preferences to localStorage.
|
||||
* Only the explicitly provided fields are updated.
|
||||
*/
|
||||
export function saveUserPreferences(partial: Partial<UserPreferences>): void {
|
||||
const current = loadUserPreferences();
|
||||
const merged = { ...current, ...partial };
|
||||
try {
|
||||
localStorage.setItem(PREFS_KEY, JSON.stringify(merged));
|
||||
} catch {
|
||||
// localStorage may be unavailable (e.g. private browsing quota exceeded)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user