select segments by tag

pull/1717/head
Mikael Finstad 2023-09-06 12:52:46 +02:00
rodzic ecc852a2bc
commit 1acf72fcfd
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 25AB36E3E81CBC26
5 zmienionych plików z 49 dodań i 11 usunięć

Wyświetl plik

@ -83,6 +83,7 @@ The main feature is lossless trimming and cutting of video and audio files, whic
- Losslessly split a video into one file per scene (note you probably have to shift segments, see [#330](https://github.com/mifi/lossless-cut/issues/330).)
- Cut away silent parts of an audio/video
- Split video into segments to for example respect Twitter's 140 second limit
- Annotate each segment with one or more tags, then use those tags to organize your segments or use it to create an output folder structure or hierarchy for your segments.
### Export cut times as YouTube Chapters
1. Export with Merge and "Create chapters from merged segments" enabled

Wyświetl plik

@ -355,7 +355,7 @@ const App = memo(() => {
}, [isFileOpened]);
const {
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, onViewSegmentTags, 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, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment,
cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, onViewSegmentTags, 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,
} = useSegments({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly });
const jumpSegStart = useCallback((index) => userSeekAbs(apparentCutSegments[index].start), [apparentCutSegments, userSeekAbs]);
@ -2386,6 +2386,7 @@ const App = memo(() => {
jumpSegEnd={jumpSegEnd}
onViewSegmentTags={onViewSegmentTags}
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
onSelectSegmentsByTag={onSelectSegmentsByTag}
onLabelSelectedSegments={onLabelSelectedSegments}
/>
)}

Wyświetl plik

@ -23,7 +23,7 @@ const buttonBaseStyle = {
const neutralButtonColor = 'var(--gray8)';
const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments, onDuplicateSegmentClick }) => {
const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments, onDuplicateSegmentClick }) => {
const { t } = useTranslation();
const { getSegColor } = useSegColors();
@ -48,6 +48,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
{ label: t('Select all segments'), click: () => onSelectAllSegments() },
{ label: t('Deselect all segments'), click: () => onDeselectAllSegments() },
{ label: t('Select segments by label'), click: () => onSelectSegmentsByLabel(seg) },
{ label: t('Select segments by tag'), click: () => onSelectSegmentsByTag(seg) },
{ label: t('Invert selected segments'), click: () => onInvertSelectedSegments() },
{ type: 'separator' },
@ -66,7 +67,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
{ label: t('Segment tags'), click: () => onViewSegmentTags(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]);
}, [invertCutSegments, t, jumpSegStart, jumpSegEnd, addSegment, onLabelPress, onRemovePress, onLabelSelectedSegments, onRemoveSelected, onReorderPress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onInvertSelectedSegments, updateOrder, onViewSegmentTags, index, onExtractSegmentFramesAsImages]);
useContextMenu(ref, contextMenuTemplate);
@ -157,7 +158,7 @@ const SegmentList = memo(({
currentSegIndex,
updateSegOrder, updateSegOrders, addSegment, removeCutSegment, onRemoveSelected,
onLabelSegment, currentCutSeg, segmentAtCursor, toggleSegmentsList, splitCurrentSegment,
selectedSegments, isSegmentSelected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectAllSegments, onSelectSegmentsByLabel, onExtractSegmentFramesAsImages, onLabelSelectedSegments, onInvertSelectedSegments, onDuplicateSegmentClick,
selectedSegments, isSegmentSelected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onExtractSegmentFramesAsImages, onLabelSelectedSegments, onInvertSelectedSegments, onDuplicateSegmentClick,
jumpSegStart, jumpSegEnd, onViewSegmentTags,
}) => {
const { t } = useTranslation();
@ -318,6 +319,7 @@ const SegmentList = memo(({
onSelectAllSegments={onSelectAllSegments}
onViewSegmentTags={onViewSegmentTags}
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
onSelectSegmentsByTag={onSelectSegmentsByTag}
onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages}
onLabelSelectedSegments={onLabelSelectedSegments}
onInvertSelectedSegments={onInvertSelectedSegments}

Wyświetl plik

@ -505,6 +505,26 @@ export async function selectSegmentsByLabelDialog(currentName) {
return value;
}
export async function selectSegmentsByTagDialog() {
const { value: value1 } = await Swal.fire({
showCancelButton: true,
title: i18n.t('Select segments by tag'),
text: i18n.t('Enter tag name (in the next dialog you\'ll enter tag value)'),
input: 'text',
});
if (!value1) return undefined;
const { value: value2 } = await Swal.fire({
showCancelButton: true,
title: i18n.t('Select segments by tag'),
text: i18n.t('Enter tag value'),
input: 'text',
});
if (!value2) return undefined;
return { tagName: value1, tagValue: value2 };
}
export async function showEditableJsonDialog({ text, title, inputLabel, inputValue, inputValidator }) {
const { value } = await Swal.fire({
input: 'textarea',

Wyświetl plik

@ -10,7 +10,7 @@ import { blackDetect, silenceDetect, detectSceneChanges as ffmpegDetectSceneChan
import { handleError, shuffleArray } from '../util';
import { errorToast } from '../swal';
import { showParametersDialog } from '../dialogs/parameters';
import { createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, showEditableJsonDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog } from '../dialogs';
import { createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, showEditableJsonDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog, selectSegmentsByTagDialog } from '../dialogs';
import { createSegment, findSegmentsAtCursor, sortSegments, invertSegments, getSegmentTags, combineOverlappingSegments as combineOverlappingSegments2, combineSelectedSegments as combineSelectedSegments2, isDurationValid, getSegApparentStart, getSegApparentEnd as getSegApparentEnd2 } from '../segments';
import * as ffmpegParameters from '../ffmpeg-parameters';
import { maxSegmentsAllowed } from '../util/constants';
@ -436,18 +436,31 @@ export default ({
if (segments) loadCutSegments(segments);
}, [checkFileOpened, duration, loadCutSegments]);
const onSelectSegmentsByLabel = useCallback(async () => {
const { name } = currentCutSeg;
const value = await selectSegmentsByLabelDialog(name);
if (value == null) return;
const segmentsToEnable = cutSegments.filter((seg) => (seg.name || '') === value);
const enableSegments = useCallback((segmentsToEnable) => {
if (segmentsToEnable.length === 0 || segmentsToEnable.length === cutSegments.length) return; // no point
setDeselectedSegmentIds((existing) => {
const ret = { ...existing };
segmentsToEnable.forEach(({ segId }) => { ret[segId] = false; });
return ret;
});
}, [currentCutSeg, cutSegments]);
}, [cutSegments.length]);
const onSelectSegmentsByLabel = useCallback(async () => {
const { name } = currentCutSeg;
const value = await selectSegmentsByLabelDialog(name);
if (value == null) return;
const segmentsToEnable = cutSegments.filter((seg) => (seg.name || '') === value);
enableSegments(segmentsToEnable);
}, [currentCutSeg, cutSegments, enableSegments]);
const onSelectSegmentsByTag = useCallback(async () => {
const value = await selectSegmentsByTagDialog();
if (value == null) return;
const { tagName, tagValue } = value;
const segmentsToEnable = cutSegments.filter((seg) => getSegmentTags(seg)[tagName] === tagValue);
enableSegments(segmentsToEnable);
}, [cutSegments, enableSegments]);
const onLabelSelectedSegments = useCallback(async () => {
if (selectedSegmentsRaw.length < 1) return;
@ -540,6 +553,7 @@ export default ({
invertSelectedSegments,
removeSelectedSegments,
onSelectSegmentsByLabel,
onSelectSegmentsByTag,
toggleSegmentSelected,
selectOnlySegment,
setCutTime,