mirror of
https://github.com/mifi/lossless-cut.git
synced 2026-08-30 17:12:12 +08:00
fail fast in startup check when ffmpeg executable is missing
On Windows, when the ffmpeg/ffprobe executable does not exist (e.g. a custom FFmpeg directory pointing to a location without ffmpeg.exe), cross-spawn (used by execa) falls back to running the command through cmd.exe, which fails with exit code 1 and a confusing "'...' is not recognized as an internal or external command" error instead of ENOENT. This bypassed the "FFmpeg executable not found" dialog and instead triggered the error report dialog. Now check that the executables exist as part of the startup check (which also re-runs whenever the custom FFmpeg directory setting changes), so a missing executable yields a proper ENOENT. Also mention the custom FFmpeg directory setting in the error dialog when one is configured. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PE6aFnysh85Ryndeu6zXYG
This commit is contained in:
@@ -740,6 +740,7 @@
|
||||
"Some extra tracks have been discarded. You can change this option before merging.": "Some extra tracks have been discarded. You can change this option before merging.",
|
||||
"Something went wrong": "Something went wrong",
|
||||
"Sort items": "Sort items",
|
||||
"Source code": "Source code",
|
||||
"Source file no longer exists or is not accessible. Please check that it is still in its original location and that you have permission to access it.": "Source file no longer exists or is not accessible. Please check that it is still in its original location and that you have permission to access it.",
|
||||
"Source file's time minus segment end cut time": "Source file's time minus segment end cut time",
|
||||
"Source file's time plus segment start cut time": "Source file's time plus segment start cut time",
|
||||
@@ -892,6 +893,7 @@
|
||||
"You can customize the file name of the output segment(s) using special variables._one": "You can customize the file name of the output using special variables.",
|
||||
"You can customize the file name of the output segment(s) using special variables._other": "You can customize the file name of the output segments using special variables.",
|
||||
"You do not have permission to access this file": "You do not have permission to access this file",
|
||||
"You have configured a custom FFmpeg directory. You may change or reset it in Settings.": "You have configured a custom FFmpeg directory. You may change or reset it in Settings.",
|
||||
"You have enabled the \"invert segments\" mode <1></1> which will cut away selected segments instead of keeping them. But there is no space between any segments, or at least two segments are overlapping. This would not produce any output. Either make room between segments or click the Yinyang <3></3> symbol below to disable this mode. Alternatively you may combine overlapping segments from the menu.": "You have enabled the \"invert segments\" mode <1></1> which will cut away selected segments instead of keeping them. But there is no space between any segments, or at least two segments are overlapping. This would not produce any output. Either make room between segments or click the Yinyang <3></3> symbol below to disable this mode. Alternatively you may combine overlapping segments from the menu.",
|
||||
"You have no write access to the directory of this file": "You have no write access to the directory of this file",
|
||||
"You have no write access to the directory of this file, please select a custom working dir": "You have no write access to the directory of this file, please select a custom working dir",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { join } from 'node:path';
|
||||
import { access } from 'node:fs/promises';
|
||||
import readline from 'node:readline';
|
||||
import stringToStream from 'string-to-stream';
|
||||
import type { Options as ExecaOptions, ResultPromise } from 'execa';
|
||||
@@ -66,6 +67,15 @@ function getFfPath(cmd: FfCommand) {
|
||||
);
|
||||
}
|
||||
|
||||
// Used by the startup check to fail fast with a proper ENOENT if the executable doesn't exist.
|
||||
// This is because on Windows, cross-spawn (used by execa) falls back to running the command through
|
||||
// cmd.exe when it cannot resolve the executable path (e.g. custom FFmpeg directory pointing to a
|
||||
// location without ffmpeg.exe), which instead fails with a confusing exit code 1:
|
||||
// "'...' is not recognized as an internal or external command"
|
||||
export async function checkFfExists(cmd: FfCommand) {
|
||||
await access(getFfPath(cmd)); // throws with code ENOENT if it doesn't exist
|
||||
}
|
||||
|
||||
const getFfprobePath = () => getFfPath('ffprobe');
|
||||
/**
|
||||
* ⚠️ Do not use directly when running ffmpeg, because we need to add certain options before running, like `LD_LIBRARY_PATH` on linux
|
||||
|
||||
@@ -2483,7 +2483,7 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setFfmpegInfo(await runStartupCheck({ onError: ({ title, message }) => setGenericError({ title, err: message }) }));
|
||||
setFfmpegInfo(await runStartupCheck({ customFfPath, onError: ({ title, message }) => setGenericError({ title, err: message }) }));
|
||||
})();
|
||||
}, [customFfPath, setGenericError]);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { parseFfprobeDuration } from '../../common/util';
|
||||
|
||||
const { ffmpeg } = window.require('@electron/remote').require('./index.js');
|
||||
|
||||
const { renderWaveformPng, mapTimesToSegments, detectSceneChanges, captureFrames, captureFrameToFile, captureFrameToClipboard, getFfCommandLine, runFfmpegConcat, runFfmpegWithProgress, getDuration, abortFfmpegs, runFfmpeg, runFfprobe, getFfmpegPath, setCustomFfPath } = ffmpeg;
|
||||
const { renderWaveformPng, mapTimesToSegments, detectSceneChanges, captureFrames, captureFrameToFile, captureFrameToClipboard, getFfCommandLine, runFfmpegConcat, runFfmpegWithProgress, getDuration, abortFfmpegs, runFfmpeg, runFfprobe, getFfmpegPath, setCustomFfPath, checkFfExists } = ffmpeg;
|
||||
|
||||
|
||||
export { renderWaveformPng, mapTimesToSegments, detectSceneChanges, captureFrames, captureFrameToFile, captureFrameToClipboard, getFfCommandLine, runFfmpegConcat, runFfmpegWithProgress, getDuration, abortFfmpegs, runFfmpeg, getFfmpegPath, setCustomFfPath };
|
||||
@@ -550,6 +550,10 @@ const ffprobeVersionSchema = z.object({
|
||||
});
|
||||
|
||||
export async function runFfmpegStartupCheck() {
|
||||
// will throw ENOENT if the executables don't exist (e.g. custom FFmpeg directory pointing to a location without them)
|
||||
await checkFfExists('ffmpeg');
|
||||
await checkFfExists('ffprobe');
|
||||
|
||||
// will throw if exit code != 0
|
||||
const { stderr: ffmpegStderr } = await runFfmpeg(['-f', 'lavfi', '-i', 'nullsrc=s=256x256:d=1', '-f', 'null', '-']);
|
||||
console.log('FFmpeg startup check output:', new TextDecoder().decode(ffmpegStderr));
|
||||
|
||||
@@ -18,7 +18,7 @@ export async function loadMifiLink() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function runStartupCheck({ onError }: { onError: (error: { title: string, message: string }) => void }) {
|
||||
export async function runStartupCheck({ customFfPath, onError }: { customFfPath: string | undefined, onError: (error: { title: string, message: string }) => void }) {
|
||||
try {
|
||||
return await runFfmpegStartupCheck();
|
||||
} catch (err) {
|
||||
@@ -26,7 +26,15 @@ export async function runStartupCheck({ onError }: { onError: (error: { title: s
|
||||
if ('code' in err && err.code === 'ENOENT') {
|
||||
onError({
|
||||
title: i18n.t('Fatal: FFmpeg executable not found'),
|
||||
message: `${i18n.t('Make sure that the FFmpeg executable exists:')}\n\n${getFfmpegPath()}`,
|
||||
message: [
|
||||
i18n.t('Make sure that the FFmpeg executable exists:'),
|
||||
'',
|
||||
getFfmpegPath(),
|
||||
...(customFfPath != null ? [
|
||||
'',
|
||||
i18n.t('You have configured a custom FFmpeg directory. You may change or reset it in Settings.'),
|
||||
] : []),
|
||||
].join('\n'),
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user