Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ metadata and the backend fallback mirror it.

### Fixed

- Carry rounded dubbing timestamp seconds into minutes, avoiding invalid labels such as `0:60.0` — thanks @rudycelekli! (#2660)

- MCP speech tools wait through model loading and progress-extended CPU renders instead of timing out before the backend (#2609)

## [0.5.7] — 2026-10-05
Expand Down
2 changes: 2 additions & 0 deletions docs/dubbing/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ filters; POSIX filenames keep literal backslashes. Two caption styles are availa
Smart Fit exports burn captions after the video retime, so both styles follow
the fitted timeline. The karaoke script can also be downloaded on its own from
`GET /dub/ass/{job_id}` as an `.ass` sidecar.

The dubbing timeline displays elapsed times in minutes and tenths of seconds. A rounded second carries into the minute, so a boundary such as 59.96 seconds displays as `1:00.0`, keeping timestamp labels consistent in the timeline and segment editor. Display rounding does not change segment timing or exported timestamps.
6 changes: 2 additions & 4 deletions electron/src/renderer/src/features/dub/dub-timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { formatTime } from '@shared/utils/format';
import { HeadphonesIcon, LoaderCircleIcon, PlayIcon, TriangleAlertIcon, ZoomInIcon, ZoomOutIcon, MaximizeIcon } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -28,10 +29,7 @@ type Gesture = {
end: number;
};

const formatTime = (seconds: number) => {
const minutes = Math.floor(seconds / 60);
return `${minutes}:${(seconds % 60).toFixed(1).padStart(4, '0')}`;
};


export function DubTimeline({
segments,
Expand Down
2 changes: 2 additions & 0 deletions electron/src/shared/utils/format.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export function formatTime(seconds: number): string;
export function probeAudioDuration(file: Blob): Promise<number | null>;
6 changes: 4 additions & 2 deletions electron/src/shared/utils/format.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export function formatTime(s) {
const m = Math.floor(s / 60);
const sec = (s % 60).toFixed(1);
// Round once before decomposing, so a tenths carry advances the minute.
const tenths = Math.round(s * 10);
const m = Math.floor(tenths / 600);
const sec = ((tenths % 600) / 10).toFixed(1);
return `${m}:${sec.padStart(4, '0')}`;
}

Expand Down
4 changes: 4 additions & 0 deletions electron/src/shared/utils/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ describe('format utils', () => {
expect(formatTime(0)).toBe('0:00.0');
expect(formatTime(61)).toBe('1:01.0');
expect(formatTime(3661)).toBe('61:01.0');
expect(formatTime(59.94)).toBe('0:59.9');
expect(formatTime(59.96)).toBe('1:00.0');
expect(formatTime(119.96)).toBe('2:00.0');
expect(formatTime(3599.96)).toBe('60:00.0');
});
});

Expand Down
Loading