diff --git a/src/common/util.ts b/src/common/util.ts index 687dee6b..5f0db614 100644 --- a/src/common/util.ts +++ b/src/common/util.ts @@ -9,3 +9,13 @@ export const getHwaccelArgs = (hwaccel: FfmpegHwAccel) => (hwaccel !== 'none' ? // Used to be 5, but we recently increased to 6 because https://github.com/mifi/lossless-cut/issues/2838 // I don't remember why 5 was chosen initially, but if we don't truncate, ffmpeg can sometimes give an error when too many decimal places are used in the time argument, see: export const formatFfmpegTime = (time: number) => time.toFixed(6); + +export function parseRatio(str: string, char = '/') { + const split = str.split(char); + if (split.length !== 2) return undefined; + const num = parseInt(split[0]!, 10); + const den = parseInt(split[1]!, 10); + if (Number.isNaN(num) || Number.isNaN(den)) return undefined; + if (den <= 0) return undefined; + return num / den; +} diff --git a/src/renderer/src/components/ConcatDialog.tsx b/src/renderer/src/components/ConcatDialog.tsx index 2be187d0..88c7d62b 100644 --- a/src/renderer/src/components/ConcatDialog.tsx +++ b/src/renderer/src/components/ConcatDialog.tsx @@ -2,9 +2,10 @@ import type { CSSProperties, Dispatch, SetStateAction } from 'react'; import { memo, useState, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { AiOutlineMergeCells } from 'react-icons/ai'; -import { FaQuestionCircle, FaExclamationTriangle, FaCog, FaCheck, FaNotEqual } from 'react-icons/fa'; +import { FaQuestionCircle, FaExclamationTriangle, FaCog, FaCheck } from 'react-icons/fa'; import invariant from 'tiny-invariant'; import pMap from 'p-map'; +import { Table } from '@radix-ui/themes'; import Checkbox from './Checkbox'; import type { FileFfprobeMeta } from '../ffmpeg'; @@ -23,6 +24,7 @@ import FileNameTemplateEditor from './FileNameTemplateEditor'; import HighlightedText from './HighlightedText'; import type { FileStats } from '../types'; import OutDirSelector from './OutDirSelector'; +import { parseRatio } from '../../../common/util'; const { basename } = window.require('path'); @@ -37,8 +39,17 @@ function Alert({ text }: { text: string }) { ); } -type Problem = { index: number, type: 'extraneous' } - | { index: number, type: 'parameter_mismatch', key: string, values: [string | number | undefined, string | number | undefined] }; +type ProblemValue = string | number | undefined; + +type Problem = { + index: number, +} & ({ + type: 'extraneous', +} | { + type: 'parameter_mismatch', + key: string, + values: [ProblemValue, ProblemValue], +}); function ConcatDialog({ isShown, onHide, paths, mergedFileTemplate, generateMergedFileNames, onConcat, alwaysConcatMultipleFiles, setAlwaysConcatMultipleFiles, fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, onOutputFormatUserChange }: { isShown: boolean, @@ -128,10 +139,20 @@ function ConcatDialog({ isShown, onHide, paths, mergedFileTemplate, generateMerg } // check all these parameters (['codec_name', 'width', 'height', 'pix_fmt', 'level', 'profile', 'sample_fmt', 'avg_frame_rate', 'r_frame_rate', 'time_base'] as const).forEach((key) => { - const val = stream[key]; - const referenceVal = referenceStream[key]; - if (val !== referenceVal) { - addProblem(path, { type: 'parameter_mismatch', index: stream.index, key, values: [String(val), referenceVal] }); + // special handling: https://github.com/mifi/lossless-cut/discussions/2740 + if (key === 'avg_frame_rate') { + const val = parseRatio(stream[key]); + const referenceVal = parseRatio(referenceStream[key]); + const sigma = 0.01; + if ((val == null && referenceVal != null) || (val != null && referenceVal == null) || (val != null && referenceVal != null && Math.abs(val - referenceVal) >= sigma)) { + addProblem(path, { type: 'parameter_mismatch', index: stream.index, key, values: [String(val), referenceVal] }); + } + } else { + const val = stream[key]; + const referenceVal = referenceStream[key]; + if (val !== referenceVal) { + addProblem(path, { type: 'parameter_mismatch', index: stream.index, key, values: [String(val), referenceVal] }); + } } }); }); @@ -236,24 +257,44 @@ function ConcatDialog({ isShown, onHide, paths, mergedFileTemplate, generateMerg {t('Mismatches detected')} - + + + + {t('Track')} + {t('Parameter')} + {t('Expected')} + {t('Actual')} + + + + + {(problemsByFile[path] ?? []).map((problem) => ( + + + {problem.index + 1} + + + {problem.type === 'extraneous' && ( + <> + + + {t('Extraneous')} + + )} + + {problem.type === 'parameter_mismatch' && ( + <> + {problem.key} + {problem.values[0] ?? t('N/A')} + + {problem.values[1] ?? t('N/A')} + + + )} + + ))} + +