diff --git a/src/App.jsx b/src/App.jsx
index faf9eb66..f301a473 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -79,7 +79,7 @@ import { askForHtml5ifySpeed } from './dialogs/html5ify';
import { askForOutDir, askForImportChapters, promptTimeOffset, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showExportFailedDialog, showConcatFailedDialog, openYouTubeChaptersDialog, showRefuseToOverwrite, openDirToast, openExportFinishedToast, openConcatFinishedToast, showOpenDialog } from './dialogs';
import { openSendReportDialog } from './reporting';
import { fallbackLng } from './i18n';
-import { createSegment, getCleanCutSegments, findSegmentsAtCursor, sortSegments, convertSegmentsToChapters, hasAnySegmentOverlap, isDurationValid, playOnlyCurrentSegment } from './segments';
+import { createSegment, getCleanCutSegments, findSegmentsAtCursor, sortSegments, convertSegmentsToChapters, hasAnySegmentOverlap, isDurationValid, playOnlyCurrentSegment, getSegmentTags } from './segments';
import { generateOutSegFileNames as generateOutSegFileNamesRaw, defaultOutSegTemplate } from './util/outputNameTemplate';
import { rightBarWidth, leftBarWidth, ffmpegExtractWindow, zoomMax } from './util/constants';
import BigWaveform from './components/BigWaveform';
@@ -156,6 +156,8 @@ const App = memo(() => {
const [keyboardShortcutsVisible, setKeyboardShortcutsVisible] = useState(false);
const [mifiLink, setMifiLink] = useState();
const [alwaysConcatMultipleFiles, setAlwaysConcatMultipleFiles] = useState(false);
+ const [editingSegmentTagsSegmentIndex, setEditingSegmentTagsSegmentIndex] = useState();
+ const [editingSegmentTags, setEditingSegmentTags] = useState();
// Batch state / concat files
const [batchFiles, setBatchFiles] = useState([]);
@@ -1933,6 +1935,15 @@ const App = memo(() => {
}
}, []);
+ const onEditSegmentTags = useCallback((index) => {
+ setEditingSegmentTagsSegmentIndex(index);
+ setEditingSegmentTags(getSegmentTags(apparentCutSegments[index]));
+ }, [apparentCutSegments]);
+
+ const editCurrentSegmentTags = useCallback(() => {
+ onEditSegmentTags(currentSegIndexSafe);
+ }, [currentSegIndexSafe, onEditSegmentTags]);
+
const mainActions = useMemo(() => {
async function exportYouTube() {
if (!checkFileOpened()) return;
@@ -2036,6 +2047,7 @@ const App = memo(() => {
deselectAllSegments,
selectAllSegments,
selectOnlyCurrentSegment,
+ editCurrentSegmentTags,
toggleCurrentSegmentSelected,
invertSelectedSegments,
removeSelectedSegments,
@@ -2064,7 +2076,7 @@ const App = memo(() => {
showIncludeExternalStreamsDialog,
toggleFullscreenVideo,
};
- }, [addSegment, alignSegmentTimesToKeyframes, apparentCutSegments, askStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, checkFileOpened, cleanupFilesDialog, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatBatch, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, createSegmentsFromKeyframes, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, detectBlackScenes, detectSceneChanges, detectSilentScenes, duplicateCurrentSegment, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, handleShowStreamsSelectorClick, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, onExportPress, onLabelSegment, openFilesDialog, openSendReportDialogWithState, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, showIncludeExternalStreamsDialog, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleFullscreenVideo, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleSettings, toggleShowKeyframes, toggleShowThumbnails, toggleStreamsSelector, toggleStripAudio, toggleWaveformMode, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
+ }, [addSegment, alignSegmentTimesToKeyframes, apparentCutSegments, askStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, checkFileOpened, cleanupFilesDialog, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatBatch, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, createSegmentsFromKeyframes, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, detectBlackScenes, detectSceneChanges, detectSilentScenes, duplicateCurrentSegment, editCurrentSegmentTags, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, handleShowStreamsSelectorClick, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, onExportPress, onLabelSegment, openFilesDialog, openSendReportDialogWithState, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, showIncludeExternalStreamsDialog, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleFullscreenVideo, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleSettings, toggleShowKeyframes, toggleShowThumbnails, toggleStreamsSelector, toggleStripAudio, toggleWaveformMode, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
const getKeyboardAction = useCallback((action) => mainActions[action], [mainActions]);
@@ -2455,6 +2467,11 @@ const App = memo(() => {
onSelectSegmentsByTag={onSelectSegmentsByTag}
onLabelSelectedSegments={onLabelSelectedSegments}
updateSegAtIndex={updateSegAtIndex}
+ editingSegmentTags={editingSegmentTags}
+ editingSegmentTagsSegmentIndex={editingSegmentTagsSegmentIndex}
+ setEditingSegmentTags={setEditingSegmentTags}
+ setEditingSegmentTagsSegmentIndex={setEditingSegmentTagsSegmentIndex}
+ onEditSegmentTags={onEditSegmentTags}
/>
)}
diff --git a/src/SegmentList.jsx b/src/SegmentList.jsx
index aa228fb8..bb11d753 100644
--- a/src/SegmentList.jsx
+++ b/src/SegmentList.jsx
@@ -162,6 +162,7 @@ const SegmentList = memo(({
onLabelSegment, currentCutSeg, segmentAtCursor, toggleSegmentsList, splitCurrentSegment,
selectedSegments, isSegmentSelected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onExtractSegmentFramesAsImages, onLabelSelectedSegments, onInvertSelectedSegments, onDuplicateSegmentClick,
jumpSegStart, jumpSegEnd, updateSegAtIndex,
+ editingSegmentTags, editingSegmentTagsSegmentIndex, setEditingSegmentTags, setEditingSegmentTagsSegmentIndex, onEditSegmentTags,
}) => {
const { t } = useTranslation();
const { getSegColor } = useSegColors();
@@ -271,26 +272,19 @@ const SegmentList = memo(({
);
}
- const [editingSegmentTagsSegmentIndex, setEditingSegmentTagsSegmentIndex] = useState();
- const [editingSegmentTags, setEditingSegmentTags] = useState();
const [editingTag, setEditingTag] = useState();
const onTagChange = useCallback((tag, value) => setEditingSegmentTags((existingTags) => ({
...existingTags,
[tag]: value,
- })), []);
+ })), [setEditingSegmentTags]);
- const onTagReset = useCallback((tag) => setEditingSegmentTags(({ [tag]: deleted, ...rest }) => rest), []);
-
- const onEditSegmentTags = useCallback((index) => {
- setEditingSegmentTagsSegmentIndex(index);
- setEditingSegmentTags(getSegmentTags(apparentCutSegments[index]));
- }, [apparentCutSegments]);
+ const onTagReset = useCallback((tag) => setEditingSegmentTags(({ [tag]: deleted, ...rest }) => rest), [setEditingSegmentTags]);
const onSegmentTagsCloseComplete = useCallback(() => {
setEditingSegmentTagsSegmentIndex();
setEditingSegmentTags();
- }, []);
+ }, [setEditingSegmentTags, setEditingSegmentTagsSegmentIndex]);
const onSegmentTagsConfirm = useCallback(() => {
updateSegAtIndex(editingSegmentTagsSegmentIndex, { tags: editingSegmentTags });
diff --git a/src/components/KeyboardShortcuts.jsx b/src/components/KeyboardShortcuts.jsx
index 8da13d27..07029c1d 100644
--- a/src/components/KeyboardShortcuts.jsx
+++ b/src/components/KeyboardShortcuts.jsx
@@ -7,6 +7,7 @@ import groupBy from 'lodash/groupBy';
import orderBy from 'lodash/orderBy';
import uniq from 'lodash/uniq';
+import Swal from '../swal';
import SetCutpointButton from './SetCutpointButton';
import SegmentCutpointButton from './SegmentCutpointButton';
@@ -295,6 +296,10 @@ const KeyboardShortcuts = memo(({
name: t('Label current segment'),
category: segmentsAndCutpointsCategory,
},
+ editCurrentSegmentTags: {
+ name: t('Edit current segment tags'),
+ category: segmentsAndCutpointsCategory,
+ },
splitCurrentSegment: {
name: t('Split segment at cursor'),
category: segmentsAndCutpointsCategory,
@@ -622,8 +627,9 @@ const KeyboardShortcuts = memo(({
console.log('new key binding', action, keysStr);
setKeyBindings((existingBindings) => {
- const haveDuplicate = existingBindings.some((existingBinding) => existingBinding.keys === keysStr);
- if (haveDuplicate) {
+ const duplicate = existingBindings.find((existingBinding) => existingBinding.keys === keysStr);
+ if (duplicate) {
+ Swal.fire({ icon: 'error', title: t('Duplicate keyboard combination'), text: t('Combination is already bound to "{{alreadyBoundKey}}"', { alreadyBoundKey: actionsMap[duplicate.action]?.name }) });
console.log('trying to add duplicate');
return existingBindings;
}
@@ -632,7 +638,7 @@ const KeyboardShortcuts = memo(({
setCreatingBinding();
return [...existingBindings, { action, keys: keysStr }];
});
- }, [setKeyBindings]);
+ }, [actionsMap, setKeyBindings, t]);
const missingActions = Object.keys(mainActions).filter((key) => actionsMap[key] == null);
if (missingActions.length > 0) throw new Error(`Action(s) missing: ${missingActions.join(',')}`);
diff --git a/src/components/TagEditor.jsx b/src/components/TagEditor.jsx
index de7df1dd..4e7780fc 100644
--- a/src/components/TagEditor.jsx
+++ b/src/components/TagEditor.jsx
@@ -24,7 +24,7 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
setNewTag();
}, [editingTag, onTagReset, setEditingTag]);
- function onEditClick(tag) {
+ const onEditClick = useCallback((tag) => {
if (newTag) {
onTagChange(editingTag, editingTagVal);
setEditingTag();
@@ -40,7 +40,7 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
setEditingTag(tag);
setEditingTagVal(mergedTags[tag]);
}
- }
+ }, [editingTag, editingTagVal, existingTags, mergedTags, newTag, onResetClick, onTagChange, setEditingTag]);
function onSubmit(e) {
e.preventDefault();
@@ -50,12 +50,18 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
const onAddPress = useCallback(async (e) => {
e.preventDefault();
e.target.blur();
+
+ if (newTag || editingTag != null) {
+ // save any unsaved edit
+ onEditClick();
+ }
+
const tag = await askForMetadataKey({ title: addTagTitle, text: addTagText });
if (!tag || Object.keys(mergedTags).includes(tag)) return;
setEditingTag(tag);
setEditingTagVal('');
setNewTag(tag);
- }, [addTagText, addTagTitle, mergedTags, setEditingTag]);
+ }, [addTagText, addTagTitle, editingTag, mergedTags, newTag, onEditClick, setEditingTag]);
useEffect(() => {
ref.current?.focus();
@@ -92,7 +98,7 @@ function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editi
-
+
>
);
}