kopia lustrzana https://github.com/mifi/lossless-cut
rodzic
ea36efba07
commit
92c6ceab13
|
@ -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
29
cli.md
|
@ -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
|
||||
```
|
|
@ -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 };
|
||||
|
|
25
src/App.jsx
25
src/App.jsx
|
@ -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 {
|
||||
|
|
|
@ -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 }} />
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue