mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 23:05:49 +00:00
feat(electron): persist recording session offsets and model checks
This commit is contained in:
Vendored
+8
-1
@@ -153,6 +153,12 @@ interface Window {
|
||||
path?: string | null;
|
||||
error?: string;
|
||||
}>;
|
||||
getWhisperModelPathStatus: (modelPath?: string | null) => Promise<{
|
||||
success: boolean;
|
||||
exists: boolean;
|
||||
path?: string | null;
|
||||
error?: string;
|
||||
}>;
|
||||
downloadWhisperSmallModel: () => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
@@ -183,10 +189,11 @@ interface Window {
|
||||
setCurrentRecordingSession: (session: {
|
||||
videoPath: string;
|
||||
webcamPath?: string | null;
|
||||
timeOffsetMs?: number;
|
||||
}) => Promise<{ success: boolean }>;
|
||||
getCurrentRecordingSession: () => Promise<{
|
||||
success: boolean;
|
||||
session?: { videoPath: string; webcamPath?: string | null };
|
||||
session?: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number };
|
||||
}>;
|
||||
getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }>;
|
||||
clearCurrentVideoPath: () => Promise<{ success: boolean }>;
|
||||
|
||||
@@ -81,12 +81,14 @@ type WindowBounds = {
|
||||
type RecordingSessionData = {
|
||||
videoPath: string
|
||||
webcamPath?: string | null
|
||||
timeOffsetMs?: number
|
||||
}
|
||||
|
||||
type RecordingSessionManifest = {
|
||||
version: 1
|
||||
version: 1 | 2
|
||||
videoFileName: string
|
||||
webcamFileName?: string | null
|
||||
timeOffsetMs?: number
|
||||
}
|
||||
|
||||
type ProjectLibraryEntry = {
|
||||
@@ -384,6 +386,7 @@ async function loadProjectFromPath(projectPath: string) {
|
||||
currentRecordingSession = {
|
||||
videoPath: mediaSources.videoPath,
|
||||
webcamPath: mediaSources.webcamPath,
|
||||
timeOffsetMs: mediaSources.timeOffsetMs,
|
||||
}
|
||||
await rememberRecentProject(normalizedPath)
|
||||
|
||||
@@ -420,6 +423,7 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
success: true
|
||||
videoPath: string
|
||||
webcamPath: string | null
|
||||
timeOffsetMs: number
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
@@ -453,6 +457,9 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
typeof (project as { editor?: { webcam?: { sourcePath?: unknown } } }).editor?.webcam?.sourcePath === 'string'
|
||||
? ((project as { editor?: { webcam?: { sourcePath?: string } } }).editor?.webcam?.sourcePath ?? null)
|
||||
: null
|
||||
const timeOffsetMs = normalizeRecordingTimeOffsetMs(
|
||||
(project as { editor?: { webcam?: { timeOffsetMs?: unknown } } }).editor?.webcam?.timeOffsetMs,
|
||||
)
|
||||
const normalizedWebcamPath = normalizeVideoSourcePath(rawWebcamPath)
|
||||
|
||||
if (!normalizedWebcamPath) {
|
||||
@@ -460,6 +467,7 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
success: true,
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: null,
|
||||
timeOffsetMs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,16 +477,24 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
success: true,
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: normalizedWebcamPath,
|
||||
timeOffsetMs,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
success: true,
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: null,
|
||||
timeOffsetMs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRecordingTimeOffsetMs(value: unknown): number {
|
||||
return typeof value === 'number' && Number.isFinite(value)
|
||||
? Math.round(value)
|
||||
: 0
|
||||
}
|
||||
|
||||
function getRecordingSessionManifestPath(videoPath: string) {
|
||||
const extension = path.extname(videoPath)
|
||||
const baseName = path.basename(videoPath, extension)
|
||||
@@ -500,9 +516,10 @@ async function persistRecordingSessionManifest(session: RecordingSessionData): P
|
||||
}
|
||||
|
||||
const manifest: RecordingSessionManifest = {
|
||||
version: 1,
|
||||
version: 2,
|
||||
videoFileName: path.basename(normalizedVideoPath),
|
||||
webcamFileName: path.basename(normalizedWebcamPath),
|
||||
timeOffsetMs: normalizeRecordingTimeOffsetMs(session.timeOffsetMs),
|
||||
}
|
||||
|
||||
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8')
|
||||
@@ -519,7 +536,7 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi
|
||||
try {
|
||||
const content = await fs.readFile(manifestPath, 'utf-8')
|
||||
const parsed = JSON.parse(content) as Partial<RecordingSessionManifest>
|
||||
if (parsed.version !== 1) {
|
||||
if (parsed.version !== 1 && parsed.version !== 2) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -531,6 +548,7 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi
|
||||
return {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: null,
|
||||
timeOffsetMs: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,6 +558,7 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi
|
||||
return {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath,
|
||||
timeOffsetMs: normalizeRecordingTimeOffsetMs(parsed.timeOffsetMs),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
@@ -594,6 +613,7 @@ async function resolveRecordingSession(videoPath?: string | null): Promise<Recor
|
||||
return {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: linkedWebcamPath,
|
||||
timeOffsetMs: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,18 +968,33 @@ function sendWhisperModelDownloadProgress(
|
||||
}
|
||||
|
||||
async function getWhisperSmallModelStatus() {
|
||||
return getWhisperModelPathStatus(WHISPER_SMALL_MODEL_PATH)
|
||||
}
|
||||
|
||||
async function getWhisperModelPathStatus(modelPath?: string | null) {
|
||||
const requestedPath = typeof modelPath === 'string' ? modelPath.trim() : ''
|
||||
if (!requestedPath) {
|
||||
return {
|
||||
success: true,
|
||||
exists: false,
|
||||
path: null,
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedPath = path.resolve(requestedPath)
|
||||
|
||||
try {
|
||||
await fs.access(WHISPER_SMALL_MODEL_PATH, fsConstants.R_OK)
|
||||
await fs.access(normalizedPath, fsConstants.R_OK)
|
||||
return {
|
||||
success: true,
|
||||
exists: true,
|
||||
path: WHISPER_SMALL_MODEL_PATH,
|
||||
path: normalizedPath,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
success: true,
|
||||
exists: false,
|
||||
path: null,
|
||||
path: normalizedPath,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3927,6 +3962,14 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('get-whisper-model-path-status', async (_, modelPath?: string | null) => {
|
||||
try {
|
||||
return await getWhisperModelPathStatus(modelPath)
|
||||
} catch (error) {
|
||||
return { success: false, exists: false, path: null, error: String(error) }
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('download-whisper-small-model', async (event) => {
|
||||
try {
|
||||
const existing = await getWhisperSmallModelStatus()
|
||||
@@ -4241,6 +4284,7 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
?? {
|
||||
videoPath: currentVideoPath,
|
||||
webcamPath: null,
|
||||
timeOffsetMs: 0,
|
||||
}
|
||||
|
||||
currentRecordingSession = resolvedSession
|
||||
@@ -4253,12 +4297,13 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
return { success: true, webcamPath: resolvedSession.webcamPath ?? null }
|
||||
})
|
||||
|
||||
ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null }) => {
|
||||
ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number }) => {
|
||||
const normalizedVideoPath = normalizeVideoSourcePath(session.videoPath) ?? session.videoPath
|
||||
currentVideoPath = normalizedVideoPath
|
||||
currentRecordingSession = {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: normalizeVideoSourcePath(session.webcamPath ?? null),
|
||||
timeOffsetMs: normalizeRecordingTimeOffsetMs(session.timeOffsetMs),
|
||||
}
|
||||
currentProjectPath = null
|
||||
await persistRecordingSessionManifest(currentRecordingSession)
|
||||
|
||||
@@ -167,6 +167,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
getWhisperSmallModelStatus: () => {
|
||||
return ipcRenderer.invoke("get-whisper-small-model-status");
|
||||
},
|
||||
getWhisperModelPathStatus: (modelPath?: string | null) => {
|
||||
return ipcRenderer.invoke("get-whisper-model-path-status", modelPath);
|
||||
},
|
||||
downloadWhisperSmallModel: () => {
|
||||
return ipcRenderer.invoke("download-whisper-small-model");
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user