kopia lustrzana https://github.com/mifi/lossless-cut
				
				
				
			
							rodzic
							
								
									250505e9cd
								
							
						
					
					
						commit
						2505053f1d
					
				| 
						 | 
				
			
			@ -646,7 +646,7 @@ function App() {
 | 
			
		|||
  const needSmartCut = !!(areWeCutting && enableSmartCut);
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration, extractStreams,
 | 
			
		||||
    concatFiles, html5ifyDummy, cutMultiple, concatCutSegments, html5ify, fixInvalidDuration, extractStreams, tryDeleteFiles,
 | 
			
		||||
  } = useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, cutToAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate: smartCutBitrate, appendFfmpegCommandLog });
 | 
			
		||||
 | 
			
		||||
  const { captureFrameFromTag, captureFrameFromFfmpeg, captureFramesRange } = useFrameCapture({ appendFfmpegCommandLog, formatTimecode, treatOutputFileModifiedTimeAsStart });
 | 
			
		||||
| 
						 | 
				
			
			@ -1133,19 +1133,22 @@ function App() {
 | 
			
		|||
        invariant(fileName != null);
 | 
			
		||||
        mergedOutFilePath = getOutPath({ customOutDir, filePath, fileName });
 | 
			
		||||
 | 
			
		||||
        await autoConcatCutSegments({
 | 
			
		||||
        await concatCutSegments({
 | 
			
		||||
          customOutDir,
 | 
			
		||||
          outFormat: fileFormat,
 | 
			
		||||
          segmentPaths: outFiles,
 | 
			
		||||
          segmentPaths: outFiles.map((f) => f.path),
 | 
			
		||||
          ffmpegExperimental,
 | 
			
		||||
          preserveMovData,
 | 
			
		||||
          movFastStart,
 | 
			
		||||
          onProgress: setProgress,
 | 
			
		||||
          chapterNames,
 | 
			
		||||
          autoDeleteMergedSegments,
 | 
			
		||||
          preserveMetadataOnMerge,
 | 
			
		||||
          mergedOutFilePath,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // don't delete existing files that were not created by losslesscut now (due to overwrite disabled) https://github.com/mifi/lossless-cut/issues/2436
 | 
			
		||||
        const createdOutFiles = outFiles.flatMap((f) => (f.created ? [f.path] : []));
 | 
			
		||||
        if (autoDeleteMergedSegments) await tryDeleteFiles(createdOutFiles);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!enableOverwriteOutput) warnings.push(i18n.t('Overwrite output setting is disabled and some files might have been skipped.'));
 | 
			
		||||
| 
						 | 
				
			
			@ -1173,8 +1176,7 @@ function App() {
 | 
			
		|||
 | 
			
		||||
      if (areWeCutting) notices.push(i18n.t('Cutpoints may be inaccurate.'));
 | 
			
		||||
 | 
			
		||||
      const revealPath = willMerge && mergedOutFilePath != null ? mergedOutFilePath : outFiles[0];
 | 
			
		||||
      invariant(revealPath != null);
 | 
			
		||||
      const revealPath = willMerge && mergedOutFilePath != null ? mergedOutFilePath : outFiles[0]!.path;
 | 
			
		||||
      if (!hideAllNotifications) {
 | 
			
		||||
        showOsNotification(i18n.t('Export finished'));
 | 
			
		||||
        openExportFinishedToast({ filePath: revealPath, warnings, notices });
 | 
			
		||||
| 
						 | 
				
			
			@ -1213,7 +1215,7 @@ function App() {
 | 
			
		|||
      setWorking(undefined);
 | 
			
		||||
      setProgress(undefined);
 | 
			
		||||
    }
 | 
			
		||||
  }, [filePath, numStreamsToCopy, segmentsToExport, haveInvalidSegs, workingRef, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, cutMultiple, outputDir, customOutDir, fileFormat, fileDuration, 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, segmentsOrInverse, t, mergedFileTemplateOrDefault, segmentsToChapters, invertCutSegments, generateMergedFileNames, autoConcatCutSegments, autoDeleteMergedSegments, nonCopiedExtraStreams, extractStreams, showOsNotification, handleExportFailed]);
 | 
			
		||||
  }, [filePath, numStreamsToCopy, haveInvalidSegs, workingRef, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, cutMultiple, outputDir, customOutDir, fileFormat, fileDuration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, segmentsToExport, shortestFlag, ffmpegExperimental, preserveMetadata, preserveMetadataOnMerge, preserveMovData, preserveChapters, movFastStart, avoidNegativeTs, customTagsByFile, paramsByStreamId, detectedFps, willMerge, enableOverwriteOutput, exportConfirmEnabled, mainFileFormatData, mainStreams, exportExtraStreams, areWeCutting, hideAllNotifications, cleanupChoices.cleanupAfterExport, cleanupFilesWithDialog, segmentsOrInverse.selected, t, mergedFileTemplateOrDefault, segmentsToChapters, invertCutSegments, generateMergedFileNames, concatCutSegments, autoDeleteMergedSegments, tryDeleteFiles, nonCopiedExtraStreams, extractStreams, showOsNotification, handleExportFailed]);
 | 
			
		||||
 | 
			
		||||
  const onExportPress = useCallback(async () => {
 | 
			
		||||
    if (!filePath) return;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -258,8 +258,6 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 | 
			
		|||
    videoTimebase?: number | undefined,
 | 
			
		||||
    detectedFps?: number,
 | 
			
		||||
  }) => {
 | 
			
		||||
    if (await shouldSkipExistingFile(outPath)) return;
 | 
			
		||||
 | 
			
		||||
    const frameDuration = getFrameDuration(detectedFps);
 | 
			
		||||
 | 
			
		||||
    const cuttingStart = isCuttingStart(cutFrom);
 | 
			
		||||
| 
						 | 
				
			
			@ -417,11 +415,21 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 | 
			
		|||
    logStdoutStderr(result);
 | 
			
		||||
 | 
			
		||||
    await transferTimestamps({ inPath: filePath, outPath, cutFrom, cutTo, treatInputFileModifiedTimeAsStart, duration: isDurationValid(fileDuration) ? fileDuration : undefined, treatOutputFileModifiedTimeAsStart });
 | 
			
		||||
  }, [appendFfmpegCommandLog, cutFromAdjustmentFrames, cutToAdjustmentFrames, filePath, getOutputPlaybackRateArgs, shouldSkipExistingFile, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart]);
 | 
			
		||||
  }, [appendFfmpegCommandLog, cutFromAdjustmentFrames, cutToAdjustmentFrames, filePath, getOutputPlaybackRateArgs, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart]);
 | 
			
		||||
 | 
			
		||||
  // inspired by https://gist.github.com/fernandoherreradelasheras/5eca67f4200f1a7cc8281747da08496e
 | 
			
		||||
  const cutEncodeSmartPart = useCallback(async ({ cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate, videoTimebase, allFilesMeta, copyFileStreams, videoStreamIndex, ffmpegExperimental }: {
 | 
			
		||||
    cutFrom: number, cutTo: number, outPath: string, outFormat: string, videoCodec: string, videoBitrate: number, videoTimebase: number, allFilesMeta: AllFilesMeta, copyFileStreams: CopyfileStreams, videoStreamIndex: number, ffmpegExperimental: boolean,
 | 
			
		||||
    cutFrom: number,
 | 
			
		||||
    cutTo: number,
 | 
			
		||||
    outPath: string,
 | 
			
		||||
    outFormat: string,
 | 
			
		||||
    videoCodec: string,
 | 
			
		||||
    videoBitrate: number,
 | 
			
		||||
    videoTimebase: number,
 | 
			
		||||
    allFilesMeta: AllFilesMeta,
 | 
			
		||||
    copyFileStreams: CopyfileStreams,
 | 
			
		||||
    videoStreamIndex: number,
 | 
			
		||||
    ffmpegExperimental: boolean,
 | 
			
		||||
  }) => {
 | 
			
		||||
    invariant(filePath != null);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -517,22 +525,22 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 | 
			
		|||
    // 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
 | 
			
		||||
    async function maybeSmartCutSegment({ start: desiredCutFrom, end: cutTo }: { start: number, end: number }, i: number) {
 | 
			
		||||
      async function makeSegmentOutPath() {
 | 
			
		||||
        const outPath = join(outputDir, outSegFileNames[i]!);
 | 
			
		||||
        // because outSegFileNames might contain slashes https://github.com/mifi/lossless-cut/issues/1532
 | 
			
		||||
        const actualOutputDir = dirname(outPath);
 | 
			
		||||
        if (actualOutputDir !== outputDir) await mkdir(actualOutputDir, { recursive: true });
 | 
			
		||||
        return outPath;
 | 
			
		||||
      }
 | 
			
		||||
    async function cutSegment({ start: desiredCutFrom, end: cutTo }: { start: number, end: number }, i: number) {
 | 
			
		||||
      const finalOutPath = join(outputDir, outSegFileNames[i]!);
 | 
			
		||||
 | 
			
		||||
      if (await shouldSkipExistingFile(finalOutPath)) return { path: finalOutPath, created: false };
 | 
			
		||||
 | 
			
		||||
      // outSegFileNames might contain slashes and therefore might have a subdir(tree) that we need to mkdir
 | 
			
		||||
      // https://github.com/mifi/lossless-cut/issues/1532
 | 
			
		||||
      const actualOutputDir = dirname(finalOutPath);
 | 
			
		||||
      if (actualOutputDir !== outputDir) await mkdir(actualOutputDir, { recursive: true });
 | 
			
		||||
 | 
			
		||||
      if (!needSmartCut) {
 | 
			
		||||
        const outPath = await makeSegmentOutPath();
 | 
			
		||||
        invariant(outFormat != null);
 | 
			
		||||
        await losslessCutSingle({
 | 
			
		||||
          cutFrom: desiredCutFrom, cutTo, chaptersPath, outPath, copyFileStreams, keyframeCut, avoidNegativeTs, fileDuration, rotation, allFilesMeta, outFormat, shortestFlag, ffmpegExperimental, preserveMetadata, preserveMovData, preserveChapters, movFastStart, customTagsByFile, paramsByStreamId, onProgress: (progress) => onSingleProgress(i, progress),
 | 
			
		||||
          cutFrom: desiredCutFrom, cutTo, chaptersPath, outPath: finalOutPath, copyFileStreams, keyframeCut, avoidNegativeTs, fileDuration, rotation, allFilesMeta, outFormat, shortestFlag, ffmpegExperimental, preserveMetadata, preserveMovData, preserveChapters, movFastStart, customTagsByFile, paramsByStreamId, onProgress: (progress) => onSingleProgress(i, progress),
 | 
			
		||||
        });
 | 
			
		||||
        return outPath;
 | 
			
		||||
        return { path: finalOutPath, created: true };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      invariant(filePath != null);
 | 
			
		||||
| 
						 | 
				
			
			@ -572,31 +580,32 @@ 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 && 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;
 | 
			
		||||
        await cutEncodeSmartPartWrapper({ cutFrom: desiredCutFrom, cutTo, outPath: finalOutPath });
 | 
			
		||||
        return { path: finalOutPath, created: true };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      invariant(outFormat != null);
 | 
			
		||||
 | 
			
		||||
      const ext = getOutFileExtension({ isCustomFormatSelected: true, outFormat, filePath });
 | 
			
		||||
 | 
			
		||||
      const losslessPartOutPath = segmentNeedsSmartCut
 | 
			
		||||
        ? getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-copy-${i}${ext}` })
 | 
			
		||||
        : await makeSegmentOutPath();
 | 
			
		||||
 | 
			
		||||
      if (segmentNeedsSmartCut) {
 | 
			
		||||
        console.log('Cutting/encoding lossless part', { from: losslessCutFrom, to: cutTo });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const losslessPartOutPath = segmentNeedsSmartCut
 | 
			
		||||
        ? getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-copy-${i}${ext}` })
 | 
			
		||||
        : finalOutPath;
 | 
			
		||||
 | 
			
		||||
      // for smart cut we need to use keyframe cut here, and no avoid_negative_ts
 | 
			
		||||
      await losslessCutSingle({
 | 
			
		||||
        cutFrom: losslessCutFrom, cutTo, chaptersPath, outPath: losslessPartOutPath, copyFileStreams: copyFileStreamsFiltered, keyframeCut: true, avoidNegativeTs: undefined, fileDuration, rotation, allFilesMeta, outFormat, shortestFlag, ffmpegExperimental, preserveMetadata, preserveMovData, preserveChapters, movFastStart, customTagsByFile, paramsByStreamId, videoTimebase, onProgress,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // OK, just return the single cut file (we may need smart cut in other segments though)
 | 
			
		||||
      if (!segmentNeedsSmartCut) return losslessPartOutPath;
 | 
			
		||||
      // We don't need to concat, just return the single cut file (we may need smart cut in other segments though)
 | 
			
		||||
      if (!segmentNeedsSmartCut) return { path: finalOutPath, created: true };
 | 
			
		||||
 | 
			
		||||
      // We need to concat
 | 
			
		||||
 | 
			
		||||
      const smartCutEncodedPartOutPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-encode-${i}${ext}` });
 | 
			
		||||
      const smartCutSegmentsToConcat = [smartCutEncodedPartOutPath, losslessPartOutPath];
 | 
			
		||||
| 
						 | 
				
			
			@ -607,31 +616,26 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 | 
			
		|||
        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(losslessPartOutPath);
 | 
			
		||||
 | 
			
		||||
        const outPath = await makeSegmentOutPath();
 | 
			
		||||
 | 
			
		||||
        await concatFiles({ paths: smartCutSegmentsToConcat, outDir: outputDir, outPath, metadataFromPath: losslessPartOutPath, outFormat, includeAllStreams: true, streams: streamsAfterCut, ffmpegExperimental, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, videoTimebase, onProgress: onConcatProgress });
 | 
			
		||||
        return outPath;
 | 
			
		||||
        await concatFiles({ paths: smartCutSegmentsToConcat, outDir: outputDir, outPath: finalOutPath, metadataFromPath: losslessPartOutPath, outFormat, includeAllStreams: true, streams: streamsAfterCut, ffmpegExperimental, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, videoTimebase, onProgress: onConcatProgress });
 | 
			
		||||
        return { path: finalOutPath, created: true };
 | 
			
		||||
      } finally {
 | 
			
		||||
        await tryDeleteFiles(smartCutSegmentsToConcat);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const outFiles = await pMap(segments, maybeSmartCutSegment, { concurrency: 1 });
 | 
			
		||||
 | 
			
		||||
      return outFiles;
 | 
			
		||||
      return await pMap(segments, cutSegment, { concurrency: 1 });
 | 
			
		||||
    } finally {
 | 
			
		||||
      if (chaptersPath) await tryDeleteFiles([chaptersPath]);
 | 
			
		||||
    }
 | 
			
		||||
  }, [needSmartCut, filePath, losslessCutSingle, shouldSkipExistingFile, cutEncodeSmartPart, smartCutCustomBitrate, concatFiles]);
 | 
			
		||||
 | 
			
		||||
  const autoConcatCutSegments = useCallback(async ({ customOutDir, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, autoDeleteMergedSegments, chapterNames, preserveMetadataOnMerge, mergedOutFilePath }: {
 | 
			
		||||
  const concatCutSegments = useCallback(async ({ customOutDir, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, chapterNames, preserveMetadataOnMerge, mergedOutFilePath }: {
 | 
			
		||||
    customOutDir: string | undefined,
 | 
			
		||||
    outFormat: string | undefined,
 | 
			
		||||
    segmentPaths: string[],
 | 
			
		||||
| 
						 | 
				
			
			@ -639,7 +643,6 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 | 
			
		|||
    onProgress: (p: number) => void,
 | 
			
		||||
    preserveMovData: boolean,
 | 
			
		||||
    movFastStart: boolean,
 | 
			
		||||
    autoDeleteMergedSegments: boolean,
 | 
			
		||||
    chapterNames: (string | undefined)[] | undefined,
 | 
			
		||||
    preserveMetadataOnMerge: boolean,
 | 
			
		||||
    mergedOutFilePath: string,
 | 
			
		||||
| 
						 | 
				
			
			@ -655,7 +658,6 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 | 
			
		|||
    // need to re-read streams because may have changed
 | 
			
		||||
    const { streams } = await readFileMeta(metadataFromPath);
 | 
			
		||||
    await concatFiles({ paths: segmentPaths, outDir, outPath: mergedOutFilePath, metadataFromPath, outFormat, includeAllStreams: true, streams, ffmpegExperimental, onProgress, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge });
 | 
			
		||||
    if (autoDeleteMergedSegments) await tryDeleteFiles(segmentPaths);
 | 
			
		||||
  }, [concatFiles, filePath, shouldSkipExistingFile]);
 | 
			
		||||
 | 
			
		||||
  const html5ify = useCallback(async ({ customOutDir, filePath: filePathArg, speed, hasAudio, hasVideo, onProgress }: {
 | 
			
		||||
| 
						 | 
				
			
			@ -978,7 +980,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
 | 
			
		|||
  }, [extractAttachmentStreams, extractNonAttachmentStreams, filePath]);
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    cutMultiple, concatFiles, html5ify, html5ifyDummy, fixInvalidDuration, autoConcatCutSegments, extractStreams,
 | 
			
		||||
    cutMultiple, concatFiles, html5ify, html5ifyDummy, fixInvalidDuration, concatCutSegments, extractStreams, tryDeleteFiles,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue