fix(timecode): correct framesToTC for all frames beyond position 3
The previous algorithm used `if (rem >= DROP)` (i.e. rem >= 4) to decide whether to advance to the next minute group. This fired immediately at frame 4, still inside minute 0 of the 10-minute non-drop group, producing 00:01:00;00 for what should be 00:00:00;04. Every timecode display in the editor was wrong for any position past the first four frames. Each 10-minute block has one 3600-frame non-drop minute followed by nine 3596-frame drop minutes. The fix checks `rem < FRAMES_FIRST_MIN` (3600) to identify the non-drop minute, then subtracts it before dividing into drop-minute slots. Frame labels within drop minutes are shifted by DROP (+4) so the first usable label is :00;04 as per SMPTE 12M.
This commit is contained in:
parent
b23700f30a
commit
3c689ccddf
1 changed files with 31 additions and 15 deletions
|
|
@ -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)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue