mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 18:32:34 +01:00
show commanded time in addition to player time
This commit is contained in:
parent
fc4c8873ac
commit
b9c99afc5c
@ -87,7 +87,7 @@ const App = memo(() => {
|
|||||||
const [working, setWorking] = useState(false);
|
const [working, setWorking] = useState(false);
|
||||||
const [dummyVideoPath, setDummyVideoPath] = useState(false);
|
const [dummyVideoPath, setDummyVideoPath] = useState(false);
|
||||||
const [playing, setPlaying] = useState(false);
|
const [playing, setPlaying] = useState(false);
|
||||||
const [currentTime, setCurrentTime] = useState();
|
const [playerTime, setPlayerTime] = useState();
|
||||||
const [duration, setDuration] = useState();
|
const [duration, setDuration] = useState();
|
||||||
const [cutSegments, setCutSegments] = useState([createSegment()]);
|
const [cutSegments, setCutSegments] = useState([createSegment()]);
|
||||||
const [currentSegIndex, setCurrentSegIndex] = useState(0);
|
const [currentSegIndex, setCurrentSegIndex] = useState(0);
|
||||||
@ -106,6 +106,7 @@ const App = memo(() => {
|
|||||||
const [copyStreamIdsByFile, setCopyStreamIdsByFile] = useState({});
|
const [copyStreamIdsByFile, setCopyStreamIdsByFile] = useState({});
|
||||||
const [streamsSelectorShown, setStreamsSelectorShown] = useState(false);
|
const [streamsSelectorShown, setStreamsSelectorShown] = useState(false);
|
||||||
const [zoom, setZoom] = useState(1);
|
const [zoom, setZoom] = useState(1);
|
||||||
|
const [commandedTime, setCommandedTime] = useState(0);
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
const [captureFormat, setCaptureFormat] = useState(configStore.get('captureFormat'));
|
const [captureFormat, setCaptureFormat] = useState(configStore.get('captureFormat'));
|
||||||
@ -163,6 +164,7 @@ const App = memo(() => {
|
|||||||
if (outVal > video.duration) outVal = video.duration;
|
if (outVal > video.duration) outVal = video.duration;
|
||||||
|
|
||||||
video.currentTime = outVal;
|
video.currentTime = outVal;
|
||||||
|
setCommandedTime(outVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
const seekRel = useCallback((val) => {
|
const seekRel = useCallback((val) => {
|
||||||
@ -175,6 +177,7 @@ const App = memo(() => {
|
|||||||
|
|
||||||
const resetState = useCallback(() => {
|
const resetState = useCallback(() => {
|
||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
|
setCommandedTime(0);
|
||||||
video.currentTime = 0;
|
video.currentTime = 0;
|
||||||
video.playbackRate = 1;
|
video.playbackRate = 1;
|
||||||
|
|
||||||
@ -301,19 +304,22 @@ const App = memo(() => {
|
|||||||
return formatDuration({ seconds: sec, fps: timecodeShowFrames ? detectedFps : undefined });
|
return formatDuration({ seconds: sec, fps: timecodeShowFrames ? detectedFps : undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCurrentTime = useCallback(() => (
|
||||||
|
playing ? playerTime : commandedTime), [commandedTime, playerTime, playing]);
|
||||||
|
|
||||||
const addCutSegment = useCallback(() => {
|
const addCutSegment = useCallback(() => {
|
||||||
const cutStartTime = currentCutSeg.start;
|
const cutStartTime = currentCutSeg.start;
|
||||||
const cutEndTime = currentCutSeg.end;
|
const cutEndTime = currentCutSeg.end;
|
||||||
|
|
||||||
if (cutStartTime === undefined && cutEndTime === undefined) return;
|
if (cutStartTime === undefined && cutEndTime === undefined) return;
|
||||||
|
|
||||||
const suggestedStart = currentTime;
|
const suggestedStart = getCurrentTime();
|
||||||
const suggestedEnd = suggestedStart + 10;
|
const suggestedEnd = suggestedStart + 10;
|
||||||
|
|
||||||
const cutSegmentsNew = [
|
const cutSegmentsNew = [
|
||||||
...cutSegments,
|
...cutSegments,
|
||||||
createSegment({
|
createSegment({
|
||||||
start: currentTime,
|
start: suggestedStart,
|
||||||
end: suggestedEnd <= duration ? suggestedEnd : undefined,
|
end: suggestedEnd <= duration ? suggestedEnd : undefined,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
@ -322,32 +328,32 @@ const App = memo(() => {
|
|||||||
setCutSegments(cutSegmentsNew);
|
setCutSegments(cutSegmentsNew);
|
||||||
setCurrentSegIndex(currentSegIndexNew);
|
setCurrentSegIndex(currentSegIndexNew);
|
||||||
}, [
|
}, [
|
||||||
currentCutSeg, cutSegments, currentTime, duration,
|
currentCutSeg, cutSegments, getCurrentTime, duration,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const setCutStart = useCallback(() => {
|
const setCutStart = useCallback(() => {
|
||||||
// https://github.com/mifi/lossless-cut/issues/168
|
// https://github.com/mifi/lossless-cut/issues/168
|
||||||
// If we are after the end of the last segment in the timeline,
|
// If we are after the end of the last segment in the timeline,
|
||||||
// add a new segment that starts at currentTime
|
// add a new segment that starts at playerTime
|
||||||
if (currentCutSeg.end != null
|
if (currentCutSeg.end != null
|
||||||
&& currentTime > currentCutSeg.end) {
|
&& getCurrentTime() > currentCutSeg.end) {
|
||||||
addCutSegment();
|
addCutSegment();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
setCutTime('start', currentTime);
|
setCutTime('start', getCurrentTime());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorToast(err.message);
|
errorToast(err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [setCutTime, currentTime, currentCutSeg, addCutSegment]);
|
}, [setCutTime, getCurrentTime, currentCutSeg, addCutSegment]);
|
||||||
|
|
||||||
const setCutEnd = useCallback(() => {
|
const setCutEnd = useCallback(() => {
|
||||||
try {
|
try {
|
||||||
setCutTime('end', currentTime);
|
setCutTime('end', getCurrentTime());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorToast(err.message);
|
errorToast(err.message);
|
||||||
}
|
}
|
||||||
}, [setCutTime, currentTime]);
|
}, [setCutTime, getCurrentTime]);
|
||||||
|
|
||||||
async function setOutputDir() {
|
async function setOutputDir() {
|
||||||
const { filePaths } = await dialog.showOpenDialog({ properties: ['openDirectory'] });
|
const { filePaths } = await dialog.showOpenDialog({ properties: ['openDirectory'] });
|
||||||
@ -375,10 +381,10 @@ const App = memo(() => {
|
|||||||
queue.add(async () => {
|
queue.add(async () => {
|
||||||
if (!frameRenderEnabled) return;
|
if (!frameRenderEnabled) return;
|
||||||
|
|
||||||
if (currentTime == null || !filePath) return;
|
if (playerTime == null || !filePath) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const framePathNew = await ffmpeg.renderFrame(currentTime, filePath, effectiveRotation);
|
const framePathNew = await ffmpeg.renderFrame(playerTime, filePath, effectiveRotation);
|
||||||
setFramePath(framePathNew);
|
setFramePath(framePathNew);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@ -391,7 +397,7 @@ const App = memo(() => {
|
|||||||
|
|
||||||
throttledRender();
|
throttledRender();
|
||||||
}, [
|
}, [
|
||||||
filePath, currentTime, frameRenderEnabled, effectiveRotation,
|
filePath, playerTime, frameRenderEnabled, effectiveRotation,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Cleanup old frames
|
// Cleanup old frames
|
||||||
@ -399,14 +405,17 @@ const App = memo(() => {
|
|||||||
|
|
||||||
function onPlayingChange(val) {
|
function onPlayingChange(val) {
|
||||||
setPlaying(val);
|
setPlaying(val);
|
||||||
if (!val) videoRef.current.playbackRate = 1;
|
if (!val) {
|
||||||
|
videoRef.current.playbackRate = 1;
|
||||||
|
setCommandedTime(videoRef.current.currentTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTimeUpdate(e) {
|
function onTimeUpdate(e) {
|
||||||
const { currentTime: ct } = e.target;
|
const { currentTime } = e.target;
|
||||||
if (currentTime === ct) return;
|
if (playerTime === currentTime) return;
|
||||||
setRotationPreviewRequested(false); // Reset this
|
setRotationPreviewRequested(false); // Reset this
|
||||||
setCurrentTime(ct);
|
setPlayerTime(currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
function increaseRotation() {
|
function increaseRotation() {
|
||||||
@ -414,7 +423,7 @@ const App = memo(() => {
|
|||||||
setRotationPreviewRequested(true);
|
setRotationPreviewRequested(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const offsetCurrentTime = (currentTime || 0) + startTimeOffset;
|
const offsetCurrentTime = (getCurrentTime() || 0) + startTimeOffset;
|
||||||
|
|
||||||
const mergeFiles = useCallback(async ({ paths, allStreams }) => {
|
const mergeFiles = useCallback(async ({ paths, allStreams }) => {
|
||||||
try {
|
try {
|
||||||
@ -499,15 +508,18 @@ const App = memo(() => {
|
|||||||
const durationSafe = duration || 1;
|
const durationSafe = duration || 1;
|
||||||
const currentTimeWidth = 1;
|
const currentTimeWidth = 1;
|
||||||
// Prevent it from overflowing (and causing scroll) when end of timeline
|
// Prevent it from overflowing (and causing scroll) when end of timeline
|
||||||
const currentTimePos = currentTime !== undefined && currentTime < durationSafe ? `${(currentTime / durationSafe) * 100}%` : undefined;
|
|
||||||
|
const calculateTimelinePos = (time) => (time !== undefined && time < durationSafe ? `${(time / durationSafe) * 100}%` : undefined);
|
||||||
|
const currentTimePos = calculateTimelinePos(playerTime);
|
||||||
|
const commandedTimePos = calculateTimelinePos(commandedTime);
|
||||||
|
|
||||||
const zoomed = zoom > 1;
|
const zoomed = zoom > 1;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { currentTime: ct } = videoRef.current;
|
const { currentTime } = videoRef.current;
|
||||||
timelineScrollerSkipEventRef.current = true;
|
timelineScrollerSkipEventRef.current = true;
|
||||||
if (zoom > 1) {
|
if (zoom > 1) {
|
||||||
timelineScrollerRef.current.scrollLeft = (ct / durationSafe)
|
timelineScrollerRef.current.scrollLeft = (currentTime / durationSafe)
|
||||||
* (timelineWrapperRef.current.offsetWidth - timelineScrollerRef.current.offsetWidth);
|
* (timelineWrapperRef.current.offsetWidth - timelineScrollerRef.current.offsetWidth);
|
||||||
}
|
}
|
||||||
}, [zoom, durationSafe]);
|
}, [zoom, durationSafe]);
|
||||||
@ -680,12 +692,12 @@ const App = memo(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await captureFrame(customOutDir, filePath, videoRef.current, currentTime, captureFormat);
|
await captureFrame(customOutDir, filePath, videoRef.current, playerTime, captureFormat);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
errorToast('Failed to capture frame');
|
errorToast('Failed to capture frame');
|
||||||
}
|
}
|
||||||
}, [filePath, currentTime, captureFormat, customOutDir, html5FriendlyPath, dummyVideoPath]);
|
}, [filePath, playerTime, captureFormat, customOutDir, html5FriendlyPath, dummyVideoPath]);
|
||||||
|
|
||||||
const changePlaybackRate = useCallback((dir) => {
|
const changePlaybackRate = useCallback((dir) => {
|
||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
@ -1427,7 +1439,8 @@ const App = memo(() => {
|
|||||||
style={{ height: 36, width: `${zoom * 100}%`, position: 'relative', backgroundColor: '#444' }}
|
style={{ height: 36, width: `${zoom * 100}%`, position: 'relative', backgroundColor: '#444' }}
|
||||||
ref={timelineWrapperRef}
|
ref={timelineWrapperRef}
|
||||||
>
|
>
|
||||||
{currentTimePos !== undefined && <div style={{ position: 'absolute', bottom: 0, top: 0, left: currentTimePos, zIndex: 3, backgroundColor: 'rgba(255, 255, 255, 1)', width: currentTimeWidth, pointerEvents: 'none' }} />}
|
{currentTimePos !== undefined && <motion.div transition={{ type: 'spring', damping: 70, stiffness: 800 }} animate={{ left: currentTimePos }} style={{ position: 'absolute', bottom: 0, top: 0, zIndex: 3, backgroundColor: 'black', width: currentTimeWidth, pointerEvents: 'none' }} />}
|
||||||
|
{commandedTimePos !== undefined && <div style={{ left: commandedTimePos, position: 'absolute', bottom: 0, top: 0, zIndex: 4, backgroundColor: 'white', width: currentTimeWidth, pointerEvents: 'none' }} />}
|
||||||
|
|
||||||
{apparentCutSegments.map((seg, i) => (
|
{apparentCutSegments.map((seg, i) => (
|
||||||
<TimelineSeg
|
<TimelineSeg
|
||||||
|
Loading…
Reference in New Issue
Block a user