timecode 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +3 -1
- data/Rakefile +8 -8
- data/SPECS.txt +74 -0
- data/lib/timecode.rb +29 -22
- data/test/test_timecode.rb +194 -184
- data/timecode.gemspec +38 -0
- metadata +16 -3
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require './lib/timecode.rb'
|
3
2
|
require 'hoe'
|
3
|
+
require './lib/timecode.rb'
|
4
4
|
|
5
|
-
|
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|
|
5
|
+
Hoe.new('timecode', Timecode::VERSION) do |p|
|
11
6
|
p.developer('Julik', 'me@julik.nl')
|
12
7
|
p.extra_deps.reject! {|e| e[0] == 'hoe' }
|
13
|
-
p.
|
8
|
+
p.extra_deps << 'test-spec'
|
9
|
+
p.rubyforge_name = 'guerilla-di'
|
14
10
|
p.remote_rdoc_dir = 'timecode'
|
11
|
+
end
|
12
|
+
|
13
|
+
task "specs" do
|
14
|
+
`specrb test/* --rdox > SPECS.txt`
|
15
15
|
end
|
data/SPECS.txt
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
== Timecode on instantiation should
|
3
|
+
* be instantable from int
|
4
|
+
* always coerce FPS to float
|
5
|
+
* create a zero TC with no arguments
|
6
|
+
|
7
|
+
== An existing Timecode should
|
8
|
+
* report that the framerates are in delta
|
9
|
+
* validate equality based on delta
|
10
|
+
* report total as it's to_i
|
11
|
+
* support hours
|
12
|
+
* support minutes
|
13
|
+
* support seconds
|
14
|
+
* support frames
|
15
|
+
* report frame_interval as a float
|
16
|
+
|
17
|
+
== A Timecode of zero should
|
18
|
+
* properly respond to zero?
|
19
|
+
|
20
|
+
== An existing TImecode on inspection should
|
21
|
+
* properly present himself via inspect
|
22
|
+
* properly print itself
|
23
|
+
|
24
|
+
== An existing Timecode used within ranges should
|
25
|
+
* properly provide successive value that is one frame up
|
26
|
+
* work as a range member
|
27
|
+
|
28
|
+
== A Timecode on conversion should
|
29
|
+
* copy itself with a different framerate
|
30
|
+
|
31
|
+
== A Timecode on calculations should
|
32
|
+
* support addition
|
33
|
+
* should raise on addition if framerates do not match
|
34
|
+
* when added with an integer instead calculate on total
|
35
|
+
* support subtraction
|
36
|
+
* on subtraction of an integer instead calculate on total
|
37
|
+
* raise when subtracting a Timecode with a different framerate
|
38
|
+
* support multiplication
|
39
|
+
* raise when the resultig Timecode is negative
|
40
|
+
* yield a Timecode when divided by an Integer
|
41
|
+
* yield a number when divided by another Timecode
|
42
|
+
|
43
|
+
== A Timecode used with fractional number of seconds
|
44
|
+
* should properly return fractional seconds
|
45
|
+
* properly translate to frames when instantiated from fractional seconds
|
46
|
+
|
47
|
+
== Timecode.at() should
|
48
|
+
* disallow more than 99 hrs
|
49
|
+
* disallow more than 59 minutes
|
50
|
+
* disallow more than 59 seconds
|
51
|
+
* disallow more frames than what the framerate permits
|
52
|
+
* propery accept usable values
|
53
|
+
|
54
|
+
== Timecode.parse() should
|
55
|
+
* handle complete SMPTE timecode
|
56
|
+
* refuse to handle timecode that is out of range for the framerate
|
57
|
+
* parse a row of numbers as parts of a timecode starting from the right
|
58
|
+
* parse a number with f suffix as frames
|
59
|
+
* parse a number with s suffix as seconds
|
60
|
+
* parse a number with m suffix as minutes
|
61
|
+
* parse a number with h suffix as hours
|
62
|
+
* parse different suffixes as a sum of elements
|
63
|
+
* parse timecode with fractional second instead of frames
|
64
|
+
* raise on improper format
|
65
|
+
|
66
|
+
== Timecode.soft_parse should
|
67
|
+
* parse the timecode
|
68
|
+
* not raise on improper format and return zero TC instead
|
69
|
+
|
70
|
+
== Timecode with unsigned integer conversions should
|
71
|
+
* parse from a 4x4bits packed 32bit unsigned int
|
72
|
+
* properly convert itself back to 4x4 bits 32bit unsigned int
|
73
|
+
|
74
|
+
48 specifications (80 requirements), 0 failures
|
data/lib/timecode.rb
CHANGED
@@ -12,15 +12,23 @@
|
|
12
12
|
# :mapping => [%w(source_tc_frames total), %w(tape_fps fps)]
|
13
13
|
|
14
14
|
class Timecode
|
15
|
-
VERSION = '0.1.
|
15
|
+
VERSION = '0.1.5'
|
16
16
|
|
17
17
|
include Comparable
|
18
18
|
|
19
19
|
DEFAULT_FPS = 25.0
|
20
|
-
|
21
|
-
|
20
|
+
|
21
|
+
#:stopdoc:
|
22
|
+
NTSC_FPS = (30.0 * 1000 / 1001).freeze
|
23
|
+
ALLOWED_FPS_DELTA = (0.001).freeze
|
24
|
+
|
25
|
+
COMPLETE_TC_RE = /^(\d{2}):(\d{2}):(\d{2}):(\d{2})$/
|
26
|
+
DF_TC_RE = /^(\d{1,2}):(\d{1,2}):(\d{1,2});(\d{2})$/
|
27
|
+
FRACTIONAL_TC_RE = /^(\d{2}):(\d{2}):(\d{2}).(\d{1,8})$/
|
28
|
+
|
22
29
|
WITH_FRACTIONS_OF_SECOND = "%02d:%02d:%02d.%02d"
|
23
30
|
WITH_FRAMES = "%02d:%02d:%02d:%02d"
|
31
|
+
#:startdoc:
|
24
32
|
|
25
33
|
# All Timecode lib errors inherit from this
|
26
34
|
class Error < RuntimeError; end
|
@@ -31,27 +39,19 @@ class Timecode
|
|
31
39
|
# Gets raised if timecode is out of range (like 100 hours long)
|
32
40
|
class RangeError < Error; end
|
33
41
|
|
34
|
-
# Self-explanatory
|
35
|
-
class NonPositiveFps < RangeError; end
|
36
|
-
|
37
|
-
# Gets raised when you divide by zero
|
38
|
-
class TcIsZero < ZeroDivisionError; end
|
39
|
-
|
40
42
|
# Gets raised when a timecode cannot be parsed
|
41
43
|
class CannotParse < Error; end
|
42
44
|
|
43
45
|
# Gets raised when you try to compute two timecodes with different framerates together
|
44
46
|
class WrongFramerate < ArgumentError; end
|
45
|
-
|
46
|
-
# Well well...
|
47
|
-
class MethodRequiresTimecode < ArgumentError; end
|
48
47
|
|
49
48
|
# Initialize a new Timecode object with a certain amount of frames and a framerate
|
50
49
|
# will be interpreted as the total number of frames
|
51
50
|
def initialize(total = 0, fps = DEFAULT_FPS)
|
52
|
-
raise RangeError, "Timecode cannot be negative" if total.to_f < 0
|
53
51
|
raise WrongFramerate, "FPS cannot be zero" if fps.zero?
|
54
|
-
|
52
|
+
|
53
|
+
# If total is a string, use parse
|
54
|
+
raise RangeError, "Timecode cannot be negative" if total.to_i < 0
|
55
55
|
# Always cast framerate to float, and num of rames to integer
|
56
56
|
@total, @fps = total.to_i, fps.to_f
|
57
57
|
@value = validate!
|
@@ -62,10 +62,13 @@ class Timecode
|
|
62
62
|
"#<Timecode:%s (%dF@%.2f)>" % [to_s, total, fps]
|
63
63
|
end
|
64
64
|
|
65
|
-
TIME_FIELDS = 7 # :nodoc:
|
66
|
-
|
67
65
|
class << self
|
68
66
|
|
67
|
+
# Use initialize for integers and parsing for strings
|
68
|
+
def new(from, fps = DEFAULT_FPS)
|
69
|
+
from.is_a?(String) ? parse(from, fps) : super(from, fps)
|
70
|
+
end
|
71
|
+
|
69
72
|
# Parse timecode and return zero if none matched
|
70
73
|
def soft_parse(input, with_fps = DEFAULT_FPS)
|
71
74
|
parse(input) rescue new(0, with_fps)
|
@@ -78,13 +81,19 @@ class Timecode
|
|
78
81
|
# * 00:00:00:00 - will be parsed as zero TC
|
79
82
|
def parse(input, with_fps = DEFAULT_FPS)
|
80
83
|
# Drop frame goodbye
|
81
|
-
raise
|
84
|
+
raise Error, "We do not support drop frame" if (input =~ /\;/)
|
82
85
|
|
83
86
|
hrs, mins, secs, frames = 0,0,0,0
|
84
87
|
atoms = []
|
85
88
|
|
89
|
+
# 00:00:00:00
|
90
|
+
if (input =~ COMPLETE_TC_RE)
|
91
|
+
atoms = input.scan(COMPLETE_TC_RE).to_a.flatten
|
92
|
+
# 00:00:00.0
|
93
|
+
elsif input =~ FRACTIONAL_TC_RE
|
94
|
+
parse_with_fractional_seconds(input, with_fps)
|
86
95
|
# 10h 20m 10s 1f
|
87
|
-
|
96
|
+
elsif input =~ /\s/
|
88
97
|
return input.split.map{|part| parse(part, with_fps) }.inject { |sum, p| sum + p.total }
|
89
98
|
# 10s
|
90
99
|
elsif input =~ /^(\d+)s$/
|
@@ -105,8 +114,6 @@ class Timecode
|
|
105
114
|
atoms.unshift [ints.pop, ints.pop].reverse.join.to_i
|
106
115
|
atoms.unshift [ints.pop, ints.pop].reverse.join.to_i
|
107
116
|
atoms.unshift [ints.pop, ints.pop].reverse.join.to_i
|
108
|
-
elsif (input =~ COMPLETE_TC_RE)
|
109
|
-
atoms = input.scan(COMPLETE_TC_RE).to_a.flatten
|
110
117
|
else
|
111
118
|
raise CannotParse, "Cannot parse #{input} into timecode, no match"
|
112
119
|
end
|
@@ -146,7 +153,7 @@ class Timecode
|
|
146
153
|
seconds_per_frame = 1.0 / fps.to_f
|
147
154
|
frame_idx = (fraction_part / seconds_per_frame).floor
|
148
155
|
|
149
|
-
tc_with_frameno = tc_with_fractions_of_second.gsub(fraction_expr, "
|
156
|
+
tc_with_frameno = tc_with_fractions_of_second.gsub(fraction_expr, ":%02d" % frame_idx)
|
150
157
|
|
151
158
|
parse(tc_with_frameno, fps)
|
152
159
|
end
|
@@ -321,7 +328,7 @@ class Timecode
|
|
321
328
|
raise RangeError, "Timecode cannot be longer that 99 hrs" if hrs > 99
|
322
329
|
raise RangeError, "More than 59 minutes" if mins > 59
|
323
330
|
raise RangeError, "More than 59 seconds" if secs > 59
|
324
|
-
raise
|
331
|
+
raise RangeError, "More than #{@fps.to_s} frames (#{frames}) in the last second" if frames >= @fps
|
325
332
|
|
326
333
|
[hrs, mins, secs, frames]
|
327
334
|
end
|
data/test/test_timecode.rb
CHANGED
@@ -1,308 +1,318 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'test/spec'
|
4
|
+
|
2
5
|
require File.dirname(__FILE__) + '/../lib/timecode'
|
3
6
|
|
4
7
|
|
5
|
-
|
8
|
+
context "Timecode on instantiation should" do
|
6
9
|
|
7
|
-
|
8
|
-
|
10
|
+
specify "be instantable from int" do
|
11
|
+
tc = Timecode.new(10)
|
12
|
+
tc.should.be.kind_of Timecode
|
13
|
+
tc.total.should.equal 10
|
9
14
|
end
|
10
15
|
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
specify "always coerce FPS to float" do
|
17
|
+
Timecode.new(10, 24).fps.should.be.kind_of(Float)
|
18
|
+
Timecode.new(10, 25.0).fps.should.be.kind_of(Float)
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "create a zero TC with no arguments" do
|
22
|
+
Timecode.new(nil).should.be.zero?
|
23
|
+
end
|
24
|
+
end
|
14
25
|
|
15
|
-
|
16
|
-
|
26
|
+
context "An existing Timecode should" do
|
27
|
+
|
28
|
+
before do
|
29
|
+
@five_seconds = Timecode.new(5*25, 25)
|
30
|
+
@one_and_a_half_film = (90 * 60) * 24
|
31
|
+
@film_tc = Timecode.new(@one_and_a_half_film, 24)
|
17
32
|
end
|
18
33
|
|
19
|
-
|
34
|
+
|
35
|
+
specify "report that the framerates are in delta" do
|
20
36
|
tc = Timecode.new(1)
|
21
|
-
|
37
|
+
tc.framerate_in_delta(25.0000000000000001, 25.0000000000000003).should.equal(true)
|
22
38
|
end
|
23
|
-
|
24
|
-
|
39
|
+
|
40
|
+
specify "validate equality based on delta" do
|
25
41
|
t1, t2 = Timecode.new(10, 25.0000000000000000000000000001), Timecode.new(10, 25.0000000000000000000000000002)
|
26
|
-
|
42
|
+
t1.should.equal(t2)
|
27
43
|
end
|
28
44
|
|
29
|
-
|
30
|
-
|
45
|
+
specify "report total as it's to_i" do
|
46
|
+
Timecode.new(10).to_i.should.equal(10)
|
31
47
|
end
|
32
48
|
|
33
|
-
|
34
|
-
|
35
|
-
|
49
|
+
specify "support hours" do
|
50
|
+
@five_seconds.should.respond_to :hours
|
51
|
+
@five_seconds.hours.should.equal 0
|
52
|
+
@film_tc.hours.should.equal 1
|
53
|
+
end
|
54
|
+
|
55
|
+
specify "support minutes" do
|
56
|
+
@five_seconds.should.respond_to :minutes
|
57
|
+
@five_seconds.minutes.should.equal 0
|
58
|
+
@film_tc.minutes.should.equal 30
|
59
|
+
end
|
60
|
+
|
61
|
+
specify "support seconds" do
|
62
|
+
@five_seconds.should.respond_to :seconds
|
63
|
+
@five_seconds.seconds.should.equal 5
|
64
|
+
@film_tc.seconds.should.equal 0
|
36
65
|
end
|
37
66
|
|
38
|
-
|
39
|
-
|
67
|
+
specify "support frames" do
|
68
|
+
@film_tc.frames.should.equal 0
|
40
69
|
end
|
41
70
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
tc = Timecode.new(five_seconds_of_pal, 25)
|
46
|
-
assert_equal 0, tc.hours
|
47
|
-
assert_equal 0, tc.minutes
|
48
|
-
assert_equal 5, tc.seconds
|
49
|
-
assert_equal 0, tc.frames
|
50
|
-
assert_equal five_seconds_of_pal, tc.total
|
51
|
-
assert_equal "00:00:05:00", tc.to_s
|
71
|
+
specify "report frame_interval as a float" do
|
72
|
+
tc = Timecode.new(10)
|
73
|
+
tc.should.respond_to :frame_interval
|
52
74
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
assert_equal 0, film_tc.frames
|
60
|
-
assert_equal one_and_a_half_hour_of_hollywood, film_tc.total
|
61
|
-
assert_equal "01:30:00:00", film_tc.to_s
|
62
|
-
|
63
|
-
assert_equal "01:30:00:04", (film_tc + 4).to_s
|
64
|
-
assert_equal "01:30:01:04", (film_tc + 28).to_s
|
75
|
+
tc.frame_interval.should.be.close 0.04, 0.0001
|
76
|
+
tc = Timecode.new(10, 30)
|
77
|
+
tc.frame_interval.should.be.close 0.03333, 0.0001
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
65
81
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
added_tc = pal_tc + tc
|
74
|
-
assert_equal "00:00:07:05", added_tc.to_s
|
75
|
-
end
|
82
|
+
context "A Timecode of zero should" do
|
83
|
+
specify "properly respond to zero?" do
|
84
|
+
Timecode.new(0).should.respond_to :zero?
|
85
|
+
Timecode.new(0).should.be.zero
|
86
|
+
Timecode.new(1).should.not.be.zero
|
87
|
+
end
|
88
|
+
end
|
76
89
|
|
90
|
+
context "An existing TImecode on inspection should" do
|
91
|
+
specify "properly present himself via inspect" do
|
92
|
+
Timecode.new(10, 25).inspect.should.equal "#<Timecode:00:00:00:10 (10F@25.00)>"
|
77
93
|
end
|
78
94
|
|
79
|
-
|
80
|
-
|
95
|
+
specify "properly print itself" do
|
96
|
+
Timecode.new(5, 25).to_s.should.equal "00:00:00:05"
|
81
97
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
98
|
+
end
|
99
|
+
|
100
|
+
context "An existing Timecode used within ranges should" do
|
101
|
+
specify "properly provide successive value that is one frame up" do
|
102
|
+
Timecode.new(10).succ.total.should.equal 11
|
103
|
+
Timecode.new(22).succ.should.equal Timecode.new(23)
|
88
104
|
end
|
89
105
|
|
90
|
-
|
106
|
+
specify "work as a range member" do
|
91
107
|
r = Timecode.new(10)...Timecode.new(20)
|
92
|
-
|
93
|
-
|
108
|
+
r.to_a.length.should.equal 10
|
109
|
+
r.to_a[4].should.equal Timecode.new(14)
|
94
110
|
end
|
95
111
|
|
96
|
-
def test_frame_interval
|
97
|
-
tc = Timecode.new(10)
|
98
|
-
assert_in_delta tc.frame_interval, 0.04, 0.0001
|
99
|
-
|
100
|
-
tc = Timecode.new(10, 30)
|
101
|
-
assert_in_delta tc.frame_interval, 0.03333, 0.0001
|
102
|
-
end
|
103
112
|
end
|
104
113
|
|
105
|
-
|
106
|
-
|
107
|
-
tc = Timecode.new(40,
|
114
|
+
context "A Timecode on conversion should" do
|
115
|
+
specify "copy itself with a different framerate" do
|
116
|
+
tc = Timecode.new(40,25)
|
108
117
|
at24 = tc.convert(24)
|
109
|
-
|
118
|
+
at24.total.should.equal 40
|
110
119
|
end
|
111
120
|
end
|
112
121
|
|
113
|
-
|
114
|
-
|
122
|
+
context "A Timecode on calculations should" do
|
123
|
+
|
124
|
+
specify "support addition" do
|
115
125
|
a, b = Timecode.new(24, 25.000000000000001), Timecode.new(22, 25.000000000000002)
|
116
|
-
|
126
|
+
(a + b).should.equal Timecode.new(24 + 22, 25.000000000000001)
|
117
127
|
end
|
118
128
|
|
119
|
-
|
120
|
-
|
129
|
+
specify "should raise on addition if framerates do not match" do
|
130
|
+
lambda{ Timecode.new(10, 25) + Timecode.new(10, 30) }.should.raise(Timecode::WrongFramerate)
|
121
131
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
132
|
+
|
133
|
+
specify "when added with an integer instead calculate on total" do
|
134
|
+
(Timecode.new(5) + 5).should.equal(Timecode.new(10))
|
125
135
|
end
|
126
136
|
|
127
|
-
|
137
|
+
specify "support subtraction" do
|
128
138
|
a, b = Timecode.new(10), Timecode.new(4)
|
129
|
-
|
139
|
+
(a - b).should.equal Timecode.new(6)
|
130
140
|
end
|
131
141
|
|
132
|
-
|
133
|
-
|
142
|
+
specify "on subtraction of an integer instead calculate on total" do
|
143
|
+
(Timecode.new(15) - 5).should.equal Timecode.new(10)
|
134
144
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
145
|
+
|
146
|
+
specify "raise when subtracting a Timecode with a different framerate" do
|
147
|
+
lambda { Timecode.new(10, 25) - Timecode.new(10, 30) }.should.raise(Timecode::WrongFramerate)
|
138
148
|
end
|
139
149
|
|
140
|
-
|
141
|
-
|
150
|
+
specify "support multiplication" do
|
151
|
+
(Timecode.new(10) * 10).should.equal(Timecode.new(100))
|
142
152
|
end
|
143
153
|
|
144
|
-
|
145
|
-
|
154
|
+
specify "raise when the resultig Timecode is negative" do
|
155
|
+
lambda { Timecode.new(10) * -200 }.should.raise(Timecode::RangeError)
|
146
156
|
end
|
147
157
|
|
148
|
-
|
158
|
+
specify "yield a Timecode when divided by an Integer" do
|
149
159
|
v = Timecode.new(200) / 20
|
150
|
-
|
151
|
-
|
160
|
+
v.should.be.kind_of(Timecode)
|
161
|
+
v.should.equal Timecode.new(10)
|
152
162
|
end
|
153
|
-
|
154
|
-
|
163
|
+
|
164
|
+
specify "yield a number when divided by another Timecode" do
|
155
165
|
v = Timecode.new(200) / Timecode.new(20)
|
156
|
-
|
157
|
-
|
166
|
+
v.should.be.kind_of(Numeric)
|
167
|
+
v.should.equal 10
|
158
168
|
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "A Timecode used with fractional number of seconds" do
|
159
172
|
|
160
|
-
|
173
|
+
specify "should properly return fractional seconds" do
|
161
174
|
tc = Timecode.new(100 -1, fps = 25)
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
def test_float_framerate
|
168
|
-
tc = Timecode.new(25, 12.5)
|
169
|
-
assert_equal "00:00:02:00", tc.to_s
|
175
|
+
tc.frames.should.equal 24
|
176
|
+
|
177
|
+
tc.with_frames_as_fraction.should.equal "00:00:03.96"
|
178
|
+
tc.with_fractional_seconds.should.equal "00:00:03.96"
|
170
179
|
end
|
171
180
|
|
172
|
-
|
181
|
+
specify "properly translate to frames when instantiated from fractional seconds" do
|
173
182
|
fraction = 7.1
|
174
183
|
tc = Timecode.from_seconds(fraction, 10)
|
175
|
-
|
184
|
+
tc.to_s.should.equal "00:00:07:01"
|
176
185
|
|
177
186
|
fraction = 7.5
|
178
187
|
tc = Timecode.from_seconds(fraction, 10)
|
179
|
-
|
188
|
+
tc.to_s.should.equal "00:00:07:05"
|
180
189
|
|
181
190
|
fraction = 7.16
|
182
191
|
tc = Timecode.from_seconds(fraction, 12.5)
|
183
|
-
|
192
|
+
tc.to_s.should.equal "00:00:07:02"
|
184
193
|
end
|
185
194
|
|
186
195
|
end
|
187
196
|
|
188
|
-
|
197
|
+
context "Timecode.at() should" do
|
189
198
|
|
190
|
-
|
191
|
-
|
192
|
-
|
199
|
+
specify "disallow more than 99 hrs" do
|
200
|
+
lambda{ Timecode.at(99,0,0,0) }.should.not.raise
|
201
|
+
lambda{ Timecode.at(100,0,0,0) }.should.raise(Timecode::RangeError)
|
193
202
|
end
|
194
|
-
|
195
|
-
|
196
|
-
|
203
|
+
|
204
|
+
specify "disallow more than 59 minutes" do
|
205
|
+
lambda{ Timecode.at(1,60,0,0) }.should.raise(Timecode::RangeError)
|
197
206
|
end
|
198
207
|
|
199
|
-
|
200
|
-
|
208
|
+
specify "disallow more than 59 seconds" do
|
209
|
+
lambda{ Timecode.at(1,0,60,0) }.should.raise(Timecode::RangeError)
|
201
210
|
end
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
211
|
+
|
212
|
+
specify "disallow more frames than what the framerate permits" do
|
213
|
+
lambda{ Timecode.at(1,0,60,25, 25) }.should.raise(Timecode::RangeError)
|
214
|
+
lambda{ Timecode.at(1,0,60,32, 30) }.should.raise(Timecode::RangeError)
|
215
|
+
end
|
216
|
+
|
217
|
+
specify "propery accept usable values" do
|
218
|
+
Timecode.at(20, 20, 10, 5).to_s.should.equal "20:20:10:05"
|
206
219
|
end
|
207
|
-
|
208
220
|
end
|
209
221
|
|
210
|
-
class TestParsing < Test::Unit::TestCase
|
211
222
|
|
212
|
-
|
223
|
+
context "Timecode.parse() should" do
|
224
|
+
|
225
|
+
specify "handle complete SMPTE timecode" do
|
213
226
|
simple_tc = "00:10:34:10"
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
227
|
+
Timecode.parse(simple_tc).to_s.should.equal(simple_tc)
|
228
|
+
end
|
229
|
+
|
230
|
+
specify "handle complete SMPTE timecode via new" do
|
231
|
+
simple_tc = "00:10:34:10"
|
232
|
+
Timecode.new(simple_tc).to_s.should.equal(simple_tc)
|
233
|
+
end
|
219
234
|
|
235
|
+
specify "refuse to handle timecode that is out of range for the framerate" do
|
220
236
|
bad_tc = "00:76:89:30"
|
221
|
-
|
222
|
-
|
223
|
-
assert_raise(Timecode::CannotParse) do
|
224
|
-
tc = Timecode.parse(unknown_gobbledygook, 25)
|
225
|
-
end
|
226
|
-
|
227
|
-
assert_raise(Timecode::RangeError) do
|
228
|
-
Timecode.parse(bad_tc, 25)
|
229
|
-
end
|
237
|
+
lambda { Timecode.parse(bad_tc, 25) }.should.raise(Timecode::RangeError)
|
230
238
|
end
|
231
239
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
240
|
+
specify "parse a row of numbers as parts of a timecode starting from the right" do
|
241
|
+
Timecode.parse("10").should.equal Timecode.new(10)
|
242
|
+
Timecode.parse("210").should.equal Timecode.new(60)
|
243
|
+
Timecode.parse("10101010").to_s.should.equal "10:10:10:10"
|
236
244
|
end
|
237
245
|
|
238
|
-
|
239
|
-
|
246
|
+
specify "parse a number with f suffix as frames" do
|
247
|
+
Timecode.parse("60f").should.equal Timecode.new(60)
|
240
248
|
end
|
241
249
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
assert_not_equal Timecode.new(60, 25), Timecode.parse("2s", 30)
|
250
|
+
specify "parse a number with s suffix as seconds" do
|
251
|
+
Timecode.parse("2s", 25).should.equal Timecode.new(50, 25)
|
252
|
+
Timecode.parse("2s", 30).should.equal Timecode.new(60, 30)
|
246
253
|
end
|
247
254
|
|
248
|
-
|
249
|
-
|
255
|
+
specify "parse a number with m suffix as minutes" do
|
256
|
+
Timecode.parse("3m").should.equal Timecode.new(25 * 60 * 3)
|
250
257
|
end
|
251
|
-
|
252
|
-
|
253
|
-
|
258
|
+
|
259
|
+
specify "parse a number with h suffix as hours" do
|
260
|
+
Timecode.parse("3h").should.equal Timecode.new(25 * 60 * 60 * 3)
|
254
261
|
end
|
255
262
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
263
|
+
specify "parse different suffixes as a sum of elements" do
|
264
|
+
Timecode.parse("1h 4f").to_s.should.equal '01:00:00:04'
|
265
|
+
Timecode.parse("4f 1h").to_s.should.equal '01:00:00:04'
|
266
|
+
Timecode.parse("29f 1h").to_s.should.equal '01:00:01:04'
|
260
267
|
end
|
261
268
|
|
262
|
-
|
269
|
+
specify "parse timecode with fractional second instead of frames" do
|
263
270
|
fraction = "00:00:07.1"
|
264
271
|
tc = Timecode.parse_with_fractional_seconds(fraction, 10)
|
265
|
-
|
272
|
+
tc.to_s.should.equal "00:00:07:01"
|
266
273
|
|
267
274
|
fraction = "00:00:07.5"
|
268
275
|
tc = Timecode.parse_with_fractional_seconds(fraction, 10)
|
269
|
-
|
276
|
+
tc.to_s.should.equal "00:00:07:05"
|
270
277
|
|
271
278
|
fraction = "00:00:07.04"
|
272
279
|
tc = Timecode.parse_with_fractional_seconds(fraction, 12.5)
|
273
|
-
|
280
|
+
tc.to_s.should.equal "00:00:07:00"
|
274
281
|
|
275
282
|
fraction = "00:00:07.16"
|
276
283
|
tc = Timecode.parse_with_fractional_seconds(fraction, 12.5)
|
277
|
-
|
284
|
+
tc.to_s.should.equal "00:00:07:02"
|
278
285
|
end
|
279
286
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
287
|
+
specify "raise on improper format" do
|
288
|
+
lambda { Timecode.parse("Meaningless nonsense", 25) }.should.raise Timecode::CannotParse
|
289
|
+
lambda { Timecode.parse("", 25) }.should.raise Timecode::CannotParse
|
290
|
+
end
|
284
291
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
292
|
+
end
|
293
|
+
|
294
|
+
context "Timecode.soft_parse should" do
|
295
|
+
specify "parse the timecode" do
|
296
|
+
Timecode.soft_parse('200').to_s.should.equal "00:00:02:00"
|
290
297
|
end
|
291
298
|
|
292
|
-
|
293
|
-
|
294
|
-
|
299
|
+
specify "not raise on improper format and return zero TC instead" do
|
300
|
+
lambda do
|
301
|
+
tc = Timecode.soft_parse("Meaningless nonsense", 25)
|
302
|
+
tc.should.be.zero?
|
303
|
+
end.should.not.raise
|
295
304
|
end
|
296
305
|
end
|
297
306
|
|
298
|
-
|
299
|
-
|
307
|
+
context "Timecode with unsigned integer conversions should" do
|
308
|
+
|
309
|
+
specify "parse from a 4x4bits packed 32bit unsigned int" do
|
300
310
|
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
301
|
-
|
311
|
+
Timecode.from_uint(uint).should.equal tc
|
302
312
|
end
|
303
313
|
|
304
|
-
|
314
|
+
specify "properly convert itself back to 4x4 bits 32bit unsigned int" do
|
305
315
|
uint, tc = 87310853, Timecode.at(5,34,42,5)
|
306
|
-
|
316
|
+
tc.to_uint.should.equal uint
|
307
317
|
end
|
308
318
|
end
|
data/timecode.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{timecode}
|
5
|
+
s.version = "0.1.5"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Julik"]
|
9
|
+
s.date = %q{2009-01-18}
|
10
|
+
s.description = %q{Value class for SMPTE timecode information}
|
11
|
+
s.email = ["me@julik.nl"]
|
12
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt", "SPECS.txt"]
|
13
|
+
s.files = ["History.txt", "Manifest.txt", "README.txt", "SPECS.txt", "Rakefile", "lib/timecode.rb", "test/test_timecode.rb", "timecode.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://wiretap.rubyforge.org/timecode}
|
16
|
+
s.rdoc_options = ["--main", "README.txt"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{guerilla-di}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{Value class for SMPTE timecode information}
|
21
|
+
s.test_files = ["test/test_timecode.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<test-spec>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<hoe>, [">= 1.8.2"])
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<test-spec>, [">= 0"])
|
32
|
+
s.add_dependency(%q<hoe>, [">= 1.8.2"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
s.add_dependency(%q<test-spec>, [">= 0"])
|
36
|
+
s.add_dependency(%q<hoe>, [">= 1.8.2"])
|
37
|
+
end
|
38
|
+
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.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik
|
@@ -9,9 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-01-
|
12
|
+
date: 2009-01-18 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: test-spec
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
15
25
|
- !ruby/object:Gem::Dependency
|
16
26
|
name: hoe
|
17
27
|
type: :development
|
@@ -33,13 +43,16 @@ extra_rdoc_files:
|
|
33
43
|
- History.txt
|
34
44
|
- Manifest.txt
|
35
45
|
- README.txt
|
46
|
+
- SPECS.txt
|
36
47
|
files:
|
37
48
|
- History.txt
|
38
49
|
- Manifest.txt
|
39
50
|
- README.txt
|
51
|
+
- SPECS.txt
|
40
52
|
- Rakefile
|
41
53
|
- lib/timecode.rb
|
42
54
|
- test/test_timecode.rb
|
55
|
+
- timecode.gemspec
|
43
56
|
has_rdoc: true
|
44
57
|
homepage: http://wiretap.rubyforge.org/timecode
|
45
58
|
post_install_message:
|
@@ -62,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
75
|
version:
|
63
76
|
requirements: []
|
64
77
|
|
65
|
-
rubyforge_project:
|
78
|
+
rubyforge_project: guerilla-di
|
66
79
|
rubygems_version: 1.3.1
|
67
80
|
signing_key:
|
68
81
|
specification_version: 2
|