@@ -10,7 +10,8 @@ import (
1010
1111// rate represents frame rate.
1212type rate struct {
13- fps int
13+ roundFPS int
14+ actualFPS float64
1415 numerator int32
1516 denominator int32
1617 dropFrames int
@@ -19,19 +20,25 @@ type rate struct {
1920}
2021
2122var (
22- // supportedRates represents supported frame rates 23.976, 24, 25, 29.97DF, 30, 48, 50, 59.94DF, 60.
23- supportedRates = []* rate {
24- {fps : 10 , numerator : 10 , denominator : 1 , dropFrames : 0 , framesPer1Min : 10 * 60 , framesPer10Min : 10 * 600 }, // 10
25- {fps : 15 , numerator : 15 , denominator : 1 , dropFrames : 0 , framesPer1Min : 15 * 60 , framesPer10Min : 15 * 600 }, // 15
26- {fps : 24 , numerator : 24000 , denominator : 1001 , dropFrames : 0 , framesPer1Min : 24 * 60 , framesPer10Min : 24 * 600 }, // 23.976
27- {fps : 24 , numerator : 24 , denominator : 1 , dropFrames : 0 , framesPer1Min : 24 * 60 , framesPer10Min : 24 * 600 }, // 24
28- {fps : 25 , numerator : 25 , denominator : 1 , dropFrames : 0 , framesPer1Min : 25 * 60 , framesPer10Min : 25 * 600 }, // 25
29- {fps : 30 , numerator : 30000 , denominator : 1001 , dropFrames : 2 , framesPer1Min : 30 * 60 - 2 , framesPer10Min : 30 * 600 - 9 * 2 }, // 29.97DF
30- {fps : 30 , numerator : 30 , denominator : 1 , dropFrames : 0 , framesPer1Min : 30 * 60 , framesPer10Min : 30 * 600 }, // 30
31- {fps : 48 , numerator : 48 , denominator : 1 , dropFrames : 0 , framesPer1Min : 48 * 60 , framesPer10Min : 48 * 600 }, // 48
32- {fps : 50 , numerator : 50 , denominator : 1 , dropFrames : 0 , framesPer1Min : 50 * 60 , framesPer10Min : 50 * 600 }, // 50
33- {fps : 60 , numerator : 60000 , denominator : 1001 , dropFrames : 4 , framesPer1Min : 60 * 60 - 4 , framesPer10Min : 60 * 600 - 9 * 4 }, // 59.94DF
34- {fps : 60 , numerator : 60 , denominator : 1 , dropFrames : 0 , framesPer1Min : 60 * 60 , framesPer10Min : 60 * 600 }, // 60
23+ // supportedNDFRates represents supported frame rates 23.976, 24, 25, 29.97NDF, 30, 48, 50, 59.94NDF, 60.
24+ supportedNDFRates = []* rate {
25+ {roundFPS : 10 , actualFPS : 10 , numerator : 10 , denominator : 1 , dropFrames : 0 , framesPer1Min : 10 * 60 , framesPer10Min : 10 * 600 }, // 10
26+ {roundFPS : 15 , actualFPS : 15 , numerator : 15 , denominator : 1 , dropFrames : 0 , framesPer1Min : 15 * 60 , framesPer10Min : 15 * 600 }, // 15
27+ {roundFPS : 24 , actualFPS : 23.976 , numerator : 24000 , denominator : 1001 , dropFrames : 0 , framesPer1Min : 24 * 60 , framesPer10Min : 24 * 600 }, // 23.976
28+ {roundFPS : 24 , actualFPS : 24 , numerator : 24 , denominator : 1 , dropFrames : 0 , framesPer1Min : 24 * 60 , framesPer10Min : 24 * 600 }, // 24
29+ {roundFPS : 25 , actualFPS : 25 , numerator : 25 , denominator : 1 , dropFrames : 0 , framesPer1Min : 25 * 60 , framesPer10Min : 25 * 600 }, // 25
30+ {roundFPS : 30 , actualFPS : 29.97 , numerator : 30000 , denominator : 1001 , dropFrames : 0 , framesPer1Min : 30 * 60 , framesPer10Min : 30 * 600 }, // 29.97NDF (optional)
31+ {roundFPS : 30 , actualFPS : 30 , numerator : 30 , denominator : 1 , dropFrames : 0 , framesPer1Min : 30 * 60 , framesPer10Min : 30 * 600 }, // 30
32+ {roundFPS : 48 , actualFPS : 48 , numerator : 48 , denominator : 1 , dropFrames : 0 , framesPer1Min : 48 * 60 , framesPer10Min : 48 * 600 }, // 48
33+ {roundFPS : 50 , actualFPS : 50 , numerator : 50 , denominator : 1 , dropFrames : 0 , framesPer1Min : 50 * 60 , framesPer10Min : 50 * 600 }, // 50
34+ {roundFPS : 60 , actualFPS : 59.94 , numerator : 60000 , denominator : 1001 , dropFrames : 0 , framesPer1Min : 60 * 60 , framesPer10Min : 60 * 600 }, // 59.94NDF (optional)
35+ {roundFPS : 60 , actualFPS : 60 , numerator : 60 , denominator : 1 , dropFrames : 0 , framesPer1Min : 60 * 60 , framesPer10Min : 60 * 600 }, // 60
36+ }
37+
38+ // supportedDFRates represents supported frame rates 29.97DF, 59.94DF.
39+ supportedDFRates = []* rate {
40+ {roundFPS : 30 , actualFPS : 29.97 , numerator : 30000 , denominator : 1001 , dropFrames : 2 , framesPer1Min : 30 * 60 - 2 , framesPer10Min : 30 * 600 - 9 * 2 }, // 29.97DF (preferred)
41+ {roundFPS : 60 , actualFPS : 59.94 , numerator : 60000 , denominator : 1001 , dropFrames : 4 , framesPer1Min : 60 * 60 - 4 , framesPer10Min : 60 * 600 - 9 * 4 }, // 59.94DF (preferred)
3542 }
3643
3744 // timecodePattern represents timecode pattern.
@@ -49,84 +56,176 @@ var (
4956
5057// Timecode represents timecode.
5158type Timecode struct {
52- optp TimecodeOptionParam
53- r * rate
54- HH uint64
55- MM uint64
56- SS uint64
57- FF uint64
58- }
59-
60- // TimecodeOptionParam represents timecode option parameter.
61- type TimecodeOptionParam struct {
62- Sep string
63- SepDF string
59+ preferDF bool
60+ sep string
61+ lastSep string
62+ r * rate
63+ HH uint64
64+ MM uint64
65+ SS uint64
66+ FF uint64
6467}
6568
66- // TimecodeOption represents timecode option.
67- type TimecodeOption func (* TimecodeOptionParam )
68-
69- // newTimecodeOptionParam returns new TimecodeOptionParam.
70- func newTimecodeOptionParam () TimecodeOptionParam {
71- return TimecodeOptionParam {
72- Sep : ":" ,
73- SepDF : ":" ,
74- }
75- }
76-
77- // applyTimecodeOption applies TimecodeOption to TimecodeOptionParam.
78- func (p * TimecodeOptionParam ) applyTimecodeOption (opts ... TimecodeOption ) {
79- for _ , opt := range opts {
80- opt (p )
69+ // newNDFRate returns new NDF rate.
70+ func newNDFRate (num , den int32 ) (* rate , error ) {
71+ fps := float64 (num ) / float64 (den )
72+ for _ , r := range supportedNDFRates {
73+ if float64 (r .numerator )/ float64 (r .denominator ) == fps {
74+ return r , nil
75+ }
8176 }
77+ return nil , ErrUnsupportedFrameRate
8278}
8379
84- // newRate returns new rate.
85- func newRate (num , den int32 ) (* rate , error ) {
80+ // newDFRate returns new DF rate.
81+ func newDFRate (num , den int32 ) (* rate , error ) {
8682 fps := float64 (num ) / float64 (den )
87- for _ , r := range supportedRates {
83+ for _ , r := range supportedDFRates {
8884 if float64 (r .numerator )/ float64 (r .denominator ) == fps {
8985 return r , nil
9086 }
9187 }
9288 return nil , ErrUnsupportedFrameRate
9389}
9490
91+ // newRate returns new rate.
92+ func newRate (num , den int32 , preferDF bool ) (* rate , error ) {
93+ if preferDF {
94+ r , err := newDFRate (num , den )
95+ if err != nil {
96+ if errors .Is (err , ErrUnsupportedFrameRate ) {
97+ return newNDFRate (num , den )
98+ }
99+ return nil , err
100+ }
101+ return r , nil
102+ }
103+ return newNDFRate (num , den )
104+ }
105+
95106// IsSupportedFrameRate returns whether frame rate is supported.
96107func IsSupportedFrameRate (num , den int32 ) bool {
97- _ , err := newRate (num , den )
108+ _ , err := newNDFRate (num , den )
98109 return err == nil
99110}
100111
112+ // IsRepresentableFramesOptionParam represents IsRepresentableFrames option parameter.
113+ type IsRepresentableFramesOptionParam struct {
114+ PreferDF bool
115+ }
116+
117+ // IsRepresentableFramesOption represents IsRepresentableFrames option.
118+ type IsRepresentableFramesOption func (* IsRepresentableFramesOptionParam )
119+
120+ // newIsRepresentableFramesOptionParam returns new IsRepresentableFramesOptionParam.
121+ func newIsRepresentableFramesOptionParam () IsRepresentableFramesOptionParam {
122+ return IsRepresentableFramesOptionParam {
123+ PreferDF : true , // if frame rate is DF or NDF, assume DF
124+ }
125+ }
126+
127+ // applyIsRepresentableFramesOption applies IsRepresentableFramesOption to IsRepresentableFramesOptionParam.
128+ func (p * IsRepresentableFramesOptionParam ) applyIsRepresentableFramesOption (opts ... IsRepresentableFramesOption ) {
129+ for _ , opt := range opts {
130+ opt (p )
131+ }
132+ }
133+
101134// IsRepresentableFrames returns whether frames is representable.
102- func IsRepresentableFrames (frames uint64 , num , den int32 ) bool {
103- r , err := newRate (num , den )
135+ func IsRepresentableFrames (frames uint64 , num , den int32 , opts ... IsRepresentableFramesOption ) bool {
136+ p := newIsRepresentableFramesOptionParam ()
137+ p .applyIsRepresentableFramesOption (opts ... )
138+
139+ r , err := newRate (num , den , p .PreferDF )
104140 if err != nil {
105141 return false
106142 }
107143 return r .isRepresentableFrames (frames )
108144}
109145
146+ // TimecodeOptionParam represents timecode option parameter.
147+ type TimecodeOptionParam struct {
148+ PreferDF bool
149+ Sep string
150+ LastSep string
151+ }
152+
153+ // TimecodeOption represents timecode option.
154+ type TimecodeOption func (* TimecodeOptionParam )
155+
156+ // newTimecodeOptionParam returns new TimecodeOptionParam.
157+ func newTimecodeOptionParam () TimecodeOptionParam {
158+ return TimecodeOptionParam {
159+ PreferDF : true , // if frame rate is 29.97 or 59.94, assume DF. otherwise, assume NDF
160+ Sep : ":" ,
161+ LastSep : ":" ,
162+ }
163+ }
164+
165+ // applyTimecodeOption applies TimecodeOption to TimecodeOptionParam.
166+ func (p * TimecodeOptionParam ) applyTimecodeOption (opts ... TimecodeOption ) {
167+ for _ , opt := range opts {
168+ opt (p )
169+ }
170+ }
171+
110172// NewTimecode returns new Timecode.
111173func NewTimecode (frames uint64 , num , den int32 , opts ... TimecodeOption ) (* Timecode , error ) {
112- r , err := newRate (num , den )
174+ p := newTimecodeOptionParam ()
175+ p .applyTimecodeOption (opts ... )
176+
177+ r , err := newRate (num , den , p .PreferDF )
113178 if err != nil {
114179 return nil , err
115180 }
116181
117- p := newTimecodeOptionParam ()
118- p .applyTimecodeOption (opts ... )
182+ lastSep := p .LastSep
183+ if r .dropFrames == 0 {
184+ lastSep = p .Sep
185+ }
119186
120- tc , err := Reset (& Timecode {r : r , optp : p }, frames )
187+ tc , err := Reset (& Timecode {
188+ preferDF : p .PreferDF ,
189+ sep : p .Sep ,
190+ lastSep : lastSep ,
191+ r : r ,
192+ }, frames )
121193 if err != nil {
122194 return nil , err
123195 }
124196 return tc , nil
125197}
126198
199+ // TimecodeOptionParam represents timecode option parameter.
200+ type ParseTimecodeOptionParam struct {
201+ PreferDF bool
202+ Sep string
203+ LastSep string
204+ }
205+
206+ // ParseTimecodeOption represents parse timecode option.
207+ type ParseTimecodeOption func (* ParseTimecodeOptionParam )
208+
209+ // newParseTimecodeOptionParam returns new ParseTimecodeOptionParam.
210+ func newParseTimecodeOptionParam () ParseTimecodeOptionParam {
211+ return ParseTimecodeOptionParam {
212+ PreferDF : true , // if frame rate is 29.97 or 59.94, assume DF. otherwise, assume NDF
213+ }
214+ }
215+
216+ // applyParseTimecodeOption applies ParseTimecodeOption to ParseTimecodeOptionParam.
217+ func (p * ParseTimecodeOptionParam ) applyParseTimecodeOption (opts ... ParseTimecodeOption ) {
218+ for _ , opt := range opts {
219+ opt (p )
220+ }
221+ }
222+
127223// ParseTimecode returns new Timecode from formatted string.
128- func ParseTimecode (s string , num , den int32 ) (* Timecode , error ) {
129- r , err := newRate (num , den )
224+ func ParseTimecode (s string , num , den int32 , opts ... ParseTimecodeOption ) (* Timecode , error ) {
225+ p := newParseTimecodeOptionParam ()
226+ p .applyParseTimecodeOption (opts ... )
227+
228+ r , err := newRate (num , den , p .PreferDF )
130229 if err != nil {
131230 return nil , err
132231 }
@@ -142,20 +241,25 @@ func ParseTimecode(s string, num, den int32) (*Timecode, error) {
142241 sep := match [2 ]
143242 mm , _ := strconv .Atoi (match [3 ])
144243 ss , _ := strconv .Atoi (match [5 ])
145- sepDF := match [6 ]
244+ lastSep := match [6 ]
146245 ff , _ := strconv .Atoi (match [7 ])
147246
148247 if ff < r .dropFrames && mm % 10 != 0 {
149248 ff = r .dropFrames
150249 }
250+ if r .dropFrames == 0 {
251+ lastSep = sep
252+ }
151253
152254 return & Timecode {
153- r : r ,
154- optp : TimecodeOptionParam {Sep : sep , SepDF : sepDF },
155- HH : uint64 (hh ),
156- MM : uint64 (mm ),
157- SS : uint64 (ss ),
158- FF : uint64 (ff ),
255+ preferDF : p .PreferDF ,
256+ sep : sep ,
257+ lastSep : lastSep ,
258+ r : r ,
259+ HH : uint64 (hh ),
260+ MM : uint64 (mm ),
261+ SS : uint64 (ss ),
262+ FF : uint64 (ff ),
159263 }, nil
160264}
161265
@@ -179,7 +283,7 @@ func Reset(tc *Timecode, frames uint64) (*Timecode, error) {
179283 f += df * ((m - df ) / uint64 (new .r .framesPer1Min ))
180284 }
181285
182- fps := uint64 (new .r .fps )
286+ fps := uint64 (new .r .roundFPS )
183287 new .FF = f % fps
184288 new .SS = f / fps % 60
185289 new .MM = f / (fps * 60 ) % 60
@@ -193,7 +297,7 @@ func (r *rate) equal(other *rate) bool {
193297 if r == nil || other == nil {
194298 return false
195299 }
196- return r .numerator == other .numerator && r .denominator == other .denominator
300+ return r .numerator == other .numerator && r .denominator == other .denominator && r . dropFrames == other . dropFrames
197301}
198302
199303// isRepresentableFrames returns whether frames is representable.
@@ -204,12 +308,12 @@ func (r *rate) isRepresentableFrames(frames uint64) bool {
204308// Frames returns number of frames.
205309func (tc * Timecode ) Frames () uint64 {
206310 var frames uint64
207- frames += tc .HH * 3600 * uint64 (tc .r .fps )
208- frames += tc .MM * 60 * uint64 (tc .r .fps )
209- frames += tc .SS * uint64 (tc .r .fps )
311+ frames += tc .HH * 3600 * uint64 (tc .r .roundFPS )
312+ frames += tc .MM * 60 * uint64 (tc .r .roundFPS )
313+ frames += tc .SS * uint64 (tc .r .roundFPS )
210314 frames += tc .FF
211315
212- framesPer10Min := uint64 (tc .r .fps ) * 60 * 10
316+ framesPer10Min := uint64 (tc .r .roundFPS ) * 60 * 10
213317 framesPer1Min := framesPer10Min / 10
214318
215319 var df uint64
@@ -221,7 +325,7 @@ func (tc *Timecode) Frames() uint64 {
221325
222326// Duration returns duration from zero-origin.
223327func (tc * Timecode ) Duration () time.Duration {
224- return time .Duration ((float64 (tc .Frames ()) * float64 ( tc . r . denominator ) / float64 (tc .r .numerator )) * float64 (time .Second ))
328+ return time .Duration ((float64 (tc .Frames ()) / float64 (tc .r .actualFPS )) * float64 (time .Second ))
225329}
226330
227331// Framerate denominator.
@@ -269,19 +373,14 @@ func (tc *Timecode) SubFrames(frames uint64) (*Timecode, error) {
269373// String returns Timecode formatted string.
270374// e.g. 01:23:45:28
271375func (tc * Timecode ) String () string {
272- sep := tc .optp .Sep
273- lastSep := sep
274- if tc .r .dropFrames > 0 {
275- lastSep = tc .optp .SepDF
276- }
277376 return fmt .Sprintf (
278377 "%02d%s%02d%s%02d%s%02d" ,
279378 tc .HH ,
280- sep ,
379+ tc . sep ,
281380 tc .MM ,
282- sep ,
381+ tc . sep ,
283382 tc .SS ,
284- lastSep ,
383+ tc . lastSep ,
285384 tc .FF ,
286385 )
287386}
0 commit comments