1export function fmtDuration(sec: number): string {
2 if (!isFinite(sec) || sec < 0) sec = 0;
3 const s = Math.floor(sec % 60);
4 const m = Math.floor((sec / 60) % 60);
5 const h = Math.floor(sec / 3600);
6 const ss = String(s).padStart(2, '0');
7 return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`;
8}
10// m:ss.t, for editing
11export function fmtTimecode(sec: number): string {
12 if (!isFinite(sec) || sec < 0) sec = 0;
13 const tenths = Math.floor((sec * 10) % 10);
14 return `${fmtDuration(sec)}.${tenths}`;
15}
17export function fmtDate(iso: string): string {
18 const d = new Date(iso);
19 return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
20}
22export function fmtDateTime(iso: string): string {
23 const d = new Date(iso);
24 return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
25}
27export function fmtBytes(n: number): string {
28 if (n < 1024) return `${n} B`;
29 if (n < 1024 ** 2) return `${(n / 1024).toFixed(0)} KB`;
30 if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`;
31 return `${(n / 1024 ** 3).toFixed(2)} GB`;
32}
34export function decodePeaks(data: string): Uint8Array {
35 const bin = atob(data);
36 const out = new Uint8Array(bin.length);
37 for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
38 return out;
39}
41export function loadPref<T>(key: string, fallback: T): T {
42 try {
43 const s = localStorage.getItem(key);
44 return s ? { ...fallback, ...JSON.parse(s) } : fallback;
45 } catch {
46 return fallback;
47 }
48}
50export function savePref(key: string, value: unknown) {
51 try {
52 localStorage.setItem(key, JSON.stringify(value));
53 } catch {
54 // storage unavailable
55 }
56}