mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 11:43:17 +01:00
select segments by tag
This commit is contained in:
parent
ecc852a2bc
commit
1acf72fcfd
@ -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).)
|
- 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
|
- Cut away silent parts of an audio/video
|
||||||
- Split video into segments to for example respect Twitter's 140 second limit
|
- 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
|
### Export cut times as YouTube Chapters
|
||||||
1. Export with Merge and "Create chapters from merged segments" enabled
|
1. Export with Merge and "Create chapters from merged segments" enabled
|
||||||
|
@ -355,7 +355,7 @@ const App = memo(() => {
|
|||||||
}, [isFileOpened]);
|
}, [isFileOpened]);
|
||||||
|
|
||||||
const {
|
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 });
|
} = useSegments({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly });
|
||||||
|
|
||||||
const jumpSegStart = useCallback((index) => userSeekAbs(apparentCutSegments[index].start), [apparentCutSegments, userSeekAbs]);
|
const jumpSegStart = useCallback((index) => userSeekAbs(apparentCutSegments[index].start), [apparentCutSegments, userSeekAbs]);
|
||||||
@ -2386,6 +2386,7 @@ const App = memo(() => {
|
|||||||
jumpSegEnd={jumpSegEnd}
|
jumpSegEnd={jumpSegEnd}
|
||||||
onViewSegmentTags={onViewSegmentTags}
|
onViewSegmentTags={onViewSegmentTags}
|
||||||
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
||||||
|
onSelectSegmentsByTag={onSelectSegmentsByTag}
|
||||||
onLabelSelectedSegments={onLabelSelectedSegments}
|
onLabelSelectedSegments={onLabelSelectedSegments}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -23,7 +23,7 @@ const buttonBaseStyle = {
|
|||||||
const neutralButtonColor = 'var(--gray8)';
|
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 { t } = useTranslation();
|
||||||
const { getSegColor } = useSegColors();
|
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('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(seg) },
|
{ 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() },
|
{ label: t('Invert selected segments'), click: () => onInvertSelectedSegments() },
|
||||||
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
@ -66,7 +67,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
|
|||||||
{ label: t('Segment tags'), click: () => onViewSegmentTags(index) },
|
{ label: t('Segment tags'), click: () => onViewSegmentTags(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, 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);
|
useContextMenu(ref, contextMenuTemplate);
|
||||||
|
|
||||||
@ -157,7 +158,7 @@ const SegmentList = memo(({
|
|||||||
currentSegIndex,
|
currentSegIndex,
|
||||||
updateSegOrder, updateSegOrders, addSegment, removeCutSegment, onRemoveSelected,
|
updateSegOrder, updateSegOrders, addSegment, removeCutSegment, onRemoveSelected,
|
||||||
onLabelSegment, currentCutSeg, segmentAtCursor, toggleSegmentsList, splitCurrentSegment,
|
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,
|
jumpSegStart, jumpSegEnd, onViewSegmentTags,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -318,6 +319,7 @@ const SegmentList = memo(({
|
|||||||
onSelectAllSegments={onSelectAllSegments}
|
onSelectAllSegments={onSelectAllSegments}
|
||||||
onViewSegmentTags={onViewSegmentTags}
|
onViewSegmentTags={onViewSegmentTags}
|
||||||
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
||||||
|
onSelectSegmentsByTag={onSelectSegmentsByTag}
|
||||||
onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages}
|
onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages}
|
||||||
onLabelSelectedSegments={onLabelSelectedSegments}
|
onLabelSelectedSegments={onLabelSelectedSegments}
|
||||||
onInvertSelectedSegments={onInvertSelectedSegments}
|
onInvertSelectedSegments={onInvertSelectedSegments}
|
||||||
|
@ -505,6 +505,26 @@ export async function selectSegmentsByLabelDialog(currentName) {
|
|||||||
return value;
|
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 }) {
|
export async function showEditableJsonDialog({ text, title, inputLabel, inputValue, inputValidator }) {
|
||||||
const { value } = await Swal.fire({
|
const { value } = await Swal.fire({
|
||||||
input: 'textarea',
|
input: 'textarea',
|
||||||
|
@ -10,7 +10,7 @@ import { blackDetect, silenceDetect, detectSceneChanges as ffmpegDetectSceneChan
|
|||||||
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, 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 { 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 * as ffmpegParameters from '../ffmpeg-parameters';
|
||||||
import { maxSegmentsAllowed } from '../util/constants';
|
import { maxSegmentsAllowed } from '../util/constants';
|
||||||
@ -436,18 +436,31 @@ export default ({
|
|||||||
if (segments) loadCutSegments(segments);
|
if (segments) loadCutSegments(segments);
|
||||||
}, [checkFileOpened, duration, loadCutSegments]);
|
}, [checkFileOpened, duration, loadCutSegments]);
|
||||||
|
|
||||||
const onSelectSegmentsByLabel = useCallback(async () => {
|
const enableSegments = useCallback((segmentsToEnable) => {
|
||||||
const { name } = currentCutSeg;
|
|
||||||
const value = await selectSegmentsByLabelDialog(name);
|
|
||||||
if (value == null) return;
|
|
||||||
const segmentsToEnable = cutSegments.filter((seg) => (seg.name || '') === value);
|
|
||||||
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 };
|
||||||
segmentsToEnable.forEach(({ segId }) => { ret[segId] = false; });
|
segmentsToEnable.forEach(({ segId }) => { ret[segId] = false; });
|
||||||
return ret;
|
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 () => {
|
const onLabelSelectedSegments = useCallback(async () => {
|
||||||
if (selectedSegmentsRaw.length < 1) return;
|
if (selectedSegmentsRaw.length < 1) return;
|
||||||
@ -540,6 +553,7 @@ export default ({
|
|||||||
invertSelectedSegments,
|
invertSelectedSegments,
|
||||||
removeSelectedSegments,
|
removeSelectedSegments,
|
||||||
onSelectSegmentsByLabel,
|
onSelectSegmentsByLabel,
|
||||||
|
onSelectSegmentsByTag,
|
||||||
toggleSegmentSelected,
|
toggleSegmentSelected,
|
||||||
selectOnlySegment,
|
selectOnlySegment,
|
||||||
setCutTime,
|
setCutTime,
|
||||||
|
Loading…
Reference in New Issue
Block a user