@ -115,7 +115,7 @@ const App = memo(() => {
const [ usingDummyVideo , setUsingDummyVideo ] = useState ( false ) ;
const [ playing , setPlaying ] = useState ( false ) ;
const [ canvasPlayerEventId , setCanvasPlayerEventId ] = useState ( 0 ) ;
const play ingOnlySegmentId Ref = useRef ( ) ;
const play backMode Ref = useRef ( ) ;
const [ playerTime , setPlayerTime ] = useState ( ) ;
const [ duration , setDuration ] = useState ( ) ;
const [ rotation , setRotation ] = useState ( 360 ) ;
@ -278,14 +278,19 @@ const App = memo(() => {
setCanvasPlayerEventId ( ( id ) => id + 1 ) ; / / T o m a k e s u r e t h a t w e c a n s e e k e v e n t o t h e s a m e c o m m a n d e d t i m e t h a t w e a r e a l r e a d y a d d ( e . g . l o o p c u r r e n t s e g m e n t )
} , [ ] ) ;
const userSeekAbs = useCallback ( ( val ) => {
playbackModeRef . current = undefined ; / / I f t h e u s e r s e e k s , w e c l e a r a n y c u s t o m p l a y b a c k m o d e
return seekAbs ( val ) ;
} , [ seekAbs ] ) ;
const commandedTimeRef = useRef ( commandedTime ) ;
useEffect ( ( ) => {
commandedTimeRef . current = commandedTime ;
} , [ commandedTime ] ) ;
const seekRel = useCallback ( ( val ) => {
seekAbs( videoRef . current . currentTime + val ) ;
} , [ seekAbs] ) ;
u serS eekAbs( videoRef . current . currentTime + val ) ;
} , [ u serS eekAbs] ) ;
const seekRelPercent = useCallback ( ( val ) => {
if ( ! isDurationValid ( zoomedDuration ) ) return ;
@ -299,8 +304,8 @@ const App = memo(() => {
/ / t r y t o a l i g n w i t h f r a m e
const currentTimeNearestFrameNumber = getFrameCountRaw ( fps , videoRef . current . currentTime ) ;
const nextFrame = currentTimeNearestFrameNumber + direction ;
seekAbs( nextFrame / fps ) ;
} , [ seekAbs, detectedFp s] ) ;
u serS eekAbs( nextFrame / fps ) ;
} , [ detectedFps, userSeekAb s] ) ;
/ / 3 6 0 m e a n s w e d o n ' t m o d i f y r o t a t i o n
const isRotationSet = rotation !== 360 ;
@ -343,15 +348,15 @@ 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 , getApparentCutSegmentById ,
} = useSegments ( { filePath , workingRef , setWorking , setCutProgress , mainVideoStream , duration , getRelevantTime , maxLabelLength , checkFileOpened } ) ;
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 , selectedSegments , selectedSegmentsOrInverse , nonFilteredSegmentsOrInverse ,
} = useSegments ( { filePath , workingRef , setWorking , setCutProgress , mainVideoStream , duration , getRelevantTime , maxLabelLength , checkFileOpened , invertCutSegments } ) ;
const jumpSegStart = useCallback ( ( index ) => seekAbs( apparentCutSegments [ index ] . start ) , [ apparentCutSegments , seekAbs] ) ;
const jumpSegEnd = useCallback ( ( index ) => seekAbs( apparentCutSegments [ index ] . end ) , [ apparentCutSegments , seekAbs] ) ;
const jumpSegStart = useCallback ( ( index ) => u serS eekAbs( apparentCutSegments [ index ] . start ) , [ apparentCutSegments , u serS eekAbs] ) ;
const jumpSegEnd = useCallback ( ( index ) => u serS eekAbs( apparentCutSegments [ index ] . end ) , [ apparentCutSegments , u serS eekAbs] ) ;
const jumpCutStart = useCallback ( ( ) => jumpSegStart ( currentSegIndexSafe ) , [ currentSegIndexSafe , jumpSegStart ] ) ;
const jumpCutEnd = useCallback ( ( ) => jumpSegEnd ( currentSegIndexSafe ) , [ currentSegIndexSafe , jumpSegEnd ] ) ;
const jumpTimelineStart = useCallback ( ( ) => seekAbs( 0 ) , [ seekAbs] ) ;
const jumpTimelineEnd = useCallback ( ( ) => seekAbs( durationSafe ) , [ durationSafe , seekAbs] ) ;
const jumpTimelineStart = useCallback ( ( ) => u serS eekAbs( 0 ) , [ u serS eekAbs] ) ;
const jumpTimelineEnd = useCallback ( ( ) => u serS eekAbs( durationSafe ) , [ durationSafe , u serS eekAbs] ) ;
const getFrameCount = useCallback ( ( sec ) => getFrameCountRaw ( detectedFps , sec ) , [ detectedFps ] ) ;
@ -435,7 +440,7 @@ const App = memo(() => {
const onStopPlaying = useCallback ( ( ) => {
onPlayingChange ( false ) ;
play ingOnlySegmentId Ref. current = undefined ;
play backMode Ref. current = undefined ;
} , [ ] ) ;
const onSartPlaying = useCallback ( ( ) => onPlayingChange ( true ) , [ ] ) ;
const onDurationChange = useCallback ( ( e ) => {
@ -629,7 +634,7 @@ const App = memo(() => {
setPreviewFilePath ( ) ;
setUsingDummyVideo ( false ) ;
setPlaying ( false ) ;
play ingOnlySegmentId Ref. current = undefined ;
play backMode Ref. current = undefined ;
setCanvasPlayerEventId ( 0 ) ;
setDuration ( ) ;
cutSegmentsHistory . go ( 0 ) ;
@ -761,6 +766,9 @@ const App = memo(() => {
const showPlaybackFailedMessage = ( ) => errorToast ( i18n . t ( 'Unable to playback this file. Try to convert to supported format from the menu' ) ) ;
const getNewJumpIndex = ( oldIndex , direction ) => Math . max ( oldIndex + direction , 0 ) ;
const jumpSeg = useCallback ( ( direction ) => setCurrentSegIndex ( ( old ) => Math . min ( getNewJumpIndex ( old , direction ) , cutSegments . length - 1 ) ) , [ cutSegments , setCurrentSegIndex ] ) ;
const pause = useCallback ( ( ) => {
if ( ! filePath || ! playing ) return ;
videoRef . current . pause ( ) ;
@ -777,38 +785,56 @@ const App = memo(() => {
} ) ;
} , [ filePath , playing ] ) ;
const togglePlay = useCallback ( ( { resetPlaybackRate , onlyCurrentSegment } = { } ) => {
play ingOnlySegmentId Ref. current = undefined ;
const togglePlay = useCallback ( ( { resetPlaybackRate , playbackMode } = { } ) => {
play backMode Ref. current = undefined ;
if ( playing ) {
pause ( ) ;
return ;
}
if ( onlyCurrentSegment != null ) {
playingOnlySegmentIdRef . current = { segId : currentApparentCutSeg . segId , mode : onlyCurrentSegment } ;
seekAbs ( currentApparentCutSeg . start ) ;
if ( playbackMode != null ) {
if ( playbackMode === 'loop-selected-segments' ) {
const firstSelectedSegment = selectedSegments [ 0 ] ;
playbackModeRef . current = { segId : firstSelectedSegment . segId , playbackMode } ;
const index = apparentCutSegments . indexOf ( firstSelectedSegment ) ;
if ( index >= 0 ) setCurrentSegIndex ( index ) ;
seekAbs ( firstSelectedSegment . start ) ;
} else {
playbackModeRef . current = { segId : currentApparentCutSeg . segId , playbackMode } ;
seekAbs ( currentApparentCutSeg . start ) ;
}
}
play ( resetPlaybackRate ) ;
} , [ playing , play , pause , currentApparentCutSeg . segId , currentApparentCutSeg . start , seekAbs ] ) ;
} , [ playing , play , pause , selectedSegments, apparentCutSegments , setCurrentSegIndex , seekAbs , currentApparentCutSeg. segId , currentApparentCutSeg . start ] ) ;
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 ( playbackModeRef . current != null ) {
const { segId , playbackMode } = playbackModeRef . current ;
const playingSegment = getApparentCutSegmentById ( segId ) ;
if ( playingSegment != null ) {
const { seek , stop , nextSegment } = playOnlyCurrentSegment ( { playbackMode , currentTime , playingSegment } ) ;
/ / c o n s o l e . l o g ( { s e e k , s t o p , n e x t S e g m e n t } ) ;
if ( nextSegment != null ) {
const index = selectedSegments . indexOf ( playingSegment ) ;
let newIndex = getNewJumpIndex ( index >= 0 ? index : 0 , 1 ) ;
if ( newIndex > selectedSegments . length - 1 ) newIndex = 0 ; / / h a v e r e a c h e d e n d o f l a s t s e g m e n t , s t a r t o v e r
const nextSelectedSegment = selectedSegments [ newIndex ] ;
if ( nextSelectedSegment != null ) seekAbs ( nextSelectedSegment . start ) ;
playbackModeRef . current . segId = nextSelectedSegment . segId ;
}
if ( seek != null ) seekAbs ( seek ) ;
if ( stop ) {
playingOnlySegmentIdRef . current = undefined ;
play backMode Ref. current = undefined ;
pause ( ) ;
}
}
}
} , [ getApparentCutSegmentById , pause , playerTime , seekAbs ] ) ;
} , [ getApparentCutSegmentById , pause , playerTime , seekAbs , selectedSegments ]) ;
const closeFileWithConfirm = useCallback ( ( ) => {
if ( ! isFileOpened || workingRef . current ) return ;
@ -998,14 +1024,6 @@ const App = memo(() => {
}
} , [ isFileOpened , cleanupChoices , askForCleanupChoices , cleanupFiles , setWorking ] ) ;
/ / F o r i n v e r t C u t S e g m e n t s w e d o n o t s u p p o r t f i l t e r i n g
const selectedSegmentsOrInverseRaw = useMemo ( ( ) => ( invertCutSegments ? inverseCutSegments : selectedSegmentsRaw ) , [ inverseCutSegments , invertCutSegments , selectedSegmentsRaw ] ) ;
const nonFilteredSegments = useMemo ( ( ) => ( invertCutSegments ? inverseCutSegments : apparentCutSegments ) , [ invertCutSegments , inverseCutSegments , apparentCutSegments ] ) ;
/ / I f u s e r h a s s e l e c t e d n o n e t o e x p o r t , i t m a k e s n o s e n s e , s o e x p o r t a l l i n s t e a d
const selectedSegmentsOrInverse = selectedSegmentsOrInverseRaw . length > 0 ? selectedSegmentsOrInverseRaw : nonFilteredSegments ;
const segmentsToExport = useMemo ( ( ) => {
if ( ! segmentsToChaptersOnly ) return selectedSegmentsOrInverse ;
/ / s e g m e n t s T o C h a p t e r s O n l y i s a s p e c i a l m o d e w h e r e a l l s e g m e n t s w i l l b e s i m p l y w r i t t e n o u t a s c h a p t e r s t o o n e f i l e : h t t p s : / / g i t h u b . c o m / m i f i / l o s s l e s s - c u t / i s s u e s / 9 9 3 # i s s u e c o m m e n t - 1 0 3 7 9 2 7 5 9 5
@ -1430,13 +1448,11 @@ const App = memo(() => {
const toggleLastCommands = useCallback ( ( ) => setLastCommandsVisible ( val => ! val ) , [ ] ) ;
const toggleSettings = useCallback ( ( ) => setSettingsVisible ( val => ! val ) , [ ] ) ;
const jumpSeg = useCallback ( ( val ) => setCurrentSegIndex ( ( old ) => Math . max ( Math . min ( old + val , cutSegments . length - 1 ) , 0 ) ) , [ cutSegments . length , setCurrentSegIndex ] ) ;
const seekClosestKeyframe = useCallback ( ( direction ) => {
const time = findNearestKeyFrameTime ( { time : getRelevantTime ( ) , direction } ) ;
if ( time == null ) return ;
seekAbs( time ) ;
} , [ findNearestKeyFrameTime , getRelevantTime , seekAbs] ) ;
u serS eekAbs( time ) ;
} , [ findNearestKeyFrameTime , getRelevantTime , u serS eekAbs] ) ;
const seekAccelerationRef = useRef ( 1 ) ;
@ -1520,8 +1536,8 @@ const App = memo(() => {
if ( timeCode === undefined ) return ;
seekAbs( timeCode ) ;
} , [ filePath , seekAbs] ) ;
u serS eekAbs( timeCode ) ;
} , [ filePath , u serS eekAbs] ) ;
const toggleStreamsSelector = useCallback ( ( ) => setStreamsSelectorShown ( ( v ) => ! v ) , [ ] ) ;
@ -1767,6 +1783,8 @@ const App = memo(() => {
setConcatDialogVisible ( true ) ;
} , [ batchFiles . length , openFilesDialog ] ) ;
const toggleLoopSelectedSegments = useCallback ( ( ) => togglePlay ( { resetPlaybackRate : true , playbackMode : 'loop-selected-segments' } ) , [ togglePlay ] ) ;
const onKeyPress = useCallback ( ( { action , keyup } ) => {
function seekReset ( ) {
seekAccelerationRef . current = 1 ;
@ -1777,9 +1795,10 @@ const App = memo(() => {
const mainActions = {
togglePlayNoResetSpeed : ( ) => togglePlay ( ) ,
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' } ) ,
togglePlayOnlyCurrentSegment : ( ) => togglePlay ( { resetPlaybackRate : true , playbackMode : 'play-segment-once' } ) ,
toggleLoopOnlyCurrentSegment : ( ) => togglePlay ( { resetPlaybackRate : true , playbackMode : 'loop-segment' } ) ,
toggleLoopStartEndOnlyCurrentSegment : ( ) => togglePlay ( { resetPlaybackRate : true , playbackMode : 'loop-segment-start-end' } ) ,
toggleLoopSelectedSegments ,
play : ( ) => play ( ) ,
pause ,
reducePlaybackRate : ( ) => changePlaybackRate ( - 1 ) ,
@ -1907,7 +1926,7 @@ const App = memo(() => {
if ( match ) return bubble ;
return true ; / / b u b b l e t h e e v e n t
} , [ addSegment , alignSegmentTimesToKeyframes , askSetStartTimeOffset , batchFileJump , batchOpenSelectedFile , captureSnapshot , captureSnapshotAsCoverArt , changePlaybackRate , cleanupFilesDialog , clearSegments , closeBatch , closeExportConfirm , combineOverlappingSegments , concatCurrentBatch , concatDialogVisible , convertFormatBatch , createFixedDurationSegments , createNumSegments , createRandomSegments , currentSegIndexSafe , cutSegmentsHistory , deselectAllSegments , exportConfirmVisible , extractAllStreams , extractCurrentSegmentFramesAsImages , fillSegmentsGaps , goToTimecode , increaseRotation , invertAllSegments , jumpCutEnd , jumpCutStart , jumpSeg , jumpTimelineEnd , jumpTimelineStart , keyboardNormalSeekSpeed , keyboardSeekAccFactor , keyboardShortcutsVisible , onExportConfirm , onExportPress , onLabelSegment , pause , play , removeCutSegment , removeSelectedSegments , reorderSegsByStartTime , seekClosestKeyframe , seekRel , seekRelPercent , selectAllSegments , selectOnlyCurrentSegment , setCutEnd , setCutStart , setPlaybackVolume , shiftAllSegmentTimes , shortStep , shuffleSegments , splitCurrentSegment , timelineToggleComfortZoom , toggleCaptureFormat , toggleCurrentSegmentSelected , toggleKeyboardShortcuts , toggleKeyframeCut , toggleLastCommands , toggle Play, toggleSegmentsList , toggleStreamsSelector , toggleStripAudio , tryFixInvalidDuration , userHtml5ifyCurrentFile , zoomRel ] ) ;
} , [ addSegment , alignSegmentTimesToKeyframes , askSetStartTimeOffset , batchFileJump , batchOpenSelectedFile , captureSnapshot , captureSnapshotAsCoverArt , changePlaybackRate , cleanupFilesDialog , clearSegments , closeBatch , closeExportConfirm , combineOverlappingSegments , concatCurrentBatch , concatDialogVisible , convertFormatBatch , createFixedDurationSegments , createNumSegments , createRandomSegments , currentSegIndexSafe , cutSegmentsHistory , deselectAllSegments , exportConfirmVisible , extractAllStreams , extractCurrentSegmentFramesAsImages , fillSegmentsGaps , goToTimecode , increaseRotation , invertAllSegments , jumpCutEnd , jumpCutStart , jumpSeg , jumpTimelineEnd , jumpTimelineStart , keyboardNormalSeekSpeed , keyboardSeekAccFactor , keyboardShortcutsVisible , onExportConfirm , onExportPress , onLabelSegment , pause , play , removeCutSegment , removeSelectedSegments , reorderSegsByStartTime , seekClosestKeyframe , seekRel , seekRelPercent , selectAllSegments , selectOnlyCurrentSegment , setCutEnd , setCutStart , setPlaybackVolume , shiftAllSegmentTimes , shortStep , shuffleSegments , splitCurrentSegment , timelineToggleComfortZoom , toggleCaptureFormat , toggleCurrentSegmentSelected , toggleKeyboardShortcuts , toggleKeyframeCut , toggleLastCommands , toggle LoopSelectedSegments, toggle Play, toggleSegmentsList , toggleStreamsSelector , toggleStripAudio , tryFixInvalidDuration , userHtml5ifyCurrentFile , zoomRel ] ) ;
useKeyboard ( { keyBindings , onKeyPress } ) ;
@ -2277,7 +2296,7 @@ const App = memo(() => {
commandedTimeRef = { commandedTimeRef }
startTimeOffset = { startTimeOffset }
zoom = { zoom }
seekAbs = { seekAbs}
seekAbs = { u serS eekAbs}
durationSafe = { durationSafe }
apparentCutSegments = { apparentCutSegments }
setCurrentSegIndex = { setCurrentSegIndex }
@ -2304,10 +2323,11 @@ const App = memo(() => {
captureSnapshot = { captureSnapshot }
onExportPress = { onExportPress }
segmentsToExport = { segmentsToExport }
seekAbs = { seekAbs}
seekAbs = { u serS eekAbs}
currentSegIndexSafe = { currentSegIndexSafe }
cutSegments = { cutSegments }
currentCutSeg = { currentCutSeg }
selectedSegments = { selectedSegments }
setCutStart = { setCutStart }
setCutEnd = { setCutEnd }
setCurrentSegIndex = { setCurrentSegIndex }
@ -2332,6 +2352,8 @@ const App = memo(() => {
keyframesEnabled = { keyframesEnabled }
toggleKeyframesEnabled = { toggleKeyframesEnabled }
detectedFps = { detectedFps }
toggleLoopSelectedSegments = { toggleLoopSelectedSegments }
isFileOpened = { isFileOpened }
/ >
< / div >
@ -2371,7 +2393,7 @@ const App = memo(() => {
) }
< / SideSheet >
< ExportConfirm filePath = { filePath } areWeCutting = { areWeCutting } nonFilteredSegments = { nonFilteredSegments } selectedSegments = { selectedSegmentsOrInverse } segmentsToExport = { segmentsToExport } willMerge = { willMerge } visible = { exportConfirmVisible } onClosePress = { closeExportConfirm } onExportConfirm = { onExportConfirm } renderOutFmt = { renderOutFmt } outputDir = { outputDir } numStreamsTotal = { numStreamsTotal } numStreamsToCopy = { numStreamsToCopy } setStreamsSelectorShown = { setStreamsSelectorShown } outFormat = { fileFormat } setOutSegTemplate = { setOutSegTemplate } outSegTemplate = { outSegTemplateOrDefault } generateOutSegFileNames = { generateOutSegFileNames } currentSegIndexSafe = { currentSegIndexSafe } getOutSegError = { getOutSegError } mainCopiedThumbnailStreams = { mainCopiedThumbnailStreams } / >
< ExportConfirm filePath = { filePath } areWeCutting = { areWeCutting } nonFilteredSegments OrInverse = { nonFilteredSegments OrInverse } selectedSegments = { selectedSegmentsOrInverse } segmentsToExport = { segmentsToExport } willMerge = { willMerge } visible = { exportConfirmVisible } onClosePress = { closeExportConfirm } onExportConfirm = { onExportConfirm } renderOutFmt = { renderOutFmt } outputDir = { outputDir } numStreamsTotal = { numStreamsTotal } numStreamsToCopy = { numStreamsToCopy } setStreamsSelectorShown = { setStreamsSelectorShown } outFormat = { fileFormat } setOutSegTemplate = { setOutSegTemplate } outSegTemplate = { outSegTemplateOrDefault } generateOutSegFileNames = { generateOutSegFileNames } currentSegIndexSafe = { currentSegIndexSafe } getOutSegError = { getOutSegError } mainCopiedThumbnailStreams = { mainCopiedThumbnailStreams } / >
< LastCommandsSheet
visible = { lastCommandsVisible }