Detect missing source file when extracting frames and tracks too

Follow-up to #2797. `cutMultiple` already calls `assertFileExists` before
spawning ffmpeg, so a source file that was moved or deleted after being
opened produces a clear "Source file no longer exists or is not
accessible" message instead of a raw ExecaError plus the generic "Unable
to export this file" checklist (change output format, try keyframe cut,
change working directory...), none of which can help.

The other export paths had no such guard, so they still fail with a bare
ffmpeg "No such file or directory". Add the same check to:

- `captureFramesRange` (export segment frames as images)
- `captureFrameFromFfmpeg` (capture snapshot)
- `extractStreams` (extract all tracks / extract single track)

`extractAllStreams`/`extractSingleStream` swallowed the error message and
showed a generic toast, so let UserFacingError through there as well,
matching how the export and merge flows already handle it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqUn6Bh7hWqPqWejqR8p7u
claude/losslesscut-export-error-wtlsc6
Claude 1 month ago
parent 8983749ff3
commit 9406996fc5
No known key found for this signature in database

@ -1706,6 +1706,8 @@ function App() {
if (err instanceof RefuseOverwriteError) {
showRefuseToOverwrite();
} else if (err instanceof UserFacingError) {
errorToast(err.message);
} else {
errorToast(i18n.t('Failed to extract all streams'));
console.error('Failed to extract all streams', err);
@ -2248,6 +2250,8 @@ function App() {
if (err instanceof RefuseOverwriteError) {
showRefuseToOverwrite();
} else if (err instanceof UserFacingError) {
errorToast(err.message);
} else {
errorToast(i18n.t('Failed to extract track'));
console.error('Failed to extract track', err);

@ -2,7 +2,7 @@ import { dataUriToBuffer } from 'data-uri-to-buffer';
import pMap from 'p-map';
import { useCallback } from 'react';
import { getSuffixedOutPath, getOutDir, transferTimestamps, getSuffixedFileName, getOutPath, escapeRegExp, fsOperationWithRetry } from '../util';
import { getSuffixedOutPath, getOutDir, transferTimestamps, getSuffixedFileName, getOutPath, escapeRegExp, fsOperationWithRetry, assertFileExists } from '../util';
import { getNumDigits, isDurationValid } from '../segments';
import * as ffmpeg from '../ffmpeg';
@ -45,6 +45,9 @@ export default ({ appendFfmpegCommandLog, formatTimecode, treatInputFileModified
onProgress: (a: number) => void,
outputTimestamps: boolean,
}) => {
// fail fast with a helpful message if the source file has been moved/deleted since it was opened
await assertFileExists(filePath);
const getSuffix = (prefix: string) => `${prefix}.${captureFormat}`;
if (!outputTimestamps) {
@ -98,6 +101,8 @@ export default ({ appendFfmpegCommandLog, formatTimecode, treatInputFileModified
captureFormat: CaptureFormat,
quality: number,
}) => {
await assertFileExists(filePath);
const timecode = formatTimecode({ seconds: time, fileNameFriendly: true });
const nameSuffix = `${timecode}.${captureFormat}`;
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix });

Loading…
Cancel
Save