Compare commits
2 Commits
173ae61a3f
...
37b793b9e8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37b793b9e8 | ||
|
|
013e8be1f0 |
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.5.11",
|
"version": "4.5.12",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.5.11",
|
"version": "4.5.12",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.5.11",
|
"version": "4.5.12",
|
||||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"author": "xRangerDE",
|
"author": "xRangerDE",
|
||||||
|
|||||||
@ -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