diff --git a/public/configStore.js b/public/configStore.js index d4089097..7da02313 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -38,6 +38,7 @@ const defaults = { keyboardNormalSeekSpeed: 1, enableTransferTimestamps: true, outFormatLocked: undefined, + safeOutputFileName: true, }; // For portable app: https://github.com/mifi/lossless-cut/issues/645 diff --git a/src/App.jsx b/src/App.jsx index acfce4c3..e0b45cf5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -148,7 +148,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, muted, setMuted, 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, + 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, muted, setMuted, 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 { @@ -231,6 +231,11 @@ const App = memo(() => { const hideAllNotifications = hideNotifications === 'all'; + const toggleSafeOutputFileName = useCallback(() => setSafeOutputFileName((v) => { + if (v && !hideAllNotifications) toast.fire({ icon: 'info', text: i18n.t('Output file name will not be sanitized, and any special characters will be preserved. This may cause the export to fail and can cause other funny issues. Use at your own risk!') }); + return !v; + }), [setSafeOutputFileName, hideAllNotifications]); + const toggleMute = useCallback(() => { setMuted((v) => { if (!v && !hideAllNotifications) toast.fire({ icon: 'info', title: i18n.t('Muted preview (exported file will not be affected)') }); @@ -891,6 +896,8 @@ const App = memo(() => { const onExportSegmentDisableAll = useCallback(() => setDisabledSegmentIds(Object.fromEntries(cutSegments.map((s) => [s.segId, true]))), [cutSegments]); const onExportSegmentEnableAll = useCallback(() => setDisabledSegmentIds({}), []); + const filenamifyOrNot = useCallback((name) => (safeOutputFileName ? filenamify(name) : name), [safeOutputFileName]); + const generateOutSegFileNames = useCallback(({ segments = enabledOutSegments, template }) => ( segments.map(({ start, end, name = '' }, i) => { const cutFromStr = formatDuration({ seconds: start, fileNameFriendly: true }); @@ -899,17 +906,17 @@ const App = memo(() => { // https://github.com/mifi/lossless-cut/issues/583 let segSuffix = ''; - if (name) segSuffix = `-${filenamify(name)}`; + if (name) segSuffix = `-${filenamifyOrNot(name)}`; else if (segments.length > 1) segSuffix = `-seg${segNum}`; const ext = getOutFileExtension({ isCustomFormatSelected, outFormat: fileFormat, filePath }); const { name: fileNameWithoutExt } = parsePath(filePath); - const generated = generateSegFileName({ template, segSuffix, inputFileNameWithoutExt: fileNameWithoutExt, ext, segNum, segLabel: filenamify(name), cutFrom: cutFromStr, cutTo: cutToStr }); + const generated = generateSegFileName({ template, segSuffix, inputFileNameWithoutExt: fileNameWithoutExt, ext, segNum, segLabel: filenamifyOrNot(name), cutFrom: cutFromStr, cutTo: cutToStr }); return generated.substr(0, 200); // Just to be sure }) - ), [fileFormat, filePath, isCustomFormatSelected, enabledOutSegments]); + ), [fileFormat, filePath, isCustomFormatSelected, enabledOutSegments, filenamifyOrNot]); // TODO improve user feedback const isOutSegFileNamesValid = useCallback((fileNames) => fileNames.every((fileName) => { @@ -2293,7 +2300,7 @@ const App = memo(() => { - + { const { t } = useTranslation(); @@ -126,7 +127,7 @@ const ExportConfirm = memo(({ {(enabledOutSegments.length === 1 || !autoMerge) && (
  • - +
  • )} diff --git a/src/components/OutSegTemplateEditor.jsx b/src/components/OutSegTemplateEditor.jsx index ab18d8c1..76a19b11 100644 --- a/src/components/OutSegTemplateEditor.jsx +++ b/src/components/OutSegTemplateEditor.jsx @@ -2,7 +2,7 @@ import React, { memo, useState, useEffect } from 'react'; import { useDebounce } from 'use-debounce'; import i18n from 'i18next'; import { useTranslation } from 'react-i18next'; -import { Button, Alert } from 'evergreen-ui'; +import { Button, Alert, IconButton, CrossIcon, ResetIcon } from 'evergreen-ui'; import Swal from 'sweetalert2'; import withReactContent from 'sweetalert2-react-content'; @@ -12,7 +12,7 @@ import { defaultOutSegTemplate } from '../util'; const ReactSwal = withReactContent(Swal); -const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, isOutSegFileNamesValid }) => { +const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, isOutSegFileNamesValid, safeOutputFileName, toggleSafeOutputFileName }) => { const [text, setText] = useState(outSegTemplate); const [debouncedText] = useDebounce(text, 500); const [validText, setValidText] = useState(); @@ -73,9 +73,10 @@ const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate <>
    setText(e.target.value)} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" /> - {outSegFileNames && } - - + {outSegFileNames && } + + +
    {error != null && {`${i18n.t('There is an error in the file name template:')} ${error}`}} diff --git a/src/hooks/useUserPreferences.js b/src/hooks/useUserPreferences.js index 589a5679..d99ead6b 100644 --- a/src/hooks/useUserPreferences.js +++ b/src/hooks/useUserPreferences.js @@ -86,6 +86,8 @@ export default () => { useEffect(() => safeSetConfig('enableTransferTimestamps', enableTransferTimestamps), [enableTransferTimestamps]); const [outFormatLocked, setOutFormatLocked] = useState(configStore.get('outFormatLocked')); useEffect(() => safeSetConfig('outFormatLocked', outFormatLocked), [outFormatLocked]); + const [safeOutputFileName, setSafeOutputFileName] = useState(configStore.get('safeOutputFileName')); + useEffect(() => safeSetConfig('safeOutputFileName', safeOutputFileName), [safeOutputFileName]); // NOTE! This useEffect must be placed after all usages of firstUpdateRef.current (safeSetConfig) @@ -159,5 +161,7 @@ export default () => { setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, + safeOutputFileName, + setSafeOutputFileName, }; };