diff --git a/src/main/ffmpeg.ts b/src/main/ffmpeg.ts index d13772e2..31ed68a7 100644 --- a/src/main/ffmpeg.ts +++ b/src/main/ffmpeg.ts @@ -473,16 +473,17 @@ export async function captureFrames({ from, to, videoPath, outPathTemplate, qual ...(to != null ? ['-t', String(Math.max(0, to - from))] : []), ...getQualityOpts({ captureFormat, quality }), // only apply filter for non-markers - ...(filter != null && to != null + ...(to == null ? [ - '-vf', filter, - // https://superuser.com/questions/1336285/use-ffmpeg-for-thumbnail-selections - ...(framePts ? ['-frame_pts', '1'] : []), - '-vsync', '0', // else we get a ton of duplicates (thumbnail filter) - ] - : [ '-frames:v', '1', // for markers, just capture 1 frame - ] + ] : ( + // for segments (non markers), apply filter (but only if there is one) + filter != null ? [ + '-vf', filter, + // https://superuser.com/questions/1336285/use-ffmpeg-for-thumbnail-selections + ...(framePts ? ['-frame_pts', '1'] : []), + '-vsync', '0', // else we get a ton of duplicates (thumbnail filter) + ] : []) ), ...getCodecOpts(captureFormat), '-f', 'image2', diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 3db093ea..abb404ff 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1239,6 +1239,7 @@ function App() { const extractSegmentsFramesAsImages = useCallback(async (segments: SegmentBase[]) => { if (!filePath || detectedFps == null || workingRef.current || segments.length === 0) return; const segmentsNumFrames = segments.reduce((acc, { start, end }) => acc + (end == null ? 1 : (getFrameCount(end - start) ?? 0)), 0); + // If all segments are markers, we shall export every marker as a file and therefore we don't have to ask user const areAllSegmentsMarkers = segments.every((seg) => seg.end == null); const captureFramesResponse = areAllSegmentsMarkers ? { filter: undefined, estimatedMaxNumFiles: segmentsNumFrames } @@ -1264,7 +1265,7 @@ function App() { // eslint-disable-next-line no-restricted-syntax for (const [index, segment] of segments.entries()) { const { start, end } = segment; - if (filePath == null) throw new Error(); + invariant(filePath != null); // eslint-disable-next-line no-await-in-loop lastOutPath = await captureFramesRange({ customOutDir, filePath, fps: detectedFps, fromTime: start, toTime: end, estimatedMaxNumFiles: captureFramesResponse.estimatedMaxNumFiles, captureFormat, quality: captureFrameQuality, filter: captureFramesResponse.filter, outputTimestamps: captureFrameFileNameFormat === 'timestamp', onProgress: (segmentProgress) => handleSegmentProgress(index, segmentProgress) }); } @@ -2577,7 +2578,7 @@ function App() { onDeselectAllSegments={deselectAllSegments} onSelectAllSegments={selectAllSegments} onInvertSelectedSegments={invertSelectedSegments} - onExtractSegmentFramesAsImages={extractSegmentsFramesAsImages} + onExtractSegmentsFramesAsImages={extractSegmentsFramesAsImages} onExtractSelectedSegmentsFramesAsImages={extractSelectedSegmentsFramesAsImages} jumpSegStart={jumpSegStart} jumpSegEnd={jumpSegEnd} diff --git a/src/renderer/src/SegmentList.tsx b/src/renderer/src/SegmentList.tsx index 554df2bf..635bf56b 100644 --- a/src/renderer/src/SegmentList.tsx +++ b/src/renderer/src/SegmentList.tsx @@ -55,7 +55,7 @@ const Segment = memo(({ jumpSegEnd, addSegment, onEditSegmentTags, - onExtractSegmentFramesAsImages, + onExtractSegmentsFramesAsImages, onExtractSelectedSegmentsFramesAsImages, onInvertSelectedSegments, onDuplicateSegmentClick, @@ -85,7 +85,7 @@ const Segment = memo(({ jumpSegEnd: (i: number) => void, addSegment: UseSegments['addSegment'], onEditSegmentTags: (i: number) => void, - onExtractSegmentFramesAsImages: (segments: Pick[]) => Promise, + onExtractSegmentsFramesAsImages: (segments: Pick[]) => Promise, onExtractSelectedSegmentsFramesAsImages: () => void, onInvertSelectedSegments: UseSegments['invertSelectedSegments'], onDuplicateSegmentClick: UseSegments['duplicateSegment'], @@ -138,9 +138,9 @@ const Segment = memo(({ { type: 'separator' }, { label: t('Segment tags'), click: () => onEditSegmentTags(index) }, - { label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg]) }, + { label: t('Extract frames as image files'), click: () => onExtractSegmentsFramesAsImages([seg]) }, ]; - }, [invertCutSegments, t, addSegment, onLabelSelectedSegments, onRemoveSelected, onExtractSelectedSegmentsFramesAsImages, updateSegOrder, index, jumpSegStart, jumpSegEnd, onLabelPress, onRemovePress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectAllMarkers, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onInvertSelectedSegments, onMutateSegmentsByExpr, onReorderPress, onEditSegmentTags, onExtractSegmentFramesAsImages]); + }, [invertCutSegments, t, addSegment, onLabelSelectedSegments, onRemoveSelected, onExtractSelectedSegmentsFramesAsImages, updateSegOrder, index, jumpSegStart, jumpSegEnd, onLabelPress, onRemovePress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectAllMarkers, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onInvertSelectedSegments, onMutateSegmentsByExpr, onReorderPress, onEditSegmentTags, onExtractSegmentsFramesAsImages]); useContextMenu(ref, contextMenuTemplate); @@ -265,7 +265,7 @@ function SegmentList({ onSelectSegmentsByExpr, onMutateSegmentsByExpr, onSelectAllMarkers, - onExtractSegmentFramesAsImages, + onExtractSegmentsFramesAsImages, onExtractSelectedSegmentsFramesAsImages, onLabelSelectedSegments, onInvertSelectedSegments, @@ -305,7 +305,7 @@ function SegmentList({ onSelectSegmentsByExpr: UseSegments['selectSegmentsByExpr'], onSelectAllMarkers: UseSegments['selectAllMarkers'], onMutateSegmentsByExpr: UseSegments['mutateSegmentsByExpr'], - onExtractSegmentFramesAsImages: (segments: Pick[]) => Promise, + onExtractSegmentsFramesAsImages: (segments: Pick[]) => Promise, onExtractSelectedSegmentsFramesAsImages: () => void, onLabelSelectedSegments: UseSegments['labelSelectedSegments'], onInvertSelectedSegments: UseSegments['invertSelectedSegments'], @@ -523,7 +523,7 @@ function SegmentList({ onSelectSegmentsByLabel={onSelectSegmentsByLabel} onSelectSegmentsByExpr={onSelectSegmentsByExpr} onMutateSegmentsByExpr={onMutateSegmentsByExpr} - onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages} + onExtractSegmentsFramesAsImages={onExtractSegmentsFramesAsImages} onExtractSelectedSegmentsFramesAsImages={onExtractSelectedSegmentsFramesAsImages} onLabelSelectedSegments={onLabelSelectedSegments} onSelectAllMarkers={onSelectAllMarkers} diff --git a/src/renderer/src/dialogs/extractFrames.tsx b/src/renderer/src/dialogs/extractFrames.tsx index 968f5150..cacaeebd 100644 --- a/src/renderer/src/dialogs/extractFrames.tsx +++ b/src/renderer/src/dialogs/extractFrames.tsx @@ -92,6 +92,7 @@ export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps filter = `select=gt(scene\\,${minSceneChange})`; // we don't know estimatedMaxNumFiles here } + // else everyFrame estimatedMaxNumFiles += 1; // just to be sure