feat(clip): add Parts-format preset to Trim-Clip dialog
The Trim-Clip filename-format radio group only offered three presets
(simple, timestamp, custom template). Users who organise their archive
with the global filename_template_parts pattern (e.g.
08.05.2026_Part07.mp4) had to switch to "custom template" and retype
{date}_Part{part_padded}.mp4 every time.
New "parts" preset:
- index.html: 4th radio option, span#formatParts for the live preview
- types.ts + renderer-globals.d.ts: filenameFormat union extended
- main.ts: makeClipFilename branch produces ${dateStr}_Part${padded}.mp4;
sanitizeCustomClip whitelists "parts" so persisted queue items with
the new format survive a restart
- renderer.ts: getSelectedFilenameFormat returns "parts"; live preview
via partNum.padStart(2, "0")
- DE/EN locales: clips.formatParts label
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
173ae61a3f
commit
013e8be1f0
@ -106,6 +106,11 @@
|
|||||||
style="width: 18px; height: 18px; accent-color: #9146FF;">
|
style="width: 18px; height: 18px; accent-color: #9146FF;">
|
||||||
<span id="formatTimestamp" style="color: #aaa;">01.02.2026_CLIP_00-00-00_1.mp4 (mit Zeitstempel)</span>
|
<span id="formatTimestamp" style="color: #aaa;">01.02.2026_CLIP_00-00-00_1.mp4 (mit Zeitstempel)</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 8px;">
|
||||||
|
<input type="radio" name="filenameFormat" value="parts" onchange="updateFilenameExamples()"
|
||||||
|
style="width: 18px; height: 18px; accent-color: #9146FF;">
|
||||||
|
<span id="formatParts" style="color: #aaa;">01.02.2026_Part01.mp4 (Parts-Format)</span>
|
||||||
|
</label>
|
||||||
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 10px;">
|
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 10px;">
|
||||||
<input type="radio" name="filenameFormat" value="template" onchange="updateFilenameExamples()"
|
<input type="radio" name="filenameFormat" value="template" onchange="updateFilenameExamples()"
|
||||||
style="width: 18px; height: 18px; accent-color: #9146FF;">
|
style="width: 18px; height: 18px; accent-color: #9146FF;">
|
||||||
|
|||||||
12
src/main.ts
12
src/main.ts
@ -364,7 +364,7 @@ function sanitizeCustomClip(raw: unknown): CustomClip | undefined {
|
|||||||
if (!Number.isFinite(startSec) || !Number.isFinite(durationSec) || durationSec <= 0 || !Number.isFinite(startPart)) return undefined;
|
if (!Number.isFinite(startSec) || !Number.isFinite(durationSec) || durationSec <= 0 || !Number.isFinite(startPart)) return undefined;
|
||||||
|
|
||||||
const filenameFormat = raw.filenameFormat;
|
const filenameFormat = raw.filenameFormat;
|
||||||
if (filenameFormat !== 'simple' && filenameFormat !== 'timestamp' && filenameFormat !== 'template') return undefined;
|
if (filenameFormat !== 'simple' && filenameFormat !== 'timestamp' && filenameFormat !== 'template' && filenameFormat !== 'parts') return undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startSec: Math.max(0, startSec),
|
startSec: Math.max(0, startSec),
|
||||||
@ -2666,9 +2666,15 @@ async function downloadVOD(
|
|||||||
const s = Math.floor(startOffset % 60);
|
const s = Math.floor(startOffset % 60);
|
||||||
const timeStr = `${h.toString().padStart(2, '0')}-${m.toString().padStart(2, '0')}-${s.toString().padStart(2, '0')}`;
|
const timeStr = `${h.toString().padStart(2, '0')}-${m.toString().padStart(2, '0')}-${s.toString().padStart(2, '0')}`;
|
||||||
return path.join(folder, `${dateStr}_CLIP_${timeStr}_${partNum}.mp4`);
|
return path.join(folder, `${dateStr}_CLIP_${timeStr}_${partNum}.mp4`);
|
||||||
} else {
|
|
||||||
return path.join(folder, `${dateStr}_${partNum}.mp4`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clip.filenameFormat === 'parts') {
|
||||||
|
// Mirrors the global filename_template_parts default:
|
||||||
|
// `{date}_Part{part_padded}.mp4` -> e.g. 08.05.2026_Part07.mp4
|
||||||
|
return path.join(folder, `${dateStr}_Part${partNum.toString().padStart(2, '0')}.mp4`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(folder, `${dateStr}_${partNum}.mp4`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If clip is longer than part duration, split into parts
|
// If clip is longer than part duration, split into parts
|
||||||
|
|||||||
2
src/renderer-globals.d.ts
vendored
2
src/renderer-globals.d.ts
vendored
@ -34,7 +34,7 @@ interface CustomClip {
|
|||||||
startSec: number;
|
startSec: number;
|
||||||
durationSec: number;
|
durationSec: number;
|
||||||
startPart: number;
|
startPart: number;
|
||||||
filenameFormat: 'simple' | 'timestamp' | 'template';
|
filenameFormat: 'simple' | 'timestamp' | 'template' | 'parts';
|
||||||
filenameTemplate?: string;
|
filenameTemplate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -189,6 +189,7 @@ const UI_TEXT_DE = {
|
|||||||
unknownError: 'Unbekannter Fehler',
|
unknownError: 'Unbekannter Fehler',
|
||||||
formatSimple: '(Standard)',
|
formatSimple: '(Standard)',
|
||||||
formatTimestamp: '(mit Zeitstempel)',
|
formatTimestamp: '(mit Zeitstempel)',
|
||||||
|
formatParts: '(Parts-Format)',
|
||||||
formatTemplate: '(benutzerdefiniert)',
|
formatTemplate: '(benutzerdefiniert)',
|
||||||
templateEmpty: 'Das Template darf im benutzerdefinierten Modus nicht leer sein.',
|
templateEmpty: 'Das Template darf im benutzerdefinierten Modus nicht leer sein.',
|
||||||
templatePlaceholder: '{date}_{part}.mp4',
|
templatePlaceholder: '{date}_{part}.mp4',
|
||||||
|
|||||||
@ -189,6 +189,7 @@ const UI_TEXT_EN = {
|
|||||||
unknownError: 'Unknown error',
|
unknownError: 'Unknown error',
|
||||||
formatSimple: '(default)',
|
formatSimple: '(default)',
|
||||||
formatTimestamp: '(with timestamp)',
|
formatTimestamp: '(with timestamp)',
|
||||||
|
formatParts: '(parts naming)',
|
||||||
formatTemplate: '(custom template)',
|
formatTemplate: '(custom template)',
|
||||||
templateEmpty: 'Template cannot be empty in custom template mode.',
|
templateEmpty: 'Template cannot be empty in custom template mode.',
|
||||||
templatePlaceholder: '{date}_{part}.mp4',
|
templatePlaceholder: '{date}_{part}.mp4',
|
||||||
|
|||||||
@ -535,9 +535,12 @@ function formatSecondsWithPattern(totalSeconds: number, pattern: string): string
|
|||||||
.replace(/\\(.)/g, '$1');
|
.replace(/\\(.)/g, '$1');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedFilenameFormat(): 'simple' | 'timestamp' | 'template' {
|
function getSelectedFilenameFormat(): 'simple' | 'timestamp' | 'template' | 'parts' {
|
||||||
const selected = query<HTMLInputElement>('input[name="filenameFormat"]:checked').value;
|
const selected = query<HTMLInputElement>('input[name="filenameFormat"]:checked').value;
|
||||||
return selected === 'template' ? 'template' : selected === 'timestamp' ? 'timestamp' : 'simple';
|
if (selected === 'template') return 'template';
|
||||||
|
if (selected === 'timestamp') return 'timestamp';
|
||||||
|
if (selected === 'parts') return 'parts';
|
||||||
|
return 'simple';
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFilenameTemplateVisibility(): void {
|
function updateFilenameTemplateVisibility(): void {
|
||||||
@ -895,6 +898,7 @@ function updateFilenameExamples(): void {
|
|||||||
|
|
||||||
byId('formatSimple').textContent = `${dateStr}_${partNum}.mp4 ${UI_TEXT.clips.formatSimple}`;
|
byId('formatSimple').textContent = `${dateStr}_${partNum}.mp4 ${UI_TEXT.clips.formatSimple}`;
|
||||||
byId('formatTimestamp').textContent = `${dateStr}_CLIP_${timeStr}_${partNum}.mp4 ${UI_TEXT.clips.formatTimestamp}`;
|
byId('formatTimestamp').textContent = `${dateStr}_CLIP_${timeStr}_${partNum}.mp4 ${UI_TEXT.clips.formatTimestamp}`;
|
||||||
|
byId('formatParts').textContent = `${dateStr}_Part${partNum.padStart(2, '0')}.mp4 ${UI_TEXT.clips.formatParts}`;
|
||||||
byId('formatTemplate').textContent = `${buildTemplatePreview(template, {
|
byId('formatTemplate').textContent = `${buildTemplatePreview(template, {
|
||||||
title: clipDialogData.title,
|
title: clipDialogData.title,
|
||||||
date,
|
date,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ export interface CustomClip {
|
|||||||
startSec: number;
|
startSec: number;
|
||||||
durationSec: number;
|
durationSec: number;
|
||||||
startPart: number;
|
startPart: number;
|
||||||
filenameFormat: 'simple' | 'timestamp' | 'template';
|
filenameFormat: 'simple' | 'timestamp' | 'template' | 'parts';
|
||||||
filenameTemplate?: string;
|
filenameTemplate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user