kopia lustrzana https://github.com/mifi/lossless-cut
improve export confirm
rodzic
365e1fd1a8
commit
391cab538c
|
@ -1,6 +1,6 @@
|
|||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { WarningSignIcon, Button, CrossIcon } from 'evergreen-ui';
|
||||
import { WarningSignIcon, CrossIcon } from 'evergreen-ui';
|
||||
import { FaRegCheckCircle } from 'react-icons/fa';
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
|
@ -15,6 +15,7 @@ import ToggleExportConfirm from './ToggleExportConfirm';
|
|||
import OutSegTemplateEditor from './OutSegTemplateEditor';
|
||||
import HighlightedText, { highlightedTextStyle } from './HighlightedText';
|
||||
import Select from './Select';
|
||||
import Switch from './Switch';
|
||||
|
||||
import { primaryTextColor } from '../colors';
|
||||
import { withBlur } from '../util';
|
||||
|
@ -106,8 +107,6 @@ const ExportConfirm = memo(({
|
|||
toast.fire({ icon: 'info', timer: 10000, text: `${avoidNegativeTs}: ${texts[avoidNegativeTs]}` });
|
||||
}, [avoidNegativeTs]);
|
||||
|
||||
const outSegTemplateHelpIcon = <HelpIcon onClick={onOutSegTemplateHelpPress} />;
|
||||
|
||||
const canEditTemplate = !willMerge || !autoDeleteMergedSegments;
|
||||
|
||||
// https://stackoverflow.com/questions/33454533/cant-scroll-to-top-of-flex-item-that-is-overflowing-container
|
||||
|
@ -127,94 +126,199 @@ const ExportConfirm = memo(({
|
|||
<CrossIcon size={24} style={{ position: 'absolute', right: 0, top: 0, padding: 15, boxSizing: 'content-box', cursor: 'pointer' }} role="button" onClick={onClosePress} />
|
||||
|
||||
<h2 style={{ marginTop: 0, marginBottom: '.5em' }}>{t('Export options')}</h2>
|
||||
<ul style={{ margin: 0 }}>
|
||||
{selectedSegments.length !== nonFilteredSegmentsOrInverse.length && <li><FaRegCheckCircle size={12} style={{ marginRight: 3 }} />{t('{{selectedSegments}} of {{nonFilteredSegments}} segments selected', { selectedSegments: selectedSegments.length, nonFilteredSegments: nonFilteredSegmentsOrInverse.length })}</li>}
|
||||
<li>
|
||||
{selectedSegments.length > 1 ? t('Export mode for {{segments}} segments', { segments: selectedSegments.length }) : t('Export mode')} <ExportModeButton selectedSegments={selectedSegments} />
|
||||
<HelpIcon onClick={onExportModeHelpPress} />
|
||||
{effectiveExportMode === 'sesgments_to_chapters' && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Chapters only')} />}
|
||||
</li>
|
||||
<li>
|
||||
{t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })}
|
||||
<HelpIcon onClick={onOutFmtHelpPress} />
|
||||
</li>
|
||||
<li>
|
||||
<Trans>Input has {{ numStreamsTotal }} tracks - <HighlightedText style={{ cursor: 'pointer' }} onClick={() => setStreamsSelectorShown(true)}>Keeping {{ numStreamsToCopy }} tracks</HighlightedText></Trans>
|
||||
<HelpIcon onClick={onTracksHelpPress} />
|
||||
{areWeCuttingProblematicStreams && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
|
||||
{areWeCuttingProblematicStreams && <div style={warningStyle}><Trans>Warning: Cutting thumbnail tracks is known to cause problems. Consider disabling track {{ trackNumber: mainCopiedThumbnailStreams[0].index + 1 }}.</Trans></div>}
|
||||
</li>
|
||||
<li>
|
||||
{t('Save output to path:')} <span role="button" onClick={changeOutDir} style={outDirStyle}>{outputDir}</span>
|
||||
</li>
|
||||
{canEditTemplate && (
|
||||
<li>
|
||||
<OutSegTemplateEditor filePath={filePath} helpIcon={outSegTemplateHelpIcon} outSegTemplate={outSegTemplate} setOutSegTemplate={setOutSegTemplate} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} />
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<table className={styles.options}>
|
||||
<tbody>
|
||||
{selectedSegments.length !== nonFilteredSegmentsOrInverse.length && (
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<FaRegCheckCircle size={12} style={{ marginRight: 3 }} />{t('{{selectedSegments}} of {{nonFilteredSegments}} segments selected', { selectedSegments: selectedSegments.length, nonFilteredSegments: nonFilteredSegmentsOrInverse.length })}
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
)}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{selectedSegments.length > 1 ? t('Export mode for {{segments}} segments', { segments: selectedSegments.length }) : t('Export mode')}
|
||||
</td>
|
||||
<td>
|
||||
<ExportModeButton selectedSegments={selectedSegments} />
|
||||
</td>
|
||||
<td>
|
||||
{effectiveExportMode === 'sesgments_to_chapters' && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Chapters only')} />}
|
||||
<HelpIcon onClick={onExportModeHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Output container format:')}
|
||||
</td>
|
||||
<td>
|
||||
{renderOutFmt({ height: 20, maxWidth: 150 })}
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={onOutFmtHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Trans>Input has {{ numStreamsTotal }} tracks</Trans>
|
||||
{areWeCuttingProblematicStreams && (
|
||||
<div style={warningStyle}><Trans>Warning: Cutting thumbnail tracks is known to cause problems. Consider disabling track {{ trackNumber: mainCopiedThumbnailStreams[0] ? mainCopiedThumbnailStreams[0].index + 1 : 0 }}.</Trans></div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<HighlightedText style={{ cursor: 'pointer' }} onClick={() => setStreamsSelectorShown(true)}><Trans>Keeping {{ numStreamsToCopy }} tracks</Trans></HighlightedText>
|
||||
</td>
|
||||
<td>
|
||||
{areWeCuttingProblematicStreams && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
|
||||
<HelpIcon onClick={onTracksHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Save output to path:')}
|
||||
</td>
|
||||
<td>
|
||||
<span role="button" onClick={changeOutDir} style={outDirStyle}>{outputDir}</span>
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
|
||||
{canEditTemplate && (
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<OutSegTemplateEditor filePath={filePath} outSegTemplate={outSegTemplate} setOutSegTemplate={setOutSegTemplate} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} />
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={onOutSegTemplateHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 style={{ marginBottom: '.5em' }}>{t('Advanced options')}</h3>
|
||||
|
||||
{willMerge && (
|
||||
<ul style={{ marginTop: 0, marginBottom: '1em' }}>
|
||||
<li>
|
||||
{t('Create chapters from merged segments? (slow)')} <Button height={20} onClick={toggleSegmentsToChapters}>{segmentsToChapters ? t('Yes') : t('No')}</Button>
|
||||
<HelpIcon onClick={onSegmentsToChaptersHelpPress} />
|
||||
</li>
|
||||
<li>
|
||||
{t('Preserve original metadata when merging? (slow)')} <Button height={20} onClick={togglePreserveMetadataOnMerge}>{preserveMetadataOnMerge ? t('Yes') : t('No')}</Button>
|
||||
<HelpIcon onClick={onPreserveMetadataOnMergeHelpPress} />
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
<table className={styles.options}>
|
||||
<tbody>
|
||||
{willMerge && (
|
||||
<>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Create chapters from merged segments? (slow)')}
|
||||
</td>
|
||||
<td>
|
||||
<Switch checked={segmentsToChapters} onCheckedChange={toggleSegmentsToChapters} />
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={onSegmentsToChaptersHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Preserve original metadata when merging? (slow)')}
|
||||
</td>
|
||||
<td>
|
||||
<Switch checked={preserveMetadataOnMerge} onCheckedChange={togglePreserveMetadataOnMerge} />
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={onPreserveMetadataOnMergeHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
|
||||
<p style={{ margin: '.5em 0' }}>{t('Depending on your specific file/player, you may have to try different options for best results.')}</p>
|
||||
<tr>
|
||||
<td style={{ paddingTop: '.5em', color: 'var(--gray11)' }} colSpan={2}>
|
||||
{t('Depending on your specific file/player, you may have to try different options for best results.')}
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
|
||||
<ul style={{ margin: 0 }}>
|
||||
{areWeCutting && (
|
||||
<>
|
||||
<li>
|
||||
{t('Smart cut (experimental):')} <Button height={20} onClick={() => setEnableSmartCut((v) => !v)}>{enableSmartCut ? t('Yes') : t('No')}</Button>
|
||||
<HelpIcon onClick={onSmartCutHelpPress} />
|
||||
{needSmartCut && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Experimental functionality has been activated!')} />}
|
||||
</li>
|
||||
{!needSmartCut && (
|
||||
<li>
|
||||
{t('Cut mode:')} <KeyframeCutButton />
|
||||
<HelpIcon onClick={onKeyframeCutHelpPress} /> {!keyframeCut && <span style={warningStyle}>{t('Note: Keyframe cut is recommended for most common files')}</span>}
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{areWeCutting && (
|
||||
<>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Smart cut (experimental):')}
|
||||
</td>
|
||||
<td>
|
||||
<Switch checked={enableSmartCut} onCheckedChange={() => setEnableSmartCut((v) => !v)} />
|
||||
</td>
|
||||
<td>
|
||||
{needSmartCut && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" title={i18n.t('Experimental functionality has been activated!')} />}
|
||||
<HelpIcon onClick={onSmartCutHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
{!needSmartCut && (
|
||||
<tr>
|
||||
<td>
|
||||
{t('Cut mode:')}
|
||||
{!keyframeCut && <div style={warningStyle}>{t('Note: Keyframe cut is recommended for most common files')}</div>}
|
||||
</td>
|
||||
<td>
|
||||
<KeyframeCutButton />
|
||||
</td>
|
||||
<td>
|
||||
{!keyframeCut && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
|
||||
<HelpIcon onClick={onKeyframeCutHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isMov && (
|
||||
<>
|
||||
<li>
|
||||
{t('Enable MOV Faststart?')} <MovFastStartButton />
|
||||
<HelpIcon onClick={onMovFastStartHelpPress} /> {isIpod && !movFastStart && <span style={warningStyle}>{t('For the ipod format, it is recommended to activate this option')}</span>}
|
||||
</li>
|
||||
<li>
|
||||
{t('Preserve all MP4/MOV metadata?')} <PreserveMovDataButton />
|
||||
<HelpIcon onClick={onPreserveMovDataHelpPress} /> {isIpod && preserveMovData && <span style={warningStyle}>{t('For the ipod format, it is recommended to deactivate this option')}</span>}
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{isMov && (
|
||||
<>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Enable MOV Faststart?')}
|
||||
</td>
|
||||
<td>
|
||||
<MovFastStartButton />
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={onMovFastStartHelpPress} /> {isIpod && !movFastStart && <span style={warningStyle}>{t('For the ipod format, it is recommended to activate this option')}</span>}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Preserve all MP4/MOV metadata?')}
|
||||
{isIpod && preserveMovData && <div style={warningStyle}>{t('For the ipod format, it is recommended to deactivate this option')}</div>}
|
||||
</td>
|
||||
<td>
|
||||
<PreserveMovDataButton />
|
||||
</td>
|
||||
<td>
|
||||
{isIpod && preserveMovData && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
|
||||
<HelpIcon onClick={onPreserveMovDataHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!needSmartCut && (
|
||||
<li>
|
||||
"avoid_negative_ts"
|
||||
<Select value={avoidNegativeTs} onChange={(e) => setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}>
|
||||
<option value="auto">auto</option>
|
||||
<option value="make_zero">make_zero</option>
|
||||
<option value="make_non_negative">make_non_negative</option>
|
||||
<option value="disabled">disabled</option>
|
||||
</Select>
|
||||
<HelpIcon onClick={onAvoidNegativeTsHelpPress} />
|
||||
{!['make_zero', 'auto'].includes(avoidNegativeTs) && <div style={warningStyle}>{t('It\'s generally recommended to set this to one of: {{values}}', { values: '"auto", "make_zero"' })}</div>}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
{!needSmartCut && (
|
||||
<tr>
|
||||
<td>
|
||||
"avoid_negative_ts"
|
||||
{!['make_zero', 'auto'].includes(avoidNegativeTs) && <div style={warningStyle}>{t('It\'s generally recommended to set this to one of: {{values}}', { values: '"auto", "make_zero"' })}</div>}
|
||||
</td>
|
||||
<td>
|
||||
<Select value={avoidNegativeTs} onChange={(e) => setAvoidNegativeTs(e.target.value)} style={{ height: 20, marginLeft: 5 }}>
|
||||
<option value="auto">auto</option>
|
||||
<option value="make_zero">make_zero</option>
|
||||
<option value="make_non_negative">make_non_negative</option>
|
||||
<option value="disabled">disabled</option>
|
||||
</Select>
|
||||
</td>
|
||||
<td>
|
||||
{!['make_zero', 'auto'].includes(avoidNegativeTs) && <WarningSignIcon verticalAlign="middle" color="warning" marginLeft=".3em" />}
|
||||
<HelpIcon onClick={onAvoidNegativeTsHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
|
@ -24,3 +24,14 @@
|
|||
background: var(--blackA8);
|
||||
}
|
||||
|
||||
table.options {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.options td:last-child {
|
||||
text-align: right;
|
||||
width: 3em;
|
||||
}
|
||||
table.options td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { memo } from 'react';
|
|||
|
||||
import { primaryTextColor } from '../colors';
|
||||
|
||||
export const highlightedTextStyle = { textDecoration: 'underline', textUnderlineOffset: '.2em', textDecorationColor: primaryTextColor, color: 'var(--gray12)', borderRadius: '.4em', padding: '0 .3em' };
|
||||
export const highlightedTextStyle = { textDecoration: 'underline', textUnderlineOffset: '.2em', textDecorationColor: primaryTextColor, color: 'var(--gray12)', borderRadius: '.4em' };
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
const HighlightedText = memo(({ children, style, ...props }) => <span {...props} style={{ ...highlightedTextStyle, ...style }}>{children}</span>);
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import React, { memo } from 'react';
|
||||
import { Button } from 'evergreen-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { withBlur } from '../util';
|
||||
import useUserSettings from '../hooks/useUserSettings';
|
||||
import Switch from './Switch';
|
||||
|
||||
|
||||
const MovFastStartButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const { movFastStart, toggleMovFastStart } = useUserSettings();
|
||||
|
||||
return (
|
||||
<Button height={20} onClick={withBlur(toggleMovFastStart)}>
|
||||
{movFastStart ? t('Yes') : t('No')}
|
||||
</Button>
|
||||
<Switch checked={movFastStart} onCheckedChange={withBlur(toggleMovFastStart)} />
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ const ReactSwal = withReactContent(Swal);
|
|||
// eslint-disable-next-line no-template-curly-in-string
|
||||
const extVar = '${EXT}';
|
||||
|
||||
const inputStyle = { flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', appearance: 'none', border: 'none' };
|
||||
const inputStyle = { flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', border: '1px solid var(--gray6)', appearance: 'none' };
|
||||
|
||||
const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
|
||||
const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
|
||||
const { safeOutputFileName, toggleSafeOutputFileName } = useUserSettings();
|
||||
|
||||
const [text, setText] = useState(outSegTemplate);
|
||||
|
@ -86,12 +86,9 @@ const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate
|
|||
return (
|
||||
<>
|
||||
<div>
|
||||
<span>
|
||||
{outSegFileNames != null && t('Output name(s):', { count: outSegFileNames.length })}
|
||||
{' '}
|
||||
{outSegFileNames != null && <HighlightedText role="button" onClick={onShowClick} style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', cursor: needToShow ? undefined : 'pointer' }}>{outSegFileNames[currentSegIndexSafe] || outSegFileNames[0] || '-'}</HighlightedText>}
|
||||
</span>
|
||||
{helpIcon}
|
||||
<div>{outSegFileNames != null && t('Output name(s):', { count: outSegFileNames.length })}</div>
|
||||
|
||||
{outSegFileNames != null && <HighlightedText role="button" onClick={onShowClick} style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', cursor: needToShow ? undefined : 'pointer' }}>{outSegFileNames[currentSegIndexSafe] || outSegFileNames[0] || '-'}</HighlightedText>}
|
||||
</div>
|
||||
|
||||
{needToShow && (
|
||||
|
@ -107,8 +104,7 @@ const OutSegTemplateEditor = memo(({ helpIcon, outSegTemplate, setOutSegTemplate
|
|||
<div style={{ maxWidth: 600 }}>
|
||||
{error != null && <Alert intent="danger" appearance="card"><Heading color="danger">{i18n.t('There is an error in the file name template:')}</Heading><Text>{error}</Text></Alert>}
|
||||
{isMissingExtension && <Alert intent="warning" appearance="card">{i18n.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: extVar })}</Alert>}
|
||||
{/* eslint-disable-next-line no-template-curly-in-string */}
|
||||
<div style={{ fontSize: '.8em', color: 'rgba(255,255,255,0.7)' }}>
|
||||
<div style={{ fontSize: '.8em', color: 'var(--gray11)' }}>
|
||||
{`${i18n.t('Variables')}`}{': '}
|
||||
{['FILENAME', 'CUT_FROM', 'CUT_TO', 'SEG_NUM', 'SEG_LABEL', 'SEG_SUFFIX', 'EXT', 'SEG_TAGS.XX'].map((variable) => <span key={variable} role="button" style={{ cursor: 'pointer', marginRight: '.2em' }} onClick={() => setText((oldText) => `${oldText}\${${variable}}`)}>{variable}</span>)}
|
||||
</div>
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import React, { memo } from 'react';
|
||||
import { Button } from 'evergreen-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { withBlur } from '../util';
|
||||
import useUserSettings from '../hooks/useUserSettings';
|
||||
import Switch from './Switch';
|
||||
|
||||
|
||||
const PreserveMovDataButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const { preserveMovData, togglePreserveMovData } = useUserSettings();
|
||||
|
||||
return (
|
||||
<Button height={20} onClick={withBlur(togglePreserveMovData)}>
|
||||
{preserveMovData ? t('Yes') : t('No')}
|
||||
</Button>
|
||||
<Switch checked={preserveMovData} onCheckedChange={withBlur(togglePreserveMovData)} />
|
||||
);
|
||||
});
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue