timecode 1.1.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +9 -0
- data/History.txt +5 -0
- data/Rakefile +25 -12
- data/lib/timecode.rb +140 -83
- data/test/test_timecode.rb +352 -275
- data/timecode.gemspec +53 -0
- metadata +108 -69
- data/.DS_Store +0 -0
- data/.gemtest +0 -0
- data/Manifest.txt +0 -7
data/test/test_timecode.rb
CHANGED
@@ -1,493 +1,570 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'minitest/spec'
|
3
|
+
require 'minitest/autorun'
|
3
4
|
|
4
5
|
require File.expand_path(File.dirname(__FILE__)) + '/../lib/timecode'
|
5
6
|
|
7
|
+
# Needed for a number of tests from the past
|
8
|
+
Timecode.add_custom_framerate!(10)
|
9
|
+
Timecode.add_custom_framerate!(12.5)
|
10
|
+
Timecode.add_custom_framerate!(57)
|
11
|
+
Timecode.add_custom_framerate!(45)
|
12
|
+
Timecode.add_custom_framerate!(12)
|
13
|
+
|
14
|
+
|
6
15
|
describe "Timecode.new should" do
|
7
|
-
|
8
|
-
it "
|
16
|
+
|
17
|
+
it "instantiate from int" do
|
9
18
|
tc = Timecode.new(10)
|
10
|
-
tc.
|
11
|
-
tc.total.
|
19
|
+
tc.must_be_kind_of Timecode
|
20
|
+
tc.total.must_equal 10
|
12
21
|
end
|
13
|
-
|
14
|
-
it "
|
15
|
-
Timecode.new(10, 24).fps.
|
16
|
-
Timecode.new(10, 25.0).fps.
|
22
|
+
|
23
|
+
it "always coerce FPS to float" do
|
24
|
+
Timecode.new(10, 24).fps.must_be_kind_of(Float)
|
25
|
+
Timecode.new(10, 25.0).fps.must_be_kind_of(Float)
|
17
26
|
end
|
18
|
-
|
19
|
-
it "
|
20
|
-
Timecode.new(
|
27
|
+
|
28
|
+
it "create a zero TC with no arguments" do
|
29
|
+
Timecode.new.must_equal Timecode.new(0)
|
21
30
|
end
|
22
|
-
|
23
|
-
it "
|
24
|
-
Timecode.new("00:25:30:10", 25).
|
31
|
+
|
32
|
+
it "accept full string SMPTE timecode as well" do
|
33
|
+
Timecode.new("00:25:30:10", 25).must_equal Timecode.parse("00:25:30:10")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'calculates correctly (spot check with special values)' do
|
37
|
+
lambda{ Timecode.new 496159, 23.976 }.must_be_silent
|
38
|
+
lambda{ Timecode.new 548999, 23.976 }.must_be_silent
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'calculates seconds correctly for rational fps' do
|
42
|
+
Timecode.new(548999, 23.976).seconds.must_equal 37
|
25
43
|
end
|
26
|
-
|
27
44
|
end
|
28
45
|
|
29
|
-
describe "Timecode.validate_atoms! should" do
|
30
|
-
|
31
|
-
it "
|
32
|
-
lambda{ Timecode.validate_atoms!(
|
33
|
-
lambda{ Timecode.validate_atoms!(
|
46
|
+
describe "Timecode.validate_atoms! should" do
|
47
|
+
|
48
|
+
it "disallow more than 999 hrs" do
|
49
|
+
lambda{ Timecode.validate_atoms!(999,0,0,0, 25) }.must_be_silent
|
50
|
+
lambda{ Timecode.validate_atoms!(1000,0,0,0, 25) }.must_raise(Timecode::RangeError)
|
34
51
|
end
|
35
|
-
|
36
|
-
it "
|
37
|
-
lambda{ Timecode.validate_atoms!(1,60,0,0, 25) }.
|
52
|
+
|
53
|
+
it "disallow more than 59 minutes" do
|
54
|
+
lambda{ Timecode.validate_atoms!(1,60,0,0, 25) }.must_raise(Timecode::RangeError)
|
38
55
|
end
|
39
56
|
|
40
|
-
it "
|
41
|
-
lambda{ Timecode.validate_atoms!(1,0,60,0, 25) }.
|
57
|
+
it "disallow more than 59 seconds" do
|
58
|
+
lambda{ Timecode.validate_atoms!(1,0,60,0, 25) }.must_raise(Timecode::RangeError)
|
42
59
|
end
|
43
|
-
|
44
|
-
it "
|
45
|
-
lambda{ Timecode.validate_atoms!(1,0,45,25, 25) }.
|
46
|
-
lambda{ Timecode.validate_atoms!(1,0,45,32, 30) }.
|
60
|
+
|
61
|
+
it "disallow more frames than what the framerate permits" do
|
62
|
+
lambda{ Timecode.validate_atoms!(1,0,45,25, 25) }.must_raise(Timecode::RangeError)
|
63
|
+
lambda{ Timecode.validate_atoms!(1,0,45,32, 30) }.must_raise(Timecode::RangeError)
|
47
64
|
end
|
48
|
-
|
49
|
-
it "
|
50
|
-
lambda{ Timecode.validate_atoms!(20, 20, 10, 5, 25)}.
|
65
|
+
|
66
|
+
it "pass validation with usable values" do
|
67
|
+
lambda{ Timecode.validate_atoms!(20, 20, 10, 5, 25)}.must_be_silent
|
51
68
|
end
|
52
69
|
end
|
53
70
|
|
54
|
-
describe "Timecode.at should" do
|
55
|
-
|
56
|
-
it "
|
57
|
-
lambda{ Timecode.at(
|
58
|
-
lambda{ Timecode.at(
|
71
|
+
describe "Timecode.at should" do
|
72
|
+
|
73
|
+
it "disallow more than 999 hrs" do
|
74
|
+
lambda{ Timecode.at(999,0,0,0) }.must_be_silent
|
75
|
+
lambda{ Timecode.at(1000,0,0,0) }.must_raise(Timecode::RangeError)
|
59
76
|
end
|
60
|
-
|
61
|
-
it "
|
62
|
-
lambda{ Timecode.at(1,60,0,0) }.
|
77
|
+
|
78
|
+
it "disallow more than 59 minutes" do
|
79
|
+
lambda{ Timecode.at(1,60,0,0) }.must_raise(Timecode::RangeError)
|
63
80
|
end
|
64
81
|
|
65
|
-
it "
|
66
|
-
lambda{ Timecode.at(1,0,60,0) }.
|
82
|
+
it "disallow more than 59 seconds" do
|
83
|
+
lambda{ Timecode.at(1,0,60,0) }.must_raise(Timecode::RangeError)
|
67
84
|
end
|
68
|
-
|
69
|
-
it "
|
70
|
-
lambda{ Timecode.at(1,0,60,25, 25) }.
|
71
|
-
lambda{ Timecode.at(1,0,60,32, 30) }.
|
85
|
+
|
86
|
+
it "disallow more frames than what the framerate permits" do
|
87
|
+
lambda{ Timecode.at(1,0,60,25, 25) }.must_raise(Timecode::RangeError)
|
88
|
+
lambda{ Timecode.at(1,0,60,32, 30) }.must_raise(Timecode::RangeError)
|
72
89
|
end
|
73
|
-
|
74
|
-
it "
|
75
|
-
Timecode.at(20, 20, 10, 5).to_s.
|
90
|
+
|
91
|
+
it "propery accept usable values" do
|
92
|
+
Timecode.at(20, 20, 10, 5).to_s.must_equal "20:20:10:05"
|
76
93
|
end
|
77
94
|
end
|
78
95
|
|
79
96
|
describe "A new Timecode object should" do
|
80
|
-
it "
|
81
|
-
|
97
|
+
it "be frozen" do
|
98
|
+
# must_be :frozen? is somehow dead too
|
99
|
+
assert Timecode.new(10).frozen?
|
82
100
|
end
|
83
101
|
end
|
84
102
|
|
85
103
|
describe "An existing Timecode should" do
|
86
|
-
|
104
|
+
|
87
105
|
before do
|
88
106
|
@five_seconds = Timecode.new(5*25, 25)
|
89
107
|
@one_and_a_half_film = (90 * 60) * 24
|
90
108
|
@film_tc = Timecode.new(@one_and_a_half_film, 24)
|
91
109
|
end
|
92
|
-
|
93
|
-
it "
|
110
|
+
|
111
|
+
it "report that the framerates are in delta" do
|
94
112
|
tc = Timecode.new(1)
|
95
|
-
tc.framerate_in_delta(25.0000000000000001, 25.0000000000000003).
|
113
|
+
tc.framerate_in_delta(25.0000000000000001, 25.0000000000000003).must_equal(true)
|
96
114
|
end
|
97
|
-
|
98
|
-
it "
|
115
|
+
|
116
|
+
it "validate equality based on delta" do
|
99
117
|
t1, t2 = Timecode.new(10, 25.0000000000000000000000000001), Timecode.new(10, 25.0000000000000000000000000002)
|
100
|
-
t1.
|
118
|
+
t1.must_equal(t2)
|
101
119
|
end
|
102
|
-
|
103
|
-
it "
|
104
|
-
Timecode.new(10).to_i.
|
120
|
+
|
121
|
+
it "report total as it's to_i" do
|
122
|
+
Timecode.new(10).to_i.must_equal(10)
|
105
123
|
end
|
106
|
-
|
107
|
-
it "
|
108
|
-
(10 + Timecode.new(2)).
|
124
|
+
|
125
|
+
it "coerce itself to int" do
|
126
|
+
(10 + Timecode.new(2)).must_equal 12
|
109
127
|
end
|
110
|
-
|
111
|
-
it "
|
112
|
-
@five_seconds.
|
113
|
-
@five_seconds.hours.
|
114
|
-
@film_tc.hours.
|
128
|
+
|
129
|
+
it "support hours" do
|
130
|
+
@five_seconds.must_respond_to :hours
|
131
|
+
@five_seconds.hours.must_equal 0
|
132
|
+
@film_tc.hours.must_equal 1
|
115
133
|
end
|
116
134
|
|
117
|
-
it "
|
118
|
-
@five_seconds.
|
119
|
-
@five_seconds.minutes.
|
120
|
-
@film_tc.minutes.
|
135
|
+
it "support minutes" do
|
136
|
+
@five_seconds.must_respond_to :minutes
|
137
|
+
@five_seconds.minutes.must_equal 0
|
138
|
+
@film_tc.minutes.must_equal 30
|
121
139
|
end
|
122
140
|
|
123
|
-
it "
|
124
|
-
@five_seconds.
|
125
|
-
@five_seconds.seconds.
|
126
|
-
@film_tc.seconds.
|
141
|
+
it "support seconds" do
|
142
|
+
@five_seconds.must_respond_to :seconds
|
143
|
+
@five_seconds.seconds.must_equal 5
|
144
|
+
@film_tc.seconds.must_equal 0
|
127
145
|
end
|
128
|
-
|
129
|
-
it "
|
130
|
-
@film_tc.frames.
|
146
|
+
|
147
|
+
it "support frames" do
|
148
|
+
@film_tc.frames.must_equal 0
|
131
149
|
end
|
132
|
-
|
133
|
-
it "
|
150
|
+
|
151
|
+
it "report frame_interval as a float" do
|
134
152
|
tc = Timecode.new(10)
|
135
|
-
tc.
|
136
|
-
|
137
|
-
tc.frame_interval.
|
153
|
+
tc.must_respond_to :frame_interval
|
154
|
+
|
155
|
+
tc.frame_interval.must_be_within_delta 0.04, 0.0001
|
138
156
|
tc = Timecode.new(10, 30)
|
139
|
-
tc.frame_interval.
|
157
|
+
tc.frame_interval.must_be_within_delta 0.03333, 0.0001
|
140
158
|
end
|
141
|
-
|
142
|
-
it "
|
143
|
-
(Timecode.new(10) < Timecode.new(9)).
|
144
|
-
(Timecode.new(9) < Timecode.new(10)).
|
145
|
-
Timecode.new(9).
|
159
|
+
|
160
|
+
it "be comparable" do
|
161
|
+
(Timecode.new(10) < Timecode.new(9)).must_equal false
|
162
|
+
(Timecode.new(9) < Timecode.new(10)).must_equal true
|
163
|
+
Timecode.new(9).must_equal Timecode.new(9)
|
146
164
|
end
|
147
|
-
|
148
|
-
it "
|
149
|
-
lambda { Timecode.new(10, 10) < Timecode.new(10, 20)}.
|
165
|
+
|
166
|
+
it "raise on comparison of incompatible timecodes" do
|
167
|
+
lambda { Timecode.new(10, 10) < Timecode.new(10, 20)}.must_raise(Timecode::WrongFramerate)
|
150
168
|
end
|
151
169
|
end
|
152
170
|
|
171
|
+
#module MiniTest::Assertions
|
172
|
+
# UNDEFINED = MiniTest::Assertions::UNDEFINED
|
173
|
+
# def assert_operator o1, op, o2 = UNDEFINED, msg = nil
|
174
|
+
# puts [o1, op, o2, msg].inspect
|
175
|
+
# return assert_predicate o1, op, msg if UNDEFINED == o2
|
176
|
+
# msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" }
|
177
|
+
# assert o1.__send__(op, o2), msg
|
178
|
+
# end
|
179
|
+
#end
|
180
|
+
|
153
181
|
describe "A Timecode of zero should" do
|
154
|
-
it "
|
155
|
-
Timecode.new(0).
|
156
|
-
|
157
|
-
Timecode.new(
|
182
|
+
it "properly respond to zero?" do
|
183
|
+
Timecode.new(0).must_respond_to :zero?
|
184
|
+
# must_be :zero? is somehow broken
|
185
|
+
assert Timecode.new(0).zero?
|
186
|
+
refute Timecode.new(1).zero?
|
158
187
|
end
|
159
188
|
end
|
160
189
|
|
161
190
|
describe "Timecode.from_seconds should" do
|
162
|
-
it "
|
191
|
+
it "properly process this specific case for a float framerate" do
|
163
192
|
float_secs = 89.99165971643036
|
164
193
|
float_fps = 23.9898
|
165
|
-
|
194
|
+
Timecode.add_custom_framerate!(float_fps)
|
195
|
+
lambda{ Timecode.from_seconds(float_secs, float_fps) }.must_be_silent
|
166
196
|
end
|
167
197
|
end
|
168
198
|
|
169
199
|
describe "Timecode#to_seconds should" do
|
170
|
-
it "
|
171
|
-
Timecode.new(0).to_seconds.
|
200
|
+
it "return a float" do
|
201
|
+
Timecode.new(0).to_seconds.must_be_kind_of Float
|
172
202
|
end
|
173
|
-
|
174
|
-
it "
|
203
|
+
|
204
|
+
it "return the value in seconds" do
|
175
205
|
fps = 24
|
176
206
|
secs = 126.3
|
177
|
-
Timecode.new(fps * secs, fps).to_seconds.
|
207
|
+
Timecode.new(fps * secs, fps).to_seconds.must_be_within_delta 126.3, 0.1
|
178
208
|
end
|
179
|
-
|
180
|
-
it "
|
209
|
+
|
210
|
+
it "properly roundtrip a value via Timecode.from_seconds" do
|
181
211
|
secs_in = 19.76
|
182
212
|
from_secs = Timecode.from_seconds(19.76, 25.0)
|
183
|
-
from_secs.total.
|
184
|
-
from_secs.to_seconds.
|
213
|
+
from_secs.total.must_equal 494
|
214
|
+
from_secs.to_seconds.must_be_within_delta secs_in, 0.001
|
185
215
|
end
|
186
216
|
end
|
187
217
|
|
188
218
|
describe "An existing Timecode on inspection should" do
|
189
|
-
it "
|
190
|
-
Timecode.new(10, 25).inspect.
|
191
|
-
Timecode.new(10, 12).inspect.
|
219
|
+
it "properly present himself via inspect" do
|
220
|
+
Timecode.new(10, 25).inspect.must_equal "#<Timecode:00:00:00:10 (10F@25.00)>"
|
221
|
+
Timecode.new(10, 12).inspect.must_equal "#<Timecode:00:00:00:10 (10F@12.00)>"
|
192
222
|
end
|
193
|
-
|
194
|
-
it "
|
195
|
-
Timecode.new(5, 25).to_s.
|
223
|
+
|
224
|
+
it "properly print itself" do
|
225
|
+
Timecode.new(5, 25).to_s.must_equal "00:00:00:05"
|
196
226
|
end
|
197
227
|
end
|
198
228
|
|
199
229
|
describe "An existing Timecode compared by adjacency" do
|
200
|
-
it "
|
201
|
-
Timecode.new(10).
|
230
|
+
it "properly detect an adjacent timecode to the left" do
|
231
|
+
Timecode.new(10).must_be :adjacent_to?, Timecode.new(9)
|
232
|
+
Timecode.new(10).wont_be :adjacent_to?, Timecode.new(8)
|
202
233
|
end
|
203
|
-
|
204
|
-
it "
|
205
|
-
Timecode.new(10).
|
234
|
+
|
235
|
+
it "properly detect an adjacent timecode to the right" do
|
236
|
+
Timecode.new(10).must_be :adjacent_to?, Timecode.new(11)
|
237
|
+
Timecode.new(10).wont_be :adjacent_to?, Timecode.new(12)
|
206
238
|
end
|
207
|
-
|
239
|
+
|
208
240
|
end
|
209
241
|
|
210
242
|
describe "A Timecode on conversion should" do
|
211
|
-
it "
|
243
|
+
it "copy itself with a different framerate" do
|
212
244
|
tc = Timecode.new(40,25)
|
213
245
|
at24 = tc.convert(24)
|
214
|
-
at24.total.
|
246
|
+
at24.total.must_equal 40
|
215
247
|
end
|
216
248
|
end
|
217
249
|
|
218
250
|
describe "An existing Timecode used within ranges should" do
|
219
|
-
it "
|
220
|
-
Timecode.new(10).succ.total.
|
221
|
-
Timecode.new(22, 45).succ.
|
251
|
+
it "properly provide successive value that is one frame up" do
|
252
|
+
Timecode.new(10).succ.total.must_equal 11
|
253
|
+
Timecode.new(22, 45).succ.must_equal Timecode.new(23, 45)
|
222
254
|
end
|
223
|
-
|
224
|
-
it "
|
255
|
+
|
256
|
+
it "work as a range member" do
|
225
257
|
r = Timecode.new(10)...Timecode.new(20)
|
226
|
-
r.to_a.length.
|
227
|
-
r.to_a[4].
|
258
|
+
r.to_a.length.must_equal 10
|
259
|
+
r.to_a[4].must_equal Timecode.new(14)
|
228
260
|
end
|
229
|
-
|
261
|
+
|
230
262
|
end
|
231
263
|
|
232
264
|
describe "A Timecode on conversion should" do
|
233
|
-
it "
|
265
|
+
it "copy itself with a different framerate" do
|
234
266
|
tc = Timecode.new(40,25)
|
235
267
|
at24 = tc.convert(24)
|
236
|
-
at24.total.
|
268
|
+
at24.total.must_equal 40
|
237
269
|
end
|
238
270
|
end
|
239
271
|
|
240
272
|
describe "A Timecode on calculations should" do
|
241
|
-
|
242
|
-
it "
|
273
|
+
|
274
|
+
it "support addition" do
|
243
275
|
a, b = Timecode.new(24, 25.000000000000001), Timecode.new(22, 25.000000000000002)
|
244
|
-
(a + b).
|
276
|
+
(a + b).must_equal Timecode.new(24 + 22, 25.000000000000001)
|
245
277
|
end
|
246
|
-
|
247
|
-
it "should
|
248
|
-
lambda{ Timecode.new(10, 25) + Timecode.new(10, 30) }.
|
278
|
+
|
279
|
+
it "should raise on addition if framerates do not match" do
|
280
|
+
lambda{ Timecode.new(10, 25) + Timecode.new(10, 30) }.must_raise(Timecode::WrongFramerate)
|
249
281
|
end
|
250
|
-
|
251
|
-
it "
|
252
|
-
(Timecode.new(5) + 5).
|
282
|
+
|
283
|
+
it "when added with an integer instead calculate on total" do
|
284
|
+
(Timecode.new(5) + 5).must_equal(Timecode.new(10))
|
253
285
|
end
|
254
|
-
|
255
|
-
it "
|
286
|
+
|
287
|
+
it "support subtraction" do
|
256
288
|
a, b = Timecode.new(10), Timecode.new(4)
|
257
|
-
(a - b).
|
289
|
+
(a - b).must_equal Timecode.new(6)
|
258
290
|
end
|
259
291
|
|
260
|
-
it "
|
261
|
-
(Timecode.new(15) - 5).
|
292
|
+
it "on subtraction of an integer instead calculate on total" do
|
293
|
+
(Timecode.new(15) - 5).must_equal Timecode.new(10)
|
262
294
|
end
|
263
|
-
|
264
|
-
it "
|
265
|
-
lambda { Timecode.new(10, 25) - Timecode.new(10, 30) }.
|
295
|
+
|
296
|
+
it "raise when subtracting a Timecode with a different framerate" do
|
297
|
+
lambda { Timecode.new(10, 25) - Timecode.new(10, 30) }.must_raise(Timecode::WrongFramerate)
|
266
298
|
end
|
267
|
-
|
268
|
-
it "
|
269
|
-
(Timecode.new(10) * 10).
|
299
|
+
|
300
|
+
it "support multiplication" do
|
301
|
+
(Timecode.new(10) * 10).must_equal(Timecode.new(100))
|
270
302
|
end
|
271
|
-
|
272
|
-
it "
|
273
|
-
lambda { Timecode.new(10) * -200 }.
|
303
|
+
|
304
|
+
it "raise when the resultig Timecode is negative" do
|
305
|
+
lambda { Timecode.new(10) * -200 }.must_raise(Timecode::RangeError)
|
274
306
|
end
|
275
|
-
|
276
|
-
it "
|
307
|
+
|
308
|
+
it "return a Timecode when divided by an Integer" do
|
277
309
|
v = Timecode.new(200) / 20
|
278
|
-
v.
|
279
|
-
v.
|
310
|
+
v.must_be_kind_of(Timecode)
|
311
|
+
v.must_equal Timecode.new(10)
|
280
312
|
end
|
281
|
-
|
282
|
-
it "
|
313
|
+
|
314
|
+
it "return a number when divided by another Timecode" do
|
283
315
|
v = Timecode.new(200) / Timecode.new(20)
|
284
|
-
v.
|
285
|
-
v.
|
316
|
+
v.must_be_kind_of(Numeric)
|
317
|
+
v.must_equal 10
|
286
318
|
end
|
287
319
|
end
|
288
320
|
|
289
321
|
describe "A Timecode used with fractional number of seconds" do
|
290
|
-
|
291
|
-
it "should
|
322
|
+
|
323
|
+
it "should properly return fractional seconds" do
|
292
324
|
tc = Timecode.new(100 - 1, fps = 25)
|
293
|
-
tc.frames.
|
294
|
-
|
295
|
-
tc.with_frames_as_fraction.
|
296
|
-
tc.with_fractional_seconds.
|
325
|
+
tc.frames.must_equal 24
|
326
|
+
|
327
|
+
tc.with_frames_as_fraction.must_equal "00:00:03.96"
|
328
|
+
tc.with_fractional_seconds.must_equal "00:00:03.96"
|
329
|
+
tc.with_srt_fraction.must_equal "00:00:03,96"
|
297
330
|
end
|
298
|
-
|
299
|
-
it "
|
331
|
+
|
332
|
+
it "properly translate to frames when instantiated from fractional seconds" do
|
300
333
|
fraction = 7.1
|
301
334
|
tc = Timecode.from_seconds(fraction, 10)
|
302
|
-
tc.to_s.
|
335
|
+
tc.to_s.must_equal "00:00:07:01"
|
303
336
|
|
304
337
|
fraction = 7.5
|
305
338
|
tc = Timecode.from_seconds(fraction, 10)
|
306
|
-
tc.to_s.
|
339
|
+
tc.to_s.must_equal "00:00:07:05"
|
307
340
|
|
308
341
|
fraction = 7.16
|
309
342
|
tc = Timecode.from_seconds(fraction, 12.5)
|
310
|
-
tc.to_s.
|
343
|
+
tc.to_s.must_equal "00:00:07:01"
|
311
344
|
end
|
312
345
|
|
313
346
|
end
|
314
347
|
|
315
348
|
describe "A custom Timecode descendant should" do
|
316
349
|
class CustomTC < Timecode; end
|
317
|
-
|
318
|
-
it "
|
319
|
-
CustomTC.parse("001").
|
350
|
+
|
351
|
+
it "properly classify on parse" do
|
352
|
+
CustomTC.parse("001").must_be_kind_of CustomTC
|
320
353
|
end
|
321
354
|
|
322
|
-
it "
|
323
|
-
CustomTC.at(10,10,10,10).
|
355
|
+
it "properly classify on at" do
|
356
|
+
CustomTC.at(10,10,10,10).must_be_kind_of CustomTC
|
324
357
|
end
|
325
358
|
|
326
|
-
it "
|
359
|
+
it "properly classify on calculations" do
|
327
360
|
computed = CustomTC.parse("10h") + Timecode.new(10)
|
328
|
-
computed.
|
361
|
+
computed.must_be_kind_of CustomTC
|
329
362
|
|
330
363
|
computed = CustomTC.parse("10h") - Timecode.new(10)
|
331
|
-
computed.
|
364
|
+
computed.must_be_kind_of CustomTC
|
332
365
|
|
333
366
|
computed = CustomTC.parse("10h") * 5
|
334
|
-
computed.
|
367
|
+
computed.must_be_kind_of CustomTC
|
335
368
|
|
336
369
|
computed = CustomTC.parse("10h") / 5
|
337
|
-
computed.
|
370
|
+
computed.must_be_kind_of CustomTC
|
338
371
|
end
|
339
372
|
|
340
373
|
end
|
341
374
|
|
342
|
-
describe "Timecode.
|
375
|
+
describe "Timecode.from_filename_in_sequence should" do
|
376
|
+
it "detect the timecode" do
|
377
|
+
tc = Timecode.from_filename_in_sequence("foobar.0000012.jpg", fps = 25)
|
378
|
+
tc.must_equal(Timecode.new(12, 25))
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe 'Timecode with hours larger than 99 should' do
|
383
|
+
it 'print itself without rollover' do
|
384
|
+
tc = Timecode.at(129,34,42,5)
|
385
|
+
tc.to_s_without_rollover.must_equal '129:34:42:05'
|
386
|
+
end
|
343
387
|
|
344
|
-
it
|
388
|
+
it 'print itself with rollover when using to_smpte' do
|
389
|
+
tc = Timecode.at(129,34,42,5)
|
390
|
+
tc.to_s.must_equal '29:34:42:05'
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
describe "Timecode.parse should" do
|
395
|
+
|
396
|
+
it "handle complete SMPTE timecode" do
|
345
397
|
simple_tc = "00:10:34:10"
|
346
|
-
Timecode.parse(simple_tc).to_s.
|
398
|
+
Timecode.parse(simple_tc).to_s.must_equal(simple_tc)
|
347
399
|
end
|
348
|
-
|
349
|
-
it "
|
400
|
+
|
401
|
+
it "handle complete SMPTE timecode with plus for 24 frames per second" do
|
350
402
|
simple_tc = "00:10:34+10"
|
351
403
|
p = Timecode.parse(simple_tc)
|
352
|
-
p.to_s.
|
353
|
-
p.fps.
|
404
|
+
p.to_s.must_equal("00:10:34:10")
|
405
|
+
p.fps.must_equal 24
|
354
406
|
end
|
355
|
-
|
356
|
-
it "
|
407
|
+
|
408
|
+
it "handle timecode with fractional seconds" do
|
357
409
|
tc = Timecode.parse("10:10:10.2", 25)
|
358
|
-
tc.to_s.
|
410
|
+
tc.to_s.must_equal "10:10:10:05"
|
411
|
+
|
412
|
+
tc = Timecode.parse("10:10:10,200", 25)
|
413
|
+
tc.to_s.must_equal "10:10:10:05"
|
359
414
|
end
|
360
|
-
|
361
|
-
it "
|
415
|
+
|
416
|
+
it "handle timecode with fractional seconds (euro style, SRT)" do
|
417
|
+
tc = Timecode.parse("10:10:10,200", 25)
|
418
|
+
tc.to_s.must_equal "10:10:10:05"
|
419
|
+
end
|
420
|
+
|
421
|
+
it "handle timecode with ticks" do
|
362
422
|
tc = Timecode.parse("10:10:10:103", 25)
|
363
|
-
tc.to_s.
|
364
|
-
|
423
|
+
tc.to_s.must_equal "10:10:10:10"
|
424
|
+
|
365
425
|
tc = Timecode.parse("10:10:10:249", 25)
|
366
|
-
tc.to_s.
|
426
|
+
tc.to_s.must_equal "10:10:10:24"
|
367
427
|
end
|
368
428
|
|
369
|
-
it "
|
429
|
+
it "raise when there are more than 249 ticks" do
|
370
430
|
lambda {
|
371
431
|
tc = Timecode.parse("10:10:10:250", 25)
|
372
|
-
}.
|
432
|
+
}.must_raise(Timecode::RangeError)
|
373
433
|
end
|
374
434
|
|
375
|
-
it "
|
435
|
+
it "handle timecode with fractional seconds with spaces at start and end" do
|
376
436
|
tc = Timecode.parse(" 00:00:01.040 ")
|
377
|
-
tc.to_s.
|
437
|
+
tc.to_s.must_equal "00:00:01:01"
|
378
438
|
end
|
379
|
-
|
439
|
+
|
380
440
|
# I am commenting this one out for now, these were present in some odd subtitle file.
|
381
441
|
# What we probably need is a way for Timecode to "extract" timecodes from a chunk of text.
|
382
|
-
# it "
|
442
|
+
# it "handle timecode with fractional seconds with weirdo UTF spaces at start and end" do
|
383
443
|
# tc = Timecode.parse("00:00:01.040")
|
384
|
-
# tc.to_s.
|
444
|
+
# tc.to_s.must_equal "00:00:01:01"
|
385
445
|
# end
|
386
|
-
|
387
|
-
it "
|
388
|
-
Timecode.parse("10").
|
389
|
-
Timecode.parse("210").
|
390
|
-
Timecode.parse("10101010").to_s.
|
446
|
+
|
447
|
+
it "parse a row of numbers as parts of a timecode starting from the right" do
|
448
|
+
Timecode.parse("10").must_equal Timecode.new(10)
|
449
|
+
Timecode.parse("210").must_equal Timecode.new(60)
|
450
|
+
Timecode.parse("10101010").to_s.must_equal "10:10:10:10"
|
391
451
|
end
|
392
|
-
|
393
|
-
it "
|
394
|
-
Timecode.parse("60f").
|
452
|
+
|
453
|
+
it "parse a number with f suffix as frames" do
|
454
|
+
Timecode.parse("60f").must_equal Timecode.new(60)
|
395
455
|
end
|
396
|
-
|
397
|
-
it "
|
398
|
-
Timecode.parse("2s", 25).
|
399
|
-
Timecode.parse("2s", 30).
|
456
|
+
|
457
|
+
it "parse a number with s suffix as seconds" do
|
458
|
+
Timecode.parse("2s", 25).must_equal Timecode.new(50, 25)
|
459
|
+
Timecode.parse("2s", 30).must_equal Timecode.new(60, 30)
|
400
460
|
end
|
401
461
|
|
402
|
-
it "
|
403
|
-
Timecode.parse("3m").
|
462
|
+
it "parse a number with m suffix as minutes" do
|
463
|
+
Timecode.parse("3m").must_equal Timecode.new(25 * 60 * 3)
|
404
464
|
end
|
405
|
-
|
406
|
-
it "
|
407
|
-
Timecode.parse("3h").
|
465
|
+
|
466
|
+
it "parse a number with h suffix as hours" do
|
467
|
+
Timecode.parse("3h").must_equal Timecode.new(25 * 60 * 60 * 3)
|
408
468
|
end
|
409
|
-
|
410
|
-
it "
|
411
|
-
Timecode.parse("1h 4f").to_s.
|
412
|
-
Timecode.parse("4f 1h").to_s.
|
413
|
-
Timecode.parse("29f 1h").to_s.
|
414
|
-
Timecode.parse("29f \n\n\n\n\n\ 1h").to_s.
|
469
|
+
|
470
|
+
it "parse different suffixes as a sum of elements" do
|
471
|
+
Timecode.parse("1h 4f").to_s.must_equal '01:00:00:04'
|
472
|
+
Timecode.parse("4f 1h").to_s.must_equal '01:00:00:04'
|
473
|
+
Timecode.parse("29f 1h").to_s.must_equal '01:00:01:04'
|
474
|
+
Timecode.parse("29f \n\n\n\n\n\ 1h").to_s.must_equal '01:00:01:04'
|
415
475
|
end
|
416
|
-
|
417
|
-
it "
|
418
|
-
Timecode.parse("00000001").to_s.
|
419
|
-
Timecode.parse("1").to_s.
|
420
|
-
Timecode.parse("10").to_s.
|
476
|
+
|
477
|
+
it "parse a number of digits as timecode" do
|
478
|
+
Timecode.parse("00000001").to_s.must_equal "00:00:00:01"
|
479
|
+
Timecode.parse("1").to_s.must_equal "00:00:00:01"
|
480
|
+
Timecode.parse("10").to_s.must_equal "00:00:00:10"
|
421
481
|
end
|
422
|
-
|
423
|
-
it "
|
424
|
-
Timecode.parse("1000000000000000001").to_s.
|
482
|
+
|
483
|
+
it "truncate a large number to the parseable length" do
|
484
|
+
Timecode.parse("1000000000000000001").to_s.must_equal "10:00:00:00"
|
425
485
|
end
|
426
486
|
|
427
|
-
it "
|
428
|
-
Timecode.parse("123456", 57).to_s.
|
487
|
+
it "left-pad a large number to give proper TC" do
|
488
|
+
Timecode.parse("123456", 57).to_s.must_equal "00:12:34:56"
|
429
489
|
end
|
430
|
-
|
431
|
-
it "
|
490
|
+
|
491
|
+
it "parse timecode with fractional second instead of frames" do
|
432
492
|
fraction = "00:00:07.1"
|
433
493
|
tc = Timecode.parse_with_fractional_seconds(fraction, 10)
|
434
|
-
tc.to_s.
|
494
|
+
tc.to_s.must_equal "00:00:07:01"
|
435
495
|
|
436
496
|
fraction = "00:00:07.5"
|
437
497
|
tc = Timecode.parse_with_fractional_seconds(fraction, 10)
|
438
|
-
tc.to_s.
|
439
|
-
|
498
|
+
tc.to_s.must_equal "00:00:07:05"
|
499
|
+
|
440
500
|
fraction = "00:00:07.04"
|
441
501
|
tc = Timecode.parse_with_fractional_seconds(fraction, 12.5)
|
442
|
-
tc.to_s.
|
443
|
-
|
502
|
+
tc.to_s.must_equal "00:00:07:00"
|
503
|
+
|
444
504
|
fraction = "00:00:07.16"
|
445
505
|
tc = Timecode.parse_with_fractional_seconds(fraction, 12.5)
|
446
|
-
tc.to_s.
|
506
|
+
tc.to_s.must_equal "00:00:07:02"
|
447
507
|
end
|
448
|
-
|
449
|
-
it "
|
508
|
+
|
509
|
+
it "raise when trying to parse DF timecode" do
|
450
510
|
df_tc = "00:00:00;01"
|
451
|
-
lambda { Timecode.parse(df_tc)}.
|
511
|
+
lambda { Timecode.parse(df_tc)}.must_raise(Timecode::Error)
|
452
512
|
end
|
453
|
-
|
454
|
-
it "
|
455
|
-
lambda { Timecode.parse("Meaningless nonsense", 25) }.
|
456
|
-
lambda { Timecode.parse("", 25) }.
|
513
|
+
|
514
|
+
it "raise on improper format" do
|
515
|
+
lambda { Timecode.parse("Meaningless nonsense", 25) }.must_raise Timecode::CannotParse
|
516
|
+
lambda { Timecode.parse("", 25) }.must_raise Timecode::CannotParse
|
457
517
|
end
|
458
|
-
|
459
|
-
it "
|
460
|
-
lambda { Timecode.parse(" \n\n ", 25) }.
|
518
|
+
|
519
|
+
it "raise on empty argument" do
|
520
|
+
lambda { Timecode.parse(" \n\n ", 25) }.must_raise Timecode::CannotParse
|
461
521
|
end
|
462
|
-
|
463
|
-
it "
|
464
|
-
Timecode.parse( "09:08:09:08", 25).total.
|
522
|
+
|
523
|
+
it "properly handle 09 and 08 as part of complete TC pattern" do
|
524
|
+
Timecode.parse( "09:08:09:08", 25).total.must_equal 822233
|
465
525
|
end
|
466
526
|
end
|
467
527
|
|
468
528
|
describe "Timecode.soft_parse should" do
|
469
|
-
it "
|
470
|
-
Timecode.soft_parse('200').to_s.
|
529
|
+
it "parse the timecode" do
|
530
|
+
Timecode.soft_parse('200').to_s.must_equal "00:00:02:00"
|
471
531
|
end
|
472
|
-
|
473
|
-
it "
|
532
|
+
|
533
|
+
it "not raise on improper format and return zero TC instead" do
|
474
534
|
lambda do
|
475
535
|
tc = Timecode.soft_parse("Meaningless nonsense", 25)
|
476
|
-
tc.
|
477
|
-
end.
|
536
|
+
tc.must_equal Timecode.new(0)
|
537
|
+
end.must_be_silent
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
describe 'Timecode#to_s' do
|
542
|
+
it 'formats 25 and 25 FPS timecodes uniformly' do
|
543
|
+
at25 = Timecode.parse("1h", 25)
|
544
|
+
at24 = Timecode.parse("1h", 24)
|
545
|
+
at25.to_s.must_equal "01:00:00:00"
|
546
|
+
at24.to_s.must_equal "01:00:00:00"
|
478
547
|
end
|
479
548
|
end
|
480
549
|
|
550
|
+
describe 'Timecode#inspect' do
|
551
|
+
it 'formats 25 and 25 FPS timecodes differently' do
|
552
|
+
at25 = Timecode.parse("1h", 25)
|
553
|
+
at24 = Timecode.parse("1h", 24)
|
554
|
+
at25.inspect.must_equal "#<Timecode:01:00:00:00 (90000F@25.00)>"
|
555
|
+
at24.inspect.must_equal "#<Timecode:01:00:00+00 (86400F@24.00)>"
|
556
|
+
end
|
557
|
+
end
|
481
558
|
|
482
559
|
describe "Timecode with unsigned integer conversions should" do
|
483
|
-
|
484
|
-
it "
|
560
|
+
|
561
|
+
it "parse from a 4x4bits packed 32bit unsigned int" do
|
485
562
|
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
486
|
-
Timecode.from_uint(uint).
|
563
|
+
Timecode.from_uint(uint).must_equal tc
|
487
564
|
end
|
488
|
-
|
489
|
-
it "
|
565
|
+
|
566
|
+
it "properly convert itself back to 4x4 bits 32bit unsigned int" do
|
490
567
|
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
491
|
-
tc.to_uint.
|
568
|
+
tc.to_uint.must_equal uint
|
492
569
|
end
|
493
|
-
end
|
570
|
+
end
|