From 6b5857b90283de526a01c71b492258cd024cc092 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Fri, 14 Jan 2022 15:57:14 +0700 Subject: [PATCH] allow showing timecode as frame counts #878 --- public/configStore.js | 2 +- src/App.jsx | 23 +++++++---- src/Settings.jsx | 73 ++++++++++++++++++++------------- src/hooks/useUserPreferences.js | 8 ++-- 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/public/configStore.js b/public/configStore.js index b8af370d..44d79c11 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -13,7 +13,7 @@ const defaults = { keyframeCut: true, autoMerge: false, autoDeleteMergedSegments: true, - timecodeShowFrames: false, + timecodeFormat: 'timecodeWithDecimalFraction', invertCutSegments: false, autoExportExtraStreams: true, exportConfirmEnabled: true, diff --git a/src/App.jsx b/src/App.jsx index ac8d0559..d3c43d81 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -170,7 +170,7 @@ const App = memo(() => { const isCustomFormatSelected = fileFormat !== detectedFileFormat; const { - captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeShowFrames, setTimecodeShowFrames, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, setAutoSaveProjectFile, wheelSensitivity, setWheelSensitivity, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, + captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, setAutoSaveProjectFile, wheelSensitivity, setWheelSensitivity, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, } = useUserPreferences(); const { @@ -421,15 +421,22 @@ const App = memo(() => { setCutSegments(sortBy(cutSegments, getSegApparentStart)); }, [cutSegments, setCutSegments]); - const formatTimecode = useCallback((sec) => formatDuration({ - seconds: sec, fps: timecodeShowFrames ? detectedFps : undefined, - }), [detectedFps, timecodeShowFrames]); - const getFrameCount = useCallback((sec) => { if (detectedFps == null) return undefined; return Math.floor(sec * detectedFps); }, [detectedFps]); + const formatTimecode = useCallback((sec) => { + if (timecodeFormat === 'framesTotal') { + const frameCount = getFrameCount(sec); + return frameCount != null ? frameCount : ''; + } + if (timecodeFormat === 'timecodeWithFramesFraction') { + return formatDuration({ seconds: sec, fps: detectedFps }); + } + return formatDuration({ seconds: sec }); + }, [detectedFps, timecodeFormat, getFrameCount]); + useEffect(() => { currentTimeRef.current = playing ? playerTime : commandedTime; }, [commandedTime, playerTime, playing]); @@ -2047,8 +2054,8 @@ const App = memo(() => { setInvertCutSegments={setInvertCutSegments} autoSaveProjectFile={autoSaveProjectFile} setAutoSaveProjectFile={setAutoSaveProjectFile} - timecodeShowFrames={timecodeShowFrames} - setTimecodeShowFrames={setTimecodeShowFrames} + timecodeFormat={timecodeFormat} + setTimecodeFormat={setTimecodeFormat} askBeforeClose={askBeforeClose} setAskBeforeClose={setAskBeforeClose} enableAskForImportChapters={enableAskForImportChapters} @@ -2071,7 +2078,7 @@ const App = memo(() => { renderCaptureFormatButton={renderCaptureFormatButton} onTunerRequested={onTunerRequested} /> - ), [changeOutDir, customOutDir, keyframeCut, setKeyframeCut, invertCutSegments, setInvertCutSegments, autoSaveProjectFile, setAutoSaveProjectFile, timecodeShowFrames, setTimecodeShowFrames, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, ffmpegExperimental, setFfmpegExperimental, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, AutoExportToggler, renderCaptureFormatButton, onTunerRequested, enableTransferTimestamps, setEnableTransferTimestamps]); + ), [changeOutDir, customOutDir, keyframeCut, setKeyframeCut, invertCutSegments, setInvertCutSegments, autoSaveProjectFile, setAutoSaveProjectFile, timecodeFormat, setTimecodeFormat, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, ffmpegExperimental, setFfmpegExperimental, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, AutoExportToggler, renderCaptureFormatButton, onTunerRequested, enableTransferTimestamps, setEnableTransferTimestamps]); useEffect(() => { if (!isStoreBuild) loadMifiLink().then(setMifiLink); diff --git a/src/Settings.jsx b/src/Settings.jsx index de4d33a6..24cfd0bc 100644 --- a/src/Settings.jsx +++ b/src/Settings.jsx @@ -1,12 +1,39 @@ -import React, { memo, useCallback } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { FaYinYang } from 'react-icons/fa'; import { Button, Table, NumericalIcon, KeyIcon, FolderCloseIcon, DocumentIcon, TimeIcon, Checkbox, Select } from 'evergreen-ui'; import { useTranslation } from 'react-i18next'; +// https://www.electronjs.org/docs/api/locales +// See i18n.js +const langNames = { + en: 'English', + cs: 'Čeština', + de: 'Deutsch', + es: 'Español', + fr: 'Français', + it: 'Italiano', + nl: 'Nederlands', + nb: 'Norsk', + pl: 'Polski', + pt: 'Português', + pt_BR: 'português do Brasil', + fi: 'Suomi', + ru: 'русский', + // sr: 'Cрпски', + tr: 'Türkçe', + vi: 'Tiếng Việt', + ja: '日本語', + zh: '中文', + zh_Hant: '繁體中文', + zh_Hans: '简体中文', + ko: '한국어', +}; + + const Settings = memo(({ changeOutDir, customOutDir, keyframeCut, setKeyframeCut, invertCutSegments, setInvertCutSegments, - autoSaveProjectFile, setAutoSaveProjectFile, timecodeShowFrames, setTimecodeShowFrames, askBeforeClose, setAskBeforeClose, + autoSaveProjectFile, setAutoSaveProjectFile, timecodeFormat, setTimecodeFormat, askBeforeClose, setAskBeforeClose, AutoExportToggler, renderCaptureFormatButton, onTunerRequested, language, setLanguage, invertTimelineScroll, setInvertTimelineScroll, ffmpegExperimental, setFfmpegExperimental, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, @@ -26,31 +53,19 @@ const Settings = memo(({ setLanguage(l); }, [setLanguage]); - // https://www.electronjs.org/docs/api/locales - // See i18n.js - const langNames = { - en: 'English', - cs: 'Čeština', - de: 'Deutsch', - es: 'Español', - fr: 'Français', - it: 'Italiano', - nl: 'Nederlands', - nb: 'Norsk', - pl: 'Polski', - pt: 'Português', - pt_BR: 'português do Brasil', - fi: 'Suomi', - ru: 'русский', - // sr: 'Cрпски', - tr: 'Türkçe', - vi: 'Tiếng Việt', - ja: '日本語', - zh: '中文', - zh_Hant: '繁體中文', - zh_Hans: '简体中文', - ko: '한국어', - }; + const timecodeFormatOptions = useMemo(() => ({ + framesTotal: t('Frame numbers'), + timecodeWithDecimalFraction: t('Millisecond fractions'), + timecodeWithFramesFraction: t('Frame fractions'), + }), [t]); + + const onTimecodeFormatClick = useCallback(() => { + const keys = Object.keys(timecodeFormatOptions); + let index = keys.indexOf(timecodeFormat); + if (index === -1 || index >= keys.length - 1) index = 0; + else index += 1; + setTimecodeFormat(keys[index]); + }, [setTimecodeFormat, timecodeFormat, timecodeFormatOptions]); return ( <> @@ -159,8 +174,8 @@ const Settings = memo(({ {t('In timecode show')} - diff --git a/src/hooks/useUserPreferences.js b/src/hooks/useUserPreferences.js index 1ca1e3bb..8fc13789 100644 --- a/src/hooks/useUserPreferences.js +++ b/src/hooks/useUserPreferences.js @@ -38,8 +38,8 @@ export default () => { useEffect(() => safeSetConfig('avoidNegativeTs', avoidNegativeTs), [avoidNegativeTs]); const [autoMerge, setAutoMerge] = useState(configStore.get('autoMerge')); useEffect(() => safeSetConfig('autoMerge', autoMerge), [autoMerge]); - const [timecodeShowFrames, setTimecodeShowFrames] = useState(configStore.get('timecodeShowFrames')); - useEffect(() => safeSetConfig('timecodeShowFrames', timecodeShowFrames), [timecodeShowFrames]); + const [timecodeFormat, setTimecodeFormat] = useState(configStore.get('timecodeFormat')); + useEffect(() => safeSetConfig('timecodeFormat', timecodeFormat), [timecodeFormat]); const [invertCutSegments, setInvertCutSegments] = useState(configStore.get('invertCutSegments')); useEffect(() => safeSetConfig('invertCutSegments', invertCutSegments), [invertCutSegments]); const [autoExportExtraStreams, setAutoExportExtraStreams] = useState(configStore.get('autoExportExtraStreams')); @@ -113,8 +113,8 @@ export default () => { setAvoidNegativeTs, autoMerge, setAutoMerge, - timecodeShowFrames, - setTimecodeShowFrames, + timecodeFormat, + setTimecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams,