usability improvements

pull/2233/head
Mikael Finstad 1 year ago
parent e7629cf50c
commit 814866fd13
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -346,6 +346,8 @@ function BottomBar({
const PlayPause = playing ? FaPause : FaPlay;
const currentCutSegOrDefault = useMemo(() => currentCutSeg ?? { segColorIndex: 0 }, [currentCutSeg]);
return (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
@ -404,7 +406,7 @@ function BottomBar({
</>
)}
<SetCutpointButton currentCutSeg={currentCutSeg} side="start" onClick={setCutStart} title={t('Start current segment at current time')} style={{ marginRight: 5 }} />
<SetCutpointButton currentCutSeg={currentCutSegOrDefault} side="start" onClick={setCutStart} title={t('Start current segment at current time')} style={{ marginRight: 5 }} />
{!simpleMode && <CutTimeInput darkMode={darkMode} currentCutSeg={currentCutSeg} startTimeOffset={startTimeOffset} seekAbs={seekAbs} cutTime={currentCutSeg?.start} setCutTime={setCutTime} isStart formatTimecode={formatTimecode} parseTimecode={parseTimecode} />}

@ -1,4 +1,4 @@
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation, Trans } from 'react-i18next';
@ -18,6 +18,8 @@ function NoFileLoaded({ mifiLink, currentCutSeg, onClick, darkMode }: {
const { t } = useTranslation();
const { simpleMode } = useUserSettings();
const currentCutSegOrDefault = useMemo(() => currentCutSeg ?? { segColorIndex: 0 }, [currentCutSeg]);
return (
<div
className="no-user-select"
@ -32,7 +34,7 @@ function NoFileLoaded({ mifiLink, currentCutSeg, onClick, darkMode }: {
</div>
<div style={{ fontSize: '1.3em', color: 'var(--gray11)' }}>
<Trans><SetCutpointButton currentCutSeg={currentCutSeg} side="start" style={{ verticalAlign: 'middle' }} /> <SetCutpointButton currentCutSeg={currentCutSeg} side="end" style={{ verticalAlign: 'middle' }} /> or <kbd>I</kbd> <kbd>O</kbd> to set cutpoints</Trans>
<Trans><SetCutpointButton currentCutSeg={currentCutSegOrDefault} side="start" style={{ verticalAlign: 'middle' }} /> <SetCutpointButton currentCutSeg={currentCutSegOrDefault} side="end" style={{ verticalAlign: 'middle' }} /> or <kbd>I</kbd> <kbd>O</kbd> to set cutpoints</Trans>
</div>
<div style={{ fontSize: '1.3em', color: 'var(--gray11)' }} role="button" onClick={(e) => e.stopPropagation()}>

@ -3,10 +3,10 @@ import { FaStepForward } from 'react-icons/fa';
import { useSegColors } from '../contexts';
import useUserSettings from '../hooks/useUserSettings';
import { SegmentBase, SegmentColorIndex } from '../types';
import { SegmentColorIndex } from '../types';
const SegmentCutpointButton = ({ currentCutSeg, side, Icon, onClick, title, style }: {
currentCutSeg: (SegmentBase & SegmentColorIndex) | undefined,
currentCutSeg: SegmentColorIndex | undefined,
side: 'start' | 'end',
Icon: typeof FaStepForward,
onClick?: (() => void) | undefined,

@ -3,11 +3,11 @@ import { FaHandPointUp } from 'react-icons/fa';
import SegmentCutpointButton from './SegmentCutpointButton';
import { mirrorTransform } from '../util';
import { SegmentBase, SegmentColorIndex } from '../types';
import { SegmentColorIndex } from '../types';
// constant side because we are mirroring
const SetCutpointButton = ({ currentCutSeg, side, title, onClick, style }: {
currentCutSeg: (SegmentBase & SegmentColorIndex) | undefined,
currentCutSeg: SegmentColorIndex | undefined,
side: 'start' | 'end',
title?: string,
onClick?: () => void,

@ -163,8 +163,14 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
const haveInvalidSegs = useMemo(() => cutSegments.some((cutSegment) => cutSegment.end != null && cutSegment.start >= cutSegment.end), [cutSegments]);
const currentSegIndexSafe = Math.min(currentSegIndex, cutSegments.length - 1);
const currentCutSeg = useMemo(() => cutSegments[currentSegIndexSafe], [currentSegIndexSafe, cutSegments]);
const currentCutSegOrWholeTimeline = useMemo(() => {
const { start = 0, end = fileDurationNonZero } = currentCutSeg ?? {};
return { start, end };
}, [currentCutSeg, fileDurationNonZero]);
const selectedSegmentsRaw = useMemo(() => cutSegments.filter((segment) => isSegmentSelected(segment)), [cutSegments, isSegmentSelected]);
const getFfmpegParameters = useCallback((key: FfmpegDialog) => {
@ -174,7 +180,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
}, [ffmpegParameters]);
const detectBlackScenes = useCallback(async () => {
const { start = 0, end = fileDurationNonZero } = currentCutSeg ?? {};
const { start, end } = currentCutSegOrWholeTimeline;
const dialogType = 'blackdetect';
const parameters = await showParametersDialog({ title: i18n.t('Enter parameters'), dialogType, parameters: getFfmpegParameters(dialogType), docUrl: 'https://ffmpeg.org/ffmpeg-filters.html#blackdetect' });
if (parameters == null) return;
@ -183,10 +189,10 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
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 (onSegmentDetected) => blackDetect({ filePath, filterOptions, boundingMode: mode === '1', onProgress: setProgress, onSegmentDetected, from: start, to: end }) });
}, [fileDurationNonZero, currentCutSeg, getFfmpegParameters, setFfmpegParametersForDialog, filePath, detectSegments, setProgress]);
}, [currentCutSegOrWholeTimeline, getFfmpegParameters, setFfmpegParametersForDialog, filePath, detectSegments, setProgress]);
const detectSilentScenes = useCallback(async () => {
const { start = 0, end = fileDurationNonZero } = currentCutSeg ?? {};
const { start, end } = currentCutSegOrWholeTimeline;
const dialogType = 'silencedetect';
const parameters = await showParametersDialog({ title: i18n.t('Enter parameters'), dialogType, parameters: getFfmpegParameters(dialogType), docUrl: 'https://ffmpeg.org/ffmpeg-filters.html#silencedetect' });
if (parameters == null) return;
@ -195,10 +201,10 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
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 (onSegmentDetected) => silenceDetect({ filePath, filterOptions, boundingMode: mode === '1', onProgress: setProgress, onSegmentDetected, from: start, to: end }) });
}, [currentCutSeg, detectSegments, fileDurationNonZero, filePath, getFfmpegParameters, setFfmpegParametersForDialog, setProgress]);
}, [currentCutSegOrWholeTimeline, detectSegments, filePath, getFfmpegParameters, setFfmpegParametersForDialog, setProgress]);
const detectSceneChanges = useCallback(async () => {
const { start = 0, end = fileDurationNonZero } = currentCutSeg ?? {};
const { start, end } = currentCutSegOrWholeTimeline;
const dialogType = 'sceneChange';
const parameters = await showParametersDialog({ title: i18n.t('Enter parameters'), dialogType, parameters: getFfmpegParameters(dialogType) });
if (parameters == null) return;
@ -208,16 +214,16 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
const minChange = parameters['minChange'];
invariant(minChange != null);
await detectSegments({ name: 'sceneChanges', workingText: i18n.t('Detecting scene changes'), errorText: i18n.t('Failed to detect scene changes'), fn: async (onSegmentDetected) => ffmpegDetectSceneChanges({ filePath, minChange, onProgress: setProgress, onSegmentDetected, from: start, to: end }) });
}, [currentCutSeg, detectSegments, fileDurationNonZero, filePath, getFfmpegParameters, setFfmpegParametersForDialog, setProgress]);
}, [currentCutSegOrWholeTimeline, detectSegments, filePath, getFfmpegParameters, setFfmpegParametersForDialog, setProgress]);
const createSegmentsFromKeyframes = useCallback(async () => {
const { start = 0, end = fileDurationNonZero } = currentCutSeg ?? {};
const { start, end } = currentCutSegOrWholeTimeline;
if (!videoStream) return;
invariant(filePath != null);
const keyframes = (await readFrames({ filePath, from: start, to: end, streamIndex: videoStream.index })).filter((frame) => frame.keyframe);
const newSegments = mapTimesToSegments(keyframes.map((keyframe) => keyframe.time), true);
loadCutSegments(newSegments, true);
}, [currentCutSeg, fileDurationNonZero, filePath, loadCutSegments, videoStream]);
}, [currentCutSegOrWholeTimeline, filePath, loadCutSegments, videoStream]);
const removeSegments = useCallback((removeSegmentIds: string[]) => {
safeSetCutSegments((existingSegments) => {
@ -408,10 +414,12 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
if (fileDuration == null || suggestedStart >= fileDuration) return;
const newSegment = createIndexedSegment({ segment: { start: suggestedStart }, incrementCount: true });
const initial = isInitialSegment(cutSegments);
const newSegment = createIndexedSegment({ segment: { start: suggestedStart }, incrementCount: !initial });
// if initial segment, replace it instead
const cutSegmentsNew = isInitialSegment(cutSegments)
const cutSegmentsNew = initial
? [
newSegment,
] : [
@ -449,13 +457,13 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
}, [currentCutSeg, duplicateSegment]);
const setCutStart = useCallback(() => {
if (!checkFileOpened() || currentCutSeg == null) return;
if (!checkFileOpened()) return;
const relevantTime = getRelevantTime();
// https://github.com/mifi/lossless-cut/issues/168
// If current time is after the end of the current segment in the timeline,
// add a new segment that starts at playerTime
if (currentCutSeg.end != null && relevantTime >= currentCutSeg.end) {
// If current time is after the end of the current segment in the timeline, or there is no segment,
// conveniently add a new segment that starts at playerTime
if (currentCutSeg == null || (currentCutSeg.end != null && relevantTime >= currentCutSeg.end)) {
addSegment();
} else {
try {
@ -717,6 +725,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
if (currentCutSeg == null) return;
selectOnlySegment(currentCutSeg);
}, [currentCutSeg, selectOnlySegment]);
const toggleCurrentSegmentSelected = useCallback(() => {
if (currentCutSeg == null) return;
toggleSegmentSelected(currentCutSeg);

Loading…
Cancel
Save