- implement "fill segment gaps" and "invert segments" for markers too
- new function: select all markers
- import PBF as markers - closes #993
pull/2350/head
Mikael Finstad 2025-02-18 12:22:16 +08:00
rodzic 250218b8d9
commit 2502188367
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 25AB36E3E81CBC26
7 zmienionych plików z 74 dodań i 62 usunięć

Wyświetl plik

@ -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, createFixedByteSizedSegments, createRandomSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onMutateSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, findSegmentsAtCursor,
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, labelSegment, splitCurrentSegment, focusSegmentAtCursor, createNumSegments, createFixedDurationSegments, createFixedByteSizedSegments, createRandomSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, labelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, selectSegmentsByLabel, selectSegmentsByExpr, selectAllMarkers, mutateSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, findSegmentsAtCursor,
} = useSegments({ filePath, workingRef, setWorking, setProgress, videoStream: activeVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode, appendFfmpegCommandLog, durationSafe, mainFileMeta });
const { getEdlFilePath, projectFileSavePath, getProjectFileSavePath } = useSegmentsAutoSave({ autoSaveProjectFile, storeProjectInWorkingDir, filePath, customOutDir, cutSegments });
@ -1979,7 +1979,7 @@ function App() {
removeCurrentSegment: () => removeCutSegment(currentSegIndexSafe),
undo: () => cutSegmentsHistory.back(),
redo: () => cutSegmentsHistory.forward(),
labelCurrentSegment: () => { onLabelSegment(currentSegIndexSafe); return false; },
labelCurrentSegment: () => { labelSegment(currentSegIndexSafe); return false; },
addSegment,
duplicateCurrentSegment,
toggleLastCommands: () => { toggleLastCommands(); return false; },
@ -2047,10 +2047,11 @@ function App() {
toggleShowKeyframes,
showIncludeExternalStreamsDialog,
toggleFullscreenVideo,
selectAllMarkers,
};
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, createFixedByteSizedSegments, 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, cutSegments, 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]);
}, [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, createFixedByteSizedSegments, 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, selectAllMarkers, checkFileOpened, cutSegments, seekRel, keyboardSeekAccFactor, togglePlay, play, userChangePlaybackRate, keyboardNormalSeekSpeed, keyboardSeekSpeed2, keyboardSeekSpeed3, seekRelPercent, seekClosestKeyframe, shortStep, jumpSeg, setCurrentSegIndex, zoomRel, batchFileJump, removeCutSegment, currentSegIndexSafe, cutSegmentsHistory, labelSegment, toggleLastCommands, userHtml5ifyCurrentFile, toggleKeyframeCut, setPlaybackVolume, closeFileWithConfirm, openSendReportDialogWithState, detectBlackScenes, detectSilentScenes, detectSceneChanges]);
const getKeyboardAction = useCallback((action: MainKeyboardAction) => mainActions[action], [mainActions]);
@ -2497,7 +2498,7 @@ function App() {
onSegClick={setCurrentSegIndex}
updateSegOrder={updateSegOrder}
updateSegOrders={updateSegOrders}
onLabelSegment={onLabelSegment}
onLabelSegment={labelSegment}
currentCutSeg={currentCutSeg}
segmentAtCursor={segmentAtCursor}
addSegment={addSegment}
@ -2516,10 +2517,11 @@ function App() {
onExtractSegmentFramesAsImages={extractSegmentFramesAsImages}
jumpSegStart={jumpSegStart}
jumpSegEnd={jumpSegEnd}
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
onSelectSegmentsByExpr={onSelectSegmentsByExpr}
onMutateSegmentsByExpr={onMutateSegmentsByExpr}
onLabelSelectedSegments={onLabelSelectedSegments}
onSelectSegmentsByLabel={selectSegmentsByLabel}
onSelectSegmentsByExpr={selectSegmentsByExpr}
onSelectAllMarkers={selectAllMarkers}
onMutateSegmentsByExpr={mutateSegmentsByExpr}
onLabelSelectedSegments={labelSelectedSegments}
updateSegAtIndex={updateSegAtIndex}
editingSegmentTags={editingSegmentTags}
editingSegmentTagsSegmentIndex={editingSegmentTagsSegmentIndex}

Wyświetl plik

@ -47,6 +47,7 @@ const Segment = memo(({
onDeselectAllSegments,
onSelectSegmentsByLabel,
onSelectSegmentsByExpr,
onSelectAllMarkers,
onSelectAllSegments,
onMutateSegmentsByExpr,
jumpSegStart,
@ -66,17 +67,18 @@ const Segment = memo(({
onClick: (i: number) => void,
onRemovePress: UseSegments['removeCutSegment'],
onRemoveSelected: UseSegments['removeSelectedSegments'],
onLabelSelectedSegments: UseSegments['onLabelSelectedSegments'],
onLabelSelectedSegments: UseSegments['labelSelectedSegments'],
onReorderPress: (i: number) => Promise<void>,
onLabelPress: UseSegments['onLabelSegment'],
onLabelPress: UseSegments['labelSegment'],
selected: boolean,
onSelectSingleSegment: UseSegments['selectOnlySegment'],
onToggleSegmentSelected: UseSegments['toggleSegmentSelected'],
onDeselectAllSegments: UseSegments['deselectAllSegments'],
onSelectSegmentsByLabel: UseSegments['onSelectSegmentsByLabel'],
onSelectSegmentsByExpr: UseSegments['onSelectSegmentsByExpr'],
onSelectSegmentsByLabel: UseSegments['selectSegmentsByLabel'],
onSelectSegmentsByExpr: UseSegments['selectSegmentsByExpr'],
onSelectAllMarkers: UseSegments['selectAllMarkers'],
onSelectAllSegments: UseSegments['selectAllSegments'],
onMutateSegmentsByExpr: UseSegments['onMutateSegmentsByExpr'],
onMutateSegmentsByExpr: UseSegments['mutateSegmentsByExpr'],
jumpSegStart: (i: number) => void,
jumpSegEnd: (i: number) => void,
addSegment: UseSegments['addSegment'],
@ -112,6 +114,7 @@ const Segment = memo(({
{ label: t('Select only this segment'), click: () => onSelectSingleSegment(seg) },
{ label: t('Select all segments'), click: () => onSelectAllSegments() },
{ label: t('Deselect all segments'), click: () => onDeselectAllSegments() },
{ label: t('Select all markers'), click: () => onSelectAllMarkers() },
{ label: t('Select segments by label'), click: () => onSelectSegmentsByLabel() },
{ label: t('Select segments by expression'), click: () => onSelectSegmentsByExpr() },
{ label: t('Invert selected segments'), click: () => onInvertSelectedSegments() },
@ -133,7 +136,7 @@ const Segment = memo(({
{ label: t('Segment tags'), click: () => onEditSegmentTags(index) },
...(seg.end != null ? [{ label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg as Pick<InverseCutSegment, 'start' | 'end'>]) }] : []),
];
}, [invertCutSegments, t, addSegment, onLabelSelectedSegments, onRemoveSelected, updateSegOrder, index, jumpSegStart, jumpSegEnd, onLabelPress, onRemovePress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onInvertSelectedSegments, onMutateSegmentsByExpr, onReorderPress, onEditSegmentTags, onExtractSegmentFramesAsImages]);
}, [invertCutSegments, t, addSegment, onLabelSelectedSegments, onRemoveSelected, seg, updateSegOrder, index, jumpSegStart, jumpSegEnd, onLabelPress, onRemovePress, onDuplicateSegmentClick, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectAllMarkers, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onInvertSelectedSegments, onMutateSegmentsByExpr, onReorderPress, onEditSegmentTags, onExtractSegmentFramesAsImages]);
useContextMenu(ref, contextMenuTemplate);
@ -252,6 +255,7 @@ function SegmentList({
onSelectSegmentsByLabel,
onSelectSegmentsByExpr,
onMutateSegmentsByExpr,
onSelectAllMarkers,
onExtractSegmentFramesAsImages,
onLabelSelectedSegments,
onInvertSelectedSegments,
@ -277,7 +281,7 @@ function SegmentList({
addSegment: UseSegments['addSegment'],
removeCutSegment: UseSegments['removeCutSegment'],
onRemoveSelected: UseSegments['removeSelectedSegments'],
onLabelSegment: UseSegments['onLabelSegment'],
onLabelSegment: UseSegments['labelSegment'],
currentCutSeg: UseSegments['currentCutSeg'],
segmentAtCursor: StateSegment | undefined,
toggleSegmentsList: () => void,
@ -288,11 +292,12 @@ function SegmentList({
onToggleSegmentSelected: UseSegments['toggleSegmentSelected'],
onDeselectAllSegments: UseSegments['deselectAllSegments'],
onSelectAllSegments: UseSegments['selectAllSegments'],
onSelectSegmentsByLabel: UseSegments['onSelectSegmentsByLabel'],
onSelectSegmentsByExpr: UseSegments['onSelectSegmentsByExpr'],
onMutateSegmentsByExpr: UseSegments['onMutateSegmentsByExpr'],
onSelectSegmentsByLabel: UseSegments['selectSegmentsByLabel'],
onSelectSegmentsByExpr: UseSegments['selectSegmentsByExpr'],
onSelectAllMarkers: UseSegments['selectAllMarkers'],
onMutateSegmentsByExpr: UseSegments['mutateSegmentsByExpr'],
onExtractSegmentFramesAsImages: (segments: Pick<InverseCutSegment, 'start' | 'end'>[]) => Promise<void>,
onLabelSelectedSegments: UseSegments['onLabelSelectedSegments'],
onLabelSelectedSegments: UseSegments['labelSelectedSegments'],
onInvertSelectedSegments: UseSegments['invertSelectedSegments'],
onDuplicateSegmentClick: UseSegments['duplicateSegment'],
jumpSegStart: (index: number) => void,
@ -510,6 +515,7 @@ function SegmentList({
onMutateSegmentsByExpr={onMutateSegmentsByExpr}
onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages}
onLabelSelectedSegments={onLabelSelectedSegments}
onSelectAllMarkers={onSelectAllMarkers}
onInvertSelectedSegments={onInvertSelectedSegments}
onDuplicateSegmentClick={onDuplicateSegmentClick}
/>

Wyświetl plik

@ -446,6 +446,10 @@ const KeyboardShortcuts = memo(({
name: t('Select all segments'),
category: segmentsAndCutpointsCategory,
},
selectAllMarkers: {
name: t('Select all markers'),
category: segmentsAndCutpointsCategory,
},
toggleCurrentSegmentSelected: {
name: t('Toggle current segment selected'),
category: segmentsAndCutpointsCategory,

Wyświetl plik

@ -183,7 +183,7 @@ it('parses xmeml - with multiple tracks', async () => {
// see https://github.com/mifi/lossless-cut/issues/1195
it('parses fcpxml 1.9', async () => {
expect(await parseFcpXml(await readFixture('FCPXML_1_9.fcpxml'))).toMatchSnapshot();
expect(parseFcpXml(await readFixture('FCPXML_1_9.fcpxml'))).toMatchSnapshot();
});
// https://github.com/mifi/lossless-cut/issues/1024

Wyświetl plik

@ -213,30 +213,15 @@ export function parseCuesheet(cuesheet: ICueSheet) {
});
}
// See https://github.com/mifi/lossless-cut/issues/993#issuecomment-1037090403
// See https://github.com/mifi/lossless-cut/issues/993
export function parsePbf(buf: Buffer) {
const text = buf.toString('utf16le');
const bookmarks = text.split('\n').map((line) => {
return text.split('\n').flatMap((line) => {
const match = line.match(/^\d+=(\d+)\*([^*]+)*([^*]+)?/);
if (match) return { time: parseInt(match[1]!, 10) / 1000, name: match[2] };
return undefined;
}).filter(Boolean);
const out: SegmentBase[] = [];
for (let i = 0; i < bookmarks.length;) {
const bookmark = bookmarks[i]!;
const nextBookmark = bookmarks[i + 1];
if (!nextBookmark) {
out.push({ start: bookmark.time, end: undefined, name: bookmark.name });
i += 1;
} else {
out.push({ start: bookmark.time, end: nextBookmark && nextBookmark.time, name: bookmark.name });
i += nextBookmark.name === ' ' ? 2 : 1;
}
}
return out;
if (match) return [{ start: parseInt(match[1]!, 10) / 1000, name: match[2] }];
return [];
});
}
// https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/FinalCutPro_XML/VersionsoftheInterchangeFormat/VersionsoftheInterchangeFormat.html

Wyświetl plik

@ -238,24 +238,37 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
});
}, [cutSegments, duration, haveInvalidSegs]);
// Guaranteed to have at least one segment (if user has selected none to export (selectedSegments empty), it makes no sense so select all instead.)
const selectedSegments = useMemo(() => (selectedSegmentsRaw.length > 0 ? selectedSegmentsRaw : cutSegments), [cutSegments, selectedSegmentsRaw]);
const invertAllSegments = useCallback(() => {
if (inverseCutSegments.length === 0) {
// treat markers as 0 length
const sortedSegments = sortSegments(selectedSegments.map(({ end, ...rest }) => ({ ...rest, end: end ?? rest.start })));
const inverseSegmentsAndMarkers = invertSegments(sortedSegments, true, true, duration);
if (inverseSegmentsAndMarkers.length === 0) {
errorToast(i18n.t('Make sure you have no overlapping segments.'));
return;
}
// don't reset segColorIndex (which represent colors) when inverting
const newInverseCutSegments = inverseCutSegments.map((inverseSegment, index) => addSegmentColorIndex(createSegment(inverseSegment), index));
// preserve segColorIndex (which represent colors) when inverting
const newInverseCutSegments = inverseSegmentsAndMarkers.map((inverseSegment, index) => addSegmentColorIndex(createSegment(inverseSegment), index));
setCutSegments(newInverseCutSegments);
}, [inverseCutSegments, setCutSegments]);
}, [duration, selectedSegments, setCutSegments]);
const fillSegmentsGaps = useCallback(() => {
if (inverseCutSegments.length === 0) {
// treat markers as 0 length
const sortedSegments = sortSegments(selectedSegments.map(({ end, ...rest }) => ({ ...rest, end: end ?? rest.start })));
const inverseSegmentsAndMarkers = invertSegments(sortedSegments, true, true, duration);
if (inverseSegmentsAndMarkers.length === 0) {
errorToast(i18n.t('Make sure you have no overlapping segments.'));
return;
}
const newInverseCutSegments = inverseCutSegments.map((inverseSegment) => createIndexedSegment({ segment: inverseSegment, incrementCount: true }));
const newInverseCutSegments = inverseSegmentsAndMarkers.map((segment) => createIndexedSegment({ segment, incrementCount: true }));
setCutSegments((existing) => ([...existing, ...newInverseCutSegments]));
}, [createIndexedSegment, inverseCutSegments, setCutSegments]);
}, [createIndexedSegment, duration, selectedSegments, setCutSegments]);
const combineOverlappingSegments = useCallback(() => {
setCutSegments((existingSegments) => combineOverlappingSegments2(existingSegments));
@ -472,7 +485,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
}
}, [checkFileOpened, getRelevantTime, setCutTime]);
const onLabelSegment = useCallback(async (index: number) => {
const labelSegment = useCallback(async (index: number) => {
const { name } = cutSegments[index]!;
const value = await labelSegmentDialog({ currentName: name, maxLength: maxLabelLength });
if (value != null) updateSegAtIndex(index, { name: value });
@ -558,7 +571,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
});
}, [cutSegments.length]);
const onSelectSegmentsByLabel = useCallback(async () => {
const selectSegmentsByLabel = useCallback(async () => {
const { name } = currentCutSeg;
const value = await selectSegmentsByLabelDialog(name);
if (value == null) return;
@ -566,7 +579,11 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
enableSegments(segmentsToEnable);
}, [currentCutSeg, cutSegments, enableSegments]);
const onSelectSegmentsByExpr = useCallback(async () => {
const selectAllMarkers = useCallback(() => {
enableSegments(cutSegments.filter((seg) => seg.end == null));
}, [cutSegments, enableSegments]);
const selectSegmentsByExpr = useCallback(async () => {
const matchSegment = async (seg: StateSegment, index: number, expr: string) => (
(await safeishEval(expr, { segment: getScopeSegment(seg, index) })) === true
);
@ -595,7 +612,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
enableSegments(segmentsToEnable);
}, [cutSegments, enableSegments, getScopeSegment]);
const onMutateSegmentsByExpr = useCallback(async () => {
const mutateSegmentsByExpr = useCallback(async () => {
async function mutateSegment(seg: StateSegment, index: number, expr: string) {
const response = (await safeishEval(expr, { segment: getScopeSegment(seg, index) }));
invariant(typeof response === 'object' && response != null, i18n.t('The expression must return an object'));
@ -642,7 +659,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
safeSetCutSegments(await mutateSegments(value));
}, [cutSegments, getScopeSegment, isSegmentSelected, safeSetCutSegments]);
const onLabelSelectedSegments = useCallback(async () => {
const labelSelectedSegments = useCallback(async () => {
const firstSelectedSegment = selectedSegmentsRaw[0];
if (firstSelectedSegment == null) return;
const { name } = firstSelectedSegment;
@ -654,9 +671,6 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
}));
}, [maxLabelLength, selectedSegmentsRaw, setCutSegments]);
// Guaranteed to have at least one segment (if user has selected none to export (selectedSegments empty), it makes no sense so select all instead.)
const selectedSegments = useMemo(() => (selectedSegmentsRaw.length > 0 ? selectedSegmentsRaw : cutSegments), [cutSegments, selectedSegmentsRaw]);
const selectedSegmentsOrInverse = useMemo<{ start: number, end: number }[]>(() => {
// For invertCutSegments we do not support filtering (selecting) segments
if (invertCutSegments) return inverseCutSegments;
@ -713,7 +727,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
duplicateSegment,
setCutStart,
setCutEnd,
onLabelSegment,
labelSegment,
splitCurrentSegment,
focusSegmentAtCursor,
createNumSegments,
@ -735,16 +749,17 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
setCurrentSegIndex,
setDeselectedSegmentIds,
onLabelSelectedSegments,
labelSelectedSegments,
deselectAllSegments,
selectAllSegments,
selectOnlyCurrentSegment,
toggleCurrentSegmentSelected,
invertSelectedSegments,
removeSelectedSegments,
onSelectSegmentsByLabel,
onSelectSegmentsByExpr,
onMutateSegmentsByExpr,
selectSegmentsByLabel,
selectSegmentsByExpr,
selectAllMarkers,
mutateSegmentsByExpr,
toggleSegmentSelected,
selectOnlySegment,
setCutTime,

Wyświetl plik

@ -1,4 +1,4 @@
export type KeyboardAction = 'addSegment' | 'togglePlayResetSpeed' | 'togglePlayNoResetSpeed' | 'reducePlaybackRate' | 'reducePlaybackRateMore' | 'increasePlaybackRate' | 'increasePlaybackRateMore' | 'timelineToggleComfortZoom' | 'seekPreviousFrame' | 'seekNextFrame' | 'captureSnapshot' | 'setCutStart' | 'setCutEnd' | 'removeCurrentSegment' | 'cleanupFilesDialog' | 'splitCurrentSegment' | 'focusSegmentAtCursor' | 'increaseRotation' | 'goToTimecode' | 'seekBackwards' | 'seekBackwards2' | 'seekBackwards3' | 'seekBackwardsPercent' | 'seekBackwardsPercent' | 'seekBackwardsKeyframe' | 'jumpCutStart' | 'seekForwards' | 'seekForwards2' | 'seekForwards3' | 'seekForwardsPercent' | 'seekForwardsPercent' | 'seekForwardsKeyframe' | 'jumpCutEnd' | 'jumpTimelineStart' | 'jumpTimelineEnd' | 'jumpFirstSegment' | 'jumpPrevSegment' | 'timelineZoomIn' | 'timelineZoomIn' | 'batchPreviousFile' | 'jumpLastSegment' | 'jumpNextSegment' | 'timelineZoomOut' | 'timelineZoomOut' | 'batchNextFile' | 'batchOpenSelectedFile' | 'batchOpenPreviousFile' | 'batchOpenNextFile' | 'undo' | 'undo' | 'redo' | 'redo' | 'copySegmentsToClipboard' | 'copySegmentsToClipboard' | 'toggleFullscreenVideo' | 'labelCurrentSegment' | 'export' | 'toggleKeyboardShortcuts' | 'closeActiveScreen' | 'increaseVolume' | 'decreaseVolume' | 'toggleMuted' | 'detectBlackScenes' | 'detectSilentScenes' | 'detectSceneChanges' | 'toggleLastCommands' | 'play' | 'pause' | 'reloadFile' | 'html5ify' | 'togglePlayOnlyCurrentSegment' | 'toggleLoopOnlyCurrentSegment' | 'toggleLoopStartEndOnlyCurrentSegment' | 'toggleLoopSelectedSegments' | 'editCurrentSegmentTags' | 'duplicateCurrentSegment' | 'reorderSegsByStartTime' | 'invertAllSegments' | 'fillSegmentsGaps' | 'shiftAllSegmentTimes' | 'alignSegmentTimesToKeyframes' | 'createSegmentsFromKeyframes' | 'createFixedDurationSegments' | 'createNumSegments' | 'createFixedByteSizedSegments' | 'createRandomSegments' | 'shuffleSegments' | 'combineOverlappingSegments' | 'combineSelectedSegments' | 'clearSegments' | 'toggleSegmentsList' | 'selectOnlyCurrentSegment' | 'deselectAllSegments' | 'selectAllSegments' | 'toggleCurrentSegmentSelected' | 'invertSelectedSegments' | 'removeSelectedSegments' | 'toggleStreamsSelector' | 'extractAllStreams' | 'showStreamsSelector' | 'showIncludeExternalStreamsDialog' | 'captureSnapshotAsCoverArt' | 'extractCurrentSegmentFramesAsImages' | 'extractSelectedSegmentsFramesAsImages' | 'convertFormatBatch' | 'convertFormatCurrentFile' | 'fixInvalidDuration' | 'closeBatch' | 'concatBatch' | 'toggleKeyframeCutMode' | 'toggleCaptureFormat' | 'toggleStripAudio' | 'toggleStripThumbnail' | 'setStartTimeOffset' | 'toggleWaveformMode' | 'toggleShowThumbnails' | 'toggleShowKeyframes' | 'toggleSettings' | 'openSendReportDialog' | 'openFilesDialog' | 'openDirDialog' | 'exportYouTube' | 'closeCurrentFile' | 'quit';
export type KeyboardAction = 'addSegment' | 'togglePlayResetSpeed' | 'togglePlayNoResetSpeed' | 'reducePlaybackRate' | 'reducePlaybackRateMore' | 'increasePlaybackRate' | 'increasePlaybackRateMore' | 'timelineToggleComfortZoom' | 'seekPreviousFrame' | 'seekNextFrame' | 'captureSnapshot' | 'setCutStart' | 'setCutEnd' | 'removeCurrentSegment' | 'cleanupFilesDialog' | 'splitCurrentSegment' | 'focusSegmentAtCursor' | 'increaseRotation' | 'goToTimecode' | 'seekBackwards' | 'seekBackwards2' | 'seekBackwards3' | 'seekBackwardsPercent' | 'seekBackwardsPercent' | 'seekBackwardsKeyframe' | 'jumpCutStart' | 'seekForwards' | 'seekForwards2' | 'seekForwards3' | 'seekForwardsPercent' | 'seekForwardsPercent' | 'seekForwardsKeyframe' | 'jumpCutEnd' | 'jumpTimelineStart' | 'jumpTimelineEnd' | 'jumpFirstSegment' | 'jumpPrevSegment' | 'timelineZoomIn' | 'timelineZoomIn' | 'batchPreviousFile' | 'jumpLastSegment' | 'jumpNextSegment' | 'timelineZoomOut' | 'timelineZoomOut' | 'batchNextFile' | 'batchOpenSelectedFile' | 'batchOpenPreviousFile' | 'batchOpenNextFile' | 'undo' | 'undo' | 'redo' | 'redo' | 'copySegmentsToClipboard' | 'copySegmentsToClipboard' | 'toggleFullscreenVideo' | 'labelCurrentSegment' | 'export' | 'toggleKeyboardShortcuts' | 'closeActiveScreen' | 'increaseVolume' | 'decreaseVolume' | 'toggleMuted' | 'detectBlackScenes' | 'detectSilentScenes' | 'detectSceneChanges' | 'toggleLastCommands' | 'play' | 'pause' | 'reloadFile' | 'html5ify' | 'togglePlayOnlyCurrentSegment' | 'toggleLoopOnlyCurrentSegment' | 'toggleLoopStartEndOnlyCurrentSegment' | 'toggleLoopSelectedSegments' | 'editCurrentSegmentTags' | 'duplicateCurrentSegment' | 'reorderSegsByStartTime' | 'invertAllSegments' | 'fillSegmentsGaps' | 'shiftAllSegmentTimes' | 'alignSegmentTimesToKeyframes' | 'createSegmentsFromKeyframes' | 'createFixedDurationSegments' | 'createNumSegments' | 'createFixedByteSizedSegments' | 'createRandomSegments' | 'shuffleSegments' | 'combineOverlappingSegments' | 'combineSelectedSegments' | 'clearSegments' | 'toggleSegmentsList' | 'selectOnlyCurrentSegment' | 'deselectAllSegments' | 'selectAllSegments' | 'toggleCurrentSegmentSelected' | 'invertSelectedSegments' | 'removeSelectedSegments' | 'toggleStreamsSelector' | 'extractAllStreams' | 'showStreamsSelector' | 'showIncludeExternalStreamsDialog' | 'captureSnapshotAsCoverArt' | 'extractCurrentSegmentFramesAsImages' | 'extractSelectedSegmentsFramesAsImages' | 'convertFormatBatch' | 'convertFormatCurrentFile' | 'fixInvalidDuration' | 'closeBatch' | 'concatBatch' | 'toggleKeyframeCutMode' | 'toggleCaptureFormat' | 'toggleStripAudio' | 'toggleStripThumbnail' | 'setStartTimeOffset' | 'toggleWaveformMode' | 'toggleShowThumbnails' | 'toggleShowKeyframes' | 'toggleSettings' | 'openSendReportDialog' | 'openFilesDialog' | 'openDirDialog' | 'exportYouTube' | 'closeCurrentFile' | 'quit' | 'selectAllMarkers';
export interface KeyBinding {
keys: string,