webvtt-ruby 0.3.2 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +3 -3
- data/lib/webvtt/parser.rb +50 -26
- data/lib/webvtt/segmenter.rb +22 -1
- data/tests/parser.rb +47 -1
- data/tests/segmenter.rb +1 -1
- data/tests/subtitles/test_carriage_returns.vtt +1 -0
- 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 +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 378a27bdabf62c13b0446de29e0aefc7267ac38f67401f1bab668f1ea673edab
|
4
|
+
data.tar.gz: 9b68d17e5435a1ef088bc06ec1c5346cc3526dc3af5b56578c14afca6c922656
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b7e76f6306f2df4fcc2f45a194583eb288b4822f2f414723fb6b1675023adbc9ba088be5bbf6b0e6062ef13c3d649dcd0794ab5ff0bca4bbb87012f01c318e6
|
7
|
+
data.tar.gz: ea5a3ea7d52dc8f61bc1ee66fe495e1721f11c4430a1eda17a73b54fd279758eb861b716e2fda1ad5a2e1154400dde4694f83aefe8669995fee3cb0574c528d2
|
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)
|
data/lib/webvtt/parser.rb
CHANGED
@@ -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?
|
@@ -81,6 +80,29 @@ 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
|
|
@@ -146,10 +168,12 @@ module WebVTT
|
|
146
168
|
return
|
147
169
|
end
|
148
170
|
|
149
|
-
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})(.*)/)
|
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})(.*)/)
|
150
172
|
@start = Timestamp.new $1
|
151
|
-
@end = Timestamp.new $
|
152
|
-
@style = Hash[$
|
173
|
+
@end = Timestamp.new $3
|
174
|
+
@style = Hash[$5.strip.split(" ").map{|s| s.split(":").map(&:strip) }]
|
175
|
+
else
|
176
|
+
raise WebVTT::MalformedFile
|
153
177
|
end
|
154
178
|
@text = lines[1..-1].join("\n")
|
155
179
|
end
|
data/lib/webvtt/segmenter.rb
CHANGED
@@ -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/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,7 +186,7 @@ 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
191
|
cue = WebVTT::Cue.parse <<-CUE
|
160
192
|
00:00:01.000 --> 00:00:25.432
|
@@ -210,4 +242,18 @@ The text should change)
|
|
210
242
|
webvtt = WebVTT.convert_from_srt("tests/subtitles/invalid_cue.srt")
|
211
243
|
assert_equal 1, webvtt.cues.size
|
212
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
|
+
|
254
|
+
def test_invalid_vtt_without_milliseconds
|
255
|
+
assert_raises WebVTT::MalformedFile do
|
256
|
+
vtt = WebVTT::File.new('tests/subtitles/no_milliseconds.vtt')
|
257
|
+
end
|
258
|
+
end
|
213
259
|
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.2'
|
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.2
|
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: 2024-05-17 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).
|
@@ -36,8 +36,14 @@ files:
|
|
36
36
|
- tests/subtitles/no_text.vtt
|
37
37
|
- tests/subtitles/notvalid.vtt
|
38
38
|
- tests/subtitles/test.vtt
|
39
|
+
- tests/subtitles/test_carriage_returns.vtt
|
39
40
|
- tests/subtitles/test_from_srt.srt
|
40
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
|
41
47
|
- tests/subtitles/withnote.vtt
|
42
48
|
- webvtt-ruby.gemspec
|
43
49
|
homepage: https://github.com/HeyWatch/webvtt-ruby
|
@@ -59,8 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
65
|
- !ruby/object:Gem::Version
|
60
66
|
version: '0'
|
61
67
|
requirements: []
|
62
|
-
|
63
|
-
rubygems_version: 2.4.3
|
68
|
+
rubygems_version: 3.1.4
|
64
69
|
signing_key:
|
65
70
|
specification_version: 4
|
66
71
|
summary: WebVTT parser and segmenter in ruby
|