i18n more errors

pull/2599/head
Mikael Finstad 9 months ago
parent 2511049d06
commit 2511040000
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -11,3 +11,10 @@ export class UnsupportedFileError extends Error {
this.name = 'UnsupportedFileError';
}
}
export class UserFacingError extends Error {
constructor(message: string) {
super(message);
this.name = 'UserFacingError';
}
}

@ -100,7 +100,7 @@ import useSubtitles from './hooks/useSubtitles';
import useStreamsMeta from './hooks/useStreamsMeta';
import { bottomStyle, videoStyle } from './styles';
import styles from './App.module.css';
import { DirectoryAccessDeclinedError } from '../errors';
import { DirectoryAccessDeclinedError, UserFacingError } from '../errors';
import SwalContainer from './components/SwalContainer';
import ErrorDialog from './components/ErrorDialog';
import useErrorHandling from './hooks/useErrorHandling';
@ -405,7 +405,7 @@ function App() {
index += 1;
if (index >= captureFormats.length) index = 0;
const newCaptureFormat = captureFormats[index];
if (newCaptureFormat == null) throw new Error();
invariant(newCaptureFormat != null);
return newCaptureFormat;
}), [setCaptureFormat]);
@ -1228,7 +1228,7 @@ function App() {
await withErrorHandling(async () => {
const currentTime = getRelevantTime();
const video = videoRef.current;
if (video == null) throw new Error();
invariant(video != null);
const usingFfmpeg = usingPreviewFile || captureFrameMethod === 'ffmpeg';
const outPath = usingFfmpeg
? await captureFrameFromFfmpeg({ customOutDir, filePath, time: currentTime, captureFormat, quality: captureFrameQuality })
@ -1711,7 +1711,7 @@ function App() {
return [...existingFiles, ...mapPathsToFiles(newUniquePaths)];
}
const [firstNewPath] = newPaths;
if (firstNewPath == null) throw new Error();
invariant(firstNewPath != null);
setSelectedBatchFiles([firstNewPath]);
return mapPathsToFiles(newPaths);
});
@ -1884,7 +1884,7 @@ function App() {
console.warn('No video tag to full screen');
return;
}
if (videoContainerRef.current == null) throw new Error('videoContainerRef.current == null');
invariant(videoContainerRef.current != null);
await screenfull.toggle(videoContainerRef.current, { navigationUI: 'hide' });
} catch (err) {
console.error('Failed to toggle fullscreen', err);
@ -2207,7 +2207,7 @@ function App() {
console.log('Trying to create preview');
if (!isDurationValid(await getDuration(filePath))) throw new Error('Invalid duration');
if (!isDurationValid(await getDuration(filePath))) throw new UserFacingError(i18n.t('Invalid duration'));
if (hasVideo || hasAudio) {
await html5ifyAndLoadWithPreferences(customOutDir, filePath, 'fastest', hasVideo, hasAudio);

@ -1,5 +1,5 @@
import { memo, useMemo, useRef, useCallback, useState, SetStateAction, Dispatch, MouseEventHandler, CSSProperties, useEffect } from 'react';
import { FaYinYang, FaSave, FaPlus, FaMinus, FaTag, FaSortNumericDown, FaAngleRight, FaRegCheckCircle, FaRegCircle, FaTimes } from 'react-icons/fa';
import { FaYinYang, FaSave, FaPlus, FaMinus, FaTag, FaSortNumericDown, FaRegCheckCircle, FaRegCircle, FaTimes } from 'react-icons/fa';
import { AiOutlineSplitCells } from 'react-icons/ai';
import { motion } from 'framer-motion';
import { useTranslation, Trans } from 'react-i18next';
@ -8,6 +8,7 @@ import { SortableContext, verticalListSortingStrategy, arrayMove, useSortable }
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { useVirtualizer } from '@tanstack/react-virtual';
import { CSS } from '@dnd-kit/utilities';
import invariant from 'tiny-invariant';
import useContextMenu from './hooks/useContextMenu';
import useUserSettings from './hooks/useUserSettings';
@ -479,7 +480,7 @@ function SegmentList({
})), [setEditingSegmentTags]);
const onTagReset = useCallback((tag: string) => setEditingSegmentTags((tags) => {
if (tags == null) throw new Error();
invariant(tags != null);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [tag]: deleted, ...rest } = tags;
return rest;
@ -491,7 +492,7 @@ function SegmentList({
}, [setEditingSegmentTags, setEditingSegmentTagsSegmentIndex]);
const onSegmentTagsConfirm = useCallback(() => {
if (editingSegmentTagsSegmentIndex == null) throw new Error();
invariant(editingSegmentTagsSegmentIndex != null);
updateSegAtIndex(editingSegmentTagsSegmentIndex, { tags: editingSegmentTags });
onSegmentTagsCloseComplete();
}, [editingSegmentTags, editingSegmentTagsSegmentIndex, onSegmentTagsCloseComplete, updateSegAtIndex]);

@ -3,11 +3,13 @@ import { MapContainer, Popup, TileLayer } from 'react-leaflet';
import { Marker } from '@adamscybot/react-leaflet-component-marker';
import 'leaflet/dist/leaflet.css';
import { FaMapMarkerAlt } from 'react-icons/fa';
import i18n from 'i18next';
import { extractSrtGpsTrack } from '../ffmpeg';
import { parseDjiGps1, parseDjiGps2 } from '../edlFormats';
import * as Dialog from './Dialog';
import { useAppContext } from '../contexts';
import { UserFacingError } from '../../errors';
// https://www.openstreetmap.org/copyright
@ -52,7 +54,7 @@ export default function GpsMap({ filePath, streamIndex }: {
}
if (gpsPoints.length === 0) {
throw new Error('No GPS points found');
throw new UserFacingError(i18n.t('No GPS points found'));
}
setPoints(gpsPoints);

@ -1,5 +1,6 @@
import React, { useContext } from 'react';
import Color from 'color';
import invariant from 'tiny-invariant';
import { UserSettingsRoot } from './hooks/useUserSettingsRoot';
import { ExportMode, SegmentColorIndex } from './types';
@ -38,12 +39,12 @@ export const AppContext = React.createContext<AppContextType | undefined>(undefi
export function useAppContext() {
const context = useContext(AppContext);
if (context == null) throw new Error('AppContext nullish');
invariant(context != null);
return context;
}
export const useSegColors = () => {
const context = useContext(SegColorsContext);
if (context == null) throw new Error('SegColorsContext nullish');
invariant(context != null);
return context;
};

@ -13,6 +13,7 @@ import { formatDuration } from './util/duration';
import { invertSegments, sortSegments } from './segments';
import { GetFrameCount, SegmentBase, SegmentTags } from './types';
import parseCmx3600 from './cmx3600';
import { UserFacingError } from '../errors';
export const getTimeFromFrameNum = (detectedFps: number, frameNum: number) => frameNum / detectedFps;
@ -57,7 +58,7 @@ const csvHeader = [
export function parseCsv(csvStr: string, parseTimeFn: (a: string) => number | undefined) {
const rows: string[][] = csvParse(csvStr, {});
if (rows.length === 0) throw new Error(i18n.t('No rows found'));
if (rows.length === 0) throw new UserFacingError(i18n.t('No rows found'));
invariant(rows.every((row) => row.length > 0), 'One row had no columns.');
// from header
@ -99,7 +100,7 @@ export function parseCsv(csvStr: string, parseTimeFn: (a: string) => number | un
&& (end === undefined || !Number.isNaN(end))
))) {
console.log(mapped);
throw new Error(i18n.t('Invalid start or end value. Must contain a number of seconds'));
throw new UserFacingError(i18n.t('Invalid start or end value. Must contain a number of seconds'));
}
return mapped;
@ -194,7 +195,7 @@ export async function parseMplayerEdl(text: string) {
...map(sceneMarkers, 'Scene Marker', 2),
...map(commercialBreaks, 'Commercial Break', 3),
];
if (out.length === 0) throw new Error(i18n.t('Invalid EDL data found'));
if (out.length === 0) throw new UserFacingError(i18n.t('Invalid EDL data found'));
return out;
}

@ -10,7 +10,7 @@ import { isExecaError } from './util';
import { isDurationValid } from './segments';
import { FFprobeChapter, FFprobeFormat, FFprobeProbeResult, FFprobeStream } from '../../../ffprobe';
import { parseSrt, parseSrtToSegments } from './edlFormats';
import { UnsupportedFileError } from '../errors';
import { UnsupportedFileError, UserFacingError } from '../errors';
const { ffmpeg, fileTypePromise } = window.require('@electron/remote').require('./index.js');
@ -86,7 +86,7 @@ export async function readFrames({ filePath, from, to, streamIndex }: {
}
export async function readFramesAroundTime({ filePath, streamIndex, aroundTime, window }: { filePath: string, streamIndex: number, aroundTime: number, window: number }) {
if (aroundTime == null) throw new Error('aroundTime was nullish');
invariant(aroundTime != null);
const { from, to } = getIntervalAroundTime(aroundTime, window);
return readFrames({ filePath, from, to, streamIndex });
}
@ -142,12 +142,12 @@ export function getSafeCutTime(frames: Frame[], cutTime: number, nextMode: boole
let index: number;
if (frames.length < 2) throw new Error(i18n.t('Less than 2 frames found'));
if (frames.length < 2) throw new UserFacingError(i18n.t('Less than 2 frames found'));
if (nextMode) {
index = frames.findIndex((f) => f.keyframe && f.time >= cutTime - sigma);
if (index === -1) throw new Error(i18n.t('Failed to find next keyframe'));
if (index >= frames.length - 1) throw new Error(i18n.t('We are on the last frame'));
if (index === -1) throw new UserFacingError(i18n.t('Failed to find next keyframe'));
if (index >= frames.length - 1) throw new UserFacingError(i18n.t('We are on the last frame'));
const { time } = frames[index]!;
if (isCloseTo(time, cutTime)) {
return undefined; // Already on keyframe, no need to modify cut time
@ -163,8 +163,8 @@ export function getSafeCutTime(frames: Frame[], cutTime: number, nextMode: boole
};
index = findReverseIndex(frames, (f) => f.time <= cutTime + sigma);
if (index === -1) throw new Error(i18n.t('Failed to find any prev frame'));
if (index === 0) throw new Error(i18n.t('We are on the first frame'));
if (index === -1) throw new UserFacingError(i18n.t('Failed to find any prev frame'));
if (index === 0) throw new UserFacingError(i18n.t('We are on the first frame'));
if (index === frames.length - 1) {
// Last frame of video, no need to modify cut time
@ -177,8 +177,8 @@ export function getSafeCutTime(frames: Frame[], cutTime: number, nextMode: boole
// We are not on a frame before keyframe, look for preceding keyframe instead
index = findReverseIndex(frames, (f) => f.keyframe && f.time <= cutTime + sigma);
if (index === -1) throw new Error(i18n.t('Failed to find any prev keyframe'));
if (index === 0) throw new Error(i18n.t('We are on the first keyframe'));
if (index === -1) throw new UserFacingError(i18n.t('Failed to find any prev keyframe'));
if (index === 0) throw new UserFacingError(i18n.t('We are on the first keyframe'));
// Use frame before the found keyframe
return frames[index - 1]!.time;

@ -3,6 +3,7 @@ import flatMap from 'lodash/flatMap';
import sum from 'lodash/sum';
import pMap from 'p-map';
import invariant from 'tiny-invariant';
import i18n from 'i18next';
import { getSuffixedOutPath, transferTimestamps, getOutFileExtension, getOutDir, deleteDispositionValue, getHtml5ifiedPath, unlinkWithRetry, getFrameDuration, isMac } from '../util';
import { isCuttingStart, isCuttingEnd, runFfmpegWithProgress, getFfCommandLine, getDuration, createChaptersFromSegments, readFileMeta, getExperimentalArgs, getVideoTimescaleArgs, logStdoutStderr, runFfmpegConcat, RefuseOverwriteError, runFfmpeg } from '../ffmpeg';
@ -13,6 +14,7 @@ import { FFprobeStream } from '../../../../ffprobe';
import { AvoidNegativeTs, Html5ifyMode, PreserveMetadata } from '../../../../types';
import { AllFilesMeta, Chapter, CopyfileStreams, CustomTagsByFile, LiteFFprobeStream, ParamsByStreamId, SegmentToExport } from '../types';
import { LossyMode } from '../../../main';
import { UserFacingError } from '../../errors';
const { join, resolve, dirname } = window.require('path');
const { writeFile, mkdir, access, constants: { F_OK, W_OK } } = window.require('fs/promises');
@ -596,7 +598,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
}
const { losslessCutFrom, segmentNeedsSmartCut } = await needsSmartCut({ path: filePath, desiredCutFrom, videoStream });
if (segmentNeedsSmartCut && !detectedFps) throw new Error('Smart cut is not possible when FPS is unknown');
if (segmentNeedsSmartCut && !detectedFps) throw new UserFacingError(i18n.t('Smart cut is not possible when FPS is unknown'));
console.log('Smart cut on video stream', videoStream.index);
// If we are cutting within two keyframes, just encode the whole part and return that
@ -951,7 +953,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
const outPaths = await pMap(streams, async ({ index, codec_name: codec, codec_type: type }) => {
const ext = codec || 'bin';
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` });
if (outPath == null) throw new Error();
invariant(outPath != null);
if (!enableOverwriteOutput && await pathExists(outPath)) throw new RefuseOverwriteError();
streamArgs = [

@ -25,6 +25,7 @@ import ExpressionDialog from '../components/ExpressionDialog';
import Button, { DialogButton } from '../components/Button';
import { ButtonRow } from '../components/Dialog';
import * as AlertDialog from '../components/AlertDialog';
import { UserFacingError } from '../../errors';
const remote = window.require('@electron/remote');
const { shell } = remote;
@ -151,9 +152,9 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
clampDuration?: number | undefined,
getNextCurrentSegIndex?: (newEdl: SegmentBase[]) => number,
}) => {
if (segments.length === 0) throw new Error(i18n.t('No valid segments found'));
if (segments.length === 0) throw new UserFacingError(i18n.t('No valid segments found'));
if (segments.length > maxSegmentsAllowed) throw new Error(i18n.t('Tried to create too many segments (max {{maxSegmentsAllowed}}.)', { maxSegmentsAllowed }));
if (segments.length > maxSegmentsAllowed) throw new UserFacingError(i18n.t('Tried to create too many segments (max {{maxSegmentsAllowed}}.)', { maxSegmentsAllowed }));
if (!append) clearSegColorCounter();
@ -435,7 +436,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
if (index < 0) return;
const cutSegmentsNew = [...cutSegments];
const existing = cutSegments[index];
if (existing == null) throw new Error();
invariant(existing != null);
cutSegmentsNew.splice(index, 1, { ...existing, ...newProps });
safeSetCutSegments(cutSegmentsNew, fileDuration);
}, [cutSegments, safeSetCutSegments, fileDuration]);
@ -497,7 +498,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
invariant(filePath != null);
if (time != null) {
const keyframe = await findKeyframeNearTime({ filePath, streamIndex: videoStream.index, time, mode });
if (keyframe == null) throw new Error(`Cannot find any keyframe within 60 seconds of frame ${time}`);
if (keyframe == null) throw new UserFacingError(i18n.t('Cannot find any keyframe within 60 seconds of frame {{time}}', { time }));
newSegment[key] = keyframe;
}
};
@ -516,7 +517,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
if (newOrder > cutSegments.length - 1 || newOrder < 0) return;
const newSegments = [...cutSegments];
const removedSeg = newSegments.splice(index, 1)[0];
if (removedSeg == null) throw new Error();
invariant(removedSeg != null);
newSegments.splice(newOrder, 0, removedSeg);
safeSetCutSegments(newSegments);
setCurrentSegIndex(newOrder);
@ -787,20 +788,20 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
invariant(typeof response === 'object' && response != null, i18n.t('The expression must return an object'));
const ret: Partial<Pick<StateSegment, 'name' | 'start' | 'end' | 'tags'>> = {};
if ('label' in response) {
if (typeof response.label !== 'string') throw new Error(i18n.t('"{{property}}" must be a string', { property: 'label' }));
if (typeof response.label !== 'string') throw new UserFacingError(i18n.t('"{{property}}" must be a string', { property: 'label' }));
ret.name = response.label;
}
if ('start' in response) {
if (typeof response.start !== 'number') throw new Error(i18n.t('"{{property}}" must be a number', { property: 'start' }));
if (typeof response.start !== 'number') throw new UserFacingError(i18n.t('"{{property}}" must be a number', { property: 'start' }));
ret.start = response.start;
}
if ('end' in response) {
if (!(typeof response.end === 'number' || response.end === undefined)) throw new Error(i18n.t('"{{property}}" must be a number', { property: 'end' }));
if (!(typeof response.end === 'number' || response.end === undefined)) throw new UserFacingError(i18n.t('"{{property}}" must be a number', { property: 'end' }));
ret.end = response.end;
}
if ('tags' in response) {
const tags = segmentTagsSchema.safeParse(response.tags);
if (!tags.success) throw new Error(i18n.t('"{{property}}" must be an object of strings', { property: 'tags' }));
if (!tags.success) throw new UserFacingError(i18n.t('"{{property}}" must be an object of strings', { property: 'tags' }));
ret.tags = tags.data;
}
return ret;

@ -1,10 +1,11 @@
import { useContext } from 'react';
import invariant from 'tiny-invariant';
import { UserSettingsContext } from '../contexts';
export default () => {
const context = useContext(UserSettingsContext);
if (context == null) throw new Error('UserSettingsContext nullish');
invariant(context != null);
return context;
};

@ -53,7 +53,7 @@ export const sortSegments = <T extends { start: number }>(segments: T[]) => sort
// https://stackoverflow.com/a/30472982/6519037
export function partitionIntoOverlappingRanges<T extends SegmentBase>(array: T[]) {
const [firstItem] = array;
if (firstItem == null) throw new Error('No segments');
invariant(firstItem != null);
const ret: T[][] = [
[firstItem],
@ -68,7 +68,7 @@ export function partitionIntoOverlappingRanges<T extends SegmentBase>(array: T[]
if (getSegmentEnd(a) > getSegmentEnd(b)) return -1;
return 0;
});
if (array2[0] == null) throw new Error();
invariant(array2[0] != null);
return getSegmentEnd(array2[0]);
};

@ -1,7 +1,10 @@
import i18n from 'i18next';
import { getRealVideoStreams, getVideoTimebase } from './util/streams';
import { readKeyframesAroundTime, findNextKeyframe, findKeyframeAtExactTime } from './ffmpeg';
import { FFprobeStream } from '../../../ffprobe';
import { UserFacingError } from '../errors';
const { stat } = window.require('fs-extra');
@ -34,7 +37,7 @@ export async function needsSmartCut({ path, desiredCutFrom, videoStream }: {
keyframes = await readKeyframes(60);
nextKeyframe = findNextKeyframe(keyframes, desiredCutFrom);
}
if (nextKeyframe == null) throw new Error('Cannot find any keyframe after the desired start cut point');
if (nextKeyframe == null) throw new UserFacingError(i18n.t('Cannot find any keyframe after the desired start cut point'));
console.log('Smart cut from keyframe', { keyframe: nextKeyframe.time, desiredCutFrom });

@ -6,12 +6,14 @@ import sortBy from 'lodash/sortBy';
import pRetry, { Options } from 'p-retry';
import { ExecaError } from 'execa';
import confetti from 'canvas-confetti';
import invariant from 'tiny-invariant';
import isDev from './isDev';
import { ffmpegExtractWindow } from './util/constants';
import { appName } from '../../main/common';
import { Html5ifyMode } from '../../../types';
import getSwal from './swal';
import { UserFacingError } from '../errors';
const { dirname, parse: parsePath, join, extname, isAbsolute, resolve, basename } = window.require('path');
const fsExtra = window.require('fs-extra');
@ -234,7 +236,7 @@ export async function findExistingHtml5FriendlyFile(fp: string, cod: string | un
const prefix = getSuffixedFileName(fp, html5ifiedPrefix);
const outDir = getOutDir(cod, fp);
if (outDir == null) throw new Error();
invariant(outDir != null);
const dirEntries = await readdir(outDir);
const html5ifiedDirEntries = dirEntries.filter((entry) => entry.startsWith(prefix));
@ -454,7 +456,7 @@ export async function readVideoTs(videoTsPath: string) {
const files = await readdir(videoTsPath);
const relevantFiles = files.filter((file) => /^vts_\d+_\d+\.vob$/i.test(file) && !/^vts_\d+_00\.vob$/i.test(file)); // skip menu
const ret = sortBy(relevantFiles).map((file) => join(videoTsPath, file));
if (ret.length === 0) throw new Error('No VTS vob files found in folder');
if (ret.length === 0) throw new UserFacingError(i18n.t('No VTS vob files found in folder'));
return ret;
}
@ -470,7 +472,7 @@ export async function readDirRecursively(dirPath: string) {
return [absPath];
}, { concurrency: 5 })).flat();
if (ret.length === 0) throw new Error('No files found in folder');
if (ret.length === 0) throw new UserFacingError(i18n.t('No files found in folder'));
return ret;
}

@ -9,6 +9,7 @@ import isDev from '../isDev';
import { getSegmentTags, formatSegNum, getGuaranteedSegments } from '../segments';
import { FormatTimecode, SegmentToExport } from '../types';
import safeishEval from '../worker/eval';
import { UserFacingError } from '../../errors';
export const segNumVariable = 'SEG_NUM';
@ -181,7 +182,7 @@ async function interpolateOutFileName(template: string, { epochMs, inputFileName
};
const ret = (await safeishEval(`\`${template}\``, context));
if (typeof ret !== 'string') throw new Error('Expression did not lead to a string');
if (typeof ret !== 'string') throw new UserFacingError(i18n.t('Expression did not lead to a string'));
return ret;
}

Loading…
Cancel
Save