1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-22 02:12:30 +01:00

allow extract multiple segments to images

closes #1672
This commit is contained in:
Mikael Finstad 2023-08-24 13:14:39 +02:00
parent 65f0ad7c96
commit b326fbaeaf
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
5 changed files with 36 additions and 16 deletions

View File

@ -1276,19 +1276,34 @@ const App = memo(() => {
}
}, [filePath, getRelevantTime, usingPreviewFile, captureFrameMethod, captureFrameFromFfmpeg, customOutDir, captureFormat, captureFrameQuality, captureFrameFromTag, hideAllNotifications]);
const extractSegmentFramesAsImages = useCallback(async (index) => {
const extractSegmentFramesAsImages = useCallback(async (segIds) => {
if (!filePath || detectedFps == null || workingRef.current) return;
const { start, end } = apparentCutSegments[index];
const segmentNumFrames = getFrameCount(end - start);
const captureFramesResponse = await askExtractFramesAsImages({ segmentNumFrames, fps: detectedFps });
const segments = apparentCutSegments.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;
try {
setWorking(i18n.t('Extracting frames'));
console.log('Extracting frames as images', { segIds, captureFramesResponse });
setCutProgress(0);
const outPath = await captureFramesRange({ customOutDir, filePath, fps: detectedFps, fromTime: start, toTime: end, estimatedMaxNumFiles: captureFramesResponse.estimatedMaxNumFiles, captureFormat, quality: captureFrameQuality, filter: captureFramesResponse.filter, outputTimestamps: captureFrameFileNameFormat === 'timestamp', onProgress: setCutProgress });
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: outPath, text: i18n.t('Frames extracted to: {{path}}', { path: outputDir }) });
let lastOutPath;
let totalProgress = 0;
const onProgress = (progress) => {
totalProgress += progress;
setCutProgress(totalProgress / segments.length);
};
// eslint-disable-next-line no-restricted-syntax
for (const segment of segments) {
const { start, end } = segment;
// 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 });
}
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: lastOutPath, text: i18n.t('Frames extracted to: {{path}}', { path: outputDir }) });
} catch (err) {
handleError(err);
} finally {
@ -1297,7 +1312,8 @@ const App = memo(() => {
}
}, [apparentCutSegments, captureFormat, captureFrameFileNameFormat, captureFrameQuality, captureFramesRange, customOutDir, detectedFps, filePath, getFrameCount, hideAllNotifications, outputDir, setWorking]);
const extractCurrentSegmentFramesAsImages = useCallback(() => extractSegmentFramesAsImages(currentSegIndexSafe), [currentSegIndexSafe, extractSegmentFramesAsImages]);
const extractCurrentSegmentFramesAsImages = useCallback(() => extractSegmentFramesAsImages([currentCutSeg?.segId]), [currentCutSeg?.segId, extractSegmentFramesAsImages]);
const extractSelectedSegmentsFramesAsImages = useCallback(() => extractSegmentFramesAsImages(selectedSegments.map((seg) => seg.segId)), [extractSegmentFramesAsImages, selectedSegments]);
const changePlaybackRate = useCallback((dir, rateMultiplier) => {
if (canvasPlayerEnabled) {
@ -1920,6 +1936,7 @@ const App = memo(() => {
toggleLastCommands: () => { toggleLastCommands(); return false; },
export: onExportPress,
extractCurrentSegmentFramesAsImages,
extractSelectedSegmentsFramesAsImages,
reorderSegsByStartTime,
invertAllSegments,
fillSegmentsGaps,
@ -1995,7 +2012,7 @@ const App = memo(() => {
if (match) return bubble;
return true; // bubble the event
}, [addSegment, alignSegmentTimesToKeyframes, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, combineOverlappingSegments, combineSelectedSegments, concatCurrentBatch, concatDialogVisible, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, duplicateCurrentSegment, exportConfirmVisible, extractAllStreams, extractCurrentSegmentFramesAsImages, fillSegmentsGaps, goToTimecode, increaseRotation, invertAllSegments, invertSelectedSegments, 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, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
}, [addSegment, alignSegmentTimesToKeyframes, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, combineOverlappingSegments, combineSelectedSegments, concatCurrentBatch, concatDialogVisible, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, duplicateCurrentSegment, exportConfirmVisible, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, increaseRotation, invertAllSegments, invertSelectedSegments, 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, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
useKeyboard({ keyBindings, onKeyPress });

View File

@ -63,7 +63,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
{ type: 'separator' },
{ label: t('Segment tags'), click: () => onViewSegmentTags(index) },
{ label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages(index) },
{ label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg.segId]) },
];
}, [invertCutSegments, t, jumpSegStart, jumpSegEnd, addSegment, onLabelPress, onRemovePress, onLabelSelectedSegments, onRemoveSelected, onReorderPress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onInvertSelectedSegments, updateOrder, onViewSegmentTags, index, onExtractSegmentFramesAsImages]);

View File

@ -421,7 +421,11 @@ const KeyboardShortcuts = memo(({
category: outputCategory,
},
extractCurrentSegmentFramesAsImages: {
name: t('Extract frames from segment as image files'),
name: t('Extract frames from current segment as image files'),
category: outputCategory,
},
extractSelectedSegmentsFramesAsImages: {
name: t('Extract frames from selected segments as image files'),
category: outputCategory,
},
cleanupFilesDialog: {

View File

@ -4,9 +4,9 @@ import Swal from '../swal';
// eslint-disable-next-line import/prefer-default-export
export async function askExtractFramesAsImages({ segmentNumFrames, fps }) {
export async function askExtractFramesAsImages({ segmentsNumFrames, plural, fps }) {
const { value: captureChoice } = await Swal.fire({
text: i18n.t('Extract frames of the selected segment as images?'),
text: i18n.t(plural ? 'Extract frames of the selected segments as images' : 'Extract frames of the current segment as images'),
icon: 'question',
input: 'radio',
inputValue: 'thumbnailFilter',
@ -24,7 +24,7 @@ export async function askExtractFramesAsImages({ segmentNumFrames, fps }) {
if (!captureChoice) return undefined;
let filter;
let estimatedMaxNumFiles = segmentNumFrames;
let estimatedMaxNumFiles = segmentsNumFrames;
if (captureChoice === 'thumbnailFilter') {
const { value } = await Swal.fire({
@ -40,7 +40,7 @@ export async function askExtractFramesAsImages({ segmentNumFrames, fps }) {
if (Number.isNaN(intervalFrames) || intervalFrames < 1 || intervalFrames > 1000) return undefined; // a too large value uses a lot of memory
filter = `thumbnail=${intervalFrames}`;
estimatedMaxNumFiles = Math.round(segmentNumFrames / intervalFrames);
estimatedMaxNumFiles = Math.round(segmentsNumFrames / intervalFrames);
}
if (captureChoice === 'selectNthSec' || captureChoice === 'selectNthFrame') {
@ -74,7 +74,7 @@ export async function askExtractFramesAsImages({ segmentNumFrames, fps }) {
}
filter = `select=not(mod(n\\,${nthFrame}))`;
estimatedMaxNumFiles = Math.round(segmentNumFrames / nthFrame);
estimatedMaxNumFiles = Math.round(segmentsNumFrames / nthFrame);
}
if (captureChoice === 'selectScene') {
const { value } = await Swal.fire({

View File

@ -51,7 +51,6 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }) => {
const matches = files.map((fileName) => {
const escapedRegexp = escapeRegExp(getSuffixedFileName(filePath, tmpSuffix));
const regexp = `^${escapedRegexp}(\\d+)`;
console.log(regexp);
const match = fileName.match(new RegExp(regexp));
if (!match) return undefined;
const frameNum = parseInt(match[1], 10);