webvtt-ruby 0.3.0 → 0.4.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 +5 -5
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/lib/{parser.rb → webvtt/parser.rb} +61 -29
- data/lib/{segmenter.rb → webvtt/segmenter.rb} +22 -1
- data/lib/webvtt.rb +2 -2
- data/tests/parser.rb +58 -2
- data/tests/segmenter.rb +1 -1
- data/tests/subtitles/invalid_cue.srt +5 -0
- data/tests/subtitles/invalid_cue.vtt +6 -0
- data/tests/subtitles/test_carriage_returns.vtt +1 -0
- data/tests/subtitles/test_from_srt.vtt +1 -1
- data/tests/subtitles/test_mmss_format.vtt +9 -0
- data/tests/subtitles/test_multiple_line_separators.vtt +15 -0
- data/tests/subtitles/weird_format.srt +15 -0
- data/tests/subtitles/weird_format.vtt +17 -0
- data/tests/subtitles/weird_format_corrected.vtt +17 -0
- data/webvtt-ruby.gemspec +1 -1
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f4334c8feffaab4ed2438a52554ce17ebd2bd0152622adbd8445c7de99e6e14d
|
4
|
+
data.tar.gz: 6de8052ac1d531bb77630c086425c3f2640295d510cd87546d357a2791d06695
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cba26291e3f92b889ff2d03246873d44f07c12890dec24201bd4e13e61b286a0a2af4c22e207c37ff976d3056cd38381bd470be9894bf3ebccd3043fdfac0ab
|
7
|
+
data.tar.gz: 7350a1b409e9178eea25526bb0fb2d7167b7348fb4fc19bc96a3edcf353f6aa4ae3c86f3ea31ac988925604da6def8ce621f636d73b66dd02656c0d945fb13a7
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -116,6 +116,6 @@ Usage: bin/webvtt-segmenter [--arg]
|
|
116
116
|
|
117
117
|
**Bruno Celeste**
|
118
118
|
|
119
|
-
* http://
|
120
|
-
* bruno@
|
121
|
-
* [@
|
119
|
+
* http://coconut.co
|
120
|
+
* bruno@coconut.co
|
121
|
+
* [@brunoceleste](http://twitter.com/brunoceleste)
|
@@ -4,14 +4,20 @@ module WebVTT
|
|
4
4
|
File.new(file)
|
5
5
|
end
|
6
6
|
|
7
|
+
def self.from_blob(content)
|
8
|
+
Blob.new(content)
|
9
|
+
end
|
10
|
+
|
7
11
|
def self.convert_from_srt(srt_file, output=nil)
|
8
|
-
if !::File.
|
12
|
+
if !::File.exist?(srt_file)
|
9
13
|
raise InputError, "SRT file not found"
|
10
14
|
end
|
11
15
|
|
12
16
|
srt = ::File.read(srt_file)
|
13
17
|
output ||= srt_file.gsub(".srt", ".vtt")
|
14
18
|
|
19
|
+
# normalize timestamps in srt
|
20
|
+
srt.gsub!(/(:|^)(\d)(,|:)/, '\10\2\3')
|
15
21
|
# convert timestamps and save the file
|
16
22
|
srt.gsub!(/([0-9]{2}:[0-9]{2}:[0-9]{2})([,])([0-9]{3})/, '\1.\3')
|
17
23
|
# normalize new line character
|
@@ -23,19 +29,20 @@ module WebVTT
|
|
23
29
|
return File.new(output)
|
24
30
|
end
|
25
31
|
|
26
|
-
class
|
27
|
-
attr_reader :header
|
32
|
+
class Blob
|
33
|
+
attr_reader :header
|
28
34
|
attr_accessor :cues
|
29
35
|
|
30
|
-
def initialize(
|
31
|
-
|
32
|
-
raise InputError, "WebVTT file not found"
|
33
|
-
end
|
36
|
+
def initialize(content = nil)
|
37
|
+
@cues = []
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
if content
|
40
|
+
parse(
|
41
|
+
content.gsub("\r\n", "\n").gsub("\r","\n") # normalizing new line character
|
42
|
+
)
|
43
|
+
else
|
44
|
+
@header = 'WEBVTT'
|
45
|
+
end
|
39
46
|
end
|
40
47
|
|
41
48
|
def to_webvtt
|
@@ -50,20 +57,12 @@ module WebVTT
|
|
50
57
|
@cues.last.end_in_sec - @cues.first.start_in_sec
|
51
58
|
end
|
52
59
|
|
53
|
-
def
|
54
|
-
output ||= @path.gsub(".srt", ".vtt")
|
55
|
-
|
56
|
-
::File.open(output, "w") do |f|
|
57
|
-
f.write(to_webvtt)
|
58
|
-
end
|
59
|
-
return output
|
60
|
-
end
|
61
|
-
|
62
|
-
def parse
|
60
|
+
def parse(content)
|
63
61
|
# remove bom first
|
64
|
-
|
62
|
+
content.gsub!("\uFEFF", '')
|
63
|
+
|
64
|
+
cues = content.split(/\n\n+/)
|
65
65
|
|
66
|
-
cues = @content.split("\n\n")
|
67
66
|
@header = cues.shift
|
68
67
|
header_lines = @header.split("\n").map(&:strip)
|
69
68
|
if (header_lines[0] =~ /^WEBVTT/).nil?
|
@@ -72,7 +71,7 @@ module WebVTT
|
|
72
71
|
|
73
72
|
@cues = []
|
74
73
|
cues.each do |cue|
|
75
|
-
cue_parsed = Cue.
|
74
|
+
cue_parsed = Cue.parse(cue.strip)
|
76
75
|
if !cue_parsed.text.nil?
|
77
76
|
@cues << cue_parsed
|
78
77
|
end
|
@@ -81,12 +80,41 @@ module WebVTT
|
|
81
80
|
end
|
82
81
|
end
|
83
82
|
|
83
|
+
class File < Blob
|
84
|
+
attr_reader :path, :filename
|
85
|
+
|
86
|
+
def initialize(webvtt_file)
|
87
|
+
if !::File.exist?(webvtt_file)
|
88
|
+
raise InputError, "WebVTT file not found"
|
89
|
+
end
|
90
|
+
|
91
|
+
@path = webvtt_file
|
92
|
+
@filename = ::File.basename(@path)
|
93
|
+
super(::File.read(webvtt_file))
|
94
|
+
end
|
95
|
+
|
96
|
+
def save(output=nil)
|
97
|
+
output ||= @path.gsub(".srt", ".vtt")
|
98
|
+
|
99
|
+
::File.open(output, "w") do |f|
|
100
|
+
f.write(to_webvtt)
|
101
|
+
end
|
102
|
+
return output
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
84
106
|
class Cue
|
85
107
|
attr_accessor :identifier, :start, :end, :style, :text
|
86
108
|
|
87
|
-
def initialize(cue)
|
109
|
+
def initialize(cue = nil)
|
88
110
|
@content = cue
|
89
|
-
|
111
|
+
@style = {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.parse(cue)
|
115
|
+
cue = Cue.new(cue)
|
116
|
+
cue.parse
|
117
|
+
return cue
|
90
118
|
end
|
91
119
|
|
92
120
|
def to_webvtt
|
@@ -136,10 +164,14 @@ module WebVTT
|
|
136
164
|
lines.shift
|
137
165
|
end
|
138
166
|
|
139
|
-
if lines
|
167
|
+
if lines.empty?
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
if lines[0].match(/(([0-9]{2}:)?[0-9]{2}:[0-9]{2}\.[0-9]{3}) -+> (([0-9]{2}:)?[0-9]{2}:[0-9]{2}\.[0-9]{3})(.*)/)
|
140
172
|
@start = Timestamp.new $1
|
141
|
-
@end = Timestamp.new $
|
142
|
-
@style = Hash[$
|
173
|
+
@end = Timestamp.new $3
|
174
|
+
@style = Hash[$5.strip.split(" ").map{|s| s.split(":").map(&:strip) }]
|
143
175
|
end
|
144
176
|
@text = lines[1..-1].join("\n")
|
145
177
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module WebVTT
|
2
4
|
|
3
5
|
def self.segment(input, options={})
|
@@ -25,6 +27,25 @@ module WebVTT
|
|
25
27
|
@options[:length] ||= 10
|
26
28
|
@options[:output] ||= "fileSequence-%05d.vtt"
|
27
29
|
@options[:playlist] ||= "prog_index.m3u8"
|
30
|
+
|
31
|
+
# a dirty hack to check if output and playlist are ending with / (indicating a directory) and if so use the default filename and playlist name and create directory which is required.
|
32
|
+
|
33
|
+
if @options[:output].end_with?('/')
|
34
|
+
@options[:output] = "#{@options[:output]}fileSequence-%05d.vtt"
|
35
|
+
end
|
36
|
+
if @options[:playlist].end_with?('/')
|
37
|
+
@options[:playlist] = "#{@options[:playlist]}prog_index.m3u8"
|
38
|
+
end
|
39
|
+
|
40
|
+
output_dirname = ::File.dirname(@options[:output])
|
41
|
+
playlist_dirname = ::File.dirname(@options[:playlist])
|
42
|
+
|
43
|
+
unless ::File.directory?(output_dirname)
|
44
|
+
::FileUtils.mkdir_p(output_dirname)
|
45
|
+
end
|
46
|
+
unless ::File.directory?(playlist_dirname)
|
47
|
+
::FileUtils.mkdir_p(playlist_dirname)
|
48
|
+
end
|
28
49
|
end
|
29
50
|
|
30
51
|
def find_segment_files(cue)
|
@@ -123,4 +144,4 @@ module WebVTT
|
|
123
144
|
return filenames.map{|f| File.new(f) }
|
124
145
|
end
|
125
146
|
end
|
126
|
-
end
|
147
|
+
end
|
data/lib/webvtt.rb
CHANGED
data/tests/parser.rb
CHANGED
@@ -20,6 +20,12 @@ class ParserTest < Minitest::Test
|
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
+
def test_can_create_empty_webvtt
|
24
|
+
webvtt = WebVTT::Blob.new
|
25
|
+
assert_equal 'WEBVTT', webvtt.header
|
26
|
+
assert_equal [], webvtt.cues
|
27
|
+
end
|
28
|
+
|
23
29
|
def test_list_cues
|
24
30
|
webvtt = WebVTT.read("tests/subtitles/test.vtt")
|
25
31
|
assert_instance_of Array, webvtt.cues
|
@@ -43,6 +49,16 @@ class ParserTest < Minitest::Test
|
|
43
49
|
assert_equal "English subtitle 15 -Forced- (00:00:27.000)\nline:75%", cue.text
|
44
50
|
end
|
45
51
|
|
52
|
+
def test_cue_non_hours
|
53
|
+
webvtt = WebVTT.read("tests/subtitles/test_mmss_format.vtt")
|
54
|
+
cue = webvtt.cues[0]
|
55
|
+
assert_equal "00:00:29.000", cue.start.to_s
|
56
|
+
assert_equal "00:00:31.000", cue.end.to_s
|
57
|
+
assert_instance_of Hash, cue.style
|
58
|
+
assert_equal "75%", cue.style["line"]
|
59
|
+
assert_equal "English subtitle 15 -Forced- (00:00:27.000)\nline:75%", cue.text
|
60
|
+
end
|
61
|
+
|
46
62
|
def test_cue_identifier
|
47
63
|
webvtt = WebVTT.read("tests/subtitles/test.vtt")
|
48
64
|
cue = webvtt.cues[1]
|
@@ -54,6 +70,11 @@ class ParserTest < Minitest::Test
|
|
54
70
|
assert_equal "English subtitle 16 -Unforced- (00:00:31.000)\nalign:start line:0%", cue.text
|
55
71
|
end
|
56
72
|
|
73
|
+
def test_multiple_line_separators
|
74
|
+
webvtt = WebVTT.read("tests/subtitles/test_multiple_line_separators.vtt")
|
75
|
+
assert_equal 2, webvtt.cues.length
|
76
|
+
end
|
77
|
+
|
57
78
|
def test_ignore_if_note
|
58
79
|
webvtt = WebVTT.read("tests/subtitles/withnote.vtt")
|
59
80
|
assert_equal 3, webvtt.cues.size
|
@@ -129,6 +150,17 @@ The text should change)
|
|
129
150
|
assert_equal 2, webvtt.cues.size
|
130
151
|
end
|
131
152
|
|
153
|
+
def test_convert_weird_format_srt_to_webvtt
|
154
|
+
webvtt = WebVTT.convert_from_srt("tests/subtitles/weird_format.srt")
|
155
|
+
correct_webvtt = WebVTT.read("tests/subtitles/weird_format_corrected.vtt")
|
156
|
+
|
157
|
+
converted_cues = webvtt.cues.map { |cue| cue.start.to_s }
|
158
|
+
correct_cues = correct_webvtt.cues.map { |cue| cue.start.to_s }
|
159
|
+
|
160
|
+
assert_instance_of WebVTT::File, webvtt
|
161
|
+
assert_equal correct_cues, converted_cues
|
162
|
+
end
|
163
|
+
|
132
164
|
def test_parse_big_file
|
133
165
|
return
|
134
166
|
webvtt = WebVTT.read("tests/subtitles/big_srt.vtt")
|
@@ -154,9 +186,9 @@ The text should change)
|
|
154
186
|
assert_equal "00:09:02.373", webvtt.cues[1].end.to_s
|
155
187
|
assert_equal "", webvtt.cues[1].text
|
156
188
|
end
|
157
|
-
|
189
|
+
|
158
190
|
def test_cue_offset_by
|
159
|
-
cue = WebVTT::Cue.
|
191
|
+
cue = WebVTT::Cue.parse <<-CUE
|
160
192
|
00:00:01.000 --> 00:00:25.432
|
161
193
|
Test Cue
|
162
194
|
CUE
|
@@ -195,4 +227,28 @@ The text should change)
|
|
195
227
|
assert_equal "03:39:34.008", ts3.to_s
|
196
228
|
end
|
197
229
|
|
230
|
+
def test_build_cue
|
231
|
+
cue = WebVTT::Cue.new
|
232
|
+
cue.start = WebVTT::Timestamp.new 0
|
233
|
+
cue.end = WebVTT::Timestamp.new 12
|
234
|
+
cue.text = "Built from scratch"
|
235
|
+
output = ""
|
236
|
+
output << "00:00:00.000 --> 00:00:12.000\n"
|
237
|
+
output << "Built from scratch"
|
238
|
+
assert_equal output, cue.to_webvtt
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_invalid_cue
|
242
|
+
webvtt = WebVTT.convert_from_srt("tests/subtitles/invalid_cue.srt")
|
243
|
+
assert_equal 1, webvtt.cues.size
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_can_validate_webvtt_with_carriage_returns
|
247
|
+
webvtt = WebVTT::File.new("tests/subtitles/test_carriage_returns.vtt")
|
248
|
+
assert_instance_of Array, webvtt.cues
|
249
|
+
assert !webvtt.cues.empty?, "Cues should not be empty"
|
250
|
+
assert_instance_of WebVTT::Cue, webvtt.cues[0]
|
251
|
+
assert_equal 15, webvtt.cues.size
|
252
|
+
end
|
253
|
+
|
198
254
|
end
|
data/tests/segmenter.rb
CHANGED
@@ -43,7 +43,7 @@ class ParserTest < Minitest::Test
|
|
43
43
|
subs = segmenter.split_to_files
|
44
44
|
segmenter.generate_playlist(subs)
|
45
45
|
|
46
|
-
assert File.
|
46
|
+
assert File.exist?("test.m3u8")
|
47
47
|
# clean up
|
48
48
|
subs.each {|f| FileUtils.rm(f.filename)}
|
49
49
|
FileUtils.rm("test.m3u8")
|
@@ -0,0 +1 @@
|
|
1
|
+
WEBVTT
|
@@ -0,0 +1,15 @@
|
|
1
|
+
WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
00:00:29.000 --> 00:00:31.000 line:75%
|
7
|
+
English subtitle 15 -Forced- (00:00:27.000)
|
8
|
+
line:75%
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
00:00:31.000 --> 00:00:33.000 align:start line:0%
|
14
|
+
English subtitle 16 -Unforced- (00:00:31.000)
|
15
|
+
align:start line:0%
|
data/webvtt-ruby.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'webvtt-ruby'
|
3
|
-
s.version = '0.
|
3
|
+
s.version = '0.4.0'
|
4
4
|
s.summary = "WebVTT parser and segmenter in ruby"
|
5
5
|
s.description = "WebVTT parser and segmenter in ruby for HTML5 and HTTP Live Streaming (HLS)."
|
6
6
|
s.authors = ["Bruno Celeste"]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webvtt-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bruno Celeste
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: WebVTT parser and segmenter in ruby for HTML5 and HTTP Live Streaming
|
14
14
|
(HLS).
|
@@ -24,18 +24,26 @@ files:
|
|
24
24
|
- LICENSE
|
25
25
|
- README.md
|
26
26
|
- bin/webvtt-segmenter
|
27
|
-
- lib/parser.rb
|
28
|
-
- lib/segmenter.rb
|
29
27
|
- lib/webvtt.rb
|
28
|
+
- lib/webvtt/parser.rb
|
29
|
+
- lib/webvtt/segmenter.rb
|
30
30
|
- tests/parser.rb
|
31
31
|
- tests/segmenter.rb
|
32
32
|
- tests/subtitles/big_srt.srt
|
33
33
|
- tests/subtitles/big_srt.vtt
|
34
|
+
- tests/subtitles/invalid_cue.srt
|
35
|
+
- tests/subtitles/invalid_cue.vtt
|
34
36
|
- tests/subtitles/no_text.vtt
|
35
37
|
- tests/subtitles/notvalid.vtt
|
36
38
|
- tests/subtitles/test.vtt
|
39
|
+
- tests/subtitles/test_carriage_returns.vtt
|
37
40
|
- tests/subtitles/test_from_srt.srt
|
38
41
|
- tests/subtitles/test_from_srt.vtt
|
42
|
+
- tests/subtitles/test_mmss_format.vtt
|
43
|
+
- tests/subtitles/test_multiple_line_separators.vtt
|
44
|
+
- tests/subtitles/weird_format.srt
|
45
|
+
- tests/subtitles/weird_format.vtt
|
46
|
+
- tests/subtitles/weird_format_corrected.vtt
|
39
47
|
- tests/subtitles/withnote.vtt
|
40
48
|
- webvtt-ruby.gemspec
|
41
49
|
homepage: https://github.com/HeyWatch/webvtt-ruby
|
@@ -57,8 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
65
|
- !ruby/object:Gem::Version
|
58
66
|
version: '0'
|
59
67
|
requirements: []
|
60
|
-
|
61
|
-
rubygems_version: 2.4.3
|
68
|
+
rubygems_version: 3.1.4
|
62
69
|
signing_key:
|
63
70
|
specification_version: 4
|
64
71
|
summary: WebVTT parser and segmenter in ruby
|