Skip to content
Merged
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Screen Studio is an awesome product and this is definitely not a 1:1 clone. Open
- Record a specific window, region, or your whole screen.
- Record microphone and system audio.
- Webcam overlay with picture-in-picture, drag-to-position, and shape options.
- **Webcam Focus** — draw attention to your face at key moments.
- Captions, markers, and image annotations.
- Auto or manual zooms with adjustable depth, duration, easing, and pixel-precise position.
- Wallpapers, solid colors, gradients, or a custom background.
- Motion blur for smoother pan and zoom transitions.
Expand All @@ -47,6 +49,19 @@ Screen Studio is an awesome product and this is definitely not a 1:1 clone. Open
- Export to MP4 or GIF in multiple aspect ratios and resolutions.
- Translated into Arabic, English, Spanish, French, Japanese, Korean, Russian, Turkish, Vietnamese, Simplified Chinese, and Traditional Chinese.

### Webcam Focus

When you record with a webcam, you can mark specific time ranges on the timeline where the webcam should take center stage. During those segments the screen recording blurs and dims while the webcam expands to fill most of the frame. Outside the region everything returns to the normal layout. Both transitions animate smoothly.

**How to use it:**
1. Make sure your recording includes a webcam feed.
2. In the editor, click the **camera icon** (🎥) in the timeline toolbar to place a Webcam Focus region at the current playhead position.
3. Drag the edges of the indigo region to set the start and end times.
4. Press **Play** to preview — the webcam enlarges to portrait near-full-screen and the screen recording fades behind it.
5. Delete a region by selecting it and pressing `Delete` / `Backspace`.

The effect is saved with your project and included in the exported video.

## Installation

Download the latest installer for your platform from the [GitHub Releases](https://github.com/siddharthvaddem/openscreen/releases) page.
Expand Down
8 changes: 8 additions & 0 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,14 @@ interface Window {
projectState: unknown;
logs: string[];
}) => Promise<{ success: boolean; path?: string; canceled?: boolean; error?: string }>;
generateSubtitles: (
videoPath: string,
lang?: string,
) => Promise<{
success: boolean;
subtitles?: Array<{ id: string; startMs: number; endMs: number; text: string }>;
error?: string;
}>;
};
}

Expand Down
63 changes: 62 additions & 1 deletion electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import { type ChildProcessWithoutNullStreams, execFile, spawn } from "node:child_process";
import { EventEmitter } from "node:events";
import { constants as fsConstants } from "node:fs";
import fs from "node:fs/promises";
Expand Down Expand Up @@ -2867,6 +2867,67 @@ export function registerIpcHandlers(
},
);

ipcMain.handle("generate-subtitles", async (_, videoPath: string, lang = "pt") => {
const scriptPath = path.join(app.getAppPath(), "scripts", "extract-subtitles.mjs");
try {
await fs.access(scriptPath);
} catch {
return { success: false, error: "extract-subtitles.mjs script not found" };
}

const nodeBin =
process.env.NODE_BINARY ||
(process.platform === "win32" ? "node.exe" : "node");

return new Promise<{
success: boolean;
subtitles?: Array<{ id: string; startMs: number; endMs: number; text: string }>;
error?: string;
}>((resolve) => {
const child = execFile(
nodeBin,
[scriptPath, videoPath, "--json", "--lang", lang],
{
maxBuffer: 10 * 1024 * 1024,
timeout: 300_000,
cwd: app.getAppPath(),
},
(error, stdout, stderr) => {
if (error) {
console.error("Subtitle generation error:", stderr || error.message);
resolve({ success: false, error: error.message });
return;
}
try {
const jsonStart = stdout.indexOf("{");
const jsonEnd = stdout.lastIndexOf("}");
if (jsonStart === -1 || jsonEnd === -1) {
resolve({ success: false, error: "No JSON output from script" });
return;
}
const config = JSON.parse(stdout.slice(jsonStart, jsonEnd + 1));
const items = config?.subtitles?.items ?? [];
const FPS = 30;
const subtitles = items.map(
(item: { text: string; startFrame: number; endFrame: number }, idx: number) => ({
id: `sub-${idx + 1}`,
startMs: Math.round((item.startFrame / FPS) * 1000),
endMs: Math.round((item.endFrame / FPS) * 1000),
text: item.text,
}),
);
resolve({ success: true, subtitles });
} catch (parseError) {
resolve({ success: false, error: `Failed to parse output: ${String(parseError)}` });
}
},
);
child.stderr?.on("data", (data) => {
console.log("[subtitle-gen]", String(data).trim());
});
});
});

registerNativeBridgeHandlers({
getPlatform: () => process.platform,
getCurrentProjectPath: () => currentProjectPath,
Expand Down
3 changes: 3 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
}) => {
return ipcRenderer.invoke("save-diagnostic", payload);
},
generateSubtitles: (videoPath: string, lang?: string) => {
return ipcRenderer.invoke("generate-subtitles", videoPath, lang ?? "pt");
},
setMicrophoneExpanded: (expanded: boolean) => {
ipcRenderer.send("hud:setMicrophoneExpanded", expanded);
},
Expand Down
Loading
Loading