From 25110400002cc605a7ba1bd5da48eb46116d729d Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 4 Nov 2025 22:45:22 +0800 Subject: [PATCH] i18n more errors --- src/renderer/errors.ts | 7 +++++++ src/renderer/src/App.tsx | 12 ++++++------ src/renderer/src/SegmentList.tsx | 7 ++++--- src/renderer/src/components/GpsMap.tsx | 4 +++- src/renderer/src/contexts.ts | 5 +++-- src/renderer/src/edlFormats.ts | 7 ++++--- src/renderer/src/ffmpeg.ts | 18 +++++++++--------- src/renderer/src/hooks/useFfmpegOperations.ts | 6 ++++-- src/renderer/src/hooks/useSegments.tsx | 19 ++++++++++--------- src/renderer/src/hooks/useUserSettings.ts | 3 ++- src/renderer/src/segments.ts | 4 ++-- src/renderer/src/smartcut.ts | 5 ++++- src/renderer/src/util.ts | 8 +++++--- src/renderer/src/util/outputNameTemplate.ts | 3 ++- 14 files changed, 65 insertions(+), 43 deletions(-) diff --git a/src/renderer/errors.ts b/src/renderer/errors.ts index 8f1e5112..3a8daa6d 100644 --- a/src/renderer/errors.ts +++ b/src/renderer/errors.ts @@ -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'; + } +} diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 3a1378df..650bae60 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -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); diff --git a/src/renderer/src/SegmentList.tsx b/src/renderer/src/SegmentList.tsx index a8a31678..ad666a26 100644 --- a/src/renderer/src/SegmentList.tsx +++ b/src/renderer/src/SegmentList.tsx @@ -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]); diff --git a/src/renderer/src/components/GpsMap.tsx b/src/renderer/src/components/GpsMap.tsx index 45883e10..4fbf1323 100644 --- a/src/renderer/src/components/GpsMap.tsx +++ b/src/renderer/src/components/GpsMap.tsx @@ -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); diff --git a/src/renderer/src/contexts.ts b/src/renderer/src/contexts.ts index 5d43dcd9..ebc6eebc 100644 --- a/src/renderer/src/contexts.ts +++ b/src/renderer/src/contexts.ts @@ -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(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; }; diff --git a/src/renderer/src/edlFormats.ts b/src/renderer/src/edlFormats.ts index 1a86135e..0be1ec32 100644 --- a/src/renderer/src/edlFormats.ts +++ b/src/renderer/src/edlFormats.ts @@ -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; } diff --git a/src/renderer/src/ffmpeg.ts b/src/renderer/src/ffmpeg.ts index d8542d1d..2bcd906c 100644 --- a/src/renderer/src/ffmpeg.ts +++ b/src/renderer/src/ffmpeg.ts @@ -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; diff --git a/src/renderer/src/hooks/useFfmpegOperations.ts b/src/renderer/src/hooks/useFfmpegOperations.ts index 6b02b7d7..b8dd1ab1 100644 --- a/src/renderer/src/hooks/useFfmpegOperations.ts +++ b/src/renderer/src/hooks/useFfmpegOperations.ts @@ -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 = [ diff --git a/src/renderer/src/hooks/useSegments.tsx b/src/renderer/src/hooks/useSegments.tsx index a4b4af55..a4fdfdfd 100644 --- a/src/renderer/src/hooks/useSegments.tsx +++ b/src/renderer/src/hooks/useSegments.tsx @@ -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> = {}; 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; diff --git a/src/renderer/src/hooks/useUserSettings.ts b/src/renderer/src/hooks/useUserSettings.ts index ee2d60fb..e626fce3 100644 --- a/src/renderer/src/hooks/useUserSettings.ts +++ b/src/renderer/src/hooks/useUserSettings.ts @@ -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; }; diff --git a/src/renderer/src/segments.ts b/src/renderer/src/segments.ts index 56a29039..15e2de88 100644 --- a/src/renderer/src/segments.ts +++ b/src/renderer/src/segments.ts @@ -53,7 +53,7 @@ export const sortSegments = (segments: T[]) => sort // https://stackoverflow.com/a/30472982/6519037 export function partitionIntoOverlappingRanges(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(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]); }; diff --git a/src/renderer/src/smartcut.ts b/src/renderer/src/smartcut.ts index 0ec6bd5c..63e78f38 100644 --- a/src/renderer/src/smartcut.ts +++ b/src/renderer/src/smartcut.ts @@ -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 }); diff --git a/src/renderer/src/util.ts b/src/renderer/src/util.ts index a2e8610c..4794f8a0 100644 --- a/src/renderer/src/util.ts +++ b/src/renderer/src/util.ts @@ -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; } diff --git a/src/renderer/src/util/outputNameTemplate.ts b/src/renderer/src/util/outputNameTemplate.ts index 36cb6348..28c67443 100644 --- a/src/renderer/src/util/outputNameTemplate.ts +++ b/src/renderer/src/util/outputNameTemplate.ts @@ -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; }