add keyboard action cli control api

and add quit keyboard shortcut
pull/1752/head
Mikael Finstad 2023-10-15 18:44:14 +08:00
rodzic ea36efba07
commit 92c6ceab13
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 25AB36E3E81CBC26
5 zmienionych plików z 69 dodań i 11 usunięć

Wyświetl plik

@ -167,7 +167,7 @@ Unsupported files can still be converted to a supported format/codec from the `F
## [Import / export](import-export.md)
## [Command line interface (CLI)](cli.md)
## [Command line interface (CLI) / API](cli.md)
## [Known issues, limitations, troubleshooting, FAQ](issues.md)

29
cli.md
Wyświetl plik

@ -22,3 +22,32 @@ See [available settings](https://github.com/mifi/lossless-cut/blob/master/public
```bash
LosslessCut --settings-json '{captureFormat:"jpeg", "keyframeCut":true}'
```
## Controlling a running instance (experimental)
If you have the "Allow multiple instances" setting enabled, you can control a running instance of LosslessCut from the outside, using for example a command line. You do this by issuing messages to it through the `LosslessCut` command. Currently only keyboard actions are supported. *Note that this is considered experimental and the API may change at any time.*
### Keyboard actions, `--keyboard-action`
Simulate a keyboard press action. The available action names can be found in the "Keyboard shortcuts" dialog (Note: you don't have to bind them to any key).
Example:
```bash
# Export the currently opened file
LosslessCut --keyboard-action export
```
#### Batch example
Note that there is no synchronization, and the action will exit immediately, regardless of how long the action takes. This means that you will have to sleep between multiple actions.
```bash
for PROJECT in /path/to/folder/with/projects/*.llc
LosslessCut $PROJECT
sleep 5 # wait for the file to open
LosslessCut --keyboard-action export
sleep 10 # hopefully done by then
LosslessCut --keyboard-action quit
done
```

Wyświetl plik

@ -201,7 +201,11 @@ function initApp() {
if (!Array.isArray(additionalData?.argv)) return;
const argv2 = parseCliArgs(additionalData.argv);
if (argv2._) openFilesEventually(argv2._);
logger.info('second-instance', argv2);
if (argv2._ && argv2._.length > 0) openFilesEventually(argv2._);
else if (argv2.keyboardAction) mainWindow.webContents.send('apiKeyboardAction', argv2.keyboardAction);
});
// Quit when all windows are closed.
@ -317,6 +321,10 @@ function focusWindow() {
}
}
function quitApp() {
electron.app.quit();
}
const hasDisabledNetworking = () => !!disableNetworking;
module.exports = { focusWindow, isDev, hasDisabledNetworking };
module.exports = { focusWindow, isDev, hasDisabledNetworking, quitApp };

Wyświetl plik

@ -90,7 +90,7 @@ const filePathToUrl = window.require('file-url');
const { parse: parsePath, join: pathJoin, basename, dirname } = window.require('path');
const remote = window.require('@electron/remote');
const { focusWindow, hasDisabledNetworking } = remote.require('./electron');
const { focusWindow, hasDisabledNetworking, quitApp } = remote.require('./electron');
const calcShouldShowWaveform = (zoomedDuration) => (zoomedDuration != null && zoomedDuration < ffmpegExtractWindow * 8);
@ -1890,7 +1890,7 @@ const App = memo(() => {
electron.clipboard.writeText(await formatTsv(selectedSegments));
}, [isFileOpened, selectedSegments]);
const onKeyPress = useCallback(({ action, keyup }) => {
const getKeyboardAction = useCallback(({ action, keyup }) => {
function seekReset() {
seekAccelerationRef.current = 1;
}
@ -1996,10 +1996,15 @@ const App = memo(() => {
decreaseVolume: () => setPlaybackVolume((val) => Math.max(0, val - 0.07)),
copySegmentsToClipboard,
reloadFile: () => setCacheBuster((v) => v + 1),
quit: () => quitApp(),
};
return mainActions[action];
}, [addSegment, alignSegmentTimesToKeyframes, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, 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 onKeyPress = useCallback(({ action, keyup }) => {
function tryMainActions() {
const fn = mainActions[action];
const fn = getKeyboardAction({ action, keyup });
if (!fn) return { match: false };
const bubble = fn();
return { match: true, bubble };
@ -2038,7 +2043,7 @@ const App = memo(() => {
if (match) return bubble;
return true; // bubble the event
}, [addSegment, alignSegmentTimesToKeyframes, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, combineOverlappingSegments, combineSelectedSegments, concatCurrentBatch, concatDialogVisible, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, duplicateCurrentSegment, exportConfirmVisible, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegment, 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, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
}, [closeExportConfirm, concatDialogVisible, exportConfirmVisible, getKeyboardAction, keyboardShortcutsVisible, onExportConfirm, toggleKeyboardShortcuts]);
useKeyboard({ keyBindings, onKeyPress });
@ -2148,8 +2153,18 @@ const App = memo(() => {
}
}
function tryKeyboardAction(action) {
const fn = getKeyboardAction({ action });
if (!fn) {
console.error('Action not found:', action);
return;
}
fn();
}
const actions = {
openFiles: (event, filePaths) => { userOpenFiles(filePaths.map(resolvePathIfNeeded)); },
apiKeyboardAction: (event, action) => tryKeyboardAction(action),
openFilesDialog,
closeCurrentFile: () => { closeFileWithConfirm(); },
closeBatchFiles: () => { closeBatch(); },
@ -2197,7 +2212,7 @@ const App = memo(() => {
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, handleShowStreamsSelectorClick, invertAllSegments, loadCutSegments, loadMedia, openFilesDialog, openSendReportDialogWithState, reorderSegsByStartTime, selectedSegments, setWorking, shiftAllSegmentTimes, shuffleSegments, toggleKeyboardShortcuts, toggleLastCommands, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]);
}, [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, toggleKeyboardShortcuts, toggleLastCommands, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]);
const showAddStreamSourceDialog = useCallback(async () => {
try {

Wyświetl plik

@ -504,6 +504,10 @@ const KeyboardShortcuts = memo(({
name: t('Close current screen'),
category: otherCategory,
},
quit: {
name: t('Quit LosslessCut'),
category: otherCategory,
},
},
};
}, [currentCutSeg, t]);
@ -585,9 +589,11 @@ const KeyboardShortcuts = memo(({
return (
<div key={action} style={rowStyle}>
{beforeContent}
<Text title={action} marginRight={10}>{actionName}</Text>
<div>
{beforeContent}
<Text title={action} marginRight={10}>{actionName}</Text>
<div style={{ fontSize: '.8em', opacity: 0.4 }} title={t('API action name: {{action}}', { action })}>{action}</div>
</div>
<div style={{ flexGrow: 1 }} />