Add new file name template variables

`CUT_DURATION`, `CUT_FROM_NUM`, `CUT_TO_NUM`
also don't show advanced variable in simple mode
closes #2486
pull/2510/head
Mikael Finstad 3 months ago
parent 250731a169
commit 2507310985
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -67,14 +67,17 @@ The following variables are available in the template to customize the filenames
| ✅ | `${EPOCH_MS}` | `number` | Number of milliseconds since epoch (e.g. `1680852771465`). Useful to generate a unique file name on every export to prevent accidental overwrite. | ✅ | `${EPOCH_MS}` | `number` | Number of milliseconds since epoch (e.g. `1680852771465`). Useful to generate a unique file name on every export to prevent accidental overwrite.
| ✅ | `${EXPORT_COUNT}` | `number` | Number of exports done since last LosslessCut launch (starts at 1). | ✅ | `${EXPORT_COUNT}` | `number` | Number of exports done since last LosslessCut launch (starts at 1).
| ✅ | `${FILE_EXPORT_COUNT}` | `number` | Number of exports done since last file was opened (starts at 1). | ✅ | `${FILE_EXPORT_COUNT}` | `number` | Number of exports done since last file was opened (starts at 1).
| ✅ | `${SEG_LABEL}` | `string` | The label of the segment (e.g. `Getting Lunch`). In cut+merge mode, this will be an `Array`, and you can use e.g. this code to combine all labels with a comma between: `${SEG_LABEL.filter(label => label).join(',')}` | ✅ | `${SEG_LABEL}` | `string` / `string[]` | The label of the segment (e.g. `Getting Lunch`). In cut+merge mode, this will be an `Array`, and you can use e.g. this code to combine all labels with a comma between: `${SEG_LABEL.filter(label => label).join(',')}`
| | `${SEG_NUM}` | `string` | Segment index, padded string (e.g. `01`, `02` or `42`). | | `${SEG_NUM}` | `string` | Segment index, padded string (e.g. `01`, `02` or `42`).
| | `${SEG_NUM_INT}` | `number` | Segment index, as an integer (e.g. `1`, `2` or `42`). Can be used with numeric arithmetics, e.g. `${SEG_NUM_INT+100}`. | | `${SEG_NUM_INT}` | `number` | Segment index, as an integer (e.g. `1`, `2` or `42`). Can be used with numeric arithmetics, e.g. `${SEG_NUM_INT+100}`.
| | `${SELECTED_SEG_NUM}` | `string` | Same as `SEG_NUM`, but it counts only selected segments. | | `${SELECTED_SEG_NUM}` | `string` | Same as `SEG_NUM`, but it counts only selected segments.
| | `${SELECTED_SEG_NUM_INT}` | `number` | Same as `SEG_NUM_INT`, but it counts only selected segments. | | `${SELECTED_SEG_NUM_INT}` | `number` | Same as `SEG_NUM_INT`, but it counts only selected segments.
| | `${SEG_SUFFIX}` | `string` | If a label exists for this segment, the label will be used, prepended by `-`. Otherwise, the segment index prepended by `-seg` will be used (e.g. `-Getting_Lunch`, `-seg1`). | | `${SEG_SUFFIX}` | `string` | If a label exists for this segment, the label will be used, prepended by `-`. Otherwise, the segment index prepended by `-seg` will be used (e.g. `-Getting_Lunch`, `-seg1`).
| | `${CUT_FROM}` | `string` | The timestamp for the beginning of the segment in `hh.mm.ss.sss` format (e.g. `00.00.27.184`). | | `${CUT_FROM}` | `string` | The timestamp for the beginning of the segment in `hh.mm.ss.sss` format (e.g. `00.00.27.184`).
| | `${CUT_FROM_NUM}` | `number` | Same as `${CUT_FROM}`, but numeric, meaning it can be used with arithmetics.
| | `${CUT_TO}` | `string` | The timestamp for the ending of the segment in `hh.mm.ss.sss` format (e.g. `00.00.28.000`). | | `${CUT_TO}` | `string` | The timestamp for the ending of the segment in `hh.mm.ss.sss` format (e.g. `00.00.28.000`).
| | `${CUT_TO_NUM}` | `number` | See `${CUT_FROM_NUM}`.
| | `${CUT_DURATION}` | `string` | The duration of the segment (`CUT_TO-CUT_FROM`) in `hh.mm.ss.sss` format (e.g. `00.00.28.000`).
| | `${SEG_TAGS.XX}` | `object` | Allows you to retrieve the tags for a given segment by name. If a tag is called foo, it can be accessed with `${SEG_TAGS.foo}`. Note that if the tag does not exist, it will yield the text `undefined`. You can work around this as follows: `${SEG_TAGS.foo ?? ''}` | | `${SEG_TAGS.XX}` | `object` | Allows you to retrieve the tags for a given segment by name. If a tag is called foo, it can be accessed with `${SEG_TAGS.foo}`. Note that if the tag does not exist, it will yield the text `undefined`. You can work around this as follows: `${SEG_TAGS.foo ?? ''}`
Your files must always include at least one unique identifer (such as `${SEG_NUM}` or `${CUT_FROM}`), and it should end in `${EXT}` (or else players might not recognise the files). For instance, to achieve a filename sequence of `Beach Trip - 1.mp4`, `Beach Trip - 2.mp4`, `Beach Trip - 3.mp4`, your format should read `${FILENAME} - ${SEG_NUM}${EXT}`. If your template gives at least two duplicate output file names, LosslessCut will revert to using the default template instead. Your files must always include at least one unique identifer (such as `${SEG_NUM}` or `${CUT_FROM}`), and it should end in `${EXT}` (or else players might not recognise the files). For instance, to achieve a filename sequence of `Beach Trip - 1.mp4`, `Beach Trip - 2.mp4`, `Beach Trip - 3.mp4`, your format should read `${FILENAME} - ${SEG_NUM}${EXT}`. If your template gives at least two duplicate output file names, LosslessCut will revert to using the default template instead.

@ -36,7 +36,7 @@ function FileNameTemplateEditor(opts: {
})) { })) {
const { template: templateIn, setTemplate, defaultTemplate, generateFileNames, mergeMode } = opts; const { template: templateIn, setTemplate, defaultTemplate, generateFileNames, mergeMode } = opts;
const { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding } = useUserSettings(); const { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding, simpleMode } = useUserSettings();
const [text, setText] = useState(templateIn); const [text, setText] = useState(templateIn);
const [debouncedText] = useDebounce(text, 500); const [debouncedText] = useDebounce(text, 500);
@ -78,8 +78,20 @@ function FileNameTemplateEditor(opts: {
const availableVariables = useMemo(() => (mergeMode const availableVariables = useMemo(() => (mergeMode
? ['FILENAME', extVariable, 'EPOCH_MS', 'EXPORT_COUNT', 'FILE_EXPORT_COUNT', 'SEG_LABEL'] ? ['FILENAME', extVariable, 'EPOCH_MS', 'EXPORT_COUNT', 'FILE_EXPORT_COUNT', 'SEG_LABEL']
: ['FILENAME', extVariable, 'EPOCH_MS', 'EXPORT_COUNT', 'FILE_EXPORT_COUNT', 'SEG_LABEL', 'CUT_FROM', 'CUT_TO', segNumVariable, segNumIntVariable, selectedSegNumVariable, selectedSegNumIntVariable, segSuffixVariable, segTagsExample] : [
), [mergeMode]); 'FILENAME', extVariable, 'EPOCH_MS', 'EXPORT_COUNT', 'FILE_EXPORT_COUNT', 'SEG_LABEL',
'CUT_FROM',
...(!simpleMode ? ['CUT_FROM_NUM'] : []),
'CUT_TO',
...(!simpleMode ? ['CUT_TO_NUM'] : []),
'CUT_DURATION',
segNumVariable,
...(!simpleMode ? [segNumIntVariable] : []),
selectedSegNumVariable,
...(!simpleMode ? [selectedSegNumIntVariable] : []),
segSuffixVariable, segTagsExample,
]
), [mergeMode, simpleMode]);
// eslint-disable-next-line no-template-curly-in-string // eslint-disable-next-line no-template-curly-in-string
const isMissingExtension = validText != null && !validText.endsWith(extVariableFormatted); const isMissingExtension = validText != null && !validText.endsWith(extVariableFormatted);

@ -115,7 +115,7 @@ export const defaultCutMergedFileTemplate = '${FILENAME}-cut-merged-${EPOCH_MS}$
export const defaultMergedFileTemplate = '${FILENAME}-merged-${EPOCH_MS}${EXT}'; export const defaultMergedFileTemplate = '${FILENAME}-merged-${EPOCH_MS}${EXT}';
async function interpolateOutFileName(template: string, { epochMs, inputFileNameWithoutExt, ext, segSuffix, segNum, segNumPadded, selectedSegNum, selectedSegNumPadded, segLabels, cutFrom, cutTo, tags, exportCount, currentFileExportCount }: { async function interpolateOutFileName(template: string, { epochMs, inputFileNameWithoutExt, ext, segSuffix, segNum, segNumPadded, selectedSegNum, selectedSegNumPadded, segLabels, cutFrom, cutFromStr, cutTo, cutToStr, cutDurationStr, tags, exportCount, currentFileExportCount }: {
epochMs: number, epochMs: number,
inputFileNameWithoutExt: string, inputFileNameWithoutExt: string,
ext: string, ext: string,
@ -128,8 +128,11 @@ async function interpolateOutFileName(template: string, { epochMs, inputFileName
segNumPadded: string, segNumPadded: string,
selectedSegNum: number, selectedSegNum: number,
selectedSegNumPadded: string, selectedSegNumPadded: string,
cutFrom: string, cutFrom: number,
cutTo: string, cutFromStr: string,
cutTo: number,
cutToStr: string,
cutDurationStr: string,
tags: Record<string, string>, tags: Record<string, string>,
}>) { }>) {
const context = { const context = {
@ -142,8 +145,11 @@ async function interpolateOutFileName(template: string, { epochMs, inputFileName
[selectedSegNumVariable]: selectedSegNumPadded, [selectedSegNumVariable]: selectedSegNumPadded,
SEG_LABEL: segLabels.length === 1 ? segLabels[0] : segLabels, SEG_LABEL: segLabels.length === 1 ? segLabels[0] : segLabels,
EPOCH_MS: epochMs, EPOCH_MS: epochMs,
CUT_FROM: cutFrom, CUT_FROM: cutFromStr,
CUT_TO: cutTo, CUT_FROM_NUM: cutFrom,
CUT_TO: cutToStr,
CUT_TO_NUM: cutTo,
CUT_DURATION: cutDurationStr,
[segTagsVariable]: tags && { [segTagsVariable]: tags && {
// allow both original case and uppercase // allow both original case and uppercase
...tags, ...tags,
@ -239,6 +245,8 @@ export async function generateOutSegFileNames({ fileDuration, segmentsToExport:
const { name: inputFileNameWithoutExt } = parsePath(filePath); const { name: inputFileNameWithoutExt } = parsePath(filePath);
const cutDuration = end - start;
const segFileName = await interpolateOutFileName(template, { const segFileName = await interpolateOutFileName(template, {
epochMs, epochMs,
segNum, segNum,
@ -249,8 +257,11 @@ export async function generateOutSegFileNames({ fileDuration, segmentsToExport:
inputFileNameWithoutExt, inputFileNameWithoutExt,
ext: getOutFileExtension({ isCustomFormatSelected, outFormat: fileFormat, filePath }), ext: getOutFileExtension({ isCustomFormatSelected, outFormat: fileFormat, filePath }),
segLabels: [sanitizeName(name)], segLabels: [sanitizeName(name)],
cutFrom: formatTimecode({ seconds: start, fileNameFriendly: true }), cutFrom: start,
cutTo: formatTimecode({ seconds: end, fileNameFriendly: true }), cutFromStr: formatTimecode({ seconds: start, fileNameFriendly: true }),
cutTo: end,
cutToStr: formatTimecode({ seconds: end, fileNameFriendly: true }),
cutDurationStr: formatTimecode({ seconds: cutDuration, fileNameFriendly: true }),
tags: Object.fromEntries(Object.entries(getSegmentTags(segment)).map(([tag, value]) => [tag, sanitizeName(value)])), tags: Object.fromEntries(Object.entries(getSegmentTags(segment)).map(([tag, value]) => [tag, sanitizeName(value)])),
exportCount, exportCount,
currentFileExportCount, currentFileExportCount,

Loading…
Cancel
Save