vtt2ass 0.2.1
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 +7 -0
- data/.gitignore +9 -0
- data/.gitlab-ci.yml +31 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +42 -0
- data/Rakefile +10 -0
- data/bin/run +4 -0
- data/bin/setup +8 -0
- data/doc/ASSFile.html +338 -0
- data/doc/ASSStyle.html +510 -0
- data/doc/ASSSubtitle.html +1012 -0
- data/doc/Application.html +487 -0
- data/doc/VTTSubtitle.html +700 -0
- data/doc/Vtt2ass.html +253 -0
- data/doc/Vtt2ass/Error.html +124 -0
- data/doc/_index.html +155 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +497 -0
- data/doc/file.README.html +91 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +91 -0
- data/doc/js/app.js +314 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +283 -0
- data/doc/top-level-namespace.html +112 -0
- data/exe/vtt2ass +4 -0
- data/lib/vtt2ass.rb +40 -0
- data/lib/vtt2ass/ASSFile.rb +43 -0
- data/lib/vtt2ass/ASSStyle.rb +63 -0
- data/lib/vtt2ass/ASSSubtitle.rb +92 -0
- data/lib/vtt2ass/Application.rb +82 -0
- data/lib/vtt2ass/VTTSubtitle.rb +40 -0
- data/lib/vtt2ass/version.rb +3 -0
- data/vtt2ass.gemspec +27 -0
- metadata +85 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>
|
7
|
+
Top Level Namespace
|
8
|
+
|
9
|
+
— Documentation by YARD 0.9.26
|
10
|
+
|
11
|
+
</title>
|
12
|
+
|
13
|
+
<link rel="stylesheet" href="css/style.css" type="text/css" />
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="css/common.css" type="text/css" />
|
16
|
+
|
17
|
+
<script type="text/javascript">
|
18
|
+
pathId = "";
|
19
|
+
relpath = '';
|
20
|
+
</script>
|
21
|
+
|
22
|
+
|
23
|
+
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
|
24
|
+
|
25
|
+
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
|
26
|
+
|
27
|
+
|
28
|
+
</head>
|
29
|
+
<body>
|
30
|
+
<div class="nav_wrap">
|
31
|
+
<iframe id="nav" src="class_list.html?1"></iframe>
|
32
|
+
<div id="resizer"></div>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div id="main" tabindex="-1">
|
36
|
+
<div id="header">
|
37
|
+
<div id="menu">
|
38
|
+
|
39
|
+
<a href="_index.html">Index</a> »
|
40
|
+
|
41
|
+
|
42
|
+
<span class="title">Top Level Namespace</span>
|
43
|
+
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div id="search">
|
47
|
+
|
48
|
+
<a class="full_list_link" id="class_list_link"
|
49
|
+
href="class_list.html">
|
50
|
+
|
51
|
+
<svg width="24" height="24">
|
52
|
+
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
|
53
|
+
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
|
54
|
+
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
|
55
|
+
</svg>
|
56
|
+
</a>
|
57
|
+
|
58
|
+
</div>
|
59
|
+
<div class="clear"></div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div id="content"><h1>Top Level Namespace
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
</h1>
|
67
|
+
<div class="box_info">
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
</div>
|
80
|
+
|
81
|
+
<h2>Defined Under Namespace</h2>
|
82
|
+
<p class="children">
|
83
|
+
|
84
|
+
|
85
|
+
<strong class="modules">Modules:</strong> <span class='object_link'><a href="Vtt2ass.html" title="Vtt2ass (module)">Vtt2ass</a></span>
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
<strong class="classes">Classes:</strong> <span class='object_link'><a href="ASSFile.html" title="ASSFile (class)">ASSFile</a></span>, <span class='object_link'><a href="ASSStyle.html" title="ASSStyle (class)">ASSStyle</a></span>, <span class='object_link'><a href="ASSSubtitle.html" title="ASSSubtitle (class)">ASSSubtitle</a></span>, <span class='object_link'><a href="Application.html" title="Application (class)">Application</a></span>, <span class='object_link'><a href="VTTSubtitle.html" title="VTTSubtitle (class)">VTTSubtitle</a></span>
|
90
|
+
|
91
|
+
|
92
|
+
</p>
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
</div>
|
103
|
+
|
104
|
+
<div id="footer">
|
105
|
+
Generated on Thu Jan 14 00:31:16 2021 by
|
106
|
+
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
107
|
+
0.9.26 (ruby-2.7.2).
|
108
|
+
</div>
|
109
|
+
|
110
|
+
</div>
|
111
|
+
</body>
|
112
|
+
</html>
|
data/exe/vtt2ass
ADDED
data/lib/vtt2ass.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Imports
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
# Relative imports
|
5
|
+
require_relative 'vtt2ass/version'
|
6
|
+
require_relative 'vtt2ass/Application'
|
7
|
+
|
8
|
+
module Vtt2ass
|
9
|
+
##
|
10
|
+
# This function creates a new application instance and starts the process.
|
11
|
+
#
|
12
|
+
# It also defines the arguments that can be provided from the CLI.
|
13
|
+
def main
|
14
|
+
options = {}
|
15
|
+
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.banner = "Usage: vtt2ass [options]"
|
18
|
+
opts.separator ""
|
19
|
+
opts.separator "Specific options:"
|
20
|
+
opts.on("-i", "--input DIRECTORY", "Specify a custom input directory (default: './input')") do |dir|
|
21
|
+
options[:input] = dir
|
22
|
+
end
|
23
|
+
opts.on("-o", "--output DIRECTORY", "Specify a custom output directory (default: './output')") do |dir|
|
24
|
+
options[:output] = dir
|
25
|
+
end
|
26
|
+
opts.on("-s", "--font-size SIZE", Integer, "Specify a font size for the subtitles (default: 72)") do |size|
|
27
|
+
options[:font_size] = size
|
28
|
+
end
|
29
|
+
opts.on("-v", "--version", "Show version") do
|
30
|
+
puts Vtt2ass::VERSION
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end.parse!
|
34
|
+
|
35
|
+
app = Application.new(options)
|
36
|
+
app.start
|
37
|
+
end
|
38
|
+
|
39
|
+
module_function :main
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
##
|
2
|
+
# This class defines the ASS File that will be created from the conversion.
|
3
|
+
class ASSFile
|
4
|
+
|
5
|
+
##
|
6
|
+
# This method creates an instance of the ASSFile.
|
7
|
+
#
|
8
|
+
# * Requires +ass_styles+, a list of ASSStyle as input.
|
9
|
+
# * Requires +ass_subs+, a list of ASSSubtitles as input.
|
10
|
+
# * Requires a video +width+ as input.
|
11
|
+
# * Requires a video +height+ as input.
|
12
|
+
def initialize(ass_styles, ass_subs, width, height)
|
13
|
+
@width = width
|
14
|
+
@height = height
|
15
|
+
@header = [
|
16
|
+
'[Script Info]',
|
17
|
+
'Title: DKB Team',
|
18
|
+
'ScriptType: v4.00+',
|
19
|
+
'Collisions: Normal',
|
20
|
+
'PlayDepth: 0',
|
21
|
+
"PlayResX: #{@width}",
|
22
|
+
"PlayResY: #{@height}",
|
23
|
+
'WrapStyle: 0',
|
24
|
+
'ScaledBorderAndShadow: yes',
|
25
|
+
'',
|
26
|
+
'[V4+ Styles]',
|
27
|
+
'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding'
|
28
|
+
]
|
29
|
+
@ass_styles = ass_styles
|
30
|
+
@events = [
|
31
|
+
'',
|
32
|
+
'[Events]',
|
33
|
+
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text'
|
34
|
+
]
|
35
|
+
@ass_subs = ass_subs
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# This method concatenates the object data in the right order for a string output.
|
40
|
+
def to_s
|
41
|
+
return @header + @ass_styles + @events + @ass_subs
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
##
|
2
|
+
# This class defines an ASS style that can be applied on a subtitle line.
|
3
|
+
class ASSStyle
|
4
|
+
attr_reader :style_name
|
5
|
+
|
6
|
+
##
|
7
|
+
# This method creates and instance of an ASSStyle.
|
8
|
+
#
|
9
|
+
# * Requires +style_name+, a string name for the style as input.
|
10
|
+
# * Requires +params+, a string of VTT styling as input.
|
11
|
+
# * Requires a video +width+ as input.
|
12
|
+
# * Requires a video +height+ as input.
|
13
|
+
def initialize(style_name, params, font_size, width, height)
|
14
|
+
@width = width
|
15
|
+
@height = height
|
16
|
+
@font_size = font_size
|
17
|
+
@style_name = style_name
|
18
|
+
assignParams(params)
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# This method converts the string of VTT styling in values used for ASS styling.
|
23
|
+
#
|
24
|
+
# * Requires +params+, a string of VTT styling as input.
|
25
|
+
def assignParams(params)
|
26
|
+
@alignment = "2"
|
27
|
+
@left_margin = "10"
|
28
|
+
@right_margin = "10"
|
29
|
+
@vertical_margin = "50"
|
30
|
+
|
31
|
+
if params.include? "align:middle line:7%" then
|
32
|
+
@style_name = "MainTop"
|
33
|
+
@vertical_margin = "30"
|
34
|
+
@alignment = "8"
|
35
|
+
else
|
36
|
+
param_count = 0
|
37
|
+
(params.split(' ').map { |p| p.split(':') }).each do |p|
|
38
|
+
case p[0]
|
39
|
+
when "position"
|
40
|
+
@left_margin = (@width * ((p[1].gsub(/%/, '').to_f - 7) / 100)).to_i.to_s
|
41
|
+
when "align"
|
42
|
+
case p[1]
|
43
|
+
when "left"
|
44
|
+
@alignment = 1
|
45
|
+
when "middle"
|
46
|
+
@alignment = 2
|
47
|
+
when "right"
|
48
|
+
@alignment = 3
|
49
|
+
end
|
50
|
+
when "line"
|
51
|
+
@vertical_margin = (@height - (@height * ((p[1].gsub(/%/, '').to_f + 7) / 100))).to_i.to_s
|
52
|
+
end
|
53
|
+
param_count += 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# This method assigns the object values to an ASS style line and outputs it.
|
60
|
+
def to_s
|
61
|
+
return "Style: #{@style_name},Open Sans Semibold,#{@font_size},&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,2.0,2.0,#{@alignment},#{@left_margin},#{@right_margin},#{@vertical_margin},1"
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
##
|
2
|
+
# This class defines an ASS subtile line.
|
3
|
+
class ASSSubtitle
|
4
|
+
attr_reader :style, :time_start, :time_end, :params, :text
|
5
|
+
|
6
|
+
##
|
7
|
+
# This method creates an instance of an ASSSubtitle.
|
8
|
+
#
|
9
|
+
# * Requires a +style+ name as input.
|
10
|
+
# * Requires +time_start+, a VTT formatted timestamp as input.
|
11
|
+
# * Requires +time_start+, a VTT formatted timestamp as input.
|
12
|
+
# * Requires +text+, a VTT formatted string as input.
|
13
|
+
def initialize(style, time_start, time_end, params, text)
|
14
|
+
@style = style
|
15
|
+
@time_start = convertTime(time_start)
|
16
|
+
@time_end = convertTime(time_end)
|
17
|
+
@params = params
|
18
|
+
@text = convertToAssText(text)
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# This method assigns the object values and outputs an ASS dialogue line.
|
23
|
+
def to_s
|
24
|
+
return "Dialogue: 0,#{@time_start},#{@time_end},#{@style},,0,0,0,,#{@text}"
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# This method replaces characters and tags to ASS compatible characters and tags.
|
29
|
+
#
|
30
|
+
# * Requires +text+, a string of VTT formated text as input.
|
31
|
+
def convertToAssText(text)
|
32
|
+
text = text
|
33
|
+
.gsub(/\r/, '')
|
34
|
+
.gsub(/\n/, '\\N')
|
35
|
+
.gsub(/\\n/, '\\N')
|
36
|
+
.gsub(/\\N +/, '\\N')
|
37
|
+
.gsub(/ +\\N/, '\\N')
|
38
|
+
.gsub(/(\\N)+/, '\\N')
|
39
|
+
.gsub(/<b[^>]*>([^<]*)<\/b>/) { |s| "{\\b1}#{$1}{\\b0}" }
|
40
|
+
.gsub(/<i[^>]*>([^<]*)<\/i>/) { |s| "{\\i1}#{$1}{\\i0}" }
|
41
|
+
.gsub(/<u[^>]*>([^<]*)<\/u>/) { |s| "{\\u1}#{$1}{\\u0}" }
|
42
|
+
.gsub(/<c[^>]*>([^<]*)<\/c>/) { |s| $1 }
|
43
|
+
.gsub(/<[^>]>/, '')
|
44
|
+
.gsub(/\\N$/, '')
|
45
|
+
.gsub(/ +$/, '')
|
46
|
+
return text
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# This method validates the time format and sends the matching time to be converted
|
51
|
+
#
|
52
|
+
# * Requires +str+, a VTT formatted time string.
|
53
|
+
def convertTime(time)
|
54
|
+
mTime = time.match(/([\d:]*)\.?(\d*)/)
|
55
|
+
return toSubsTime(mTime[0])
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# This method converts time from VTT format to the ASS format.
|
60
|
+
#
|
61
|
+
# * Requires +str+, a VTT formatted time string.
|
62
|
+
def toSubsTime(str)
|
63
|
+
n = []
|
64
|
+
x = str.split(/[:.]/).map { |x| x.to_i }
|
65
|
+
|
66
|
+
msLen = 2
|
67
|
+
hLen = 1
|
68
|
+
|
69
|
+
x[3] = '0.' + (x[3].to_s).rjust(3, '0')
|
70
|
+
sx = x[0]*60*60 + x[1]*60 + x[2] + x[3].to_f
|
71
|
+
sx = ("%.2f" % sx).split('.')
|
72
|
+
|
73
|
+
n.unshift(padTimeNum('.', sx[1], msLen))
|
74
|
+
sx = sx[0].to_f
|
75
|
+
|
76
|
+
n.unshift(padTimeNum(':', (sx % 60).to_i, 2))
|
77
|
+
n.unshift(padTimeNum(':', (sx / 60).floor % 60, 2))
|
78
|
+
n.unshift(padTimeNum('', (sx / 3600).floor % 60, hLen))
|
79
|
+
|
80
|
+
return n.join('')
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# The method pads text so that time numbers are a fixed number of digit.
|
85
|
+
#
|
86
|
+
# * Requires +sep+, a string separator.
|
87
|
+
# * Requires +input+, an integer.
|
88
|
+
# * Requires +pad+, an integer for the number of digits to be padded.
|
89
|
+
def padTimeNum(sep, input, pad)
|
90
|
+
return sep + (input.to_s).rjust(pad, '0')
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Imports
|
2
|
+
require 'os'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
# Relative imports
|
6
|
+
require_relative 'VTTSubtitle'
|
7
|
+
require_relative 'ASSSubtitle'
|
8
|
+
require_relative 'ASSStyle'
|
9
|
+
require_relative 'ASSFile'
|
10
|
+
|
11
|
+
##
|
12
|
+
# Main application class that manages all the operations.
|
13
|
+
class Application
|
14
|
+
|
15
|
+
##
|
16
|
+
# Creates a new Application instance.
|
17
|
+
# It receives +options+ that can define the input and output directories.
|
18
|
+
def initialize(options)
|
19
|
+
@input_dir = options[:input] ? options[:input]: "./input"
|
20
|
+
@output_dir = options[:output] ? options[:output]: "./output"
|
21
|
+
@width = 1920
|
22
|
+
@height = 1080
|
23
|
+
@font_size = options[:font_size] ? options[:font_size] : 72
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# This method starts the application process.
|
28
|
+
# It sends the file_paths of VTT files in the input directory to convertFileToASS method
|
29
|
+
# and outputs the resulting ASS format to a new file.
|
30
|
+
def start
|
31
|
+
Dir["#{@input_dir}/*.vtt"].each do |file_path|
|
32
|
+
file_name = File.basename(file_path).gsub('.vtt', '.ass')
|
33
|
+
FileUtils.mkdir_p @output_dir
|
34
|
+
File.open("#{@output_dir}/" + file_name, 'w') do |line|
|
35
|
+
line.print "\ufeff"
|
36
|
+
line.puts convertFileToASS(file_path)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# This method reads the VTT file and sends back a list of paragraphs.
|
43
|
+
# It requires a +file_path+ as input.
|
44
|
+
# It outputs a list named list_paragraph.
|
45
|
+
def readVTTFile(file_path)
|
46
|
+
list_parapraph = []
|
47
|
+
separator = OS.linux? ? "\r\n\r\n": "\n\n"
|
48
|
+
File.foreach(file_path, separator) do |paragraph|
|
49
|
+
paragraph = paragraph.rstrip.gsub(/\r\n/, "\n")
|
50
|
+
if not paragraph.eql? "" then
|
51
|
+
list_parapraph.push(VTTSubtitle.new(paragraph))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
list_parapraph.shift
|
55
|
+
return list_parapraph
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# This method gets the list of paragraphs from the VTT file and creates lists of ASSSubtitle and ASSStyles objects from them.
|
60
|
+
# Those lists are given a new ASSFile object to generate the file content.
|
61
|
+
# It requires a +file_path+ as input.
|
62
|
+
# It outputs an ASSFile object.
|
63
|
+
def convertFileToASS(file_path)
|
64
|
+
vtt_subs = readVTTFile(file_path)
|
65
|
+
ass_subs = []
|
66
|
+
ass_styles = []
|
67
|
+
vtt_subs.each do |sub|
|
68
|
+
ass_subs.push(ASSSubtitle.new(sub.style, sub.time_start, sub.time_end, sub.params, sub.text))
|
69
|
+
style_exists = false
|
70
|
+
ass_styles.each do |style|
|
71
|
+
if (style.style_name == sub.style) then
|
72
|
+
style_exists = true
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
if not style_exists then
|
77
|
+
ass_styles.push(ASSStyle.new(sub.style, sub.params, @font_size, @width, @height))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
return ASSFile.new(ass_styles, ass_subs, @width, @height).to_s
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
##
|
2
|
+
# This class defines a VTT subtile line.
|
3
|
+
class VTTSubtitle
|
4
|
+
attr_reader :style, :time_start, :time_end, :params, :text
|
5
|
+
|
6
|
+
##
|
7
|
+
# This method creates an instance of an VTTSubtitle.
|
8
|
+
#
|
9
|
+
# * Requires +paragraph+, a VTT formatted string as input.
|
10
|
+
def initialize(paragraph)
|
11
|
+
lines = paragraph.split("\n")
|
12
|
+
rx = /^([\d:.]*) --> ([\d:.]*)\s?(.*?)\s*$/
|
13
|
+
@style = "Main"
|
14
|
+
@text, @time_start, @time_end, @params = ""
|
15
|
+
count = 0
|
16
|
+
|
17
|
+
lines.each do |line|
|
18
|
+
m = line.match(rx)
|
19
|
+
if not m and count == 0 then
|
20
|
+
@style = line
|
21
|
+
elsif m then
|
22
|
+
@time_start = m[1]
|
23
|
+
@time_end = m[2]
|
24
|
+
@params = m[3]
|
25
|
+
if @params.include? "align:middle line:7%" then
|
26
|
+
@style = "MainTop"
|
27
|
+
end
|
28
|
+
else
|
29
|
+
@text += line + "\n"
|
30
|
+
end
|
31
|
+
count += 1;
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# This method assigns the object values and outputs a VTT dialogue line.
|
37
|
+
def to_s
|
38
|
+
return "#{@style} \n#{@time_start} --> #{@time_end} #{@params}\n#{@text}"
|
39
|
+
end
|
40
|
+
end
|