- fallback sanitize properly (force filenamify)
- improve export failed feedback #1409
- add more invalid chars to filename check
- always show out seg error in export page
pull/1413/head
Mikael Finstad 2022-12-31 16:53:24 +08:00
rodzic 55e9a0a088
commit 967fe22cbe
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 25AB36E3E81CBC26
4 zmienionych plików z 29 dodań i 19 usunięć

Wyświetl plik

@ -69,7 +69,7 @@ import {
} from './util';
import { formatDuration } from './util/duration';
import { adjustRate } from './util/rate-calculator';
import { askForOutDir, askForInputDir, askForImportChapters, createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, promptTimeOffset, askForHtml5ifySpeed, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showCutFailedDialog, labelSegmentDialog, openYouTubeChaptersDialog, openAbout, showEditableJsonDialog, askForShiftSegments, selectSegmentsByLabelDialog, confirmExtractFramesAsImages, showRefuseToOverwrite, showParametersDialog, openDirToast, openCutFinishedToast } from './dialogs';
import { askForOutDir, askForInputDir, askForImportChapters, createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, promptTimeOffset, askForHtml5ifySpeed, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showExportFailedDialog, labelSegmentDialog, openYouTubeChaptersDialog, openAbout, showEditableJsonDialog, askForShiftSegments, selectSegmentsByLabelDialog, confirmExtractFramesAsImages, showRefuseToOverwrite, showParametersDialog, openDirToast, openCutFinishedToast } from './dialogs';
import { openSendReportDialog } from './reporting';
import { fallbackLng } from './i18n';
import { createSegment, getCleanCutSegments, getSegApparentStart, getSegApparentEnd as getSegApparentEnd2, findSegmentsAtCursor, sortSegments, invertSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap, combineOverlappingSegments as combineOverlappingSegments2, isDurationValid } from './segments';
@ -1178,8 +1178,6 @@ const App = memo(() => {
const selectOnlyCurrentSegment = useCallback(() => selectOnlySegment(currentCutSeg), [currentCutSeg, selectOnlySegment]);
const toggleCurrentSegmentSelected = useCallback(() => toggleSegmentSelected(currentCutSeg), [currentCutSeg, toggleSegmentSelected]);
const filenamifyOrNot = useCallback((name) => (safeOutputFileName ? filenamify(name) : name).substr(0, maxLabelLength), [safeOutputFileName, maxLabelLength]);
const onLabelSegment = useCallback(async (index) => {
const { name } = cutSegments[index];
const value = await labelSegmentDialog({ currentName: name, maxLength: maxLabelLength });
@ -1205,13 +1203,15 @@ const App = memo(() => {
const areWeCutting = useMemo(() => segmentsToExport.some(({ start, end }) => isCuttingStart(start) || isCuttingEnd(end, duration)), [duration, segmentsToExport]);
const generateOutSegFileNames = useCallback(({ segments = segmentsToExport, template }) => (
const generateOutSegFileNames = useCallback(({ segments = segmentsToExport, template, forceSafeOutputFileName }) => (
segments.map((segment, i) => {
const { start, end, name = '' } = segment;
const cutFromStr = formatDuration({ seconds: start, fileNameFriendly: true });
const cutToStr = formatDuration({ seconds: end, fileNameFriendly: true });
const segNum = i + 1;
const filenamifyOrNot = (fileName) => (safeOutputFileName || forceSafeOutputFileName ? filenamify(fileName) : fileName).substr(0, maxLabelLength);
// https://github.com/mifi/lossless-cut/issues/583
let segSuffix = '';
if (name) segSuffix = `-${filenamifyOrNot(name)}`;
@ -1228,7 +1228,7 @@ const App = memo(() => {
const generated = generateSegFileName({ template, segSuffix, inputFileNameWithoutExt: fileNameWithoutExt, ext, segNum, segLabel: nameSanitized, cutFrom: cutFromStr, cutTo: cutToStr, tags: tagsSanitized });
return safeOutputFileName ? generated.substring(0, 200) : generated; // If sanitation is enabled, make sure filename is not too long
})
), [segmentsToExport, filenamifyOrNot, isCustomFormatSelected, fileFormat, filePath, safeOutputFileName]);
), [segmentsToExport, isCustomFormatSelected, fileFormat, filePath, safeOutputFileName, maxLabelLength]);
const getOutSegError = useCallback((fileNames) => getOutSegErrorRaw({ fileNames, filePath, outputDir }), [outputDir, filePath]);
@ -1250,10 +1250,10 @@ const App = memo(() => {
openSendReportDialog(err, state);
}, [filePath, fileFormat, externalFilesMeta, mainStreams, copyStreamIdsByFile, cutSegments, mainFileFormatData, rotation, shortestFlag, effectiveExportMode, outSegTemplate]);
const handleCutFailed = useCallback(async (err) => {
const sendErrorReport = await showCutFailedDialog({ detectedFileFormat });
const handleExportFailed = useCallback(async (err) => {
const sendErrorReport = await showExportFailedDialog({ detectedFileFormat, safeOutputFileName });
if (sendErrorReport) openSendReportDialogWithState(err);
}, [openSendReportDialogWithState, detectedFileFormat]);
}, [detectedFileFormat, safeOutputFileName, openSendReportDialogWithState]);
const closeExportConfirm = useCallback(() => setExportConfirmVisible(false), []);
@ -1287,8 +1287,8 @@ const App = memo(() => {
let outSegFileNames = generateOutSegFileNames({ segments: segmentsToExport, template: outSegTemplateOrDefault });
if (getOutSegError(outSegFileNames) != null) {
console.error('Output segments file name invalid, using default instead', outSegFileNames);
outSegFileNames = generateOutSegFileNames({ segments: segmentsToExport, template: defaultOutSegTemplate });
console.warn('Output segments file name invalid, using default instead', outSegFileNames);
outSegFileNames = generateOutSegFileNames({ segments: segmentsToExport, template: defaultOutSegTemplate, forceSafeOutputFileName: true });
}
// throw (() => { const err = new Error('test'); err.code = 'ENOENT'; return err; })();
@ -1377,7 +1377,7 @@ const App = memo(() => {
showDiskFull();
return;
}
handleCutFailed(err);
handleExportFailed(err);
return;
}
@ -1386,7 +1386,7 @@ const App = memo(() => {
setWorking();
setCutProgress();
}
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, detectedFps, enableSmartCut, enableOverwriteOutput, willMerge, mainFileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, selectedSegmentsOrInverse, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, filePath, nonCopiedExtraStreams, handleCutFailed]);
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, detectedFps, enableSmartCut, enableOverwriteOutput, willMerge, mainFileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, selectedSegmentsOrInverse, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, filePath, nonCopiedExtraStreams, handleExportFailed]);
const onExportPress = useCallback(async () => {
if (!filePath || workingRef.current || segmentsToExport.length < 1) return;

Wyświetl plik

@ -81,16 +81,18 @@ const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate
const onTextChange = useCallback((e) => setText(e.target.value), []);
const needToShow = shown || error != null;
return (
<>
<div>
<span role="button" onClick={onShowClick} style={{ cursor: shown ? undefined : 'pointer' }}>
<span role="button" onClick={onShowClick} style={{ cursor: needToShow ? undefined : 'pointer' }}>
{t('Output name(s):')} {outSegFileNames != null && <HighlightedText style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{outSegFileNames[currentSegIndexSafe] || outSegFileNames[0]}</HighlightedText>}
</span>
{helpIcon}
</div>
{shown && (
{needToShow && (
<>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 5, marginTop: 5 }}>
<input type="text" style={inputStyle} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />

Wyświetl plik

@ -501,11 +501,12 @@ export async function createRandomSegments(fileDuration) {
return edl;
}
export async function showCutFailedDialog({ detectedFileFormat }) {
export async function showExportFailedDialog({ detectedFileFormat, safeOutputFileName }) {
const html = (
<div style={{ textAlign: 'left' }}>
<Trans>Try one of the following before exporting again:</Trans>
<ol>
{!safeOutputFileName && <li><Trans>Output file names are not sanitized. Try to enable sanitazion or check your segment labels for invalid characters.</Trans></li>}
{detectedFileFormat === 'mp4' && <li><Trans>Change output <b>Format</b> from <b>MP4</b> to <b>MOV</b></Trans></li>}
<li><Trans>Select a different output <b>Format</b> (<b>matroska</b> and <b>mp4</b> support most codecs)</Trans></li>
<li><Trans>Disable unnecessary <b>Tracks</b></Trans></li>

Wyświetl plik

@ -16,10 +16,17 @@ export function getOutSegError({ fileNames, filePath, outputDir }) {
break;
}
const invalidChars = [pathSep];
const invalidChars = new Set();
// Colon is invalid on windows https://github.com/mifi/lossless-cut/issues/631 and on MacOS, but not Linux https://github.com/mifi/lossless-cut/issues/830
if (isMac || isWindows) invalidChars.push(':');
// https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
if (isWindows) {
['/', '<', '>', ':', '"', '\\', '|', '?', '*'].forEach((char) => invalidChars.add(char));
} else if (isMac) {
// Colon is invalid on windows https://github.com/mifi/lossless-cut/issues/631 and on MacOS, but not Linux https://github.com/mifi/lossless-cut/issues/830
['/', ':'].forEach((char) => invalidChars.add(char));
} else {
invalidChars.add(pathSep);
}
const outPath = pathNormalize(pathJoin(outputDir, fileName));
const sameAsInputPath = outPath === pathNormalize(filePath);
@ -30,7 +37,7 @@ export function getOutSegError({ fileNames, filePath, outputDir }) {
error = i18n.t('At least one resulting file name has no length');
break;
}
if (invalidChars.some((c) => fileName.includes(c))) {
if ([...fileName].some((char) => invalidChars.has(char))) {
error = i18n.t('At least one resulting file name contains invalid characters');
break;
}