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.
| ✅ | `${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).
| ✅ | `${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_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_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`).
| | `${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_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 ?? ''}`
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 { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding } = useUserSettings();
const { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding, simpleMode } = useUserSettings();
const [text, setText] = useState(templateIn);
const [debouncedText] = useDebounce(text, 500);
@ -78,8 +78,20 @@ function FileNameTemplateEditor(opts: {
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', '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
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}';
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,
inputFileNameWithoutExt: string,
ext: string,
@ -128,8 +128,11 @@ async function interpolateOutFileName(template: string, { epochMs, inputFileName
segNumPadded: string,
selectedSegNum: number,
selectedSegNumPadded: string,
cutFrom: string,
cutTo: string,
cutFrom: number,
cutFromStr: string,
cutTo: number,
cutToStr: string,
cutDurationStr: string,
tags: Record<string, string>,
}>) {
const context = {
@ -142,8 +145,11 @@ async function interpolateOutFileName(template: string, { epochMs, inputFileName
[selectedSegNumVariable]: selectedSegNumPadded,
SEG_LABEL: segLabels.length === 1 ? segLabels[0] : segLabels,
EPOCH_MS: epochMs,
CUT_FROM: cutFrom,
CUT_TO: cutTo,
CUT_FROM: cutFromStr,
CUT_FROM_NUM: cutFrom,
CUT_TO: cutToStr,
CUT_TO_NUM: cutTo,
CUT_DURATION: cutDurationStr,
[segTagsVariable]: tags && {
// allow both original case and uppercase
...tags,
@ -239,6 +245,8 @@ export async function generateOutSegFileNames({ fileDuration, segmentsToExport:
const { name: inputFileNameWithoutExt } = parsePath(filePath);
const cutDuration = end - start;
const segFileName = await interpolateOutFileName(template, {
epochMs,
segNum,
@ -249,8 +257,11 @@ export async function generateOutSegFileNames({ fileDuration, segmentsToExport:
inputFileNameWithoutExt,
ext: getOutFileExtension({ isCustomFormatSelected, outFormat: fileFormat, filePath }),
segLabels: [sanitizeName(name)],
cutFrom: formatTimecode({ seconds: start, fileNameFriendly: true }),
cutTo: formatTimecode({ seconds: end, fileNameFriendly: true }),
cutFrom: start,
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)])),
exportCount,
currentFileExportCount,

Loading…
Cancel
Save