mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +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).)
|
||||
- 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
|
||||
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
@ -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}
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user