improve thumbnails

pull/276/head
Mikael Finstad 6 years ago
parent 3378a5a88f
commit 9d6ce3d12d

@ -126,9 +126,15 @@ const Timeline = memo(({
{thumbnailsEnabled && (
<div style={{ height: timelineHeight, width: `${zoom * 100}%`, position: 'relative' }}>
{thumbnails.map((thumbnail) => (
<img key={thumbnail.url} src={thumbnail.url} alt="" style={{ position: 'absolute', left: `${(thumbnail.time / durationSafe) * 100}%`, height: timelineHeight * 1.5, zIndex: 1, maxWidth: '13%', objectFit: 'cover', border: '1px solid rgba(255, 255, 255, 0.5)', borderBottomRightRadius: 15, borderTopLeftRadius: 15, borderTopRightRadius: 15 }} />
))}
{thumbnails.map((thumbnail, i) => {
const leftPercent = (thumbnail.time / durationSafe) * 100;
const nextThumbnail = thumbnails[i + 1];
const nextThumbTime = nextThumbnail ? nextThumbnail.time : durationSafe;
const maxWidthPercent = ((nextThumbTime - thumbnail.time) / durationSafe) * 100 * 0.9;
return (
<img key={thumbnail.url} src={thumbnail.url} alt="" style={{ position: 'absolute', left: `${leftPercent}%`, height: timelineHeight * 1.5, zIndex: 1, maxWidth: `${maxWidthPercent}%`, objectFit: 'cover', border: '1px solid rgba(255, 255, 255, 0.5)', borderBottomRightRadius: 15, borderTopLeftRadius: 15, borderTopRightRadius: 15 }} />
);
})}
</div>
)}

@ -517,11 +517,22 @@ async function renderThumbnail(filePath, timestamp) {
return URL.createObjectURL(blob);
}
async function renderThumbnails({ filePath, numThumbs, from, duration, onThumbnail }) {
const thumbTimes = Array(numThumbs).fill().map((unused, i) => (from + ((duration * i) / numThumbs)));
async function renderThumbnails({ filePath, from, duration, onThumbnail }) {
// Time first render to determine how many to render
const startTime = new Date().getTime() / 1000;
let url = await renderThumbnail(filePath, from);
const endTime = new Date().getTime() / 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(numThumbs - 1).fill().map((unused, i) => (from + ((duration * (i + 1)) / (numThumbs))));
console.log(thumbTimes);
await pMap(thumbTimes, async (time) => {
const url = await renderThumbnail(filePath, time);
url = await renderThumbnail(filePath, time);
onThumbnail({ time, url });
}, { concurrency: 2 });
}

@ -98,8 +98,8 @@ function doesPlayerSupportFile(streams) {
}
const ffmpegExtractWindow = 60;
const calcShouldShowWaveform = (duration, zoom) => (duration != null && duration / zoom < ffmpegExtractWindow * 8);
const calcShouldShowKeyframes = (duration, zoom) => (duration != null && duration / zoom < ffmpegExtractWindow * 8);
const calcShouldShowWaveform = (zoomedDuration) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
const calcShouldShowKeyframes = (zoomedDuration) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
const commonFormats = ['mov', 'mp4', 'matroska', 'mp3', 'ipod'];
@ -172,10 +172,11 @@ const App = memo(() => {
}, 500, [cutSegments]);
const durationSafe = duration || 1;
const zoomedDuration = duration != null ? duration / zoom : undefined;
const [, cancelWaveformDataDebounce] = useDebounce(() => {
setDebouncedWaveformData({ filePath, commandedTime, duration, zoom, waveformEnabled, mainAudioStream });
}, 500, [filePath, commandedTime, duration, zoom, waveformEnabled, mainAudioStream]);
setDebouncedWaveformData({ filePath, commandedTime, zoomedDuration, waveformEnabled, mainAudioStream });
}, 500, [filePath, commandedTime, zoomedDuration, waveformEnabled, mainAudioStream]);
const [, cancelReadKeyframeDataDebounce] = useDebounce(() => {
setDebouncedReadKeyframesData({ keyframesEnabled, filePath, commandedTime, mainVideoStream });
@ -320,8 +321,8 @@ const App = memo(() => {
}, [seekAbs]);
const seekRelPercent = useCallback((val) => {
seekRel(val * (duration / zoom));
}, [seekRel, duration, zoom]);
seekRel(val * zoomedDuration);
}, [seekRel, zoomedDuration]);
const shortStep = useCallback((dir) => {
seekRel((1 / (detectedFps || 60)) * dir);
@ -686,8 +687,6 @@ const App = memo(() => {
setCutSegments(cutSegmentsNew);
}, [currentSegIndexSafe, cutSegments, setCutSegments]);
const shouldShowKeyframes = keyframesEnabled && !!mainVideoStream && calcShouldShowKeyframes(duration, zoom);
const thumnailsRef = useRef([]);
const thumnailsRenderingPromiseRef = useRef();
@ -701,9 +700,8 @@ const App = memo(() => {
if (!thumbnailsEnabled || thumnailsRenderingPromiseRef.current) return;
try {
const numThumbs = 5;
setThumbnails([]);
const promise = ffmpeg.renderThumbnails({ filePath, numThumbs, from: zoomWindowStartTime, duration: duration / zoom, onThumbnail: addThumbnail });
const promise = ffmpeg.renderThumbnails({ filePath, from: zoomWindowStartTime, duration: zoomedDuration, onThumbnail: addThumbnail });
thumnailsRenderingPromiseRef.current = promise;
await promise;
} catch (err) {
@ -714,7 +712,7 @@ const App = memo(() => {
}
if (duration) renderThumbnails();
}, [duration, filePath, zoom, zoomWindowStartTime, thumbnailsEnabled]);
}, [zoomedDuration, duration, filePath, zoomWindowStartTime, thumbnailsEnabled]);
// Cleanup removed thumbnails
useEffect(() => {
@ -748,7 +746,7 @@ const App = memo(() => {
useEffect(() => {
async function run() {
const d = debouncedWaveformData;
if (!d || !d.filePath || !d.mainAudioStream || d.commandedTime == null || !calcShouldShowWaveform(d.duration, d.zoom) || !d.waveformEnabled || creatingWaveformPromise.current) return;
if (!d || !d.filePath || !d.mainAudioStream || d.commandedTime == null || !calcShouldShowWaveform(d.zoomedDuration) || !d.waveformEnabled || creatingWaveformPromise.current) return;
try {
const promise = ffmpeg.renderWaveformPng({ filePath: d.filePath, aroundTime: d.commandedTime, window: ffmpegExtractWindow, color: waveformColor });
creatingWaveformPromise.current = promise;
@ -1429,9 +1427,12 @@ const App = memo(() => {
const hasAudio = !!mainAudioStream;
const hasVideo = !!mainVideoStream;
const shouldShowWaveform = calcShouldShowWaveform(duration, zoom);
const shouldShowKeyframes = keyframesEnabled && !!mainVideoStream && calcShouldShowKeyframes(zoomedDuration);
const shouldShowWaveform = calcShouldShowWaveform(zoomedDuration);
const bottomBarHeight = 96 + ((hasAudio && waveformEnabled) || (hasVideo && thumbnailsEnabled) ? timelineHeight : 0);
const thumbnailsSorted = useMemo(() => sortBy(thumbnails, thumbnail => thumbnail.time), [thumbnails]);
let timelineMode;
if (thumbnailsEnabled) timelineMode = 'thumbnails';
if (waveformEnabled) timelineMode = 'waveform';
@ -1633,7 +1634,7 @@ const App = memo(() => {
waveformEnabled={waveformEnabled}
thumbnailsEnabled={thumbnailsEnabled}
neighbouringFrames={neighbouringFrames}
thumbnails={thumbnails}
thumbnails={thumbnailsSorted}
getCurrentTime={getCurrentTime}
startTimeOffset={startTimeOffset}
playerTime={playerTime}

Loading…
Cancel
Save