Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ vite.config.d.ts
# Native capture build artifacts
electron/native/wgc-capture/build/
electron/native/cursor-monitor/build/

# Local build tools and caches
.cache/
.cmake_ext/
ebcache/
13 changes: 9 additions & 4 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,28 @@ interface Window {
canceled?: boolean;
error?: string;
}>;
getWhisperSmallModelStatus: () => Promise<{
getWhisperModelStatus: (
modelName: string,
) => Promise<{
success: boolean;
exists: boolean;
path?: string | null;
error?: string;
}>;
downloadWhisperSmallModel: () => Promise<{
downloadWhisperModel: (
modelName: string,
) => Promise<{
success: boolean;
path?: string;
alreadyDownloaded?: boolean;
error?: string;
}>;
deleteWhisperSmallModel: () => Promise<{ success: boolean; error?: string }>;
onWhisperSmallModelDownloadProgress: (
deleteWhisperModel: (modelName: string) => Promise<{ success: boolean; error?: string }>;
onWhisperModelDownloadProgress: (
callback: (state: {
status: "idle" | "downloading" | "downloaded" | "error";
progress: number;
model: string;
path?: string | null;
error?: string;
}) => void,
Expand Down
206 changes: 134 additions & 72 deletions electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,48 @@ const AUTO_RECORDING_RETENTION_COUNT = 20
const AUTO_RECORDING_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000
const ALLOW_RECORDLY_WINDOW_CAPTURE = Boolean(process.env['VITE_DEV_SERVER_URL'])
const RECORDING_SESSION_MANIFEST_SUFFIX = '.recordly-session.json'
const WHISPER_MODEL_DOWNLOAD_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin'
const WHISPER_MODEL_DIR = path.join(app.getPath('userData'), 'whisper')
const WHISPER_SMALL_MODEL_PATH = path.join(WHISPER_MODEL_DIR, 'ggml-small.bin')
const WHISPER_MODEL_DIR = path.join(app.getPath("userData"), "whisper");

const WHISPER_MODELS = {
tiny: {
label: "Tiny",
size: "75 MB",
filename: "ggml-tiny.bin",
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin",
},
base: {
label: "Base",
size: "142 MB",
filename: "ggml-base.bin",
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin",
},
small: {
label: "Small",
size: "466 MB",
filename: "ggml-small.bin",
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin",
},
medium: {
label: "Medium",
size: "1.5 GB",
filename: "ggml-medium.bin",
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin",
},
large: {
label: "Large (v3)",
size: "2.9 GB",
filename: "ggml-large-v3.bin",
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin",
},
} as const;

function getWhisperModelPath(modelName: string) {
const model = WHISPER_MODELS[modelName as keyof typeof WHISPER_MODELS];
if (!model) {
throw new Error(`Unsupported Whisper model: ${modelName}`);
}
return path.join(WHISPER_MODEL_DIR, model.filename);
}

function getAssetRootPath() {
if (app.isPackaged) {
Expand Down Expand Up @@ -961,27 +1000,34 @@ function getFfmpegBinaryPath() {
}

function sendWhisperModelDownloadProgress(
webContents: Electron.WebContents,
payload: { status: 'idle' | 'downloading' | 'downloaded' | 'error'; progress: number; path?: string | null; error?: string },
webContents: Electron.WebContents,
payload: {
status: "idle" | "downloading" | "downloaded" | "error";
progress: number;
model: string;
path?: string | null;
error?: string;
},
) {
webContents.send('whisper-small-model-download-progress', payload)
}

async function getWhisperSmallModelStatus() {
try {
await fs.access(WHISPER_SMALL_MODEL_PATH, fsConstants.R_OK)
return {
success: true,
exists: true,
path: WHISPER_SMALL_MODEL_PATH,
}
} catch {
return {
success: true,
exists: false,
path: null,
}
}
webContents.send("whisper-model-download-progress", payload);
}
Comment on lines +1043 to +1048
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's see the full context around the flagged lines
cd electron/ipc && wc -l handlers.ts

Repository: webadderall/Recordly

Length of output: 80


🏁 Script executed:

# Read the function at lines 1039-1050 and surrounding context
sed -n '1030,1060p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 687


🏁 Script executed:

# Read the second location at lines 1566-1580 and surrounding context
sed -n '1560,1590p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 1251


🏁 Script executed:

# Search for other webContents.send calls in the handlers file to understand the pattern
rg "webContents\.send" electron/ipc/handlers.ts -n -A 2 -B 2

Repository: webadderall/Recordly

Length of output: 2496


🏁 Script executed:

# Check if there are any existing guard patterns for webContents in the codebase
rg "isDestroyed|\.destroyed" electron/ipc/ -n

Repository: webadderall/Recordly

Length of output: 610


🏁 Script executed:

# Look for any event listener cleanup patterns when dealing with progress/streams
rg "removeListener|off\(" electron/ipc/handlers.ts -n -B 2 -A 2

Repository: webadderall/Recordly

Length of output: 1557


🏁 Script executed:

# Find calls to sendWhisperModelDownloadProgress
rg "sendWhisperModelDownloadProgress" electron/ipc/handlers.ts -n -B 3 -A 3

Repository: webadderall/Recordly

Length of output: 1813


🏁 Script executed:

# Find the downloadWhisperModel function and see how it uses the progress callback
rg "downloadWhisperModel|downloadFileWithProgress" electron/ipc/handlers.ts -n -B 2 -A 5 | head -80

Repository: webadderall/Recordly

Length of output: 1180


🏁 Script executed:

# Find where generateAutoCaptionsFromVideo is called from
rg "generateAutoCaptionsFromVideo" electron/ipc/handlers.ts -n -B 2 -A 2

Repository: webadderall/Recordly

Length of output: 351


🏁 Script executed:

# Look at the actual downloadFileWithProgress to understand the callback pattern
rg "function downloadFileWithProgress|const downloadFileWithProgress" electron/ipc/ -n -A 15

Repository: webadderall/Recordly

Length of output: 1197


🏁 Script executed:

# Check what happens when send is called - look for any try/catch around webContents.send in downloadFileWithProgress context
sed -n '1070,1130p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 1948


🏁 Script executed:

# Check what happens in downloadWhisperModel around the callback
sed -n '1134,1185p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 1383


🏁 Script executed:

# Check IPC handler wrapper - how is downloadWhisperModel called and what happens on error
sed -n '4108,4130p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 824


🏁 Script executed:

# Check if there's any existing error handling or try-catch when using webContents that was destroyed
rg "try.*webContents|webContents.*catch" electron/ipc/handlers.ts -A 3 -B 3

Repository: webadderall/Recordly

Length of output: 46


🏁 Script executed:

# Check generateAutoCaptionsFromVideo and its progress callbacks around lines 1566-1580
sed -n '1514,1590p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 2957


🏁 Script executed:

# Look for how generateAutoCaptionsFromVideo is called and what webContents is
sed -n '4150,4170p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 757


🏁 Script executed:

# Search Electron documentation behavior - verify what happens when webContents.send is called on destroyed context
web_search
Electron WebContents.send() destroyed error behavior

Repository: webadderall/Recordly

Length of output: 252


🌐 Web query:

Electron WebContents.send() on destroyed window throws error or fails silently

💡 Result:

Yes, calling webContents.send on a destroyed WebContents throws an "Object has been destroyed" error. This is a well-documented behavior in Electron across multiple versions. To prevent the error, always check webContents.isDestroyed before calling send: if (!win.webContents.isDestroyed) { win.webContents.send('channel', data); } This check is explicitly recommended in the official Electron documentation, which provides the isDestroyed method to determine if the web page is destroyed. Multiple GitHub issues and Stack Overflow reports confirm that attempting to send IPC messages after destruction (e.g., after window close) triggers this error, and the standard solution is the isDestroyed check. The official docs do not indicate that send fails silently; it throws an error, though some related serialization errors may log to console without throwing in specific cases (unrelated to destruction).

Citations:


Guard webContents.send() calls in progress callbacks to prevent main-process crashes.

These functions call webContents.send() from asynchronous progress callbacks without checking if the WebContents is destroyed. If the user closes the window during a long download or caption operation, Electron will throw "Object has been destroyed" when the callback fires, crashing the main process if the exception is unhandled. Add !webContents.isDestroyed() checks before each send call. The codebase already uses this pattern elsewhere (see lines 90, 1891, 2165, 2209); apply the same guard here.

Locations:

  • Line 1049: sendWhisperModelDownloadProgress() helper function
  • Lines 1157, 1165, 1174: progress callbacks in downloadWhisperModel()
  • Lines 1567, 1579: progress callbacks in generateAutoCaptionsFromVideo()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/handlers.ts` around lines 1039 - 1050, The
sendWhisperModelDownloadProgress helper and the progress callbacks that call
webContents.send must be guarded to avoid "Object has been destroyed" crashes:
wrap each webContents.send(...) invocation in a check that
webContents.isDestroyed() is false (or early-return if destroyed). Specifically,
add the guard inside sendWhisperModelDownloadProgress (the function that sends
"whisper-model-download-progress") and also apply the same check before calling
send in the progress callbacks inside downloadWhisperModel and
generateAutoCaptionsFromVideo so they no-op when the target WebContents has been
destroyed.


async function getWhisperModelStatus(_event: any, modelName: string) {
try {
const modelPath = getWhisperModelPath(modelName);
await fs.access(modelPath, fsConstants.R_OK);
return {
success: true,
exists: true,
path: modelPath,
};
} catch {
return {
success: true,
exists: false,
path: null,
};
}
Comment on lines +1050 to +1065
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don't swallow unsupported models as “not downloaded.”

This helper currently converts every failure into { success: true, exists: false }, including unknown model names from getWhisperModelPath(...) and non-missing filesystem errors. That makes corrupted preferences or bad IPC input look like a missing file and leaves the renderer in a misleading idle state.

🧰 Tools
🪛 Biome (2.4.9)

[error] 1052-1052: Unexpected any. Specify a different type.

(lint/suspicious/noExplicitAny)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/handlers.ts` around lines 1052 - 1067, getWhisperModelStatus is
swallowing all failures as "exists: false"; change it so errors from
getWhisperModelPath are treated as invalid/unsupported input (return success:
false and surface the error), while filesystem failures are distinguished: if
fs.access throws ENOENT return { success: true, exists: false, path: null },
otherwise return { success: false, error: <error message> }. Update the
try/catch to call getWhisperModelPath outside the fs.access try, catch and
inspect error.code on fs.access (use fsConstants.R_OK) and include identifying
symbols getWhisperModelStatus, getWhisperModelPath, fs.access, and
fsConstants.R_OK when locating the code to implement these branches.

}

function downloadFileWithProgress(
Expand Down Expand Up @@ -1048,46 +1094,60 @@ function downloadFileWithProgress(
return request(url)
}

async function downloadWhisperSmallModel(webContents: Electron.WebContents) {
await fs.mkdir(WHISPER_MODEL_DIR, { recursive: true })
const tempPath = `${WHISPER_SMALL_MODEL_PATH}.download`

sendWhisperModelDownloadProgress(webContents, {
status: 'downloading',
progress: 0,
path: null,
})

try {
await fs.rm(tempPath, { force: true })
await downloadFileWithProgress(WHISPER_MODEL_DOWNLOAD_URL, tempPath, (progress) => {
sendWhisperModelDownloadProgress(webContents, {
status: 'downloading',
progress,
path: null,
})
})
await fs.rename(tempPath, WHISPER_SMALL_MODEL_PATH)
sendWhisperModelDownloadProgress(webContents, {
status: 'downloaded',
progress: 100,
path: WHISPER_SMALL_MODEL_PATH,
})
return WHISPER_SMALL_MODEL_PATH
} catch (error) {
await fs.rm(tempPath, { force: true }).catch(() => undefined)
sendWhisperModelDownloadProgress(webContents, {
status: 'error',
progress: 0,
path: null,
error: String(error),
})
throw error
}
}

async function deleteWhisperSmallModel() {
await fs.rm(WHISPER_SMALL_MODEL_PATH, { force: true })
async function downloadWhisperModel(
webContents: Electron.WebContents,
modelName: string,
) {
const model = WHISPER_MODELS[modelName as keyof typeof WHISPER_MODELS];
if (!model) {
throw new Error(`Unsupported Whisper model: ${modelName}`);
}

await fs.mkdir(WHISPER_MODEL_DIR, { recursive: true });
const modelPath = getWhisperModelPath(modelName);
const tempPath = `${modelPath}.download`;

sendWhisperModelDownloadProgress(webContents, {
status: "downloading",
progress: 0,
model: modelName,
path: null,
});

try {
await fs.rm(tempPath, { force: true });
await downloadFileWithProgress(model.url, tempPath, (progress) => {
sendWhisperModelDownloadProgress(webContents, {
status: "downloading",
progress,
model: modelName,
path: null,
});
});
await fs.rename(tempPath, modelPath);
sendWhisperModelDownloadProgress(webContents, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate the file and examine the context around lines 1154-1165
fd handlers.ts | head -5

Repository: webadderall/Recordly

Length of output: 152


🏁 Script executed:

# Read the relevant section of the file
wc -l electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 93


🏁 Script executed:

# Get the context around the suspicious code
sed -n '1140,1180p' electron/ipc/handlers.ts | cat -n

Repository: webadderall/Recordly

Length of output: 1396


🏁 Script executed:

# Find the downloadFileWithProgress function definition
rg -n "downloadFileWithProgress" --type ts

Repository: webadderall/Recordly

Length of output: 228


🏁 Script executed:

# Get the downloadFileWithProgress function starting at line 1070
sed -n '1070,1120p' electron/ipc/handlers.ts | cat -n

Repository: webadderall/Recordly

Length of output: 2187


🏁 Script executed:

# Get the rest of the function
sed -n '1120,1135p' electron/ipc/handlers.ts | cat -n

Repository: webadderall/Recordly

Length of output: 417


Wait for the file handle to close before renaming.

The downloadFileWithProgress function resolves on the 'finish' event of the write stream (line 1050), but this does not guarantee the underlying file descriptor is closed. On Windows, NTFS file locking prevents rename operations on files with open handles, causing fs.rename to fail with EPERM or EBUSY errors, especially for large files. The fix is to wait for the 'close' event in addition to 'finish', or restructure the stream handling to ensure the handle is fully released before the promise resolves.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/handlers.ts` around lines 1154 - 1165, The caller is renaming
tempPath to modelPath immediately after downloadFileWithProgress resolves, but
that function currently resolves on the write stream's 'finish' which doesn't
guarantee the file handle is closed on Windows; update the code so
downloadFileWithProgress only resolves when the write stream emits 'close' (or
await a promise that listens for both 'finish' and 'close'), or alternatively
add explicit waiting here after downloadFileWithProgress for the stream to close
before calling fs.rename; change the downloadFileWithProgress implementation (or
its return) to ensure the file descriptor is released, then proceed with
fs.rename(tempPath, modelPath) and continue using
sendWhisperModelDownloadProgress as before.

status: "downloaded",
progress: 100,
model: modelName,
path: modelPath,
});
return modelPath;
} catch (error) {
await fs.rm(tempPath, { force: true }).catch(() => undefined);
sendWhisperModelDownloadProgress(webContents, {
status: "error",
progress: 0,
model: modelName,
path: null,
error: String(error),
});
throw error;
}
}

async function deleteWhisperModel(_event: any, modelName: string) {
const modelPath = getWhisperModelPath(modelName);
await fs.rm(modelPath, { force: true });
}

function parseSrtTimestamp(value: string) {
Expand Down Expand Up @@ -3975,45 +4035,47 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
}
})

ipcMain.handle('get-whisper-small-model-status', async () => {
ipcMain.handle('get-whisper-model-status', async (event, modelName: string) => {
try {
return await getWhisperSmallModelStatus()
return await getWhisperModelStatus(event, modelName)
} catch (error) {
return { success: false, exists: false, path: null, error: String(error) }
}
})

ipcMain.handle('download-whisper-small-model', async (event) => {
ipcMain.handle('download-whisper-model', async (event, modelName: string) => {
try {
const existing = await getWhisperSmallModelStatus()
const existing = await getWhisperModelStatus(event, modelName)
if (existing.exists) {
sendWhisperModelDownloadProgress(event.sender, {
status: 'downloaded',
progress: 100,
model: modelName,
path: existing.path,
})
return { success: true, path: existing.path, alreadyDownloaded: true }
}

const modelPath = await downloadWhisperSmallModel(event.sender)
const modelPath = await downloadWhisperModel(event.sender, modelName)
return { success: true, path: modelPath }
} catch (error) {
console.error('Failed to download Whisper small model:', error)
console.error(`Failed to download Whisper model ${modelName}:`, error)
return { success: false, error: String(error) }
}
})

ipcMain.handle('delete-whisper-small-model', async (event) => {
ipcMain.handle('delete-whisper-model', async (event, modelName: string) => {
try {
await deleteWhisperSmallModel()
await deleteWhisperModel(event, modelName)
sendWhisperModelDownloadProgress(event.sender, {
status: 'idle',
progress: 0,
model: modelName,
path: null,
})
Comment on lines +4168 to 4176
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 'delete-whisper-model'
rg -n -C2 'generate-auto-captions|whisperModelPath'

Repository: webadderall/Recordly

Length of output: 13489


🏁 Script executed:

# Find the deleteWhisperModel function implementation
rg -n 'deleteWhisperModel' electron/ipc/handlers.ts -A 20 | head -60

# Find whisper model resolution logic
rg -n 'resolveWhisperModel|Unsupported Whisper model' 

# Check if there's model name to path resolution
rg -n 'whisperSelectedModel|selectedModel' electron/ipc/handlers.ts | head -20

Repository: webadderall/Recordly

Length of output: 1597


🏁 Script executed:

# Find getWhisperModelPath implementation
rg -n 'getWhisperModelPath' -A 30

# Check the error context at lines 71 and 1140
sed -n '65,80p' electron/ipc/handlers.ts
sed -n '1135,1150p' electron/ipc/handlers.ts

# Find selectedModel usage to understand model selection flow
rg -n 'whisperSelectedModel' electron/ipc/handlers.ts -B 3 -A 3

Repository: webadderall/Recordly

Length of output: 7745


🏁 Script executed:

# View the full generate-auto-captions handler starting at line 4213
sed -n '4213,4280p' electron/ipc/handlers.ts

Repository: webadderall/Recordly

Length of output: 2603


🏁 Script executed:

# Find generateAutoCaptionsFromVideo function implementation
rg -n 'generateAutoCaptionsFromVideo' -A 50 | head -80

Repository: webadderall/Recordly

Length of output: 5601


🏁 Script executed:

# Check how the renderer calls these IPC handlers
rg -n 'generateAutoCaptions|deleteWhisperModel' src/components/video-editor/ -B 2 -A 2 | head -100

# Also check what happens when whisperModelPath is null/undefined
rg -n 'whisperModelPath.*null|whisperModelPath.*undefined' src/components/video-editor/VideoEditor.tsx -B 2 -A 2

Repository: webadderall/Recordly

Length of output: 1566


🏁 Script executed:

# View the full generateAutoCaptions call context
sed -n '1605,1620p' src/components/video-editor/VideoEditor.tsx

# Check if there are other places calling delete-whisper-model
rg -n 'deleteWhisperModel' src/components/ -B 3 -A 3

# Check what autoCaptionSettings.selectedModel is and if it could be undefined
rg -n 'autoCaptionSettings.*selectedModel|selectedModel.*=' src/components/video-editor/VideoEditor.tsx -B 2 -A 2 | head -40

Repository: webadderall/Recordly

Length of output: 2285


🏁 Script executed:

# Check the autoCaptionSettings structure and initialization
rg -n 'autoCaptionSettings.*=' src/components/video-editor/VideoEditor.tsx -B 1 -A 1 | head -40

# Check the default value for selectedModel
rg -n 'selectedModel.*=' src/components/video-editor/ -B 2 -A 2 | head -50

# Look at the flow when model download completes - line 1425
sed -n '1415,1445p' src/components/video-editor/VideoEditor.tsx

Repository: webadderall/Recordly

Length of output: 3335


Unify model identity across the IPC boundary.

delete-whisper-model validates model identity against a whitelist (via modelName parameter), but generate-auto-captions accepts an arbitrary file path with no model validation. This split leaves the main process unable to enforce model selection: the renderer's whisperModelPath state can drift from the actual selected model, allowing transcription with stale or deleted .bin files. Redesign both handlers to accept a single selectedModel input (name-based), resolve built-in model paths in main, and add a separate explicit branch for custom user-provided files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@electron/ipc/handlers.ts` around lines 4197 - 4205, The two IPC handlers are
inconsistent: 'delete-whisper-model' takes a modelName and validates it, while
'generate-auto-captions' accepts an arbitrary whisperModelPath, allowing the
renderer to reference stale or deleted files; unify them by changing both IPC
handlers ('delete-whisper-model' and 'generate-auto-captions') to accept a
single selectedModel parameter (name-based) and an optional flag or separate
field for customFilePath for user-provided files, resolve built-in model paths
in the main process (use the same whitelist/lookup used by deleteWhisperModel),
validate selectedModel against the allowed list in main, and implement an
explicit branch in generateAutoCaptions that, if customFilePath is provided,
uses it after validating existence/permissions, otherwise resolves the built-in
path from selectedModel before proceeding; update calls to deleteWhisperModel
and sendWhisperModelDownloadProgress to use the unified selectedModel identity.

return { success: true }
} catch (error) {
console.error('Failed to delete Whisper small model:', error)
console.error(`Failed to delete Whisper model ${modelName}:`, error)
return { success: false, error: String(error) }
}
})
Expand Down
Binary file added electron/native/bin/win32-x64/whisper-bench.exe
Binary file not shown.
Binary file added electron/native/bin/win32-x64/whisper-cli.exe
Binary file not shown.
Binary file not shown.
6 changes: 6 additions & 0 deletions electron/native/bin/win32-x64/whisper-runtime.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": "v1.8.4",
"platform": "win32",
"arch": "x64",
"binary": "whisper-cli.exe"
}
Binary file added electron/native/bin/win32-x64/whisper-server.exe
Binary file not shown.
Binary file not shown.
40 changes: 26 additions & 14 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,36 @@ contextBridge.exposeInMainWorld("electronAPI", {
openWhisperModelPicker: () => {
return ipcRenderer.invoke("open-whisper-model-picker");
},
getWhisperSmallModelStatus: () => {
return ipcRenderer.invoke("get-whisper-small-model-status");
},
downloadWhisperSmallModel: () => {
return ipcRenderer.invoke("download-whisper-small-model");
},
deleteWhisperSmallModel: () => {
return ipcRenderer.invoke("delete-whisper-small-model");
},
onWhisperSmallModelDownloadProgress: (
callback: (state: { status: "idle" | "downloading" | "downloaded" | "error"; progress: number; path?: string | null; error?: string }) => void,
getWhisperModelStatus: (modelName: string) => {
return ipcRenderer.invoke("get-whisper-model-status", modelName);
},
downloadWhisperModel: (modelName: string) => {
return ipcRenderer.invoke("download-whisper-model", modelName);
},
deleteWhisperModel: (modelName: string) => {
return ipcRenderer.invoke("delete-whisper-model", modelName);
},
onWhisperModelDownloadProgress: (
callback: (state: {
status: "idle" | "downloading" | "downloaded" | "error";
progress: number;
model: string;
path?: string | null;
error?: string;
}) => void,
) => {
const listener = (
_event: Electron.IpcRendererEvent,
payload: { status: "idle" | "downloading" | "downloaded" | "error"; progress: number; path?: string | null; error?: string },
payload: {
status: "idle" | "downloading" | "downloaded" | "error";
progress: number;
model: string;
path?: string | null;
error?: string;
},
) => callback(payload);
ipcRenderer.on("whisper-small-model-download-progress", listener);
return () => ipcRenderer.removeListener("whisper-small-model-download-progress", listener);
ipcRenderer.on("whisper-model-download-progress", listener);
return () => ipcRenderer.removeListener("whisper-model-download-progress", listener);
},
generateAutoCaptions: (options: {
videoPath: string;
Expand Down
6 changes: 6 additions & 0 deletions scripts/build-cursor-monitor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ function findCmake() {
// not on PATH
}

// Local .cmake_ext path
const localCmake = path.join(projectRoot, ".cmake_ext", "cmake-4.3.0-windows-x86_64", "bin", "cmake.exe");
if (existsSync(localCmake)) {
return `"${localCmake}"`;
}

// VS 2022 bundled CMake
const vsEditions = ["Community", "Professional", "Enterprise", "BuildTools"];
for (const edition of vsEditions) {
Expand Down
Loading