Skip to content

Commit f9c0431

Browse files
committed
fix bugs found during exhaustive testing against python library
1 parent 184c864 commit f9c0431

File tree

4 files changed

+100106
-41
lines changed

4 files changed

+100106
-41
lines changed

src/main/java/org/moormanity/smpte/timecode/FrameRate.java

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,81 +18,81 @@ public enum FrameRate {
1818
* 24 fps
1919
* (film, ATSC, 2k, 4k, 6k)
2020
*/
21-
_24("24 fps", 24, 1, 24, false, 0),
21+
_24("24 fps", 24, 1, 24, false, 0),
2222

2323

2424
/**
2525
* 24.98 fps
2626
* This frame rate is commonly used to facilitate transfers between PAL and NTSC video and film sources. It is mostly used to compensate for some error.
2727
*/
28-
_24_98("24.98 fps", 25000, 1001, 25, false, 0),
28+
_24_98("24.98 fps", 25000, 1001, 25, false, 0),
2929

3030
/**
3131
* 25 fps
3232
* (PAL, used in Europe, Uruguay, Argentina, Australia), SECAM, DVB, ATSC)
3333
*/
34-
_25("25 fps", 25, 1, 25, false, 0),
34+
_25("25 fps", 25, 1, 25, false, 0),
3535

3636
/**
3737
* 29.97 fps (30p)
3838
* (NTSC American System (US, Canada, Mexico, Colombia, etc.), ATSC, PAL-M (Brazil))
3939
* (30 / 1.001) frame/sec
4040
*/
41-
_29_97("29.97 fps", 30000, 1001, 30, false, 0),
41+
_29_97("29.97 fps", 30000, 1001, 30, false, 0),
4242
/**
4343
* 29.97 drop fps
4444
*/
45-
_29_97_drop("29.97 fps drop", 30000, 1001, 30, true, 2),
45+
_29_97_drop("29.97 fps drop", 30000, 1001, 30, true, 2),
4646

4747
/**
4848
* 30 fps
4949
* (ATSC) This is the frame count of NTSC broadcast video. However, the actual frame rate or speed of the video format runs at 29.97 fps.
5050
* This timecode clock does not run in realtime. It is slightly slower by 0.1%.
5151
* ie: 1:00:00:00:00 (1 day/24 hours) at 30 fps is approx 1:00:00:00;02 in 29.97dfA
5252
*/
53-
_30("30", 30, 1, 30, false, 0),
53+
_30("30", 30, 1, 30, false, 0),
5454

5555
/**
5656
* 30 drop fps
5757
*/
58-
_30_drop("30 drop", 3, 1, 30, true, 2),
58+
_30_drop("30 drop", 3, 1, 30, true, 2),
5959

6060
/**
6161
* 47.952 (48p?)
6262
* Double 23.976 fps
6363
*/
64-
_47_952("47.952 fps", 48000, 1001, 48, false, 0),
64+
_47_952("47.952 fps", 48000, 1001, 48, false, 0),
6565

6666
/**
6767
* 48 fps
6868
* Double 24 fps
6969
*/
70-
_48("48 fps", 48, 1, 48, false, 0),
70+
_48("48 fps", 48, 1, 48, false, 0),
7171

7272
/**
7373
* 50 fps
7474
* Double 25 fps\
7575
*/
76-
_50("50 fps", 50, 1, 50, false, 0),
76+
_50("50 fps", 50, 1, 50, false, 0),
7777

7878
/**
7979
* 59.94 fps
8080
* Double 29.97 fps
8181
* This video frame rate is supported by high definition cameras and is compatible with NTSC (29.97 fps).
8282
*/
83-
_59_94("59.94 fps", 60000, 1001, 60, false, 0),
83+
_59_94("59.94 fps", 60000, 1001, 60, false, 0),
8484

8585
/**
8686
* 59.94 fps drop
8787
*/
88-
_59_94_drop("59.94 fps drop", 60000, 1001, 60, true, 4),
88+
_59_94_drop("59.94 fps drop", 60000, 1001, 60, true, 4),
8989

9090
/**
9191
* 60 fps
9292
* Double 30 fps
9393
* This video frame rate is supported by many high definition cameras. However, the NTSC compatible 59.94 fps frame rate is much more common.
9494
*/
95-
_60("60 fps", 60, 1, 60, false, 0),
95+
_60("60 fps", 60, 1, 60, false, 0),
9696

9797

9898
/**
@@ -101,31 +101,31 @@ public enum FrameRate {
101101
* See the description for 30 drop for more info.
102102
* - Warning: This is not a video frame rate - it is a display rate only.
103103
*/
104-
_60_drop("60 fps drop", 60, 1, 60, true, 4),
104+
_60_drop("60 fps drop", 60, 1, 60, true, 4),
105105

106106
/**
107107
* 100 fps
108108
* Double 50 fps / quadruple 25 fps
109109
*/
110-
_100("100 fps", 100, 1, 100, false, 0),
110+
_100("100 fps", 100, 1, 100, false, 0),
111111

112112
/**
113113
* 119.88 fps
114114
* Double 59.94 fps / quadruple 29.97 fps
115115
*/
116-
_119_88("119.88 fps", 120_000, 1001, 120, false, 0),
116+
_119_88("119.88 fps", 120_000, 1001, 120, false, 0),
117117

118118
/**
119119
* 119.88 drop fps
120120
* Double 59.94 drop fps / quadruple 29.97 drop fps
121121
*/
122-
_119_88_drop("119.88 fps", 120_000, 1001, 120, true, 4),
122+
_119_88_drop("119.88 fps", 120_000, 1001, 120, true, 4),
123123

124124
/**
125125
* 120 fps
126126
* Double 60 fps / quadruple 30 fps
127127
*/
128-
_120("120 fps", 120, 1, 120, false, 0),
128+
_120("120 fps", 120, 1, 120, false, 0),
129129

130130
/**
131131
* 120 drop fps
@@ -146,4 +146,20 @@ public enum FrameRate {
146146
private final boolean isDropFrameMode;
147147
private final int numberOfFramesToDropInOneMinute;
148148

149+
public double frameRateForElapsedFramesCalculation() {
150+
switch (this) {
151+
case _29_97_drop:
152+
return 29.97;
153+
case _59_94_drop:
154+
return 59.94;
155+
case _60_drop:
156+
return 59.94;
157+
case _119_88_drop:
158+
return 119.88;
159+
case _120_drop:
160+
return 119.88;
161+
default:
162+
return getNumberOfElapsedFramesThatCompriseOneSecond();
163+
}
164+
}
149165
}

src/main/java/org/moormanity/smpte/timecode/TimecodeOperations.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,41 +43,42 @@ public static String toTimecodeString(@NonNull TimecodeRecord a) {
4343
a.getFrames());
4444
}
4545

46-
public static TimecodeRecord fromElapsedFrames(int frameNumber, @NonNull FrameRate frameRate) {
47-
if (frameNumber < 0) {
48-
throw new IllegalArgumentException("frameNumber must be positive: " + frameNumber);
46+
public static TimecodeRecord fromElapsedFrames(int elapsedFrames, @NonNull FrameRate frameRate) {
47+
if (elapsedFrames < 0) {
48+
throw new IllegalArgumentException("frameNumber must be positive: " + elapsedFrames);
4949
}
50-
int inElapsedFrames;
50+
int adjustedElapsedFrames;
5151
// adjust for dropFrame
5252

5353
// modify input elapsed frame count in the case of a drop rate
54-
int framesPer10Minutes = frameRate.getNumberOfElapsedFramesThatCompriseOneSecond() * 600;
55-
int d = frameNumber / framesPer10Minutes;
56-
int m = frameNumber % framesPer10Minutes;
54+
double framesPer10Minutes = frameRate.frameRateForElapsedFramesCalculation() * 600;
55+
int d = (int) (elapsedFrames / framesPer10Minutes);
56+
int m = (int) (elapsedFrames % framesPer10Minutes);
5757
// don't allow negative numbers
5858
int f = Math.max(0, m - frameRate.getNumberOfFramesToDropInOneMinute());
5959

6060
int part1 = 9 * frameRate.getNumberOfFramesToDropInOneMinute() * d;
61-
int part2 = frameRate.getNumberOfFramesToDropInOneMinute() *
62-
(f / ((framesPer10Minutes - frameRate.getNumberOfFramesToDropInOneMinute()) / 10));
63-
inElapsedFrames = frameNumber + part1 + part2;
61+
int part2 = (frameRate.getNumberOfFramesToDropInOneMinute() *
62+
(int)(f / ((framesPer10Minutes - frameRate.getNumberOfFramesToDropInOneMinute()) / 10)));
63+
adjustedElapsedFrames = elapsedFrames + part1 + part2;
6464

6565

6666
int logicalFps = frameRate.getNumberOfElapsedFramesThatCompriseOneSecond();
67-
int frames = inElapsedFrames % logicalFps;
68-
int seconds = (inElapsedFrames / logicalFps) % 60;
69-
int minutes = (inElapsedFrames / (logicalFps * 60)) % 60;
70-
int hours = inElapsedFrames / (logicalFps * 3600) % 24;
67+
int frames = adjustedElapsedFrames % logicalFps;
68+
int seconds = (adjustedElapsedFrames / logicalFps) % 60;
69+
int minutes = (adjustedElapsedFrames / (logicalFps * 60)) % 60;
70+
int hours = adjustedElapsedFrames / (logicalFps * 3600) % 24;
7171
return new TimecodeRecord(hours, minutes, seconds, frames, frameRate);
7272
}
7373

7474
public static int toElapsedFrameCount(@NonNull TimecodeRecord a) {
75+
7576
int totalMinutes = (60 * a.getHours()) + a.getMinutes();
76-
int logicalFps = a.getFrameRate().getNumberOfElapsedFramesThatCompriseOneSecond();
77-
int base = (logicalFps * 60 * 60 * a.getHours())
78-
+ (logicalFps * 60 * a.getMinutes())
79-
+ (logicalFps * a.getSeconds())
80-
+ a.getFrames();
77+
int frameRateForElapsedFramesCalculation = a.getFrameRate().getNumberOfElapsedFramesThatCompriseOneSecond();
78+
int base = (frameRateForElapsedFramesCalculation * 60 * 60 * a.getHours())
79+
+ (frameRateForElapsedFramesCalculation * 60 * a.getMinutes())
80+
+ (frameRateForElapsedFramesCalculation * a.getSeconds())
81+
+ a.getFrames();
8182
int dropOffset = a.getFrameRate().getNumberOfFramesToDropInOneMinute() * (totalMinutes - (totalMinutes / 10));
8283
return base - dropOffset;
8384
}

src/test/java/org/moormanity/smpte/timecode/TimecodeOperationsTest.java

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@
22

33
import org.junit.jupiter.api.Test;
44

5+
import java.io.BufferedReader;
6+
import java.io.FileReader;
7+
import java.io.IOException;
8+
59
import static org.junit.jupiter.api.Assertions.*;
610

711
public class TimecodeOperationsTest {
812

913
@Test
1014
public void verifyFramesVsTimecode() {
15+
verifyElapsedFramesVsTimecodeString("00:01:59;29", FrameRate._29_97_drop, 3597);
16+
1117
verifyElapsedFramesVsTimecodeString("00:10:00;00", FrameRate._29_97_drop, 17982);
18+
1219
verifyElapsedFramesVsTimecodeString("00:10:00;00", FrameRate._59_94_drop, 17982 * 2);
1320
verifyElapsedFramesVsTimecodeString("10:00:00;00", FrameRate._29_97_drop, 1078920);
1421
verifyElapsedFramesVsTimecodeString("10:00:00;00", FrameRate._59_94_drop, 1078920 * 2);
15-
verifyElapsedFramesVsTimecodeString("00:01:59;29", FrameRate._29_97_drop, 3597);
1622
verifyElapsedFramesVsTimecodeString("00:01:59;59", FrameRate._59_94_drop, 3597 * 2 + 1);
1723

1824
verifyElapsedFramesVsTimecodeString("00:10:00:00", FrameRate._25, 15000);
@@ -37,7 +43,7 @@ public void testAdd() {
3743
public void testSubtract() {
3844
TimecodeRecord a = TimecodeOperations.fromTimecodeString("23:30:00;00", FrameRate._29_97_drop);
3945
TimecodeRecord b = TimecodeOperations.fromTimecodeString("01:00:00;00", FrameRate._29_97_drop);
40-
TimecodeRecord expected = TimecodeOperations.fromTimecodeString("22:29:59;28", FrameRate._29_97_drop);
46+
TimecodeRecord expected = TimecodeOperations.fromTimecodeString("22:30:00;00", FrameRate._29_97_drop);
4147
assertEquals(
4248
expected,
4349
TimecodeOperations.subtract(a,b)
@@ -71,18 +77,60 @@ public void timecodeStringFrameMustBeValid() {
7177
assertThrows(IllegalArgumentException.class, ()-> TimecodeOperations.fromTimecodeString("01:00:00;99", FrameRate._29_97_drop));
7278
}
7379

80+
81+
@Test
82+
public void testVsPythonTimecodePackage() throws IOException {
83+
84+
BufferedReader reader = new BufferedReader(new FileReader(getClass().getResource("/test-case.csv").getFile()));
85+
String line = null;
86+
reader.readLine(); //skip header
87+
while ((line = reader.readLine()) != null) {
88+
89+
String parts[] = line.split(",");
90+
int frames = Integer.parseInt(parts[0]);
91+
String _23_976 = parts[1];
92+
String _23_98 = parts[2];
93+
String _24 = parts[3];
94+
String _25 = parts[4];
95+
String _29_97 = parts[5];
96+
String _30 = parts[6];
97+
String _50 = parts[7];
98+
String _59_94 = parts[8];
99+
String _60 = parts[9];
100+
String _29_97_non_drop = parts[10];
101+
String _59_94_non_drop = parts[11];
102+
103+
verifyElapsedFramesVsTimecodeString(_23_976, FrameRate._23_976,frames);
104+
verifyElapsedFramesVsTimecodeString(_24, FrameRate._24,frames);
105+
verifyElapsedFramesVsTimecodeString(_25, FrameRate._25,frames);
106+
verifyElapsedFramesVsTimecodeString(_29_97, FrameRate._29_97_drop,frames);
107+
verifyElapsedFramesVsTimecodeString(_30, FrameRate._30,frames);
108+
verifyElapsedFramesVsTimecodeString(_50, FrameRate._50,frames);
109+
verifyElapsedFramesVsTimecodeString(_59_94, FrameRate._59_94_drop,frames);
110+
verifyElapsedFramesVsTimecodeString(_60, FrameRate._60,frames);
111+
verifyElapsedFramesVsTimecodeString(_29_97_non_drop, FrameRate._29_97,frames);
112+
verifyElapsedFramesVsTimecodeString(_59_94_non_drop, FrameRate._59_94,frames);
113+
114+
}
115+
116+
}
117+
74118
private void verifyElapsedFramesVsTimecodeString(String timecodeString, FrameRate frameRate, int totalElapsedFrames) {
75119
assertEquals(
76120
totalElapsedFrames,
77121
TimecodeOperations.toElapsedFrameCount(
78122
TimecodeOperations.fromTimecodeString(timecodeString, frameRate)
79-
)
123+
),
124+
timecodeString + " , " + frameRate + " , " + totalElapsedFrames
80125
);
81126

82127
assertEquals(
83128
timecodeString,
84129
TimecodeOperations.toTimecodeString(
85130
TimecodeOperations.fromElapsedFrames(totalElapsedFrames, frameRate)
86-
));
131+
),
132+
timecodeString + " , " + frameRate + " , " + totalElapsedFrames
133+
134+
);
87135
}
88136
}

0 commit comments

Comments
 (0)