@ -82,7 +82,7 @@ import { askForHtml5ifySpeed } from './dialogs/html5ify';
import { askForOutDir , askForImportChapters , promptTimecode , askForFileOpenAction , confirmExtractAllStreamsDialog , showCleanupFilesDialog , showDiskFull , showExportFailedDialog , showConcatFailedDialog , openYouTubeChaptersDialog , showRefuseToOverwrite , openDirToast , openExportFinishedToast , openConcatFinishedToast , showOpenDialog , showMuxNotSupported , promptDownloadMediaUrl , CleanupChoicesType , showOutputNotWritable } from './dialogs' ;
import { openSendReportDialog } from './reporting' ;
import { fallbackLng } from './i18n' ;
import { findSegmentsAtCursor , sortSegments , convertSegmentsToChapters , hasAnySegmentOverlap , isDurationValid , playOnlyCurrentSegment, getSegmentTags } from './segments' ;
import { findSegmentsAtCursor , sortSegments , convertSegmentsToChapters , hasAnySegmentOverlap , isDurationValid , getPlaybackMode, getSegmentTags , getSegApparentStart } from './segments' ;
import { generateOutSegFileNames as generateOutSegFileNamesRaw , generateMergedFileNames as generateMergedFileNamesRaw , defaultOutSegTemplate , defaultCutMergedFileTemplate } from './util/outputNameTemplate' ;
import { rightBarWidth , leftBarWidth , ffmpegExtractWindow , zoomMax } from './util/constants' ;
import BigWaveform from './components/BigWaveform' ;
@ -335,7 +335,7 @@ function App() {
} , [ isFileOpened ] ) ;
const {
cutSegments , cutSegmentsHistory , createSegmentsFromKeyframes , shuffleSegments , detectBlackScenes , detectSilentScenes , detectSceneChanges , removeCutSegment , invertAllSegments , fillSegmentsGaps , combineOverlappingSegments , combineSelectedSegments , shiftAllSegmentTimes , alignSegmentTimesToKeyframes , updateSegOrder , updateSegOrders , reorderSegsByStartTime , addSegment , setCutStart , setCutEnd , onLabelSegment , splitCurrentSegment , focusSegmentAtCursor , createNumSegments , createFixedDurationSegments , createRandomSegments , apparentCutSegments, haveInvalidSegs, currentSegIndexSafe , cur rentCutSeg, currentAppa rentCutSeg, inverseCutSegments , clearSegments , loadCutSegments , isSegmentSelected , setCutTime , setCurrentSegIndex , onLabelSelectedSegments , deselectAllSegments , selectAllSegments , selectOnlyCurrentSegment , toggleCurrentSegmentSelected , invertSelectedSegments , removeSelectedSegments , setDeselectedSegmentIds , onSelectSegmentsByLabel , onSelectSegmentsByExpr , onMutateSegmentsByExpr , toggleSegmentSelected , selectOnlySegment , get Apparent CutSegmentById, selectedSegments , selectedSegmentsOrInverse , nonFilteredSegmentsOrInverse , segmentsToExport , duplicateCurrentSegment , duplicateSegment , updateSegAtIndex ,
cutSegments , cutSegmentsHistory , createSegmentsFromKeyframes , shuffleSegments , detectBlackScenes , detectSilentScenes , detectSceneChanges , removeCutSegment , invertAllSegments , fillSegmentsGaps , combineOverlappingSegments , combineSelectedSegments , shiftAllSegmentTimes , alignSegmentTimesToKeyframes , updateSegOrder , updateSegOrders , reorderSegsByStartTime , addSegment , setCutStart , setCutEnd , onLabelSegment , splitCurrentSegment , focusSegmentAtCursor , createNumSegments , createFixedDurationSegments , createRandomSegments , haveInvalidSegs, currentSegIndexSafe , cur rentCutSeg, inverseCutSegments , clearSegments , loadCutSegments , isSegmentSelected , setCutTime , setCurrentSegIndex , onLabelSelectedSegments , deselectAllSegments , selectAllSegments , selectOnlyCurrentSegment , toggleCurrentSegmentSelected , invertSelectedSegments , removeSelectedSegments , setDeselectedSegmentIds , onSelectSegmentsByLabel , onSelectSegmentsByExpr , onMutateSegmentsByExpr , toggleSegmentSelected , selectOnlySegment , get CutSegmentById, selectedSegments , selectedSegmentsOrInverse , nonFilteredSegmentsOrInverse , segmentsToExport , duplicateCurrentSegment , duplicateSegment , updateSegAtIndex , getSegApparentEnd , getApparentCutSegments ,
} = useSegments ( { filePath , workingRef , setWorking , setProgress , videoStream : activeVideoStream , duration , getRelevantTime , maxLabelLength , checkFileOpened , invertCutSegments , segmentsToChaptersOnly , timecodePlaceholder , parseTimecode , appendFfmpegCommandLog } ) ;
const { getEdlFilePath , getEdlFilePathOld , projectFileSavePath , getProjectFileSavePath } = useSegmentsAutoSave ( { autoSaveProjectFile , storeProjectInWorkingDir , filePath , customOutDir , cutSegments } ) ;
@ -343,12 +343,12 @@ function App() {
const { nonCopiedExtraStreams , exportExtraStreams , mainCopiedThumbnailStreams , numStreamsToCopy , toggleStripAudio , toggleStripThumbnail , copyAnyAudioTrack , copyStreamIdsByFile , setCopyStreamIdsByFile , copyFileStreams , mainCopiedStreams , setCopyStreamIdsForPath , toggleCopyStreamId , isCopyingStreamId } = useStreamsMeta ( { mainStreams , filePath , autoExportExtraStreams } ) ;
const segmentAtCursor = useMemo ( ( ) = > {
const segmentsAtCursorIndexes = findSegmentsAtCursor ( apparentC utSegments, commandedTime ) ;
const segmentsAtCursorIndexes = findSegmentsAtCursor ( c utSegments, commandedTime ) ;
const firstSegmentAtCursorIndex = segmentsAtCursorIndexes [ 0 ] ;
if ( firstSegmentAtCursorIndex == null ) return undefined ;
return cutSegments [ firstSegmentAtCursorIndex ] ;
} , [ apparentCutSegments, commandedTime , cutSegments ] ) ;
} , [ cutSegments, commandedTime ] ) ;
const segmentAtCursorRef = useRef < StateSegment > ( ) ;
useEffect ( ( ) = > {
@ -371,8 +371,8 @@ function App() {
seekAbs ( nextFrame / fps ) ;
} , [ detectedFps , seekAbs , videoRef ] ) ;
const jumpSegStart = useCallback ( ( index : number ) = > seekAbs ( apparentCutSegments[ index ] ! . start ) , [ apparentC utSegments, seekAbs ] ) ;
const jumpSegEnd = useCallback ( ( index : number ) = > seekAbs ( apparentCutSegments[ index ] ! . end ) , [ apparentCutSegments , seekAbs ] ) ;
const jumpSegStart = useCallback ( ( index : number ) = > seekAbs ( getSegApparentStart( cutSegments [ index ] ! ) ) , [ c utSegments, seekAbs ] ) ;
const jumpSegEnd = useCallback ( ( index : number ) = > seekAbs ( getSegApparentEnd( cutSegments [ index ] ! ) ) , [ cutSegments , getSegApparentEnd , seekAbs ] ) ;
const jumpCutStart = useCallback ( ( ) = > jumpSegStart ( currentSegIndexSafe ) , [ currentSegIndexSafe , jumpSegStart ] ) ;
const jumpCutEnd = useCallback ( ( ) = > jumpSegEnd ( currentSegIndexSafe ) , [ currentSegIndexSafe , jumpSegEnd ] ) ;
const jumpTimelineStart = useCallback ( ( ) = > seekAbs ( 0 ) , [ seekAbs ] ) ;
@ -700,21 +700,21 @@ function App() {
if ( playbackModeRef . current != null ) {
const selectedSegmentAtCursor = selectedSegments . find ( ( selectedSegment ) = > selectedSegment . segId === segmentAtCursorRef . current ? . segId ) ;
const isSomeSegmentAtCursor = selectedSegmentAtCursor != null && commandedTimeRef . current != null && selectedSegmentAtCursor. end - commandedTimeRef . current > 0.1 ;
const isSomeSegmentAtCursor = selectedSegmentAtCursor != null && commandedTimeRef . current != null && getSegApparentEnd( selectedSegmentAtCursor ) - commandedTimeRef . current > 0.1 ;
if ( ! isSomeSegmentAtCursor ) { // if a segment is already at cursor, don't do anything
if ( playbackModeRef . current === 'loop-selected-segments' ) {
const firstSelectedSegment = selectedSegments [ 0 ] ;
if ( firstSelectedSegment == null ) throw new Error ( ) ;
const index = apparentC utSegments. indexOf ( firstSelectedSegment ) ;
const index = c utSegments. indexOf ( firstSelectedSegment ) ;
if ( index >= 0 ) setCurrentSegIndex ( index ) ;
seekAbs ( firstSelectedSegment . start ) ;
} else {
seekAbs ( currentApparentCutSeg. start ) ;
seekAbs ( getSegApparentStart( currentCutSeg ) ) ;
}
}
}
play ( resetPlaybackRate ) ;
} , [ playbackModeRef , playingRef , play , pause , selectedSegments , commandedTimeRef , apparentC utSegments, setCurrentSegIndex , seekAbs , current Apparent CutSeg. start ] ) ;
} , [ playbackModeRef , playingRef , play , pause , selectedSegments , commandedTimeRef , getSegApparentEnd, c utSegments, setCurrentSegIndex , seekAbs , current CutSeg] ) ;
const onTimeUpdate = useCallback < ReactEventHandler < HTMLVideoElement > > ( ( e ) = > {
const { currentTime } = e . currentTarget ;
@ -723,28 +723,30 @@ function App() {
const playbackMode = playbackModeRef . current ;
if ( playbackMode != null && segmentAtCursorRef . current != null ) { // todo and is currently playing?
const playingSegment = get Apparent CutSegmentById( segmentAtCursorRef . current . segId ) ;
const playingSegment = get CutSegmentById( segmentAtCursorRef . current . segId ) ;
if ( playingSegment != null ) {
const nextAction = playOnlyCurrentSegment ( { playbackMode , currentTime , playingSegment } ) ;
// console.log(nextAction);
if ( nextAction . nextSegment ) {
const index = selectedSegments . indexOf ( playingSegment ) ;
let newIndex = getNewJumpIndex ( index >= 0 ? index : 0 , 1 ) ;
if ( newIndex > selectedSegments . length - 1 ) newIndex = 0 ; // have reached end of last segment, start over
const nextSelectedSegment = selectedSegments [ newIndex ] ;
if ( nextSelectedSegment != null ) seekAbs ( nextSelectedSegment . start ) ;
}
if ( nextAction . seekTo != null ) {
seekAbs ( nextAction . seekTo ) ;
}
if ( nextAction . exit ) {
playbackModeRef . current = undefined ;
pause ( ) ;
const nextAction = getPlaybackMode ( { playbackMode , currentTime , playingSegment : { start : getSegApparentStart ( playingSegment ) , end : getSegApparentEnd ( playingSegment ) } } ) ;
if ( nextAction != null ) {
console . log ( nextAction ) ;
if ( nextAction . nextSegment ) {
const index = selectedSegments . indexOf ( playingSegment ) ;
let newIndex = getNewJumpIndex ( index >= 0 ? index : 0 , 1 ) ;
if ( newIndex > selectedSegments . length - 1 ) newIndex = 0 ; // have reached end of last segment, start over
const nextSelectedSegment = selectedSegments [ newIndex ] ;
if ( nextSelectedSegment != null ) seekAbs ( nextSelectedSegment . start ) ;
}
if ( nextAction . seekTo != null ) {
seekAbs ( nextAction . seekTo ) ;
}
if ( nextAction . exit ) {
playbackModeRef . current = undefined ;
pause ( ) ;
}
}
}
}
} , [ get Apparent CutSegmentById, pause , playbackModeRef , playerTime , seekAbs , selectedSegments , setPlayerTime ] ) ;
} , [ get CutSegmentById, getSegApparentEn d, pause , playbackModeRef , playerTime , seekAbs , selectedSegments , setPlayerTime ] ) ;
const closeFileWithConfirm = useCallback ( ( ) = > {
if ( ! isFileOpened || workingRef . current ) return ;
@ -992,7 +994,7 @@ function App() {
// Special segments-to-chapters mode:
let chaptersToAdd : Chapter [ ] | undefined ;
if ( segmentsToChaptersOnly ) {
const sortedSegments = sortSegments ( selectedSegmentsOrInverse) ;
const sortedSegments = sortSegments ( getApparentCutSegments( selectedSegmentsOrInverse) ) ;
if ( hasAnySegmentOverlap ( sortedSegments ) ) {
errorToast ( i18n . t ( 'Make sure you have no overlapping segments.' ) ) ;
return ;
@ -1138,7 +1140,7 @@ function App() {
setWorking ( undefined ) ;
setProgress ( undefined ) ;
}
} , [ filePath , numStreamsToCopy , segmentsToExport , haveInvalidSegs , workingRef , setWorking , segmentsToChaptersOnly , outSegTemplateOrDefault , generateOutSegFileNames , cutMultiple , outputDir , customOutDir , fileFormat , duration , isRotationSet , effectiveRotation , copyFileStreams , allFilesMeta , keyframeCut , shortestFlag , ffmpegExperimental , preserveMetadata , preserveMetadataOnMerge , preserveMovData , preserveChapters , movFastStart , avoidNegativeTs , customTagsByFile , paramsByStreamId , detectedFps , willMerge , enableOverwriteOutput , exportConfirmEnabled , mainFileFormatData , mainStreams , exportExtraStreams , areWeCutting , hideAllNotifications , cleanupChoices . cleanupAfterExport , cleanupFilesWithDialog , selectedSegmentsOrInverse, t , mergedFileTemplateOrDefault , segmentsToChapters , invertCutSegments , generateMergedFileNames , autoConcatCutSegments , autoDeleteMergedSegments , nonCopiedExtraStreams , extractStreams , showOsNotification , handleExportFailed ] ) ;
} , [ filePath , numStreamsToCopy , segmentsToExport , haveInvalidSegs , workingRef , setWorking , segmentsToChaptersOnly , outSegTemplateOrDefault , generateOutSegFileNames , cutMultiple , outputDir , customOutDir , fileFormat , duration , isRotationSet , effectiveRotation , copyFileStreams , allFilesMeta , keyframeCut , shortestFlag , ffmpegExperimental , preserveMetadata , preserveMetadataOnMerge , preserveMovData , preserveChapters , movFastStart , avoidNegativeTs , customTagsByFile , paramsByStreamId , detectedFps , willMerge , enableOverwriteOutput , exportConfirmEnabled , mainFileFormatData , mainStreams , exportExtraStreams , areWeCutting , hideAllNotifications , cleanupChoices . cleanupAfterExport , cleanupFilesWithDialog , getApparentCutSegments, selectedSegmentsOrInverse, t , mergedFileTemplateOrDefault , segmentsToChapters , invertCutSegments , generateMergedFileNames , autoConcatCutSegments , autoDeleteMergedSegments , nonCopiedExtraStreams , extractStreams , showOsNotification , handleExportFailed ] ) ;
const onExportPress = useCallback ( async ( ) = > {
if ( ! filePath ) return ;
@ -1169,7 +1171,7 @@ function App() {
const extractSegmentFramesAsImages = useCallback ( async ( segIds : string [ ] ) = > {
if ( ! filePath || detectedFps == null || workingRef . current ) return ;
const segments = apparentC utSegments. filter ( ( seg ) = > segIds . includes ( seg . segId ) ) ;
const segments = getApparentCutSegments( c utSegments. filter ( ( seg ) = > segIds . includes ( seg . segId ) ) ) ;
const segmentsNumFrames = segments . reduce ( ( acc , { start , end } ) = > acc + ( getFrameCount ( end - start ) ? ? 0 ) , 0 ) ;
const captureFramesResponse = await askExtractFramesAsImages ( { segmentsNumFrames , plural : segments.length > 1 , fps : detectedFps } ) ;
if ( captureFramesResponse == null ) return ;
@ -1207,7 +1209,7 @@ function App() {
setWorking ( undefined ) ;
setProgress ( undefined ) ;
}
} , [ apparentCutSegments, captureFormat , captureFrameFileNameFormat , captureFrameQuality , captureFramesRange , customOutDir , detectedFps , filePath , getFrameCount , hideAllNotifications , outputDir , setWorking , showOsNotification , w orkingRef ] ) ;
} , [ filePath, detectedFps , workingRef , getApparentCutSegments , cutSegments , getFrameCount , setWorking , hideAllNotifications , captureFramesRange , customOutDir , captureFormat , captureFrameQuality , captureFrameFileNameFormat , showOsNotification , outputDi r] ) ;
const extractCurrentSegmentFramesAsImages = useCallback ( ( ) = > extractSegmentFramesAsImages ( [ currentCutSeg ? . segId ] ) , [ currentCutSeg ? . segId , extractSegmentFramesAsImages ] ) ;
const extractSelectedSegmentsFramesAsImages = useCallback ( ( ) = > extractSegmentFramesAsImages ( selectedSegments . map ( ( seg ) = > seg . segId ) ) , [ extractSegmentFramesAsImages , selectedSegments ] ) ;
@ -1863,10 +1865,10 @@ function App() {
const onEditSegmentTags = useCallback ( ( index : number ) = > {
setEditingSegmentTagsSegmentIndex ( index ) ;
const seg = apparentC utSegments[ index ] ;
if ( seg == null ) throw new Error ( ) ;
const seg = c utSegments[ index ] ;
invariant ( seg != null ) ;
setEditingSegmentTags ( getSegmentTags ( seg ) ) ;
} , [ apparentC utSegments] ) ;
} , [ c utSegments] ) ;
const editCurrentSegmentTags = useCallback ( ( ) = > {
onEditSegmentTags ( currentSegIndexSafe ) ;
@ -1895,7 +1897,7 @@ function App() {
const mainActions = useMemo ( ( ) = > {
async function exportYouTube() {
if ( ! checkFileOpened ( ) ) return ;
await openYouTubeChaptersDialog ( formatYouTube ( apparentCutSegments ) ) ;
await openYouTubeChaptersDialog ( formatYouTube ( getApparentCutSegments( cutSegments ) ) ) ;
}
function seekReset() {
@ -2037,7 +2039,7 @@ function App() {
} ;
return ret ;
} , [ toggleLoopSelectedSegments , pause , timelineToggleComfortZoom , captureSnapshot , captureSnapshotAsCoverArt , setCutStart , setCutEnd , cleanupFilesDialog , splitCurrentSegment , focusSegmentAtCursor , increaseRotation , goToTimecode , jumpCutStart , jumpCutEnd , jumpTimelineStart , jumpTimelineEnd , batchOpenSelectedFile , closeBatch , addSegment , duplicateCurrentSegment , onExportPress , extractCurrentSegmentFramesAsImages , extractSelectedSegmentsFramesAsImages , reorderSegsByStartTime , invertAllSegments , fillSegmentsGaps , combineOverlappingSegments , combineSelectedSegments , createFixedDurationSegments , createNumSegments , createRandomSegments , alignSegmentTimesToKeyframes , shuffleSegments , clearSegments , toggleSegmentsList , toggleStreamsSelector , extractAllStreams , convertFormatBatch , concatBatch , toggleCaptureFormat , toggleStripAudio , toggleStripThumbnail , askStartTimeOffset , deselectAllSegments , selectAllSegments , selectOnlyCurrentSegment , editCurrentSegmentTags , toggleCurrentSegmentSelected , invertSelectedSegments , removeSelectedSegments , tryFixInvalidDuration , shiftAllSegmentTimes , toggleMuted , copySegmentsToClipboard , handleShowStreamsSelectorClick , openFilesDialog , openDirDialog , toggleSettings , createSegmentsFromKeyframes , toggleWaveformMode , toggleShowThumbnails , toggleShowKeyframes , showIncludeExternalStreamsDialog , toggleFullscreenVideo , checkFileOpened , apparentC utSegments, seekRel , keyboardSeekAccFactor , togglePlay , play , userChangePlaybackRate , keyboardNormalSeekSpeed , keyboardSeekSpeed2 , keyboardSeekSpeed3 , seekRelPercent , seekClosestKeyframe , shortStep , jumpSeg , setCurrentSegIndex , cutSegments . length , zoomRel , batchFileJump , removeCutSegment , currentSegIndexSafe , cutSegmentsHistory , onLabelSegment , toggleLastCommands , userHtml5ifyCurrentFile , toggleKeyframeCut , setPlaybackVolume , closeFileWithConfirm , openSendReportDialogWithState , detectBlackScenes , detectSilentScenes , detectSceneChanges ] ) ;
} , [ toggleLoopSelectedSegments , pause , timelineToggleComfortZoom , captureSnapshot , captureSnapshotAsCoverArt , setCutStart , setCutEnd , cleanupFilesDialog , splitCurrentSegment , focusSegmentAtCursor , increaseRotation , goToTimecode , jumpCutStart , jumpCutEnd , jumpTimelineStart , jumpTimelineEnd , batchOpenSelectedFile , closeBatch , addSegment , duplicateCurrentSegment , onExportPress , extractCurrentSegmentFramesAsImages , extractSelectedSegmentsFramesAsImages , reorderSegsByStartTime , invertAllSegments , fillSegmentsGaps , combineOverlappingSegments , combineSelectedSegments , createFixedDurationSegments , createNumSegments , createRandomSegments , alignSegmentTimesToKeyframes , shuffleSegments , clearSegments , toggleSegmentsList , toggleStreamsSelector , extractAllStreams , convertFormatBatch , concatBatch , toggleCaptureFormat , toggleStripAudio , toggleStripThumbnail , askStartTimeOffset , deselectAllSegments , selectAllSegments , selectOnlyCurrentSegment , editCurrentSegmentTags , toggleCurrentSegmentSelected , invertSelectedSegments , removeSelectedSegments , tryFixInvalidDuration , shiftAllSegmentTimes , toggleMuted , copySegmentsToClipboard , handleShowStreamsSelectorClick , openFilesDialog , openDirDialog , toggleSettings , createSegmentsFromKeyframes , toggleWaveformMode , toggleShowThumbnails , toggleShowKeyframes , showIncludeExternalStreamsDialog , toggleFullscreenVideo , checkFileOpened , getApparentCutSegments, c utSegments, seekRel , keyboardSeekAccFactor , togglePlay , play , userChangePlaybackRate , keyboardNormalSeekSpeed , keyboardSeekSpeed2 , keyboardSeekSpeed3 , seekRelPercent , seekClosestKeyframe , shortStep , jumpSeg , setCurrentSegIndex , zoomRel , batchFileJump , removeCutSegment , currentSegIndexSafe , cutSegmentsHistory , onLabelSegment , toggleLastCommands , userHtml5ifyCurrentFile , toggleKeyframeCut , setPlaybackVolume , closeFileWithConfirm , openSendReportDialogWithState , detectBlackScenes , detectSilentScenes , detectSceneChanges ] ) ;
const getKeyboardAction = useCallback ( ( action : MainKeyboardAction ) = > mainActions [ action ] , [ mainActions ] ) ;
@ -2477,7 +2479,8 @@ function App() {
< SegmentList
width = { rightBarWidth }
currentSegIndex = { currentSegIndexSafe }
apparentCutSegments = { apparentCutSegments }
cutSegments = { cutSegments }
getSegApparentEnd = { getSegApparentEnd }
inverseCutSegments = { inverseCutSegments }
getFrameCount = { getFrameCount }
formatTimecode = { formatTimecode }
@ -2535,7 +2538,7 @@ function App() {
zoom = { zoom }
seekAbs = { seekAbs }
durationSafe = { durationSafe }
apparentCutSegments= { apparentC utSegments}
cutSegments= { c utSegments}
setCurrentSegIndex = { setCurrentSegIndex }
currentSegIndexSafe = { currentSegIndexSafe }
inverseCutSegments = { inverseCutSegments }
@ -2550,6 +2553,7 @@ function App() {
goToTimecode = { goToTimecode }
isSegmentSelected = { isSegmentSelected }
darkMode = { darkMode }
getSegApparentEnd = { getSegApparentEnd }
/ >
< BottomBar
@ -2579,7 +2583,7 @@ function App() {
jumpTimelineEnd = { jumpTimelineEnd }
startTimeOffset = { startTimeOffset }
setCutTime = { setCutTime }
currentApparentCutSeg= { currentApparentCutSeg }
getSegApparentEnd= { getSegApparentEnd }
playing = { playing }
shortStep = { shortStep }
seekClosestKeyframe = { seekClosestKeyframe }