indicate when we are on a keyframe

(approximately)

closes #2155
pull/2510/head
Mikael Finstad 3 months ago
parent 250731e4ce
commit 250731e9aa
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -575,9 +575,15 @@ function App() {
const { thumbnailsSorted, setThumbnails } = useThumbnails({ filePath, zoomedDuration, zoomWindowStartTime, showThumbnails });
const { neighbouringKeyFrames, findNearestKeyFrameTime } = useKeyframes({ keyframesEnabled, filePath, commandedTime, videoStream: activeVideoStream, detectedFps, ffmpegExtractWindow });
const { neighbouringKeyFrames, findNearestKeyFrameTime, keyframeByNumber } = useKeyframes({ keyframesEnabled, filePath, commandedTime, videoStream: activeVideoStream, detectedFps, ffmpegExtractWindow });
const { waveforms, overviewWaveform, renderOverviewWaveform } = useWaveform({ filePath, relevantTime, waveformEnabled, audioStream: activeAudioStreams[0], ffmpegExtractWindow, fileDuration });
const currentFrame = useMemo(() => {
const frameNum = getFrameCount(commandedTime);
if (frameNum == null) return undefined;
return keyframeByNumber[frameNum];
}, [commandedTime, getFrameCount, keyframeByNumber]);
const onGenerateOverviewWaveformClick = useCallback(async () => {
if (working) return;
try {
@ -2734,6 +2740,7 @@ function App() {
formatTimecode={formatTimecode}
parseTimecode={parseTimecode}
playbackRate={playbackRate}
currentFrame={currentFrame}
/>
</div>

@ -26,6 +26,7 @@ import useUserSettings from './hooks/useUserSettings';
import { askForPlaybackRate } from './dialogs';
import { FormatTimecode, ParseTimecode, SegmentColorIndex, SegmentToExport, StateSegment } from './types';
import { WaveformMode } from '../../../types';
import { Frame } from './ffmpeg';
const { clipboard } = window.require('electron');
@ -150,10 +151,10 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
trySetTime(timeWithOffset);
}, [isEmptyEndTime, parseTimecode, trySetTime]);
function handleCutTimeInput(text: string) {
const handleCutTimeInput = useCallback((text: string) => {
if (isExactDurationMatch(text) || isEmptyEndTime(text)) parseAndSetCutTime(text);
else setCutTimeManual(text);
}
}, [isEmptyEndTime, parseAndSetCutTime]);
const tryPaste = useCallback((clipboardText: string) => {
try {
@ -190,7 +191,7 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
return (
<form onSubmit={handleSubmit}>
<input
style={{ ...cutTimeInputStyle, color: isCutTimeManualSet() ? 'var(--red-11)' : 'var(--gray-12)' }}
style={{ ...cutTimeInputStyle, color: isCutTimeManualSet() ? 'var(--gray-12)' : 'var(--gray-11)' }}
type="text"
title={isStart ? t('Manually input current segment\'s start time') : t('Manually input current segment\'s end time')}
onChange={(e) => handleCutTimeInput(e.target.value)}
@ -216,6 +217,7 @@ function BottomBar({
toggleShowThumbnails, toggleWaveformMode, waveformMode, showThumbnails,
outputPlaybackRate, setOutputPlaybackRate,
formatTimecode, parseTimecode, playbackRate,
currentFrame,
}: {
zoom: number,
setZoom: (fn: (z: number) => number) => void,
@ -264,6 +266,7 @@ function BottomBar({
formatTimecode: FormatTimecode,
parseTimecode: ParseTimecode,
playbackRate: number,
currentFrame: Frame | undefined,
}) {
const { t } = useTranslation();
const { getSegColor } = useSegColors();
@ -302,6 +305,10 @@ function BottomBar({
};
}, [selectedSegments]);
const keyframeStyle = useMemo(() => ({
color: currentFrame != null && currentFrame.keyframe ? primaryTextColor : undefined,
}), [currentFrame]);
const { invertCutSegments, setInvertCutSegments, simpleMode, toggleSimpleMode, exportConfirmEnabled } = useUserSettings();
const rotationStr = `${rotation}°`;
@ -414,7 +421,7 @@ function BottomBar({
size={25}
role="button"
title={t('Seek previous keyframe')}
style={{ flexShrink: 0, marginRight: 2, transform: mirrorTransform }}
style={{ flexShrink: 0, marginRight: 2, transform: mirrorTransform, ...keyframeStyle }}
onClick={() => seekClosestKeyframe(-1)}
/>
@ -446,7 +453,7 @@ function BottomBar({
)}
<IoMdKey
style={{ flexShrink: 0, marginLeft: 2 }}
style={{ flexShrink: 0, marginLeft: 2, ...keyframeStyle }}
size={25}
role="button"
title={t('Seek next keyframe')}

@ -63,12 +63,9 @@ function getIntervalAroundTime(time: number, window: number) {
};
}
interface Keyframe {
export interface Frame {
time: number,
createdAt: Date,
}
export interface Frame extends Keyframe {
keyframe: boolean
}
@ -99,14 +96,14 @@ export async function readKeyframesAroundTime({ filePath, streamIndex, aroundTim
return frames.filter((frame) => frame.keyframe);
}
export const findKeyframeAtExactTime = (keyframes: Keyframe[], time: number) => keyframes.find((keyframe) => Math.abs(keyframe.time - time) < 0.000001);
export const findNextKeyframe = (keyframes: Keyframe[], time: number) => keyframes.find((keyframe) => keyframe.time >= time); // (assume they are already sorted)
const findPreviousKeyframe = (keyframes: Keyframe[], time: number) => keyframes.findLast((keyframe) => keyframe.time <= time);
const findNearestKeyframe = (keyframes: Keyframe[], time: number) => minBy(keyframes, (keyframe) => Math.abs(keyframe.time - time));
export const findKeyframeAtExactTime = (keyframes: Frame[], time: number) => keyframes.find((keyframe) => Math.abs(keyframe.time - time) < 0.000001);
export const findNextKeyframe = (keyframes: Frame[], time: number) => keyframes.find((keyframe) => keyframe.time >= time); // (assume they are already sorted)
const findPreviousKeyframe = (keyframes: Frame[], time: number) => keyframes.findLast((keyframe) => keyframe.time <= time);
const findNearestKeyframe = (keyframes: Frame[], time: number) => minBy(keyframes, (keyframe) => Math.abs(keyframe.time - time));
export type FindKeyframeMode = 'nearest' | 'before' | 'after';
function findKeyframe(keyframes: Keyframe[], time: number, mode: FindKeyframeMode) {
function findKeyframe(keyframes: Frame[], time: number, mode: FindKeyframeMode) {
switch (mode) {
case 'nearest': {
return findNearestKeyframe(keyframes, time);
@ -139,7 +136,7 @@ export async function findKeyframeNearTime({ filePath, streamIndex, time, mode }
// todo this is not in use
// https://stackoverflow.com/questions/14005110/how-to-split-a-video-using-ffmpeg-so-that-each-chunk-starts-with-a-key-frame
// http://kicherer.org/joomla/index.php/de/blog/42-avcut-frame-accurate-video-cutting-with-only-small-quality-loss
export function getSafeCutTime(frames: (Frame & { time: number })[], cutTime: number, nextMode: boolean) {
export function getSafeCutTime(frames: Frame[], cutTime: number, nextMode: boolean) {
const sigma = 0.01;
const isCloseTo = (time1: number, time2: number) => Math.abs(time1 - time2) < sigma;

@ -4,6 +4,7 @@ import useDebounceOld from 'react-use/lib/useDebounce'; // Want to phase out thi
import { readFramesAroundTime, findNearestKeyFrameTime as ffmpegFindNearestKeyFrameTime, Frame } from '../ffmpeg';
import { FFprobeStream } from '../../../../ffprobe';
import { getFrameCountRaw } from '../edlFormats';
const maxKeyframes = 1000;
// const maxKeyframes = 100;
@ -20,6 +21,16 @@ function useKeyframes({ keyframesEnabled, filePath, commandedTime, videoStream,
const [neighbouringKeyFramesMap, setNeighbouringKeyFrames] = useState<Record<string, Frame>>({});
const neighbouringKeyFrames = useMemo(() => Object.values(neighbouringKeyFramesMap), [neighbouringKeyFramesMap]);
const keyframeByNumber = useMemo(() => {
const map: Record<number, Frame> = {};
if (detectedFps != null) {
neighbouringKeyFrames.forEach((frame) => {
map[getFrameCountRaw(detectedFps, frame.time)!] = frame;
});
}
return map;
}, [detectedFps, neighbouringKeyFrames]);
const findNearestKeyFrameTime = useCallback(({ time, direction }: { time: number, direction: number }) => ffmpegFindNearestKeyFrameTime({ frames: neighbouringKeyFrames, time, direction, fps: detectedFps }), [neighbouringKeyFrames, detectedFps]);
useEffect(() => setNeighbouringKeyFrames({}), [filePath, videoStream]);
@ -64,7 +75,7 @@ function useKeyframes({ keyframesEnabled, filePath, commandedTime, videoStream,
}, 500, [keyframesEnabled, filePath, commandedTime, videoStream, ffmpegExtractWindow]);
return {
neighbouringKeyFrames, findNearestKeyFrameTime,
neighbouringKeyFrames, findNearestKeyFrameTime, keyframeByNumber,
};
}

Loading…
Cancel
Save