mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +01:00
improve error handling
and types
This commit is contained in:
parent
e84b9a8266
commit
9cdd5edbfb
13
src/renderer/errors.ts
Normal file
13
src/renderer/errors.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export class DirectoryAccessDeclinedError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.name = 'DirectoryAccessDeclinedError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UnsupportedFileError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'UnsupportedFileError';
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ import useKeyboard from './hooks/useKeyboard';
|
|||||||
import useFileFormatState from './hooks/useFileFormatState';
|
import useFileFormatState from './hooks/useFileFormatState';
|
||||||
import useFrameCapture from './hooks/useFrameCapture';
|
import useFrameCapture from './hooks/useFrameCapture';
|
||||||
import useSegments from './hooks/useSegments';
|
import useSegments from './hooks/useSegments';
|
||||||
import useDirectoryAccess, { DirectoryAccessDeclinedError } from './hooks/useDirectoryAccess';
|
import useDirectoryAccess from './hooks/useDirectoryAccess';
|
||||||
|
|
||||||
import { UserSettingsContext, SegColorsContext, UserSettingsContextType } from './contexts';
|
import { UserSettingsContext, SegColorsContext, UserSettingsContextType } from './contexts';
|
||||||
|
|
||||||
@ -72,6 +72,7 @@ import {
|
|||||||
isMuxNotSupported,
|
isMuxNotSupported,
|
||||||
getDownloadMediaOutPath,
|
getDownloadMediaOutPath,
|
||||||
isAbortedError,
|
isAbortedError,
|
||||||
|
withErrorHandling,
|
||||||
} from './util';
|
} from './util';
|
||||||
import { toast, errorToast, showPlaybackFailedMessage } from './swal';
|
import { toast, errorToast, showPlaybackFailedMessage } from './swal';
|
||||||
import { adjustRate } from './util/rate-calculator';
|
import { adjustRate } from './util/rate-calculator';
|
||||||
@ -98,6 +99,7 @@ import useSubtitles from './hooks/useSubtitles';
|
|||||||
import useStreamsMeta from './hooks/useStreamsMeta';
|
import useStreamsMeta from './hooks/useStreamsMeta';
|
||||||
import { bottomStyle, videoStyle } from './styles';
|
import { bottomStyle, videoStyle } from './styles';
|
||||||
import styles from './App.module.css';
|
import styles from './App.module.css';
|
||||||
|
import { DirectoryAccessDeclinedError } from '../errors';
|
||||||
|
|
||||||
const electron = window.require('electron');
|
const electron = window.require('electron');
|
||||||
const { exists } = window.require('fs-extra');
|
const { exists } = window.require('fs-extra');
|
||||||
@ -492,13 +494,14 @@ function App() {
|
|||||||
}
|
}
|
||||||
const subtitleStream = index != null && subtitleStreams.find((s) => s.index === index);
|
const subtitleStream = index != null && subtitleStreams.find((s) => s.index === index);
|
||||||
if (!subtitleStream || workingRef.current) return;
|
if (!subtitleStream || workingRef.current) return;
|
||||||
try {
|
|
||||||
setWorking({ text: i18n.t('Loading subtitle') });
|
setWorking({ text: i18n.t('Loading subtitle') });
|
||||||
|
try {
|
||||||
|
await withErrorHandling(async () => {
|
||||||
invariant(filePath != null);
|
invariant(filePath != null);
|
||||||
await loadSubtitle({ filePath, index, subtitleStream });
|
await loadSubtitle({ filePath, index, subtitleStream });
|
||||||
setActiveSubtitleStreamIndex(index);
|
setActiveSubtitleStreamIndex(index);
|
||||||
} catch (err) {
|
}, i18n.t('Failed to load subtitles from track {{index}}', { index }));
|
||||||
handleError(`Failed to extract subtitles for stream ${index}`, err instanceof Error && err.message);
|
|
||||||
} finally {
|
} finally {
|
||||||
setWorking(undefined);
|
setWorking(undefined);
|
||||||
}
|
}
|
||||||
@ -646,10 +649,10 @@ function App() {
|
|||||||
if (!speed) return;
|
if (!speed) return;
|
||||||
|
|
||||||
if (workingRef.current) return;
|
if (workingRef.current) return;
|
||||||
try {
|
|
||||||
setWorking({ text: i18n.t('Batch converting to supported format') });
|
setWorking({ text: i18n.t('Batch converting to supported format') });
|
||||||
setCutProgress(0);
|
setCutProgress(0);
|
||||||
|
try {
|
||||||
|
await withErrorHandling(async () => {
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const path of filePaths) {
|
for (const path of filePaths) {
|
||||||
try {
|
try {
|
||||||
@ -670,9 +673,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (failedFiles.length > 0) toast.fire({ title: `${i18n.t('Failed to convert files:')} ${failedFiles.join(' ')}`, timer: null as unknown as undefined, showConfirmButton: true });
|
if (failedFiles.length > 0) toast.fire({ title: `${i18n.t('Failed to convert files:')} ${failedFiles.join(' ')}`, timer: null as unknown as undefined, showConfirmButton: true });
|
||||||
} catch (err) {
|
}, i18n.t('Failed to batch convert to supported format'));
|
||||||
errorToast(i18n.t('Failed to batch convert to supported format'));
|
|
||||||
console.error('Failed to html5ify', err);
|
|
||||||
} finally {
|
} finally {
|
||||||
setWorking(undefined);
|
setWorking(undefined);
|
||||||
setCutProgress(undefined);
|
setCutProgress(undefined);
|
||||||
@ -906,7 +907,7 @@ function App() {
|
|||||||
resetState();
|
resetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await withErrorHandling(async () => {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
setWorking({ text: i18n.t('Cleaning up'), abortController });
|
setWorking({ text: i18n.t('Cleaning up'), abortController });
|
||||||
console.log('Cleaning up files', cleanupChoices2);
|
console.log('Cleaning up files', cleanupChoices2);
|
||||||
@ -917,10 +918,7 @@ function App() {
|
|||||||
if (cleanupChoices2.trashSourceFile && savedPaths.sourceFilePath) pathsToDelete.push(savedPaths.sourceFilePath);
|
if (cleanupChoices2.trashSourceFile && savedPaths.sourceFilePath) pathsToDelete.push(savedPaths.sourceFilePath);
|
||||||
|
|
||||||
await deleteFiles({ paths: pathsToDelete, deleteIfTrashFails: cleanupChoices2.deleteIfTrashFails, signal: abortController.signal });
|
await deleteFiles({ paths: pathsToDelete, deleteIfTrashFails: cleanupChoices2.deleteIfTrashFails, signal: abortController.signal });
|
||||||
} catch (err) {
|
}, (err) => i18n.t('Unable to delete file: {{message}}', { message: err instanceof Error ? err.message : String(err) }));
|
||||||
errorToast(i18n.t('Unable to delete file: {{message}}', { message: err instanceof Error ? err.message : String(err) }));
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}, [batchListRemoveFile, filePath, previewFilePath, projectFileSavePath, resetState, setWorking]);
|
}, [batchListRemoveFile, filePath, previewFilePath, projectFileSavePath, resetState, setWorking]);
|
||||||
|
|
||||||
const askForCleanupChoices = useCallback(async () => {
|
const askForCleanupChoices = useCallback(async () => {
|
||||||
@ -1148,7 +1146,7 @@ function App() {
|
|||||||
const captureSnapshot = useCallback(async () => {
|
const captureSnapshot = useCallback(async () => {
|
||||||
if (!filePath) return;
|
if (!filePath) return;
|
||||||
|
|
||||||
try {
|
await withErrorHandling(async () => {
|
||||||
const currentTime = getRelevantTime();
|
const currentTime = getRelevantTime();
|
||||||
const video = videoRef.current;
|
const video = videoRef.current;
|
||||||
if (video == null) throw new Error();
|
if (video == null) throw new Error();
|
||||||
@ -1158,10 +1156,7 @@ function App() {
|
|||||||
: await captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, quality: captureFrameQuality });
|
: await captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, quality: captureFrameQuality });
|
||||||
|
|
||||||
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: outPath, text: `${i18n.t('Screenshot captured to:')} ${outPath}` });
|
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: outPath, text: `${i18n.t('Screenshot captured to:')} ${outPath}` });
|
||||||
} catch (err) {
|
}, i18n.t('Failed to capture frame'));
|
||||||
console.error(err);
|
|
||||||
errorToast(i18n.t('Failed to capture frame'));
|
|
||||||
}
|
|
||||||
}, [filePath, getRelevantTime, videoRef, usingPreviewFile, captureFrameMethod, captureFrameFromFfmpeg, customOutDir, captureFormat, captureFrameQuality, captureFrameFromTag, hideAllNotifications]);
|
}, [filePath, getRelevantTime, videoRef, usingPreviewFile, captureFrameMethod, captureFrameFromFfmpeg, customOutDir, captureFormat, captureFrameQuality, captureFrameFromTag, hideAllNotifications]);
|
||||||
|
|
||||||
const extractSegmentFramesAsImages = useCallback(async (segIds: string[]) => {
|
const extractSegmentFramesAsImages = useCallback(async (segIds: string[]) => {
|
||||||
@ -1199,7 +1194,6 @@ function App() {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showOsNotification(i18n.t('Failed to extract frames'));
|
showOsNotification(i18n.t('Failed to extract frames'));
|
||||||
|
|
||||||
handleError(err);
|
handleError(err);
|
||||||
} finally {
|
} finally {
|
||||||
setWorking(undefined);
|
setWorking(undefined);
|
||||||
@ -1577,10 +1571,9 @@ function App() {
|
|||||||
if (workingRef.current) return;
|
if (workingRef.current) return;
|
||||||
try {
|
try {
|
||||||
setWorking({ text: i18n.t('Converting to supported format') });
|
setWorking({ text: i18n.t('Converting to supported format') });
|
||||||
|
await withErrorHandling(async () => {
|
||||||
await html5ifyAndLoad(customOutDir, filePath, selectedOption, hasVideo, hasAudio);
|
await html5ifyAndLoad(customOutDir, filePath, selectedOption, hasVideo, hasAudio);
|
||||||
} catch (err) {
|
}, i18n.t('Failed to convert file. Try a different conversion'));
|
||||||
errorToast(i18n.t('Failed to convert file. Try a different conversion'));
|
|
||||||
console.error('Failed to html5ify file', err);
|
|
||||||
} finally {
|
} finally {
|
||||||
setWorking(undefined);
|
setWorking(undefined);
|
||||||
}
|
}
|
||||||
@ -1605,6 +1598,7 @@ function App() {
|
|||||||
const tryFixInvalidDuration = useCallback(async () => {
|
const tryFixInvalidDuration = useCallback(async () => {
|
||||||
if (!checkFileOpened() || workingRef.current) return;
|
if (!checkFileOpened() || workingRef.current) return;
|
||||||
try {
|
try {
|
||||||
|
await withErrorHandling(async () => {
|
||||||
setWorking({ text: i18n.t('Fixing file duration') });
|
setWorking({ text: i18n.t('Fixing file duration') });
|
||||||
setCutProgress(0);
|
setCutProgress(0);
|
||||||
invariant(fileFormat != null);
|
invariant(fileFormat != null);
|
||||||
@ -1612,9 +1606,7 @@ function App() {
|
|||||||
showNotification({ icon: 'info', text: i18n.t('Duration has been fixed') });
|
showNotification({ icon: 'info', text: i18n.t('Duration has been fixed') });
|
||||||
|
|
||||||
await loadMedia({ filePath: path });
|
await loadMedia({ filePath: path });
|
||||||
} catch (err) {
|
}, i18n.t('Failed to fix file duration'));
|
||||||
errorToast(i18n.t('Failed to fix file duration'));
|
|
||||||
console.error('Failed to fix file duration', err);
|
|
||||||
} finally {
|
} finally {
|
||||||
setWorking(undefined);
|
setWorking(undefined);
|
||||||
setCutProgress(undefined);
|
setCutProgress(undefined);
|
||||||
@ -1652,15 +1644,12 @@ function App() {
|
|||||||
|
|
||||||
const captureSnapshotAsCoverArt = useCallback(async () => {
|
const captureSnapshotAsCoverArt = useCallback(async () => {
|
||||||
if (!filePath) return;
|
if (!filePath) return;
|
||||||
try {
|
await withErrorHandling(async () => {
|
||||||
const currentTime = getRelevantTime();
|
const currentTime = getRelevantTime();
|
||||||
const path = await captureFrameFromFfmpeg({ customOutDir, filePath, fromTime: currentTime, captureFormat, quality: captureFrameQuality });
|
const path = await captureFrameFromFfmpeg({ customOutDir, filePath, fromTime: currentTime, captureFormat, quality: captureFrameQuality });
|
||||||
if (!(await addFileAsCoverArt(path))) return;
|
if (!(await addFileAsCoverArt(path))) return;
|
||||||
showNotification({ text: i18n.t('Current frame has been set as cover art') });
|
showNotification({ text: i18n.t('Current frame has been set as cover art') });
|
||||||
} catch (err) {
|
}, i18n.t('Failed to capture frame'));
|
||||||
console.error(err);
|
|
||||||
errorToast(i18n.t('Failed to capture frame'));
|
|
||||||
}
|
|
||||||
}, [addFileAsCoverArt, captureFormat, captureFrameFromFfmpeg, captureFrameQuality, customOutDir, filePath, getRelevantTime, showNotification]);
|
}, [addFileAsCoverArt, captureFormat, captureFrameFromFfmpeg, captureFrameQuality, customOutDir, filePath, getRelevantTime, showNotification]);
|
||||||
|
|
||||||
const batchLoadPaths = useCallback((newPaths: string[], append?: boolean) => {
|
const batchLoadPaths = useCallback((newPaths: string[], append?: boolean) => {
|
||||||
@ -1681,7 +1670,7 @@ function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const userOpenFiles = useCallback(async (filePathsIn?: string[]) => {
|
const userOpenFiles = useCallback(async (filePathsIn?: string[]) => {
|
||||||
try {
|
await withErrorHandling(async () => {
|
||||||
let filePaths = filePathsIn;
|
let filePaths = filePathsIn;
|
||||||
if (!filePaths || filePaths.length === 0) return;
|
if (!filePaths || filePaths.length === 0) return;
|
||||||
|
|
||||||
@ -1804,10 +1793,7 @@ function App() {
|
|||||||
} finally {
|
} finally {
|
||||||
setWorking(undefined);
|
setWorking(undefined);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
}, i18n.t('Failed to open file'));
|
||||||
console.error('userOpenFiles', err);
|
|
||||||
handleError(i18n.t('Failed to open file'), err);
|
|
||||||
}
|
|
||||||
}, [workingRef, alwaysConcatMultipleFiles, batchLoadPaths, setWorking, isFileOpened, batchFiles.length, userOpenSingleFile, checkFileOpened, loadEdlFile, enableAskForFileOpenAction, addStreamSourceFile, filePath]);
|
}, [workingRef, alwaysConcatMultipleFiles, batchLoadPaths, setWorking, isFileOpened, batchFiles.length, userOpenSingleFile, checkFileOpened, loadEdlFile, enableAskForFileOpenAction, addStreamSourceFile, filePath]);
|
||||||
|
|
||||||
const openFilesDialog = useCallback(async () => {
|
const openFilesDialog = useCallback(async () => {
|
||||||
@ -1840,14 +1826,12 @@ function App() {
|
|||||||
}, [isFileOpened, selectedSegments]);
|
}, [isFileOpened, selectedSegments]);
|
||||||
|
|
||||||
const showIncludeExternalStreamsDialog = useCallback(async () => {
|
const showIncludeExternalStreamsDialog = useCallback(async () => {
|
||||||
try {
|
await withErrorHandling(async () => {
|
||||||
const { canceled, filePaths } = await showOpenDialog({ properties: ['openFile'], title: t('Include more tracks from other file') });
|
const { canceled, filePaths } = await showOpenDialog({ properties: ['openFile'], title: t('Include more tracks from other file') });
|
||||||
const [firstFilePath] = filePaths;
|
const [firstFilePath] = filePaths;
|
||||||
if (canceled || firstFilePath == null) return;
|
if (canceled || firstFilePath == null) return;
|
||||||
await addStreamSourceFile(firstFilePath);
|
await addStreamSourceFile(firstFilePath);
|
||||||
} catch (err) {
|
}, i18n.t('Failed to include track'));
|
||||||
handleError(err);
|
|
||||||
}
|
|
||||||
}, [addStreamSourceFile, t]);
|
}, [addStreamSourceFile, t]);
|
||||||
|
|
||||||
const toggleFullscreenVideo = useCallback(async () => {
|
const toggleFullscreenVideo = useCallback(async () => {
|
||||||
@ -1881,6 +1865,7 @@ function App() {
|
|||||||
const promptDownloadMediaUrlWrapper = useCallback(async () => {
|
const promptDownloadMediaUrlWrapper = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setWorking({ text: t('Downloading URL') });
|
setWorking({ text: t('Downloading URL') });
|
||||||
|
await withErrorHandling(async () => {
|
||||||
const newCustomOutDir = await ensureWritableOutDir({ outDir: customOutDir });
|
const newCustomOutDir = await ensureWritableOutDir({ outDir: customOutDir });
|
||||||
if (newCustomOutDir == null) {
|
if (newCustomOutDir == null) {
|
||||||
errorToast(i18n.t('Please select a working directory first'));
|
errorToast(i18n.t('Please select a working directory first'));
|
||||||
@ -1889,9 +1874,7 @@ function App() {
|
|||||||
const outPath = getDownloadMediaOutPath(newCustomOutDir, `downloaded-media-${Date.now()}.mkv`);
|
const outPath = getDownloadMediaOutPath(newCustomOutDir, `downloaded-media-${Date.now()}.mkv`);
|
||||||
const downloaded = await promptDownloadMediaUrl(outPath);
|
const downloaded = await promptDownloadMediaUrl(outPath);
|
||||||
if (downloaded) await loadMedia({ filePath: outPath });
|
if (downloaded) await loadMedia({ filePath: outPath });
|
||||||
} catch (err) {
|
}, i18n.t('Failed to download URL'));
|
||||||
if (err instanceof DirectoryAccessDeclinedError) return;
|
|
||||||
handleError(err);
|
|
||||||
} finally {
|
} finally {
|
||||||
setWorking();
|
setWorking();
|
||||||
}
|
}
|
||||||
@ -1902,7 +1885,6 @@ function App() {
|
|||||||
const mainActions = useMemo(() => {
|
const mainActions = useMemo(() => {
|
||||||
async function exportYouTube() {
|
async function exportYouTube() {
|
||||||
if (!checkFileOpened()) return;
|
if (!checkFileOpened()) return;
|
||||||
|
|
||||||
await openYouTubeChaptersDialog(formatYouTube(apparentCutSegments));
|
await openYouTubeChaptersDialog(formatYouTube(apparentCutSegments));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2186,29 +2168,24 @@ function App() {
|
|||||||
|
|
||||||
const tryExportEdlFile = useCallback(async (type: EdlExportType) => {
|
const tryExportEdlFile = useCallback(async (type: EdlExportType) => {
|
||||||
if (!checkFileOpened()) return;
|
if (!checkFileOpened()) return;
|
||||||
try {
|
await withErrorHandling(async () => {
|
||||||
await exportEdlFile({ type, cutSegments: selectedSegments, customOutDir, filePath, getFrameCount });
|
await exportEdlFile({ type, cutSegments: selectedSegments, customOutDir, filePath, getFrameCount });
|
||||||
} catch (err) {
|
}, i18n.t('Failed to export project'));
|
||||||
errorToast(i18n.t('Failed to export project'));
|
|
||||||
console.error('Failed to export project', type, err);
|
|
||||||
}
|
|
||||||
}, [checkFileOpened, customOutDir, filePath, getFrameCount, selectedSegments]);
|
}, [checkFileOpened, customOutDir, filePath, getFrameCount, selectedSegments]);
|
||||||
|
|
||||||
const importEdlFile = useCallback(async (type: EdlImportType) => {
|
const importEdlFile = useCallback(async (type: EdlImportType) => {
|
||||||
if (!checkFileOpened()) return;
|
if (!checkFileOpened()) return;
|
||||||
|
|
||||||
try {
|
await withErrorHandling(async () => {
|
||||||
const edl = await askForEdlImport({ type, fps: detectedFps });
|
const edl = await askForEdlImport({ type, fps: detectedFps });
|
||||||
if (edl.length > 0) loadCutSegments(edl, true);
|
if (edl.length > 0) loadCutSegments(edl, true);
|
||||||
} catch (err) {
|
}, i18n.t('Failed to import project file'));
|
||||||
handleError(err);
|
|
||||||
}
|
|
||||||
}, [checkFileOpened, detectedFps, loadCutSegments]);
|
}, [checkFileOpened, detectedFps, loadCutSegments]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const openFiles = (filePaths: string[]) => { userOpenFiles(filePaths.map((p) => resolvePathIfNeeded(p))); };
|
const openFiles = (filePaths: string[]) => { userOpenFiles(filePaths.map((p) => resolvePathIfNeeded(p))); };
|
||||||
|
|
||||||
async function actionWithCatch(fn: () => void) {
|
async function actionWithCatch(fn: () => Promise<void>) {
|
||||||
try {
|
try {
|
||||||
await fn();
|
await fn();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -544,8 +544,8 @@ export async function showConcatFailedDialog({ fileFormat }: { fileFormat: strin
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openYouTubeChaptersDialog(text: string) {
|
export async function openYouTubeChaptersDialog(text: string) {
|
||||||
ReactSwal.fire({
|
await ReactSwal.fire({
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
title: i18n.t('YouTube Chapters'),
|
title: i18n.t('YouTube Chapters'),
|
||||||
html: (
|
html: (
|
||||||
|
@ -11,6 +11,7 @@ import { isDurationValid } from './segments';
|
|||||||
import { FFprobeChapter, FFprobeFormat, FFprobeProbeResult, FFprobeStream } from '../../../ffprobe';
|
import { FFprobeChapter, FFprobeFormat, FFprobeProbeResult, FFprobeStream } from '../../../ffprobe';
|
||||||
import { parseSrt, parseSrtToSegments } from './edlFormats';
|
import { parseSrt, parseSrtToSegments } from './edlFormats';
|
||||||
import { CopyfileStreams, LiteFFprobeStream } from './types';
|
import { CopyfileStreams, LiteFFprobeStream } from './types';
|
||||||
|
import { UnsupportedFileError } from '../errors';
|
||||||
|
|
||||||
const { pathExists } = window.require('fs-extra');
|
const { pathExists } = window.require('fs-extra');
|
||||||
|
|
||||||
@ -368,7 +369,7 @@ export async function readFileMeta(filePath: string) {
|
|||||||
return { format, streams, chapters };
|
return { format, streams, chapters };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isExecaError(err)) {
|
if (isExecaError(err)) {
|
||||||
throw Object.assign(new Error(`Unsupported file: ${err.message}`), { code: 'LLC_FFPROBE_UNSUPPORTED_FILE' });
|
throw new UnsupportedFileError(err.message);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,9 @@ import invariant from 'tiny-invariant';
|
|||||||
import { getOutDir, getFileDir, checkDirWriteAccess, dirExists, isMasBuild } from '../util';
|
import { getOutDir, getFileDir, checkDirWriteAccess, dirExists, isMasBuild } from '../util';
|
||||||
import { askForOutDir, askForInputDir } from '../dialogs';
|
import { askForOutDir, askForInputDir } from '../dialogs';
|
||||||
import { errorToast } from '../swal';
|
import { errorToast } from '../swal';
|
||||||
|
import { DirectoryAccessDeclinedError } from '../../errors';
|
||||||
// import isDev from '../isDev';
|
// import isDev from '../isDev';
|
||||||
|
|
||||||
export class DirectoryAccessDeclinedError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.name = 'DirectoryAccessDeclinedError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MacOS App Store sandbox doesn't allow reading/writing anywhere,
|
// MacOS App Store sandbox doesn't allow reading/writing anywhere,
|
||||||
// except those exact file paths that have been explicitly drag-dropped into LosslessCut or opened using the opener dialog
|
// except those exact file paths that have been explicitly drag-dropped into LosslessCut or opened using the opener dialog
|
||||||
|
@ -10,6 +10,8 @@ import isDev from './isDev';
|
|||||||
import Swal, { errorToast, toast } from './swal';
|
import Swal, { errorToast, toast } from './swal';
|
||||||
import { ffmpegExtractWindow } from './util/constants';
|
import { ffmpegExtractWindow } from './util/constants';
|
||||||
import { appName } from '../../main/common';
|
import { appName } from '../../main/common';
|
||||||
|
import { DirectoryAccessDeclinedError, UnsupportedFileError } from '../errors';
|
||||||
|
import { Html5ifyMode } from '../../../types';
|
||||||
|
|
||||||
const { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
|
const { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
|
||||||
const fsExtra = window.require('fs-extra');
|
const fsExtra = window.require('fs-extra');
|
||||||
@ -159,29 +161,6 @@ export async function transferTimestamps({ inPath, outPath, cutFrom = 0, cutTo =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleError(arg1: unknown, arg2?: unknown) {
|
|
||||||
console.error('handleError', arg1, arg2);
|
|
||||||
|
|
||||||
let err: Error | undefined;
|
|
||||||
let str: string | undefined;
|
|
||||||
|
|
||||||
if (typeof arg1 === 'string') str = arg1;
|
|
||||||
else if (typeof arg2 === 'string') str = arg2;
|
|
||||||
|
|
||||||
if (arg1 instanceof Error) err = arg1;
|
|
||||||
else if (arg2 instanceof Error) err = arg2;
|
|
||||||
|
|
||||||
if (err != null && 'code' in err && err.code === 'LLC_FFPROBE_UNSUPPORTED_FILE') {
|
|
||||||
errorToast(i18n.t('Unsupported file'));
|
|
||||||
} else {
|
|
||||||
Swal.fire({
|
|
||||||
icon: 'error',
|
|
||||||
title: str || i18n.t('An error has occurred.'),
|
|
||||||
text: err?.message ? err?.message.slice(0, 300) : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function filenamify(name: string) {
|
export function filenamify(name: string) {
|
||||||
return name.replaceAll(/[^\w.-]/g, '_');
|
return name.replaceAll(/[^\w.-]/g, '_');
|
||||||
}
|
}
|
||||||
@ -193,7 +172,7 @@ export function withBlur(cb) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dragPreventer(ev) {
|
export function dragPreventer(ev: DragEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +216,7 @@ export const resolvePathIfNeeded = (inPath: string) => (isAbsolute(inPath) ? inP
|
|||||||
export const html5ifiedPrefix = 'html5ified-';
|
export const html5ifiedPrefix = 'html5ified-';
|
||||||
export const html5dummySuffix = 'dummy';
|
export const html5dummySuffix = 'dummy';
|
||||||
|
|
||||||
export async function findExistingHtml5FriendlyFile(fp, cod) {
|
export async function findExistingHtml5FriendlyFile(fp: string, cod: string | undefined) {
|
||||||
// The order is the priority we will search:
|
// The order is the priority we will search:
|
||||||
const suffixes = ['slowest', 'slow-audio', 'slow', 'fast-audio-remux', 'fast-audio', 'fast', html5dummySuffix];
|
const suffixes = ['slowest', 'slow-audio', 'slow', 'fast-audio-remux', 'fast-audio', 'fast', html5dummySuffix];
|
||||||
const prefix = getSuffixedFileName(fp, html5ifiedPrefix);
|
const prefix = getSuffixedFileName(fp, html5ifiedPrefix);
|
||||||
@ -270,7 +249,7 @@ export async function findExistingHtml5FriendlyFile(fp, cod) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHtml5ifiedPath(cod: string | undefined, fp, type) {
|
export function getHtml5ifiedPath(cod: string | undefined, fp: string, type: Html5ifyMode) {
|
||||||
// See also inside ffmpegHtml5ify
|
// See also inside ffmpegHtml5ify
|
||||||
const ext = (isMac && ['slowest', 'slow', 'slow-audio'].includes(type)) ? 'mp4' : 'mkv';
|
const ext = (isMac && ['slowest', 'slow', 'slow-audio'].includes(type)) ? 'mp4' : 'mkv';
|
||||||
return getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: `${html5ifiedPrefix}${type}.${ext}` });
|
return getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: `${html5ifiedPrefix}${type}.${ext}` });
|
||||||
@ -335,6 +314,55 @@ export const isMuxNotSupported = (err: InvariantExecaError) => (
|
|||||||
&& /Could not write header .*incorrect codec parameters .*Invalid argument/.test(getStdioString(err.stderr) ?? '')
|
&& /Could not write header .*incorrect codec parameters .*Invalid argument/.test(getStdioString(err.stderr) ?? '')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export function handleError(arg1: unknown, arg2?: unknown) {
|
||||||
|
console.error('handleError', arg1, arg2);
|
||||||
|
|
||||||
|
let err: Error | undefined;
|
||||||
|
let str: string | undefined;
|
||||||
|
|
||||||
|
if (typeof arg1 === 'string') str = arg1;
|
||||||
|
else if (typeof arg2 === 'string') str = arg2;
|
||||||
|
|
||||||
|
if (arg1 instanceof Error) err = arg1;
|
||||||
|
else if (arg2 instanceof Error) err = arg2;
|
||||||
|
|
||||||
|
if (err instanceof UnsupportedFileError) {
|
||||||
|
errorToast(i18n.t('Unsupported file'));
|
||||||
|
} else {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: str || i18n.t('An error has occurred.'),
|
||||||
|
text: err?.message ? err?.message.slice(0, 300) : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run an operation with error handling
|
||||||
|
*/
|
||||||
|
export async function withErrorHandling(operation: () => Promise<void>, errorMsgOrFn?: string | ((err: unknown) => string)) {
|
||||||
|
try {
|
||||||
|
await operation();
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof DirectoryAccessDeclinedError || isAbortedError(err)) return;
|
||||||
|
|
||||||
|
if (err instanceof UnsupportedFileError) {
|
||||||
|
errorToast(i18n.t('Unsupported file'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let errorMsg: string | undefined;
|
||||||
|
if (typeof errorMsgOrFn === 'string') errorMsg = errorMsgOrFn;
|
||||||
|
if (typeof errorMsgOrFn === 'function') errorMsg = errorMsgOrFn(err);
|
||||||
|
if (errorMsg != null) {
|
||||||
|
console.error(errorMsg, err);
|
||||||
|
errorToast(errorMsg);
|
||||||
|
} else {
|
||||||
|
handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkAppPath() {
|
export async function checkAppPath() {
|
||||||
try {
|
try {
|
||||||
const forceCheck = false;
|
const forceCheck = false;
|
||||||
@ -364,10 +392,10 @@ export async function checkAppPath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/2450976/6519037
|
// https://stackoverflow.com/a/2450976/6519037
|
||||||
export function shuffleArray(arrayIn) {
|
export function shuffleArray<T>(arrayIn: T[]) {
|
||||||
const array = [...arrayIn];
|
const array = [...arrayIn];
|
||||||
let currentIndex = array.length;
|
let currentIndex = array.length;
|
||||||
let randomIndex;
|
let randomIndex: number;
|
||||||
|
|
||||||
// While there remain elements to shuffle...
|
// While there remain elements to shuffle...
|
||||||
while (currentIndex !== 0) {
|
while (currentIndex !== 0) {
|
||||||
@ -377,23 +405,24 @@ export function shuffleArray(arrayIn) {
|
|||||||
|
|
||||||
// And swap it with the current element.
|
// And swap it with the current element.
|
||||||
[array[currentIndex], array[randomIndex]] = [
|
[array[currentIndex], array[randomIndex]] = [
|
||||||
array[randomIndex], array[currentIndex]];
|
array[randomIndex]!, array[currentIndex]!,
|
||||||
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||||
export function escapeRegExp(string) {
|
export function escapeRegExp(str: string) {
|
||||||
// eslint-disable-next-line unicorn/better-regex
|
// eslint-disable-next-line unicorn/better-regex
|
||||||
return string.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
return str.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const readFileSize = async (path) => (await stat(path)).size;
|
export const readFileSize = async (path: string) => (await stat(path)).size;
|
||||||
|
|
||||||
export const readFileSizes = (paths) => pMap(paths, async (path) => readFileSize(path), { concurrency: 5 });
|
export const readFileSizes = (paths: string[]) => pMap(paths, async (path) => readFileSize(path), { concurrency: 5 });
|
||||||
|
|
||||||
export function checkFileSizes(inputSize, outputSize) {
|
export function checkFileSizes(inputSize: number, outputSize: number) {
|
||||||
const diff = Math.abs(outputSize - inputSize);
|
const diff = Math.abs(outputSize - inputSize);
|
||||||
const relDiff = diff / inputSize;
|
const relDiff = diff / inputSize;
|
||||||
const maxDiffPercent = 5;
|
const maxDiffPercent = 5;
|
||||||
|
Loading…
Reference in New Issue
Block a user