julik-timecode 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -1
- data/SPECS.txt +15 -5
- data/lib/timecode.rb +26 -18
- data/test/test_timecode.rb +43 -12
- data/timecode.gemspec +1 -1
- metadata +1 -1
data/History.txt
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
=== 0.1.
|
1
|
+
=== 0.1.8 / 2009-01-31
|
2
|
+
|
3
|
+
* Bail out with empty or whitespace strings passed to Timecode.parse
|
4
|
+
* Add Timecode#to_seconds that returns a float value (useful for Quicktime)
|
5
|
+
* Move the validation routine into the class itself
|
6
|
+
|
7
|
+
=== 0.1.7 / 2009-01-31
|
2
8
|
|
3
9
|
* Simplified parsing code, more safeguards and needless exceptions removed
|
4
10
|
|
data/SPECS.txt
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
|
2
|
-
== Timecode.new
|
2
|
+
== Timecode.new should
|
3
3
|
* instantiate from int
|
4
4
|
* always coerce FPS to float
|
5
5
|
* create a zero TC with no arguments
|
6
6
|
* accept full string SMPTE timecode as well
|
7
7
|
|
8
|
-
== Timecode.
|
8
|
+
== Timecode.validate_atoms! should
|
9
|
+
* disallow more than 99 hrs
|
10
|
+
* disallow more than 59 minutes
|
11
|
+
* disallow more than 59 seconds
|
12
|
+
* disallow more frames than what the framerate permits
|
13
|
+
* pass validation with usable values
|
14
|
+
|
15
|
+
== Timecode.at should
|
9
16
|
* disallow more than 99 hrs
|
10
17
|
* disallow more than 59 minutes
|
11
18
|
* disallow more than 59 seconds
|
@@ -30,6 +37,10 @@
|
|
30
37
|
== A Timecode of zero should
|
31
38
|
* properly respond to zero?
|
32
39
|
|
40
|
+
== Timecode#to_seconds should
|
41
|
+
* return a float
|
42
|
+
* return the value in seconds
|
43
|
+
|
33
44
|
== An existing Timecode on inspection should
|
34
45
|
* properly present himself via inspect
|
35
46
|
* properly print itself
|
@@ -64,8 +75,6 @@
|
|
64
75
|
|
65
76
|
== Timecode.parse should
|
66
77
|
* handle complete SMPTE timecode
|
67
|
-
* handle complete SMPTE timecode via new
|
68
|
-
* refuse to handle timecode that is out of range for the framerate
|
69
78
|
* parse a row of numbers as parts of a timecode starting from the right
|
70
79
|
* parse a number with f suffix as frames
|
71
80
|
* parse a number with s suffix as seconds
|
@@ -78,6 +87,7 @@
|
|
78
87
|
* parse timecode with fractional second instead of frames
|
79
88
|
* raise when trying to parse DF timecode
|
80
89
|
* raise on improper format
|
90
|
+
* raise on empty argument
|
81
91
|
|
82
92
|
== Timecode.soft_parse should
|
83
93
|
* parse the timecode
|
@@ -87,4 +97,4 @@
|
|
87
97
|
* parse from a 4x4bits packed 32bit unsigned int
|
88
98
|
* properly convert itself back to 4x4 bits 32bit unsigned int
|
89
99
|
|
90
|
-
|
100
|
+
66 specifications (109 requirements), 0 failures
|
data/lib/timecode.rb
CHANGED
@@ -12,7 +12,7 @@
|
|
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.8'
|
16
16
|
|
17
17
|
include Comparable
|
18
18
|
|
@@ -78,13 +78,13 @@ class Timecode
|
|
78
78
|
# * 00:00:00:00 - will be parsed as zero TC
|
79
79
|
def parse(input, with_fps = DEFAULT_FPS)
|
80
80
|
# Drop frame goodbye
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
if (input =~ DF_TC_RE)
|
82
|
+
raise Error, "We do not support drop-frame TC"
|
83
|
+
# No empty values
|
84
|
+
elsif (input =~ /A(\s+)Z/)
|
85
|
+
raise CannotParse, "Empty timecode"
|
86
86
|
# 00:00:00:00
|
87
|
-
|
87
|
+
elsif (input =~ COMPLETE_TC_RE)
|
88
88
|
atoms_and_fps = input.scan(COMPLETE_TC_RE).to_a.flatten.map{|e| Integer(e)} + [with_fps]
|
89
89
|
return at(*atoms_and_fps)
|
90
90
|
# 00:00:00.0
|
@@ -92,7 +92,9 @@ class Timecode
|
|
92
92
|
parse_with_fractional_seconds(input, with_fps)
|
93
93
|
# 10h 20m 10s 1f 00:00:00:01 - space separated is a sum of parts
|
94
94
|
elsif input =~ /\s/
|
95
|
-
|
95
|
+
parts = input.gsub(/\s/, ' ').split.reject{|e| e.strip.empty? }
|
96
|
+
raise CannotParse, "No atoms" if parts.empty?
|
97
|
+
parts.map{|part| parse(part, with_fps) }.inject{|sum, p| sum + p.total }
|
96
98
|
# 10s
|
97
99
|
elsif input =~ /^(\d+)s$/
|
98
100
|
return new(input.to_i * with_fps, with_fps)
|
@@ -119,6 +121,13 @@ class Timecode
|
|
119
121
|
|
120
122
|
# Initialize a Timecode object at this specfic timecode
|
121
123
|
def at(hrs, mins, secs, frames, with_fps = DEFAULT_FPS)
|
124
|
+
validate_atoms!(hrs, mins, secs, frames, with_fps)
|
125
|
+
total = (hrs*(60*60*with_fps) + mins*(60*with_fps) + secs*with_fps + frames).round
|
126
|
+
new(total, with_fps)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Validate the passed atoms for the concrete framerate
|
130
|
+
def validate_atoms!(hrs, mins, secs, frames, with_fps)
|
122
131
|
case true
|
123
132
|
when hrs > 99
|
124
133
|
raise RangeError, "There can be no more than 99 hours, got #{hrs}"
|
@@ -129,9 +138,6 @@ class Timecode
|
|
129
138
|
when frames > (with_fps -1)
|
130
139
|
raise RangeError, "There can be no more than #{with_fps -1} frames @#{with_fps}, got #{frames}"
|
131
140
|
end
|
132
|
-
|
133
|
-
total = (hrs*(60*60*with_fps) + mins*(60*with_fps) + secs*with_fps + frames).round
|
134
|
-
new(total, with_fps)
|
135
141
|
end
|
136
142
|
|
137
143
|
# Parse a timecode with fractional seconds instead of frames. This is how ffmpeg reports
|
@@ -217,6 +223,11 @@ class Timecode
|
|
217
223
|
uint
|
218
224
|
end
|
219
225
|
|
226
|
+
# get the timecode as a floating-point number of seconds (used in Quicktime)
|
227
|
+
def to_seconds
|
228
|
+
(@total / @fps)
|
229
|
+
end
|
230
|
+
|
220
231
|
# Convert to different framerate based on the total frames. Therefore,
|
221
232
|
# 1 second of PAL video will convert to 25 frames of NTSC (this
|
222
233
|
# is suitable for PAL to film TC conversions and back).
|
@@ -305,7 +316,7 @@ class Timecode
|
|
305
316
|
|
306
317
|
private
|
307
318
|
|
308
|
-
#
|
319
|
+
# Prepare and format the values for TC output
|
309
320
|
def validate!
|
310
321
|
frames = @total
|
311
322
|
secs = (@total.to_f/@fps).floor
|
@@ -314,12 +325,9 @@ class Timecode
|
|
314
325
|
secs -= (mins*60)
|
315
326
|
hrs = (mins/60).floor
|
316
327
|
mins-= (hrs*60)
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
raise RangeError, "More than 59 seconds" if secs > 59
|
321
|
-
raise RangeError, "More than #{@fps.to_s} frames (#{frames}) in the last second" if frames >= @fps
|
322
|
-
|
328
|
+
|
329
|
+
self.class.validate_atoms!(hrs, mins, secs, frames, @fps)
|
330
|
+
|
323
331
|
[hrs, mins, secs, frames]
|
324
332
|
end
|
325
333
|
|
data/test/test_timecode.rb
CHANGED
@@ -5,7 +5,7 @@ require 'test/spec'
|
|
5
5
|
require File.dirname(__FILE__) + '/../lib/timecode'
|
6
6
|
|
7
7
|
|
8
|
-
context "Timecode.new
|
8
|
+
context "Timecode.new should" do
|
9
9
|
|
10
10
|
specify "instantiate from int" do
|
11
11
|
tc = Timecode.new(10)
|
@@ -25,9 +25,35 @@ context "Timecode.new() should" do
|
|
25
25
|
specify "accept full string SMPTE timecode as well" do
|
26
26
|
Timecode.new("00:25:30:10", 25).should.equal Timecode.parse("00:25:30:10")
|
27
27
|
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context "Timecode.validate_atoms! should" do
|
32
|
+
|
33
|
+
specify "disallow more than 99 hrs" do
|
34
|
+
lambda{ Timecode.validate_atoms!(99,0,0,0, 25) }.should.not.raise
|
35
|
+
lambda{ Timecode.validate_atoms!(100,0,0,0, 25) }.should.raise(Timecode::RangeError)
|
36
|
+
end
|
37
|
+
|
38
|
+
specify "disallow more than 59 minutes" do
|
39
|
+
lambda{ Timecode.validate_atoms!(1,60,0,0, 25) }.should.raise(Timecode::RangeError)
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "disallow more than 59 seconds" do
|
43
|
+
lambda{ Timecode.validate_atoms!(1,0,60,0, 25) }.should.raise(Timecode::RangeError)
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "disallow more frames than what the framerate permits" do
|
47
|
+
lambda{ Timecode.validate_atoms!(1,0,60,25, 25) }.should.raise(Timecode::RangeError)
|
48
|
+
lambda{ Timecode.validate_atoms!(1,0,60,32, 30) }.should.raise(Timecode::RangeError)
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "pass validation with usable values" do
|
52
|
+
lambda{ Timecode.validate_atoms!(20, 20, 10, 5, 25)}.should.not.raise
|
53
|
+
end
|
28
54
|
end
|
29
55
|
|
30
|
-
context "Timecode.at
|
56
|
+
context "Timecode.at should" do
|
31
57
|
|
32
58
|
specify "disallow more than 99 hrs" do
|
33
59
|
lambda{ Timecode.at(99,0,0,0) }.should.not.raise
|
@@ -130,6 +156,18 @@ context "A Timecode of zero should" do
|
|
130
156
|
end
|
131
157
|
end
|
132
158
|
|
159
|
+
context "Timecode#to_seconds should" do
|
160
|
+
specify "return a float" do
|
161
|
+
Timecode.new(0).to_seconds.should.be.kind_of Float
|
162
|
+
end
|
163
|
+
|
164
|
+
specify "return the value in seconds" do
|
165
|
+
fps = 24
|
166
|
+
secs = 126.3
|
167
|
+
Timecode.new(fps * secs, fps).to_seconds.should.be.close 126.3, 0.1
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
133
171
|
context "An existing Timecode on inspection should" do
|
134
172
|
specify "properly present himself via inspect" do
|
135
173
|
Timecode.new(10, 25).inspect.should.equal "#<Timecode:00:00:00:10 (10F@25.00)>"
|
@@ -271,16 +309,6 @@ context "Timecode.parse should" do
|
|
271
309
|
simple_tc = "00:10:34:10"
|
272
310
|
Timecode.parse(simple_tc).to_s.should.equal(simple_tc)
|
273
311
|
end
|
274
|
-
|
275
|
-
specify "handle complete SMPTE timecode via new" do
|
276
|
-
simple_tc = "00:10:34:10"
|
277
|
-
Timecode.new(simple_tc).to_s.should.equal(simple_tc)
|
278
|
-
end
|
279
|
-
|
280
|
-
specify "refuse to handle timecode that is out of range for the framerate" do
|
281
|
-
bad_tc = "00:76:89:30"
|
282
|
-
lambda { Timecode.parse(bad_tc, 25) }.should.raise(Timecode::RangeError)
|
283
|
-
end
|
284
312
|
|
285
313
|
specify "parse a row of numbers as parts of a timecode starting from the right" do
|
286
314
|
Timecode.parse("10").should.equal Timecode.new(10)
|
@@ -354,6 +382,9 @@ context "Timecode.parse should" do
|
|
354
382
|
lambda { Timecode.parse("", 25) }.should.raise Timecode::CannotParse
|
355
383
|
end
|
356
384
|
|
385
|
+
specify "raise on empty argument" do
|
386
|
+
lambda { Timecode.parse(" \n\n ", 25) }.should.raise Timecode::CannotParse
|
387
|
+
end
|
357
388
|
end
|
358
389
|
|
359
390
|
context "Timecode.soft_parse should" do
|
data/timecode.gemspec
CHANGED