voot 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/Guardfile +1 -1
- data/README.md +10 -0
- data/lib/voot.rb +12 -0
- data/lib/voot/cue.rb +3 -5
- data/lib/voot/cue_list.rb +7 -1
- data/lib/voot/cue_timing.rb +9 -21
- data/lib/voot/parser.rb +43 -0
- data/lib/voot/timestamp.rb +49 -0
- data/lib/voot/transform.rb +35 -0
- data/lib/voot/version.rb +1 -1
- data/lib/voot/vtt.rb +2 -3
- data/spec/lib/voot/cue_list_spec.rb +26 -2
- data/spec/lib/voot/cue_spec.rb +3 -3
- data/spec/lib/voot/cue_timing_spec.rb +10 -10
- data/spec/lib/voot/parser_spec.rb +82 -0
- data/spec/lib/voot/timestamp_spec.rb +91 -0
- data/spec/lib/voot/transform_spec.rb +72 -0
- data/spec/lib/voot/vtt_spec.rb +2 -2
- data/spec/lib/voot_spec.rb +51 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/cue_helper.rb +7 -1
- data/voot.gemspec +2 -0
- metadata +27 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4663014318069925f156ecc91d0375178add107a
|
4
|
+
data.tar.gz: d462fa6bf6fed6bcbd8b292a46b981c2255bbdb2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 473e633f0c06fd79742ae767d30170f0a4e6bda3ca9ba98c961ecd8b845f6634550e4f4164a42c553a5709658a3fc9ca663d91808056d2a39df0eff1bd02bfb1
|
7
|
+
data.tar.gz: d8e8f6032846b150ba72b99e2300acadc1ec3f24c4be8c0245726d0668165acab2251f1102c4d9f2412302abb15a9f2fca582241fa0604f53ca483283cc1a88c
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -4,6 +4,16 @@
|
|
4
4
|
|
5
5
|
Voot lets you read and write WebVTT files with great ease.
|
6
6
|
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
> vtt = Voot.load("~/Movies/My Indian Films/manty-poothon.vtt")
|
10
|
+
=> #<Voot::Vtt>
|
11
|
+
> vtt.cues.length
|
12
|
+
=> 1
|
13
|
+
> vtt.cues.first.payload
|
14
|
+
=> "You are those knights and whatever, let me marry Srivani or we will dance"
|
15
|
+
|
16
|
+
|
7
17
|
## Installation
|
8
18
|
|
9
19
|
Add this line to your application's Gemfile:
|
data/lib/voot.rb
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
require "voot/version"
|
2
2
|
require "voot/vtt"
|
3
|
+
require "voot/parser"
|
4
|
+
require "voot/transform"
|
3
5
|
|
4
6
|
module Voot
|
7
|
+
def self.load(path)
|
8
|
+
transform = Voot::Transform.new
|
9
|
+
parser = Voot::Parser.new
|
10
|
+
vtt_contents = File.read(path)
|
11
|
+
parse_tree = parser.parse(vtt_contents)
|
12
|
+
|
13
|
+
transform.apply(parse_tree).tap do |vtt|
|
14
|
+
vtt.path = path
|
15
|
+
end
|
16
|
+
end
|
5
17
|
end
|
data/lib/voot/cue.rb
CHANGED
@@ -15,11 +15,9 @@ module Voot
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_webvtt
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
"#{cue_timing.to_webvtt}\n#{payload}"
|
22
|
-
end
|
18
|
+
output = ""
|
19
|
+
output << identifier << "\n" if has_identifier?
|
20
|
+
output << cue_timing.to_webvtt << "\n" << payload
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
data/lib/voot/cue_list.rb
CHANGED
data/lib/voot/cue_timing.rb
CHANGED
@@ -1,32 +1,20 @@
|
|
1
|
+
require "voot/timestamp"
|
2
|
+
|
1
3
|
module Voot
|
2
4
|
class CueTiming
|
3
|
-
attr_reader :
|
5
|
+
attr_reader :start_timestamp, :stop_timestamp
|
4
6
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
7
|
+
def initialize(start_timestamp, stop_timestamp)
|
8
|
+
@start_timestamp = start_timestamp
|
9
|
+
@stop_timestamp = stop_timestamp
|
8
10
|
end
|
9
11
|
|
10
12
|
def to_webvtt
|
11
|
-
"
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def format_timestamp(seconds)
|
17
|
-
"#{format_minutes(seconds)}:#{format_seconds(seconds)}.#{format_subseconds(seconds)}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def format_minutes(seconds)
|
21
|
-
(seconds.to_i / 60).to_s.rjust(2, "0")
|
22
|
-
end
|
23
|
-
|
24
|
-
def format_seconds(seconds)
|
25
|
-
(seconds.to_i % 60).to_s.rjust(2, "0")
|
13
|
+
start_timestamp.to_webvtt << " --> " << stop_timestamp.to_webvtt
|
26
14
|
end
|
27
15
|
|
28
|
-
def
|
29
|
-
|
16
|
+
def cover?(seconds)
|
17
|
+
seconds.between?(start_timestamp.seconds_since_origin, stop_timestamp.seconds_since_origin)
|
30
18
|
end
|
31
19
|
end
|
32
20
|
end
|
data/lib/voot/parser.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "parslet"
|
2
|
+
|
3
|
+
module Voot
|
4
|
+
class Parser < Parslet::Parser
|
5
|
+
rule(:line_break) { match("[\r\n]|\r\n") }
|
6
|
+
rule(:whitespace) { match("[\s\t]") }
|
7
|
+
rule(:section_separator) { line_break.repeat(2) }
|
8
|
+
|
9
|
+
rule(:header_string) { (line_break.absent? >> any).repeat }
|
10
|
+
rule(:header) { str("WEBVTT") >> whitespace.repeat >> header_string.as(:header) }
|
11
|
+
|
12
|
+
rule(:digit) { match("[0-9]") }
|
13
|
+
rule(:colon) { match("[:]") }
|
14
|
+
rule(:period) { match("[.]") }
|
15
|
+
|
16
|
+
rule(:hours) { digit.repeat(2).as(:hours) >> colon }
|
17
|
+
rule(:minutes) { digit.repeat(2, 2).as(:minutes) >> colon }
|
18
|
+
rule(:seconds) { digit.repeat(2, 2).as(:seconds) >> period }
|
19
|
+
rule(:subseconds) { digit.repeat(3, 3).as(:subseconds) }
|
20
|
+
|
21
|
+
rule(:timestamp_without_hours) { minutes >> seconds >> subseconds }
|
22
|
+
rule(:timestamp_with_hours) { hours >> minutes >> seconds >> subseconds }
|
23
|
+
|
24
|
+
rule(:timestamp) { timestamp_without_hours | timestamp_with_hours }
|
25
|
+
|
26
|
+
rule(:timing_delimiter) { str("-->") }
|
27
|
+
rule(:cue_timing) { timestamp.as(:start) >> whitespace.repeat(1) >> timing_delimiter >> whitespace.repeat(1) >> timestamp.as(:stop) }
|
28
|
+
|
29
|
+
rule(:cue_identifier) { (line_break.absent? >> timing_delimiter.absent? >> any).repeat.as(:identifier) }
|
30
|
+
|
31
|
+
rule(:cue_payload) { (section_separator.absent? >> any).repeat.as(:payload) }
|
32
|
+
|
33
|
+
rule(:cue_without_identifier) { cue_timing >> line_break >> cue_payload }
|
34
|
+
rule(:cue_with_identifier) { cue_identifier >> line_break >> cue_timing >> line_break >> cue_payload }
|
35
|
+
|
36
|
+
rule(:cue) { cue_without_identifier | cue_with_identifier }
|
37
|
+
rule(:cue_list) { (section_separator >> cue).repeat }
|
38
|
+
|
39
|
+
rule(:file_body) { header >> cue_list.as(:cues) >> line_break.repeat }
|
40
|
+
|
41
|
+
root(:file_body)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Voot
|
2
|
+
class Timestamp
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_reader :seconds_since_origin
|
6
|
+
|
7
|
+
def initialize(seconds_since_origin)
|
8
|
+
@seconds_since_origin = seconds_since_origin.to_f.round(3)
|
9
|
+
end
|
10
|
+
|
11
|
+
def <=>(other)
|
12
|
+
if other.is_a?(Numeric)
|
13
|
+
seconds_since_origin <=> other
|
14
|
+
else
|
15
|
+
seconds_since_origin <=> other.seconds_since_origin
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def hours
|
20
|
+
seconds_since_origin.to_i / 3600
|
21
|
+
end
|
22
|
+
|
23
|
+
def minutes
|
24
|
+
seconds_without_hours.to_i / 60
|
25
|
+
end
|
26
|
+
|
27
|
+
def seconds
|
28
|
+
seconds_since_origin.to_i % 60
|
29
|
+
end
|
30
|
+
|
31
|
+
def milliseconds
|
32
|
+
milliseconds_since_origin.round % 1000
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_webvtt
|
36
|
+
"%02d:%02d:%02d.%03d" % [hours, minutes, seconds, milliseconds]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def milliseconds_since_origin
|
42
|
+
seconds_since_origin * 1000
|
43
|
+
end
|
44
|
+
|
45
|
+
def seconds_without_hours
|
46
|
+
seconds_since_origin % 3600
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "parslet"
|
2
|
+
|
3
|
+
module Voot
|
4
|
+
class Transform < Parslet::Transform
|
5
|
+
rule(:minutes => simple(:minutes), :seconds => simple(:seconds), :subseconds => simple(:subseconds)) do
|
6
|
+
Voot::Timestamp.new((minutes.to_i * 60) + seconds.to_i + (subseconds.to_i / 1000.0))
|
7
|
+
end
|
8
|
+
|
9
|
+
rule(:hours => simple(:hours), :minutes => simple(:minutes), :seconds => simple(:seconds), :subseconds => simple(:subseconds)) do
|
10
|
+
Voot::Timestamp.new((hours.to_i * 3600) + (minutes.to_i * 60) + seconds.to_i + (subseconds.to_i / 1000.0))
|
11
|
+
end
|
12
|
+
|
13
|
+
rule(:start => simple(:start_timestamp), :stop => simple(:stop_timestamp), :payload => simple(:payload)) do
|
14
|
+
cue_timing = Voot::CueTiming.new(start_timestamp, stop_timestamp)
|
15
|
+
Voot::Cue.new(cue_timing: cue_timing, payload: payload.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
rule(:start => simple(:start_timestamp), :stop => simple(:stop_timestamp), :payload => simple(:payload), :identifier => simple(:identifier)) do
|
19
|
+
cue_timing = Voot::CueTiming.new(start_timestamp, stop_timestamp)
|
20
|
+
Voot::Cue.new(cue_timing: cue_timing, payload: payload.to_s, identifier: identifier.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
rule(:header => sequence(:header), :cues => sequence(:cues)) do
|
24
|
+
vtt = Voot::Vtt.new
|
25
|
+
cues.each { |cue| vtt.cues << cue }
|
26
|
+
vtt
|
27
|
+
end
|
28
|
+
|
29
|
+
rule(:header => simple(:header), :cues => sequence(:cues)) do
|
30
|
+
vtt = Voot::Vtt.new(header: header)
|
31
|
+
cues.each { |cue| vtt.cues << cue }
|
32
|
+
vtt
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/voot/version.rb
CHANGED
data/lib/voot/vtt.rb
CHANGED
@@ -2,11 +2,10 @@ require "voot/cue_list"
|
|
2
2
|
|
3
3
|
module Voot
|
4
4
|
class Vtt
|
5
|
-
|
6
|
-
attr_accessor :header
|
5
|
+
attr_accessor :header, :path
|
7
6
|
|
8
7
|
def initialize(options = {})
|
9
|
-
@path = options
|
8
|
+
@path = options[:path]
|
10
9
|
@header = options[:header]
|
11
10
|
end
|
12
11
|
|
@@ -11,12 +11,36 @@ describe Voot::CueList do
|
|
11
11
|
context "when the list has an item in it" do
|
12
12
|
before { cue_list << create_cue("reasonably priced", 0, 0) }
|
13
13
|
|
14
|
-
its(:to_webvtt) { should == "00:00.000 --> 00:00.000\nreasonably priced" }
|
14
|
+
its(:to_webvtt) { should == "00:00:00.000 --> 00:00:00.000\nreasonably priced" }
|
15
15
|
|
16
16
|
context "when the list has multiple items" do
|
17
17
|
before { cue_list << create_cue("toiletries", 1, 1) }
|
18
18
|
|
19
|
-
its(:to_webvtt) { should include "00:01.000 --> 00:01.000\ntoiletries" }
|
19
|
+
its(:to_webvtt) { should include "00:00:01.000 --> 00:00:01.000\ntoiletries" }
|
20
|
+
|
21
|
+
it "separates cues with a double newline" do
|
22
|
+
cue_list.to_webvtt.should == <<-ALL_THE_CUES.strip
|
23
|
+
00:00:00.000 --> 00:00:00.000
|
24
|
+
reasonably priced
|
25
|
+
|
26
|
+
00:00:01.000 --> 00:00:01.000
|
27
|
+
toiletries
|
28
|
+
ALL_THE_CUES
|
29
|
+
end
|
30
|
+
|
31
|
+
it "orders the cues by start time" do
|
32
|
+
cue_list << create_cue("hair spray", 0.5, 1)
|
33
|
+
cue_list.to_webvtt.should == <<-ALL_THE_CUES.strip
|
34
|
+
00:00:00.000 --> 00:00:00.000
|
35
|
+
reasonably priced
|
36
|
+
|
37
|
+
00:00:00.500 --> 00:00:01.000
|
38
|
+
hair spray
|
39
|
+
|
40
|
+
00:00:01.000 --> 00:00:01.000
|
41
|
+
toiletries
|
42
|
+
ALL_THE_CUES
|
43
|
+
end
|
20
44
|
end
|
21
45
|
end
|
22
46
|
end
|
data/spec/lib/voot/cue_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require "spec_helper"
|
|
3
3
|
describe Voot::Cue do
|
4
4
|
let(:identifier) { "taco massacre" }
|
5
5
|
let(:payload) { "tasty" }
|
6
|
-
let(:cue_timing) {
|
6
|
+
let(:cue_timing) { create_timing(0, 1) }
|
7
7
|
|
8
8
|
subject(:cue) do
|
9
9
|
Voot::Cue.new(
|
@@ -33,13 +33,13 @@ describe Voot::Cue do
|
|
33
33
|
|
34
34
|
describe "#to_webvtt" do
|
35
35
|
context "when the identifier is set" do
|
36
|
-
its(:to_webvtt) { should == "taco massacre\n00:00.000 --> 00:01.000\ntasty" }
|
36
|
+
its(:to_webvtt) { should == "taco massacre\n00:00:00.000 --> 00:00:01.000\ntasty" }
|
37
37
|
end
|
38
38
|
|
39
39
|
context "when the identifier is not set" do
|
40
40
|
let(:identifier) { nil }
|
41
41
|
|
42
|
-
its(:to_webvtt) { should == "00:00.000 --> 00:01.000\ntasty" }
|
42
|
+
its(:to_webvtt) { should == "00:00:00.000 --> 00:00:01.000\ntasty" }
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Voot::CueTiming do
|
4
|
-
let(:
|
5
|
-
let(:
|
6
|
-
|
7
|
-
subject(:cue_timing)
|
8
|
-
Voot::CueTiming.new(
|
9
|
-
start_seconds: start_seconds,
|
10
|
-
end_seconds: end_seconds
|
11
|
-
)
|
12
|
-
end
|
4
|
+
let(:start_timestamp) { Voot::Timestamp.new(1.0001) }
|
5
|
+
let(:stop_timestamp) { Voot::Timestamp.new(65.0009) }
|
6
|
+
|
7
|
+
subject(:cue_timing) { Voot::CueTiming.new(start_timestamp, stop_timestamp) }
|
13
8
|
|
14
|
-
its(:to_webvtt) { should == "00:01.000 --> 01:05.001" }
|
9
|
+
its(:to_webvtt) { should == "00:00:01.000 --> 00:01:05.001" }
|
10
|
+
|
11
|
+
describe "#cover?" do
|
12
|
+
it { should cover 1.000 }
|
13
|
+
it { should cover 65.001 }
|
14
|
+
end
|
15
15
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Voot::Parser do
|
4
|
+
subject(:parser) { Voot::Parser.new }
|
5
|
+
|
6
|
+
it { should parse("WEBVTT") }
|
7
|
+
it { should parse("WEBVTT\n\n") }
|
8
|
+
|
9
|
+
describe "#header" do
|
10
|
+
specify { parser.header.should parse("WEBVTT").as(header: []) }
|
11
|
+
specify { parser.header.should parse("WEBVTT - goose hugs").as(header: "- goose hugs") }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#subseconds" do
|
15
|
+
specify { parser.subseconds.should parse("164").as(subseconds: "164") }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#seconds" do
|
19
|
+
specify { parser.seconds.should parse("66.").as(seconds: "66") }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#minutes" do
|
23
|
+
specify { parser.minutes.should parse("01:").as(minutes: "01") }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#hours" do
|
27
|
+
specify { parser.hours.should parse("13:").as(hours: "13") }
|
28
|
+
specify { parser.hours.should parse("9000:").as(hours: "9000") }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#timestamp_without_hours" do
|
32
|
+
specify { parser.timestamp_without_hours.should parse("01:33.007").as(minutes: "01", seconds: "33", subseconds: "007") }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#timestamp_with_hours" do
|
36
|
+
specify { parser.timestamp_with_hours.should parse("11:59:59.999").as(hours: "11", minutes: "59", seconds: "59", subseconds: "999") }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#timestamp" do
|
40
|
+
specify { parser.timestamp.should parse("01:33.007").as(minutes: "01", seconds: "33", subseconds: "007") }
|
41
|
+
specify { parser.timestamp.should parse("11:59:59.999").as(hours: "11", minutes: "59", seconds: "59", subseconds: "999") }
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#timing_delimiter" do
|
45
|
+
specify { parser.timing_delimiter.should parse("-->") }
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#cue_timing" do
|
49
|
+
specify { parser.cue_timing.should parse("01:33.007 -->\t 11:59:59.999").as(start: anything, stop: anything) }
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#cue_identifier" do
|
53
|
+
specify { parser.cue_identifier.should_not parse("drugboats --> ahoy") }
|
54
|
+
specify { parser.cue_identifier.should_not parse("puppet\npals") }
|
55
|
+
specify { parser.cue_identifier.should parse("evil squid cult").as(identifier: "evil squid cult") }
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#cue_payload" do
|
59
|
+
specify { parser.cue_payload.should parse("once upon a dingus").as(payload: "once upon a dingus") }
|
60
|
+
specify { parser.cue_payload.should parse("there were\nan prancess").as(payload: "there were\nan prancess") }
|
61
|
+
specify { parser.cue_payload.should_not parse("whare is\n\nlazy prance") }
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#cue_without_identifier" do
|
65
|
+
specify { parser.cue_without_identifier.should parse("00:00.000 --> 00:30.000\nget to the choppa?").as(start: anything, stop: anything, payload: anything) }
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#cue_with_identifier" do
|
69
|
+
specify { parser.cue_with_identifier.should parse("777\n00:00.000 --> 00:30.000\njhonny paints the wall").as(identifier: anything, start: anything, stop: anything, payload: anything) }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#cue" do
|
73
|
+
specify { parser.cue.should parse("the hablet\n00:00.000 --> 00:30.000\nwelcam ashire\nbalbo").as(identifier: anything, start: anything, stop: anything, payload: anything) }
|
74
|
+
specify { parser.cue.should parse("04:00.000 --> 55:30.000\n(tom belmadal sings)").as(start: anything, stop: anything, payload: anything) }
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#file_body" do
|
78
|
+
specify { parser.file_body.should parse("WEBVTT").as(header: [], cues: []) }
|
79
|
+
specify { parser.file_body.should parse("WEBVTT\n\n00:00.000 --> 00:00.000\nhi").as(header: [], cues: [anything]) }
|
80
|
+
specify { parser.file_body.should parse("WEBVTT\n\n00:00.000 --> 00:00.000\nlike\n\n00:01.000 --> 00:01.000\nwut").as(header: [], cues: [anything, anything]) }
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Voot::Timestamp do
|
4
|
+
subject(:timestamp) { Voot::Timestamp.new(seconds) }
|
5
|
+
|
6
|
+
describe "#<=>" do
|
7
|
+
let(:seconds) { 1.1 }
|
8
|
+
|
9
|
+
context "when comparing against the number of seconds" do
|
10
|
+
it { should == seconds }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when comparing against another timestamp" do
|
14
|
+
let(:other) { double(:timestamp, seconds_since_origin: seconds) }
|
15
|
+
|
16
|
+
it { should == other }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#seconds_since_origin" do
|
21
|
+
context "when the seconds can round up" do
|
22
|
+
let(:seconds) { 0.0005 }
|
23
|
+
|
24
|
+
it "rounds the seconds to the nearest millisecond" do
|
25
|
+
timestamp.seconds_since_origin.should == 0.001
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when the seconds can round up" do
|
30
|
+
let(:seconds) { 0.0014 }
|
31
|
+
|
32
|
+
it "rounds the seconds to the nearest millisecond" do
|
33
|
+
timestamp.seconds_since_origin.should == 0.001
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#hours" do
|
39
|
+
let(:seconds) { 3600 }
|
40
|
+
|
41
|
+
it "returns the number of hours in the timestamp" do
|
42
|
+
timestamp.hours.should == 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#minutes" do
|
47
|
+
let(:seconds) { 120 }
|
48
|
+
|
49
|
+
it "returns the number of minutes in the timestamp" do
|
50
|
+
timestamp.minutes.should == 2
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#seconds" do
|
55
|
+
let(:seconds) { 3 }
|
56
|
+
|
57
|
+
it "returns the number of seconds in the timestamp" do
|
58
|
+
timestamp.seconds.should == 3
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#milliseconds" do
|
63
|
+
let(:seconds) { 0.004 }
|
64
|
+
|
65
|
+
it "returns the number of milliseconds in the timestamp" do
|
66
|
+
timestamp.milliseconds.should == 4
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when milliseconds can round up" do
|
70
|
+
let(:seconds) { 0.0045 }
|
71
|
+
|
72
|
+
it "returns the rounded-up number of milliseconds in the timestamp" do
|
73
|
+
timestamp.milliseconds.should == 5
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when milliseconds can round down" do
|
78
|
+
let(:seconds) { 0.0031 }
|
79
|
+
|
80
|
+
it "returns the rounded-down number of milliseconds in the timestamp" do
|
81
|
+
timestamp.milliseconds.should == 3
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#to_webvtt" do
|
87
|
+
let(:seconds) { 3723.004 }
|
88
|
+
|
89
|
+
its(:to_webvtt) { should == "01:02:03.004" }
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Voot::Transform do
|
4
|
+
let(:cue_timing) { create_timing(61.001, 3661.001) }
|
5
|
+
let(:parser) { Voot::Parser.new }
|
6
|
+
|
7
|
+
subject(:transformed) { Voot::Transform.new.apply(parsed_input) }
|
8
|
+
|
9
|
+
describe "#timestamp" do
|
10
|
+
let(:parsed_input) { parser.timestamp.parse(input_string) }
|
11
|
+
|
12
|
+
context "when the timestamp does not have hours" do
|
13
|
+
let(:input_string) { "01:01.001" }
|
14
|
+
|
15
|
+
it { should == 61.001 }
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when the timestamp has hours" do
|
19
|
+
let(:input_string) { "01:01:01.001" }
|
20
|
+
|
21
|
+
it { should == 3661.001 }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#cue_timing" do
|
26
|
+
let(:input_string) { cue_timing.to_webvtt }
|
27
|
+
let(:parsed_input) { parser.cue_timing.parse(input_string) }
|
28
|
+
|
29
|
+
it { should == {start: 61.001, stop: 3661.001} }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#cue" do
|
33
|
+
let(:parsed_input) { parser.cue.parse(cue.to_webvtt) }
|
34
|
+
|
35
|
+
context "when there is an identifier" do
|
36
|
+
let(:cue) { Voot::Cue.new(cue_timing: cue_timing, identifier: "ferdo", payload: "hes also too an habblet") }
|
37
|
+
|
38
|
+
its(:identifier) { should == "ferdo" }
|
39
|
+
its(:payload) { should == "hes also too an habblet" }
|
40
|
+
its(:cue_timing) { should cover 61.001 }
|
41
|
+
its(:cue_timing) { should cover 3661.001 }
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when there is no identifier" do
|
45
|
+
let(:cue) { Voot::Cue.new(cue_timing: cue_timing, payload: "gordolf he is a beard") }
|
46
|
+
|
47
|
+
its(:identifier) { should be_nil }
|
48
|
+
its(:payload) { should == "gordolf he is a beard" }
|
49
|
+
its(:cue_timing) { should cover 61.001 }
|
50
|
+
its(:cue_timing) { should cover 3661.001 }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#file_body" do
|
55
|
+
let(:vtt) { Voot::Vtt.new.tap { |vtt| vtt.cues << cue } }
|
56
|
+
let(:cue) { Voot::Cue.new(cue_timing: cue_timing, payload: "borgomar is a dumb") }
|
57
|
+
let(:parsed_input) { parser.file_body.parse(vtt.to_webvtt) }
|
58
|
+
|
59
|
+
context "when the header is not set" do
|
60
|
+
|
61
|
+
its(:header) { should be_nil }
|
62
|
+
its(:cues) { should have(1).cue }
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when the header is set" do
|
66
|
+
before { vtt.header = "lerd of rang" }
|
67
|
+
|
68
|
+
its(:header) { should == "lerd of rang" }
|
69
|
+
its(:cues) { should have(1).cue }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/lib/voot/vtt_spec.rb
CHANGED
@@ -19,7 +19,7 @@ describe Voot::Vtt do
|
|
19
19
|
context "when the file exists" do
|
20
20
|
let(:pesky_user_data) { "hey guys tacos are great right" }
|
21
21
|
|
22
|
-
before { File.
|
22
|
+
before { File.open(vtt_path, "w") { |file| file << pesky_user_data } }
|
23
23
|
|
24
24
|
it "clobbers the file content" do
|
25
25
|
expect do
|
@@ -65,7 +65,7 @@ describe Voot::Vtt do
|
|
65
65
|
context "when there is a cue" do
|
66
66
|
before { vtt.cues << create_cue("not the drill!", 0, 2) }
|
67
67
|
|
68
|
-
its(:to_webvtt) { should == "WEBVTT\n\n00:00.000 --> 00:02.000\nnot the drill!" }
|
68
|
+
its(:to_webvtt) { should == "WEBVTT\n\n00:00:00.000 --> 00:00:02.000\nnot the drill!" }
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Voot do
|
4
|
+
let(:voot_path) { Tempfile.new("voot").path }
|
5
|
+
|
6
|
+
describe ".load" do
|
7
|
+
subject { Voot.load(voot_path) }
|
8
|
+
|
9
|
+
context "when the file is empty" do
|
10
|
+
it "fails to parse" do
|
11
|
+
expect { Voot.load(voot_path) }.to raise_error(Parslet::ParseFailed)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when the file does not contain a webvtt header" do
|
16
|
+
before { File.open(voot_path, "w") { |file| file << "taco thursdays yo" } }
|
17
|
+
|
18
|
+
it "fails to parse" do
|
19
|
+
expect { Voot.load(voot_path) }.to raise_error(Parslet::ParseFailed)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when the file contains a webvtt header" do
|
24
|
+
before { File.open(voot_path, "w") { |file| file << "WEBVTT" } }
|
25
|
+
|
26
|
+
its(:cues) { should be_empty }
|
27
|
+
its(:header) { should be_nil }
|
28
|
+
its(:path) { should == voot_path }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when the file contains a header" do
|
32
|
+
before { File.open(voot_path, "w") { |file| file << "WEBVTT presents bob ross classics" } }
|
33
|
+
|
34
|
+
its(:header) { should == "presents bob ross classics" }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when the file contains a junk-filled header" do
|
38
|
+
before { File.open(voot_path, "w") { |file| file << "WEBVTT\ni liek ppokemans" } }
|
39
|
+
|
40
|
+
it "fails to parse" do
|
41
|
+
expect { Voot.load(voot_path) }.to raise_error(Parslet::ParseFailed)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the file contains cues" do
|
46
|
+
before { File.open(voot_path, "w") { |file| file << "WEBVTT\n\n00:00.000 --> 00:00.000\ndrugboat" } }
|
47
|
+
|
48
|
+
it { should have(1).cues }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/cue_helper.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
module CueHelper
|
2
|
+
def create_timing(start, stop)
|
3
|
+
start_timestamp = Voot::Timestamp.new(start)
|
4
|
+
stop_timestamp = Voot::Timestamp.new(stop)
|
5
|
+
Voot::CueTiming.new(start_timestamp, stop_timestamp)
|
6
|
+
end
|
7
|
+
|
2
8
|
def create_cue(payload, start, stop)
|
3
|
-
cue_timing =
|
9
|
+
cue_timing = create_timing(start, stop)
|
4
10
|
Voot::Cue.new(cue_timing: cue_timing, payload: payload)
|
5
11
|
end
|
6
12
|
end
|
data/voot.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = `git ls-files -- spec`.split("\n")
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "parslet"
|
22
|
+
|
21
23
|
spec.add_development_dependency "rspec"
|
22
24
|
spec.add_development_dependency "guard-rspec"
|
23
25
|
spec.add_development_dependency "guard-bundler"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: voot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Doc Ritezel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parslet
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,13 +101,20 @@ files:
|
|
87
101
|
- lib/voot/cue.rb
|
88
102
|
- lib/voot/cue_list.rb
|
89
103
|
- lib/voot/cue_timing.rb
|
104
|
+
- lib/voot/parser.rb
|
105
|
+
- lib/voot/timestamp.rb
|
106
|
+
- lib/voot/transform.rb
|
90
107
|
- lib/voot/version.rb
|
91
108
|
- lib/voot/vtt.rb
|
92
109
|
- script/ci.sh
|
93
110
|
- spec/lib/voot/cue_list_spec.rb
|
94
111
|
- spec/lib/voot/cue_spec.rb
|
95
112
|
- spec/lib/voot/cue_timing_spec.rb
|
113
|
+
- spec/lib/voot/parser_spec.rb
|
114
|
+
- spec/lib/voot/timestamp_spec.rb
|
115
|
+
- spec/lib/voot/transform_spec.rb
|
96
116
|
- spec/lib/voot/vtt_spec.rb
|
117
|
+
- spec/lib/voot_spec.rb
|
97
118
|
- spec/spec_helper.rb
|
98
119
|
- spec/support/cue_helper.rb
|
99
120
|
- voot.gemspec
|
@@ -125,6 +146,10 @@ test_files:
|
|
125
146
|
- spec/lib/voot/cue_list_spec.rb
|
126
147
|
- spec/lib/voot/cue_spec.rb
|
127
148
|
- spec/lib/voot/cue_timing_spec.rb
|
149
|
+
- spec/lib/voot/parser_spec.rb
|
150
|
+
- spec/lib/voot/timestamp_spec.rb
|
151
|
+
- spec/lib/voot/transform_spec.rb
|
128
152
|
- spec/lib/voot/vtt_spec.rb
|
153
|
+
- spec/lib/voot_spec.rb
|
129
154
|
- spec/spec_helper.rb
|
130
155
|
- spec/support/cue_helper.rb
|