pull/728/head
Mikael Finstad 5 years ago
parent 21a2c767ac
commit 523bdb7daa
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -21,6 +21,8 @@ import isEqual from 'lodash/isEqual';
import useTimelineScroll from './hooks/useTimelineScroll'; import useTimelineScroll from './hooks/useTimelineScroll';
import useUserPreferences from './hooks/useUserPreferences'; import useUserPreferences from './hooks/useUserPreferences';
import useFfmpegOperations from './hooks/useFfmpegOperations'; import useFfmpegOperations from './hooks/useFfmpegOperations';
import useKeyframes from './hooks/useKeyframes';
import useWaveform from './hooks/useWaveform';
import NoFileLoaded from './NoFileLoaded'; import NoFileLoaded from './NoFileLoaded';
import Canvas from './Canvas'; import Canvas from './Canvas';
import TopMenu from './TopMenu'; import TopMenu from './TopMenu';
@ -36,14 +38,14 @@ import TimelineControls from './TimelineControls';
import ExportConfirm from './ExportConfirm'; import ExportConfirm from './ExportConfirm';
import ValueTuner from './components/ValueTuner'; import ValueTuner from './components/ValueTuner';
import { loadMifiLink } from './mifi'; import { loadMifiLink } from './mifi';
import { primaryColor, controlsBackground, waveformColor } from './colors'; import { primaryColor, controlsBackground } from './colors';
import allOutFormats from './outFormats'; import allOutFormats from './outFormats';
import { captureFrameFromTag, captureFrameFfmpeg } from './capture-frame'; import { captureFrameFromTag, captureFrameFfmpeg } from './capture-frame';
import { import {
defaultProcessedCodecTypes, getStreamFps, isCuttingStart, isCuttingEnd, defaultProcessedCodecTypes, getStreamFps, isCuttingStart, isCuttingEnd,
getDefaultOutFormat, getFormatData, renderThumbnails as ffmpegRenderThumbnails, getDefaultOutFormat, getFormatData, renderThumbnails as ffmpegRenderThumbnails,
readFrames, renderWaveformPng, extractStreams, getAllStreams, extractStreams, getAllStreams,
findNearestKeyFrameTime as ffmpegFindNearestKeyFrameTime, isStreamThumbnail, isAudioSupported, isIphoneHevc, tryReadChaptersToEdl, isStreamThumbnail, isAudioSupported, isIphoneHevc, tryReadChaptersToEdl,
getDuration, getTimecodeFromStreams, createChaptersFromSegments, getDuration, getTimecodeFromStreams, createChaptersFromSegments,
} from './ffmpeg'; } from './ffmpeg';
import { saveCsv, saveTsv, loadCsv, loadXmeml, loadCue, loadPbf, loadMplayerEdl, saveCsvHuman } from './edlStore'; import { saveCsv, saveTsv, loadCsv, loadXmeml, loadCue, loadPbf, loadMplayerEdl, saveCsvHuman } from './edlStore';
@ -92,7 +94,6 @@ const videoStyle = { width: '100%', height: '100%', objectFit: 'contain' };
const App = memo(() => { const App = memo(() => {
// Per project state // Per project state
const [waveform, setWaveform] = useState();
const [html5FriendlyPath, setHtml5FriendlyPath] = useState(); const [html5FriendlyPath, setHtml5FriendlyPath] = useState();
const [working, setWorking] = useState(); const [working, setWorking] = useState();
const [dummyVideoPath, setDummyVideoPath] = useState(false); const [dummyVideoPath, setDummyVideoPath] = useState(false);
@ -118,7 +119,6 @@ const App = memo(() => {
const [zoom, setZoom] = useState(1); const [zoom, setZoom] = useState(1);
const [commandedTime, setCommandedTime] = useState(0); const [commandedTime, setCommandedTime] = useState(0);
const [ffmpegCommandLog, setFfmpegCommandLog] = useState([]); const [ffmpegCommandLog, setFfmpegCommandLog] = useState([]);
const [neighbouringFrames, setNeighbouringFrames] = useState([]);
const [thumbnails, setThumbnails] = useState([]); const [thumbnails, setThumbnails] = useState([]);
const [shortestFlag, setShortestFlag] = useState(false); const [shortestFlag, setShortestFlag] = useState(false);
const [zoomWindowStartTime, setZoomWindowStartTime] = useState(0); const [zoomWindowStartTime, setZoomWindowStartTime] = useState(0);
@ -171,8 +171,6 @@ const App = memo(() => {
const [mifiLink, setMifiLink] = useState(); const [mifiLink, setMifiLink] = useState();
const videoRef = useRef(); const videoRef = useRef();
const readingKeyframesPromise = useRef();
const creatingWaveformPromise = useRef();
const currentTimeRef = useRef(); const currentTimeRef = useRef();
const isFileOpened = !!filePath; const isFileOpened = !!filePath;
@ -711,49 +709,13 @@ const App = memo(() => {
thumnailsRef.current = thumbnails; thumnailsRef.current = thumbnails;
}, [thumbnails]); }, [thumbnails]);
const [, cancelReadKeyframeDataDebounce] = useDebounceOld(() => {
async function run() {
// We still want to calculate keyframes even if not shouldShowKeyframes because maybe we want to step to closest keyframe
if (!keyframesEnabled || !filePath || !mainVideoStream || commandedTime == null || readingKeyframesPromise.current) return;
try {
const promise = readFrames({ filePath, aroundTime: commandedTime, stream: mainVideoStream.index, window: ffmpegExtractWindow });
readingKeyframesPromise.current = promise;
const newFrames = await promise;
// console.log(newFrames);
setNeighbouringFrames(newFrames);
} catch (err) {
console.error('Failed to read keyframes', err);
} finally {
readingKeyframesPromise.current = undefined;
}
}
run();
}, 500, [keyframesEnabled, filePath, commandedTime, mainVideoStream]);
const hasAudio = !!mainAudioStream; const hasAudio = !!mainAudioStream;
const hasVideo = !!mainVideoStream; const hasVideo = !!mainVideoStream;
const shouldShowKeyframes = keyframesEnabled && !!mainVideoStream && calcShouldShowKeyframes(zoomedDuration); const shouldShowKeyframes = keyframesEnabled && !!mainVideoStream && calcShouldShowKeyframes(zoomedDuration);
const shouldShowWaveform = calcShouldShowWaveform(zoomedDuration); const shouldShowWaveform = calcShouldShowWaveform(zoomedDuration);
const [, cancelWaveformDataDebounce] = useDebounceOld(() => { const { neighbouringFrames, findNearestKeyFrameTime } = useKeyframes({ keyframesEnabled, filePath, commandedTime, mainVideoStream, detectedFps, ffmpegExtractWindow });
async function run() { const { waveform } = useWaveform({ filePath, commandedTime, zoomedDuration, waveformEnabled, mainAudioStream, shouldShowWaveform, ffmpegExtractWindow });
if (!filePath || !mainAudioStream || commandedTime == null || !shouldShowWaveform || !waveformEnabled || creatingWaveformPromise.current) return;
try {
const promise = renderWaveformPng({ filePath, aroundTime: commandedTime, window: ffmpegExtractWindow, color: waveformColor });
creatingWaveformPromise.current = promise;
const wf = await promise;
setWaveform(wf);
} catch (err) {
console.error('Failed to render waveform', err);
} finally {
creatingWaveformPromise.current = undefined;
}
}
run();
}, 500, [filePath, commandedTime, zoomedDuration, waveformEnabled, mainAudioStream, shouldShowWaveform]);
const resetState = useCallback(() => { const resetState = useCallback(() => {
const video = videoRef.current; const video = videoRef.current;
@ -795,20 +757,11 @@ const App = memo(() => {
setExportConfirmVisible(false); setExportConfirmVisible(false);
setWaveform();
cancelWaveformDataDebounce();
setNeighbouringFrames([]);
cancelReadKeyframeDataDebounce();
setThumbnails([]); setThumbnails([]);
cancelRenderThumbnails(); cancelRenderThumbnails();
}, [cutSegmentsHistory, setCutSegments, cancelWaveformDataDebounce, cancelReadKeyframeDataDebounce, cancelRenderThumbnails]); }, [cutSegmentsHistory, setCutSegments, cancelRenderThumbnails]);
// Cleanup old
useEffect(() => () => waveform && URL.revokeObjectURL(waveform.url), [waveform]);
const showUnsupportedFileMessage = useCallback(() => { const showUnsupportedFileMessage = useCallback(() => {
if (!hideAllNotifications) toast.fire({ timer: 13000, text: i18n.t('File not natively supported. Preview may have no audio or low quality. The final export will however be lossless with audio. You may convert it from the menu for a better preview with audio.') }); if (!hideAllNotifications) toast.fire({ timer: 13000, text: i18n.t('File not natively supported. Preview may have no audio or low quality. The final export will however be lossless with audio. You may convert it from the menu for a better preview with audio.') });
}, [hideAllNotifications]); }, [hideAllNotifications]);
@ -1356,8 +1309,6 @@ const App = memo(() => {
const jumpSeg = useCallback((val) => setCurrentSegIndex((old) => Math.max(Math.min(old + val, cutSegments.length - 1), 0)), [cutSegments.length]); const jumpSeg = useCallback((val) => setCurrentSegIndex((old) => Math.max(Math.min(old + val, cutSegments.length - 1), 0)), [cutSegments.length]);
const findNearestKeyFrameTime = useCallback(({ time, direction }) => ffmpegFindNearestKeyFrameTime({ frames: neighbouringFrames, time, direction, fps: detectedFps }), [neighbouringFrames, detectedFps]);
const seekClosestKeyframe = useCallback((direction) => { const seekClosestKeyframe = useCallback((direction) => {
const time = findNearestKeyFrameTime({ time: currentTimeRef.current, direction }); const time = findNearestKeyFrameTime({ time: currentTimeRef.current, direction });
if (time == null) return; if (time == null) return;

@ -0,0 +1,43 @@
import { useState, useCallback, useRef, useEffect } from 'react';
import useDebounceOld from 'react-use/lib/useDebounce'; // Want to phase out this
import { readFrames, findNearestKeyFrameTime as ffmpegFindNearestKeyFrameTime } from '../ffmpeg';
export default ({ keyframesEnabled, filePath, commandedTime, mainVideoStream, detectedFps, ffmpegExtractWindow }) => {
const readingKeyframesPromise = useRef();
const [neighbouringFrames, setNeighbouringFrames] = useState([]);
const findNearestKeyFrameTime = useCallback(({ time, direction }) => ffmpegFindNearestKeyFrameTime({ frames: neighbouringFrames, time, direction, fps: detectedFps }), [neighbouringFrames, detectedFps]);
useEffect(() => {
setNeighbouringFrames([]);
}, [filePath]);
useDebounceOld(() => {
// We still want to calculate keyframes even if not shouldShowKeyframes because maybe we want to be able to step to the closest keyframe
const shouldRun = () => keyframesEnabled && filePath && mainVideoStream && commandedTime != null;
async function run() {
if (!shouldRun() || readingKeyframesPromise.current) return;
try {
const promise = readFrames({ filePath, aroundTime: commandedTime, stream: mainVideoStream.index, window: ffmpegExtractWindow });
readingKeyframesPromise.current = promise;
const newFrames = await promise;
if (!shouldRun()) return;
// console.log(newFrames);
setNeighbouringFrames(newFrames);
} catch (err) {
console.error('Failed to read keyframes', err);
} finally {
readingKeyframesPromise.current = undefined;
}
}
run();
}, 500, [keyframesEnabled, filePath, commandedTime, mainVideoStream]);
return {
neighbouringFrames, findNearestKeyFrameTime,
};
};

@ -0,0 +1,40 @@
import { useState, useRef, useEffect } from 'react';
import useDebounceOld from 'react-use/lib/useDebounce'; // Want to phase out this
import { waveformColor } from '../colors';
import { renderWaveformPng } from '../ffmpeg';
export default ({ filePath, commandedTime, zoomedDuration, waveformEnabled, mainAudioStream, shouldShowWaveform, ffmpegExtractWindow }) => {
const creatingWaveformPromise = useRef();
const [waveform, setWaveform] = useState();
useEffect(() => {
setWaveform();
}, [filePath]);
useDebounceOld(() => {
const shouldRun = () => filePath && mainAudioStream && commandedTime != null && shouldShowWaveform && waveformEnabled;
async function run() {
if (!shouldRun() || creatingWaveformPromise.current) return;
try {
const promise = renderWaveformPng({ filePath, aroundTime: commandedTime, window: ffmpegExtractWindow, color: waveformColor });
creatingWaveformPromise.current = promise;
if (!shouldRun()) return;
const wf = await promise;
setWaveform(wf);
} catch (err) {
console.error('Failed to render waveform', err);
} finally {
creatingWaveformPromise.current = undefined;
}
}
run();
}, 500, [filePath, commandedTime, zoomedDuration, waveformEnabled, mainAudioStream, shouldShowWaveform]);
// Cleanup old
useEffect(() => () => waveform && URL.revokeObjectURL(waveform.url), [waveform]);
return { waveform };
};
Loading…
Cancel
Save