From a00cc54341b8213c45d48a7698593348819b3182 Mon Sep 17 00:00:00 2001 From: lomavkin Date: Thu, 18 Apr 2024 14:53:48 +0900 Subject: [PATCH] support 29.97, 59.94 NDF --- timecode/timecode.go | 249 +++++++++++++++++++-------- timecode/timecode_test.go | 348 +++++++++++++++++++++++++------------- 2 files changed, 407 insertions(+), 190 deletions(-) diff --git a/timecode/timecode.go b/timecode/timecode.go index d686134..beb34ec 100644 --- a/timecode/timecode.go +++ b/timecode/timecode.go @@ -10,7 +10,8 @@ import ( // rate represents frame rate. type rate struct { - fps int + roundFPS int + actualFPS float64 numerator int32 denominator int32 dropFrames int @@ -19,19 +20,25 @@ type rate struct { } var ( - // supportedRates represents supported frame rates 23.976, 24, 25, 29.97DF, 30, 48, 50, 59.94DF, 60. - supportedRates = []*rate{ - {fps: 10, numerator: 10, denominator: 1, dropFrames: 0, framesPer1Min: 10 * 60, framesPer10Min: 10 * 600}, // 10 - {fps: 15, numerator: 15, denominator: 1, dropFrames: 0, framesPer1Min: 15 * 60, framesPer10Min: 15 * 600}, // 15 - {fps: 24, numerator: 24000, denominator: 1001, dropFrames: 0, framesPer1Min: 24 * 60, framesPer10Min: 24 * 600}, // 23.976 - {fps: 24, numerator: 24, denominator: 1, dropFrames: 0, framesPer1Min: 24 * 60, framesPer10Min: 24 * 600}, // 24 - {fps: 25, numerator: 25, denominator: 1, dropFrames: 0, framesPer1Min: 25 * 60, framesPer10Min: 25 * 600}, // 25 - {fps: 30, numerator: 30000, denominator: 1001, dropFrames: 2, framesPer1Min: 30*60 - 2, framesPer10Min: 30*600 - 9*2}, // 29.97DF - {fps: 30, numerator: 30, denominator: 1, dropFrames: 0, framesPer1Min: 30 * 60, framesPer10Min: 30 * 600}, // 30 - {fps: 48, numerator: 48, denominator: 1, dropFrames: 0, framesPer1Min: 48 * 60, framesPer10Min: 48 * 600}, // 48 - {fps: 50, numerator: 50, denominator: 1, dropFrames: 0, framesPer1Min: 50 * 60, framesPer10Min: 50 * 600}, // 50 - {fps: 60, numerator: 60000, denominator: 1001, dropFrames: 4, framesPer1Min: 60*60 - 4, framesPer10Min: 60*600 - 9*4}, // 59.94DF - {fps: 60, numerator: 60, denominator: 1, dropFrames: 0, framesPer1Min: 60 * 60, framesPer10Min: 60 * 600}, // 60 + // supportedNDFRates represents supported frame rates 23.976, 24, 25, 29.97NDF, 30, 48, 50, 59.94NDF, 60. + supportedNDFRates = []*rate{ + {roundFPS: 10, actualFPS: 10, numerator: 10, denominator: 1, dropFrames: 0, framesPer1Min: 10 * 60, framesPer10Min: 10 * 600}, // 10 + {roundFPS: 15, actualFPS: 15, numerator: 15, denominator: 1, dropFrames: 0, framesPer1Min: 15 * 60, framesPer10Min: 15 * 600}, // 15 + {roundFPS: 24, actualFPS: 23.976, numerator: 24000, denominator: 1001, dropFrames: 0, framesPer1Min: 24 * 60, framesPer10Min: 24 * 600}, // 23.976 + {roundFPS: 24, actualFPS: 24, numerator: 24, denominator: 1, dropFrames: 0, framesPer1Min: 24 * 60, framesPer10Min: 24 * 600}, // 24 + {roundFPS: 25, actualFPS: 25, numerator: 25, denominator: 1, dropFrames: 0, framesPer1Min: 25 * 60, framesPer10Min: 25 * 600}, // 25 + {roundFPS: 30, actualFPS: 29.97, numerator: 30000, denominator: 1001, dropFrames: 0, framesPer1Min: 30 * 60, framesPer10Min: 30 * 600}, // 29.97NDF (optional) + {roundFPS: 30, actualFPS: 30, numerator: 30, denominator: 1, dropFrames: 0, framesPer1Min: 30 * 60, framesPer10Min: 30 * 600}, // 30 + {roundFPS: 48, actualFPS: 48, numerator: 48, denominator: 1, dropFrames: 0, framesPer1Min: 48 * 60, framesPer10Min: 48 * 600}, // 48 + {roundFPS: 50, actualFPS: 50, numerator: 50, denominator: 1, dropFrames: 0, framesPer1Min: 50 * 60, framesPer10Min: 50 * 600}, // 50 + {roundFPS: 60, actualFPS: 59.94, numerator: 60000, denominator: 1001, dropFrames: 0, framesPer1Min: 60 * 60, framesPer10Min: 60 * 600}, // 59.94NDF (optional) + {roundFPS: 60, actualFPS: 60, numerator: 60, denominator: 1, dropFrames: 0, framesPer1Min: 60 * 60, framesPer10Min: 60 * 600}, // 60 + } + + // supportedDFRates represents supported frame rates 29.97DF, 59.94DF. + supportedDFRates = []*rate{ + {roundFPS: 30, actualFPS: 29.97, numerator: 30000, denominator: 1001, dropFrames: 2, framesPer1Min: 30*60 - 2, framesPer10Min: 30*600 - 9*2}, // 29.97DF (preferred) + {roundFPS: 60, actualFPS: 59.94, numerator: 60000, denominator: 1001, dropFrames: 4, framesPer1Min: 60*60 - 4, framesPer10Min: 60*600 - 9*4}, // 59.94DF (preferred) } // timecodePattern represents timecode pattern. @@ -49,42 +56,31 @@ var ( // Timecode represents timecode. type Timecode struct { - optp TimecodeOptionParam - r *rate - HH uint64 - MM uint64 - SS uint64 - FF uint64 -} - -// TimecodeOptionParam represents timecode option parameter. -type TimecodeOptionParam struct { - Sep string - SepDF string + preferDF bool + sep string + lastSep string + r *rate + HH uint64 + MM uint64 + SS uint64 + FF uint64 } -// TimecodeOption represents timecode option. -type TimecodeOption func(*TimecodeOptionParam) - -// newTimecodeOptionParam returns new TimecodeOptionParam. -func newTimecodeOptionParam() TimecodeOptionParam { - return TimecodeOptionParam{ - Sep: ":", - SepDF: ":", - } -} - -// applyTimecodeOption applies TimecodeOption to TimecodeOptionParam. -func (p *TimecodeOptionParam) applyTimecodeOption(opts ...TimecodeOption) { - for _, opt := range opts { - opt(p) +// newNDFRate returns new NDF rate. +func newNDFRate(num, den int32) (*rate, error) { + fps := float64(num) / float64(den) + for _, r := range supportedNDFRates { + if float64(r.numerator)/float64(r.denominator) == fps { + return r, nil + } } + return nil, ErrUnsupportedFrameRate } -// newRate returns new rate. -func newRate(num, den int32) (*rate, error) { +// newDFRate returns new DF rate. +func newDFRate(num, den int32) (*rate, error) { fps := float64(num) / float64(den) - for _, r := range supportedRates { + for _, r := range supportedDFRates { if float64(r.numerator)/float64(r.denominator) == fps { return r, nil } @@ -92,41 +88,144 @@ func newRate(num, den int32) (*rate, error) { return nil, ErrUnsupportedFrameRate } +// newRate returns new rate. +func newRate(num, den int32, preferDF bool) (*rate, error) { + if preferDF { + r, err := newDFRate(num, den) + if err != nil { + if errors.Is(err, ErrUnsupportedFrameRate) { + return newNDFRate(num, den) + } + return nil, err + } + return r, nil + } + return newNDFRate(num, den) +} + // IsSupportedFrameRate returns whether frame rate is supported. func IsSupportedFrameRate(num, den int32) bool { - _, err := newRate(num, den) + _, err := newNDFRate(num, den) return err == nil } +// IsRepresentableFramesOptionParam represents IsRepresentableFrames option parameter. +type IsRepresentableFramesOptionParam struct { + PreferDF bool +} + +// IsRepresentableFramesOption represents IsRepresentableFrames option. +type IsRepresentableFramesOption func(*IsRepresentableFramesOptionParam) + +// newIsRepresentableFramesOptionParam returns new IsRepresentableFramesOptionParam. +func newIsRepresentableFramesOptionParam() IsRepresentableFramesOptionParam { + return IsRepresentableFramesOptionParam{ + PreferDF: true, // if frame rate is DF or NDF, assume DF + } +} + +// applyIsRepresentableFramesOption applies IsRepresentableFramesOption to IsRepresentableFramesOptionParam. +func (p *IsRepresentableFramesOptionParam) applyIsRepresentableFramesOption(opts ...IsRepresentableFramesOption) { + for _, opt := range opts { + opt(p) + } +} + // IsRepresentableFrames returns whether frames is representable. -func IsRepresentableFrames(frames uint64, num, den int32) bool { - r, err := newRate(num, den) +func IsRepresentableFrames(frames uint64, num, den int32, opts ...IsRepresentableFramesOption) bool { + p := newIsRepresentableFramesOptionParam() + p.applyIsRepresentableFramesOption(opts...) + + r, err := newRate(num, den, p.PreferDF) if err != nil { return false } return r.isRepresentableFrames(frames) } +// TimecodeOptionParam represents timecode option parameter. +type TimecodeOptionParam struct { + PreferDF bool + Sep string + LastSep string +} + +// TimecodeOption represents timecode option. +type TimecodeOption func(*TimecodeOptionParam) + +// newTimecodeOptionParam returns new TimecodeOptionParam. +func newTimecodeOptionParam() TimecodeOptionParam { + return TimecodeOptionParam{ + PreferDF: true, // if frame rate is 29.97 or 59.94, assume DF. otherwise, assume NDF + Sep: ":", + LastSep: ":", + } +} + +// applyTimecodeOption applies TimecodeOption to TimecodeOptionParam. +func (p *TimecodeOptionParam) applyTimecodeOption(opts ...TimecodeOption) { + for _, opt := range opts { + opt(p) + } +} + // NewTimecode returns new Timecode. func NewTimecode(frames uint64, num, den int32, opts ...TimecodeOption) (*Timecode, error) { - r, err := newRate(num, den) + p := newTimecodeOptionParam() + p.applyTimecodeOption(opts...) + + r, err := newRate(num, den, p.PreferDF) if err != nil { return nil, err } - p := newTimecodeOptionParam() - p.applyTimecodeOption(opts...) + lastSep := p.LastSep + if r.dropFrames == 0 { + lastSep = p.Sep + } - tc, err := Reset(&Timecode{r: r, optp: p}, frames) + tc, err := Reset(&Timecode{ + preferDF: p.PreferDF, + sep: p.Sep, + lastSep: lastSep, + r: r, + }, frames) if err != nil { return nil, err } return tc, nil } +// TimecodeOptionParam represents timecode option parameter. +type ParseTimecodeOptionParam struct { + PreferDF bool + Sep string + LastSep string +} + +// ParseTimecodeOption represents parse timecode option. +type ParseTimecodeOption func(*ParseTimecodeOptionParam) + +// newParseTimecodeOptionParam returns new ParseTimecodeOptionParam. +func newParseTimecodeOptionParam() ParseTimecodeOptionParam { + return ParseTimecodeOptionParam{ + PreferDF: true, // if frame rate is 29.97 or 59.94, assume DF. otherwise, assume NDF + } +} + +// applyParseTimecodeOption applies ParseTimecodeOption to ParseTimecodeOptionParam. +func (p *ParseTimecodeOptionParam) applyParseTimecodeOption(opts ...ParseTimecodeOption) { + for _, opt := range opts { + opt(p) + } +} + // ParseTimecode returns new Timecode from formatted string. -func ParseTimecode(s string, num, den int32) (*Timecode, error) { - r, err := newRate(num, den) +func ParseTimecode(s string, num, den int32, opts ...ParseTimecodeOption) (*Timecode, error) { + p := newParseTimecodeOptionParam() + p.applyParseTimecodeOption(opts...) + + r, err := newRate(num, den, p.PreferDF) if err != nil { return nil, err } @@ -142,20 +241,25 @@ func ParseTimecode(s string, num, den int32) (*Timecode, error) { sep := match[2] mm, _ := strconv.Atoi(match[3]) ss, _ := strconv.Atoi(match[5]) - sepDF := match[6] + lastSep := match[6] ff, _ := strconv.Atoi(match[7]) if ff < r.dropFrames && mm%10 != 0 { ff = r.dropFrames } + if r.dropFrames == 0 { + lastSep = sep + } return &Timecode{ - r: r, - optp: TimecodeOptionParam{Sep: sep, SepDF: sepDF}, - HH: uint64(hh), - MM: uint64(mm), - SS: uint64(ss), - FF: uint64(ff), + preferDF: p.PreferDF, + sep: sep, + lastSep: lastSep, + r: r, + HH: uint64(hh), + MM: uint64(mm), + SS: uint64(ss), + FF: uint64(ff), }, nil } @@ -179,7 +283,7 @@ func Reset(tc *Timecode, frames uint64) (*Timecode, error) { f += df * ((m - df) / uint64(new.r.framesPer1Min)) } - fps := uint64(new.r.fps) + fps := uint64(new.r.roundFPS) new.FF = f % fps new.SS = f / fps % 60 new.MM = f / (fps * 60) % 60 @@ -193,7 +297,7 @@ func (r *rate) equal(other *rate) bool { if r == nil || other == nil { return false } - return r.numerator == other.numerator && r.denominator == other.denominator + return r.numerator == other.numerator && r.denominator == other.denominator && r.dropFrames == other.dropFrames } // isRepresentableFrames returns whether frames is representable. @@ -204,12 +308,12 @@ func (r *rate) isRepresentableFrames(frames uint64) bool { // Frames returns number of frames. func (tc *Timecode) Frames() uint64 { var frames uint64 - frames += tc.HH * 3600 * uint64(tc.r.fps) - frames += tc.MM * 60 * uint64(tc.r.fps) - frames += tc.SS * uint64(tc.r.fps) + frames += tc.HH * 3600 * uint64(tc.r.roundFPS) + frames += tc.MM * 60 * uint64(tc.r.roundFPS) + frames += tc.SS * uint64(tc.r.roundFPS) frames += tc.FF - framesPer10Min := uint64(tc.r.fps) * 60 * 10 + framesPer10Min := uint64(tc.r.roundFPS) * 60 * 10 framesPer1Min := framesPer10Min / 10 var df uint64 @@ -221,7 +325,7 @@ func (tc *Timecode) Frames() uint64 { // Duration returns duration from zero-origin. func (tc *Timecode) Duration() time.Duration { - return time.Duration((float64(tc.Frames()) * float64(tc.r.denominator) / float64(tc.r.numerator)) * float64(time.Second)) + return time.Duration((float64(tc.Frames()) / float64(tc.r.actualFPS)) * float64(time.Second)) } // Framerate denominator. @@ -269,19 +373,14 @@ func (tc *Timecode) SubFrames(frames uint64) (*Timecode, error) { // String returns Timecode formatted string. // e.g. 01:23:45:28 func (tc *Timecode) String() string { - sep := tc.optp.Sep - lastSep := sep - if tc.r.dropFrames > 0 { - lastSep = tc.optp.SepDF - } return fmt.Sprintf( "%02d%s%02d%s%02d%s%02d", tc.HH, - sep, + tc.sep, tc.MM, - sep, + tc.sep, tc.SS, - lastSep, + tc.lastSep, tc.FF, ) } diff --git a/timecode/timecode_test.go b/timecode/timecode_test.go index 71367ff..7402b31 100644 --- a/timecode/timecode_test.go +++ b/timecode/timecode_test.go @@ -9,179 +9,186 @@ import ( func TestNewRate(t *testing.T) { t.Run("NaN", func(t *testing.T) { - _, err := newRate(1, 0) + _, err := newRate(1, 0, true) assert.Error(t, err) }) t.Run("0fps", func(t *testing.T) { - _, err := newRate(0, 1001) + _, err := newRate(0, 1001, true) assert.Error(t, err) }) t.Run("1fps", func(t *testing.T) { - _, err := newRate(1, 1) + _, err := newRate(1, 1, true) assert.Error(t, err) }) t.Run("23.976fps", func(t *testing.T) { - r, err := newRate(24000, 1001) + r, err := newRate(24000, 1001, true) assert.NoError(t, err) - assert.Equal(t, 24, r.fps) + assert.Equal(t, 24, r.roundFPS) assert.Equal(t, 0, r.dropFrames) assert.Equal(t, 24*60, r.framesPer1Min) assert.Equal(t, 24*600, r.framesPer10Min) }) t.Run("24fps", func(t *testing.T) { - r, err := newRate(24, 1) + r, err := newRate(24, 1, true) assert.NoError(t, err) - assert.Equal(t, 24, r.fps) + assert.Equal(t, 24, r.roundFPS) assert.Equal(t, 0, r.dropFrames) assert.Equal(t, 24*60, r.framesPer1Min) assert.Equal(t, 24*600, r.framesPer10Min) }) t.Run("25fps", func(t *testing.T) { - r, err := newRate(25, 1) + r, err := newRate(25, 1, true) assert.NoError(t, err) - assert.Equal(t, 25, r.fps) + assert.Equal(t, 25, r.roundFPS) assert.Equal(t, 0, r.dropFrames) assert.Equal(t, 25*60, r.framesPer1Min) assert.Equal(t, 25*600, r.framesPer10Min) }) t.Run("29.97fps", func(t *testing.T) { - r, err := newRate(30000, 1001) + r, err := newRate(30000, 1001, true) assert.NoError(t, err) - assert.Equal(t, 30, r.fps) + assert.Equal(t, 30, r.roundFPS) assert.Equal(t, 2, r.dropFrames) assert.Equal(t, 30*60-2, r.framesPer1Min) assert.Equal(t, 30*600-9*2, r.framesPer10Min) }) t.Run("30fps", func(t *testing.T) { - r, err := newRate(30, 1) + r, err := newRate(30, 1, true) assert.NoError(t, err) - assert.Equal(t, 30, r.fps) + assert.Equal(t, 30, r.roundFPS) assert.Equal(t, 0, r.dropFrames) assert.Equal(t, 30*60, r.framesPer1Min) assert.Equal(t, 30*600, r.framesPer10Min) }) t.Run("48fps", func(t *testing.T) { - r, err := newRate(48, 1) + r, err := newRate(48, 1, true) assert.NoError(t, err) - assert.Equal(t, 48, r.fps) + assert.Equal(t, 48, r.roundFPS) assert.Equal(t, 0, r.dropFrames) assert.Equal(t, 48*60, r.framesPer1Min) assert.Equal(t, 48*600, r.framesPer10Min) }) t.Run("50fps", func(t *testing.T) { - r, err := newRate(50, 1) + r, err := newRate(50, 1, true) assert.NoError(t, err) - assert.Equal(t, 50, r.fps) + assert.Equal(t, 50, r.roundFPS) assert.Equal(t, 0, r.dropFrames) assert.Equal(t, 50*60, r.framesPer1Min) assert.Equal(t, 50*600, r.framesPer10Min) }) t.Run("59.94fps", func(t *testing.T) { - r, err := newRate(60000, 1001) + r, err := newRate(60000, 1001, true) assert.NoError(t, err) - assert.Equal(t, 60, r.fps) + assert.Equal(t, 60, r.roundFPS) assert.Equal(t, 4, r.dropFrames) assert.Equal(t, 60*60-4, r.framesPer1Min) assert.Equal(t, 60*600-9*4, r.framesPer10Min) }) t.Run("60fps", func(t *testing.T) { - r, err := newRate(60, 1) + r, err := newRate(60, 1, true) assert.NoError(t, err) - assert.Equal(t, 60, r.fps) + assert.Equal(t, 60, r.roundFPS) assert.Equal(t, 0, r.dropFrames) assert.Equal(t, 60*60, r.framesPer1Min) assert.Equal(t, 60*600, r.framesPer10Min) }) t.Run("error/23.995fps", func(t *testing.T) { - r, err := newRate(29995, 1000) + r, err := newRate(29995, 1000, true) assert.Equal(t, ErrUnsupportedFrameRate, err) assert.Nil(t, r) }) t.Run("error/23.997fps", func(t *testing.T) { - r, err := newRate(29997, 1000) + r, err := newRate(29997, 1000, true) assert.Equal(t, ErrUnsupportedFrameRate, err) assert.Nil(t, r) }) t.Run("error/29.96fps", func(t *testing.T) { - r, err := newRate(29960, 1000) + r, err := newRate(29960, 1000, true) assert.Equal(t, ErrUnsupportedFrameRate, err) assert.Nil(t, r) }) t.Run("error/29.98fps", func(t *testing.T) { - r, err := newRate(29980, 1000) + r, err := newRate(29980, 1000, true) assert.Equal(t, ErrUnsupportedFrameRate, err) assert.Nil(t, r) }) t.Run("error/59.93fps", func(t *testing.T) { - r, err := newRate(59930, 1000) + r, err := newRate(59930, 1000, true) assert.Equal(t, ErrUnsupportedFrameRate, err) assert.Nil(t, r) }) t.Run("error/59.95fps", func(t *testing.T) { - r, err := newRate(59950, 1000) + r, err := newRate(59950, 1000, true) assert.Equal(t, ErrUnsupportedFrameRate, err) assert.Nil(t, r) }) t.Run("error/60.001fps", func(t *testing.T) { - r, err := newRate(60001, 1000) + r, err := newRate(60001, 1000, true) assert.Equal(t, ErrUnsupportedFrameRate, err) assert.Nil(t, r) }) } -func TestNewTestcodeNonDF(t *testing.T) { +func TestNewTimecodeNDF(t *testing.T) { + assumeDF := func(p *TimecodeOptionParam) { + p.PreferDF = true + } + assumeNDF := func(p *TimecodeOptionParam) { + p.PreferDF = false + } + t.Run("NaN", func(t *testing.T) { - _, err := NewTimecode(1, 1, 0) + _, err := NewTimecode(1, 1, 0, assumeDF) assert.Error(t, err) }) t.Run("0fps", func(t *testing.T) { - _, err := NewTimecode(1, 0, 1001) + _, err := NewTimecode(1, 0, 1001, assumeDF) assert.Error(t, err) }) t.Run("1fps", func(t *testing.T) { - _, err := NewTimecode(1, 1, 1) + _, err := NewTimecode(1, 1, 1, assumeDF) assert.Error(t, err) }) t.Run("23.976fps", func(t *testing.T) { - tc, err := NewTimecode(1439, 24000, 1001) + tc, err := NewTimecode(1439, 24000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:23", tc.String()) assert.Equal(t, uint64(1439), tc.Frames()) assert.Equal(t, 60.018, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(1440, 24000, 1001) + tc, err = NewTimecode(1440, 24000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:00", tc.String()) assert.Equal(t, uint64(1440), tc.Frames()) - tc, err = NewTimecode(1441, 24000, 1001) + tc, err = NewTimecode(1441, 24000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:01", tc.String()) assert.Equal(t, uint64(1441), tc.Frames()) - tc, err = NewTimecode(1440*10, 24000, 1001) + tc, err = NewTimecode(1440*10, 24000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(1440*10), tc.Frames()) - assert.Equal(t, 600.6, math.Round(tc.Duration().Seconds()*1000)/1000) + assert.Equal(t, 600.601, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(1440*10+1, 24000, 1001) + tc, err = NewTimecode(1440*10+1, 24000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:01", tc.String()) assert.Equal(t, uint64(1440*10+1), tc.Frames()) maxFrames := uint64(24*6*(1440*10)) - 1 - tc, err = NewTimecode(maxFrames, 24000, 1001) + tc, err = NewTimecode(maxFrames, 24000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:23", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) - tc, err = NewTimecode(maxFrames+1, 24000, 1001) + tc, err = NewTimecode(maxFrames+1, 24000, 1001, assumeDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) t.Run("24fps", func(t *testing.T) { - tc, err := NewTimecode(1439, 24, 1) + tc, err := NewTimecode(1439, 24, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:23", tc.String()) assert.Equal(t, uint64(1439), tc.Frames()) @@ -189,40 +196,40 @@ func TestNewTestcodeNonDF(t *testing.T) { assert.Equal(t, int32(24), tc.FramerateNumerator()) assert.Equal(t, int32(1), tc.FramerateDenominator()) - tc, err = NewTimecode(1440, 24, 1) + tc, err = NewTimecode(1440, 24, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:00", tc.String()) assert.Equal(t, uint64(1440), tc.Frames()) - tc, err = NewTimecode(1441, 24, 1) + tc, err = NewTimecode(1441, 24, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:01", tc.String()) assert.Equal(t, uint64(1441), tc.Frames()) - tc, err = NewTimecode(1440*10, 24, 1) + tc, err = NewTimecode(1440*10, 24, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(1440*10), tc.Frames()) assert.Equal(t, 600.0, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(1440*10+1, 24, 1) + tc, err = NewTimecode(1440*10+1, 24, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:01", tc.String()) assert.Equal(t, uint64(1440*10+1), tc.Frames()) maxFrames := uint64(24*6*(1440*10)) - 1 - tc, err = NewTimecode(maxFrames, 24, 1) + tc, err = NewTimecode(maxFrames, 24, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:23", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) assert.Equal(t, 86399.958, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(maxFrames+1, 24, 1) + tc, err = NewTimecode(maxFrames+1, 24, 1, assumeDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) t.Run("25", func(t *testing.T) { - tc, err := NewTimecode(1499, 25, 1) + tc, err := NewTimecode(1499, 25, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:24", tc.String()) assert.Equal(t, uint64(1499), tc.Frames()) @@ -230,40 +237,81 @@ func TestNewTestcodeNonDF(t *testing.T) { assert.Equal(t, int32(25), tc.FramerateNumerator()) assert.Equal(t, int32(1), tc.FramerateDenominator()) - tc, err = NewTimecode(1500, 25, 1) + tc, err = NewTimecode(1500, 25, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:00", tc.String()) assert.Equal(t, uint64(1500), tc.Frames()) - tc, err = NewTimecode(1501, 25, 1) + tc, err = NewTimecode(1501, 25, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:01", tc.String()) assert.Equal(t, uint64(1501), tc.Frames()) - tc, err = NewTimecode(1500*10, 25, 1) + tc, err = NewTimecode(1500*10, 25, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(1500*10), tc.Frames()) assert.Equal(t, 600.0, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(1500*10+1, 25, 1) + tc, err = NewTimecode(1500*10+1, 25, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:01", tc.String()) assert.Equal(t, uint64(1500*10+1), tc.Frames()) maxFrames := uint64(24*6*(1500*10)) - 1 - tc, err = NewTimecode(maxFrames, 25, 1) + tc, err = NewTimecode(maxFrames, 25, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:24", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) assert.Equal(t, 86399.96, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(maxFrames+1, 25, 1) + tc, err = NewTimecode(maxFrames+1, 25, 1, assumeDF) + assert.Equal(t, ErrTooManyFrames, err) + assert.Nil(t, tc) + }) + t.Run("29.97fps (NDF)", func(t *testing.T) { + tc, err := NewTimecode(1799, 30000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:00:59:29", tc.String()) + assert.Equal(t, uint64(1799), tc.Frames()) + assert.Equal(t, 60.027, math.Round(tc.Duration().Seconds()*1000)/1000) + assert.Equal(t, int32(30000), tc.FramerateNumerator()) + assert.Equal(t, int32(1001), tc.FramerateDenominator()) + + tc, err = NewTimecode(1800, 30000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:01:00:00", tc.String()) + assert.Equal(t, uint64(1800), tc.Frames()) + + tc, err = NewTimecode(1801, 30000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:01:00:01", tc.String()) + assert.Equal(t, uint64(1801), tc.Frames()) + + tc, err = NewTimecode(1800*10, 30000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:10:00:00", tc.String()) + assert.Equal(t, uint64(1800*10), tc.Frames()) + assert.Equal(t, 600.601, math.Round(tc.Duration().Seconds()*1000)/1000) + + tc, err = NewTimecode(1800*10+1, 30000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:10:00:01", tc.String()) + assert.Equal(t, uint64(1800*10+1), tc.Frames()) + + maxFrames := uint64(24*6*(1800*10)) - 1 + tc, err = NewTimecode(maxFrames, 30000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "23:59:59:29", tc.String()) + assert.Equal(t, maxFrames, tc.Frames()) + assert.Equal(t, 86486.453, math.Round(tc.Duration().Seconds()*1000)/1000) + + tc, err = NewTimecode(maxFrames+1, 30000, 1001, assumeNDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) t.Run("30fps", func(t *testing.T) { - tc, err := NewTimecode(1799, 30, 1) + tc, err := NewTimecode(1799, 30, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:29", tc.String()) assert.Equal(t, uint64(1799), tc.Frames()) @@ -271,40 +319,40 @@ func TestNewTestcodeNonDF(t *testing.T) { assert.Equal(t, int32(30), tc.FramerateNumerator()) assert.Equal(t, int32(1), tc.FramerateDenominator()) - tc, err = NewTimecode(1800, 30, 1) + tc, err = NewTimecode(1800, 30, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:00", tc.String()) assert.Equal(t, uint64(1800), tc.Frames()) - tc, err = NewTimecode(1801, 30, 1) + tc, err = NewTimecode(1801, 30, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:01", tc.String()) assert.Equal(t, uint64(1801), tc.Frames()) - tc, err = NewTimecode(1800*10, 30, 1) + tc, err = NewTimecode(1800*10, 30, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(1800*10), tc.Frames()) assert.Equal(t, 600.0, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(1800*10+1, 30, 1) + tc, err = NewTimecode(1800*10+1, 30, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:01", tc.String()) assert.Equal(t, uint64(1800*10+1), tc.Frames()) maxFrames := uint64(24*6*(1800*10)) - 1 - tc, err = NewTimecode(maxFrames, 30, 1) + tc, err = NewTimecode(maxFrames, 30, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:29", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) assert.Equal(t, 86399.967, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(maxFrames+1, 30, 1) + tc, err = NewTimecode(maxFrames+1, 30, 1, assumeDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) t.Run("48", func(t *testing.T) { - tc, err := NewTimecode(2879, 48, 1) + tc, err := NewTimecode(2879, 48, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:47", tc.String()) assert.Equal(t, uint64(2879), tc.Frames()) @@ -312,40 +360,40 @@ func TestNewTestcodeNonDF(t *testing.T) { assert.Equal(t, int32(48), tc.FramerateNumerator()) assert.Equal(t, int32(1), tc.FramerateDenominator()) - tc, err = NewTimecode(2880, 48, 1) + tc, err = NewTimecode(2880, 48, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:00", tc.String()) assert.Equal(t, uint64(2880), tc.Frames()) - tc, err = NewTimecode(2881, 48, 1) + tc, err = NewTimecode(2881, 48, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:01", tc.String()) assert.Equal(t, uint64(2881), tc.Frames()) - tc, err = NewTimecode(2880*10, 48, 1) + tc, err = NewTimecode(2880*10, 48, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(2880*10), tc.Frames()) assert.Equal(t, 600.0, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(2880*10+1, 48, 1) + tc, err = NewTimecode(2880*10+1, 48, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:01", tc.String()) assert.Equal(t, uint64(2880*10+1), tc.Frames()) maxFrames := uint64(24*6*(2880*10)) - 1 - tc, err = NewTimecode(maxFrames, 48, 1) + tc, err = NewTimecode(maxFrames, 48, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:47", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) assert.Equal(t, 86399.979, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(maxFrames+1, 48, 1) + tc, err = NewTimecode(maxFrames+1, 48, 1, assumeDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) t.Run("50fps", func(t *testing.T) { - tc, err := NewTimecode(2999, 50, 1) + tc, err := NewTimecode(2999, 50, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:49", tc.String()) assert.Equal(t, uint64(2999), tc.Frames()) @@ -353,40 +401,86 @@ func TestNewTestcodeNonDF(t *testing.T) { assert.Equal(t, int32(50), tc.FramerateNumerator()) assert.Equal(t, int32(1), tc.FramerateDenominator()) - tc, err = NewTimecode(3000, 50, 1) + tc, err = NewTimecode(3000, 50, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:00", tc.String()) assert.Equal(t, uint64(3000), tc.Frames()) - tc, err = NewTimecode(3001, 50, 1) + tc, err = NewTimecode(3001, 50, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:01", tc.String()) assert.Equal(t, uint64(3001), tc.Frames()) - tc, err = NewTimecode(3000*10, 50, 1) + tc, err = NewTimecode(3000*10, 50, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(3000*10), tc.Frames()) assert.Equal(t, 600.0, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(3000*10+1, 50, 1) + tc, err = NewTimecode(3000*10+1, 50, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:01", tc.String()) assert.Equal(t, uint64(3000*10+1), tc.Frames()) maxFrames := uint64(24*6*(3000*10)) - 1 - tc, err = NewTimecode(maxFrames, 50, 1) + tc, err = NewTimecode(maxFrames, 50, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:49", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) assert.Equal(t, 86399.98, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(maxFrames+1, 50, 1) + tc, err = NewTimecode(maxFrames+1, 50, 1, assumeDF) + assert.Equal(t, ErrTooManyFrames, err) + assert.Nil(t, tc) + }) + t.Run("59.94fps (NDF)", func(t *testing.T) { + tc, err := NewTimecode(0, 60000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:00:00:00", tc.String()) + assert.Equal(t, uint64(0), tc.Frames()) + assert.Equal(t, 0.0, math.Round(tc.Duration().Seconds()*1000)/1000) + assert.Equal(t, int32(60000), tc.FramerateNumerator()) + assert.Equal(t, int32(1001), tc.FramerateDenominator()) + + tc, err = NewTimecode(3599, 60000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:00:59:59", tc.String()) + assert.Equal(t, uint64(3599), tc.Frames()) + + tc, err = NewTimecode(3600, 60000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:01:00:00", tc.String()) + assert.Equal(t, uint64(3600), tc.Frames()) + + tc, err = NewTimecode(3601, 60000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:01:00:01", tc.String()) + assert.Equal(t, uint64(3601), tc.Frames()) + + tc, err = NewTimecode(3600*10, 60000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:10:00:00", tc.String()) + assert.Equal(t, uint64(3600*10), tc.Frames()) + assert.Equal(t, 600.601, math.Round(tc.Duration().Seconds()*1000)/1000) + + tc, err = NewTimecode(3600*10+1, 60000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "00:10:00:01", tc.String()) + assert.Equal(t, uint64(3600*10+1), tc.Frames()) + + maxFrames := uint64(24*6*(3600*10)) - 1 + tc, err = NewTimecode(maxFrames, 60000, 1001, assumeNDF) + assert.NoError(t, err) + assert.Equal(t, "23:59:59:59", tc.String()) + assert.Equal(t, maxFrames, tc.Frames()) + assert.Equal(t, 86486.47, math.Round(tc.Duration().Seconds()*1000)/1000) + + tc, err = NewTimecode(maxFrames+1, 60000, 1001, assumeNDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) t.Run("60fps", func(t *testing.T) { - tc, err := NewTimecode(0, 60, 1) + tc, err := NewTimecode(0, 60, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:00:00", tc.String()) assert.Equal(t, uint64(0), tc.Frames()) @@ -394,162 +488,176 @@ func TestNewTestcodeNonDF(t *testing.T) { assert.Equal(t, int32(60), tc.FramerateNumerator()) assert.Equal(t, int32(1), tc.FramerateDenominator()) - tc, err = NewTimecode(3599, 60, 1) + tc, err = NewTimecode(3599, 60, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:59", tc.String()) assert.Equal(t, uint64(3599), tc.Frames()) - tc, err = NewTimecode(3600, 60, 1) + tc, err = NewTimecode(3600, 60, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:00", tc.String()) assert.Equal(t, uint64(3600), tc.Frames()) - tc, err = NewTimecode(3601, 60, 1) + tc, err = NewTimecode(3601, 60, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:01", tc.String()) assert.Equal(t, uint64(3601), tc.Frames()) - tc, err = NewTimecode(3600*10, 60, 1) + tc, err = NewTimecode(3600*10, 60, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(3600*10), tc.Frames()) assert.Equal(t, 600.0, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(3600*10+1, 60, 1) + tc, err = NewTimecode(3600*10+1, 60, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:01", tc.String()) assert.Equal(t, uint64(3600*10+1), tc.Frames()) maxFrames := uint64(24*6*(3600*10)) - 1 - tc, err = NewTimecode(maxFrames, 60, 1) + tc, err = NewTimecode(maxFrames, 60, 1, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:59", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) assert.Equal(t, 86399.983, math.Round(tc.Duration().Seconds()*1000)/1000) - tc, err = NewTimecode(maxFrames+1, 60, 1) + tc, err = NewTimecode(maxFrames+1, 60, 1, assumeDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) } -func TestNewTestcodeDF(t *testing.T) { +func TestNewTimecodeDF(t *testing.T) { + assumeDF := func(p *TimecodeOptionParam) { + p.PreferDF = true + } + t.Run("30DF", func(t *testing.T) { - tc, err := NewTimecode(1798, 30000, 1001) + tc, err := NewTimecode(1798, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:28", tc.String()) assert.Equal(t, uint64(1798), tc.Frames()) - tc, err = NewTimecode(1799, 30000, 1001) + tc, err = NewTimecode(1799, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:29", tc.String()) assert.Equal(t, uint64(1799), tc.Frames()) - tc, err = NewTimecode(1800, 30000, 1001) + tc, err = NewTimecode(1800, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:02", tc.String()) assert.Equal(t, uint64(1800), tc.Frames()) - tc, err = NewTimecode(1800+1798*8, 30000, 1001) + tc, err = NewTimecode(1800+1798*8, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:09:00:02", tc.String()) assert.Equal(t, uint64(1800+1798*8), tc.Frames()) - tc, err = NewTimecode(1800+1798*9, 30000, 1001) + tc, err = NewTimecode(1800+1798*9, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(1800+1798*9), tc.Frames()) - tc, err = NewTimecode(1800+1798*9+1799, 30000, 1001) + tc, err = NewTimecode(1800+1798*9+1799, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:59:29", tc.String()) assert.Equal(t, uint64(1800+1798*9+1799), tc.Frames()) - tc, err = NewTimecode(1800+1798*9+1800, 30000, 1001) + tc, err = NewTimecode(1800+1798*9+1800, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:11:00:02", tc.String()) assert.Equal(t, uint64(1800+1798*9+1800), tc.Frames()) maxFrames := uint64(24*6*(1800+1798*9)) - 1 - tc, err = NewTimecode(maxFrames, 30000, 1001) + tc, err = NewTimecode(maxFrames, 30000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:29", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) - tc, err = NewTimecode(maxFrames+1, 30000, 1001) + tc, err = NewTimecode(maxFrames+1, 30000, 1001, assumeDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) t.Run("60DF", func(t *testing.T) { - tc, err := NewTimecode(3596, 60000, 1001) + tc, err := NewTimecode(3596, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:56", tc.String()) assert.Equal(t, uint64(3596), tc.Frames()) - tc, err = NewTimecode(3599, 60000, 1001) + tc, err = NewTimecode(3599, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:00:59:59", tc.String()) assert.Equal(t, uint64(3599), tc.Frames()) - tc, err = NewTimecode(3600, 60000, 1001) + tc, err = NewTimecode(3600, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:01:00:04", tc.String()) assert.Equal(t, uint64(3600), tc.Frames()) - tc, err = NewTimecode(3600+3596*8, 60000, 1001) + tc, err = NewTimecode(3600+3596*8, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:09:00:04", tc.String()) assert.Equal(t, uint64(3600+3596*8), tc.Frames()) - tc, err = NewTimecode(3600+3596*9, 60000, 1001) + tc, err = NewTimecode(3600+3596*9, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:00:00", tc.String()) assert.Equal(t, uint64(3600+3596*9), tc.Frames()) - tc, err = NewTimecode(3600+3596*9+3599, 60000, 1001) + tc, err = NewTimecode(3600+3596*9+3599, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:10:59:59", tc.String()) assert.Equal(t, uint64(3600+3596*9+3599), tc.Frames()) - tc, err = NewTimecode(3600+3596*9+3600, 60000, 1001) + tc, err = NewTimecode(3600+3596*9+3600, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "00:11:00:04", tc.String()) assert.Equal(t, uint64(3600+3596*9+3600), tc.Frames()) maxFrames := uint64(24*6*(3600+3596*9)) - 1 - tc, err = NewTimecode(maxFrames, 60000, 1001) + tc, err = NewTimecode(maxFrames, 60000, 1001, assumeDF) assert.NoError(t, err) assert.Equal(t, "23:59:59:59", tc.String()) assert.Equal(t, maxFrames, tc.Frames()) - tc, err = NewTimecode(maxFrames+1, 60000, 1001) + tc, err = NewTimecode(maxFrames+1, 60000, 1001, assumeDF) assert.Equal(t, ErrTooManyFrames, err) assert.Nil(t, tc) }) } func TestParseTimecode(t *testing.T) { - t.Run("ParseTimecode", func(t *testing.T) { - tc, err := ParseTimecode("00:01:00;00", 30000, 1001) + t.Run("ParseTimecode/29.97DF", func(t *testing.T) { + tc, err := ParseTimecode("00:01:00;00", 30000, 1001) // DF assert.NoError(t, err) assert.Equal(t, "00:01:00;02", tc.String()) assert.Equal(t, uint64(1800), tc.Frames()) - assert.Equal(t, ":", tc.optp.Sep) - assert.Equal(t, ";", tc.optp.SepDF) + assert.Equal(t, ":", tc.sep) + assert.Equal(t, ";", tc.lastSep) + }) + t.Run("ParseTimecode/29.97NDF", func(t *testing.T) { + tc, err := ParseTimecode("00:01:00;00", 30000, 1001, func(p *ParseTimecodeOptionParam) { + p.PreferDF = false + }) // NDF + assert.NoError(t, err) + assert.Equal(t, "00:01:00:00", tc.String()) + assert.Equal(t, uint64(1800), tc.Frames()) + assert.Equal(t, ":", tc.sep) + assert.Equal(t, ":", tc.lastSep) }) t.Run("ParseTimecode/19h", func(t *testing.T) { tc, err := ParseTimecode("19:00:00;00", 24, 1) assert.NoError(t, err) assert.Equal(t, uint64(1641600), tc.Frames()) - assert.Equal(t, ":", tc.optp.Sep) - assert.Equal(t, ";", tc.optp.SepDF) + assert.Equal(t, ":", tc.sep) + assert.Equal(t, ":", tc.lastSep) }) t.Run("ParseTimecode/23h", func(t *testing.T) { tc, err := ParseTimecode("23:00:00;00", 24, 1) assert.NoError(t, err) assert.Equal(t, uint64(1987200), tc.Frames()) - assert.Equal(t, ":", tc.optp.Sep) - assert.Equal(t, ";", tc.optp.SepDF) + assert.Equal(t, ":", tc.sep) + assert.Equal(t, ":", tc.lastSep) }) t.Run("ParseTimecode/24h", func(t *testing.T) { tc, err := ParseTimecode("24:01:00;00", 24, 1) @@ -624,13 +732,22 @@ func TestAdd(t *testing.T) { tc2, _ := tc1.AddFrames(982) assert.Equal(t, "00:10:00:00", tc2.String()) }) - t.Run("Add/mismatch frame rate", func(t *testing.T) { + t.Run("Add/mismatch frame rate1", func(t *testing.T) { tc1, _ := NewTimecode(1, 30, 1) tc2, _ := NewTimecode(1, 30000, 1001) tc3, err := tc1.Add(tc2) assert.Nil(t, tc3) assert.Equal(t, ErrMismatchFrameRate, err) }) + t.Run("Add/mismatch frame rate2", func(t *testing.T) { + tc1, _ := NewTimecode(1, 30000, 1001) + tc2, _ := NewTimecode(1, 30000, 1001, func(p *TimecodeOptionParam) { + p.PreferDF = false + }) + tc3, err := tc1.Add(tc2) + assert.Nil(t, tc3) + assert.Equal(t, ErrMismatchFrameRate, err) + }) t.Run("Add/overflow", func(t *testing.T) { tc1, _ := NewTimecode(2589407, 30000, 1001) tc2, _ := NewTimecode(1, 30000, 1001) @@ -684,7 +801,7 @@ func TestTimecodeOption(t *testing.T) { t.Run("single option/DF", func(t *testing.T) { opt := func(p *TimecodeOptionParam) { p.Sep = "." - p.SepDF = "," + p.LastSep = "," } tc, err := NewTimecode(3596, 60000, 1001, opt) assert.NoError(t, err) @@ -695,7 +812,7 @@ func TestTimecodeOption(t *testing.T) { p.Sep = "," } opt2 := func(p *TimecodeOptionParam) { - p.SepDF = ";" + p.LastSep = ";" } tc, err := NewTimecode(3596, 60000, 1001, opt1, opt2) assert.NoError(t, err) @@ -706,9 +823,10 @@ func TestTimecodeOption(t *testing.T) { p.Sep = "." } opt2 := func(p *TimecodeOptionParam) { - p.SepDF = ";" + p.LastSep = ";" + p.PreferDF = false } - tc, err := NewTimecode(3596, 60, 1, opt1, opt2) + tc, err := NewTimecode(3596, 60000, 1001, opt1, opt2) assert.NoError(t, err) assert.Equal(t, "00.00.59.56", tc.String()) })