diff --git a/src/main.ts b/src/main.ts index 8412708..2034e8a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,7 @@ import axios from 'axios'; import { autoUpdater } from 'electron-updater'; import { compareUpdateVersions, isNewerUpdateVersion, normalizeUpdateVersion } from './main/domain/update-version-utils'; import { writeFileAtomicSync } from './main/infra/fs-atomic'; +import { parseDuration, formatDuration, formatDurationDashed } from './main/infra/duration'; import { CustomClip, MergeGroupItem, MergeGroup, QueueItem, DownloadProgress, DownloadResult } from './types'; import { setDebugLogFn, initToolDirs, @@ -1073,38 +1074,6 @@ function appendDebugLog(message: string, details?: unknown): void { setDebugLogFn(appendDebugLog); initToolDirs(TOOLS_STREAMLINK_DIR, TOOLS_FFMPEG_DIR, () => app.getPath('temp')); -// ========================================== -// DURATION HELPERS -// ========================================== -function parseDuration(duration: string): number { - let seconds = 0; - const hours = duration.match(/(\d+)h/); - const minutes = duration.match(/(\d+)m/); - const secs = duration.match(/(\d+)s/); - - if (hours) seconds += parseInt(hours[1]) * 3600; - if (minutes) seconds += parseInt(minutes[1]) * 60; - if (secs) seconds += parseInt(secs[1]); - - return seconds; -} - -function formatDuration(seconds: number): string { - if (!isFinite(seconds) || seconds < 0) return '00:00:00'; - const h = Math.floor(seconds / 3600); - const m = Math.floor((seconds % 3600) / 60); - const s = Math.floor(seconds % 60); - return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; -} - -function formatDurationDashed(seconds: number): string { - if (!isFinite(seconds) || seconds < 0) return '00-00-00'; - const h = Math.floor(seconds / 3600); - const m = Math.floor((seconds % 3600) / 60); - const s = Math.floor(seconds % 60); - return `${h.toString().padStart(2, '0')}-${m.toString().padStart(2, '0')}-${s.toString().padStart(2, '0')}`; -} - const claimedFilenames = new Set(); const itemClaimedFilenames = new Map>(); diff --git a/src/main/infra/duration.test.ts b/src/main/infra/duration.test.ts new file mode 100644 index 0000000..f776ff7 --- /dev/null +++ b/src/main/infra/duration.test.ts @@ -0,0 +1,65 @@ +import { test, expect, describe } from 'vitest'; +import { parseDuration, formatDuration, formatDurationDashed } from './duration'; + +describe('parseDuration', () => { + test('1h2m3s = 3723', () => { + expect(parseDuration('1h2m3s')).toBe(3723); + }); + test('45m = 2700', () => { + expect(parseDuration('45m')).toBe(2700); + }); + test('10s = 10', () => { + expect(parseDuration('10s')).toBe(10); + }); + test('empty string = 0', () => { + expect(parseDuration('')).toBe(0); + }); + test('unknown format = 0', () => { + expect(parseDuration('abcdef')).toBe(0); + }); + test('partial 2h = 7200', () => { + expect(parseDuration('2h')).toBe(7200); + }); + test('h and s without m = 3601', () => { + expect(parseDuration('1h1s')).toBe(3601); + }); +}); + +describe('formatDuration', () => { + test('3723 = 01:02:03', () => { + expect(formatDuration(3723)).toBe('01:02:03'); + }); + test('0 = 00:00:00', () => { + expect(formatDuration(0)).toBe('00:00:00'); + }); + test('negative = 00:00:00', () => { + expect(formatDuration(-1)).toBe('00:00:00'); + }); + test('Infinity = 00:00:00', () => { + expect(formatDuration(Infinity)).toBe('00:00:00'); + }); + test('NaN = 00:00:00', () => { + expect(formatDuration(NaN)).toBe('00:00:00'); + }); + test('3600 = 01:00:00', () => { + expect(formatDuration(3600)).toBe('01:00:00'); + }); + test('86399 = 23:59:59', () => { + expect(formatDuration(86399)).toBe('23:59:59'); + }); + test('fractional seconds floored', () => { + expect(formatDuration(3723.9)).toBe('01:02:03'); + }); +}); + +describe('formatDurationDashed', () => { + test('3723 = 01-02-03', () => { + expect(formatDurationDashed(3723)).toBe('01-02-03'); + }); + test('negative = 00-00-00', () => { + expect(formatDurationDashed(-1)).toBe('00-00-00'); + }); + test('NaN = 00-00-00', () => { + expect(formatDurationDashed(NaN)).toBe('00-00-00'); + }); +}); diff --git a/src/main/infra/duration.ts b/src/main/infra/duration.ts new file mode 100644 index 0000000..e2aa5ec --- /dev/null +++ b/src/main/infra/duration.ts @@ -0,0 +1,28 @@ +export function parseDuration(duration: string): number { + let seconds = 0; + const hours = duration.match(/(\d+)h/); + const minutes = duration.match(/(\d+)m/); + const secs = duration.match(/(\d+)s/); + + if (hours) seconds += parseInt(hours[1]) * 3600; + if (minutes) seconds += parseInt(minutes[1]) * 60; + if (secs) seconds += parseInt(secs[1]); + + return seconds; +} + +export function formatDuration(seconds: number): string { + if (!isFinite(seconds) || seconds < 0) return '00:00:00'; + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; +} + +export function formatDurationDashed(seconds: number): string { + if (!isFinite(seconds) || seconds < 0) return '00-00-00'; + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + return `${h.toString().padStart(2, '0')}-${m.toString().padStart(2, '0')}-${s.toString().padStart(2, '0')}`; +}