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 +7 -0
- data/Gemfile +8 -2
- data/README.rdoc +3 -4
- data/lib/timecode.rb +126 -34
- data/test/test_timecode.rb +111 -19
- data/timecode.gemspec +19 -15
- metadata +81 -91
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
|
-
|
7
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
-
#
|
164
|
+
# 00:00:00;00
|
135
165
|
if (input =~ DF_TC_RE)
|
136
|
-
|
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
|
-
|
184
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
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
|
|
data/test/test_timecode.rb
CHANGED
@@ -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
|
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 "
|
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(
|
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(
|
288
|
+
tc = Timecode.new(1800, 25)
|
245
289
|
at24 = tc.convert(24)
|
246
|
-
at24.total.must_equal
|
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,
|
343
|
-
tc.to_s.must_equal "00:00:07:
|
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 "
|
601
|
+
it "reports DF timecode" do
|
510
602
|
df_tc = "00:00:00;01"
|
511
|
-
|
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 =
|
8
|
-
s.version = "2.1
|
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 =
|
13
|
-
s.email =
|
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 =
|
28
|
+
s.homepage = "http://guerilla-di.org/timecode"
|
27
29
|
s.licenses = ["MIT"]
|
28
|
-
s.
|
29
|
-
s.
|
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 =
|
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>, ["
|
38
|
-
s.add_development_dependency(%q<rake>, ["
|
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>, ["
|
43
|
-
s.add_dependency(%q<rake>, ["
|
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>, ["
|
49
|
-
s.add_dependency(%q<rake>, ["
|
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
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
53
|
-
|
54
|
-
none: false
|
55
|
-
requirements:
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
56
31
|
- - ">="
|
57
|
-
- !ruby/object:Gem::Version
|
58
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
107
|
-
requirements:
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
108
107
|
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
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:
|
117
|
+
rubygems_version: 2.2.2
|
127
118
|
signing_key:
|
128
|
-
specification_version:
|
119
|
+
specification_version: 4
|
129
120
|
summary: Timecode value class
|
130
121
|
test_files: []
|
131
|
-
|