timecode 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -1
- data/README.txt +1 -1
- data/Rakefile +8 -2
- data/lib/timecode.rb +26 -24
- data/test/test_timecode.rb +66 -28
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
=== 1.
|
1
|
+
=== 0.1.2 / 2008-12-25
|
2
|
+
|
3
|
+
* Fix to_uint
|
4
|
+
* Always use float frame rates and rely on a small delta for comparison
|
5
|
+
|
6
|
+
=== 0.1.1 / 2008-12-15
|
7
|
+
|
8
|
+
* Allow passing framerate to from_uint
|
9
|
+
|
10
|
+
=== 0.1.0 / 2008-12-15
|
2
11
|
|
3
12
|
* 1 major enhancement
|
4
13
|
|
data/README.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'hoe'
|
3
2
|
require './lib/timecode.rb'
|
3
|
+
require 'hoe'
|
4
4
|
|
5
|
-
|
5
|
+
Class.new(Hoe) do
|
6
|
+
def extra_deps
|
7
|
+
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
|
8
|
+
@extra_deps
|
9
|
+
end
|
10
|
+
end.new('timecode', Timecode::VERSION) do |p|
|
6
11
|
p.developer('Julik', 'me@julik.nl')
|
7
12
|
p.extra_deps.reject! {|e| e[0] == 'hoe' }
|
8
13
|
p.rubyforge_name = 'wiretap'
|
14
|
+
p.remote_rdoc_dir = 'timecode'
|
9
15
|
end
|
data/lib/timecode.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
# Timecode is a convenience object for calculating SMPTE timecode natively.
|
2
|
-
# various StoryTool models and templates, offers string output and is immutable.
|
3
|
-
#
|
1
|
+
# Timecode is a convenience object for calculating SMPTE timecode natively.
|
4
2
|
# The promise is that you only have to store two values to know the timecode - the amount
|
5
3
|
# of frames and the framerate. An additional perk might be to save the dropframeness,
|
6
4
|
# but we avoid that at this point.
|
@@ -14,10 +12,12 @@
|
|
14
12
|
# :mapping => [%w(source_tc_frames total), %w(tape_fps fps)]
|
15
13
|
|
16
14
|
class Timecode
|
17
|
-
VERSION = '0.1.
|
15
|
+
VERSION = '0.1.2'
|
18
16
|
|
19
17
|
include Comparable
|
20
|
-
|
18
|
+
|
19
|
+
DEFAULT_FPS = 25.0
|
20
|
+
ALLOWED_FPS_DELTA = 0.001
|
21
21
|
COMPLETE_TC_RE = /^(\d{1,2}):(\d{1,2}):(\d{1,2}):(\d{1,2})$/
|
22
22
|
|
23
23
|
# All Timecode lib errors inherit from this
|
@@ -32,9 +32,6 @@ class Timecode
|
|
32
32
|
# Self-explanatory
|
33
33
|
class NonPositiveFps < RangeError; end
|
34
34
|
|
35
|
-
# Gets raised when float frame count is passed
|
36
|
-
class FrameIsWhole < RangeError; end
|
37
|
-
|
38
35
|
# Gets raised when you divide by zero
|
39
36
|
class TcIsZero < ZeroDivisionError; end
|
40
37
|
|
@@ -60,19 +57,17 @@ class Timecode
|
|
60
57
|
end
|
61
58
|
|
62
59
|
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
60
|
raise RangeError, "Timecode cannot be negative" if total.to_f < 0
|
68
61
|
raise WrongFramerate, "FPS cannot be zero" if fps.zero?
|
69
|
-
|
62
|
+
|
63
|
+
# Always cast framerate to float, and num of rames to integer
|
64
|
+
@total, @fps = total.to_i, fps.to_f
|
70
65
|
@value = validate!
|
71
66
|
freeze
|
72
67
|
end
|
73
68
|
|
74
69
|
def inspect # :nodoc:
|
75
|
-
|
70
|
+
"#<Timecode:%s (%dF@%.2f)>" % [to_s, total, fps]
|
76
71
|
end
|
77
72
|
|
78
73
|
TIME_FIELDS = 7 # :nodoc:
|
@@ -167,13 +162,14 @@ class Timecode
|
|
167
162
|
|
168
163
|
|
169
164
|
# Some systems (like SGIs) and DPX format store timecode as unsigned integer, bit-packed
|
170
|
-
def from_uint(uint)
|
165
|
+
def from_uint(uint, fps = DEFAULT_FPS)
|
171
166
|
shift = 4 * TIME_FIELDS
|
172
167
|
tc_elements = (0..TIME_FIELDS).map do
|
173
168
|
part = ((uint >> shift) & 0x0F)
|
174
169
|
shift -= 4
|
175
170
|
part
|
176
171
|
end.join.scan(/(\d{2})/).flatten.map{|e| e.to_i}
|
172
|
+
tc_elements << fps
|
177
173
|
at(*tc_elements)
|
178
174
|
end
|
179
175
|
end
|
@@ -218,12 +214,12 @@ class Timecode
|
|
218
214
|
1.0/@fps
|
219
215
|
end
|
220
216
|
|
221
|
-
# get the timecode as bit-packed unsigned int (suitable for DPX and SGI)
|
217
|
+
# get the timecode as bit-packed unsigned 32 bit int (suitable for DPX and SGI)
|
222
218
|
def to_uint
|
223
219
|
elements = (("%02d" * 4) % [hours,minutes,seconds,frames]).split(//).map{|e| e.to_i }
|
224
220
|
uint = 0
|
225
|
-
elements.
|
226
|
-
uint
|
221
|
+
elements.reverse.each_with_index do | p, i |
|
222
|
+
uint |= p << 4 * i
|
227
223
|
end
|
228
224
|
uint
|
229
225
|
end
|
@@ -255,7 +251,7 @@ class Timecode
|
|
255
251
|
|
256
252
|
# add number of frames (or another timecode) to this one
|
257
253
|
def +(arg)
|
258
|
-
if (arg.is_a?(Timecode) && arg.fps
|
254
|
+
if (arg.is_a?(Timecode) && framerate_in_delta(arg.fps, @fps))
|
259
255
|
Timecode.new(@total+arg.total, @fps)
|
260
256
|
elsif (arg.is_a?(Timecode))
|
261
257
|
raise WrongFramerate, "You are calculating timecodes with different framerates"
|
@@ -266,7 +262,7 @@ class Timecode
|
|
266
262
|
|
267
263
|
# Subtract a number of frames
|
268
264
|
def -(arg)
|
269
|
-
if (arg.is_a?(Timecode) && arg.fps
|
265
|
+
if (arg.is_a?(Timecode) && framerate_in_delta(arg.fps, @fps))
|
270
266
|
Timecode.new(@total-arg.total, @fps)
|
271
267
|
elsif (arg.is_a?(Timecode))
|
272
268
|
raise WrongFramerate, "You are calculating timecodes with different framerates"
|
@@ -283,7 +279,7 @@ class Timecode
|
|
283
279
|
|
284
280
|
# Get the next frame
|
285
281
|
def succ
|
286
|
-
self.class.new(@total + 1)
|
282
|
+
self.class.new(@total + 1, @fps)
|
287
283
|
end
|
288
284
|
|
289
285
|
# Slice the timespan in pieces
|
@@ -293,13 +289,19 @@ class Timecode
|
|
293
289
|
|
294
290
|
# Timecodes can be compared to each other
|
295
291
|
def <=>(other_tc)
|
296
|
-
if other_tc.is_a?(Timecode)
|
297
|
-
self.total <=> other_tc.
|
292
|
+
if other_tc.is_a?(Timecode) && framerate_in_delta(fps, other_tc.fps)
|
293
|
+
self.total <=> other_tc.total
|
298
294
|
else
|
299
295
|
self.total <=> other_tc
|
300
296
|
end
|
301
297
|
end
|
302
|
-
|
298
|
+
|
299
|
+
|
300
|
+
# Validate that framerates are within a small delta deviation considerable for floats
|
301
|
+
def framerate_in_delta(one, two)
|
302
|
+
(one.to_f - two.to_f).abs <= ALLOWED_FPS_DELTA
|
303
|
+
end
|
304
|
+
|
303
305
|
private
|
304
306
|
|
305
307
|
# Formats the actual timecode output from the number of frames
|
data/test/test_timecode.rb
CHANGED
@@ -7,6 +7,37 @@ require 'active_support'
|
|
7
7
|
|
8
8
|
class TimecodeTest < Test::Unit::TestCase
|
9
9
|
|
10
|
+
def test_timecode_with_nil_gives_zero
|
11
|
+
assert_equal Timecode.new(0), Timecode.new(nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_fps_always_coerced_to_float
|
15
|
+
t = Timecode.new(10, 25)
|
16
|
+
assert_kind_of Float, t.fps
|
17
|
+
|
18
|
+
t = Timecode.new(10, 25.0)
|
19
|
+
assert_kind_of Float, t.fps
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_framerate_in_delta
|
23
|
+
tc = Timecode.new(1)
|
24
|
+
assert tc.framerate_in_delta(25.0000000000000001, 25.0000000000000003)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_equality_validated_based_on_deltas
|
28
|
+
t1, t2 = Timecode.new(10, 25.0000000000000000000000000001), Timecode.new(10, 25.0000000000000000000000000002)
|
29
|
+
assert t1 == t2
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_inspect
|
33
|
+
tc = Timecode.new(10, 25)
|
34
|
+
assert_equal "#<Timecode:00:00:00:10 (10F@25.00)>", tc.inspect
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_equality_on_succ
|
38
|
+
assert_equal Timecode.parse("05:43:02:01"), Timecode.parse("05:43:02:00").succ
|
39
|
+
end
|
40
|
+
|
10
41
|
def test_basics
|
11
42
|
five_seconds_of_pal = 5.seconds * 25
|
12
43
|
tc = Timecode.new(five_seconds_of_pal, 25)
|
@@ -42,8 +73,26 @@ class TimecodeTest < Test::Unit::TestCase
|
|
42
73
|
end
|
43
74
|
|
44
75
|
end
|
45
|
-
|
46
|
-
def
|
76
|
+
|
77
|
+
def test_succ
|
78
|
+
assert_equal Timecode.new(23), Timecode.new(22).succ
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_zero
|
82
|
+
assert Timecode.new(0).zero?
|
83
|
+
assert !Timecode.new(1).zero?
|
84
|
+
assert !Timecode.new(1000).zero?
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_plus
|
88
|
+
a, b = Timecode.new(24, 25.000000000000001), Timecode.new(22, 25.000000000000002)
|
89
|
+
assert_equal Timecode.new(24 + 22, 25.000000000000001), (a + b)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class TestParsing < Test::Unit::TestCase
|
94
|
+
|
95
|
+
def test_parse_simple
|
47
96
|
simple_tc = "00:10:34:10"
|
48
97
|
|
49
98
|
assert_nothing_raised do
|
@@ -63,16 +112,6 @@ class TimecodeTest < Test::Unit::TestCase
|
|
63
112
|
end
|
64
113
|
end
|
65
114
|
|
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
115
|
def test_parse_from_numbers
|
77
116
|
assert_equal Timecode.new(10), Timecode.parse("10")
|
78
117
|
assert_equal Timecode.new(60), Timecode.parse("210")
|
@@ -84,8 +123,9 @@ class TimecodeTest < Test::Unit::TestCase
|
|
84
123
|
end
|
85
124
|
|
86
125
|
def test_parse_s
|
87
|
-
assert_equal Timecode.new(50), Timecode.parse("2s")
|
88
|
-
assert_equal Timecode.new(60), Timecode.parse("2s", 30)
|
126
|
+
assert_equal Timecode.new(50, 25), Timecode.parse("2s", 25)
|
127
|
+
assert_equal Timecode.new(60, 30), Timecode.parse("2s", 30)
|
128
|
+
assert_not_equal Timecode.new(60, 25), Timecode.parse("2s", 30)
|
89
129
|
end
|
90
130
|
|
91
131
|
def test_parse_m
|
@@ -107,10 +147,6 @@ class TimecodeTest < Test::Unit::TestCase
|
|
107
147
|
assert_equal "00:00:02:00", tc.to_s
|
108
148
|
end
|
109
149
|
|
110
|
-
def test_timecode_with_nil_gives_zero
|
111
|
-
assert_equal Timecode.new(0), Timecode.new(nil)
|
112
|
-
end
|
113
|
-
|
114
150
|
def test_parse_fractional_tc
|
115
151
|
fraction = "00:00:07.1"
|
116
152
|
tc = Timecode.parse_with_fractional_seconds(fraction, 10)
|
@@ -134,16 +170,6 @@ class TimecodeTest < Test::Unit::TestCase
|
|
134
170
|
# assert_equal Timecode.new(17), tc
|
135
171
|
# end
|
136
172
|
|
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
173
|
def test_from_seconds
|
148
174
|
fraction = 7.1
|
149
175
|
tc = Timecode.from_seconds(fraction, 10)
|
@@ -158,3 +184,15 @@ class TimecodeTest < Test::Unit::TestCase
|
|
158
184
|
assert_equal "00:00:07:02", tc.to_s
|
159
185
|
end
|
160
186
|
end
|
187
|
+
|
188
|
+
class TestUintConversion < Test::Unit::TestCase
|
189
|
+
def test_from_uint
|
190
|
+
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
191
|
+
assert_equal tc, Timecode.from_uint(uint)
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_to_uint
|
195
|
+
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
196
|
+
assert_equal uint, tc.to_uint
|
197
|
+
end
|
198
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timecode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-12-
|
12
|
+
date: 2008-12-25 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -41,7 +41,7 @@ files:
|
|
41
41
|
- lib/timecode.rb
|
42
42
|
- test/test_timecode.rb
|
43
43
|
has_rdoc: true
|
44
|
-
homepage: http://
|
44
|
+
homepage: http://wiretap.rubyforge.org/timecode
|
45
45
|
post_install_message:
|
46
46
|
rdoc_options:
|
47
47
|
- --main
|