timecode 0.1.0 → 0.1.2
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 +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
|