diff --git a/services/web-ui/public/js/timecode.js b/services/web-ui/public/js/timecode.js index e7bebe4..52f0181 100644 --- a/services/web-ui/public/js/timecode.js +++ b/services/web-ui/public/js/timecode.js @@ -6,33 +6,49 @@ 'use strict'; // 59.94 = 60000/1001 - const FPS_EXACT = 60000 / 1001; - const NOM = 60; // nominal integer fps - const DROP = 4; // frames dropped per minute (except every 10th) - const FRAMES_PER_MIN = NOM * 60 - DROP; // 3596 - const FRAMES_PER_10MIN = FRAMES_PER_MIN * 10 + DROP; // 35964 - const FRAMES_PER_HOUR = FRAMES_PER_10MIN * 6; // 215784 + const FPS_EXACT = 60000 / 1001; + const NOM = 60; // nominal integer fps + const DROP = 4; // frames dropped per minute (except every 10th) + const FRAMES_FIRST_MIN = NOM * 60; // 3600 – non-drop minute + const FRAMES_PER_MIN = NOM * 60 - DROP; // 3596 – drop minute + const FRAMES_PER_10MIN = FRAMES_PER_MIN * 10 + DROP; // 35964 + const FRAMES_PER_HOUR = FRAMES_PER_10MIN * 6; // 215784 function pad2(n) { return String(Math.floor(n)).padStart(2, '0'); } /** * Convert a frame count to a 59.94 DF timecode string: HH:MM:SS;FF + * + * Each 10-minute group contains one non-drop minute (3600 frames) followed + * by nine drop minutes (3596 frames each). Frame numbers 0–3 are skipped at + * the start of every drop minute; we account for this by shifting the + * within-minute frame index by DROP when computing ss/ff. */ function framesToTC(totalFrames) { const fc = Math.max(0, Math.round(totalFrames)); const h = Math.floor(fc / FRAMES_PER_HOUR); let rem = fc % FRAMES_PER_HOUR; - const tm = Math.floor(rem / FRAMES_PER_10MIN); // tens of minutes (0–5) + const tm = Math.floor(rem / FRAMES_PER_10MIN); // tens-of-minutes group (0–5) rem = rem % FRAMES_PER_10MIN; - let m = 0; - if (rem >= DROP) { - m = Math.floor((rem - DROP) / FRAMES_PER_MIN) + 1; - rem = (rem - DROP) % FRAMES_PER_MIN; + + let m, ss, ff; + if (rem < FRAMES_FIRST_MIN) { + // First minute of the 10-min group — no frame drop + m = 0; + ss = Math.floor(rem / NOM); + ff = rem % NOM; + } else { + // Minutes 1–9: frame numbers 0–3 at second :00 are skipped + rem -= FRAMES_FIRST_MIN; + m = Math.floor(rem / FRAMES_PER_MIN) + 1; + const remInMin = rem % FRAMES_PER_MIN; + // Shift by DROP so the label starts at :00;04 instead of :00;00 + const adj = remInMin + DROP; + ss = Math.floor(adj / NOM); + ff = adj % NOM; } - const M = tm * 10 + m; - const s = Math.floor(rem / NOM); - const ff = rem % NOM; - return `${pad2(h)}:${pad2(M)}:${pad2(s)};${pad2(ff)}`; + const M = tm * 10 + m; + return `${pad2(h)}:${pad2(M)}:${pad2(ss)};${pad2(ff)}`; } /**