From 3b53051e3d57760d6f16d8b9dd19b54de378e84b Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 26 Jul 2023 11:33:16 +0200 Subject: [PATCH] don't error if skipping overwrite existing file closes #1655 --- src/App.jsx | 9 +++----- src/hooks/useFfmpegOperations.js | 36 +++++++++++++++++--------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 38402166..1fbb6df6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -716,7 +716,7 @@ const App = memo(() => { const { concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration, - } = useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut }); + } = useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut, enableOverwriteOutput }); const html5ifyAndLoad = useCallback(async (cod, fp, speed, hv, ha) => { const usesDummyVideo = ['fastest-audio', 'fastest-audio-remux', 'fastest'].includes(speed); @@ -1130,7 +1130,6 @@ const App = memo(() => { dispositionByStreamId, chapters: chaptersToAdd, detectedFps, - enableOverwriteOutput, }); let concatOutPath; @@ -1159,6 +1158,8 @@ const App = memo(() => { const notices = []; const warnings = []; + if (!enableOverwriteOutput) warnings.push(i18n.t('Overwrite output setting is disabled and some files might have been skipped.')); + if (!exportConfirmEnabled) notices.push(i18n.t('Export options are not shown. You can enable export options by clicking the icon right next to the export button.')); // https://github.com/mifi/lossless-cut/issues/329 @@ -1190,10 +1191,6 @@ const App = memo(() => { // assume execa killed (aborted by user) return; } - if (err instanceof RefuseOverwriteError) { - showRefuseToOverwrite(); - return; - } console.error('stdout:', err.stdout); console.error('stderr:', err.stderr); diff --git a/src/hooks/useFfmpegOperations.js b/src/hooks/useFfmpegOperations.js index bb1e3ca6..3b18d6f1 100644 --- a/src/hooks/useFfmpegOperations.js +++ b/src/hooks/useFfmpegOperations.js @@ -4,7 +4,7 @@ import sum from 'lodash/sum'; import pMap from 'p-map'; import { getSuffixedOutPath, transferTimestamps, getOutFileExtension, getOutDir, deleteDispositionValue, getHtml5ifiedPath } from '../util'; -import { isCuttingStart, isCuttingEnd, runFfmpegWithProgress, getFfCommandLine, getDuration, createChaptersFromSegments, readFileMeta, cutEncodeSmartPart, getExperimentalArgs, html5ify as ffmpegHtml5ify, getVideoTimescaleArgs, RefuseOverwriteError, logStdoutStderr, runFfmpegConcat } from '../ffmpeg'; +import { isCuttingStart, isCuttingEnd, runFfmpegWithProgress, getFfCommandLine, getDuration, createChaptersFromSegments, readFileMeta, cutEncodeSmartPart, getExperimentalArgs, html5ify as ffmpegHtml5ify, getVideoTimescaleArgs, logStdoutStderr, runFfmpegConcat } from '../ffmpeg'; import { getMapStreamsArgs, getStreamIdsToCopy } from '../util/streams'; import { getSmartCutParams } from '../smartcut'; @@ -55,12 +55,20 @@ const tryDeleteFiles = async (paths) => pMap(paths, (path) => { unlink(path).catch((err) => console.error('Failed to delete', path, err)); }, { concurrency: 5 }); -function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut }) { +function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut, enableOverwriteOutput }) { const optionalTransferTimestamps = useCallback(async (...args) => { if (enableTransferTimestamps) await transferTimestamps(...args); }, [enableTransferTimestamps]); + const shouldSkipExistingFile = useCallback(async (path) => { + const skip = !enableOverwriteOutput && await pathExists(path); + if (skip) console.log('Not overwriting existing file', path); + return skip; + }, [enableOverwriteOutput]); + const concatFiles = useCallback(async ({ paths, outDir, outPath, metadataFromPath, includeAllStreams, streams, outFormat, ffmpegExperimental, onProgress = () => {}, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, videoTimebase, appendFfmpegCommandLog }) => { + if (await shouldSkipExistingFile(outPath)) return { haveExcludedStreams: false }; + console.log('Merging files', { paths }, 'to', outPath); const durations = await pMap(paths, getDuration, { concurrency: 1 }); @@ -163,12 +171,14 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut } finally { if (chaptersPath) await tryDeleteFiles([chaptersPath]); } - }, [optionalTransferTimestamps]); + }, [optionalTransferTimestamps, shouldSkipExistingFile]); const cutSingle = useCallback(async ({ keyframeCut: ssBeforeInput, avoidNegativeTs, copyFileStreams, cutFrom, cutTo, chaptersPath, onProgress, outPath, videoDuration, rotation, allFilesMeta, outFormat, appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, customTagsByFile, customTagsByStreamId, dispositionByStreamId, videoTimebase, }) => { + if (await shouldSkipExistingFile(outPath)) return; + const cuttingStart = isCuttingStart(cutFrom); const cuttingEnd = isCuttingEnd(cutTo, videoDuration); console.log('Cutting from', cuttingStart ? cutFrom : 'start', 'to', cuttingEnd ? cutTo : 'end'); @@ -310,14 +320,13 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut logStdoutStderr(result); await optionalTransferTimestamps(filePath, outPath, cutFrom); - }, [filePath, optionalTransferTimestamps]); + }, [filePath, optionalTransferTimestamps, shouldSkipExistingFile]); const cutMultiple = useCallback(async ({ outputDir, customOutDir, segments, outSegFileNames, videoDuration, rotation, detectedFps, onProgress: onTotalProgress, keyframeCut, copyFileStreams, allFilesMeta, outFormat, appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, chapters, preserveMetadataOnMerge, - enableOverwriteOutput, }) => { console.log('customTagsByFile', customTagsByFile); console.log('customTagsByStreamId', customTagsByStreamId); @@ -330,11 +339,6 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut const chaptersPath = await writeChaptersFfmetadata(outputDir, chapters); - async function checkOverwrite(path) { - if (!enableOverwriteOutput && await pathExists(path)) throw new RefuseOverwriteError(); - } - - // This function will either call cutSingle (if no smart cut enabled) // or if enabled, will first cut&encode the part before the next keyframe, trying to match the input file's codec params // then it will cut the part *from* the keyframe to "end", and concat them together and return the concated file @@ -351,7 +355,6 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut if (!needSmartCut) { // old fashioned way const outPath = await makeSegmentOutPath(); - await checkOverwrite(outPath); await cutSingle({ cutFrom: desiredCutFrom, cutTo, chaptersPath, outPath, copyFileStreams, keyframeCut, avoidNegativeTs, videoDuration, rotation, allFilesMeta, outFormat, appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, customTagsByFile, customTagsByStreamId, dispositionByStreamId, onProgress: (progress) => onSingleProgress(i, progress), }); @@ -379,13 +382,15 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut }]; // eslint-disable-next-line no-shadow - const cutEncodeSmartPartWrapper = async ({ cutFrom, cutTo, outPath }) => cutEncodeSmartPart({ filePath, cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate, videoStreamIndex, videoTimebase, allFilesMeta, copyFileStreams: copyFileStreamsFiltered, ffmpegExperimental }); + async function cutEncodeSmartPartWrapper({ cutFrom, cutTo, outPath }) { + if (await shouldSkipExistingFile(outPath)) return; + await cutEncodeSmartPart({ filePath, cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate, videoStreamIndex, videoTimebase, allFilesMeta, copyFileStreams: copyFileStreamsFiltered, ffmpegExperimental }); + } // If we are cutting within two keyframes, just encode the whole part and return that // See https://github.com/mifi/lossless-cut/pull/1267#issuecomment-1236381740 if (segmentNeedsSmartCut && encodeCutTo > cutTo) { const outPath = await makeSegmentOutPath(); - await checkOverwrite(outPath); await cutEncodeSmartPartWrapper({ cutFrom: desiredCutFrom, cutTo, outPath }); return outPath; } @@ -400,8 +405,6 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut const smartCutSegmentsToConcat = [smartCutEncodedPartOutPath, smartCutMainPartOutPath]; - if (!segmentNeedsSmartCut) await checkOverwrite(smartCutMainPartOutPath); - // for smart cut we need to use keyframe cut here, and no avoid_negative_ts await cutSingle({ cutFrom: encodeCutTo, cutTo, chaptersPath, outPath: smartCutMainPartOutPath, copyFileStreams: copyFileStreamsFiltered, keyframeCut: true, avoidNegativeTs: false, videoDuration, rotation, allFilesMeta, outFormat, appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, customTagsByFile, customTagsByStreamId, dispositionByStreamId, videoTimebase, onProgress: onCutProgress, @@ -420,7 +423,6 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut const { streams: streamsAfterCut } = await readFileMeta(smartCutMainPartOutPath); const outPath = await makeSegmentOutPath(); - await checkOverwrite(outPath); await concatFiles({ paths: smartCutSegmentsToConcat, outDir: outputDir, outPath, metadataFromPath: smartCutMainPartOutPath, outFormat, includeAllStreams: true, streams: streamsAfterCut, ffmpegExperimental, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, videoTimebase, appendFfmpegCommandLog, onProgress: onConcatProgress }); return outPath; @@ -436,7 +438,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut } finally { if (chaptersPath) await tryDeleteFiles([chaptersPath]); } - }, [concatFiles, cutSingle, filePath, needSmartCut]); + }, [concatFiles, cutSingle, filePath, needSmartCut, shouldSkipExistingFile]); const autoConcatCutSegments = useCallback(async ({ customOutDir, isCustomFormatSelected, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, autoDeleteMergedSegments, chapterNames, preserveMetadataOnMerge, appendFfmpegCommandLog }) => { const ext = getOutFileExtension({ isCustomFormatSelected, outFormat, filePath });