From 6a98e3315b1017866ab4f958f28682604a2a380a Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 6 Aug 2024 00:55:49 +0200 Subject: [PATCH] allow relative timeline seek #2056 --- src/renderer/src/App.tsx | 17 ++++++++++------- src/renderer/src/dialogs/index.tsx | 25 +++++++++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 07125062..830d64fd 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -79,7 +79,7 @@ import { formatDuration, parseDuration } from './util/duration'; import { adjustRate } from './util/rate-calculator'; import { askExtractFramesAsImages } from './dialogs/extractFrames'; import { askForHtml5ifySpeed } from './dialogs/html5ify'; -import { askForOutDir, askForImportChapters, promptTimeOffset, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showExportFailedDialog, showConcatFailedDialog, openYouTubeChaptersDialog, showRefuseToOverwrite, openDirToast, openExportFinishedToast, openConcatFinishedToast, showOpenDialog, showMuxNotSupported } from './dialogs'; +import { askForOutDir, askForImportChapters, promptTimecode, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showExportFailedDialog, showConcatFailedDialog, openYouTubeChaptersDialog, showRefuseToOverwrite, openDirToast, openExportFinishedToast, openConcatFinishedToast, showOpenDialog, showMuxNotSupported } from './dialogs'; import { openSendReportDialog } from './reporting'; import { fallbackLng } from './i18n'; import { createSegment, getCleanCutSegments, findSegmentsAtCursor, sortSegments, convertSegmentsToChapters, hasAnySegmentOverlap, isDurationValid, playOnlyCurrentSegment, getSegmentTags } from './segments'; @@ -1736,17 +1736,20 @@ function App() { const goToTimecode = useCallback(async () => { if (!filePath) return; - const timecode = await promptTimeOffset({ + const timecode = await promptTimecode({ initialValue: formatTimecode({ seconds: commandedTimeRef.current }), title: i18n.t('Seek to timecode'), + text: i18n.t('Use + and - for relative seek'), + allowRelative: true, inputPlaceholder: timecodePlaceholder, parseTimecode, }); if (timecode === undefined) return; - userSeekAbs(timecode); - }, [filePath, formatTimecode, parseTimecode, timecodePlaceholder, userSeekAbs]); + if (timecode.relDirection != null) seekRel(timecode.duration * timecode.relDirection); + else userSeekAbs(timecode.duration); + }, [filePath, formatTimecode, parseTimecode, seekRel, timecodePlaceholder, userSeekAbs]); const goToTimecodeDirect = useCallback(async ({ time: timeStr }: { time: string }) => { if (!filePath) return; @@ -1819,7 +1822,7 @@ function App() { }, [customOutDir, filePath, html5ifyAndLoad, hasVideo, hasAudio, rememberConvertToSupportedFormat, setWorking]); const askStartTimeOffset = useCallback(async () => { - const newStartTimeOffset = await promptTimeOffset({ + const newStartTimeOffset = await promptTimecode({ initialValue: startTimeOffset !== undefined ? formatTimecode({ seconds: startTimeOffset }) : undefined, title: i18n.t('Set custom start time offset'), text: i18n.t('Instead of video apparently starting at 0, you can offset by a specified value. This only applies to the preview inside LosslessCut and does not modify the file in any way. (Useful for viewing/cutting videos according to timecodes)'), @@ -1827,9 +1830,9 @@ function App() { parseTimecode, }); - if (newStartTimeOffset === undefined) return; + if (newStartTimeOffset === undefined || newStartTimeOffset.duration < 0) return; - setStartTimeOffset(newStartTimeOffset); + setStartTimeOffset(newStartTimeOffset.duration); }, [formatTimecode, parseTimecode, startTimeOffset, timecodePlaceholder]); const toggleKeyboardShortcuts = useCallback(() => setKeyboardShortcutsVisible((v) => !v), []); diff --git a/src/renderer/src/dialogs/index.tsx b/src/renderer/src/dialogs/index.tsx index 1cb545bc..05c4aa3b 100644 --- a/src/renderer/src/dialogs/index.tsx +++ b/src/renderer/src/dialogs/index.tsx @@ -18,8 +18,10 @@ import { ParseTimecode, SegmentBase } from '../types'; const { dialog, shell } = window.require('@electron/remote'); -export async function promptTimeOffset({ initialValue, title, text, inputPlaceholder, parseTimecode }: { initialValue?: string | undefined, title: string, text?: string | undefined, inputPlaceholder: string, parseTimecode: ParseTimecode }) { - const { value } = await Swal.fire({ +export async function promptTimecode({ initialValue, title, text, inputPlaceholder, parseTimecode, allowRelative = false }: { + initialValue?: string | undefined, title: string, text?: string | undefined, inputPlaceholder: string, parseTimecode: ParseTimecode, allowRelative?: boolean, +}) { + const { value } = await Swal.fire({ title, text, input: 'text', @@ -35,11 +37,22 @@ export async function promptTimeOffset({ initialValue, title, text, inputPlaceho return undefined; } - const duration = parseTimecode(value); - // Invalid, try again - if (duration === undefined) return promptTimeOffset({ initialValue: value, title, text, inputPlaceholder, parseTimecode }); + let relDirection: number | undefined; + if (allowRelative) { + if (value.startsWith('-')) relDirection = -1; + else if (value.startsWith('+')) relDirection = 1; + } - return duration; + const value2 = allowRelative ? value.replace(/^[+-]/, '') : value; + + const duration = parseTimecode(value2); + // Invalid, try again + if (duration === undefined) return promptTimecode({ initialValue: value, title, text, inputPlaceholder, parseTimecode, allowRelative }); + + return { + duration, + relDirection, + }; } // https://github.com/mifi/lossless-cut/issues/1495