kopia lustrzana https://github.com/mifi/lossless-cut
rodzic
8dcd6113d5
commit
7e056410ec
|
@ -9,6 +9,7 @@ When exporting multiple segments as separate files, LosslessCut offers you the a
|
|||
| `${FILENAME}` | The original filename without the extension (e.g. `Beach Trip` for a file named `Beach Trip.mp4`)
|
||||
| `${EXT}` | The extension of the file (e.g.: `.mp4`, `.mkv`)
|
||||
| `${SEG_NUM}` | Number of the segment (e.g. `1`, `2` or `3`)
|
||||
| `${EPOCH_MS}` | Number of milliseconds since epoch (e.g. `1680852771465`)
|
||||
| `${SEG_LABEL}` | The label of the segment (e.g. `Getting_Lunch`)
|
||||
| `${SEG_SUFFIX}` | If a label exists for this segment, the label will be used, prepended by `-`. Otherwise, the segment number prepended by `-seg` will be used (e.g. `-Getting_Lunch`, `-seg1`)
|
||||
| `${CUT_FROM}` | The timestamp for the beginning of the segment in `hh.mm.ss.sss` format (e.g. `00.00.27.184`)
|
||||
|
|
|
@ -4,6 +4,7 @@ import i18n from 'i18next';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { WarningSignIcon, ErrorIcon, Button, IconButton, TickIcon, ResetIcon } from 'evergreen-ui';
|
||||
import withReactContent from 'sweetalert2-react-content';
|
||||
import { IoIosHelpCircle } from 'react-icons/io';
|
||||
|
||||
import Swal from '../swal';
|
||||
import HighlightedText from './HighlightedText';
|
||||
|
@ -12,6 +13,8 @@ import useUserSettings from '../hooks/useUserSettings';
|
|||
|
||||
const ReactSwal = withReactContent(Swal);
|
||||
|
||||
const electron = window.require('electron');
|
||||
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
const extVar = '${EXT}';
|
||||
|
||||
|
@ -26,7 +29,7 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
|
|||
const [error, setError] = useState();
|
||||
const [outSegFileNames, setOutSegFileNames] = useState();
|
||||
const [shown, setShown] = useState();
|
||||
const inputRef = useRef(null);
|
||||
const inputRef = useRef();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
@ -84,18 +87,14 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
|
|||
|
||||
const needToShow = shown || error != null;
|
||||
|
||||
const onTextClick = useCallback(() => {
|
||||
inputRef.current.focus();
|
||||
}, []);
|
||||
|
||||
const onVariableClick = useCallback((variable) => {
|
||||
const input = inputRef.current;
|
||||
const startPos = input.selectionStart;
|
||||
const endPos = input.selectionEnd;
|
||||
|
||||
const newValue = `${text.slice(0, startPos)}${'${' + variable + '}' + text.slice(endPos)}`;
|
||||
|
||||
const newValue = `${text.slice(0, startPos)}${`\${${variable}}${text.slice(endPos)}`}`;
|
||||
setText(newValue);
|
||||
|
||||
|
||||
// Move the cursor to after the inserted variable
|
||||
const newPos = startPos + variable.length + 2;
|
||||
input.selectionStart = newPos;
|
||||
|
@ -113,7 +112,7 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
|
|||
{needToShow && (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 5, marginTop: 10 }}>
|
||||
<input type="text" ref={inputRef} style={inputStyle} onChange={onTextChange} onClick={onTextClick} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />
|
||||
<input type="text" ref={inputRef} style={inputStyle} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />
|
||||
|
||||
{outSegFileNames != null && <Button height={20} onClick={onAllSegmentsPreviewPress} marginLeft={5}>{t('Preview')}</Button>}
|
||||
<Button title={t('Whether or not to sanitize output file names (sanitizing removes special characters)')} marginLeft={5} height={20} onClick={toggleSafeOutputFileName} intent={safeOutputFileName ? 'success' : 'danger'}>{safeOutputFileName ? t('Sanitize') : t('No sanitize')}</Button>
|
||||
|
@ -123,9 +122,10 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
|
|||
<div style={{ maxWidth: 600 }}>
|
||||
{error != null && <div style={{ marginBottom: '1em' }}><ErrorIcon color="var(--red9)" /> {i18n.t('There is an error in the file name template:')} {error}</div>}
|
||||
{isMissingExtension && <div style={{ marginBottom: '1em' }}><WarningSignIcon color="var(--amber9)" /> {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 })}</div>}
|
||||
<div style={{ fontSize: '.8em', color: 'var(--gray11)' }}>
|
||||
{`${i18n.t('Variables')}`}{': '}
|
||||
{['FILENAME', 'TIMESTAMP', 'CUT_FROM', 'CUT_TO', 'SEG_NUM', 'SEG_LABEL', 'SEG_SUFFIX', 'EXT', 'SEG_TAGS.XX'].map((variable) => (
|
||||
<div style={{ fontSize: '.8em', color: 'var(--gray11)', display: 'flex', gap: '.3em', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
{`${i18n.t('Variables')}:`}
|
||||
<IoIosHelpCircle fontSize="1.3em" color="var(--gray12)" role="button" cursor="pointer" onClick={() => electron.shell.openExternal('https://github.com/mifi/lossless-cut/blob/master/import-export.md#customising-exported-file-names')} />
|
||||
{['FILENAME', 'CUT_FROM', 'CUT_TO', 'SEG_NUM', 'SEG_LABEL', 'SEG_SUFFIX', 'EXT', 'SEG_TAGS.XX', 'EPOCH_MS'].map((variable) => (
|
||||
<span key={variable} role="button" style={{ cursor: 'pointer', marginRight: '.2em' }} onClick={() => onVariableClick(variable)}>{variable}</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -65,15 +65,9 @@ export function getOutSegError({ fileNames, filePath, outputDir, safeOutputFileN
|
|||
// This is used as a fallback and so it has to always generate unique file names
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
export const defaultOutSegTemplate = '${FILENAME}-${CUT_FROM}-${CUT_TO}${SEG_SUFFIX}${EXT}';
|
||||
let currentTimestamp = Date.now();
|
||||
function interpolateSegmentFileName({ template, inputFileNameWithoutExt, segSuffix, ext, segNum, segLabel, cutFrom, cutTo, tags }) {
|
||||
const compiled = lodashTemplate(template);
|
||||
|
||||
if (segSuffix) {
|
||||
if (segNum > 1) {
|
||||
currentTimestamp += 1;
|
||||
}
|
||||
}
|
||||
function interpolateSegmentFileName({ template, epochMs, inputFileNameWithoutExt, segSuffix, ext, segNum, segLabel, cutFrom, cutTo, tags }) {
|
||||
const compiled = lodashTemplate(template);
|
||||
|
||||
const data = {
|
||||
FILENAME: inputFileNameWithoutExt,
|
||||
|
@ -81,7 +75,7 @@ function interpolateSegmentFileName({ template, inputFileNameWithoutExt, segSuff
|
|||
EXT: ext,
|
||||
SEG_NUM: segNum,
|
||||
SEG_LABEL: segLabel,
|
||||
TIMESTAMP: currentTimestamp,
|
||||
EPOCH_MS: String(epochMs),
|
||||
CUT_FROM: cutFrom,
|
||||
CUT_TO: cutTo,
|
||||
SEG_TAGS: {
|
||||
|
@ -99,6 +93,8 @@ function formatSegNum(segIndex, segments) {
|
|||
}
|
||||
|
||||
export function generateOutSegFileNames({ segments, template, forceSafeOutputFileName, formatTimecode, isCustomFormatSelected, fileFormat, filePath, safeOutputFileName, maxLabelLength }) {
|
||||
const currentTimestamp = Date.now();
|
||||
|
||||
return segments.map((segment, i) => {
|
||||
const { start, end, name = '' } = segment;
|
||||
const segNum = formatSegNum(i, segments);
|
||||
|
@ -118,6 +114,7 @@ export function generateOutSegFileNames({ segments, template, forceSafeOutputFil
|
|||
|
||||
const segFileName = interpolateSegmentFileName({
|
||||
template,
|
||||
epochMs: currentTimestamp + i, // for convenience: give each segment a unique timestamp
|
||||
segNum,
|
||||
inputFileNameWithoutExt,
|
||||
segSuffix: getSegSuffix(),
|
||||
|
|
Ładowanie…
Reference in New Issue