timecode 2.1.0 → 2.2.1

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.
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
-