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