julik-timecode 0.1.7 → 0.1.8
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 +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