mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 11:43:17 +01:00
make sure that all actions are key bindable
adds the following actions: - Convert to supported format - Create segments from keyframes - Detect black scenes - Detect silent scenes - Detect scene changes - Edit tracks / metadata tags - Set custom start offset/timecode - Settings - Open - Start times as YouTube Chapters - Report an error
This commit is contained in:
parent
3206ba3868
commit
ccb261bf42
@ -33,7 +33,7 @@ module.exports = ({ app, mainWindow, newVersion, isStoreBuild }) => {
|
||||
{
|
||||
label: esc(t('Close batch')),
|
||||
async click() {
|
||||
mainWindow.webContents.send('closeBatchFiles');
|
||||
mainWindow.webContents.send('closeBatch');
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
@ -331,7 +331,7 @@ module.exports = ({ app, mainWindow, newVersion, isStoreBuild }) => {
|
||||
{
|
||||
label: esc(t('Merge/concatenate files')),
|
||||
click() {
|
||||
mainWindow.webContents.send('concatCurrentBatch');
|
||||
mainWindow.webContents.send('concatBatch');
|
||||
},
|
||||
},
|
||||
{
|
||||
|
150
src/App.jsx
150
src/App.jsx
@ -67,7 +67,8 @@ import {
|
||||
getOutPath, getSuffixedOutPath, handleError, getOutDir,
|
||||
isStoreBuild, dragPreventer,
|
||||
havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile,
|
||||
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName, mustDisallowVob, readVideoTs,
|
||||
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName, mustDisallowVob, readVideoTs, getImportProjectType,
|
||||
calcShouldShowWaveform, calcShouldShowKeyframes,
|
||||
} from './util';
|
||||
import { toast, errorToast } from './swal';
|
||||
import { formatDuration } from './util/duration';
|
||||
@ -93,24 +94,12 @@ const remote = window.require('@electron/remote');
|
||||
const { focusWindow, hasDisabledNetworking, quitApp } = remote.require('./electron');
|
||||
|
||||
|
||||
const calcShouldShowWaveform = (zoomedDuration) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
|
||||
const calcShouldShowKeyframes = (zoomedDuration) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
|
||||
|
||||
|
||||
const videoStyle = { width: '100%', height: '100%', objectFit: 'contain' };
|
||||
const bottomStyle = { background: controlsBackground, transition: darkModeTransition };
|
||||
|
||||
let lastOpenedPath;
|
||||
const hevcPlaybackSupportedPromise = doesPlayerSupportHevcPlayback();
|
||||
hevcPlaybackSupportedPromise.catch((err) => console.error(err));
|
||||
|
||||
function getImportProjectType(filePath) {
|
||||
if (filePath.endsWith('Summary.txt')) return 'dv-analyzer-summary-txt';
|
||||
const edlFormatForExtension = { csv: 'csv', pbf: 'pbf', edl: 'mplayer', cue: 'cue', xml: 'xmeml', fcpxml: 'fcpxml' };
|
||||
const matchingExt = Object.keys(edlFormatForExtension).find((ext) => filePath.toLowerCase().endsWith(`.${ext}`));
|
||||
if (!matchingExt) return undefined;
|
||||
return edlFormatForExtension[matchingExt];
|
||||
}
|
||||
|
||||
const App = memo(() => {
|
||||
// Per project state
|
||||
@ -154,6 +143,7 @@ const App = memo(() => {
|
||||
const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState();
|
||||
|
||||
// State per application launch
|
||||
const lastOpenedPathRef = useRef();
|
||||
const [waveformMode, setWaveformMode] = useState();
|
||||
const [thumbnailsEnabled, setThumbnailsEnabled] = useState(false);
|
||||
const [keyframesEnabled, setKeyframesEnabled] = useState(true);
|
||||
@ -1793,7 +1783,7 @@ const App = memo(() => {
|
||||
console.log('userOpenFiles');
|
||||
console.log(filePaths.join('\n'));
|
||||
|
||||
[lastOpenedPath] = filePaths;
|
||||
[lastOpenedPathRef.current] = filePaths;
|
||||
|
||||
// https://en.wikibooks.org/wiki/Inside_DVD-Video/Directory_Structure
|
||||
if (filePaths.length === 1 && /^VIDEO_TS$/i.test(basename(filePaths[0]))) {
|
||||
@ -1888,12 +1878,12 @@ const App = memo(() => {
|
||||
}, [alwaysConcatMultipleFiles, batchLoadPaths, setWorking, isFileOpened, batchFiles.length, userOpenSingleFile, checkFileOpened, loadEdlFile, enableAskForFileOpenAction, addStreamSourceFile, filePath]);
|
||||
|
||||
const openFilesDialog = useCallback(async () => {
|
||||
const { canceled, filePaths } = await showOpenDialog({ properties: ['openFile', 'multiSelections'], defaultPath: lastOpenedPath });
|
||||
const { canceled, filePaths } = await showOpenDialog({ properties: ['openFile', 'multiSelections'], defaultPath: lastOpenedPathRef.current });
|
||||
if (canceled) return;
|
||||
userOpenFiles(filePaths);
|
||||
}, [userOpenFiles]);
|
||||
|
||||
const concatCurrentBatch = useCallback(() => {
|
||||
const concatBatch = useCallback(() => {
|
||||
if (batchFiles.length < 2) {
|
||||
openFilesDialog();
|
||||
return;
|
||||
@ -1909,14 +1899,20 @@ const App = memo(() => {
|
||||
electron.clipboard.writeText(await formatTsv(selectedSegments));
|
||||
}, [isFileOpened, selectedSegments]);
|
||||
|
||||
const getKeyboardAction = useCallback(({ action, keyup }) => {
|
||||
const mainActions = useMemo(() => {
|
||||
async function exportEdlYouTube() {
|
||||
if (!checkFileOpened()) return;
|
||||
|
||||
await openYouTubeChaptersDialog(formatYouTube(apparentCutSegments));
|
||||
}
|
||||
|
||||
function seekReset() {
|
||||
seekAccelerationRef.current = 1;
|
||||
}
|
||||
|
||||
// NOTE: Do not change these keys because users have bound keys by these names
|
||||
return {
|
||||
// NOTE: Do not change these keys because users have bound keys by these names in their config files
|
||||
// For actions, see also KeyboardShortcuts.jsx
|
||||
const mainActions = {
|
||||
togglePlayNoResetSpeed: () => togglePlay(),
|
||||
togglePlayResetSpeed: () => togglePlay({ resetPlaybackRate: true }),
|
||||
togglePlayOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, playbackMode: 'play-segment-once' }),
|
||||
@ -1938,7 +1934,7 @@ const App = memo(() => {
|
||||
splitCurrentSegment,
|
||||
increaseRotation,
|
||||
goToTimecode,
|
||||
seekBackwards() {
|
||||
seekBackwards({ keyup }) {
|
||||
if (keyup) {
|
||||
seekReset();
|
||||
return;
|
||||
@ -1946,7 +1942,7 @@ const App = memo(() => {
|
||||
seekRel(keyboardNormalSeekSpeed * seekAccelerationRef.current * -1);
|
||||
seekAccelerationRef.current *= keyboardSeekAccFactor;
|
||||
},
|
||||
seekForwards() {
|
||||
seekForwards({ keyup }) {
|
||||
if (keyup) {
|
||||
seekReset();
|
||||
return;
|
||||
@ -1998,7 +1994,7 @@ const App = memo(() => {
|
||||
extractAllStreams,
|
||||
convertFormatCurrentFile: () => userHtml5ifyCurrentFile(),
|
||||
convertFormatBatch,
|
||||
concatBatch: concatCurrentBatch,
|
||||
concatBatch,
|
||||
toggleKeyframeCutMode: () => toggleKeyframeCut(true),
|
||||
toggleCaptureFormat,
|
||||
toggleStripAudio,
|
||||
@ -2017,16 +2013,28 @@ const App = memo(() => {
|
||||
reloadFile: () => setCacheBuster((v) => v + 1),
|
||||
quit: () => quitApp(),
|
||||
closeCurrentFile: () => { closeFileWithConfirm(); },
|
||||
exportEdlYouTube,
|
||||
showStreamsSelector: handleShowStreamsSelectorClick,
|
||||
askSetStartTimeOffset,
|
||||
html5ify: () => userHtml5ifyCurrentFile({ ignoreRememberedValue: true }),
|
||||
openFilesDialog,
|
||||
toggleKeyboardShortcuts,
|
||||
toggleSettings,
|
||||
openSendReportDialog: () => { openSendReportDialogWithState(); },
|
||||
detectBlackScenes,
|
||||
detectSilentScenes,
|
||||
detectSceneChanges,
|
||||
createSegmentsFromKeyframes,
|
||||
};
|
||||
}, [addSegment, alignSegmentTimesToKeyframes, apparentCutSegments, askSetStartTimeOffset, 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, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleSettings, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
|
||||
|
||||
return mainActions[action];
|
||||
}, [addSegment, alignSegmentTimesToKeyframes, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatCurrentBatch, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, duplicateCurrentSegment, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, onExportPress, onLabelSegment, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
|
||||
const getKeyboardAction = useCallback((action) => mainActions[action], [mainActions]);
|
||||
|
||||
const onKeyPress = useCallback(({ action, keyup }) => {
|
||||
function tryMainActions() {
|
||||
const fn = getKeyboardAction({ action, keyup });
|
||||
const fn = getKeyboardAction(action);
|
||||
if (!fn) return { match: false };
|
||||
const bubble = fn();
|
||||
const bubble = fn({ keyup });
|
||||
return { match: true, bubble };
|
||||
}
|
||||
|
||||
@ -2146,7 +2154,7 @@ const App = memo(() => {
|
||||
}, [fileUri, usingPreviewFile, filePath, setWorking, hasVideo, hasAudio, html5ifyAndLoadWithPreferences, customOutDir, showUnsupportedFileMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
async function exportEdlFile2(e, type) {
|
||||
async function tryExportEdlFile(type) {
|
||||
if (!checkFileOpened()) return;
|
||||
try {
|
||||
await exportEdlFile({ type, cutSegments: selectedSegments, customOutDir, filePath, getFrameCount });
|
||||
@ -2156,13 +2164,7 @@ const App = memo(() => {
|
||||
}
|
||||
}
|
||||
|
||||
async function exportEdlYouTube() {
|
||||
if (!checkFileOpened()) return;
|
||||
|
||||
await openYouTubeChaptersDialog(formatYouTube(apparentCutSegments));
|
||||
}
|
||||
|
||||
async function importEdlFile(e, type) {
|
||||
async function importEdlFile(type) {
|
||||
if (!checkFileOpened()) return;
|
||||
|
||||
try {
|
||||
@ -2176,67 +2178,53 @@ const App = memo(() => {
|
||||
async function tryApiKeyboardAction(event, { id, action }) {
|
||||
console.log('API keyboard action:', action);
|
||||
try {
|
||||
const fn = getKeyboardAction({ action });
|
||||
const fn = getKeyboardAction(action);
|
||||
if (!fn) throw new Error(`Action not found: ${action}`);
|
||||
await fn();
|
||||
await fn({ keyup: false });
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
} finally {
|
||||
// todo correlation ids
|
||||
event.sender.send('apiKeyboardActionResponse', { id });
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
openFiles: (event, filePaths) => { userOpenFiles(filePaths.map(resolvePathIfNeeded)); },
|
||||
apiKeyboardAction: tryApiKeyboardAction,
|
||||
openFilesDialog,
|
||||
closeCurrentFile: () => { closeFileWithConfirm(); },
|
||||
closeBatchFiles: () => { closeBatch(); },
|
||||
html5ify: () => userHtml5ifyCurrentFile({ ignoreRememberedValue: true }),
|
||||
askSetStartTimeOffset,
|
||||
extractAllStreams,
|
||||
showStreamsSelector: handleShowStreamsSelectorClick,
|
||||
const actionsWithArgs = {
|
||||
openFiles: (filePaths) => { userOpenFiles(filePaths.map(resolvePathIfNeeded)); },
|
||||
// todo separate actions per type and move them into mainActions?
|
||||
importEdlFile,
|
||||
exportEdlFile: exportEdlFile2,
|
||||
exportEdlYouTube,
|
||||
toggleLastCommands,
|
||||
toggleKeyboardShortcuts,
|
||||
toggleSettings,
|
||||
openSendReportDialog: () => { openSendReportDialogWithState(); },
|
||||
clearSegments,
|
||||
shuffleSegments,
|
||||
createNumSegments,
|
||||
createFixedDurationSegments,
|
||||
createRandomSegments,
|
||||
invertAllSegments,
|
||||
fillSegmentsGaps,
|
||||
combineOverlappingSegments,
|
||||
combineSelectedSegments,
|
||||
splitCurrentSegment,
|
||||
fixInvalidDuration: tryFixInvalidDuration,
|
||||
reorderSegsByStartTime,
|
||||
concatCurrentBatch,
|
||||
detectBlackScenes,
|
||||
detectSilentScenes,
|
||||
detectSceneChanges,
|
||||
createSegmentsFromKeyframes,
|
||||
shiftAllSegmentTimes,
|
||||
alignSegmentTimesToKeyframes,
|
||||
exportEdlFile: tryExportEdlFile,
|
||||
};
|
||||
|
||||
const actionsWithCatch = Object.entries(actions).map(([key, action]) => [
|
||||
key,
|
||||
async (...args) => {
|
||||
async function actionWithCatch(fn) {
|
||||
try {
|
||||
await action(...args);
|
||||
await fn();
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
}
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const actionsWithCatch = [
|
||||
// actions with arguments:
|
||||
...Object.entries(actionsWithArgs).map(([key, fn]) => [
|
||||
key,
|
||||
async (event, ...args) => actionWithCatch(() => fn(...args)),
|
||||
]),
|
||||
// all main actions (no arguments, except keyup which we don't support):
|
||||
...Object.entries(mainActions).map(([key, fn]) => [
|
||||
key,
|
||||
async () => actionWithCatch(() => fn({ keyup: false })),
|
||||
]),
|
||||
];
|
||||
|
||||
actionsWithCatch.forEach(([key, action]) => electron.ipcRenderer.on(key, action));
|
||||
return () => actionsWithCatch.forEach(([key, action]) => electron.ipcRenderer.removeListener(key, action));
|
||||
}, [alignSegmentTimesToKeyframes, apparentCutSegments, askSetStartTimeOffset, checkFileOpened, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatCurrentBatch, createFixedDurationSegments, createNumSegments, createRandomSegments, createSegmentsFromKeyframes, customOutDir, cutSegments, detectBlackScenes, detectSceneChanges, detectSilentScenes, detectedFps, extractAllStreams, fileFormat, filePath, fillSegmentsGaps, getFrameCount, getKeyboardAction, handleShowStreamsSelectorClick, invertAllSegments, loadCutSegments, loadMedia, openFilesDialog, openSendReportDialogWithState, reorderSegsByStartTime, selectedSegments, setWorking, shiftAllSegmentTimes, shuffleSegments, splitCurrentSegment, toggleKeyboardShortcuts, toggleLastCommands, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]);
|
||||
electron.ipcRenderer.on('apiKeyboardAction', tryApiKeyboardAction);
|
||||
|
||||
return () => {
|
||||
actionsWithCatch.forEach(([key, action]) => electron.ipcRenderer.off(key, action));
|
||||
electron.ipcRenderer.off('apiKeyboardAction', tryApiKeyboardAction);
|
||||
};
|
||||
}, [checkFileOpened, customOutDir, detectedFps, filePath, getFrameCount, getKeyboardAction, loadCutSegments, mainActions, selectedSegments, userOpenFiles]);
|
||||
|
||||
const showAddStreamSourceDialog = useCallback(async () => {
|
||||
try {
|
||||
@ -2337,7 +2325,7 @@ const App = memo(() => {
|
||||
onBatchFileSelect={onBatchFileSelect}
|
||||
batchListRemoveFile={batchListRemoveFile}
|
||||
closeBatch={closeBatch}
|
||||
onMergeFilesClick={concatCurrentBatch}
|
||||
onMergeFilesClick={concatBatch}
|
||||
onBatchConvertToSupportedFormatClick={convertFormatBatch}
|
||||
/>
|
||||
)}
|
||||
@ -2571,7 +2559,7 @@ const App = memo(() => {
|
||||
|
||||
<ConcatDialog isShown={batchFiles.length > 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} />
|
||||
|
||||
<KeyboardShortcuts isShown={keyboardShortcutsVisible} onHide={() => setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
|
||||
<KeyboardShortcuts isShown={keyboardShortcutsVisible} onHide={() => setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} mainActions={mainActions} />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</UserSettingsContext.Provider>
|
||||
|
@ -107,7 +107,7 @@ const CreateBinding = memo(({
|
||||
const rowStyle = { display: 'flex', alignItems: 'center', margin: '6px 0' };
|
||||
|
||||
const KeyboardShortcuts = memo(({
|
||||
keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg,
|
||||
keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg, mainActions,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -193,6 +193,10 @@ const KeyboardShortcuts = memo(({
|
||||
name: t('Reload current media'),
|
||||
category: playbackCategory,
|
||||
},
|
||||
html5ify: {
|
||||
name: t('Convert to supported format'),
|
||||
category: playbackCategory,
|
||||
},
|
||||
|
||||
// selectivePlaybackCategory
|
||||
togglePlayOnlyCurrentSegment: {
|
||||
@ -327,6 +331,10 @@ const KeyboardShortcuts = memo(({
|
||||
name: t('Align segment times to keyframes'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
},
|
||||
createSegmentsFromKeyframes: {
|
||||
name: t('Create segments from keyframes'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
},
|
||||
createFixedDurationSegments: {
|
||||
name: t('Create fixed duration segments'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
@ -339,6 +347,18 @@ const KeyboardShortcuts = memo(({
|
||||
name: t('Create random segments'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
},
|
||||
detectBlackScenes: {
|
||||
name: t('Detect black scenes'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
},
|
||||
detectSilentScenes: {
|
||||
name: t('Detect silent scenes'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
},
|
||||
detectSceneChanges: {
|
||||
name: t('Detect scene changes'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
},
|
||||
shuffleSegments: {
|
||||
name: t('Shuffle segments order'),
|
||||
category: segmentsAndCutpointsCategory,
|
||||
@ -392,6 +412,10 @@ const KeyboardShortcuts = memo(({
|
||||
name: t('Extract all tracks'),
|
||||
category: streamsCategory,
|
||||
},
|
||||
showStreamsSelector: {
|
||||
name: t('Edit tracks / metadata tags'),
|
||||
category: streamsCategory,
|
||||
},
|
||||
|
||||
// zoomOperationsCategory
|
||||
timelineZoomIn: {
|
||||
@ -500,6 +524,26 @@ const KeyboardShortcuts = memo(({
|
||||
name: t('Copy selected segments times to clipboard'),
|
||||
category: otherCategory,
|
||||
},
|
||||
askSetStartTimeOffset: {
|
||||
name: t('Set custom start offset/timecode'),
|
||||
category: otherCategory,
|
||||
},
|
||||
toggleSettings: {
|
||||
name: t('Settings'),
|
||||
category: otherCategory,
|
||||
},
|
||||
openSendReportDialog: {
|
||||
name: t('Report an error'),
|
||||
category: otherCategory,
|
||||
},
|
||||
openFilesDialog: {
|
||||
name: t('Open'),
|
||||
category: otherCategory,
|
||||
},
|
||||
exportEdlYouTube: {
|
||||
name: t('Start times as YouTube Chapters'),
|
||||
category: otherCategory,
|
||||
},
|
||||
closeActiveScreen: {
|
||||
name: t('Close current screen'),
|
||||
category: otherCategory,
|
||||
@ -574,6 +618,9 @@ const KeyboardShortcuts = memo(({
|
||||
});
|
||||
}, [setKeyBindings]);
|
||||
|
||||
const missingAction = Object.keys(mainActions).find((key) => actionsMap[key] == null);
|
||||
if (missingAction) throw new Error(`Action missing: ${missingAction}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ color: 'black' }}>
|
||||
@ -631,7 +678,7 @@ const KeyboardShortcuts = memo(({
|
||||
});
|
||||
|
||||
const KeyboardShortcutsDialog = memo(({
|
||||
isShown, onHide, keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg,
|
||||
isShown, onHide, keyBindings, setKeyBindings, resetKeyBindings, currentCutSeg, mainActions,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -645,7 +692,7 @@ const KeyboardShortcutsDialog = memo(({
|
||||
onConfirm={onHide}
|
||||
topOffset="3vh"
|
||||
>
|
||||
{isShown ? <KeyboardShortcuts keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> : <div />}
|
||||
{isShown ? <KeyboardShortcuts keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} mainActions={mainActions} /> : <div />}
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
12
src/util.js
12
src/util.js
@ -7,6 +7,7 @@ import pRetry from 'p-retry';
|
||||
|
||||
import isDev from './isDev';
|
||||
import Swal, { toast } from './swal';
|
||||
import { ffmpegExtractWindow } from './util/constants';
|
||||
|
||||
const { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
|
||||
const fsExtra = window.require('fs-extra');
|
||||
@ -393,3 +394,14 @@ export async function readVideoTs(videoTsPath) {
|
||||
if (ret.length === 0) throw new Error('No VTS vob files found in folder');
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function getImportProjectType(filePath) {
|
||||
if (filePath.endsWith('Summary.txt')) return 'dv-analyzer-summary-txt';
|
||||
const edlFormatForExtension = { csv: 'csv', pbf: 'pbf', edl: 'mplayer', cue: 'cue', xml: 'xmeml', fcpxml: 'fcpxml' };
|
||||
const matchingExt = Object.keys(edlFormatForExtension).find((ext) => filePath.toLowerCase().endsWith(`.${ext}`));
|
||||
if (!matchingExt) return undefined;
|
||||
return edlFormatForExtension[matchingExt];
|
||||
}
|
||||
|
||||
export const calcShouldShowWaveform = (zoomedDuration) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
|
||||
export const calcShouldShowKeyframes = (zoomedDuration) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
|
||||
|
Loading…
Reference in New Issue
Block a user