@ -3,7 +3,7 @@ import flatMap from 'lodash/flatMap';
 
		
	
		
			
				import  sum  from  'lodash/sum' ;  
		
	
		
			
				import  pMap  from  'p-map' ;  
		
	
		
			
				
 
		
	
		
			
				import  {  getSuffixedOutPath ,  transferTimestamps ,  getOutFileExtension ,  getOutDir ,  deleteDispositionValue ,  getHtml5ifiedPath ,  unlinkWithRetry  }  from  '../util' ;  
		
	
		
			
				import  {  getSuffixedOutPath ,  transferTimestamps ,  getOutFileExtension ,  getOutDir ,  deleteDispositionValue ,  getHtml5ifiedPath ,  unlinkWithRetry ,  getFrameDuration }  from  '../util' ;  
		
	
		
			
				import  {  isCuttingStart ,  isCuttingEnd ,  runFfmpegWithProgress ,  getFfCommandLine ,  getDuration ,  createChaptersFromSegments ,  readFileMeta ,  cutEncodeSmartPart ,  getExperimentalArgs ,  html5ify  as  ffmpegHtml5ify ,  getVideoTimescaleArgs ,  logStdoutStderr ,  runFfmpegConcat  }  from  '../ffmpeg' ;  
		
	
		
			
				import  {  getMapStreamsArgs ,  getStreamIdsToCopy  }  from  '../util/streams' ;  
		
	
		
			
				import  {  getSmartCutParams  }  from  '../smartcut' ;  
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
			
			@ -56,7 +56,7 @@ async function tryDeleteFiles(paths) {
 
		
	
		
			
				  return  pMap ( paths ,  ( path )  =>  unlinkWithRetry ( path ) . catch ( ( err )  =>  console . error ( 'Failed to delete' ,  path ,  err ) ) ,  {  concurrency :  5  } ) ; 
 
		
	
		
			
				}  
		
	
		
			
				
 
		
	
		
			
				function  useFfmpegOperations ( {  filePath ,  treatInputFileModifiedTimeAsStart ,  treatOutputFileModifiedTimeAsStart ,  needSmartCut ,  enableOverwriteOutput ,  outputPlaybackRate  } )  {  
		
	
		
			
				function  useFfmpegOperations ( {  filePath ,  treatInputFileModifiedTimeAsStart ,  treatOutputFileModifiedTimeAsStart ,  needSmartCut ,  enableOverwriteOutput ,  outputPlaybackRate ,  cutFromAdjustmentFrames } )  {  
		
	
		
			
				  const  shouldSkipExistingFile  =  useCallback ( async  ( path )  =>  { 
 
		
	
		
			
				    const  skip  =  ! enableOverwriteOutput  &&  await  pathExists ( path ) ; 
 
		
	
		
			
				    if  ( skip )  console . log ( 'Not overwriting existing file' ,  path ) ; 
 
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
			
			@ -119,7 +119,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				        manuallyCopyDisposition :  true , 
 
		
	
		
			
				      } ) ; 
 
		
	
		
			
				
 
		
	
		
			
				      // Keep this similar to  c utSingle()
 
		
	
		
			
				      // Keep this similar to  losslessC utSingle()
 
		
	
		
			
				      const  ffmpegArgs  =  [ 
 
		
	
		
			
				        '-hide_banner' , 
 
		
	
		
			
				        // No progress if we set loglevel warning :(
 
 
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
			
			@ -172,20 +172,24 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				    } 
 
		
	
		
			
				  } ,  [ shouldSkipExistingFile ,  treatOutputFileModifiedTimeAsStart ] ) ; 
 
		
	
		
			
				
 
		
	
		
			
				  const  c utSingle =  useCallback ( async  ( { 
 
		
	
		
			
				  const  losslessC utSingle =  useCallback ( async  ( { 
 
		
	
		
			
				    keyframeCut :  ssBeforeInput ,  avoidNegativeTs ,  copyFileStreams ,  cutFrom ,  cutTo ,  chaptersPath ,  onProgress ,  outPath , 
 
		
	
		
			
				    videoDuration ,  rotation ,  allFilesMeta ,  outFormat ,  appendFfmpegCommandLog ,  shortestFlag ,  ffmpegExperimental ,  preserveMovData ,  movFastStart ,  customTagsByFile ,  paramsByStreamId ,  videoTimebase , 
 
		
	
		
			
				    videoDuration ,  rotation ,  allFilesMeta ,  outFormat ,  appendFfmpegCommandLog ,  shortestFlag ,  ffmpegExperimental ,  preserveMovData ,  movFastStart ,  customTagsByFile ,  paramsByStreamId ,  videoTimebase ,  detectedFps ,  
 
		
	
		
			
				  } )  =>  { 
 
		
	
		
			
				    if  ( await  shouldSkipExistingFile ( outPath ) )  return ; 
 
		
	
		
			
				
 
		
	
		
			
				    const  frameDuration  =  getFrameDuration ( detectedFps ) ; 
 
		
	
		
			
				
 
		
	
		
			
				    const  cuttingStart  =  isCuttingStart ( cutFrom ) ; 
 
		
	
		
			
				    const  cutFromWithAdjustment  =  cutFrom  +  cutFromAdjustmentFrames  *  frameDuration ; 
 
		
	
		
			
				    const  cuttingEnd  =  isCuttingEnd ( cutTo ,  videoDuration ) ; 
 
		
	
		
			
				    console . log ( 'Cutting from' ,  cuttingStart  ?  cutFrom  :  'start' ,  'to' ,  cuttingEnd  ?  cutTo  :  'end' ) ; 
 
		
	
		
			
				    console . log ( 'Cutting from' ,  cuttingStart  ?  ` ${ cutFrom }  ( ${ cutFromWithAdjustment }  adjusted  ${ cutFromAdjustmentFrames }  frames) ` :  'start' ,  'to' ,  cuttingEnd  ?  cutTo  :  'end' ) ; 
 
		
	
		
			
				
 
		
	
		
			
				    const  cutDuration  =  cutTo  -  cutFrom ; 
 
		
	
		
			
				    let  cutDuration  =  cutTo  -  cutFromWithAdjustment ; 
 
		
	
		
			
				    if  ( detectedFps  !=  null )  cutDuration  =  Math . max ( cutDuration ,  frameDuration ) ;  // ensure at least one frame duration
 
 
		
	
		
			
				
 
		
	
		
			
				    // Don't cut if no need: https://github.com/mifi/lossless-cut/issues/50
 
 
		
	
		
			
				    const  cutFromArgs  =  cuttingStart  ?  [ '-ss' ,  cutFrom . toFixed ( 5 ) ]  :  [ ] ; 
 
		
	
		
			
				    const  cutFromArgs  =  cuttingStart  ?  [ '-ss' ,  cutFrom WithAdjustment . toFixed ( 5 ) ]  :  [ ] ; 
 
		
	
		
			
				    const  cutToArgs  =  cuttingEnd  ?  [ '-t' ,  cutDuration . toFixed ( 5 ) ]  :  [ ] ; 
 
		
	
		
			
				
 
		
	
		
			
				    const  copyFileStreamsFiltered  =  copyFileStreams . filter ( ( {  streamIds  } )  =>  streamIds . length  >  0 ) ; 
 
		
	
	
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
				
			
			@ -321,7 +325,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				    logStdoutStderr ( result ) ; 
 
		
	
		
			
				
 
		
	
		
			
				    await  transferTimestamps ( {  inPath :  filePath ,  outPath ,  cutFrom ,  cutTo ,  treatInputFileModifiedTimeAsStart ,  duration :  isDurationValid ( videoDuration )  ?  videoDuration  :  undefined ,  treatOutputFileModifiedTimeAsStart  } ) ; 
 
		
	
		
			
				  } ,  [ ,  getOutputPlaybackRateArgs ,  outputPlaybackRate ,  shouldSkipExistingFile ,  treatInputFileModifiedTimeAsStart ,  treatOutputFileModifiedTimeAsStart ] ) ; 
 
		
	
		
			
				  } ,  [ cutFromAdjustmentFrames,   filePath,  getOutputPlaybackRateArgs ,  outputPlaybackRate ,  shouldSkipExistingFile ,  treatInputFileModifiedTimeAsStart ,  treatOutputFileModifiedTimeAsStart ] ) ; 
 
		
	
		
			
				
 
		
	
		
			
				  const  cutMultiple  =  useCallback ( async  ( { 
 
		
	
		
			
				    outputDir ,  customOutDir ,  segments ,  outSegFileNames ,  videoDuration ,  rotation ,  detectedFps , 
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -340,7 +344,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				
 
		
	
		
			
				    const  chaptersPath  =  await  writeChaptersFfmetadata ( outputDir ,  chapters ) ; 
 
		
	
		
			
				
 
		
	
		
			
				    // This function will either call  c utSingle (if no smart cut enabled)
 
		
	
		
			
				    // This function will either call  losslessC utSingle (if no smart cut enabled)
 
		
	
		
			
				    // or if enabled, will first cut&encode the part before the next keyframe, trying to match the input file's codec params
 
 
		
	
		
			
				    // then it will cut the part *from* the keyframe to "end", and concat them together and return the concated file
 
 
		
	
		
			
				    // so that for the calling code it looks as if it's just a normal segment
 
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -354,9 +358,8 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				      } 
 
		
	
		
			
				
 
		
	
		
			
				      if  ( ! needSmartCut )  { 
 
		
	
		
			
				        // old fashioned way
 
 
		
	
		
			
				        const  outPath  =  await  makeSegmentOutPath ( ) ; 
 
		
	
		
			
				        await  c utSingle( { 
 
		
	
		
			
				        await  losslessC utSingle( { 
 
		
	
		
			
				          cutFrom :  desiredCutFrom ,  cutTo ,  chaptersPath ,  outPath ,  copyFileStreams ,  keyframeCut ,  avoidNegativeTs ,  videoDuration ,  rotation ,  allFilesMeta ,  outFormat ,  appendFfmpegCommandLog ,  shortestFlag ,  ffmpegExperimental ,  preserveMovData ,  movFastStart ,  customTagsByFile ,  paramsByStreamId ,  onProgress :  ( progress )  =>  onSingleProgress ( i ,  progress ) , 
 
		
	
		
			
				        } ) ; 
 
		
	
		
			
				        return  outPath ; 
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -367,7 +370,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				      const  streamsToCopyFromMainFile  =  copyFileStreams . find ( ( {  path  } )  =>  path  ===  filePath ) . streamIds 
 
		
	
		
			
				        . map ( ( streamId )  =>  streams . find ( ( stream )  =>  stream . index  ===  streamId ) ) ; 
 
		
	
		
			
				
 
		
	
		
			
				      const  {  cutFrom:  encodeCutTo  ,  segmentNeedsSmartCut ,  videoCodec ,  videoBitrate ,  videoStreamIndex ,  videoTimebase  }  =  await  getSmartCutParams ( {  path :  filePath ,  videoDuration ,  desiredCutFrom ,  streams :  streamsToCopyFromMainFile  } ) ; 
 
		
	
		
			
				      const  {  losslessCutFrom ,  segmentNeedsSmartCut ,  videoCodec ,  videoBitrate ,  videoStreamIndex ,  videoTimebase  }  =  await  getSmartCutParams ( {  path :  filePath ,  videoDuration ,  desiredCutFrom ,  streams :  streamsToCopyFromMainFile  } ) ; 
 
		
	
		
			
				
 
		
	
		
			
				      if  ( segmentNeedsSmartCut  &&  ! detectedFps )  throw  new  Error ( 'Smart cut is not possible when FPS is unknown' ) ; 
 
		
	
		
			
				
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -390,42 +393,49 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				
 
		
	
		
			
				      // If we are cutting within two keyframes, just encode the whole part and return that
 
 
		
	
		
			
				      // See https://github.com/mifi/lossless-cut/pull/1267#issuecomment-1236381740
 
 
		
	
		
			
				      if  ( segmentNeedsSmartCut  &&  encodeCutTo  >  cutTo )  { 
 
		
	
		
			
				      if  ( segmentNeedsSmartCut  &&  losslessCutFrom  >  cutTo )  { 
 
		
	
		
			
				        const  outPath  =  await  makeSegmentOutPath ( ) ; 
 
		
	
		
			
				        console . log ( 'Segment is between two keyframes, cutting/encoding the whole segment' ,  {  desiredCutFrom ,  losslessCutFrom ,  cutTo  } ) ; 
 
		
	
		
			
				        await  cutEncodeSmartPartWrapper ( {  cutFrom :  desiredCutFrom ,  cutTo ,  outPath  } ) ; 
 
		
	
		
			
				        return  outPath ; 
 
		
	
		
			
				      } 
 
		
	
		
			
				
 
		
	
		
			
				      const  ext  =  getOutFileExtension ( {  isCustomFormatSelected :  true ,  outFormat ,  filePath  } ) ; 
 
		
	
		
			
				
 
		
	
		
			
				      const  smartCutMain PartOutPath =  segmentNeedsSmartCut 
 
		
	
		
			
				      const  lossless PartOutPath =  segmentNeedsSmartCut 
 
		
	
		
			
				        ?  getSuffixedOutPath ( {  customOutDir ,  filePath ,  nameSuffix :  ` smartcut-segment-copy- ${ i } ${ ext } `  } ) 
 
		
	
		
			
				        :  await  makeSegmentOutPath ( ) ; 
 
		
	
		
			
				
 
		
	
		
			
				      const  smartCutEncodedPartOutPath  =  getSuffixedOutPath ( {  customOutDir ,  filePath ,  nameSuffix :  ` smartcut-segment-encode- ${ i } ${ ext } `  } ) ; 
 
		
	
		
			
				
  
		
	
		
			
				      const  smartCutSegmentsToConcat  =  [ smartCutEncodedPartOutPath ,  smartCutMainPartOutPath ] ; 
 
		
	
		
			
				      if  ( segmentNeedsSmartCut )  { 
 
		
	
		
			
				        console . log ( 'Cutting/encoding lossless part' ,  {  from :  losslessCutFrom ,  to :  cutTo  } ) ;   
		
	
		
			
				      } 
 
		
	
		
			
				
 
		
	
		
			
				      // for smart cut we need to use keyframe cut here, and no avoid_negative_ts
 
 
		
	
		
			
				      await  c utSingle( { 
 
		
	
		
			
				        cutFrom :  encodeCutTo,  cutTo ,  chaptersPath ,  outPath :  smartCutMain  PartOutPath,  copyFileStreams :  copyFileStreamsFiltered ,  keyframeCut :  true ,  avoidNegativeTs :  false ,  videoDuration ,  rotation ,  allFilesMeta ,  outFormat ,  appendFfmpegCommandLog ,  shortestFlag ,  ffmpegExperimental ,  preserveMovData ,  movFastStart ,  customTagsByFile ,  paramsByStreamId ,  videoTimebase ,  onProgress :  onCutProgress , 
 
		
	
		
			
				      await  losslessC utSingle( { 
 
		
	
		
			
				        cutFrom :  losslessCutFrom,  cutTo ,  chaptersPath ,  outPath :  lossless  PartOutPath,  copyFileStreams :  copyFileStreamsFiltered ,  keyframeCut :  true ,  avoidNegativeTs :  false ,  videoDuration ,  rotation ,  allFilesMeta ,  outFormat ,  appendFfmpegCommandLog ,  shortestFlag ,  ffmpegExperimental ,  preserveMovData ,  movFastStart ,  customTagsByFile ,  paramsByStreamId ,  videoTimebase ,  onProgress :  onCutProgress , 
 
		
	
		
			
				      } ) ; 
 
		
	
		
			
				
 
		
	
		
			
				      // OK, just return the single cut file (we may need smart cut in other segments though)
 
 
		
	
		
			
				      if  ( ! segmentNeedsSmartCut )  return  smartCutMainPartOutPath ; 
 
		
	
		
			
				      if  ( ! segmentNeedsSmartCut )  return  losslessPartOutPath ; 
 
		
	
		
			
				
 
		
	
		
			
				      const  smartCutEncodedPartOutPath  =  getSuffixedOutPath ( {  customOutDir ,  filePath ,  nameSuffix :  ` smartcut-segment-encode- ${ i } ${ ext } `  } ) ; 
 
		
	
		
			
				      const  smartCutSegmentsToConcat  =  [ smartCutEncodedPartOutPath ,  losslessPartOutPath ] ; 
 
		
	
		
			
				
 
		
	
		
			
				      try  { 
 
		
	
		
			
				        const  frameDuration  =  1  /  detectedFps ; 
 
		
	
		
			
				        const  encodeCutToSafe  =  Math . max ( desiredCutFrom  +  frameDuration ,  encodeCutTo  -  frameDuration ) ;  // Subtract one frame so we don't end up with duplicates when concating, and make sure we don't create a 0 length segment
 
 
		
	
		
			
				        const  frameDuration  =  getFrameDuration ( detectedFps ) ; 
 
		
	
		
			
				        // Subtract one frame so we don't end up with duplicates when concating, and make sure we don't create a 0 length segment
 
 
		
	
		
			
				        const  encodeCutToSafe  =  Math . max ( desiredCutFrom  +  frameDuration ,  losslessCutFrom  -  frameDuration ) ; 
 
		
	
		
			
				
 
		
	
		
			
				        console . log ( 'Cutting/encoding smart part' ,  {  from :  desiredCutFrom ,  to :  encodeCutToSafe  } ) ; 
 
		
	
		
			
				
 
		
	
		
			
				        await  cutEncodeSmartPartWrapper ( {  cutFrom :  desiredCutFrom ,  cutTo :  encodeCutToSafe ,  outPath :  smartCutEncodedPartOutPath  } ) ; 
 
		
	
		
			
				
 
		
	
		
			
				        // need to re-read streams because indexes may have changed. Using main file as source of streams and metadata
 
 
		
	
		
			
				        const  {  streams :  streamsAfterCut  }  =  await  readFileMeta ( smartCutMain PartOutPath) ; 
 
		
	
		
			
				        const  {  streams :  streamsAfterCut  }  =  await  readFileMeta ( lossless PartOutPath) ; 
 
		
	
		
			
				
 
		
	
		
			
				        const  outPath  =  await  makeSegmentOutPath ( ) ; 
 
		
	
		
			
				
 
		
	
		
			
				        await  concatFiles ( {  paths :  smartCutSegmentsToConcat ,  outDir :  outputDir ,  outPath ,  metadataFromPath :  smartCutMain PartOutPath,  outFormat ,  includeAllStreams :  true ,  streams :  streamsAfterCut ,  ffmpegExperimental ,  preserveMovData ,  movFastStart ,  chapters ,  preserveMetadataOnMerge ,  videoTimebase ,  appendFfmpegCommandLog ,  onProgress :  onConcatProgress  } ) ; 
 
		
	
		
			
				        await  concatFiles ( {  paths :  smartCutSegmentsToConcat ,  outDir :  outputDir ,  outPath ,  metadataFromPath :  lossless PartOutPath,  outFormat ,  includeAllStreams :  true ,  streams :  streamsAfterCut ,  ffmpegExperimental ,  preserveMovData ,  movFastStart ,  chapters ,  preserveMetadataOnMerge ,  videoTimebase ,  appendFfmpegCommandLog ,  onProgress :  onConcatProgress  } ) ; 
 
		
	
		
			
				        return  outPath ; 
 
		
	
		
			
				      }  finally  { 
 
		
	
		
			
				        await  tryDeleteFiles ( smartCutSegmentsToConcat ) ; 
 
		
	
	
		
			
				
					
						
						
						
							
								 
						
					 
				
			
			@ -439,7 +449,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 
		
	
		
			
				    }  finally  { 
 
		
	
		
			
				      if  ( chaptersPath )  await  tryDeleteFiles ( [ chaptersPath ] ) ; 
 
		
	
		
			
				    } 
 
		
	
		
			
				  } ,  [ concatFiles ,  c utSingle,  filePath ,  needSmartCut ,  shouldSkipExistingFile ] ) ; 
 
		
	
		
			
				  } ,  [ concatFiles ,  losslessC utSingle,  filePath ,  needSmartCut ,  shouldSkipExistingFile ] ) ; 
 
		
	
		
			
				
 
		
	
		
			
				  const  autoConcatCutSegments  =  useCallback ( async  ( {  customOutDir ,  outFormat ,  segmentPaths ,  ffmpegExperimental ,  onProgress ,  preserveMovData ,  movFastStart ,  autoDeleteMergedSegments ,  chapterNames ,  preserveMetadataOnMerge ,  appendFfmpegCommandLog ,  mergedOutFilePath  } )  =>  { 
 
		
	
		
			
				    const  outDir  =  getOutDir ( customOutDir ,  filePath ) ;