v0.2.0 introduced Player mode with floating controls, auto-hide UI,
PiP, fullscreen, JKL shuttle, speed selector, captions, and cut-clip.
v0.2.1 builds on that with extensive UI polish and new features:
Player Controls Polish:
- Enlarged center playback icons (Play 48, Rewind/FF 36) with filled SVGs
- Moved Cut Clip scissors into center group with hidden cut-before button
- Narrowed floating panel from 70% to 60% width
- Custom-styled volume slider matching seek bar (linear-gradient fill)
- Pure opacity fade animation (0.35s) replacing slide-in
- Tinted panel background (rgba(53,54,80,0.75)) visible on black canvas
- Drop shadow on center transport controls
- Scissors buttons dimmed via color (not opacity) with single-property transitions
- ScissorsLineDashed icon for cut-before variant
- Timestamps in single seek row (time | bar | duration) with sans-serif font
- Milliseconds shown at smaller size, revealed on hover
Screenshot Capture (S / Shift+S):
- Canvas compositing at native video resolution with optional caption burn-in
- Caption rendering matches CSS preview (font, background, outline, word highlight)
- New Rust save_screenshot command using existing filename pattern
- Output: {title} - {ms}ms-screenshot.png
- crossorigin=anonymous on video element for untainted canvas
Other:
- Q keyboard shortcut to quit via Tauri process plugin
- formatTimeParts() utility for split timestamp rendering
- Updated About dialog shortcuts list
- Version bump to 0.2.1
Co-authored-by: Cursor <cursoragent@cursor.com>
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
export function formatTime(totalSeconds: number): string {
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = Math.floor(totalSeconds % 60);
|
|
const millis = Math.round((totalSeconds % 1) * 1000);
|
|
|
|
const mm = String(minutes).padStart(2, '0');
|
|
const ss = String(seconds).padStart(2, '0');
|
|
const ms = String(millis).padStart(3, '0');
|
|
|
|
if (hours > 0) {
|
|
return `${hours}:${mm}:${ss}.${ms}`;
|
|
}
|
|
return `${mm}:${ss}.${ms}`;
|
|
}
|
|
|
|
export function formatTimeParts(totalSeconds: number): { main: string; ms: string } {
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = Math.floor(totalSeconds % 60);
|
|
const millis = Math.round((totalSeconds % 1) * 1000);
|
|
|
|
const mm = String(minutes).padStart(2, '0');
|
|
const ss = String(seconds).padStart(2, '0');
|
|
const ms = String(millis).padStart(3, '0');
|
|
|
|
const main = hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
return { main, ms: `.${ms}` };
|
|
}
|
|
|
|
export function parseTime(str: string): number | null {
|
|
const hmsMatch = str.match(/^(\d+):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/);
|
|
if (hmsMatch) {
|
|
const h = parseInt(hmsMatch[1], 10);
|
|
const m = parseInt(hmsMatch[2], 10);
|
|
const s = parseInt(hmsMatch[3], 10);
|
|
const ms = hmsMatch[4] ? parseInt(hmsMatch[4].padEnd(3, '0'), 10) : 0;
|
|
return h * 3600 + m * 60 + s + ms / 1000;
|
|
}
|
|
|
|
const msMatch = str.match(/^(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/);
|
|
if (msMatch) {
|
|
const m = parseInt(msMatch[1], 10);
|
|
const s = parseInt(msMatch[2], 10);
|
|
const ms = msMatch[3] ? parseInt(msMatch[3].padEnd(3, '0'), 10) : 0;
|
|
return m * 60 + s + ms / 1000;
|
|
}
|
|
|
|
return null;
|
|
}
|