1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-24 11:22:34 +01:00
This commit is contained in:
Mikael Finstad 2024-09-26 13:16:06 +02:00
parent 9cdd5edbfb
commit 7d306f7736
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
5 changed files with 52 additions and 49 deletions

View File

@ -122,7 +122,7 @@ function App() {
const [previewFilePath, setPreviewFilePath] = useState<string>();
const [usingDummyVideo, setUsingDummyVideo] = useState(false);
const [rotation, setRotation] = useState(360);
const [cutProgress, setCutProgress] = useState<number>();
const [progress, setProgress] = useState<number>();
const [startTimeOffset, setStartTimeOffset] = useState(0);
const [filePath, setFilePath] = useState<string>();
const [externalFilesMeta, setExternalFilesMeta] = useState<FilesMeta>({});
@ -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<number, number> = {};
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() {
)}
<AnimatePresence>
{working && <Working text={working.text} cutProgress={cutProgress} onAbortClick={abortWorking} />}
{working && <Working text={working.text} progress={progress} onAbortClick={abortWorking} />}
</AnimatePresence>
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible(undefined)} />}

View File

@ -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 (
<div style={{ position: 'absolute', bottom: 0, top: 0, left: 0, right: 0, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
@ -32,9 +32,9 @@ function Working({ text, cutProgress, onAbortClick }: {
{text}...
</div>
{(cutProgress != null) && (
{(progress != null) && (
<div style={{ marginTop: 5 }}>
{`${(cutProgress * 100).toFixed(1)} %`}
{`${(progress * 100).toFixed(1)} %`}
</div>
)}

View File

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

View File

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

View File

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