diff --git a/public/menu.js b/public/menu.js index 842086e5..ea194696 100644 --- a/public/menu.js +++ b/public/menu.js @@ -207,6 +207,12 @@ module.exports = (app, mainWindow, newVersion) => { mainWindow.webContents.send('invertAllCutSegments'); }, }, + { + label: i18n.t('Fill gaps between segments'), + click() { + mainWindow.webContents.send('fillSegmentsGaps'); + }, + }, { label: i18n.t('Shuffle segments order'), click() { diff --git a/src/App.jsx b/src/App.jsx index 3fc6a322..21d91cf7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -375,15 +375,24 @@ const App = memo(() => { }, [apparentCutSegments, duration, haveInvalidSegs]); const invertAllCutSegments = useCallback(() => { - // don't reset segIndex (which represent colors) when inverting - const newInverseCutSegments = inverseCutSegments.map((inverseSegment, segIndex) => createSegment({ ...inverseSegment, segIndex })); - if (newInverseCutSegments.length < 1) { + if (inverseCutSegments.length < 1) { errorToast(i18n.t('Make sure you have no overlapping segments.')); return; } + // don't reset segIndex (which represent colors) when inverting + const newInverseCutSegments = inverseCutSegments.map((inverseSegment, segIndex) => createSegment({ ...inverseSegment, segIndex })); setCutSegments(newInverseCutSegments); }, [inverseCutSegments, setCutSegments]); + const fillSegmentsGaps = useCallback(() => { + if (inverseCutSegments.length < 1) { + errorToast(i18n.t('Make sure you have no overlapping segments.')); + return; + } + const newInverseCutSegments = inverseCutSegments.map((inverseSegment) => createIndexedSegment({ segment: inverseSegment, incrementCount: true })); + setCutSegments((existing) => ([...existing, ...newInverseCutSegments])); + }, [createIndexedSegment, inverseCutSegments, setCutSegments]); + const updateSegAtIndex = useCallback((index, newProps) => { if (index < 0) return; const cutSegmentsNew = [...cutSegments]; @@ -1835,6 +1844,7 @@ const App = memo(() => { export: onExportPress, reorderSegsByStartTime, invertAllCutSegments, + fillSegmentsGaps, createFixedDurationSegments, createNumSegments, shuffleSegments, @@ -1897,7 +1907,7 @@ const App = memo(() => { if (match) return bubble; return true; // bubble the event - }, [addCutSegment, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, exportConfirmVisible, extractAllStreams, goToTimecode, increaseRotation, invertAllCutSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegmentPress, pause, play, removeCutSegment, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleHelp, toggleKeyboardShortcuts, toggleKeyframeCut, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); + }, [addCutSegment, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, exportConfirmVisible, extractAllStreams, fillSegmentsGaps, goToTimecode, increaseRotation, invertAllCutSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegmentPress, pause, play, removeCutSegment, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleHelp, toggleKeyboardShortcuts, toggleKeyframeCut, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); useKeyboard({ keyBindings, onKeyPress }); @@ -2121,6 +2131,7 @@ const App = memo(() => { createNumSegments, createFixedDurationSegments, invertAllCutSegments, + fillSegmentsGaps, fixInvalidDuration: tryFixInvalidDuration, reorderSegsByStartTime, concatCurrentBatch, @@ -2130,7 +2141,7 @@ const App = memo(() => { const entries = Object.entries(action); entries.forEach(([key, value]) => electron.ipcRenderer.on(key, value)); return () => entries.forEach(([key, value]) => electron.ipcRenderer.removeListener(key, value)); - }, [apparentCutSegments, askSetStartTimeOffset, checkFileOpened, clearSegments, closeBatch, closeFileWithConfirm, concatCurrentBatch, createFixedDurationSegments, createNumSegments, customOutDir, cutSegments, extractAllStreams, fileFormat, filePath, getFrameCount, getTimeFromFrameNum, invertAllCutSegments, loadCutSegments, loadMedia, openSendReportDialogWithState, reorderSegsByStartTime, setWorking, shiftAllSegmentTimes, shuffleSegments, toggleHelp, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]); + }, [apparentCutSegments, askSetStartTimeOffset, checkFileOpened, clearSegments, closeBatch, closeFileWithConfirm, concatCurrentBatch, createFixedDurationSegments, createNumSegments, customOutDir, cutSegments, extractAllStreams, fileFormat, filePath, fillSegmentsGaps, getFrameCount, getTimeFromFrameNum, invertAllCutSegments, loadCutSegments, loadMedia, openSendReportDialogWithState, reorderSegsByStartTime, setWorking, shiftAllSegmentTimes, shuffleSegments, toggleHelp, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]); const showAddStreamSourceDialog = useCallback(async () => { try { diff --git a/src/components/KeyboardShortcuts.jsx b/src/components/KeyboardShortcuts.jsx index aeb7583d..8f2969ad 100644 --- a/src/components/KeyboardShortcuts.jsx +++ b/src/components/KeyboardShortcuts.jsx @@ -288,6 +288,10 @@ const KeyboardShortcuts = memo(({ name: t('Invert all segments on timeline'), category: segmentsAndCutpointsCategory, }, + fillSegmentsGaps: { + name: t('Fill gaps between segments'), + category: segmentsAndCutpointsCategory, + }, createFixedDurationSegments: { name: t('Create fixed duration segments'), category: segmentsAndCutpointsCategory,