From 645d2f147b5c6a15c2ba87edfd8347d67d33c0ed Mon Sep 17 00:00:00 2001 From: xRangerDE Date: Thu, 19 Mar 2026 17:47:36 +0100 Subject: [PATCH] feat(merge-split): add createMergeGroup IPC handler Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main.ts | 83 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/preload.ts | 1 + 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/main.ts b/src/main.ts index fe84d73..a10f301 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2387,7 +2387,8 @@ async function cutVideo( async function mergeVideos( inputFiles: string[], outputFile: string, - onProgress: (percent: number) => void + onProgress: (percent: number) => void, + totalDurationSec?: number ): Promise { const ffmpegReady = await ensureFfmpegInstalled(); if (!ffmpegReady) { @@ -2397,7 +2398,10 @@ async function mergeVideos( const ffmpeg = getFFmpegPath(); const concatFile = path.join(app.getPath('temp'), `concat_${Date.now()}.txt`); - const concatContent = inputFiles.map((filePath) => `file '${filePath.replace(/'/g, "'\\''")}'`).join('\n'); + const concatContent = inputFiles.map((filePath) => { + const normalized = filePath.replace(/\\/g, '/'); + return `file '${normalized.replace(/'/g, "'\\''")}'`; + }).join('\n'); fs.writeFileSync(concatFile, concatContent); let mergeInputBytes = 0; @@ -3494,6 +3498,81 @@ ipcMain.handle('retry-failed-downloads', () => { return downloadQueue; }); +ipcMain.handle('create-merge-group', (_, itemIds: string[]) => { + const selectedItems = downloadQueue.filter(item => itemIds.includes(item.id)); + + if (selectedItems.length < 2) { + return downloadQueue; + } + + // Validate all are pending + if (selectedItems.some(item => item.status !== 'pending')) { + return downloadQueue; + } + + // Sort chronologically by ISO timestamp (handles same-day different times) + const sorted = [...selectedItems].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + + // Calculate total duration + const totalDurationSec = sorted.reduce((sum, item) => sum + parseDuration(item.duration_str), 0); + const totalDurationStr = (() => { + const h = Math.floor(totalDurationSec / 3600); + const m = Math.floor((totalDurationSec % 3600) / 60); + const s = totalDurationSec % 60; + const parts: string[] = []; + if (h > 0) parts.push(`${h}h`); + if (m > 0) parts.push(`${m}m`); + if (s > 0 || parts.length === 0) parts.push(`${s}s`); + return parts.join(''); + })(); + + // Generate title (language-aware) + const first = sorted[0]; + const isEnglish = config.language === 'en'; + const title = sorted.length === 2 + ? `Merge: ${first.title} + ${sorted[1].title}` + : `Merge: ${first.title} + ${sorted.length - 1} ${isEnglish ? 'more' : 'weitere'}`; + + // Build merge group + const mergeGroup: MergeGroup = { + items: sorted.map(item => ({ + url: item.url, + title: item.title, + date: item.date, + streamer: item.streamer, + duration_str: item.duration_str + })), + mergePhase: 'downloading', + currentItemIndex: 0, + downloadedFiles: {}, + totalDurationSec + }; + + // Create merged queue item + const mergedItem: QueueItem = { + id: generateQueueItemId(), + title, + url: first.url, + date: first.date, + streamer: first.streamer, + duration_str: totalDurationStr, + status: 'pending', + progress: 0, + mergeGroup + }; + + // Find position of first selected item + const firstIndex = downloadQueue.findIndex(item => itemIds.includes(item.id)); + + // Remove selected items and insert merged item at first position + downloadQueue = downloadQueue.filter(item => !itemIds.includes(item.id)); + downloadQueue.splice(firstIndex >= 0 ? Math.min(firstIndex, downloadQueue.length) : downloadQueue.length, 0, mergedItem); + + saveQueue(downloadQueue); + emitQueueUpdated(); + return downloadQueue; +}); + ipcMain.handle('start-download', async () => { downloadQueue = downloadQueue.map((item) => item.status === 'paused' ? { ...item, status: 'pending' } : item); diff --git a/src/preload.ts b/src/preload.ts index 22df4c4..a48b607 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -123,6 +123,7 @@ contextBridge.exposeInMainWorld('api', { reorderQueue: (orderIds: string[]) => ipcRenderer.invoke('reorder-queue', orderIds), clearCompleted: () => ipcRenderer.invoke('clear-completed'), retryFailedDownloads: () => ipcRenderer.invoke('retry-failed-downloads'), + createMergeGroup: (itemIds: string[]) => ipcRenderer.invoke('create-merge-group', itemIds), // Download startDownload: () => ipcRenderer.invoke('start-download'),