mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 19:52:44 +01:00
parent
bc4edf9829
commit
ff56c44a32
51
src/App.jsx
51
src/App.jsx
@ -76,7 +76,7 @@ import { askForHtml5ifySpeed } from './dialogs/html5ify';
|
||||
import { askForOutDir, askForImportChapters, promptTimeOffset, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showExportFailedDialog, showConcatFailedDialog, openYouTubeChaptersDialog, openAbout, showRefuseToOverwrite, openDirToast, openCutFinishedToast, openConcatFinishedToast } from './dialogs';
|
||||
import { openSendReportDialog } from './reporting';
|
||||
import { fallbackLng } from './i18n';
|
||||
import { createSegment, getCleanCutSegments, findSegmentsAtCursor, sortSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap, isDurationValid } from './segments';
|
||||
import { createSegment, getCleanCutSegments, findSegmentsAtCursor, sortSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap, isDurationValid, playOnlyCurrentSegment } from './segments';
|
||||
import { getOutSegError as getOutSegErrorRaw } from './util/outputNameTemplate';
|
||||
import { rightBarWidth, leftBarWidth, ffmpegExtractWindow, zoomMax } from './util/constants';
|
||||
|
||||
@ -114,6 +114,7 @@ const App = memo(() => {
|
||||
const [working, setWorkingState] = useState();
|
||||
const [usingDummyVideo, setUsingDummyVideo] = useState(false);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const playingOnlySegmentIdRef = useRef();
|
||||
const [playerTime, setPlayerTime] = useState();
|
||||
const [duration, setDuration] = useState();
|
||||
const [rotation, setRotation] = useState(360);
|
||||
@ -341,7 +342,7 @@ const App = memo(() => {
|
||||
}, [isFileOpened]);
|
||||
|
||||
const {
|
||||
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, onViewSegmentTags, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, selectedSegmentsRaw, setCutTime, getSegApparentEnd, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, toggleSegmentSelected, selectOnlySegment,
|
||||
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, onViewSegmentTags, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, selectedSegmentsRaw, setCutTime, getSegApparentEnd, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById,
|
||||
} = useSegments({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened });
|
||||
|
||||
const jumpSegStart = useCallback((index) => seekAbs(apparentCutSegments[index].start), [apparentCutSegments, seekAbs]);
|
||||
@ -431,7 +432,10 @@ const App = memo(() => {
|
||||
}
|
||||
}
|
||||
|
||||
const onStopPlaying = useCallback(() => onPlayingChange(false), []);
|
||||
const onStopPlaying = useCallback(() => {
|
||||
onPlayingChange(false);
|
||||
playingOnlySegmentIdRef.current = undefined;
|
||||
}, []);
|
||||
const onSartPlaying = useCallback(() => onPlayingChange(true), []);
|
||||
const onDurationChange = useCallback((e) => {
|
||||
// Some files report duration infinity first, then proper duration later
|
||||
@ -441,12 +445,6 @@ const App = memo(() => {
|
||||
if (isDurationValid(durationNew)) setDuration(durationNew);
|
||||
}, []);
|
||||
|
||||
const onTimeUpdate = useCallback((e) => {
|
||||
const { currentTime } = e.target;
|
||||
if (playerTime === currentTime) return;
|
||||
setPlayerTime(currentTime);
|
||||
}, [playerTime]);
|
||||
|
||||
const increaseRotation = useCallback(() => {
|
||||
setRotation((r) => (r + 90) % 450);
|
||||
setHideCanvasPreview(false);
|
||||
@ -630,6 +628,7 @@ const App = memo(() => {
|
||||
setPreviewFilePath();
|
||||
setUsingDummyVideo(false);
|
||||
setPlaying(false);
|
||||
playingOnlySegmentIdRef.current = undefined;
|
||||
setDuration();
|
||||
cutSegmentsHistory.go(0);
|
||||
clearSegments(); // TODO this will cause two history items
|
||||
@ -775,13 +774,38 @@ const App = memo(() => {
|
||||
});
|
||||
}, [filePath, playing]);
|
||||
|
||||
const togglePlay = useCallback((resetPlaybackRate) => {
|
||||
const togglePlay = useCallback(({ resetPlaybackRate, onlyCurrentSegment } = {}) => {
|
||||
playingOnlySegmentIdRef.current = undefined;
|
||||
if (playing) {
|
||||
pause();
|
||||
return;
|
||||
}
|
||||
if (onlyCurrentSegment != null) {
|
||||
playingOnlySegmentIdRef.current = { segId: currentApparentCutSeg.segId, mode: onlyCurrentSegment };
|
||||
seekAbs(currentApparentCutSeg.start);
|
||||
}
|
||||
play(resetPlaybackRate);
|
||||
}, [playing, play, pause]);
|
||||
}, [playing, play, pause, currentApparentCutSeg.segId, currentApparentCutSeg.start, seekAbs]);
|
||||
|
||||
const onTimeUpdate = useCallback((e) => {
|
||||
const { currentTime } = e.target;
|
||||
if (playerTime === currentTime) return;
|
||||
setPlayerTime(currentTime);
|
||||
|
||||
if (playingOnlySegmentIdRef.current != null) {
|
||||
const { segId, mode } = playingOnlySegmentIdRef.current;
|
||||
const playingOnlySegment = getApparentCutSegmentById(segId);
|
||||
|
||||
if (playingOnlySegment != null) {
|
||||
const { seek, stop } = playOnlyCurrentSegment({ mode, currentTime, playingOnlySegment });
|
||||
if (seek) seekAbs(seek);
|
||||
if (stop) {
|
||||
playingOnlySegmentIdRef.current = undefined;
|
||||
pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [getApparentCutSegmentById, pause, playerTime, seekAbs]);
|
||||
|
||||
const closeFileWithConfirm = useCallback(() => {
|
||||
if (!isFileOpened || workingRef.current) return;
|
||||
@ -1710,7 +1734,10 @@ const App = memo(() => {
|
||||
// For actions, see also KeyboardShortcuts.jsx
|
||||
const mainActions = {
|
||||
togglePlayNoResetSpeed: () => togglePlay(),
|
||||
togglePlayResetSpeed: () => togglePlay(true),
|
||||
togglePlayResetSpeed: () => togglePlay({ resetPlaybackRate: true }),
|
||||
togglePlayOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, onlyCurrentSegment: 'play' }),
|
||||
toggleLoopOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, onlyCurrentSegment: 'loop-full' }),
|
||||
toggleLoopStartEndOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, onlyCurrentSegment: 'loop-start-end' }),
|
||||
play: () => play(),
|
||||
pause,
|
||||
reducePlaybackRate: () => changePlaybackRate(-1),
|
||||
|
@ -232,7 +232,7 @@ const BottomBar = memo(({
|
||||
/>
|
||||
)}
|
||||
|
||||
<div role="button" onClick={togglePlay} style={{ background: primaryColor, margin: '2px 5px 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, borderRadius: 17 }}>
|
||||
<div role="button" onClick={() => togglePlay()} style={{ background: primaryColor, margin: '2px 5px 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, borderRadius: 17 }}>
|
||||
<PlayPause
|
||||
style={{ marginLeft: playing ? 0 : 2 }}
|
||||
size={16}
|
||||
|
@ -156,6 +156,18 @@ const KeyboardShortcuts = memo(({
|
||||
name: t('Play/pause (no reset speed)'),
|
||||
category: playbackCategory,
|
||||
},
|
||||
togglePlayOnlyCurrentSegment: {
|
||||
name: t('Play/pause (only current segment)'),
|
||||
category: playbackCategory,
|
||||
},
|
||||
toggleLoopOnlyCurrentSegment: {
|
||||
name: t('Loop/pause (only current segment)'),
|
||||
category: playbackCategory,
|
||||
},
|
||||
toggleLoopStartEndOnlyCurrentSegment: {
|
||||
name: t('Loop/pause (only beginning and end of current segment)'),
|
||||
category: playbackCategory,
|
||||
},
|
||||
play: {
|
||||
name: t('Play'),
|
||||
category: playbackCategory,
|
||||
|
@ -106,6 +106,8 @@ export default ({
|
||||
// These are segments guaranteed to have a start and end time
|
||||
const apparentCutSegments = useMemo(() => getApparentCutSegments(cutSegments), [cutSegments, getApparentCutSegments]);
|
||||
|
||||
const getApparentCutSegmentById = useCallback((id) => apparentCutSegments.find((s) => s.segId === id), [apparentCutSegments]);
|
||||
|
||||
const haveInvalidSegs = useMemo(() => apparentCutSegments.some((cutSegment) => cutSegment.start >= cutSegment.end), [apparentCutSegments]);
|
||||
|
||||
const currentSegIndexSafe = Math.min(currentSegIndex, cutSegments.length - 1);
|
||||
@ -465,6 +467,7 @@ export default ({
|
||||
createFixedDurationSegments,
|
||||
createRandomSegments,
|
||||
apparentCutSegments,
|
||||
getApparentCutSegmentById,
|
||||
haveInvalidSegs,
|
||||
currentSegIndexSafe,
|
||||
currentCutSeg,
|
||||
|
@ -164,3 +164,36 @@ export function convertSegmentsToChapters(sortedSegments) {
|
||||
// inverted segments will be "gap" segments. Merge together with normal segments
|
||||
return sortSegments([...sortedSegments, ...invertedSegments]);
|
||||
}
|
||||
|
||||
export function playOnlyCurrentSegment({ mode, currentTime, playingOnlySegment }) {
|
||||
if (mode === 'loop-start-end') {
|
||||
const maxSec = 3; // max time each side (start/end)
|
||||
const sec = Math.min(maxSec, (playingOnlySegment.end - playingOnlySegment.start) / 3) * 2;
|
||||
|
||||
const startWindowEnd = playingOnlySegment.start + sec / 2;
|
||||
const endWindowStart = playingOnlySegment.end - sec / 2;
|
||||
|
||||
if (currentTime >= playingOnlySegment.end) {
|
||||
return { seek: playingOnlySegment.start };
|
||||
}
|
||||
if (currentTime < endWindowStart && currentTime >= startWindowEnd) {
|
||||
return { seek: endWindowStart };
|
||||
}
|
||||
}
|
||||
|
||||
if (mode === 'loop-full') {
|
||||
if (currentTime >= playingOnlySegment.end) {
|
||||
return { seek: playingOnlySegment.start };
|
||||
}
|
||||
}
|
||||
|
||||
// mode === 'play'
|
||||
if (currentTime >= playingOnlySegment.end) {
|
||||
return {
|
||||
seek: playingOnlySegment.end,
|
||||
stop: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user