mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +01:00
allow selecting segments by expression
also remove select by tags dialog to reduce code it's covered by expression fixes #1999
This commit is contained in:
parent
58adc59c9d
commit
b5028dc142
@ -131,6 +131,7 @@
|
|||||||
"i18next-fs-backend": "^2.1.1",
|
"i18next-fs-backend": "^2.1.1",
|
||||||
"json5": "^2.2.2",
|
"json5": "^2.2.2",
|
||||||
"lodash": "^4.17.19",
|
"lodash": "^4.17.19",
|
||||||
|
"mathjs": "^12.4.2",
|
||||||
"mime-types": "^2.1.14",
|
"mime-types": "^2.1.14",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
|
@ -406,7 +406,7 @@ function App() {
|
|||||||
}, [detectedFps, timecodeFormat, getFrameCount]);
|
}, [detectedFps, timecodeFormat, getFrameCount]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByTag, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex,
|
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex,
|
||||||
} = useSegments({ filePath, workingRef, setWorking, setCutProgress, videoStream: activeVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode });
|
} = useSegments({ filePath, workingRef, setWorking, setCutProgress, videoStream: activeVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode });
|
||||||
|
|
||||||
|
|
||||||
@ -2637,7 +2637,7 @@ function App() {
|
|||||||
jumpSegStart={jumpSegStart}
|
jumpSegStart={jumpSegStart}
|
||||||
jumpSegEnd={jumpSegEnd}
|
jumpSegEnd={jumpSegEnd}
|
||||||
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
||||||
onSelectSegmentsByTag={onSelectSegmentsByTag}
|
onSelectSegmentsByExpr={onSelectSegmentsByExpr}
|
||||||
onLabelSelectedSegments={onLabelSelectedSegments}
|
onLabelSelectedSegments={onLabelSelectedSegments}
|
||||||
updateSegAtIndex={updateSegAtIndex}
|
updateSegAtIndex={updateSegAtIndex}
|
||||||
editingSegmentTags={editingSegmentTags}
|
editingSegmentTags={editingSegmentTags}
|
||||||
|
@ -44,7 +44,7 @@ const Segment = memo(({
|
|||||||
onToggleSegmentSelected,
|
onToggleSegmentSelected,
|
||||||
onDeselectAllSegments,
|
onDeselectAllSegments,
|
||||||
onSelectSegmentsByLabel,
|
onSelectSegmentsByLabel,
|
||||||
onSelectSegmentsByTag,
|
onSelectSegmentsByExpr,
|
||||||
onSelectAllSegments,
|
onSelectAllSegments,
|
||||||
jumpSegStart,
|
jumpSegStart,
|
||||||
jumpSegEnd,
|
jumpSegEnd,
|
||||||
@ -71,7 +71,7 @@ const Segment = memo(({
|
|||||||
onToggleSegmentSelected: UseSegments['toggleSegmentSelected'],
|
onToggleSegmentSelected: UseSegments['toggleSegmentSelected'],
|
||||||
onDeselectAllSegments: UseSegments['deselectAllSegments'],
|
onDeselectAllSegments: UseSegments['deselectAllSegments'],
|
||||||
onSelectSegmentsByLabel: UseSegments['onSelectSegmentsByLabel'],
|
onSelectSegmentsByLabel: UseSegments['onSelectSegmentsByLabel'],
|
||||||
onSelectSegmentsByTag: UseSegments['onSelectSegmentsByTag'],
|
onSelectSegmentsByExpr: UseSegments['onSelectSegmentsByExpr'],
|
||||||
onSelectAllSegments: UseSegments['selectAllSegments'],
|
onSelectAllSegments: UseSegments['selectAllSegments'],
|
||||||
jumpSegStart: (i: number) => void,
|
jumpSegStart: (i: number) => void,
|
||||||
jumpSegEnd: (i: number) => void,
|
jumpSegEnd: (i: number) => void,
|
||||||
@ -109,7 +109,7 @@ const Segment = memo(({
|
|||||||
{ label: t('Select all segments'), click: () => onSelectAllSegments() },
|
{ label: t('Select all segments'), click: () => onSelectAllSegments() },
|
||||||
{ label: t('Deselect all segments'), click: () => onDeselectAllSegments() },
|
{ label: t('Deselect all segments'), click: () => onDeselectAllSegments() },
|
||||||
{ label: t('Select segments by label'), click: () => onSelectSegmentsByLabel() },
|
{ label: t('Select segments by label'), click: () => onSelectSegmentsByLabel() },
|
||||||
{ label: t('Select segments by tag'), click: () => onSelectSegmentsByTag() },
|
{ label: t('Select segments by expression'), click: () => onSelectSegmentsByExpr() },
|
||||||
{ label: t('Invert selected segments'), click: () => onInvertSelectedSegments() },
|
{ label: t('Invert selected segments'), click: () => onInvertSelectedSegments() },
|
||||||
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@ -128,7 +128,7 @@ const Segment = memo(({
|
|||||||
{ label: t('Segment tags'), click: () => onEditSegmentTags(index) },
|
{ label: t('Segment tags'), click: () => onEditSegmentTags(index) },
|
||||||
{ label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg.segId]) },
|
{ label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg.segId]) },
|
||||||
];
|
];
|
||||||
}, [invertCutSegments, t, addSegment, onLabelSelectedSegments, onRemoveSelected, updateSegOrder, index, jumpSegStart, jumpSegEnd, onLabelPress, onRemovePress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onInvertSelectedSegments, onReorderPress, onEditSegmentTags, onExtractSegmentFramesAsImages]);
|
}, [invertCutSegments, t, addSegment, onLabelSelectedSegments, onRemoveSelected, updateSegOrder, index, jumpSegStart, jumpSegEnd, onLabelPress, onRemovePress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onInvertSelectedSegments, onReorderPress, onEditSegmentTags, onExtractSegmentFramesAsImages]);
|
||||||
|
|
||||||
useContextMenu(ref, contextMenuTemplate);
|
useContextMenu(ref, contextMenuTemplate);
|
||||||
|
|
||||||
@ -243,7 +243,7 @@ const SegmentList = memo(({
|
|||||||
onDeselectAllSegments,
|
onDeselectAllSegments,
|
||||||
onSelectAllSegments,
|
onSelectAllSegments,
|
||||||
onSelectSegmentsByLabel,
|
onSelectSegmentsByLabel,
|
||||||
onSelectSegmentsByTag,
|
onSelectSegmentsByExpr,
|
||||||
onExtractSegmentFramesAsImages,
|
onExtractSegmentFramesAsImages,
|
||||||
onLabelSelectedSegments,
|
onLabelSelectedSegments,
|
||||||
onInvertSelectedSegments,
|
onInvertSelectedSegments,
|
||||||
@ -281,7 +281,7 @@ const SegmentList = memo(({
|
|||||||
onDeselectAllSegments: UseSegments['deselectAllSegments'],
|
onDeselectAllSegments: UseSegments['deselectAllSegments'],
|
||||||
onSelectAllSegments: UseSegments['selectAllSegments'],
|
onSelectAllSegments: UseSegments['selectAllSegments'],
|
||||||
onSelectSegmentsByLabel: UseSegments['onSelectSegmentsByLabel'],
|
onSelectSegmentsByLabel: UseSegments['onSelectSegmentsByLabel'],
|
||||||
onSelectSegmentsByTag: UseSegments['onSelectSegmentsByTag'],
|
onSelectSegmentsByExpr: UseSegments['onSelectSegmentsByExpr'],
|
||||||
onExtractSegmentFramesAsImages: (segIds: string[]) => Promise<void>,
|
onExtractSegmentFramesAsImages: (segIds: string[]) => Promise<void>,
|
||||||
onLabelSelectedSegments: UseSegments['onLabelSelectedSegments'],
|
onLabelSelectedSegments: UseSegments['onLabelSelectedSegments'],
|
||||||
onInvertSelectedSegments: UseSegments['invertSelectedSegments'],
|
onInvertSelectedSegments: UseSegments['invertSelectedSegments'],
|
||||||
@ -487,7 +487,7 @@ const SegmentList = memo(({
|
|||||||
onSelectAllSegments={onSelectAllSegments}
|
onSelectAllSegments={onSelectAllSegments}
|
||||||
onEditSegmentTags={onEditSegmentTags}
|
onEditSegmentTags={onEditSegmentTags}
|
||||||
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
||||||
onSelectSegmentsByTag={onSelectSegmentsByTag}
|
onSelectSegmentsByExpr={onSelectSegmentsByExpr}
|
||||||
onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages}
|
onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages}
|
||||||
onLabelSelectedSegments={onLabelSelectedSegments}
|
onLabelSelectedSegments={onLabelSelectedSegments}
|
||||||
onInvertSelectedSegments={onInvertSelectedSegments}
|
onInvertSelectedSegments={onInvertSelectedSegments}
|
||||||
|
@ -434,7 +434,7 @@ export async function createFixedDurationSegments({ fileDuration, inputPlacehold
|
|||||||
return edl;
|
return edl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createRandomSegments(fileDuration) {
|
export async function createRandomSegments(fileDuration: number) {
|
||||||
const response = await askForSegmentsRandomDurationRange();
|
const response = await askForSegmentsRandomDurationRange();
|
||||||
if (response == null) return undefined;
|
if (response == null) return undefined;
|
||||||
|
|
||||||
@ -451,14 +451,14 @@ export async function createRandomSegments(fileDuration) {
|
|||||||
return edl;
|
return edl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MovSuggestion = ({ fileFormat }) => (fileFormat === 'mp4' ? <li><Trans>Change output <b>Format</b> from <b>MP4</b> to <b>MOV</b></Trans></li> : null);
|
const MovSuggestion = ({ fileFormat }: { fileFormat: string | undefined }) => (fileFormat === 'mp4' ? <li><Trans>Change output <b>Format</b> from <b>MP4</b> to <b>MOV</b></Trans></li> : null);
|
||||||
const OutputFormatSuggestion = () => <li><Trans>Select a different output <b>Format</b> (<b>matroska</b> and <b>mp4</b> support most codecs)</Trans></li>;
|
const OutputFormatSuggestion = () => <li><Trans>Select a different output <b>Format</b> (<b>matroska</b> and <b>mp4</b> support most codecs)</Trans></li>;
|
||||||
const WorkingDirectorySuggestion = () => <li><Trans>Set a different <b>Working directory</b></Trans></li>;
|
const WorkingDirectorySuggestion = () => <li><Trans>Set a different <b>Working directory</b></Trans></li>;
|
||||||
const DifferentFileSuggestion = () => <li><Trans>Try with a <b>Different file</b></Trans></li>;
|
const DifferentFileSuggestion = () => <li><Trans>Try with a <b>Different file</b></Trans></li>;
|
||||||
const HelpSuggestion = () => <li><Trans>See <b>Help</b></Trans> menu</li>;
|
const HelpSuggestion = () => <li><Trans>See <b>Help</b></Trans> menu</li>;
|
||||||
const ErrorReportSuggestion = () => <li><Trans>If nothing helps, you can send an <b>Error report</b></Trans></li>;
|
const ErrorReportSuggestion = () => <li><Trans>If nothing helps, you can send an <b>Error report</b></Trans></li>;
|
||||||
|
|
||||||
export async function showExportFailedDialog({ fileFormat, safeOutputFileName }) {
|
export async function showExportFailedDialog({ fileFormat, safeOutputFileName }: { fileFormat: string | undefined, safeOutputFileName: boolean }) {
|
||||||
const html = (
|
const html = (
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
<Trans>Try one of the following before exporting again:</Trans>
|
<Trans>Try one of the following before exporting again:</Trans>
|
||||||
@ -480,7 +480,7 @@ export async function showExportFailedDialog({ fileFormat, safeOutputFileName })
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showConcatFailedDialog({ fileFormat }) {
|
export async function showConcatFailedDialog({ fileFormat }: { fileFormat: string | undefined }) {
|
||||||
const html = (
|
const html = (
|
||||||
<div style={{ textAlign: 'left' }}>
|
<div style={{ textAlign: 'left' }}>
|
||||||
<Trans>Try each of the following before merging again:</Trans>
|
<Trans>Try each of the following before merging again:</Trans>
|
||||||
@ -517,7 +517,7 @@ export function openYouTubeChaptersDialog(text: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function labelSegmentDialog({ currentName, maxLength }) {
|
export async function labelSegmentDialog({ currentName, maxLength }: { currentName: string, maxLength: number }) {
|
||||||
const { value } = await Swal.fire({
|
const { value } = await Swal.fire({
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
title: i18n.t('Label current segment'),
|
title: i18n.t('Label current segment'),
|
||||||
@ -528,7 +528,7 @@ export async function labelSegmentDialog({ currentName, maxLength }) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectSegmentsByLabelDialog(currentName) {
|
export async function selectSegmentsByLabelDialog(currentName: string) {
|
||||||
const { value } = await Swal.fire({
|
const { value } = await Swal.fire({
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
title: i18n.t('Select segments by label'),
|
title: i18n.t('Select segments by label'),
|
||||||
@ -538,24 +538,47 @@ export async function selectSegmentsByLabelDialog(currentName) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function selectSegmentsByTagDialog() {
|
export async function selectSegmentsByExprDialog(inputValidator: (v: string) => string | undefined) {
|
||||||
const { value: value1 } = await Swal.fire({
|
const examples = {
|
||||||
showCancelButton: true,
|
duration: { name: i18n.t('Segment duration less than 5 seconds'), code: 'segment.duration < 5' },
|
||||||
title: i18n.t('Select segments by tag'),
|
start: { name: i18n.t('Segment starts after 00:60'), code: 'segment.start > 60' },
|
||||||
text: i18n.t('Enter tag name (in the next dialog you\'ll enter tag value)'),
|
label: { name: i18n.t('Segment label'), code: "equalText(segment.label, 'My label')" },
|
||||||
input: 'text',
|
tag: { name: i18n.t('Segment tag value'), code: "equalText(segment.tags.myTag, 'tag value')" },
|
||||||
});
|
};
|
||||||
if (!value1) return undefined;
|
|
||||||
|
|
||||||
const { value: value2 } = await Swal.fire({
|
function addExample(type: string) {
|
||||||
showCancelButton: true,
|
Swal.getInput()!.value = examples[type]?.code ?? '';
|
||||||
title: i18n.t('Select segments by tag'),
|
}
|
||||||
text: i18n.t('Enter tag value'),
|
|
||||||
input: 'text',
|
|
||||||
});
|
|
||||||
if (!value2) return undefined;
|
|
||||||
|
|
||||||
return { tagName: value1, tagValue: value2 };
|
const { value } = await ReactSwal.fire<string>({
|
||||||
|
showCancelButton: true,
|
||||||
|
title: i18n.t('Select segments by expression'),
|
||||||
|
input: 'text',
|
||||||
|
html: (
|
||||||
|
<div style={{ textAlign: 'left' }}>
|
||||||
|
<div style={{ marginBottom: '1em' }}>
|
||||||
|
{i18n.t('Enter an expression which will be evaluated for each segment. Segments for which the expression evaluates to "true" will be selected. For available syntax, see {{url}}.', { url: 'https://mathjs.org/' })}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><b>{i18n.t('Variables')}:</b></div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '1em' }}>
|
||||||
|
segment.label, segment.start, segment.end, segment.duration
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><b>{i18n.t('Examples')}:</b></div>
|
||||||
|
|
||||||
|
{Object.entries(examples).map(([key, { name }]) => (
|
||||||
|
<button key={key} type="button" onClick={() => addExample(key)} className="button-unstyled" style={{ display: 'block', marginBottom: '.1em' }}>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
inputPlaceholder: 'segment.duration < 5',
|
||||||
|
inputValidator,
|
||||||
|
});
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showJson5Dialog({ title, json }: { title: string, json: unknown }) {
|
export function showJson5Dialog({ title, json }: { title: string, json: unknown }) {
|
||||||
@ -631,7 +654,7 @@ export async function askForPlaybackRate({ detectedFps, outputPlaybackRate }) {
|
|||||||
const fps = detectedFps || 1;
|
const fps = detectedFps || 1;
|
||||||
const currentFps = fps * outputPlaybackRate;
|
const currentFps = fps * outputPlaybackRate;
|
||||||
|
|
||||||
function parseValue(v) {
|
function parseValue(v: string) {
|
||||||
const newFps = parseFloat(v);
|
const newFps = parseFloat(v);
|
||||||
if (!Number.isNaN(newFps)) {
|
if (!Number.isNaN(newFps)) {
|
||||||
return newFps / fps;
|
return newFps / fps;
|
||||||
|
@ -3,15 +3,15 @@ import { useStateWithHistory } from 'react-use/lib/useStateWithHistory';
|
|||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import pMap from 'p-map';
|
import pMap from 'p-map';
|
||||||
import invariant from 'tiny-invariant';
|
import invariant from 'tiny-invariant';
|
||||||
|
import { evaluate } from 'mathjs';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
|
|
||||||
import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg';
|
import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg';
|
||||||
import { handleError, shuffleArray } from '../util';
|
import { handleError, shuffleArray } from '../util';
|
||||||
import { errorToast } from '../swal';
|
import { errorToast } from '../swal';
|
||||||
import { showParametersDialog } from '../dialogs/parameters';
|
import { showParametersDialog } from '../dialogs/parameters';
|
||||||
import { createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog, selectSegmentsByTagDialog } from '../dialogs';
|
import { createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog, selectSegmentsByExprDialog } from '../dialogs';
|
||||||
import { createSegment, findSegmentsAtCursor, sortSegments, invertSegments, getSegmentTags, combineOverlappingSegments as combineOverlappingSegments2, combineSelectedSegments as combineSelectedSegments2, isDurationValid, getSegApparentStart, getSegApparentEnd as getSegApparentEnd2, addSegmentColorIndex } from '../segments';
|
import { createSegment, findSegmentsAtCursor, sortSegments, invertSegments, combineOverlappingSegments as combineOverlappingSegments2, combineSelectedSegments as combineSelectedSegments2, isDurationValid, getSegApparentStart, getSegApparentEnd as getSegApparentEnd2, addSegmentColorIndex } from '../segments';
|
||||||
import * as ffmpegParameters from '../ffmpeg-parameters';
|
import * as ffmpegParameters from '../ffmpeg-parameters';
|
||||||
import { maxSegmentsAllowed } from '../util/constants';
|
import { maxSegmentsAllowed } from '../util/constants';
|
||||||
import { ParseTimecode, SegmentBase, SegmentToExport, StateSegment, UpdateSegAtIndex } from '../types';
|
import { ParseTimecode, SegmentBase, SegmentToExport, StateSegment, UpdateSegAtIndex } from '../types';
|
||||||
@ -454,7 +454,7 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt
|
|||||||
if (segments) loadCutSegments(segments);
|
if (segments) loadCutSegments(segments);
|
||||||
}, [checkFileOpened, duration, loadCutSegments]);
|
}, [checkFileOpened, duration, loadCutSegments]);
|
||||||
|
|
||||||
const enableSegments = useCallback((segmentsToEnable) => {
|
const enableSegments = useCallback((segmentsToEnable: { segId: string }[]) => {
|
||||||
if (segmentsToEnable.length === 0 || segmentsToEnable.length === cutSegments.length) return; // no point
|
if (segmentsToEnable.length === 0 || segmentsToEnable.length === cutSegments.length) return; // no point
|
||||||
setDeselectedSegmentIds((existing) => {
|
setDeselectedSegmentIds((existing) => {
|
||||||
const ret = { ...existing };
|
const ret = { ...existing };
|
||||||
@ -471,13 +471,43 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt
|
|||||||
enableSegments(segmentsToEnable);
|
enableSegments(segmentsToEnable);
|
||||||
}, [currentCutSeg, cutSegments, enableSegments]);
|
}, [currentCutSeg, cutSegments, enableSegments]);
|
||||||
|
|
||||||
const onSelectSegmentsByTag = useCallback(async () => {
|
const onSelectSegmentsByExpr = useCallback(async () => {
|
||||||
const value = await selectSegmentsByTagDialog();
|
function matchSegment(seg: StateSegment, expr: string) {
|
||||||
|
const start = getSegApparentStart(seg);
|
||||||
|
const end = getSegApparentEnd(seg);
|
||||||
|
// must clone tags because scope is mutable (editable by expression)
|
||||||
|
const scopeSegment: { label: string, start: number, end: number, duration: number, tags: Record<string, string> } = { label: seg.name, start, end, duration: end - start, tags: { ...seg.tags } };
|
||||||
|
return evaluate(expr, { segment: scopeSegment }) === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSegmentsToEnable = (expr: string) => cutSegments.filter((seg) => {
|
||||||
|
try {
|
||||||
|
return matchSegment(seg, expr);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof TypeError) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = await selectSegmentsByExprDialog((v: string) => {
|
||||||
|
try {
|
||||||
|
const segments = getSegmentsToEnable(v);
|
||||||
|
if (segments.length === 0) return i18n.t('No segments matched');
|
||||||
|
return undefined;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
const { tagName, tagValue } = value;
|
const segmentsToEnable = getSegmentsToEnable(value);
|
||||||
const segmentsToEnable = cutSegments.filter((seg) => getSegmentTags(seg)[tagName] === tagValue);
|
|
||||||
enableSegments(segmentsToEnable);
|
enableSegments(segmentsToEnable);
|
||||||
}, [cutSegments, enableSegments]);
|
}, [cutSegments, enableSegments, getSegApparentEnd]);
|
||||||
|
|
||||||
const onLabelSelectedSegments = useCallback(async () => {
|
const onLabelSelectedSegments = useCallback(async () => {
|
||||||
if (selectedSegmentsRaw.length === 0) return;
|
if (selectedSegmentsRaw.length === 0) return;
|
||||||
@ -570,7 +600,7 @@ function useSegments({ filePath, workingRef, setWorking, setCutProgress, videoSt
|
|||||||
invertSelectedSegments,
|
invertSelectedSegments,
|
||||||
removeSelectedSegments,
|
removeSelectedSegments,
|
||||||
onSelectSegmentsByLabel,
|
onSelectSegmentsByLabel,
|
||||||
onSelectSegmentsByTag,
|
onSelectSegmentsByExpr,
|
||||||
toggleSegmentSelected,
|
toggleSegmentSelected,
|
||||||
selectOnlySegment,
|
selectOnlySegment,
|
||||||
setCutTime,
|
setCutTime,
|
||||||
|
85
yarn.lock
85
yarn.lock
@ -545,6 +545,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/runtime@npm:^7.24.4":
|
||||||
|
version: 7.24.5
|
||||||
|
resolution: "@babel/runtime@npm:7.24.5"
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: "npm:^0.14.0"
|
||||||
|
checksum: e0f4f4d4503f7338749d1dd92361ad132d683bde64e6b61d6c855e100dcd01592295fcfdcc960c946b85ef7908dc2f501080da58447c05812cf3cd80c599bb62
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/template@npm:^7.20.7":
|
"@babel/template@npm:^7.20.7":
|
||||||
version: 7.20.7
|
version: 7.20.7
|
||||||
resolution: "@babel/template@npm:7.20.7"
|
resolution: "@babel/template@npm:7.20.7"
|
||||||
@ -3898,6 +3907,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"complex.js@npm:^2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "complex.js@npm:2.1.1"
|
||||||
|
checksum: 1905d5204dd8a4d6f591182aca2045986f1ff3c5373e455ccd10c6ee2905bf1d3811a313d38c68f8a8507523202f91e25177387e3adc386c1b5b5ec2f13a6dbb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"compute-scroll-into-view@npm:^1.0.14, compute-scroll-into-view@npm:^1.0.17":
|
"compute-scroll-into-view@npm:^1.0.14, compute-scroll-into-view@npm:^1.0.17":
|
||||||
version: 1.0.17
|
version: 1.0.17
|
||||||
resolution: "compute-scroll-into-view@npm:1.0.17"
|
resolution: "compute-scroll-into-view@npm:1.0.17"
|
||||||
@ -4238,6 +4254,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"decimal.js@npm:^10.4.3":
|
||||||
|
version: 10.4.3
|
||||||
|
resolution: "decimal.js@npm:10.4.3"
|
||||||
|
checksum: de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"decompress-response@npm:^6.0.0":
|
"decompress-response@npm:^6.0.0":
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
resolution: "decompress-response@npm:6.0.0"
|
resolution: "decompress-response@npm:6.0.0"
|
||||||
@ -5195,6 +5218,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"escape-latex@npm:^1.2.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "escape-latex@npm:1.2.0"
|
||||||
|
checksum: 73a787319f0965ecb8244bb38bf3a3cba872f0b9a5d3da8821140e9f39fe977045dc953a62b1a2bed4d12bfccbe75a7d8ec786412bf00739eaa2f627d0a8e0d6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"escape-string-regexp@npm:5.0.0":
|
"escape-string-regexp@npm:5.0.0":
|
||||||
version: 5.0.0
|
version: 5.0.0
|
||||||
resolution: "escape-string-regexp@npm:5.0.0"
|
resolution: "escape-string-regexp@npm:5.0.0"
|
||||||
@ -5945,6 +5975,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fraction.js@npm:4.3.4":
|
||||||
|
version: 4.3.4
|
||||||
|
resolution: "fraction.js@npm:4.3.4"
|
||||||
|
checksum: 3a1e6b268038ffdea625fab6a8d155d7ab644d35d0c99bc59084bfd29fbc714f3a38381b0627751ddb5f188bcde0b3f48c27e80eeb2ecd440825a7d2cd2bf9f1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"framer-motion@npm:^9.0.3":
|
"framer-motion@npm:^9.0.3":
|
||||||
version: 9.0.3
|
version: 9.0.3
|
||||||
resolution: "framer-motion@npm:9.0.3"
|
resolution: "framer-motion@npm:9.0.3"
|
||||||
@ -7498,6 +7535,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"javascript-natural-sort@npm:^0.7.1":
|
||||||
|
version: 0.7.1
|
||||||
|
resolution: "javascript-natural-sort@npm:0.7.1"
|
||||||
|
checksum: 7bf6eab67871865d347f09a95aa770f9206c1ab0226bcda6fdd9edec340bf41111a7f82abac30556aa16a21cfa3b2b1ca4a362c8b73dd5ce15220e5d31f49d79
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"js-cookie@npm:^2.2.1":
|
"js-cookie@npm:^2.2.1":
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
resolution: "js-cookie@npm:2.2.1"
|
resolution: "js-cookie@npm:2.2.1"
|
||||||
@ -7949,6 +7993,7 @@ __metadata:
|
|||||||
ky: "npm:^0.33.1"
|
ky: "npm:^0.33.1"
|
||||||
lodash: "npm:^4.17.19"
|
lodash: "npm:^4.17.19"
|
||||||
luxon: "npm:^3.3.0"
|
luxon: "npm:^3.3.0"
|
||||||
|
mathjs: "npm:^12.4.2"
|
||||||
mime-types: "npm:^2.1.14"
|
mime-types: "npm:^2.1.14"
|
||||||
mkdirp: "npm:^1.0.3"
|
mkdirp: "npm:^1.0.3"
|
||||||
morgan: "npm:^1.10.0"
|
morgan: "npm:^1.10.0"
|
||||||
@ -8140,6 +8185,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"mathjs@npm:^12.4.2":
|
||||||
|
version: 12.4.2
|
||||||
|
resolution: "mathjs@npm:12.4.2"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime": "npm:^7.24.4"
|
||||||
|
complex.js: "npm:^2.1.1"
|
||||||
|
decimal.js: "npm:^10.4.3"
|
||||||
|
escape-latex: "npm:^1.2.0"
|
||||||
|
fraction.js: "npm:4.3.4"
|
||||||
|
javascript-natural-sort: "npm:^0.7.1"
|
||||||
|
seedrandom: "npm:^3.0.5"
|
||||||
|
tiny-emitter: "npm:^2.1.0"
|
||||||
|
typed-function: "npm:^4.1.1"
|
||||||
|
bin:
|
||||||
|
mathjs: bin/cli.js
|
||||||
|
checksum: 4b88ac1b137d00b8f3d66f4d1662d3670399390b59623ecf3ab7d587ba18be7b97ce9c5b07e953029ac75f48567d675c99323889ae231eb071ddd84db5dd699c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"mdn-data@npm:2.0.14":
|
"mdn-data@npm:2.0.14":
|
||||||
version: 2.0.14
|
version: 2.0.14
|
||||||
resolution: "mdn-data@npm:2.0.14"
|
resolution: "mdn-data@npm:2.0.14"
|
||||||
@ -10298,6 +10362,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"seedrandom@npm:^3.0.5":
|
||||||
|
version: 3.0.5
|
||||||
|
resolution: "seedrandom@npm:3.0.5"
|
||||||
|
checksum: acad5e516c04289f61c2fb9848f449b95f58362b75406b79ec51e101ec885293fc57e3675d2f39f49716336559d7190f7273415d185fead8cd27b171ebf7d8fb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"semver-compare@npm:^1.0.0":
|
"semver-compare@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "semver-compare@npm:1.0.0"
|
resolution: "semver-compare@npm:1.0.0"
|
||||||
@ -11209,6 +11280,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tiny-emitter@npm:^2.1.0":
|
||||||
|
version: 2.1.0
|
||||||
|
resolution: "tiny-emitter@npm:2.1.0"
|
||||||
|
checksum: 75633f4de4f47f43af56aff6162f25b87be7efc6f669fda256658f3c3f4a216f23dc0d13200c6fafaaf1b0c7142f0201352fb06aec0b77f68aea96be898f4516
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tiny-invariant@npm:1.2.0":
|
"tiny-invariant@npm:1.2.0":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "tiny-invariant@npm:1.2.0"
|
resolution: "tiny-invariant@npm:1.2.0"
|
||||||
@ -11536,6 +11614,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"typed-function@npm:^4.1.1":
|
||||||
|
version: 4.1.1
|
||||||
|
resolution: "typed-function@npm:4.1.1"
|
||||||
|
checksum: 0ef538d5f02e5c40659cccc14b5f2727f0e4181f11d91bb7897327c33cc2893de7e92343b6b32e1bb15e44a215a1e92e27ab2aa1353b100a9a2697abf2989a0c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"typedarray-to-buffer@npm:^3.1.5":
|
"typedarray-to-buffer@npm:^3.1.5":
|
||||||
version: 3.1.5
|
version: 3.1.5
|
||||||
resolution: "typedarray-to-buffer@npm:3.1.5"
|
resolution: "typedarray-to-buffer@npm:3.1.5"
|
||||||
|
Loading…
Reference in New Issue
Block a user