Skip to content

Commit c7d1d34

Browse files
feat: parse framerate from fraction
1 parent b915eb4 commit c7d1d34

File tree

2 files changed

+79
-0
lines changed

2 files changed

+79
-0
lines changed

src/rate.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {
2+
Rate,
3+
RateFromFraction,
4+
Rate_23_976,
5+
Rate_24,
6+
Rate_29_97,
7+
Rate_30,
8+
Rate_59_94,
9+
Rate_60,
10+
} from './rate';
11+
12+
describe('testing create framerate from fraction', () => {
13+
test('frame rate from fraction', () => {
14+
const cases: [number, number, Rate][] = [
15+
[24000, 1001, Rate_23_976],
16+
[24, 1, Rate_24],
17+
[30, 1, Rate_30],
18+
[30000, 1001, Rate_29_97],
19+
[60, 1, Rate_60],
20+
[60000, 1001, Rate_59_94],
21+
];
22+
for (const [num, den, expected] of cases) {
23+
const rate = RateFromFraction(num, den);
24+
expect(rate.num).toBe(expected.num);
25+
expect(rate.den).toBe(expected.den);
26+
expect(rate.nominal).toBe(expected.nominal);
27+
expect(rate.drop).toBe(expected.drop);
28+
}
29+
});
30+
test('frame rate from fraction for built-in rates', () => {
31+
const cases: Rate[] = [
32+
Rate_23_976,
33+
Rate_24,
34+
Rate_30,
35+
Rate_29_97,
36+
Rate_60,
37+
Rate_59_94,
38+
];
39+
for (const rate of cases) {
40+
const newRate = RateFromFraction(rate.num, rate.den);
41+
expect(newRate.num).toBe(rate.num);
42+
expect(newRate.den).toBe(rate.den);
43+
expect(newRate.nominal).toBe(rate.nominal);
44+
expect(newRate.drop).toBe(rate.drop);
45+
}
46+
});
47+
});

src/rate.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const SECONDS_PER_HOUR = 3600;
2+
const DROP_OCCURRENCES_PER_HOUR = 54;
3+
14
// Rate represents a frame rate for a timecode
25
export interface Rate {
36
nominal: number;
@@ -26,6 +29,35 @@ export function ParseRate(str: string): Rate | null {
2629
}
2730
}
2831

32+
export function RateFromFraction(num: number, den: number): Rate {
33+
// Calculate the nominal frame rate (number of frames in a second without drops)
34+
const nominal = num % den === 0 ? num / den : den - (num % den);
35+
36+
// format it as a string (ie. 23.976)
37+
let str = (num / den).toFixed(3);
38+
while (str.endsWith('0')) {
39+
str = str.slice(0, -1);
40+
}
41+
if (str.endsWith('.')) {
42+
str = str.slice(0, -1);
43+
}
44+
45+
// Calculate the number of frames to skip per drop occurrence
46+
const actualFramesPerHour = Math.floor((num * SECONDS_PER_HOUR) / den);
47+
const nominalFramesPerHour = nominal * SECONDS_PER_HOUR;
48+
const totalFramesDropped = nominalFramesPerHour - actualFramesPerHour;
49+
const framesPerDrop = Math.round(
50+
totalFramesDropped / DROP_OCCURRENCES_PER_HOUR
51+
);
52+
53+
return {
54+
nominal,
55+
drop: framesPerDrop,
56+
num,
57+
den,
58+
};
59+
}
60+
2961
// 23.976 has exactly 24 frames per second. However, the textual representation of timecodes using this rate
3062
// skip two frames every minute, except when the minute is a multiple of 10. This is because 23.976 footage
3163
// actually does display at a rate of 23.976 frames each second on televisions. To ensure that the first

0 commit comments

Comments
 (0)