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",
"XML files": "XML files",
"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 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.",

@ -2,7 +2,7 @@ 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 } from 'react-icons/fa';
import { FaQuestionCircle, FaExclamationTriangle, FaCog, FaCheck, FaInfoCircle } from 'react-icons/fa';
import invariant from 'tiny-invariant';
import pMap from 'p-map';
import { Table } from '@radix-ui/themes';
@ -18,7 +18,7 @@ import type { FFprobeStream } from '../../../common/ffprobe';
import Button, { DialogButton } from './Button';
import type { GeneratedOutFileNames, GenerateMergedOutFileNames } 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 FileNameTemplateEditor from './FileNameTemplateEditor';
import HighlightedText from './HighlightedText';
@ -72,7 +72,7 @@ function ConcatDialog({ isShown, onHide, paths, mergedFileTemplate, generateMerg
const [includeAllStreams, setIncludeAllStreams] = useState(false);
const [allFilesMeta, setAllFilesMeta] = useState<Record<string, { ffprobeMeta: FileFfprobeMeta, stats: FileStats }>>({});
const [clearBatchFilesAfterConcat, setClearBatchFilesAfterConcat] = useState(false);
const [enableReadFileMeta, setEnableReadFileMeta] = useState(false);
const [enableReadFileMeta, setEnableReadFileMeta] = useState(simpleMode);
const [uniqueSuffix, setUniqueSuffix] = useState(() => Date.now());
const firstPath = useMemo(() => paths[0], [paths]);
@ -331,10 +331,19 @@ function ConcatDialog({ isShown, onHide, paths, mergedFileTemplate, generateMerg
{!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.')} />
)}
{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>
<Dialog.ButtonRow>
<Checkbox checked={enableReadFileMeta} onCheckedChange={handleReadFileMetaCheckedChange} label={t('Check compatibility')} />
{!simpleMode && (
<Checkbox checked={enableReadFileMeta} onCheckedChange={handleReadFileMetaCheckedChange} label={t('Check compatibility')} />
)}
<Dialog.Close asChild>
<DialogButton>{t('Cancel')}</DialogButton>

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

@ -37,12 +37,6 @@ import OutDirSelector from './OutDirSelector';
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 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 }) {
const noticeStyle: CSSProperties = {};
const infoStyle: CSSProperties = { ...noticeStyle, color: primaryTextColor };
const warningStyle: CSSProperties = { ...noticeStyle, color: warningColor };
const { text, warning } = notice;
return (
<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) {
const { url } = notice;
return (
<tr key={notice.text}>
<tr key={notice.text} className={styles['notice-row']}>
<td colSpan={2}>
<Notice notice={notice} />
</td>
@ -112,6 +110,8 @@ function renderGenericNotice(notice: GenericNotice) {
);
}
const rightIconStyle: CSSProperties = { fontSize: '1.2em', verticalAlign: 'middle' };
function ExportConfirm({
areWeCutting,
segmentsToExport,
@ -225,6 +225,10 @@ function ExportConfirm({
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) {
generic.push({ text: t('Exporting whole file without cutting, because there are no segments to export.') });
}
@ -247,7 +251,7 @@ function ExportConfirm({
specific,
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(() => ({
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 [generated, setGenerated] = useState<GeneratedOutFileNames>();
const isSimpleMergeFilesMode = simpleMode && mode === 'merge-files';
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(() => {
// 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);
}, [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[]) {
if (mode === 'separate') {
const { currentSegIndexSafe } = opts;
@ -163,11 +162,10 @@ function FileNameTemplateEditor(opts: {
return (
<>
{generated != null && (
<div>{
(mode === 'merge-files' || mode === 'merge-segments')
<div>
{(mode === 'merge-files' || mode === 'merge-segments')
? t('Merged output file name:')
: t('Output name(s):', { count: generated.fileNames.length })
}
: t('Output name(s):', { count: generated.fileNames.length })}
</div>
)}
@ -198,7 +196,9 @@ function FileNameTemplateEditor(opts: {
animate={{ opacity: 1, height: 'auto', marginTop: '.7em', marginBottom: '1em' }}
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' }}>
<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>
)}
<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 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 title={t('Whether or not to sanitize output file names (sanitizing removes special characters)')} style={{ marginBottom: '.3em' }}>
<Switch checked={safeOutputFileName} onCheckedChange={toggleSafeOutputFileName} style={{ verticalAlign: 'middle', marginRight: '.5em' }} />
<span>{t('Sanitize file names')}</span>
{!simpleMode && (
<div title={t('Whether or not to sanitize output file names (sanitizing removes special characters)')} style={{ marginBottom: '.3em' }}>
<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' }} />}
</div>
{!safeOutputFileName && <FaExclamationTriangle color={warningColor} style={{ marginLeft: '.5em', verticalAlign: 'middle' }} />}
</div>
)}
</motion.div>
)}
</AnimatePresence>
@ -269,7 +273,8 @@ function FileNameTemplateEditor(opts: {
</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' }}>
<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 })}

Loading…
Cancel
Save