improve simple mode #2927

master
Mikael Finstad 3 weeks ago
parent 2606178611
commit 26062215ec
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -886,6 +886,7 @@
"Working directory": "Working directory", "Working directory": "Working directory",
"XML files": "XML files", "XML files": "XML files",
"Yes": "Yes", "Yes": "Yes",
"You are in simple mode, meaning some functionality has been simplified or hidden.": "You are in simple mode, meaning some functionality has been simplified or hidden.",
"You are not allowed to write the output file. This probably means that the file already exists with the wrong permissions, or you don't have write permissions to the output folder.": "You are not allowed to write the output file. This probably means that the file already exists with the wrong permissions, or you don't have write permissions to the output folder.", "You are not allowed to write the output file. This probably means that the file already exists with the wrong permissions, or you don't have write permissions to the output folder.": "You are not allowed to write the output file. This probably means that the file already exists with the wrong permissions, or you don't have write permissions to the output folder.",
"You can customize the file name of the merged file using special variables.": "You can customize the file name of the merged file using special variables.", "You can customize the file name of the merged file using special variables.": "You can customize the file name of the merged file using special variables.",
"You can customize the file name of the output segment(s) using special variables._one": "You can customize the file name of the output using special variables.", "You can customize the file name of the output segment(s) using special variables._one": "You can customize the file name of the output using special variables.",

@ -2,7 +2,7 @@ import type { CSSProperties, Dispatch, SetStateAction } from 'react';
import { memo, useState, useCallback, useEffect, useMemo } from 'react'; import { memo, useState, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AiOutlineMergeCells } from 'react-icons/ai'; import { AiOutlineMergeCells } from 'react-icons/ai';
import { FaQuestionCircle, FaExclamationTriangle, FaCog, FaCheck } from 'react-icons/fa'; import { FaQuestionCircle, FaExclamationTriangle, FaCog, FaCheck, FaInfoCircle } from 'react-icons/fa';
import invariant from 'tiny-invariant'; import invariant from 'tiny-invariant';
import pMap from 'p-map'; import pMap from 'p-map';
import { Table } from '@radix-ui/themes'; import { Table } from '@radix-ui/themes';
@ -18,7 +18,7 @@ import type { FFprobeStream } from '../../../common/ffprobe';
import Button, { DialogButton } from './Button'; import Button, { DialogButton } from './Button';
import type { GeneratedOutFileNames, GenerateMergedOutFileNames } from '../util/outputNameTemplate'; import type { GeneratedOutFileNames, GenerateMergedOutFileNames } from '../util/outputNameTemplate';
import { defaultMergedFileTemplate } from '../util/outputNameTemplate'; import { defaultMergedFileTemplate } from '../util/outputNameTemplate';
import { dangerColor, saveColor, warningColor } from '../colors'; import { dangerColor, primaryTextColor, saveColor, warningColor } from '../colors';
import * as Dialog from './Dialog'; import * as Dialog from './Dialog';
import FileNameTemplateEditor from './FileNameTemplateEditor'; import FileNameTemplateEditor from './FileNameTemplateEditor';
import HighlightedText from './HighlightedText'; import HighlightedText from './HighlightedText';
@ -72,7 +72,7 @@ function ConcatDialog({ isShown, onHide, paths, mergedFileTemplate, generateMerg
const [includeAllStreams, setIncludeAllStreams] = useState(false); const [includeAllStreams, setIncludeAllStreams] = useState(false);
const [allFilesMeta, setAllFilesMeta] = useState<Record<string, { ffprobeMeta: FileFfprobeMeta, stats: FileStats }>>({}); const [allFilesMeta, setAllFilesMeta] = useState<Record<string, { ffprobeMeta: FileFfprobeMeta, stats: FileStats }>>({});
const [clearBatchFilesAfterConcat, setClearBatchFilesAfterConcat] = useState(false); const [clearBatchFilesAfterConcat, setClearBatchFilesAfterConcat] = useState(false);
const [enableReadFileMeta, setEnableReadFileMeta] = useState(false); const [enableReadFileMeta, setEnableReadFileMeta] = useState(simpleMode);
const [uniqueSuffix, setUniqueSuffix] = useState(() => Date.now()); const [uniqueSuffix, setUniqueSuffix] = useState(() => Date.now());
const firstPath = useMemo(() => paths[0], [paths]); const firstPath = useMemo(() => paths[0], [paths]);
@ -331,10 +331,19 @@ function ConcatDialog({ isShown, onHide, paths, mergedFileTemplate, generateMerg
{!enableReadFileMeta && ( {!enableReadFileMeta && (
<Alert text={t('File compatibility check is not enabled, so the merge operation might not produce a valid output. Enable "Check compatibility" below to check file compatibility before merging.')} /> <Alert text={t('File compatibility check is not enabled, so the merge operation might not produce a valid output. Enable "Check compatibility" below to check file compatibility before merging.')} />
)} )}
{simpleMode && (
<div style={{ display: 'flex', alignItems: 'center', gap: '0 .5em', color: primaryTextColor }}>
<FaInfoCircle color={primaryTextColor} style={{ verticalAlign: 'middle' }} />
{t('You are in simple mode, meaning some functionality has been simplified or hidden.')}
</div>
)}
</div> </div>
<Dialog.ButtonRow> <Dialog.ButtonRow>
<Checkbox checked={enableReadFileMeta} onCheckedChange={handleReadFileMetaCheckedChange} label={t('Check compatibility')} /> {!simpleMode && (
<Checkbox checked={enableReadFileMeta} onCheckedChange={handleReadFileMetaCheckedChange} label={t('Check compatibility')} />
)}
<Dialog.Close asChild> <Dialog.Close asChild>
<DialogButton>{t('Cancel')}</DialogButton> <DialogButton>{t('Cancel')}</DialogButton>

@ -18,3 +18,7 @@ table.options td {
table.options td { table.options td {
vertical-align: top; vertical-align: top;
} }
tr.notice-row td {
padding: 0;
}

@ -37,12 +37,6 @@ import OutDirSelector from './OutDirSelector';
import mainApi from '../mainApi'; import mainApi from '../mainApi';
const noticeStyle: CSSProperties = { marginBottom: '.5em' };
const infoStyle: CSSProperties = { ...noticeStyle, color: primaryTextColor };
const warningStyle: CSSProperties = { ...noticeStyle, color: warningColor };
const rightIconStyle: CSSProperties = { fontSize: '1.2em', verticalAlign: 'middle' };
const adjustCutFromValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const adjustCutFromValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const adjustCutToValues = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const adjustCutToValues = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
@ -80,6 +74,10 @@ interface GenericNotice {
} }
function Notice({ notice }: { notice: Notice | GenericNotice }) { function Notice({ notice }: { notice: Notice | GenericNotice }) {
const noticeStyle: CSSProperties = {};
const infoStyle: CSSProperties = { ...noticeStyle, color: primaryTextColor };
const warningStyle: CSSProperties = { ...noticeStyle, color: warningColor };
const { text, warning } = notice; const { text, warning } = notice;
return ( return (
<div style={{ ...(warning ? warningStyle : infoStyle), display: 'flex', alignItems: 'center', gap: '0 .5em' }}> <div style={{ ...(warning ? warningStyle : infoStyle), display: 'flex', alignItems: 'center', gap: '0 .5em' }}>
@ -101,7 +99,7 @@ function renderNotice(notice: Notice | undefined) {
function renderGenericNotice(notice: GenericNotice) { function renderGenericNotice(notice: GenericNotice) {
const { url } = notice; const { url } = notice;
return ( return (
<tr key={notice.text}> <tr key={notice.text} className={styles['notice-row']}>
<td colSpan={2}> <td colSpan={2}>
<Notice notice={notice} /> <Notice notice={notice} />
</td> </td>
@ -112,6 +110,8 @@ function renderGenericNotice(notice: GenericNotice) {
); );
} }
const rightIconStyle: CSSProperties = { fontSize: '1.2em', verticalAlign: 'middle' };
function ExportConfirm({ function ExportConfirm({
areWeCutting, areWeCutting,
segmentsToExport, segmentsToExport,
@ -225,6 +225,10 @@ function ExportConfirm({
const generic: GenericNotice[] = []; const generic: GenericNotice[] = [];
if (simpleMode) {
generic.push({ text: t('You are in simple mode, meaning some functionality has been simplified or hidden.') });
}
if ((effectiveExportMode === 'separate' || effectiveExportMode === 'merge' || effectiveExportMode === 'merge+separate') && !areWeCutting) { if ((effectiveExportMode === 'separate' || effectiveExportMode === 'merge' || effectiveExportMode === 'merge+separate') && !areWeCutting) {
generic.push({ text: t('Exporting whole file without cutting, because there are no segments to export.') }); generic.push({ text: t('Exporting whole file without cutting, because there are no segments to export.') });
} }
@ -247,7 +251,7 @@ function ExportConfirm({
specific, specific,
totalNum: generic.filter((n) => n.warning).length + Object.values(specific).filter((n) => n != null && n.warning).length, totalNum: generic.filter((n) => n.warning).length + Object.values(specific).filter((n) => n != null && n.warning).length,
}; };
}, [areWeCutting, areWeCuttingProblematicStreams, avoidNegativeTs, effectiveExportMode, enableOverwriteOutput, isEncoding, isIpod, isMov, keyframeCut, keyframesEnabled, mainCopiedThumbnailStreams, movFastStart, needSmartCut, outFormat, outputPlaybackRate, preserveMovData, haveSegmentWithProblematicKeyframe, t, willMerge]); }, [effectiveExportMode, areWeCuttingProblematicStreams, mainCopiedThumbnailStreams, isMov, isIpod, movFastStart, t, preserveMovData, areWeCutting, needSmartCut, isEncoding, keyframeCut, enableOverwriteOutput, simpleMode, willMerge, avoidNegativeTs, outFormat, outputPlaybackRate, keyframesEnabled, haveSegmentWithProblematicKeyframe]);
const exportModeDescription = useMemo(() => ({ const exportModeDescription = useMemo(() => ({
segments_to_chapters: t('Don\'t cut the file, but instead export an unmodified original which has chapters generated from segments'), segments_to_chapters: t('Don\'t cut the file, but instead export an unmodified original which has chapters generated from segments'),

@ -45,8 +45,10 @@ function FileNameTemplateEditor(opts: {
const [debouncedText] = useDebounce(text, 500); const [debouncedText] = useDebounce(text, 500);
const [generated, setGenerated] = useState<GeneratedOutFileNames>(); const [generated, setGenerated] = useState<GeneratedOutFileNames>();
const isSimpleMergeFilesMode = simpleMode && mode === 'merge-files';
const haveImportantMessage = generated != null && (generated.problems.error != null || generated.problems.sameAsInputFileNameWarning); const haveImportantMessage = generated != null && (generated.problems.error != null || generated.problems.sameAsInputFileNameWarning);
const [open, setOpen] = useState(haveImportantMessage || (simpleMode && mode === 'merge-files')); const [open, setOpen] = useState(haveImportantMessage || isSimpleMergeFilesMode);
useEffect(() => { useEffect(() => {
// if an important message appears, make sure we don't auto-close after it's resolved // if an important message appears, make sure we don't auto-close after it's resolved
@ -145,9 +147,6 @@ function FileNameTemplateEditor(opts: {
setText(newValue); setText(newValue);
}, [text]); }, [text]);
// In simple mode for merge-files, we auto generate file name, so there will be no ${EXT} variable
const shouldIgnoreMissingExtension = useMemo(() => simpleMode && mode === 'merge-files', [simpleMode, mode]);
function formatCurrentSegFileOrFirst(names: string[]) { function formatCurrentSegFileOrFirst(names: string[]) {
if (mode === 'separate') { if (mode === 'separate') {
const { currentSegIndexSafe } = opts; const { currentSegIndexSafe } = opts;
@ -163,11 +162,10 @@ function FileNameTemplateEditor(opts: {
return ( return (
<> <>
{generated != null && ( {generated != null && (
<div>{ <div>
(mode === 'merge-files' || mode === 'merge-segments') {(mode === 'merge-files' || mode === 'merge-segments')
? t('Merged output file name:') ? t('Merged output file name:')
: t('Output name(s):', { count: generated.fileNames.length }) : t('Output name(s):', { count: generated.fileNames.length })}
}
</div> </div>
)} )}
@ -198,7 +196,9 @@ function FileNameTemplateEditor(opts: {
animate={{ opacity: 1, height: 'auto', marginTop: '.7em', marginBottom: '1em' }} animate={{ opacity: 1, height: 'auto', marginTop: '.7em', marginBottom: '1em' }}
exit={{ opacity: 0, height: 0, marginTop: 0, marginBottom: 0 }} exit={{ opacity: 0, height: 0, marginTop: 0, marginBottom: 0 }}
> >
<div style={{ color: 'var(--gray-11)', fontSize: '.8em' }}>{t('Output file name template')}:</div> {!isSimpleMergeFilesMode && (
<div style={{ color: 'var(--gray-11)', fontSize: '.8em' }}>{t('Output file name template')}:</div>
)}
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '.2em', gap: '.5em' }}> <div style={{ display: 'flex', alignItems: 'center', marginBottom: '.2em', gap: '.5em' }}>
<TextInput ref={inputRef} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" style={{ padding: '.3em' }} /> <TextInput ref={inputRef} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" style={{ padding: '.3em' }} />
@ -224,7 +224,9 @@ function FileNameTemplateEditor(opts: {
</Dialog.Root> </Dialog.Root>
)} )}
<Button onClick={reset} style={{ marginLeft: '.3em', padding: '.3em' }}><FaUndo style={{ fontSize: '.8em', color: dangerColor, marginRight: '.5em' }} />{t('Reset')}</Button> {!isSimpleMergeFilesMode && (
<Button onClick={reset} style={{ marginLeft: '.3em', padding: '.3em' }}><FaUndo style={{ fontSize: '.8em', color: dangerColor, marginRight: '.5em' }} />{t('Reset')}</Button>
)}
</div> </div>
<div style={{ fontSize: '.9em', color: 'var(--gray-11)', display: 'flex', gap: '.3em', flexWrap: 'wrap', alignItems: 'center', marginBottom: '.7em' }}> <div style={{ fontSize: '.9em', color: 'var(--gray-11)', display: 'flex', gap: '.3em', flexWrap: 'wrap', alignItems: 'center', marginBottom: '.7em' }}>
@ -245,12 +247,14 @@ function FileNameTemplateEditor(opts: {
</div> </div>
)} )}
<div title={t('Whether or not to sanitize output file names (sanitizing removes special characters)')} style={{ marginBottom: '.3em' }}> {!simpleMode && (
<Switch checked={safeOutputFileName} onCheckedChange={toggleSafeOutputFileName} style={{ verticalAlign: 'middle', marginRight: '.5em' }} /> <div title={t('Whether or not to sanitize output file names (sanitizing removes special characters)')} style={{ marginBottom: '.3em' }}>
<span>{t('Sanitize file names')}</span> <Switch checked={safeOutputFileName} onCheckedChange={toggleSafeOutputFileName} style={{ verticalAlign: 'middle', marginRight: '.5em' }} />
<span>{t('Sanitize file names')}</span>
{!safeOutputFileName && <FaExclamationTriangle color={warningColor} style={{ marginLeft: '.5em', verticalAlign: 'middle' }} />} {!safeOutputFileName && <FaExclamationTriangle color={warningColor} style={{ marginLeft: '.5em', verticalAlign: 'middle' }} />}
</div> </div>
)}
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
@ -269,7 +273,8 @@ function FileNameTemplateEditor(opts: {
</div> </div>
)} )}
{!shouldIgnoreMissingExtension && isMissingExtension && ( {/* In simple mode for merge-files, we auto generate file name, so there might be no ${EXT} variable */}
{!isSimpleMergeFilesMode && isMissingExtension && (
<div style={{ marginBottom: '1em' }}> <div style={{ marginBottom: '1em' }}>
<FaExclamationTriangle style={{ verticalAlign: 'middle', marginRight: '.3em' }} color={warningColor} /> <FaExclamationTriangle style={{ verticalAlign: 'middle', marginRight: '.3em' }} color={warningColor} />
{t('The file name template is missing {{ext}} and will result in a file without the suggested extension. This may result in an unplayable output file.', { ext: extVariableFormatted })} {t('The file name template is missing {{ext}} and will result in a file without the suggested extension. This may result in an unplayable output file.', { ext: extVariableFormatted })}

Loading…
Cancel
Save