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.
Files changed (4) hide show
  1. data/History.txt +7 -1
  2. data/lib/timecode.rb +26 -18
  3. data/test/test_timecode.rb +43 -12
  4. metadata +2 -2
data/History.txt CHANGED
@@ -1,4 +1,10 @@
1
- === 0.1.6 / 2009-01-31
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/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.7'
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
- raise Error, "We do not support drop frame" if (input =~ DF_TC_RE)
82
-
83
- hrs, mins, secs, frames = 0,0,0,0
84
- atoms = []
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
- if (input =~ COMPLETE_TC_RE)
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
- return input.split.map{|part| parse(part, with_fps) }.inject { |sum, p| sum + p.total }
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
- # Formats the actual timecode output from the number of frames
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
- raise RangeError, "Timecode cannot be longer that 99 hrs" if hrs > 99
319
- raise RangeError, "More than 59 minutes" if mins > 59
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
 
@@ -5,7 +5,7 @@ require 'test/spec'
5
5
  require File.dirname(__FILE__) + '/../lib/timecode'
6
6
 
7
7
 
8
- context "Timecode.new() should" do
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() should" do
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
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.7
4
+ version: 0.1.8
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: 2009-01-31 00:00:00 +01:00
12
+ date: 2009-02-04 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency