1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-21 18:02:35 +01:00

always render all thumbs

but abort processes when params change

#2229
This commit is contained in:
Mikael Finstad 2024-11-01 17:03:05 +08:00
parent 0e5e7dffea
commit 94a43986cb
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
3 changed files with 45 additions and 55 deletions

View File

@ -531,7 +531,7 @@ function App() {
const bigWaveformEnabled = waveformEnabled && waveformMode === 'big-waveform'; const bigWaveformEnabled = waveformEnabled && waveformMode === 'big-waveform';
const showThumbnails = thumbnailsEnabled && hasVideo; const showThumbnails = thumbnailsEnabled && hasVideo;
const { cancelRenderThumbnails, thumbnailsSorted, setThumbnails } = useThumbnails({ filePath, zoomedDuration, zoomWindowStartTime, showThumbnails }); const { thumbnailsSorted, setThumbnails } = useThumbnails({ filePath, zoomedDuration, zoomWindowStartTime, showThumbnails });
const shouldShowKeyframes = keyframesEnabled && hasVideo && calcShouldShowKeyframes(zoomedDuration); const shouldShowKeyframes = keyframesEnabled && hasVideo && calcShouldShowKeyframes(zoomedDuration);
const shouldShowWaveform = calcShouldShowWaveform(zoomedDuration); const shouldShowWaveform = calcShouldShowWaveform(zoomedDuration);
@ -581,9 +581,7 @@ function App() {
setHideMediaSourcePlayer(false); setHideMediaSourcePlayer(false);
setExportConfirmVisible(false); setExportConfirmVisible(false);
setOutputPlaybackRateState(1); setOutputPlaybackRateState(1);
}, [videoRef, setCommandedTime, setPlaybackRate, setPlaying, playingRef, playbackModeRef, setCompatPlayerEventId, setDuration, cutSegmentsHistory, clearSegments, setFileFormat, setDetectedFileFormat, setCopyStreamIdsByFile, setThumbnails, setDeselectedSegmentIds, setSubtitlesByStreamId, setOutputPlaybackRateState]);
cancelRenderThumbnails();
}, [videoRef, setCommandedTime, setPlaybackRate, setPlaying, playingRef, playbackModeRef, setCompatPlayerEventId, setDuration, cutSegmentsHistory, clearSegments, setFileFormat, setDetectedFileFormat, setCopyStreamIdsByFile, setThumbnails, setDeselectedSegmentIds, setSubtitlesByStreamId, setOutputPlaybackRateState, cancelRenderThumbnails]);
const showUnsupportedFileMessage = useCallback(() => { const showUnsupportedFileMessage = useCallback(() => {

View File

@ -506,7 +506,7 @@ export async function extractStreams({ filePath, customOutDir, streams, enableOv
]; ];
} }
async function renderThumbnail(filePath: string, timestamp: number) { async function renderThumbnail(filePath: string, timestamp: number, signal: AbortSignal) {
const args = [ const args = [
'-ss', String(timestamp), '-ss', String(timestamp),
'-i', filePath, '-i', filePath,
@ -517,7 +517,7 @@ async function renderThumbnail(filePath: string, timestamp: number) {
'-', '-',
]; ];
const { stdout } = await runFfmpeg(args); const { stdout } = await runFfmpeg(args, { signal });
const blob = new Blob([fixRemoteBuffer(stdout)], { type: 'image/jpeg' }); const blob = new Blob([fixRemoteBuffer(stdout)], { type: 'image/jpeg' });
return URL.createObjectURL(blob); return URL.createObjectURL(blob);
@ -561,24 +561,19 @@ export async function extractSubtitleTrackVtt(filePath: string, streamId: number
return URL.createObjectURL(blob); return URL.createObjectURL(blob);
} }
export async function renderThumbnails({ filePath, from, duration, onThumbnail }: { export async function renderThumbnails({ filePath, from, duration, onThumbnail, signal }: {
filePath: string, from: number, duration: number, onThumbnail: (a: { time: number, url: string }) => void, filePath: string,
from: number,
duration: number,
onThumbnail: (a: { time: number, url: string }) => void,
signal: AbortSignal,
}) { }) {
// Time first render to determine how many to render const numThumbs = 10;
const startTime = Date.now() / 1000; const thumbTimes = Array.from({ length: numThumbs }).fill(undefined).map((_unused, i) => (from + ((duration * i) / numThumbs)));
let url = await renderThumbnail(filePath, from);
const endTime = Date.now() / 1000;
onThumbnail({ time: from, url });
// Aim for max 3 sec to render all
const numThumbs = Math.floor(Math.min(Math.max(3 / (endTime - startTime), 3), 10));
// console.log(numThumbs);
const thumbTimes = Array.from({ length: numThumbs - 1 }).fill(undefined).map((_unused, i) => (from + ((duration * (i + 1)) / (numThumbs))));
// console.log(thumbTimes); // console.log(thumbTimes);
await pMap(thumbTimes, async (time) => { await pMap(thumbTimes, async (time) => {
url = await renderThumbnail(filePath, time); const url = await renderThumbnail(filePath, time, signal);
onThumbnail({ time, url }); onThumbnail({ time, url });
}, { concurrency: 2 }); }, { concurrency: 2 });
} }

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import useDebounceOld from 'react-use/lib/useDebounce'; // Want to phase out this import { useDebounce } from 'use-debounce';
import invariant from 'tiny-invariant'; import invariant from 'tiny-invariant';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
@ -15,51 +15,48 @@ export default ({ filePath, zoomedDuration, zoomWindowStartTime, showThumbnails
showThumbnails: boolean, showThumbnails: boolean,
}) => { }) => {
const [thumbnails, setThumbnails] = useState<Thumbnail[]>([]); const [thumbnails, setThumbnails] = useState<Thumbnail[]>([]);
const thumnailsRef = useRef<Thumbnail[]>([]);
const thumnailsRenderingPromiseRef = useRef<Promise<void>>();
function addThumbnail(thumbnail) { const [debounced] = useDebounce({ zoomedDuration, filePath, zoomWindowStartTime, showThumbnails }, 300, {
// console.log('Rendered thumbnail', thumbnail.url); equalityFn: (a, b) => JSON.stringify(a) === JSON.stringify(b),
setThumbnails((v) => [...v, thumbnail]); });
}
const [, cancelRenderThumbnails] = useDebounceOld(() => { useEffect(() => {
async function renderThumbnails() { const abortController = new AbortController();
if (!showThumbnails || thumnailsRenderingPromiseRef.current) return; const thumbnails2: Thumbnail[] = [];
(async () => {
if (!isDurationValid(debounced.zoomedDuration) || !debounced.showThumbnails) return;
try { try {
setThumbnails([]); invariant(debounced.filePath != null);
invariant(filePath != null); invariant(debounced.zoomedDuration != null);
invariant(zoomedDuration != null);
const promise = ffmpegRenderThumbnails({ filePath, from: zoomWindowStartTime, duration: zoomedDuration, onThumbnail: addThumbnail }); const addThumbnail = (t: Thumbnail) => {
thumnailsRenderingPromiseRef.current = promise; if (abortController.signal.aborted) return; // because the bridge is async
await promise; thumbnails2.push(t);
setThumbnails((v) => [...v, t]);
};
await ffmpegRenderThumbnails({ signal: abortController.signal, filePath: debounced.filePath, from: debounced.zoomWindowStartTime, duration: debounced.zoomedDuration, onThumbnail: addThumbnail });
} catch (err) { } catch (err) {
console.error('Failed to render thumbnail', err); if ((err as Error).name !== 'AbortError') {
} finally { console.error('Failed to render thumbnails', err);
thumnailsRenderingPromiseRef.current = undefined; }
} }
} })();
if (isDurationValid(zoomedDuration)) renderThumbnails(); return () => {
}, 500, [zoomedDuration, filePath, zoomWindowStartTime, showThumbnails]); abortController.abort();
console.log('Cleanup thumbnails', thumbnails2.map((t) => t.time));
// Cleanup removed thumbnails thumbnails2.forEach((thumbnail) => URL.revokeObjectURL(thumbnail.url));
useEffect(() => { setThumbnails([]);
thumnailsRef.current.forEach((thumbnail) => { };
if (!thumbnails.some((nextThumbnail) => nextThumbnail.url === thumbnail.url)) { }, [debounced]);
console.log('Cleanup thumbnail', thumbnail.time);
URL.revokeObjectURL(thumbnail.url);
}
});
thumnailsRef.current = thumbnails;
}, [thumbnails]);
const thumbnailsSorted = useMemo(() => sortBy(thumbnails, (thumbnail) => thumbnail.time), [thumbnails]); const thumbnailsSorted = useMemo(() => sortBy(thumbnails, (thumbnail) => thumbnail.time), [thumbnails]);
return { return {
thumbnailsSorted, thumbnailsSorted,
setThumbnails, setThumbnails,
cancelRenderThumbnails,
}; };
}; };