mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 19:52:44 +01:00
parent
0ae7ed4211
commit
8497f07bdc
@ -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
|
||||
|
17
src/App.jsx
17
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(() => {
|
||||
</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}
|
||||
|
@ -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>
|
||||
|
@ -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>}
|
||||
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user