timecode 2.1.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 80fce38f051f8788789292d5583602e98283db90
4
+ data.tar.gz: 8391c0b89202dc1a5b227eeb81e83dedb1fd0269
5
+ SHA512:
6
+ metadata.gz: f27b6b9eac311f9439f9f3d1a83a47518a91092182f6946f23f571979e2423b913c23898bf280cc7710af5ffb751b959b7af85d17f0d9861a3ab78d1f5d25fc5
7
+ data.tar.gz: e0f302f8835221704ed169731b638f3426d5aadce08f96ed2c696ef4b2c913a2da5b78a0b4d3fd21384b91af09816d5d03ccbaa5b8ce98bb4febaa679a1862a7
data/Gemfile CHANGED
@@ -3,7 +3,13 @@ source 'https://rubygems.org'
3
3
  gem 'approximately', '~> 1.1'
4
4
 
5
5
  group :development do
6
- gem "jeweler", '1.8.4' # Last one without the stupid nokogiri dependency
7
- gem "rake"
6
+ if RUBY_VERSION < "1.9"
7
+ gem "jeweler", '1.8.4' # Last one without the stupid nokogiri dependency
8
+ else
9
+ gem "jeweler"
10
+ end
11
+
12
+ gem "rake", '~> 10'
13
+ gem 'git', '1.2.9.1'
8
14
  gem 'minitest'
9
15
  end
data/README.rdoc CHANGED
@@ -11,12 +11,11 @@ Value class for SMPTE timecode information
11
11
  tc = Timecode.parse("00:00:10:12", fps = 25)
12
12
  tc.total #=> 262
13
13
 
14
+ Drop frame
15
+ tc = Timecode.parse("00:00:10;12", fps = 29.97)
16
+
14
17
  plus_ten = tc + Timecode.parse("10h", fps = 25)
15
18
  plus_ten #=> "10:00:10:12"
16
-
17
- == PROBLEMS:
18
-
19
- Currently there is no support for drop-frame timecode
20
19
 
21
20
  == INSTALL:
22
21
 
data/lib/timecode.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # of frames and the framerate. An additional perk might be to save the dropframeness,
4
4
  # but we avoid that at this point.
5
5
  #
6
- # You can calculate in timecode objects ass well as with conventional integers and floats.
6
+ # You can calculate in timecode objects as well as with conventional integers and floats.
7
7
  # Timecode is immutable and can be used as a value object. Timecode objects are sortable.
8
8
  #
9
9
  # Here's how to use it with ActiveRecord (your column names will be source_tc_frames_total and tape_fps)
@@ -14,8 +14,33 @@
14
14
  require "approximately"
15
15
 
16
16
  class Timecode
17
+
18
+ class ComputationValues
19
+ attr_reader :drop_count, :frames_per_min, :frames_per_10_min, :frames_per_hour, :nd_frames_per_min
20
+
21
+ def initialize(fps, drop_frame)
22
+ rounded_base = fps.round
23
+ if (drop_frame)
24
+ # first 2 frame numbers shall be omitted at the start of each minute,
25
+ # except minutes 0, 10, 20, 30, 40 and 50
26
+ @drop_count = 2
27
+ if (fps > 59 && fps < 60)
28
+ @drop_count *= 2
29
+ end
30
+
31
+ @frames_per_min = rounded_base * 60 - @drop_count
32
+ @frames_per_10_min = @frames_per_min * 10 + @drop_count
33
+ else
34
+ @frames_per_min = rounded_base * 60
35
+ @frames_per_10_min = @frames_per_min * 10
36
+ end
17
37
 
18
- VERSION = '2.1.0'
38
+ @frames_per_hour = @frames_per_10_min * 6
39
+ @nd_frames_per_min = rounded_base * 60
40
+ end
41
+ end
42
+
43
+ VERSION = '2.2.1'
19
44
 
20
45
  include Comparable, Approximately
21
46
 
@@ -47,6 +72,7 @@ class Timecode
47
72
  WITH_SRT_FRACTION = "%02d:%02d:%02d,%02d"
48
73
  WITH_FRACTIONS_OF_SECOND_COMMA = "%02d:%02d:%02d,%03d"
49
74
  WITH_FRAMES = "%02d:%02d:%02d:%02d"
75
+ WITH_FRAMES_DF = "%02d:%02d:%02d;%02d"
50
76
  WITH_FRAMES_24 = "%02d:%02d:%02d+%02d"
51
77
 
52
78
  #:startdoc:
@@ -62,16 +88,20 @@ class Timecode
62
88
 
63
89
  # Gets raised when you try to compute two timecodes with different framerates together
64
90
  class WrongFramerate < ArgumentError; end
91
+
92
+ # Gets raised when you try to compute two timecodes with different drop frame flag together
93
+ class WrongDropFlag < ArgumentError; end
65
94
 
66
- # Initialize a new Timecode object with a certain amount of frames and a framerate
95
+ # Initialize a new Timecode object with a certain amount of frames, a framerate and an optional drop frame flag
67
96
  # will be interpreted as the total number of frames
68
- def initialize(total = 0, fps = DEFAULT_FPS)
97
+ def initialize(total = 0, fps = DEFAULT_FPS, drop_frame = false)
69
98
  raise WrongFramerate, "FPS cannot be zero" if fps.zero?
70
99
  self.class.check_framerate!(fps)
71
100
  # If total is a string, use parse
72
101
  raise RangeError, "Timecode cannot be negative" if total.to_i < 0
73
- # Always cast framerate to float, and num of rames to integer
102
+ # Always cast framerate to float, and num of frames to integer
74
103
  @total, @fps = total.to_i, fps.to_f
104
+ @drop_frame = drop_frame
75
105
  @value = validate!
76
106
  freeze
77
107
  end
@@ -107,8 +137,8 @@ class Timecode
107
137
  end
108
138
 
109
139
  # Use initialize for integers and parsing for strings
110
- def new(from = nil, fps = DEFAULT_FPS)
111
- from.is_a?(String) ? parse(from, fps) : super(from, fps)
140
+ def new(from = nil, fps = DEFAULT_FPS, drop_frame = false)
141
+ from.is_a?(String) ? parse(from, fps) : super(from, fps, drop_frame)
112
142
  end
113
143
 
114
144
  # Parse timecode and return zero if none matched
@@ -131,9 +161,10 @@ class Timecode
131
161
  def parse(spaced_input, with_fps = DEFAULT_FPS)
132
162
  input = spaced_input.strip
133
163
 
134
- # Drop frame goodbye
164
+ # 00:00:00;00
135
165
  if (input =~ DF_TC_RE)
136
- raise Error, "We do not support drop-frame TC"
166
+ atoms_and_fps = input.scan(DF_TC_RE).to_a.flatten.map{|e| e.to_i} + [with_fps, true]
167
+ return at(*atoms_and_fps)
137
168
  # 00:00:00:00
138
169
  elsif (input =~ COMPLETE_TC_RE)
139
170
  atoms_and_fps = input.scan(COMPLETE_TC_RE).to_a.flatten.map{|e| e.to_i} + [with_fps]
@@ -178,10 +209,24 @@ class Timecode
178
209
  end
179
210
 
180
211
  # Initialize a Timecode object at this specfic timecode
181
- def at(hrs, mins, secs, frames, with_fps = DEFAULT_FPS)
212
+ def at(hrs, mins, secs, frames, with_fps = DEFAULT_FPS, drop_frame = false)
182
213
  validate_atoms!(hrs, mins, secs, frames, with_fps)
183
- total = (hrs*(60*60*with_fps) + mins*(60*with_fps) + secs*with_fps + frames).round
184
- new(total, with_fps)
214
+ comp = ComputationValues.new(with_fps, drop_frame)
215
+ if drop_frame && secs == 0 && (mins % 10) && (frames < comp.drop_count)
216
+ frames = comp.drop_count
217
+ end
218
+
219
+ total = hrs * comp.frames_per_hour
220
+ if drop_frame
221
+ total += (mins / 10) * comp.frames_per_10_min
222
+ total += (mins % 10) * comp.frames_per_min
223
+ else
224
+ total += mins * comp.frames_per_min
225
+ end
226
+ rounded_base = with_fps.round
227
+ total += secs * rounded_base
228
+ total += frames
229
+ new(total, with_fps, drop_frame)
185
230
  end
186
231
 
187
232
  # Validate the passed atoms for the concrete framerate
@@ -230,9 +275,9 @@ class Timecode
230
275
 
231
276
  # create a timecode from the number of seconds. This is how current time is supplied by
232
277
  # QuickTime and other systems which have non-frame-based timescales
233
- def from_seconds(seconds_float, the_fps = DEFAULT_FPS)
234
- total_frames = (seconds_float.to_f * the_fps.to_f).to_i
235
- new(total_frames, the_fps)
278
+ def from_seconds(seconds_float, the_fps = DEFAULT_FPS, drop_frame = false)
279
+ total_frames = (seconds_float.to_f * the_fps.to_f).round.to_i
280
+ new(total_frames, the_fps, drop_frame)
236
281
  end
237
282
 
238
283
  # Some systems (like SGIs) and DPX format store timecode as unsigned integer, bit-packed. This method
@@ -270,6 +315,11 @@ class Timecode
270
315
  def total
271
316
  to_f
272
317
  end
318
+
319
+ # get DF
320
+ def drop?
321
+ @drop_frame
322
+ end
273
323
 
274
324
  # get FPS
275
325
  def fps
@@ -316,11 +366,11 @@ class Timecode
316
366
  (@total / @fps)
317
367
  end
318
368
 
319
- # Convert to different framerate based on the total frames. Therefore,
369
+ # Convert to different framerate and drop frame based on the total frames. Therefore,
320
370
  # 1 second of PAL video will convert to 25 frames of NTSC (this
321
371
  # is suitable for PAL to film TC conversions and back).
322
- def convert(new_fps)
323
- self.class.new(@total, new_fps)
372
+ def convert(new_fps, drop_frame = @drop_frame)
373
+ self.class.new(@total, new_fps, drop_frame)
324
374
  end
325
375
 
326
376
  # Get formatted SMPTE timecode. Hour count larger than 99 will roll over to the next
@@ -329,7 +379,7 @@ class Timecode
329
379
  def to_s
330
380
  vs = value_parts
331
381
  vs[0] = vs[0] % 100 # Rollover any values > 99
332
- WITH_FRAMES % vs
382
+ (@drop_frame ? WITH_FRAMES_DF : WITH_FRAMES) % vs
333
383
  end
334
384
 
335
385
  # Get formatted SMPTE timecode. Hours might be larger than 99 and will not roll over
@@ -349,12 +399,16 @@ class Timecode
349
399
 
350
400
  # add number of frames (or another timecode) to this one
351
401
  def +(arg)
352
- if (arg.is_a?(Timecode) && framerate_in_delta(arg.fps, @fps))
353
- self.class.new(@total+arg.total, @fps)
402
+ if (arg.is_a?(Timecode) && framerate_in_delta(arg.fps, @fps) && (arg.drop? == @drop_frame))
403
+ self.class.new(@total + arg.total, @fps, @drop_frame)
354
404
  elsif (arg.is_a?(Timecode))
355
- raise WrongFramerate, "You are calculating timecodes with different framerates"
405
+ if (arg.drop? != @drop_frame)
406
+ raise WrongDropFlag, "You are calculating timecodes with different drop flag values"
407
+ else
408
+ raise WrongFramerate, "You are calculating timecodes with different framerates"
409
+ end
356
410
  else
357
- self.class.new(@total + arg, @fps)
411
+ self.class.new(@total + arg, @fps, @drop_frame)
358
412
  end
359
413
  end
360
414
 
@@ -366,19 +420,23 @@ class Timecode
366
420
 
367
421
  # Subtract a number of frames
368
422
  def -(arg)
369
- if (arg.is_a?(Timecode) && framerate_in_delta(arg.fps, @fps))
370
- self.class.new(@total-arg.total, @fps)
423
+ if (arg.is_a?(Timecode) && framerate_in_delta(arg.fps, @fps) && (arg.drop? == @drop_frame))
424
+ self.class.new(@total-arg.total, @fps, @drop_frame)
371
425
  elsif (arg.is_a?(Timecode))
372
- raise WrongFramerate, "You are calculating timecodes with different framerates"
426
+ if (arg.drop? != @drop_frame)
427
+ raise WrongDropFlag, "You are calculating timecodes with different drop flag values"
428
+ else
429
+ raise WrongFramerate, "You are calculating timecodes with different framerates"
430
+ end
373
431
  else
374
- self.class.new(@total-arg, @fps)
432
+ self.class.new(@total-arg, @fps, @drop_frame)
375
433
  end
376
434
  end
377
435
 
378
436
  # Multiply the timecode by a number
379
437
  def *(arg)
380
438
  raise RangeError, "Timecode multiplier cannot be negative" if (arg < 0)
381
- self.class.new(@total*arg.to_i, @fps)
439
+ self.class.new(@total*arg.to_i, @fps, @drop_frame)
382
440
  end
383
441
 
384
442
  # Get the next frame
@@ -389,7 +447,7 @@ class Timecode
389
447
  # Get the number of times a passed timecode fits into this time span (if performed with Timecode) or
390
448
  # a Timecode that multiplied by arg will give this one
391
449
  def /(arg)
392
- arg.is_a?(Timecode) ? (@total / arg.total) : self.class.new(@total / arg, @fps)
450
+ arg.is_a?(Timecode) ? (@total / arg.total) : self.class.new(@total / arg, @fps, @drop_frame)
393
451
  end
394
452
 
395
453
  # Timecodes can be compared to each other
@@ -428,11 +486,45 @@ class Timecode
428
486
 
429
487
  # Prepare and format the values for TC output
430
488
  def validate!
431
- secs = (@total / @fps).floor
432
- rest_frames = (@total % @fps).floor
433
- hrs = secs.to_i / 3600
434
- mins = (secs.to_i / 60) % 60
435
- secs = secs % 60
489
+ comp = ComputationValues.new(@fps, @drop_frame)
490
+
491
+ frames_dropped = false
492
+ temp_total = @total
493
+ hrs = (temp_total / comp.frames_per_hour).floor
494
+
495
+ temp_total %= comp.frames_per_hour
496
+ mins = (temp_total / comp.frames_per_10_min * 10).floor
497
+
498
+ temp_total %= comp.frames_per_10_min
499
+ if (temp_total >= comp.nd_frames_per_min)
500
+ temp_total -= comp.nd_frames_per_min
501
+ mins += ((temp_total / comp.frames_per_min) + 1).floor
502
+ temp_total %= comp.frames_per_min
503
+ frames_dropped = @drop_frame
504
+ end
505
+
506
+ rounded_base = @fps.round
507
+ secs = (temp_total / rounded_base).floor
508
+ rest_frames = (temp_total % rounded_base).floor
509
+
510
+ if frames_dropped
511
+ rest_frames += comp.drop_count
512
+ if rest_frames >= rounded_base
513
+ rest_frames -= rounded_base
514
+ secs += 1
515
+ if secs >= 60
516
+ secs = 0
517
+ mins += 1
518
+ if mins >= 60
519
+ mins = 0
520
+ hrs += 1
521
+ if hrs >= 999
522
+ hrs = 0
523
+ end
524
+ end
525
+ end
526
+ end
527
+ end
436
528
 
437
529
  self.class.validate_atoms!(hrs, mins, secs, rest_frames, @fps)
438
530
 
@@ -23,6 +23,8 @@ describe "Timecode.new should" do
23
23
  it "always coerce FPS to float" do
24
24
  Timecode.new(10, 24).fps.must_be_kind_of(Float)
25
25
  Timecode.new(10, 25.0).fps.must_be_kind_of(Float)
26
+ Timecode.new(10, 29.97).fps.must_be_kind_of(Float)
27
+ Timecode.new(10, 59.94).fps.must_be_kind_of(Float)
26
28
  end
27
29
 
28
30
  it "create a zero TC with no arguments" do
@@ -36,10 +38,26 @@ describe "Timecode.new should" do
36
38
  it 'calculates correctly (spot check with special values)' do
37
39
  lambda{ Timecode.new 496159, 23.976 }.must_be_silent
38
40
  lambda{ Timecode.new 548999, 23.976 }.must_be_silent
41
+ lambda{ Timecode.new 9662, 29.97 }.must_be_silent
39
42
  end
40
43
 
41
44
  it 'calculates seconds correctly for rational fps' do
42
- Timecode.new(548999, 23.976).seconds.must_equal 37
45
+ Timecode.new(548999, 23.976).seconds.must_equal 14
46
+ Timecode.new(9662, 29.97, true).seconds.must_equal 22
47
+ Timecode.new(1078920, 29.97, true).seconds.must_equal 0
48
+ end
49
+
50
+ it 'calculates timecode correctly for rational fps' do
51
+ atoms_ok = lambda { |tc, h, m, s, f|
52
+ tc.hours.must_equal h
53
+ tc.minutes.must_equal m
54
+ tc.seconds.must_equal s
55
+ tc.frames.must_equal f
56
+ }
57
+
58
+ atoms_ok.call(Timecode.new(9662, 29.97, true), 0, 5, 22, 12)
59
+ atoms_ok.call(Timecode.new(467637, 29.97, true), 4, 20, 3, 15)
60
+ atoms_ok.call(Timecode.new(1078920, 29.97, true), 10, 0, 0, 0)
43
61
  end
44
62
  end
45
63
 
@@ -88,7 +106,7 @@ describe "Timecode.at should" do
88
106
  lambda{ Timecode.at(1,0,60,32, 30) }.must_raise(Timecode::RangeError)
89
107
  end
90
108
 
91
- it "propery accept usable values" do
109
+ it "properly accept usable values" do
92
110
  Timecode.at(20, 20, 10, 5).to_s.must_equal "20:20:10:05"
93
111
  end
94
112
  end
@@ -194,6 +212,13 @@ describe "Timecode.from_seconds should" do
194
212
  Timecode.add_custom_framerate!(float_fps)
195
213
  lambda{ Timecode.from_seconds(float_secs, float_fps) }.must_be_silent
196
214
  end
215
+
216
+ it "properly process a DF framerate" do
217
+ Timecode.from_seconds(322.4004, 29.97, true).to_i.must_equal 9662
218
+ Timecode.from_seconds(600.0, 29.97, true).to_i.must_equal 17982
219
+ Timecode.from_seconds(15603.5005, 29.97, true).to_i.must_equal 467637
220
+ Timecode.from_seconds(36000.0, 29.97, true).to_i.must_equal 1078920
221
+ end
197
222
  end
198
223
 
199
224
  describe "Timecode#to_seconds should" do
@@ -204,14 +229,19 @@ describe "Timecode#to_seconds should" do
204
229
  it "return the value in seconds" do
205
230
  fps = 24
206
231
  secs = 126.3
207
- Timecode.new(fps * secs, fps).to_seconds.must_be_within_delta 126.3, 0.1
232
+ Timecode.new(fps * secs, fps).to_seconds.must_be_within_delta 126.3, 0.1
208
233
  end
209
234
 
210
235
  it "properly roundtrip a value via Timecode.from_seconds" do
211
236
  secs_in = 19.76
212
- from_secs = Timecode.from_seconds(19.76, 25.0)
237
+ from_secs = Timecode.from_seconds(secs_in, 25.0)
213
238
  from_secs.total.must_equal 494
214
239
  from_secs.to_seconds.must_be_within_delta secs_in, 0.001
240
+
241
+ secs_in = 15603.50
242
+ from_secs = Timecode.from_seconds(secs_in, 29.97, true)
243
+ from_secs.total.must_equal 467637
244
+ from_secs.to_seconds.must_be_within_delta secs_in, 0.005
215
245
  end
216
246
  end
217
247
 
@@ -224,6 +254,11 @@ describe "An existing Timecode on inspection should" do
224
254
  it "properly print itself" do
225
255
  Timecode.new(5, 25).to_s.must_equal "00:00:00:05"
226
256
  end
257
+
258
+ it "properly print itself with DF" do
259
+ Timecode.new(9662, 29.97, true).to_s.must_equal "00:05:22;12"
260
+ Timecode.new(9662, 29.97, false).to_s.must_equal "00:05:22:02"
261
+ end
227
262
  end
228
263
 
229
264
  describe "An existing Timecode compared by adjacency" do
@@ -231,19 +266,42 @@ describe "An existing Timecode compared by adjacency" do
231
266
  Timecode.new(10).must_be :adjacent_to?, Timecode.new(9)
232
267
  Timecode.new(10).wont_be :adjacent_to?, Timecode.new(8)
233
268
  end
269
+
270
+ it "properly detect an adjacent DF timecode to the left" do
271
+ Timecode.new(1800, 29.97, true).must_be :adjacent_to?, Timecode.new(1799, 29.97, true)
272
+ Timecode.new(1800, 29.97, true).wont_be :adjacent_to?, Timecode.new(1798, 29.97, true)
273
+ end
234
274
 
235
275
  it "properly detect an adjacent timecode to the right" do
236
276
  Timecode.new(10).must_be :adjacent_to?, Timecode.new(11)
237
277
  Timecode.new(10).wont_be :adjacent_to?, Timecode.new(12)
238
278
  end
239
-
279
+
280
+ it "properly detect an adjacent DF timecode to the right" do
281
+ Timecode.new(1799, 29.97, true).must_be :adjacent_to?, Timecode.new(1800, 29.97, true)
282
+ Timecode.new(1799, 29.97, true).wont_be :adjacent_to?, Timecode.new(1801, 29.97, true)
283
+ end
240
284
  end
241
285
 
242
286
  describe "A Timecode on conversion should" do
243
287
  it "copy itself with a different framerate" do
244
- tc = Timecode.new(40,25)
288
+ tc = Timecode.new(1800, 25)
245
289
  at24 = tc.convert(24)
246
- at24.total.must_equal 40
290
+ at24.total.must_equal 1800
291
+ at29 = tc.convert(29.97)
292
+ at29.total.must_equal 1800
293
+ at29.to_s.must_equal "00:01:00:00"
294
+ at29DF = tc.convert(29.97, true)
295
+ at29DF.total.must_equal 1800
296
+ at29DF.to_s.must_equal "00:01:00;02"
297
+
298
+ tc1 = Timecode.new(1800, 23.976, true)
299
+ at29 = tc1.convert(29.97)
300
+ at29.total.must_equal 1800
301
+ at29.to_s.must_equal "00:01:00;02"
302
+ at29ND = tc1.convert(29.97, false)
303
+ at29ND.total.must_equal 1800
304
+ at29ND.to_s.must_equal "00:01:00:00"
247
305
  end
248
306
  end
249
307
 
@@ -251,6 +309,7 @@ describe "An existing Timecode used within ranges should" do
251
309
  it "properly provide successive value that is one frame up" do
252
310
  Timecode.new(10).succ.total.must_equal 11
253
311
  Timecode.new(22, 45).succ.must_equal Timecode.new(23, 45)
312
+ Timecode.new(1799, 29.97, true).succ.must_equal Timecode.new(1800, 29.97, true)
254
313
  end
255
314
 
256
315
  it "work as a range member" do
@@ -261,14 +320,6 @@ describe "An existing Timecode used within ranges should" do
261
320
 
262
321
  end
263
322
 
264
- describe "A Timecode on conversion should" do
265
- it "copy itself with a different framerate" do
266
- tc = Timecode.new(40,25)
267
- at24 = tc.convert(24)
268
- at24.total.must_equal 40
269
- end
270
- end
271
-
272
323
  describe "A Timecode on calculations should" do
273
324
 
274
325
  it "support addition" do
@@ -279,10 +330,23 @@ describe "A Timecode on calculations should" do
279
330
  it "should raise on addition if framerates do not match" do
280
331
  lambda{ Timecode.new(10, 25) + Timecode.new(10, 30) }.must_raise(Timecode::WrongFramerate)
281
332
  end
333
+
334
+ it "should raise on addition if drop flag mismatches" do
335
+ lambda{ Timecode.new(10, 29.97, true) + Timecode.new(10, 29.97) }.must_raise(Timecode::WrongDropFlag)
336
+ end
282
337
 
283
338
  it "when added with an integer instead calculate on total" do
284
339
  (Timecode.new(5) + 5).must_equal(Timecode.new(10))
285
340
  end
341
+
342
+ it "when adding DF flag is preserved" do
343
+ a, b = Timecode.new(24, 29.97, true), Timecode.new(22, 29.97, true)
344
+ c, d = Timecode.new(24, 29.97), Timecode.new(22, 29.97)
345
+ tcsum = a + b
346
+ tcsum.must_equal Timecode.new(24 + 22, 29.97, true)
347
+ tcsum.drop?.must_equal true
348
+ (c + d).drop?.must_equal false
349
+ end
286
350
 
287
351
  it "support subtraction" do
288
352
  a, b = Timecode.new(10), Timecode.new(4)
@@ -296,6 +360,19 @@ describe "A Timecode on calculations should" do
296
360
  it "raise when subtracting a Timecode with a different framerate" do
297
361
  lambda { Timecode.new(10, 25) - Timecode.new(10, 30) }.must_raise(Timecode::WrongFramerate)
298
362
  end
363
+
364
+ it "raise when subtracting a Timecode with a different drop frame flag" do
365
+ lambda { Timecode.new(10, 29.97, true) - Timecode.new(10, 29.97) }.must_raise(Timecode::WrongDropFlag)
366
+ end
367
+
368
+ it "when subtracting DF flag is preserved" do
369
+ a, b = Timecode.new(24, 29.97, true), Timecode.new(22, 29.97, true)
370
+ c, d = Timecode.new(24, 29.97), Timecode.new(22, 29.97)
371
+ tcsub = a - b
372
+ tcsub.must_equal Timecode.new(24 - 22, 29.97, true)
373
+ tcsub.drop?.must_equal true
374
+ (c - d).drop?.must_equal false
375
+ end
299
376
 
300
377
  it "support multiplication" do
301
378
  (Timecode.new(10) * 10).must_equal(Timecode.new(100))
@@ -304,6 +381,10 @@ describe "A Timecode on calculations should" do
304
381
  it "raise when the resultig Timecode is negative" do
305
382
  lambda { Timecode.new(10) * -200 }.must_raise(Timecode::RangeError)
306
383
  end
384
+
385
+ it "preserves drop frame flag when multiplying" do
386
+ (Timecode.new(10, 29.97, true) * 10).drop?.must_equal true
387
+ end
307
388
 
308
389
  it "return a Timecode when divided by an Integer" do
309
390
  v = Timecode.new(200) / 20
@@ -316,6 +397,10 @@ describe "A Timecode on calculations should" do
316
397
  v.must_be_kind_of(Numeric)
317
398
  v.must_equal 10
318
399
  end
400
+
401
+ it "preserves drop frame flag when dividing" do
402
+ (Timecode.new(200, 29.97, true) / 20).drop?.must_equal true
403
+ end
319
404
  end
320
405
 
321
406
  describe "A Timecode used with fractional number of seconds" do
@@ -339,8 +424,8 @@ describe "A Timecode used with fractional number of seconds" do
339
424
  tc.to_s.must_equal "00:00:07:05"
340
425
 
341
426
  fraction = 7.16
342
- tc = Timecode.from_seconds(fraction, 12.5)
343
- tc.to_s.must_equal "00:00:07:01"
427
+ tc = Timecode.from_seconds(fraction, 25)
428
+ tc.to_s.must_equal "00:00:07:04"
344
429
  end
345
430
 
346
431
  end
@@ -397,6 +482,13 @@ describe "Timecode.parse should" do
397
482
  simple_tc = "00:10:34:10"
398
483
  Timecode.parse(simple_tc).to_s.must_equal(simple_tc)
399
484
  end
485
+
486
+ it "handle complete SMPTE timecode with drop frame flag" do
487
+ simple_tc = "00:10:34;10"
488
+ tc = Timecode.parse(simple_tc, 29.97)
489
+ tc.to_s.must_equal(simple_tc)
490
+ tc.drop?.must_equal true
491
+ end
400
492
 
401
493
  it "handle complete SMPTE timecode with plus for 24 frames per second" do
402
494
  simple_tc = "00:10:34+10"
@@ -506,9 +598,9 @@ describe "Timecode.parse should" do
506
598
  tc.to_s.must_equal "00:00:07:02"
507
599
  end
508
600
 
509
- it "raise when trying to parse DF timecode" do
601
+ it "reports DF timecode" do
510
602
  df_tc = "00:00:00;01"
511
- lambda { Timecode.parse(df_tc)}.must_raise(Timecode::Error)
603
+ Timecode.parse(df_tc, 29.97).drop?.must_equal true
512
604
  end
513
605
 
514
606
  it "raise on improper format" do
data/timecode.gemspec CHANGED
@@ -2,15 +2,17 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
+ # stub: timecode 2.2.1 ruby lib
5
6
 
6
7
  Gem::Specification.new do |s|
7
- s.name = %q{timecode}
8
- s.version = "2.1.0"
8
+ s.name = "timecode"
9
+ s.version = "2.2.1"
9
10
 
10
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
11
13
  s.authors = ["Julik Tarkhanov"]
12
- s.date = %q{2015-10-21}
13
- s.email = %q{me@julik.nl}
14
+ s.date = "2016-06-06"
15
+ s.email = "me@julik.nl"
14
16
  s.extra_rdoc_files = [
15
17
  "README.rdoc"
16
18
  ]
@@ -23,30 +25,32 @@ Gem::Specification.new do |s|
23
25
  "test/test_timecode.rb",
24
26
  "timecode.gemspec"
25
27
  ]
26
- s.homepage = %q{http://guerilla-di.org/timecode}
28
+ s.homepage = "http://guerilla-di.org/timecode"
27
29
  s.licenses = ["MIT"]
28
- s.require_paths = ["lib"]
29
- s.rubygems_version = %q{1.6.2}
30
- s.summary = %q{Timecode value class}
30
+ s.rubygems_version = "2.2.2"
31
+ s.summary = "Timecode value class"
31
32
 
32
33
  if s.respond_to? :specification_version then
33
- s.specification_version = 3
34
+ s.specification_version = 4
34
35
 
35
36
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
36
37
  s.add_runtime_dependency(%q<approximately>, ["~> 1.1"])
37
- s.add_development_dependency(%q<jeweler>, ["= 1.8.4"])
38
- s.add_development_dependency(%q<rake>, [">= 0"])
38
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
39
+ s.add_development_dependency(%q<rake>, ["~> 10"])
40
+ s.add_development_dependency(%q<git>, ["= 1.2.9.1"])
39
41
  s.add_development_dependency(%q<minitest>, [">= 0"])
40
42
  else
41
43
  s.add_dependency(%q<approximately>, ["~> 1.1"])
42
- s.add_dependency(%q<jeweler>, ["= 1.8.4"])
43
- s.add_dependency(%q<rake>, [">= 0"])
44
+ s.add_dependency(%q<jeweler>, [">= 0"])
45
+ s.add_dependency(%q<rake>, ["~> 10"])
46
+ s.add_dependency(%q<git>, ["= 1.2.9.1"])
44
47
  s.add_dependency(%q<minitest>, [">= 0"])
45
48
  end
46
49
  else
47
50
  s.add_dependency(%q<approximately>, ["~> 1.1"])
48
- s.add_dependency(%q<jeweler>, ["= 1.8.4"])
49
- s.add_dependency(%q<rake>, [">= 0"])
51
+ s.add_dependency(%q<jeweler>, [">= 0"])
52
+ s.add_dependency(%q<rake>, ["~> 10"])
53
+ s.add_dependency(%q<git>, ["= 1.2.9.1"])
50
54
  s.add_dependency(%q<minitest>, [">= 0"])
51
55
  end
52
56
  end
metadata CHANGED
@@ -1,91 +1,92 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: timecode
3
- version: !ruby/object:Gem::Version
4
- hash: 11
5
- prerelease:
6
- segments:
7
- - 2
8
- - 1
9
- - 0
10
- version: 2.1.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.2.1
11
5
  platform: ruby
12
- authors:
6
+ authors:
13
7
  - Julik Tarkhanov
14
8
  autorequire:
15
9
  bindir: bin
16
10
  cert_chain: []
17
-
18
- date: 2015-10-21 00:00:00 +02:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- requirement: &id001 !ruby/object:Gem::Requirement
23
- none: false
24
- requirements:
25
- - - ~>
26
- - !ruby/object:Gem::Version
27
- hash: 13
28
- segments:
29
- - 1
30
- - 1
31
- version: "1.1"
32
- type: :runtime
33
- version_requirements: *id001
34
- prerelease: false
11
+ date: 2016-06-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
35
14
  name: approximately
36
- - !ruby/object:Gem::Dependency
37
- requirement: &id002 !ruby/object:Gem::Requirement
38
- none: false
39
- requirements:
40
- - - "="
41
- - !ruby/object:Gem::Version
42
- hash: 63
43
- segments:
44
- - 1
45
- - 8
46
- - 4
47
- version: 1.8.4
48
- type: :development
49
- version_requirements: *id002
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
50
21
  prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
51
28
  name: jeweler
52
- - !ruby/object:Gem::Dependency
53
- requirement: &id003 !ruby/object:Gem::Requirement
54
- none: false
55
- requirements:
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
56
31
  - - ">="
57
- - !ruby/object:Gem::Version
58
- hash: 3
59
- segments:
60
- - 0
61
- version: "0"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
62
34
  type: :development
63
- version_requirements: *id003
64
35
  prerelease: false
65
- name: rake
66
- - !ruby/object:Gem::Dependency
67
- requirement: &id004 !ruby/object:Gem::Requirement
68
- none: false
69
- requirements:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
70
38
  - - ">="
71
- - !ruby/object:Gem::Version
72
- hash: 3
73
- segments:
74
- - 0
75
- version: "0"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10'
76
48
  type: :development
77
- version_requirements: *id004
78
49
  prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: git
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.2.9.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.2.9.1
69
+ - !ruby/object:Gem::Dependency
79
70
  name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
80
83
  description:
81
84
  email: me@julik.nl
82
85
  executables: []
83
-
84
86
  extensions: []
85
-
86
- extra_rdoc_files:
87
+ extra_rdoc_files:
87
88
  - README.rdoc
88
- files:
89
+ files:
89
90
  - Gemfile
90
91
  - History.txt
91
92
  - README.rdoc
@@ -93,39 +94,28 @@ files:
93
94
  - lib/timecode.rb
94
95
  - test/test_timecode.rb
95
96
  - timecode.gemspec
96
- has_rdoc: true
97
97
  homepage: http://guerilla-di.org/timecode
98
- licenses:
98
+ licenses:
99
99
  - MIT
100
+ metadata: {}
100
101
  post_install_message:
101
102
  rdoc_options: []
102
-
103
- require_paths:
103
+ require_paths:
104
104
  - lib
105
- required_ruby_version: !ruby/object:Gem::Requirement
106
- none: false
107
- requirements:
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
108
107
  - - ">="
109
- - !ruby/object:Gem::Version
110
- hash: 3
111
- segments:
112
- - 0
113
- version: "0"
114
- required_rubygems_version: !ruby/object:Gem::Requirement
115
- none: false
116
- requirements:
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
117
112
  - - ">="
118
- - !ruby/object:Gem::Version
119
- hash: 3
120
- segments:
121
- - 0
122
- version: "0"
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
123
115
  requirements: []
124
-
125
116
  rubyforge_project:
126
- rubygems_version: 1.6.2
117
+ rubygems_version: 2.2.2
127
118
  signing_key:
128
- specification_version: 3
119
+ specification_version: 4
129
120
  summary: Timecode value class
130
121
  test_files: []
131
-