diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 1e34912d..d2e8adde 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -122,7 +122,7 @@ function App() { const [previewFilePath, setPreviewFilePath] = useState(); const [usingDummyVideo, setUsingDummyVideo] = useState(false); const [rotation, setRotation] = useState(360); - const [cutProgress, setCutProgress] = useState(); + const [progress, setProgress] = useState(); const [startTimeOffset, setStartTimeOffset] = useState(0); const [filePath, setFilePath] = useState(); const [externalFilesMeta, setExternalFilesMeta] = useState({}); @@ -183,9 +183,9 @@ function App() { const zoom = Math.floor(zoomUnrounded); const zoomedDuration = isDurationValid(duration) ? duration / zoom : undefined; - useEffect(() => setDocumentTitle({ filePath, working: working?.text, cutProgress }), [cutProgress, filePath, working?.text]); + useEffect(() => setDocumentTitle({ filePath, working: working?.text, progress }), [progress, filePath, working?.text]); - useEffect(() => setProgressBar(cutProgress ?? -1), [cutProgress]); + useEffect(() => setProgressBar(progress ?? -1), [progress]); useEffect(() => { ffmpegSetCustomFfPath(customFfPath); @@ -330,7 +330,7 @@ function App() { const { cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, focusSegmentAtCursor, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, - } = useSegments({ filePath, workingRef, setWorking, setCutProgress, videoStream: activeVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode }); + } = useSegments({ filePath, workingRef, setWorking, setProgress, videoStream: activeVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode }); const { getEdlFilePath, getEdlFilePathOld, projectFileSavePath, getProjectFileSavePath } = useSegmentsAutoSave({ autoSaveProjectFile, storeProjectInWorkingDir, filePath, customOutDir, cutSegments }); @@ -563,7 +563,7 @@ function App() { setFileFormat(undefined); setDetectedFileFormat(undefined); setRotation(360); - setCutProgress(undefined); + setProgress(undefined); setStartTimeOffset(0); setFilePath(undefined); setExternalFilesMeta({}); @@ -614,19 +614,19 @@ function App() { if (speed === 'fastest') { const path = getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: `${html5ifiedPrefix}${html5dummySuffix}.mkv` }); try { - setCutProgress(0); - await html5ifyDummy({ filePath: fp, outPath: path, onProgress: setCutProgress }); + setProgress(0); + await html5ifyDummy({ filePath: fp, outPath: path, onProgress: setProgress }); } finally { - setCutProgress(undefined); + setProgress(undefined); } return path; } try { const shouldIncludeVideo = !usesDummyVideo && hv; - return await html5ify({ customOutDir: cod, filePath: fp, speed, hasAudio: ha, hasVideo: shouldIncludeVideo, onProgress: setCutProgress }); + return await html5ify({ customOutDir: cod, filePath: fp, speed, hasAudio: ha, hasVideo: shouldIncludeVideo, onProgress: setProgress }); } finally { - setCutProgress(undefined); + setProgress(undefined); } } @@ -643,14 +643,14 @@ function App() { const failedFiles: string[] = []; let i = 0; - const setTotalProgress = (fileProgress = 0) => setCutProgress((i + fileProgress) / filePaths.length); + const setTotalProgress = (fileProgress = 0) => setProgress((i + fileProgress) / filePaths.length); const { selectedOption: speed } = await askForHtml5ifySpeed({ allowedOptions: ['fast-audio-remux', 'fast-audio', 'fast', 'slow', 'slow-audio', 'slowest'] }); if (!speed) return; if (workingRef.current) return; setWorking({ text: i18n.t('Batch converting to supported format') }); - setCutProgress(0); + setProgress(0); try { await withErrorHandling(async () => { // eslint-disable-next-line no-restricted-syntax @@ -676,7 +676,7 @@ function App() { }, i18n.t('Failed to batch convert to supported format')); } finally { setWorking(undefined); - setCutProgress(undefined); + setProgress(undefined); } }, [batchFiles, customOutDir, ensureWritableOutDir, html5ify, setWorking, workingRef]); @@ -848,7 +848,7 @@ function App() { // console.log('merge', paths); const metadataFromPath = paths[0]; invariant(metadataFromPath != null); - const { haveExcludedStreams } = await concatFiles({ paths, outPath, outDir, outFormat, metadataFromPath, includeAllStreams, streams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments }); + const { haveExcludedStreams } = await concatFiles({ paths, outPath, outDir, outFormat, metadataFromPath, includeAllStreams, streams, ffmpegExperimental, onProgress: setProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments }); const warnings: string[] = []; const notices: string[] = []; @@ -892,7 +892,7 @@ function App() { handleConcatFailed(err, reportState); } finally { setWorking(undefined); - setCutProgress(undefined); + setProgress(undefined); } }, [workingRef, setWorking, ensureWritableOutDir, customOutDir, segmentsToChapters, concatFiles, ffmpegExperimental, preserveMovData, movFastStart, preserveMetadataOnMerge, closeBatch, hideAllNotifications, showOsNotification, handleConcatFailed]); @@ -1018,7 +1018,7 @@ function App() { keyframeCut, segments: segmentsToExport, outSegFileNames, - onProgress: setCutProgress, + onProgress: setProgress, shortestFlag, ffmpegExperimental, preserveMovData, @@ -1036,7 +1036,7 @@ function App() { if (willMerge) { console.log('mergedFileTemplateOrDefault', mergedFileTemplateOrDefault); - setCutProgress(0); + setProgress(0); setWorking({ text: i18n.t('Merging') }); const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined; @@ -1057,7 +1057,7 @@ function App() { ffmpegExperimental, preserveMovData, movFastStart, - onProgress: setCutProgress, + onProgress: setProgress, chapterNames, autoDeleteMergedSegments, preserveMetadataOnMerge, @@ -1081,7 +1081,7 @@ function App() { if (exportExtraStreams) { try { - setCutProgress(undefined); // If extracting extra streams takes a long time, prevent loader from being stuck at 100% + setProgress(undefined); // If extracting extra streams takes a long time, prevent loader from being stuck at 100% setWorking({ text: i18n.t('Extracting {{count}} unprocessable tracks', { count: nonCopiedExtraStreams.length }) }); await extractStreams({ filePath, customOutDir, streams: nonCopiedExtraStreams, enableOverwriteOutput }); notices.push(i18n.t('Unprocessable streams were exported as separate files.')); @@ -1128,7 +1128,7 @@ function App() { handleExportFailed(err); } finally { setWorking(undefined); - setCutProgress(undefined); + setProgress(undefined); } }, [filePath, numStreamsToCopy, segmentsToExport, haveInvalidSegs, workingRef, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, paramsByStreamId, detectedFps, willMerge, enableOverwriteOutput, exportConfirmEnabled, mainFileFormatData, mainStreams, exportExtraStreams, areWeCutting, hideAllNotifications, cleanupChoices.cleanupAfterExport, cleanupFilesWithDialog, selectedSegmentsOrInverse, mergedFileTemplateOrDefault, segmentsToChapters, invertCutSegments, generateMergedFileNames, autoConcatCutSegments, autoDeleteMergedSegments, nonCopiedExtraStreams, showOsNotification, handleExportFailed]); @@ -1170,15 +1170,15 @@ function App() { setWorking({ text: i18n.t('Extracting frames') }); console.log('Extracting frames as images', { segIds, captureFramesResponse }); - setCutProgress(0); + setProgress(0); let lastOutPath: string | undefined; const segmentProgresses: Record = {}; - const handleSegmentProgress = (segIndex: number, progress: number) => { - segmentProgresses[segIndex] = progress; + const handleSegmentProgress = (segIndex: number, segmentProgress: number) => { + segmentProgresses[segIndex] = segmentProgress; const totalProgress = segments.reduce((acc, _ignored, index) => acc + (segmentProgresses[index] ?? 0), 0); - setCutProgress(totalProgress / segments.length); + setProgress(totalProgress / segments.length); }; // eslint-disable-next-line no-restricted-syntax @@ -1186,7 +1186,7 @@ function App() { const { start, end } = segment; if (filePath == null) throw new Error(); // eslint-disable-next-line no-await-in-loop - lastOutPath = await captureFramesRange({ customOutDir, filePath, fps: detectedFps, fromTime: start, toTime: end, estimatedMaxNumFiles: captureFramesResponse.estimatedMaxNumFiles, captureFormat, quality: captureFrameQuality, filter: captureFramesResponse.filter, outputTimestamps: captureFrameFileNameFormat === 'timestamp', onProgress: (progress) => handleSegmentProgress(index, progress) }); + lastOutPath = await captureFramesRange({ customOutDir, filePath, fps: detectedFps, fromTime: start, toTime: end, estimatedMaxNumFiles: captureFramesResponse.estimatedMaxNumFiles, captureFormat, quality: captureFrameQuality, filter: captureFramesResponse.filter, outputTimestamps: captureFrameFileNameFormat === 'timestamp', onProgress: (segmentProgress) => handleSegmentProgress(index, segmentProgress) }); } if (!hideAllNotifications && lastOutPath != null) { showOsNotification(i18n.t('Frames have been extracted')); @@ -1197,7 +1197,7 @@ function App() { handleError(err); } finally { setWorking(undefined); - setCutProgress(undefined); + setProgress(undefined); } }, [apparentCutSegments, captureFormat, captureFrameFileNameFormat, captureFrameQuality, captureFramesRange, customOutDir, detectedFps, filePath, getFrameCount, hideAllNotifications, outputDir, setWorking, showOsNotification, workingRef]); @@ -1600,16 +1600,16 @@ function App() { try { await withErrorHandling(async () => { setWorking({ text: i18n.t('Fixing file duration') }); - setCutProgress(0); + setProgress(0); invariant(fileFormat != null); - const path = await fixInvalidDuration({ fileFormat, customOutDir, onProgress: setCutProgress }); + const path = await fixInvalidDuration({ fileFormat, customOutDir, onProgress: setProgress }); showNotification({ icon: 'info', text: i18n.t('Duration has been fixed') }); await loadMedia({ filePath: path }); }, i18n.t('Failed to fix file duration')); } finally { setWorking(undefined); - setCutProgress(undefined); + setProgress(undefined); } }, [checkFileOpened, customOutDir, fileFormat, fixInvalidDuration, loadMedia, setWorking, showNotification, workingRef]); @@ -2438,7 +2438,7 @@ function App() { )} - {working && } + {working && } {tunerVisible && setTunerVisible(undefined)} />} diff --git a/src/renderer/src/components/Working.tsx b/src/renderer/src/components/Working.tsx index b76118e6..ee7d1026 100644 --- a/src/renderer/src/components/Working.tsx +++ b/src/renderer/src/components/Working.tsx @@ -8,8 +8,8 @@ import { primaryColor } from '../colors'; import loadingLottie from '../7077-magic-flow.json'; -function Working({ text, cutProgress, onAbortClick }: { - text: string, cutProgress?: number | undefined, onAbortClick: () => void +function Working({ text, progress, onAbortClick }: { + text: string, progress?: number | undefined, onAbortClick: () => void }) { return (
@@ -32,9 +32,9 @@ function Working({ text, cutProgress, onAbortClick }: { {text}...
- {(cutProgress != null) && ( + {(progress != null) && (
- {`${(cutProgress * 100).toFixed(1)} %`} + {`${(progress * 100).toFixed(1)} %`}
)} diff --git a/src/renderer/src/hooks/useFfmpegOperations.ts b/src/renderer/src/hooks/useFfmpegOperations.ts index b7a39d9f..5a409ffe 100644 --- a/src/renderer/src/hooks/useFfmpegOperations.ts +++ b/src/renderer/src/hooks/useFfmpegOperations.ts @@ -459,7 +459,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea console.log('Smart cut on video stream', videoStreamIndex); - const onCutProgress = (progress: number) => onSingleProgress(i, progress / 2); + const onProgress = (progress: number) => onSingleProgress(i, progress / 2); const onConcatProgress = (progress: number) => onSingleProgress(i, (1 + progress) / 2); const copyFileStreamsFiltered = [{ @@ -501,7 +501,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea // for smart cut we need to use keyframe cut here, and no avoid_negative_ts await losslessCutSingle({ - cutFrom: losslessCutFrom, cutTo, chaptersPath, outPath: losslessPartOutPath, copyFileStreams: copyFileStreamsFiltered, keyframeCut: true, avoidNegativeTs: undefined, videoDuration, rotation, allFilesMeta, outFormat, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, customTagsByFile, paramsByStreamId, videoTimebase, onProgress: onCutProgress, + cutFrom: losslessCutFrom, cutTo, chaptersPath, outPath: losslessPartOutPath, copyFileStreams: copyFileStreamsFiltered, keyframeCut: true, avoidNegativeTs: undefined, videoDuration, rotation, allFilesMeta, outFormat, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, customTagsByFile, paramsByStreamId, videoTimebase, onProgress, }); // OK, just return the single cut file (we may need smart cut in other segments though) diff --git a/src/renderer/src/hooks/useSegments.ts b/src/renderer/src/hooks/useSegments.ts index b48fba38..9dcbb585 100644 --- a/src/renderer/src/hooks/useSegments.ts +++ b/src/renderer/src/hooks/useSegments.ts @@ -21,11 +21,11 @@ import { FFprobeStream } from '../../../../ffprobe'; const { ffmpeg: { blackDetect, silenceDetect } } = window.require('@electron/remote').require('./index.js'); -function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode }: { +function useSegments({ filePath, workingRef, setWorking, setProgress, videoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode }: { filePath?: string | undefined, workingRef: MutableRefObject, setWorking: (w: { text: string, abortController?: AbortController } | undefined) => void, - setCutProgress: (a: number | undefined) => void, + setProgress: (a: number | undefined) => void, videoStream: FFprobeStream | undefined, duration?: number | undefined, getRelevantTime: () => number, @@ -100,7 +100,7 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt if (workingRef.current) return; try { setWorking({ text: workingText }); - setCutProgress(0); + setProgress(0); const newSegments = await fn(); console.log(name, newSegments); @@ -109,9 +109,9 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt if (!(err instanceof Error && err.name === 'AbortError')) handleError(errorText, err); } finally { setWorking(undefined); - setCutProgress(undefined); + setProgress(undefined); } - }, [filePath, workingRef, setWorking, setCutProgress, loadCutSegments]); + }, [filePath, workingRef, setWorking, setProgress, loadCutSegments]); const getSegApparentEnd = useCallback((seg: SegmentBase) => getSegApparentEnd2(seg, duration), [duration]); @@ -148,8 +148,8 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt const { mode, ...filterOptions } = parameters; invariant(mode === '1' || mode === '2'); invariant(filePath != null); - await detectSegments({ name: 'blackScenes', workingText: i18n.t('Detecting black scenes'), errorText: i18n.t('Failed to detect black scenes'), fn: async () => blackDetect({ filePath, filterOptions, boundingMode: mode === '1', onProgress: setCutProgress, from: currentApparentCutSeg.start, to: currentApparentCutSeg.end }) }); - }, [currentApparentCutSeg.end, currentApparentCutSeg.start, detectSegments, filePath, setCutProgress]); + await detectSegments({ name: 'blackScenes', workingText: i18n.t('Detecting black scenes'), errorText: i18n.t('Failed to detect black scenes'), fn: async () => blackDetect({ filePath, filterOptions, boundingMode: mode === '1', onProgress: setProgress, from: currentApparentCutSeg.start, to: currentApparentCutSeg.end }) }); + }, [currentApparentCutSeg.end, currentApparentCutSeg.start, detectSegments, filePath, setProgress]); const detectSilentScenes = useCallback(async () => { const parameters = await showParametersDialog({ title: i18n.t('Enter parameters'), parameters: ffmpegParameters.silencedetect(), docUrl: 'https://ffmpeg.org/ffmpeg-filters.html#silencedetect' }); @@ -157,8 +157,8 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt const { mode, ...filterOptions } = parameters; invariant(mode === '1' || mode === '2'); invariant(filePath != null); - await detectSegments({ name: 'silentScenes', workingText: i18n.t('Detecting silent scenes'), errorText: i18n.t('Failed to detect silent scenes'), fn: async () => silenceDetect({ filePath, filterOptions, boundingMode: mode === '1', onProgress: setCutProgress, from: currentApparentCutSeg.start, to: currentApparentCutSeg.end }) }); - }, [currentApparentCutSeg.end, currentApparentCutSeg.start, detectSegments, filePath, setCutProgress]); + await detectSegments({ name: 'silentScenes', workingText: i18n.t('Detecting silent scenes'), errorText: i18n.t('Failed to detect silent scenes'), fn: async () => silenceDetect({ filePath, filterOptions, boundingMode: mode === '1', onProgress: setProgress, from: currentApparentCutSeg.start, to: currentApparentCutSeg.end }) }); + }, [currentApparentCutSeg.end, currentApparentCutSeg.start, detectSegments, filePath, setProgress]); const detectSceneChanges = useCallback(async () => { const filterOptions = await showParametersDialog({ title: i18n.t('Enter parameters'), parameters: ffmpegParameters.sceneChange() }); @@ -167,8 +167,8 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt // eslint-disable-next-line prefer-destructuring const minChange = filterOptions['minChange']; invariant(minChange != null); - await detectSegments({ name: 'sceneChanges', workingText: i18n.t('Detecting scene changes'), errorText: i18n.t('Failed to detect scene changes'), fn: async () => ffmpegDetectSceneChanges({ filePath, minChange, onProgress: setCutProgress, from: currentApparentCutSeg.start, to: currentApparentCutSeg.end }) }); - }, [currentApparentCutSeg.end, currentApparentCutSeg.start, detectSegments, filePath, setCutProgress]); + await detectSegments({ name: 'sceneChanges', workingText: i18n.t('Detecting scene changes'), errorText: i18n.t('Failed to detect scene changes'), fn: async () => ffmpegDetectSceneChanges({ filePath, minChange, onProgress: setProgress, from: currentApparentCutSeg.start, to: currentApparentCutSeg.end }) }); + }, [currentApparentCutSeg.end, currentApparentCutSeg.start, detectSegments, filePath, setProgress]); const createSegmentsFromKeyframes = useCallback(async () => { if (!videoStream) return; diff --git a/src/renderer/src/util.ts b/src/renderer/src/util.ts index 3da4da2c..ad367dcf 100644 --- a/src/renderer/src/util.ts +++ b/src/renderer/src/util.ts @@ -432,11 +432,14 @@ export function checkFileSizes(inputSize: number, outputSize: number) { return undefined; } -export function setDocumentTitle({ filePath, working, cutProgress }: { filePath?: string | undefined, working?: string | undefined, cutProgress?: number | undefined }) { +export function setDocumentTitle({ filePath, working, progress }: { + filePath?: string | undefined, + working?: string | undefined, + progress?: number | undefined }) { const parts: string[] = []; if (working) { - if (cutProgress != null) parts.push(`${(cutProgress * 100).toFixed(1)}%`); + if (progress != null) parts.push(`${(progress * 100).toFixed(1)}%`); parts.push(working); }