vtt2ass 0.3.4 → 0.3.6
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/.gitignore +3 -0
- data/.rubocop.yml +19 -0
- data/Gemfile +2 -0
- data/README.md +5 -1
- data/Rakefile +8 -6
- data/bin/vtt2ass +2 -1
- data/exe/vtt2ass +2 -1
- data/lib/vtt2ass/application.rb +69 -65
- data/lib/vtt2ass/ass_file.rb +91 -94
- data/lib/vtt2ass/ass_line.rb +89 -86
- data/lib/vtt2ass/ass_style.rb +42 -38
- data/lib/vtt2ass/ass_style_params.rb +97 -75
- data/lib/vtt2ass/css_file.rb +28 -26
- data/lib/vtt2ass/css_rule.rb +25 -17
- data/lib/vtt2ass/validator.rb +12 -8
- data/lib/vtt2ass/version.rb +3 -1
- data/lib/vtt2ass/vtt_file.rb +45 -45
- data/lib/vtt2ass/vtt_line.rb +37 -37
- data/lib/vtt2ass.rb +66 -21
- data/vtt2ass.gemspec +20 -16
- metadata +31 -15
data/lib/vtt2ass/ass_style.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative 'ass_style_params'
|
3
4
|
require_relative 'validator'
|
4
5
|
require 'redgreenblue'
|
@@ -6,43 +7,46 @@ require 'redgreenblue'
|
|
6
7
|
##
|
7
8
|
# This class defines an ASS style that can be applied on a subtitle line.
|
8
9
|
class ASSStyle
|
9
|
-
|
10
|
+
attr_reader :style_name
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@is_italic = is_italic
|
33
|
-
@is_bold = is_bold
|
34
|
-
end
|
12
|
+
##
|
13
|
+
# This method creates and instance of an ASSStyle.
|
14
|
+
#
|
15
|
+
# * Requires +style_name+, a string name for the style as input.
|
16
|
+
# * Requires +params+, a string of VTT styling as input.
|
17
|
+
# * Requires a video +width+ as input.
|
18
|
+
# * Requires a video +height+ as input.
|
19
|
+
def initialize(style_name, params, font_family, font_size, font_color, is_bold, is_italic, offset, width, height)
|
20
|
+
@width = width
|
21
|
+
@height = height
|
22
|
+
@font_family = font_family
|
23
|
+
@font_size = font_size
|
24
|
+
@font_color = font_color
|
25
|
+
@style_name = style_name
|
26
|
+
@s_params = ASSStyleParams.new(params, width, height)
|
27
|
+
@s_params.vertical_margin = 50 if style_name.eql? 'MainTop'
|
28
|
+
@s_params.vertical_margin -= offset[:line] if style_name.include?('Subtitle') || style_name.eql?('Main')
|
29
|
+
@s_params.vertical_margin += offset[:caption] if style_name.include?('Caption') || style_name.eql?('MainTop')
|
30
|
+
@is_italic = is_italic
|
31
|
+
@is_bold = is_bold
|
32
|
+
end
|
35
33
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
##
|
35
|
+
# This method assigns the object values to an ASS style line and outputs it.
|
36
|
+
def to_s
|
37
|
+
# Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour,
|
38
|
+
# Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment,
|
39
|
+
# MarginL, MarginR, MarginV, Encoding
|
40
|
+
"Style: #{@style_name},#{@font_family},#{@font_size},#{@font_color},&H000000FF,&H00020713,&H00000000,"\
|
41
|
+
"#{@is_bold ? '-1' : '0'},#{@is_italic ? '-1' : '0'},0,0,100,100,0,0,1,2.0,2.0,#{@s_params.alignment},"\
|
42
|
+
"#{@s_params.horizontal_margin},0,#{@s_params.vertical_margin},1"
|
43
|
+
end
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
##
|
46
|
+
# This method returns a ASS formated color value based on hex or color name value
|
47
|
+
def self.convert_color(color_value)
|
48
|
+
color_value.gsub!('#', '')
|
49
|
+
color = Validator.hex?(color_value) ? RGB.hex(color_value) : RGB.css(color_value)
|
50
|
+
format('&H00%<blue>02x%<green>02x%<red>02x', blue: color.b, green: color.g, red: color.r).upcase
|
51
|
+
end
|
52
|
+
end
|
@@ -1,87 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
##
|
2
4
|
# This class defines the ASS style parameters from VTT cue settings.
|
3
5
|
class ASSStyleParams
|
4
|
-
|
6
|
+
attr_accessor :horizontal_margin, :vertical_margin, :alignment, :align
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
8
|
+
##
|
9
|
+
# Creates an instance of ASSStyleParams
|
10
|
+
# It takes VTT style arguments and assign them to their respectful instance variable.
|
11
|
+
# It calls methods to create ASS values from the VTT cue settings.
|
12
|
+
def initialize(params, width, height)
|
13
|
+
@align = nil
|
14
|
+
split_params(params)
|
15
|
+
create_alignment
|
16
|
+
create_horizontal_margin(width)
|
17
|
+
create_vertical_margin(height)
|
18
|
+
end
|
19
|
+
|
20
|
+
def split_params(params)
|
21
|
+
(params.split.map { |p| p.split(':') }).each do |p|
|
22
|
+
case p[0]
|
23
|
+
when 'position'
|
24
|
+
@position = p[1].gsub(/%/, '').to_i
|
25
|
+
when 'line'
|
26
|
+
@line = p[1].gsub(/%/, '').to_i
|
27
|
+
@line = @line == -1 ? 100 : @line
|
28
|
+
when 'align'
|
29
|
+
@align = p[1].chomp
|
30
|
+
end
|
25
31
|
end
|
32
|
+
end
|
26
33
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
34
|
+
##
|
35
|
+
# This method decides the alignement value in a 9 position grid based of the
|
36
|
+
# values in cue settings "align" and "line".
|
37
|
+
def create_alignment
|
38
|
+
@alignment =
|
39
|
+
if defined?(@line) && !defined?(@position)
|
40
|
+
find_alignment(@align)
|
41
|
+
elsif defined?(@line) && defined?(@position)
|
42
|
+
1 # bottom left
|
43
|
+
else
|
44
|
+
find_default_alignment(@align)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# This method returns alignment when "line" value is specified but not "position"
|
50
|
+
def find_alignment(align)
|
51
|
+
if align.nil?
|
52
|
+
# If position is higher than 50% align to bottom center, else align to top center
|
53
|
+
@line >= 50 ? 2 : 8
|
54
|
+
else
|
55
|
+
case align
|
56
|
+
when 'left', 'start'
|
57
|
+
@line >= 50 ? 1 : 7
|
58
|
+
when 'right', 'end'
|
59
|
+
@line >= 50 ? 3 : 9
|
60
|
+
when 'center', 'middle'
|
61
|
+
@line >= 50 ? 2 : 8
|
62
|
+
end
|
58
63
|
end
|
64
|
+
end
|
59
65
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
##
|
67
|
+
# This method returns alignment when "line" and "position" values are not specified
|
68
|
+
def find_default_alignment(align)
|
69
|
+
case align
|
70
|
+
when 'left', 'start'
|
71
|
+
1
|
72
|
+
when 'right', 'end'
|
73
|
+
3
|
74
|
+
# when 'center', 'middle'
|
75
|
+
else
|
76
|
+
2
|
70
77
|
end
|
78
|
+
end
|
71
79
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
80
|
+
##
|
81
|
+
# This method calculates the horizontal margin in px between the alignement position and
|
82
|
+
# and the content displayed by using the "position" cue setting.
|
83
|
+
def create_horizontal_margin(width)
|
84
|
+
steps = (width / 100).to_i
|
85
|
+
@horizontal_margin =
|
86
|
+
if defined?(@position)
|
87
|
+
@position * steps
|
88
|
+
else
|
89
|
+
0
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# This method calculates the vertical margin in px between the alignement position and
|
95
|
+
# and the content displayed by using the "line" cue setting.
|
96
|
+
def create_vertical_margin(height)
|
97
|
+
steps = (height / 100).to_i
|
98
|
+
@vertical_margin =
|
99
|
+
if defined?(@line)
|
100
|
+
if @alignment == 1
|
101
|
+
(100 - @line) * steps
|
83
102
|
else
|
84
|
-
|
103
|
+
@line >= 50 ? (100 - @line) * steps : @line * steps
|
85
104
|
end
|
86
|
-
|
87
|
-
|
105
|
+
else
|
106
|
+
50
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/vtt2ass/css_file.rb
CHANGED
@@ -1,36 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'css_parser'
|
2
4
|
require_relative 'css_rule'
|
3
5
|
|
6
|
+
##
|
7
|
+
# This class defines a CSS file for subtitles.
|
4
8
|
class CSSFile
|
5
|
-
|
6
|
-
include CssParser
|
9
|
+
attr_reader :rules
|
7
10
|
|
8
|
-
|
9
|
-
@file_path = file_path
|
10
|
-
parser = CssParser::Parser.new
|
11
|
-
parser.load_file!(file_path)
|
12
|
-
@rules = []
|
13
|
-
parser.each_selector do |selector, declarations, specificity|
|
14
|
-
css_obj = CSSRule.new(selector, declarations)
|
15
|
-
if not css_obj.name.empty? then
|
16
|
-
@rules.push(css_obj)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
11
|
+
include CssParser
|
20
12
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
return return_rule
|
13
|
+
def initialize(file_path)
|
14
|
+
@file_path = file_path
|
15
|
+
parser = CssParser::Parser.new
|
16
|
+
parser.load_file!(file_path)
|
17
|
+
@rules = []
|
18
|
+
parser.each_selector do |selector, declarations, _specificity|
|
19
|
+
css_obj = CSSRule.new(selector, declarations)
|
20
|
+
@rules.push(css_obj) unless css_obj.name.empty?
|
30
21
|
end
|
22
|
+
end
|
31
23
|
|
32
|
-
|
33
|
-
|
24
|
+
def find_rule(value)
|
25
|
+
return_rule = nil
|
26
|
+
@rules.each do |rule|
|
27
|
+
if rule.name == value
|
28
|
+
return_rule = rule
|
29
|
+
break
|
30
|
+
end
|
34
31
|
end
|
32
|
+
return_rule
|
33
|
+
end
|
35
34
|
|
36
|
-
|
35
|
+
def to_s
|
36
|
+
@file_path
|
37
|
+
end
|
38
|
+
end
|
data/lib/vtt2ass/css_rule.rb
CHANGED
@@ -1,22 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# This class defines a CSS rule that is included in the CSS file.
|
1
5
|
class CSSRule
|
2
|
-
|
6
|
+
attr_reader :name, :properties
|
3
7
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
8
|
+
def initialize(selector, declarations)
|
9
|
+
@name = reduce_selector(selector)
|
10
|
+
@properties = []
|
11
|
+
declarations.split(/;\s?/).each do |dec|
|
12
|
+
temp = dec.split(/:\s?/)
|
13
|
+
@properties.push(
|
14
|
+
{ key: temp.first, value: temp.last }
|
15
|
+
)
|
13
16
|
end
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
def to_s
|
20
|
+
"#{@name} #{@properties}"
|
21
|
+
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
##
|
24
|
+
# This method removes the generic selector from a block.
|
25
|
+
def reduce_selector(selector)
|
26
|
+
selector.to_s.gsub(
|
27
|
+
/\.rmp-container>\.rmp-content>\.rmp-cc-area>\.rmp-cc-container>\.rmp-cc-display>\.rmp-cc-cue\s?\.?/, ''
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
data/lib/vtt2ass/validator.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# This class defines validation tools for data.
|
1
5
|
class Validator
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
return hex
|
6
|
+
def self.hex?(value)
|
7
|
+
hex = true
|
8
|
+
value.gsub!('#', '')
|
9
|
+
value.chars.each do |digit|
|
10
|
+
hex = false unless digit.match(/\h/)
|
9
11
|
end
|
10
|
-
|
12
|
+
hex
|
13
|
+
end
|
14
|
+
end
|
data/lib/vtt2ass/version.rb
CHANGED
data/lib/vtt2ass/vtt_file.rb
CHANGED
@@ -1,58 +1,58 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative 'vtt_line'
|
3
4
|
|
4
5
|
##
|
5
6
|
# This class defines a VTT subtile file.
|
6
7
|
class VTTFile
|
7
|
-
|
8
|
+
attr_accessor :lines
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
@lines.push(vtt_line)
|
29
|
-
count += 1
|
30
|
-
end
|
10
|
+
##
|
11
|
+
# Creates a new VTTFile instance and assigns the default values of instance variables.
|
12
|
+
def initialize(file_path, width, height)
|
13
|
+
@title = File.basename(file_path).gsub('.vtt', '')
|
14
|
+
@lines = []
|
15
|
+
separator = determine_line_ending(file_path) ? "\n\n" : "\r\n\r\n"
|
16
|
+
count = 0
|
17
|
+
style_count = 1
|
18
|
+
File.foreach(file_path, separator) do |paragraph|
|
19
|
+
paragraph = paragraph.rstrip.gsub(/[\r\n]/, "\n")
|
20
|
+
unless paragraph.eql? ''
|
21
|
+
vtt_line = VTTLine.new(paragraph, width, height)
|
22
|
+
if vtt_line.style.eql?('Main') &&
|
23
|
+
!vtt_line.params.to_s.empty? &&
|
24
|
+
(!vtt_line.params.to_s.eql?('align:middle') &&
|
25
|
+
!vtt_line.params.to_s.eql?('align:center'))
|
26
|
+
vtt_line.style = "Style#{style_count}"
|
27
|
+
style_count += 1
|
31
28
|
end
|
32
|
-
@lines.
|
29
|
+
@lines.push(vtt_line)
|
30
|
+
count += 1
|
31
|
+
end
|
33
32
|
end
|
33
|
+
@lines.shift
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
##
|
44
|
-
# This method writes the content of the VTTFile object into a file path that is provided.
|
45
|
-
def write_to_file(file_path)
|
46
|
-
File.open(file_path, 'w') do |line|
|
47
|
-
line.print "\ufeff"
|
48
|
-
line.puts self.to_s
|
49
|
-
end
|
36
|
+
##
|
37
|
+
# This method determines the line ending character to use as a separator.
|
38
|
+
def determine_line_ending(file_path)
|
39
|
+
File.open(file_path, 'r') do |file|
|
40
|
+
return file.readline[/\r?\n$/] == "\n"
|
50
41
|
end
|
42
|
+
end
|
51
43
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
44
|
+
##
|
45
|
+
# This method writes the content of the VTTFile object into a file path that is provided.
|
46
|
+
def write_to_file(file_path)
|
47
|
+
File.open(file_path, 'w') do |line|
|
48
|
+
line.print "\ufeff"
|
49
|
+
line.puts to_s
|
56
50
|
end
|
51
|
+
end
|
57
52
|
|
58
|
-
|
53
|
+
##
|
54
|
+
# This method concatenates the object data in the right order for a string output.
|
55
|
+
def to_s
|
56
|
+
"WEBVTT\n\n\n#{@lines}"
|
57
|
+
end
|
58
|
+
end
|
data/lib/vtt2ass/vtt_line.rb
CHANGED
@@ -1,44 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
##
|
2
4
|
# This class defines a VTT subtile line.
|
3
5
|
class VTTLine
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
##
|
8
|
-
# This method creates an instance of an VTTLine.
|
9
|
-
#
|
10
|
-
# * Requires +paragraph+, a VTT formatted string as input.
|
11
|
-
def initialize(paragraph, width, height)
|
12
|
-
lines = paragraph.split("\n")
|
13
|
-
rx = /^([\d:.]*) --> ([\d:.]*)\s?(.*?)\s*$/
|
14
|
-
@style = "Main"
|
15
|
-
@text, @time_start, @time_end, @params = ""
|
16
|
-
count = 0
|
6
|
+
attr_accessor :style
|
7
|
+
attr_reader :time_start, :time_end, :params, :text
|
17
8
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@style = 'MainTop'
|
29
|
-
end
|
30
|
-
else
|
31
|
-
@text += line + "\n"
|
32
|
-
end
|
33
|
-
count += 1;
|
34
|
-
end
|
9
|
+
##
|
10
|
+
# This method creates an instance of an VTTLine.
|
11
|
+
#
|
12
|
+
# * Requires +paragraph+, a VTT formatted string as input.
|
13
|
+
def initialize(paragraph, width, height)
|
14
|
+
lines = paragraph.split("\n")
|
15
|
+
rx = /^([\d:.]*) --> ([\d:.]*)\s?(.*?)\s*$/
|
16
|
+
@style = 'Main'
|
17
|
+
@text, @time_start, @time_end, @params = ''
|
18
|
+
count = 0
|
35
19
|
|
36
|
-
|
20
|
+
lines.each do |line|
|
21
|
+
m = line.match(rx)
|
22
|
+
if !m && count.zero?
|
23
|
+
@style = line
|
24
|
+
elsif m
|
25
|
+
@time_start = m[1]
|
26
|
+
@time_end = m[2]
|
27
|
+
@params = m[3]
|
28
|
+
ass_style = ASSStyleParams.new(@params, width, height)
|
29
|
+
@style = 'MainTop' if @style.eql?('Main') && ass_style.alignment == 8
|
30
|
+
else
|
31
|
+
@text += "#{line}\n"
|
32
|
+
end
|
33
|
+
count += 1
|
37
34
|
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
@text = @text.lstrip
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# This method assigns the object values and outputs a VTT dialogue line.
|
41
|
+
def to_s
|
42
|
+
"#{@style} \n#{@time_start} --> #{@time_end} #{@params}\n#{@text}"
|
43
|
+
end
|
44
|
+
end
|