feat(merge-split): add createMergeGroup IPC handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4750af2f97
commit
645d2f147b
83
src/main.ts
83
src/main.ts
@ -2387,7 +2387,8 @@ async function cutVideo(
|
|||||||
async function mergeVideos(
|
async function mergeVideos(
|
||||||
inputFiles: string[],
|
inputFiles: string[],
|
||||||
outputFile: string,
|
outputFile: string,
|
||||||
onProgress: (percent: number) => void
|
onProgress: (percent: number) => void,
|
||||||
|
totalDurationSec?: number
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const ffmpegReady = await ensureFfmpegInstalled();
|
const ffmpegReady = await ensureFfmpegInstalled();
|
||||||
if (!ffmpegReady) {
|
if (!ffmpegReady) {
|
||||||
@ -2397,7 +2398,10 @@ async function mergeVideos(
|
|||||||
|
|
||||||
const ffmpeg = getFFmpegPath();
|
const ffmpeg = getFFmpegPath();
|
||||||
const concatFile = path.join(app.getPath('temp'), `concat_${Date.now()}.txt`);
|
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);
|
fs.writeFileSync(concatFile, concatContent);
|
||||||
|
|
||||||
let mergeInputBytes = 0;
|
let mergeInputBytes = 0;
|
||||||
@ -3494,6 +3498,81 @@ ipcMain.handle('retry-failed-downloads', () => {
|
|||||||
return downloadQueue;
|
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 () => {
|
ipcMain.handle('start-download', async () => {
|
||||||
downloadQueue = downloadQueue.map((item) => item.status === 'paused' ? { ...item, status: 'pending' } : item);
|
downloadQueue = downloadQueue.map((item) => item.status === 'paused' ? { ...item, status: 'pending' } : item);
|
||||||
|
|
||||||
|
|||||||
@ -123,6 +123,7 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
reorderQueue: (orderIds: string[]) => ipcRenderer.invoke('reorder-queue', orderIds),
|
reorderQueue: (orderIds: string[]) => ipcRenderer.invoke('reorder-queue', orderIds),
|
||||||
clearCompleted: () => ipcRenderer.invoke('clear-completed'),
|
clearCompleted: () => ipcRenderer.invoke('clear-completed'),
|
||||||
retryFailedDownloads: () => ipcRenderer.invoke('retry-failed-downloads'),
|
retryFailedDownloads: () => ipcRenderer.invoke('retry-failed-downloads'),
|
||||||
|
createMergeGroup: (itemIds: string[]) => ipcRenderer.invoke('create-merge-group', itemIds),
|
||||||
|
|
||||||
// Download
|
// Download
|
||||||
startDownload: () => ipcRenderer.invoke('start-download'),
|
startDownload: () => ipcRenderer.invoke('start-download'),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user