timecode 0.1.4 → 0.1.5
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 +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
|