timecode 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|