1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-25 19:52:44 +01:00

add a setting for allowing disabling filename sanitizing #821 #427

This commit is contained in:
Mikael Finstad 2021-08-23 23:50:21 +07:00
parent 0ae7ed4211
commit 8497f07bdc
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
5 changed files with 25 additions and 11 deletions

View File

@ -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

View File

@ -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(() => {
</div>
</motion.div>
<ExportConfirm filePath={filePath} autoMerge={autoMerge} setAutoMerge={setAutoMerge} areWeCutting={areWeCutting} enabledOutSegments={enabledOutSegments} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} keyframeCut={keyframeCut} toggleKeyframeCut={toggleKeyframeCut} renderOutFmt={renderOutFmt} preserveMovData={preserveMovData} togglePreserveMovData={togglePreserveMovData} movFastStart={movFastStart} toggleMovFastStart={toggleMovFastStart} avoidNegativeTs={avoidNegativeTs} setAvoidNegativeTs={setAvoidNegativeTs} changeOutDir={changeOutDir} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} setStreamsSelectorShown={setStreamsSelectorShown} exportConfirmEnabled={exportConfirmEnabled} toggleExportConfirmEnabled={toggleExportConfirmEnabled} segmentsToChapters={segmentsToChapters} toggleSegmentsToChapters={toggleSegmentsToChapters} outFormat={fileFormat} preserveMetadataOnMerge={preserveMetadataOnMerge} togglePreserveMetadataOnMerge={togglePreserveMetadataOnMerge} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} isOutSegFileNamesValid={isOutSegFileNamesValid} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} />
<ExportConfirm filePath={filePath} autoMerge={autoMerge} setAutoMerge={setAutoMerge} areWeCutting={areWeCutting} enabledOutSegments={enabledOutSegments} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} keyframeCut={keyframeCut} toggleKeyframeCut={toggleKeyframeCut} renderOutFmt={renderOutFmt} preserveMovData={preserveMovData} togglePreserveMovData={togglePreserveMovData} movFastStart={movFastStart} toggleMovFastStart={toggleMovFastStart} avoidNegativeTs={avoidNegativeTs} setAvoidNegativeTs={setAvoidNegativeTs} changeOutDir={changeOutDir} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} setStreamsSelectorShown={setStreamsSelectorShown} exportConfirmEnabled={exportConfirmEnabled} toggleExportConfirmEnabled={toggleExportConfirmEnabled} segmentsToChapters={segmentsToChapters} toggleSegmentsToChapters={toggleSegmentsToChapters} outFormat={fileFormat} preserveMetadataOnMerge={preserveMetadataOnMerge} togglePreserveMetadataOnMerge={togglePreserveMetadataOnMerge} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} isOutSegFileNamesValid={isOutSegFileNamesValid} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} safeOutputFileName={safeOutputFileName} toggleSafeOutputFileName={toggleSafeOutputFileName} />
<HelpSheet
visible={helpVisible}

View File

@ -45,6 +45,7 @@ const ExportConfirm = memo(({
exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, toggleSegmentsToChapters, outFormat,
preserveMetadataOnMerge, togglePreserveMetadataOnMerge, outSegTemplate, setOutSegTemplate, generateOutSegFileNames,
filePath, currentSegIndexSafe, isOutSegFileNamesValid, autoDeleteMergedSegments, setAutoDeleteMergedSegments,
safeOutputFileName, toggleSafeOutputFileName,
}) => {
const { t } = useTranslation();
@ -126,7 +127,7 @@ const ExportConfirm = memo(({
</li>
{(enabledOutSegments.length === 1 || !autoMerge) && (
<li>
<OutSegTemplateEditor filePath={filePath} helpIcon={outSegTemplateHelpIcon} outSegTemplate={outSegTemplate} setOutSegTemplate={setOutSegTemplate} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} isOutSegFileNamesValid={isOutSegFileNamesValid} />
<OutSegTemplateEditor filePath={filePath} helpIcon={outSegTemplateHelpIcon} outSegTemplate={outSegTemplate} setOutSegTemplate={setOutSegTemplate} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} isOutSegFileNamesValid={isOutSegFileNamesValid} safeOutputFileName={safeOutputFileName} toggleSafeOutputFileName={toggleSafeOutputFileName} />
</li>
)}
</ul>

View File

@ -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
<>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 5, marginTop: 5 }}>
<input type="text" style={{ flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em' }} onChange={(e) => setText(e.target.value)} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />
{outSegFileNames && <Button height={20} onClick={onAllSegmentsPreviewPress} style={{ marginLeft: 5 }}>{t('Preview')}</Button>}
<Button height={20} onClick={reset} style={{ marginLeft: 5 }} intent="danger">{t('Reset')}</Button>
<Button height={20} onClick={onToggleClick} style={{ marginLeft: 5 }} intent="success">{t('Close')}</Button>
{outSegFileNames && <Button height={20} onClick={onAllSegmentsPreviewPress} marginLeft={5}>{t('Preview')}</Button>}
<Button title={t('Whether to sanitize file name or not (sanitizing removes special characters)')} marginLeft={5} height={20} onClick={toggleSafeOutputFileName} intent={safeOutputFileName ? 'success' : 'danger'}>{safeOutputFileName ? t('Sanitize') : t('No sanitize')}</Button>
<IconButton title={t('Reset')} icon={ResetIcon} height={20} onClick={reset} marginLeft={5} intent="danger" />
<IconButton title={t('Close')} icon={CrossIcon} height={20} onClick={onToggleClick} marginLeft={5} intent="success" />
</div>
<div>
{error != null && <Alert intent="danger" appearance="card">{`${i18n.t('There is an error in the file name template:')} ${error}`}</Alert>}

View File

@ -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,
};
};