timecode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +6 -0
- data/Manifest.txt +6 -0
- data/README.txt +48 -0
- data/Rakefile +9 -0
- data/lib/timecode.rb +327 -0
- data/test/test_timecode.rb +160 -0
- metadata +71 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= timecode
|
2
|
+
|
3
|
+
* http://projects.juli.nl/timecode
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Value class for SMPTE timecode information
|
8
|
+
|
9
|
+
== SYNOPSIS:
|
10
|
+
|
11
|
+
tc = Timecode.parse("00:00:10:12", fps = 25)
|
12
|
+
tc.total #=> 262
|
13
|
+
|
14
|
+
plus_ten = tc + Timecode.parse("10h", fps = 25)
|
15
|
+
plus_ten #=> "10:00:10:12"
|
16
|
+
|
17
|
+
== PROBLEMS:
|
18
|
+
|
19
|
+
Currently there is no support for drop-frame timecode
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* sudo gem install timecode
|
24
|
+
|
25
|
+
== LICENSE:
|
26
|
+
|
27
|
+
(The MIT License)
|
28
|
+
|
29
|
+
Copyright (c) 2008 Julik Tarkhanov
|
30
|
+
|
31
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
32
|
+
a copy of this software and associated documentation files (the
|
33
|
+
'Software'), to deal in the Software without restriction, including
|
34
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
35
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
36
|
+
permit persons to whom the Software is furnished to do so, subject to
|
37
|
+
the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be
|
40
|
+
included in all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
43
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
44
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
45
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
46
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
47
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
48
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/lib/timecode.rb
ADDED
@@ -0,0 +1,327 @@
|
|
1
|
+
# Timecode is a convenience object for calculating SMPTE timecode natively. It is used in
|
2
|
+
# various StoryTool models and templates, offers string output and is immutable.
|
3
|
+
#
|
4
|
+
# The promise is that you only have to store two values to know the timecode - the amount
|
5
|
+
# of frames and the framerate. An additional perk might be to save the dropframeness,
|
6
|
+
# but we avoid that at this point.
|
7
|
+
#
|
8
|
+
# You can calculate in timecode objects ass well as with conventional integers and floats.
|
9
|
+
# Timecode is immutable and can be used as a value object. Timecode objects are sortable.
|
10
|
+
#
|
11
|
+
# Here's how to use it with ActiveRecord (your column names will be source_tc_frames_total and tape_fps)
|
12
|
+
#
|
13
|
+
# composed_of :source_tc, :class_name => 'Timecode',
|
14
|
+
# :mapping => [%w(source_tc_frames total), %w(tape_fps fps)]
|
15
|
+
|
16
|
+
class Timecode
|
17
|
+
VERSION = '0.1.0'
|
18
|
+
|
19
|
+
include Comparable
|
20
|
+
DEFAULT_FPS = 25
|
21
|
+
COMPLETE_TC_RE = /^(\d{1,2}):(\d{1,2}):(\d{1,2}):(\d{1,2})$/
|
22
|
+
|
23
|
+
# All Timecode lib errors inherit from this
|
24
|
+
class Error < RuntimeError; end
|
25
|
+
|
26
|
+
# Will be raised for functions that are not supported
|
27
|
+
class TimecodeLibError < Error; end
|
28
|
+
|
29
|
+
# Gets raised if timecode is out of range (like 100 hours long)
|
30
|
+
class RangeError < Error; end
|
31
|
+
|
32
|
+
# Self-explanatory
|
33
|
+
class NonPositiveFps < RangeError; end
|
34
|
+
|
35
|
+
# Gets raised when float frame count is passed
|
36
|
+
class FrameIsWhole < RangeError; end
|
37
|
+
|
38
|
+
# Gets raised when you divide by zero
|
39
|
+
class TcIsZero < ZeroDivisionError; end
|
40
|
+
|
41
|
+
# Gets raised when a timecode cannot be parsed
|
42
|
+
class CannotParse < Error; end
|
43
|
+
|
44
|
+
# Gets raised when you try to compute two timecodes with different framerates together
|
45
|
+
class WrongFramerate < ArgumentError; end
|
46
|
+
|
47
|
+
# Well well...
|
48
|
+
class MethodRequiresTimecode < ArgumentError; end
|
49
|
+
|
50
|
+
# Initialize a new Timecode. If a string is passed, it will be parsed, an integer
|
51
|
+
# will be interpreted as the total number of frames
|
52
|
+
def self.new(total_or_string = 0, fps = DEFAULT_FPS)
|
53
|
+
if total_or_string.nil?
|
54
|
+
new(0, fps)
|
55
|
+
elsif total_or_string.is_a?(String)
|
56
|
+
parse(total_or_string, fps)
|
57
|
+
else
|
58
|
+
super(total_or_string, fps)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(total = 0, fps = DEFAULT_FPS) # :nodoc:
|
63
|
+
if total.is_a?(Float)
|
64
|
+
raise FrameIsWhole, "the number of frames cannot be partial (Integer value needed)"
|
65
|
+
end
|
66
|
+
|
67
|
+
raise RangeError, "Timecode cannot be negative" if total.to_f < 0
|
68
|
+
raise WrongFramerate, "FPS cannot be zero" if fps.zero?
|
69
|
+
@total, @fps = total, fps
|
70
|
+
@value = validate!
|
71
|
+
freeze
|
72
|
+
end
|
73
|
+
|
74
|
+
def inspect # :nodoc:
|
75
|
+
super.gsub(/@fps/, self.to_s + ' @fps').gsub(/ @value=\[(.+)\],/, '')
|
76
|
+
end
|
77
|
+
|
78
|
+
TIME_FIELDS = 7 # :nodoc:
|
79
|
+
|
80
|
+
class << self
|
81
|
+
|
82
|
+
# Parse timecode and return zero if none matched
|
83
|
+
def soft_parse(input, with_fps = DEFAULT_FPS)
|
84
|
+
parse(input) rescue new(0, with_fps)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Parse timecode entered by the user. Will raise if the string cannot be parsed.
|
88
|
+
def parse(input, with_fps = DEFAULT_FPS)
|
89
|
+
# Drop frame goodbye
|
90
|
+
raise TimecodeLibError, "We do not support drop frame" if (input =~ /\;/)
|
91
|
+
|
92
|
+
hrs, mins, secs, frames = 0,0,0,0
|
93
|
+
atoms = []
|
94
|
+
|
95
|
+
# 10h 20m 10s 1f
|
96
|
+
if input =~ /\s/
|
97
|
+
return input.split.map{|part| parse(part, with_fps) }.inject { |sum, p| sum + p.total }
|
98
|
+
# 10s
|
99
|
+
elsif input =~ /^(\d+)s$/
|
100
|
+
return new(input.to_i * with_fps, with_fps)
|
101
|
+
# 10h
|
102
|
+
elsif input =~ /^(\d+)h$/i
|
103
|
+
return new(input.to_i * 60 * 60 * with_fps, with_fps)
|
104
|
+
# 20m
|
105
|
+
elsif input =~ /^(\d+)m$/i
|
106
|
+
return new(input.to_i * 60 * with_fps, with_fps)
|
107
|
+
# 60f - 60 frames, or 2 seconds and 10 frames
|
108
|
+
elsif input =~ /^(\d+)f$/i
|
109
|
+
return new(input.to_i, with_fps)
|
110
|
+
# A bunch of integers
|
111
|
+
elsif (input =~ /^(\d+)$/)
|
112
|
+
ints = input.split(//)
|
113
|
+
atoms.unshift [ints.pop, ints.pop].reverse.join.to_i
|
114
|
+
atoms.unshift [ints.pop, ints.pop].reverse.join.to_i
|
115
|
+
atoms.unshift [ints.pop, ints.pop].reverse.join.to_i
|
116
|
+
atoms.unshift [ints.pop, ints.pop].reverse.join.to_i
|
117
|
+
elsif (input =~ COMPLETE_TC_RE)
|
118
|
+
atoms = input.scan(COMPLETE_TC_RE).to_a.flatten
|
119
|
+
else
|
120
|
+
raise CannotParse, "Cannot parse #{input} into timecode, no match"
|
121
|
+
end
|
122
|
+
|
123
|
+
if atoms.any?
|
124
|
+
hrs, mins, secs, frames = atoms.map{|e| e.to_i}
|
125
|
+
else
|
126
|
+
raise CannotParse, "Cannot parse #{input} into timecode, atoms were empty"
|
127
|
+
end
|
128
|
+
|
129
|
+
at(hrs, mins, secs, frames, with_fps)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Initialize a Timecode object at this specfic timecode
|
133
|
+
def at(hrs, mins, secs, frames, with_fps = DEFAULT_FPS)
|
134
|
+
case true
|
135
|
+
when hrs > 99
|
136
|
+
raise RangeError, "There can be no more than 99 hours, got #{hrs}"
|
137
|
+
when mins > 59
|
138
|
+
raise RangeError, "There can be no more than 59 minutes, got #{mins}"
|
139
|
+
when secs > 59
|
140
|
+
raise RangeError, "There can be no more than 59 seconds, got #{secs}"
|
141
|
+
when frames > (with_fps -1)
|
142
|
+
raise RangeError, "There can be no more than #{with_fps -1} frames @#{with_fps}, got #{frames}"
|
143
|
+
end
|
144
|
+
|
145
|
+
total = (hrs*(60*60*with_fps) + mins*(60*with_fps) + secs*with_fps + frames).round
|
146
|
+
new(total, with_fps)
|
147
|
+
end
|
148
|
+
|
149
|
+
def parse_with_fractional_seconds(tc_with_fractions_of_second, fps = DEFAULT_FPS)
|
150
|
+
fraction_expr = /\.(\d+)$/
|
151
|
+
fraction_part = ('.' + tc_with_fractions_of_second.scan(fraction_expr)[0][0]).to_f
|
152
|
+
|
153
|
+
seconds_per_frame = 1.0 / fps.to_f
|
154
|
+
frame_idx = (fraction_part / seconds_per_frame).floor
|
155
|
+
|
156
|
+
tc_with_frameno = tc_with_fractions_of_second.gsub(fraction_expr, ":#{frame_idx}")
|
157
|
+
|
158
|
+
parse(tc_with_frameno, fps)
|
159
|
+
end
|
160
|
+
|
161
|
+
# create a timecode from seconds. Seconds can be float (this is how current time is supplied by
|
162
|
+
# QuickTime and other systems which have non-frame-based timescales)
|
163
|
+
def from_seconds(seconds_float, the_fps = DEFAULT_FPS)
|
164
|
+
total_frames = (seconds_float.to_f * the_fps.to_f).ceil
|
165
|
+
new(total_frames, the_fps)
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Some systems (like SGIs) and DPX format store timecode as unsigned integer, bit-packed
|
170
|
+
def from_uint(uint)
|
171
|
+
shift = 4 * TIME_FIELDS
|
172
|
+
tc_elements = (0..TIME_FIELDS).map do
|
173
|
+
part = ((uint >> shift) & 0x0F)
|
174
|
+
shift -= 4
|
175
|
+
part
|
176
|
+
end.join.scan(/(\d{2})/).flatten.map{|e| e.to_i}
|
177
|
+
at(*tc_elements)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# is the timecode at 00:00:00:00
|
182
|
+
def zero?
|
183
|
+
@total.zero?
|
184
|
+
end
|
185
|
+
|
186
|
+
# get total frame count
|
187
|
+
def total
|
188
|
+
to_f
|
189
|
+
end
|
190
|
+
|
191
|
+
#get FPS
|
192
|
+
def fps
|
193
|
+
@fps
|
194
|
+
end
|
195
|
+
|
196
|
+
# get the number of frames
|
197
|
+
def frames
|
198
|
+
value_parts[3]
|
199
|
+
end
|
200
|
+
|
201
|
+
# get the number of seconds
|
202
|
+
def seconds
|
203
|
+
value_parts[2]
|
204
|
+
end
|
205
|
+
|
206
|
+
# get the number of minutes
|
207
|
+
def minutes
|
208
|
+
value_parts[1]
|
209
|
+
end
|
210
|
+
|
211
|
+
# get the number of hours
|
212
|
+
def hours
|
213
|
+
value_parts[0]
|
214
|
+
end
|
215
|
+
|
216
|
+
# get frame interval in fractions of a second
|
217
|
+
def frame_interval
|
218
|
+
1.0/@fps
|
219
|
+
end
|
220
|
+
|
221
|
+
# get the timecode as bit-packed unsigned int (suitable for DPX and SGI)
|
222
|
+
def to_uint
|
223
|
+
elements = (("%02d" * 4) % [hours,minutes,seconds,frames]).split(//).map{|e| e.to_i }
|
224
|
+
uint = 0
|
225
|
+
elements.each do | el |
|
226
|
+
uint = ((uint >> el))
|
227
|
+
end
|
228
|
+
uint
|
229
|
+
end
|
230
|
+
|
231
|
+
# Convert to different framerate based on the total frames. Therefore,
|
232
|
+
# 1 second of PAL video will convert to 25 frames of NTSC (this
|
233
|
+
# is suitable for PAL to film TC conversions and back).
|
234
|
+
# It does not account for pulldown or anything in that sense, because
|
235
|
+
# then you need to think about cadences and such
|
236
|
+
def convert(new_fps)
|
237
|
+
raise NonPositiveFps, "FPS cannot be less than 0" if new_fps < 1
|
238
|
+
self.class.new((total/fps)*new_fps, new_fps)
|
239
|
+
end
|
240
|
+
|
241
|
+
# get formatted SMPTE timecode
|
242
|
+
def to_s
|
243
|
+
"%02d:%02d:%02d:%02d" % value_parts
|
244
|
+
end
|
245
|
+
|
246
|
+
# get total frames as float
|
247
|
+
def to_f
|
248
|
+
@total
|
249
|
+
end
|
250
|
+
|
251
|
+
# get total frames as integer
|
252
|
+
def to_i
|
253
|
+
@total
|
254
|
+
end
|
255
|
+
|
256
|
+
# add number of frames (or another timecode) to this one
|
257
|
+
def +(arg)
|
258
|
+
if (arg.is_a?(Timecode) && arg.fps == @fps)
|
259
|
+
Timecode.new(@total+arg.total, @fps)
|
260
|
+
elsif (arg.is_a?(Timecode))
|
261
|
+
raise WrongFramerate, "You are calculating timecodes with different framerates"
|
262
|
+
else
|
263
|
+
Timecode.new(@total + arg, @fps)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Subtract a number of frames
|
268
|
+
def -(arg)
|
269
|
+
if (arg.is_a?(Timecode) && arg.fps == @fps)
|
270
|
+
Timecode.new(@total-arg.total, @fps)
|
271
|
+
elsif (arg.is_a?(Timecode))
|
272
|
+
raise WrongFramerate, "You are calculating timecodes with different framerates"
|
273
|
+
else
|
274
|
+
Timecode.new(@total-arg, @fps)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Multiply the timecode by a number
|
279
|
+
def *(arg)
|
280
|
+
raise RangeError, "Timecode multiplier cannot be negative" if (arg < 0)
|
281
|
+
Timecode.new(@total*arg.to_i, @fps)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Get the next frame
|
285
|
+
def succ
|
286
|
+
self.class.new(@total + 1)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Slice the timespan in pieces
|
290
|
+
def /(arg)
|
291
|
+
Timecode.new(@total/arg, @fps)
|
292
|
+
end
|
293
|
+
|
294
|
+
# Timecodes can be compared to each other
|
295
|
+
def <=>(other_tc)
|
296
|
+
if other_tc.is_a?(Timecode)
|
297
|
+
self.total <=> other_tc.class.new(other_tc.total, self.fps).total
|
298
|
+
else
|
299
|
+
self.total <=> other_tc
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
private
|
304
|
+
|
305
|
+
# Formats the actual timecode output from the number of frames
|
306
|
+
def validate!
|
307
|
+
frames = @total
|
308
|
+
secs = (@total.to_f/@fps).floor
|
309
|
+
frames-=(secs*@fps)
|
310
|
+
mins = (secs/60).floor
|
311
|
+
secs -= (mins*60)
|
312
|
+
hrs = (mins/60).floor
|
313
|
+
mins-= (hrs*60)
|
314
|
+
|
315
|
+
raise RangeError, "Timecode cannot be longer that 99 hrs" if hrs > 99
|
316
|
+
raise RangeError, "More than 59 minutes" if mins > 59
|
317
|
+
raise RangeError, "More than 59 seconds" if secs > 59
|
318
|
+
raise TimecodeLibError, "More than #{@fps.to_s} frames (#{frames}) in the last second" if frames >= @fps
|
319
|
+
|
320
|
+
[hrs, mins, secs, frames]
|
321
|
+
end
|
322
|
+
|
323
|
+
def value_parts
|
324
|
+
@value ||= validate!
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'timecode'
|
4
|
+
|
5
|
+
# for Fixnum#hours
|
6
|
+
require 'active_support'
|
7
|
+
|
8
|
+
class TimecodeTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def test_basics
|
11
|
+
five_seconds_of_pal = 5.seconds * 25
|
12
|
+
tc = Timecode.new(five_seconds_of_pal, 25)
|
13
|
+
assert_equal 0, tc.hours
|
14
|
+
assert_equal 0, tc.minutes
|
15
|
+
assert_equal 5, tc.seconds
|
16
|
+
assert_equal 0, tc.frames
|
17
|
+
assert_equal five_seconds_of_pal, tc.total
|
18
|
+
assert_equal "00:00:05:00", tc.to_s
|
19
|
+
|
20
|
+
one_and_a_half_hour_of_hollywood = 90.minutes * 24
|
21
|
+
|
22
|
+
film_tc = Timecode.new(one_and_a_half_hour_of_hollywood, 24)
|
23
|
+
assert_equal 1, film_tc.hours
|
24
|
+
assert_equal 30, film_tc.minutes
|
25
|
+
assert_equal 0, film_tc.seconds
|
26
|
+
assert_equal 0, film_tc.frames
|
27
|
+
assert_equal one_and_a_half_hour_of_hollywood, film_tc.total
|
28
|
+
assert_equal "01:30:00:00", film_tc.to_s
|
29
|
+
|
30
|
+
assert_equal "01:30:00:04", (film_tc + 4).to_s
|
31
|
+
assert_equal "01:30:01:04", (film_tc + 28).to_s
|
32
|
+
|
33
|
+
assert_raise(Timecode::WrongFramerate) do
|
34
|
+
tc + film_tc
|
35
|
+
end
|
36
|
+
|
37
|
+
two_seconds_and_five_frames_of_pal = ((2.seconds * 25) + 5)
|
38
|
+
pal_tc = Timecode.new(two_seconds_and_five_frames_of_pal, 25)
|
39
|
+
assert_nothing_raised do
|
40
|
+
added_tc = pal_tc + tc
|
41
|
+
assert_equal "00:00:07:05", added_tc.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_parse
|
47
|
+
simple_tc = "00:10:34:10"
|
48
|
+
|
49
|
+
assert_nothing_raised do
|
50
|
+
@tc = Timecode.parse(simple_tc)
|
51
|
+
assert_equal simple_tc, @tc.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
bad_tc = "00:76:89:30"
|
55
|
+
unknown_gobbledygook = "this is insane"
|
56
|
+
|
57
|
+
assert_raise(Timecode::CannotParse) do
|
58
|
+
tc = Timecode.parse(unknown_gobbledygook, 25)
|
59
|
+
end
|
60
|
+
|
61
|
+
assert_raise(Timecode::RangeError) do
|
62
|
+
Timecode.parse(bad_tc, 25)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_succ
|
67
|
+
assert_equal Timecode.new(23), Timecode.new(22).succ
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_zero
|
71
|
+
assert Timecode.new(0).zero?
|
72
|
+
assert !Timecode.new(1).zero?
|
73
|
+
assert !Timecode.new(1000).zero?
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_parse_from_numbers
|
77
|
+
assert_equal Timecode.new(10), Timecode.parse("10")
|
78
|
+
assert_equal Timecode.new(60), Timecode.parse("210")
|
79
|
+
assert_equal "10:10:10:10", Timecode.parse("10101010").to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_parse_with_f
|
83
|
+
assert_equal Timecode.new(60), Timecode.parse("60f")
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_parse_s
|
87
|
+
assert_equal Timecode.new(50), Timecode.parse("2s")
|
88
|
+
assert_equal Timecode.new(60), Timecode.parse("2s", 30)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_parse_m
|
92
|
+
assert_equal Timecode.new(25 * 60 * 3), Timecode.parse("3m")
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_parse_h
|
96
|
+
assert_equal Timecode.new(25 * 60 * 60 * 3), Timecode.parse("3h")
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_parse_from_elements
|
100
|
+
assert_equal '01:00:00:04', Timecode.parse("1h 4f").to_s
|
101
|
+
assert_equal '01:00:00:04', Timecode.parse("4f 1h").to_s
|
102
|
+
assert_equal '01:00:01:04', Timecode.parse("29f 1h").to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_float_framerate
|
106
|
+
tc = Timecode.new(25, 12.5)
|
107
|
+
assert_equal "00:00:02:00", tc.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_timecode_with_nil_gives_zero
|
111
|
+
assert_equal Timecode.new(0), Timecode.new(nil)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_parse_fractional_tc
|
115
|
+
fraction = "00:00:07.1"
|
116
|
+
tc = Timecode.parse_with_fractional_seconds(fraction, 10)
|
117
|
+
assert_equal "00:00:07:01", tc.to_s
|
118
|
+
|
119
|
+
fraction = "00:00:07.5"
|
120
|
+
tc = Timecode.parse_with_fractional_seconds(fraction, 10)
|
121
|
+
assert_equal "00:00:07:05", tc.to_s
|
122
|
+
|
123
|
+
fraction = "00:00:07.04"
|
124
|
+
tc = Timecode.parse_with_fractional_seconds(fraction, 12.5)
|
125
|
+
assert_equal "00:00:07:00", tc.to_s
|
126
|
+
|
127
|
+
fraction = "00:00:07.16"
|
128
|
+
tc = Timecode.parse_with_fractional_seconds(fraction, 12.5)
|
129
|
+
assert_equal "00:00:07:02", tc.to_s
|
130
|
+
end
|
131
|
+
|
132
|
+
# def test_parse_with_calculation
|
133
|
+
# tc = Timecode.parse_with_calculation("00:00:00:15 +2f")
|
134
|
+
# assert_equal Timecode.new(17), tc
|
135
|
+
# end
|
136
|
+
|
137
|
+
def test_from_uint
|
138
|
+
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
139
|
+
assert_equal tc, Timecode.from_uint(uint)
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_to_uint
|
143
|
+
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
144
|
+
assert_equal uint, tc.to_uint
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_from_seconds
|
148
|
+
fraction = 7.1
|
149
|
+
tc = Timecode.from_seconds(fraction, 10)
|
150
|
+
assert_equal "00:00:07:01", tc.to_s
|
151
|
+
|
152
|
+
fraction = 7.5
|
153
|
+
tc = Timecode.from_seconds(fraction, 10)
|
154
|
+
assert_equal "00:00:07:05", tc.to_s
|
155
|
+
|
156
|
+
fraction = 7.16
|
157
|
+
tc = Timecode.from_seconds(fraction, 12.5)
|
158
|
+
assert_equal "00:00:07:02", tc.to_s
|
159
|
+
end
|
160
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: timecode
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-19 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.2
|
24
|
+
version:
|
25
|
+
description: Value class for SMPTE timecode information
|
26
|
+
email:
|
27
|
+
- me@julik.nl
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- History.txt
|
34
|
+
- Manifest.txt
|
35
|
+
- README.txt
|
36
|
+
files:
|
37
|
+
- History.txt
|
38
|
+
- Manifest.txt
|
39
|
+
- README.txt
|
40
|
+
- Rakefile
|
41
|
+
- lib/timecode.rb
|
42
|
+
- test/test_timecode.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://projects.juli.nl/timecode
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --main
|
48
|
+
- README.txt
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project: wiretap
|
66
|
+
rubygems_version: 1.3.1
|
67
|
+
signing_key:
|
68
|
+
specification_version: 2
|
69
|
+
summary: Value class for SMPTE timecode information
|
70
|
+
test_files:
|
71
|
+
- test/test_timecode.rb
|