vtt2ass 0.3.2 → 0.3.5
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/.rubocop.yml +19 -0
- data/Gemfile +2 -0
- data/README.md +70 -10
- data/Rakefile +8 -6
- data/bin/vtt2ass +2 -1
- data/exe/vtt2ass +2 -1
- data/lib/vtt2ass/application.rb +78 -0
- data/lib/vtt2ass/ass_file.rb +106 -0
- data/lib/vtt2ass/ass_line.rb +97 -0
- data/lib/vtt2ass/ass_style.rb +51 -0
- data/lib/vtt2ass/ass_style_params.rb +108 -0
- data/lib/vtt2ass/css_file.rb +38 -0
- data/lib/vtt2ass/css_rule.rb +30 -0
- data/lib/vtt2ass/validator.rb +14 -0
- data/lib/vtt2ass/version.rb +3 -1
- data/lib/vtt2ass/vtt_file.rb +58 -0
- data/lib/vtt2ass/vtt_line.rb +44 -0
- data/lib/vtt2ass.rb +52 -22
- data/vtt2ass.gemspec +19 -16
- metadata +32 -30
- data/lib/vtt2ass/ASSFile.rb +0 -110
- data/lib/vtt2ass/ASSLine.rb +0 -94
- data/lib/vtt2ass/ASSStyle.rb +0 -48
- data/lib/vtt2ass/ASSStyleParams.rb +0 -87
- data/lib/vtt2ass/Application.rb +0 -68
- data/lib/vtt2ass/CSSFile.rb +0 -36
- data/lib/vtt2ass/CSSRule.rb +0 -22
- data/lib/vtt2ass/VTTFile.rb +0 -58
- data/lib/vtt2ass/VTTLine.rb +0 -44
- data/lib/vtt2ass/Validator.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3047227acbd6bf731e5dc95113a7d70081bed94392e3b00078b760ebbc4440a4
|
4
|
+
data.tar.gz: e9bc514e5b41028d872d9e983cc4dbd4442fc648ab1065e2c3e5a2aca3c9b8fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 388af342ebe22e69e52e92e4dadf5a04ea48cb6e491398e864ed1e9d5ae3a602601cddc070ae20f7e8edef0b652a87aa3e8a66f8b8128626315dbac0dd421873
|
7
|
+
data.tar.gz: fa2f3b0fa33c3ee1ade343b14837e8f09d2b5145f41e60f68e1cc7236e17e23c67cb7a811f5abf145df7e7121fd651364870073b6ff74679f6c9ddfe375a2ddc
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
AllCops:
|
3
|
+
TargetRubyVersion: 2.7.2
|
4
|
+
NewCops: enable
|
5
|
+
# Set method line number to 25
|
6
|
+
Metrics/MethodLength:
|
7
|
+
Max: 25
|
8
|
+
# Set parameter number to 10
|
9
|
+
Metrics/ParameterLists:
|
10
|
+
Max: 10
|
11
|
+
# Disable checking of ABC metrics (assignment, branch, condition)
|
12
|
+
Metrics/AbcSize:
|
13
|
+
Enabled: false
|
14
|
+
# Disable method perceived complexity (how it is hard to read a method)
|
15
|
+
Metrics/PerceivedComplexity:
|
16
|
+
Enabled: false
|
17
|
+
# Disable cyclomatic complexity which is a number of paths through a method
|
18
|
+
Metrics/CyclomaticComplexity:
|
19
|
+
Enabled: false
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,35 +2,54 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/vtt2ass)
|
4
4
|
|
5
|
-
|
5
|
+
## Description
|
6
|
+
|
7
|
+
This is a simple CLI (Command Line Interface) application to convert VTT files to ASS subtitles.
|
8
|
+
|
9
|
+
This application is originally based on the `vttconvert` module of [anidl/hidive-downloader-nx](https://github.com/anidl/hidive-downloader-nx) repository. The [maxwbot/maxwhidive](https://github.com/maxwbot/maxwhidive) repository was also used as inspiration for handling the positionning of subs.
|
10
|
+
|
11
|
+
Those two tools were missing features and didn't work well on a lot of more complex subtitles files. For that reason, I wrote a new tool that can handle everything.
|
12
|
+
|
13
|
+
### Features
|
14
|
+
|
15
|
+
- Convert simple VTT files
|
16
|
+
- Convert complex VTT files with positioning
|
17
|
+
- Convert Hidive VTT files with CSS styling
|
18
|
+
- Convert subtitles in batches by specifying the input directory
|
19
|
+
- Handles subtitles made for lower resolution video
|
20
|
+
- Can add offset to subtitle lines
|
21
|
+
- Can output result to the CLI
|
22
|
+
- Can output to the specifed directory
|
23
|
+
- Can change the base font size
|
24
|
+
- Can specify a custom font family for non-styled lines
|
25
|
+
- Can add a title to the converted files
|
6
26
|
|
7
27
|
## Requirements
|
28
|
+
|
8
29
|
- ruby 2.7.2 or newer
|
9
30
|
|
10
|
-
|
31
|
+
Development is currently done on ruby 3.0+, but the Gitlab runner for builds works with ruby version 2.7.2. Older versions of ruby may be compatible, but they won't be tested.
|
11
32
|
|
12
33
|
## Installation
|
13
34
|
|
14
35
|
To install:
|
15
36
|
```bash
|
16
|
-
gem install vtt2ass
|
17
|
-
```
|
18
|
-
|
19
|
-
# Build
|
20
|
-
|
21
|
-
```bash
|
22
|
-
gem build vtt2ass.gemspec
|
37
|
+
$ gem install vtt2ass
|
23
38
|
```
|
24
39
|
|
25
40
|
## Usage
|
26
41
|
|
42
|
+
- Empty arguments lists the available commands
|
27
43
|
```bash
|
28
44
|
$ vtt2ass
|
29
45
|
Commands:
|
30
46
|
vtt2ass convert INPUT # Run the VTT to ASS conversion for the specified file(s)
|
31
47
|
vtt2ass help [COMMAND] # Describe available commands or one specific command
|
32
48
|
vtt2ass version # Show version
|
49
|
+
```
|
33
50
|
|
51
|
+
- Help command shows available options of the specified command
|
52
|
+
```bash
|
34
53
|
$ vtt2ass help convert
|
35
54
|
Usage:
|
36
55
|
vtt2ass convert INPUT
|
@@ -45,12 +64,53 @@ Options:
|
|
45
64
|
-c, [--css=CSS] # Specify a CSS file path for Hidive subs
|
46
65
|
-l, [--line-offset=N] # Specify a line offset for the main dialog (e.g. 50 lowers the text line by 50px of the total height)
|
47
66
|
# Default: 0
|
67
|
+
-w, [--width=N] # Specify the video width
|
68
|
+
# Default: 1920
|
69
|
+
-h, [--height=N] # Specify the video height
|
70
|
+
# Default: 1080
|
48
71
|
-q, [--quiet], [--no-quiet] # Don't output to the console
|
49
72
|
|
50
73
|
Run the VTT to ASS conversion for the specified file(s)
|
51
74
|
```
|
52
75
|
|
53
|
-
|
76
|
+
- Convert command
|
77
|
+
```bash
|
78
|
+
$ vtt2ass convert ./path/to/input/ -o ./path/to/output/ -l 50 -q
|
79
|
+
```
|
80
|
+
|
81
|
+
- Version command shows the application version
|
82
|
+
```bash
|
83
|
+
$ vtt2ass version
|
84
|
+
0.3.3
|
85
|
+
```
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
Contributions are welcome. Create an *Issue* on Gitlab and link it with a *Pull Request* of the changes made. The changes needs to pass the ruby tests.
|
90
|
+
|
91
|
+
```
|
92
|
+
$ rake test
|
93
|
+
```
|
94
|
+
|
95
|
+
## Build
|
96
|
+
|
97
|
+
To build a gem file for local installation:
|
98
|
+
```bash
|
99
|
+
$ git clone https://gitlab.com/dkb-weeblets/vtt2ass.git
|
100
|
+
$ cd vtt2ass/
|
101
|
+
$ gem build vtt2ass.gemspec
|
102
|
+
```
|
103
|
+
|
104
|
+
To install the gem file:
|
105
|
+
```bash
|
106
|
+
$ gem install ./vtt2ass-0.3.5.gem
|
107
|
+
```
|
108
|
+
|
109
|
+
## License
|
110
|
+
|
111
|
+
Licensed under the **MIT** Licence. For more information read the `LICENSE.txt` file.
|
112
|
+
|
113
|
+
## Donate
|
54
114
|
|
55
115
|
If you want to support me, consider buying me a coffee.
|
56
116
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
3
5
|
|
4
6
|
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.libs <<
|
6
|
-
t.libs <<
|
7
|
-
t.test_files = FileList[
|
7
|
+
t.libs << 'test'
|
8
|
+
t.libs << 'lib'
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
10
|
end
|
9
11
|
|
10
|
-
task :
|
12
|
+
task default: :test
|
data/bin/vtt2ass
CHANGED
data/exe/vtt2ass
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'vtt_file'
|
4
|
+
require_relative 'ass_file'
|
5
|
+
|
6
|
+
##
|
7
|
+
# Main application class that manages all the operations.
|
8
|
+
class Application
|
9
|
+
##
|
10
|
+
# Creates a new Application instance.
|
11
|
+
# It receives +options+ that can define the input and output directories.
|
12
|
+
def initialize(input, options)
|
13
|
+
@options = options
|
14
|
+
@input = sanitize_path(input)
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Replace backslashes from Windows paths to normal slashes.
|
19
|
+
# Deletes the trailing slash if there is one.
|
20
|
+
def sanitize_path(path)
|
21
|
+
path&.gsub('\\', '/')&.delete_suffix('/')
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# This method starts the application process.
|
26
|
+
# It sends the file_paths of VTT files in the input directory to convertFileToASS method
|
27
|
+
# and outputs the resulting ASS format to a new file.
|
28
|
+
def start
|
29
|
+
if File.directory?(@input)
|
30
|
+
Dir["#{@input}/*.vtt"].each do |file_path|
|
31
|
+
convert(file_path)
|
32
|
+
end
|
33
|
+
elsif File.file?(@input)
|
34
|
+
convert(@input)
|
35
|
+
else
|
36
|
+
raise StandardError, 'ERROR: Input file or directory does not exist.'
|
37
|
+
end
|
38
|
+
rescue SystemExit, Interrupt
|
39
|
+
puts 'ERROR: The application stopped unexpectedly. The conversion may not have been completed.'
|
40
|
+
rescue StandardError => e
|
41
|
+
puts e.message
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# This method launches the conversion process on the specified input file.
|
46
|
+
def convert(input_path)
|
47
|
+
output = sanitize_path(@options[:output])
|
48
|
+
raise StandardError, 'ERROR: Output directory does not exist.' unless File.directory?(output)
|
49
|
+
|
50
|
+
ass_file = vtt_to_ass(input_path)
|
51
|
+
ass_file.write_to_file("#{output}/#{File.basename(input_path).gsub('.vtt', '.ass')}") unless output.nil?
|
52
|
+
puts ass_file.to_s unless @options[:quiet]
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# This method creates a new VTTFile object from the file path provided and convert its content
|
57
|
+
# inside a new ASSFile object.
|
58
|
+
def vtt_to_ass(file_path)
|
59
|
+
base_file_name = File.basename(file_path).gsub('.vtt', '')
|
60
|
+
css_file =
|
61
|
+
if !@options[:css].nil? && File.directory?(@options[:css])
|
62
|
+
"#{sanitize_path(@options[:css])}/#{base_file_name}.css"
|
63
|
+
elsif File.file?("#{file_path.gsub('.vtt', '')}.css")
|
64
|
+
"#{file_path.gsub('.vtt', '')}.css"
|
65
|
+
else
|
66
|
+
@options[:css]
|
67
|
+
end
|
68
|
+
vtt_file = VTTFile.new(file_path, @options[:width], @options[:height])
|
69
|
+
ass_file = ASSFile.new(
|
70
|
+
(@options[:title].nil? ? base_file_name : @options[:title]),
|
71
|
+
@options[:width],
|
72
|
+
@options[:height],
|
73
|
+
css_file
|
74
|
+
)
|
75
|
+
ass_file.convert_vtt_to_ass(vtt_file, @options[:font_family], @options[:font_size], @options[:line_offset])
|
76
|
+
ass_file
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ass_line'
|
4
|
+
require_relative 'ass_style'
|
5
|
+
require_relative 'css_file'
|
6
|
+
require_relative 'css_rule'
|
7
|
+
|
8
|
+
##
|
9
|
+
# This class defines an ASS subtitle file.
|
10
|
+
class ASSFile
|
11
|
+
attr_reader :title, :width, :height
|
12
|
+
attr_accessor :ass_styles, :ass_lines
|
13
|
+
|
14
|
+
##
|
15
|
+
# Creates a new ASSFile instance and assigns the default values of instance variables.
|
16
|
+
def initialize(title, width, height, css_file_path = nil)
|
17
|
+
@width = width
|
18
|
+
@height = height
|
19
|
+
@css_file = CSSFile.new(css_file_path) unless css_file_path.nil?
|
20
|
+
@header = <<~HEADER
|
21
|
+
[Script Info]
|
22
|
+
Title: #{title}
|
23
|
+
ScriptType: v4.00+
|
24
|
+
Collisions: Normal
|
25
|
+
PlayDepth: 0
|
26
|
+
PlayResX: #{@width}
|
27
|
+
PlayResY: #{@height}
|
28
|
+
WrapStyle: 0
|
29
|
+
ScaledBorderAndShadow: yes
|
30
|
+
|
31
|
+
[V4+ Styles]
|
32
|
+
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
33
|
+
HEADER
|
34
|
+
@events = <<~EVENTS
|
35
|
+
|
36
|
+
[Events]
|
37
|
+
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
38
|
+
EVENTS
|
39
|
+
@ass_styles = []
|
40
|
+
@ass_lines = []
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# This method receives a VTTFile object and font arguments creates new ASSLine with the params of
|
45
|
+
# each VTTLine. All those ASSLine are stored in an array. It also creates an array of ASSStyle that
|
46
|
+
# will be used in the ASS style list.
|
47
|
+
def convert_vtt_to_ass(vtt_file, font_family, font_size, line_offset = 0) # rubocop:disable Metrics/MethodLength
|
48
|
+
fs = font_size
|
49
|
+
vtt_file.lines.each do |line| # rubocop:disable Metrics/BlockLength
|
50
|
+
font_color = '&H00FFFFFF'
|
51
|
+
is_italic = false
|
52
|
+
is_bold = false
|
53
|
+
@ass_lines.push(ASSLine.new(line.style, line.time_start, line.time_end, line.text))
|
54
|
+
style_exists = false
|
55
|
+
@ass_styles.each do |style|
|
56
|
+
if style.style_name.eql? line.style
|
57
|
+
style_exists = true
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
next if style_exists
|
62
|
+
|
63
|
+
unless @css_file.nil?
|
64
|
+
css_rule = @css_file.find_rule(line.style)
|
65
|
+
css_rule&.properties&.each do |property|
|
66
|
+
case property[:key]
|
67
|
+
when 'font-family'
|
68
|
+
font_family = property[:value].gsub('"', '').split(' ,').last
|
69
|
+
when 'font-size'
|
70
|
+
em_size = 1
|
71
|
+
em_size = "0#{property[:value]}".gsub('em', '').to_f if property[:value][0].eql? '.'
|
72
|
+
font_size = (fs * em_size).to_i
|
73
|
+
when 'color'
|
74
|
+
font_color = ASSStyle.convert_color(property[:value])
|
75
|
+
when 'font-weight'
|
76
|
+
is_bold = true if property[:value].eql? 'bold'
|
77
|
+
when 'font-style'
|
78
|
+
is_italic = true if property[:value].eql? 'italic'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
@ass_styles.push(
|
83
|
+
ASSStyle.new(
|
84
|
+
line.style, line.params,
|
85
|
+
font_family, font_size, font_color, is_bold, is_italic,
|
86
|
+
line_offset, @width, @height
|
87
|
+
)
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# This method writes the content of the ASSFile object into a file path that is provided.
|
94
|
+
def write_to_file(file_path)
|
95
|
+
File.open(file_path, 'w') do |line|
|
96
|
+
line.print "\ufeff"
|
97
|
+
line.puts to_s
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# This method concatenates the object data in the right order for a string output.
|
103
|
+
def to_s
|
104
|
+
"#{@header}#{@ass_styles.join("\n")}#{@events}#{@ass_lines.join("\n")}"
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'htmlentities'
|
4
|
+
|
5
|
+
##
|
6
|
+
# This class defines an ASS subtile line.
|
7
|
+
class ASSLine
|
8
|
+
attr_reader :style, :time_start, :time_end, :text
|
9
|
+
|
10
|
+
##
|
11
|
+
# This method creates an instance of an ASSLine.
|
12
|
+
#
|
13
|
+
# * Requires a +style+ name as input.
|
14
|
+
# * Requires +time_start+, a VTT formatted timestamp as input.
|
15
|
+
# * Requires +time_start+, a VTT formatted timestamp as input.
|
16
|
+
# * Requires +text+, a VTT formatted string as input.
|
17
|
+
def initialize(style, time_start, time_end, text)
|
18
|
+
@style = style
|
19
|
+
@time_start = convert_time(time_start)
|
20
|
+
@time_end = convert_time(time_end)
|
21
|
+
@text = convert_to_ass_text(text)
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# This method assigns the object values and outputs an ASS dialogue line.
|
26
|
+
def to_s
|
27
|
+
"Dialogue: 0,#{@time_start},#{@time_end},#{@style},,0,0,0,,#{@text}"
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# This method replaces characters and tags to ASS compatible characters and tags.
|
32
|
+
#
|
33
|
+
# * Requires +text+, a string of VTT formated text as input.
|
34
|
+
def convert_to_ass_text(text)
|
35
|
+
decoder = HTMLEntities.new
|
36
|
+
text =
|
37
|
+
text
|
38
|
+
.gsub(/\r/, '')
|
39
|
+
.gsub(/\n/, '\\N')
|
40
|
+
.gsub(/\\n/, '\\N')
|
41
|
+
.gsub(/\\N +/, '\\N')
|
42
|
+
.gsub(/ +\\N/, '\\N')
|
43
|
+
.gsub(/(\\N)+/, '\\N')
|
44
|
+
.gsub(%r{<b[^>]*>([^<]*)</b>}) { |_s| "{\\b1}#{Regexp.last_match(1)}{\\b0}" }
|
45
|
+
.gsub(%r{<i[^>]*>([^<]*)</i>}) { |_s| "{\\i1}#{Regexp.last_match(1)}{\\i0}" }
|
46
|
+
.gsub(%r{<u[^>]*>([^<]*)</u>}) { |_s| "{\\u1}#{Regexp.last_match(1)}{\\u0}" }
|
47
|
+
.gsub(%r{<c[^>]*>([^<]*)</c>}) { |_s| Regexp.last_match(1) }
|
48
|
+
.gsub(/<[^>]>/, '')
|
49
|
+
.gsub(/\\N$/, '')
|
50
|
+
.gsub(/ +$/, '')
|
51
|
+
decoder.decode(text)
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# This method validates the time format and sends the matching time to be converted
|
56
|
+
#
|
57
|
+
# * Requires +str+, a VTT formatted time string.
|
58
|
+
def convert_time(time)
|
59
|
+
matched_time = time.match(/([\d:]*)\.?(\d*)/)
|
60
|
+
to_subs_time(matched_time[0])
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# This method converts time from VTT format to the ASS format.
|
65
|
+
#
|
66
|
+
# * Requires +str+, a VTT formatted time string.
|
67
|
+
def to_subs_time(str)
|
68
|
+
n = []
|
69
|
+
x = str.split(/[:.]/).map(&:to_i)
|
70
|
+
|
71
|
+
ms_len = 2
|
72
|
+
h_len = 1
|
73
|
+
|
74
|
+
x[3] = "0.#{x[3].to_s.rjust(3, '0')}"
|
75
|
+
sx = (x[0] * 60 * 60) + (x[1] * 60) + x[2] + x[3].to_f
|
76
|
+
sx = format('%.2f', sx).split('.')
|
77
|
+
|
78
|
+
n.unshift(pad_time_num('.', sx[1], ms_len))
|
79
|
+
sx = sx[0].to_f
|
80
|
+
|
81
|
+
n.unshift(pad_time_num(':', (sx % 60).to_i, 2))
|
82
|
+
n.unshift(pad_time_num(':', (sx / 60).floor % 60, 2))
|
83
|
+
n.unshift(pad_time_num('', (sx / 3600).floor % 60, h_len))
|
84
|
+
|
85
|
+
n.join
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# This method pads text so that time numbers are a fixed number of digit.
|
90
|
+
#
|
91
|
+
# * Requires +sep+, a string separator.
|
92
|
+
# * Requires +input+, an integer.
|
93
|
+
# * Requires +pad+, an integer for the number of digits to be padded.
|
94
|
+
def pad_time_num(sep, input, pad)
|
95
|
+
sep + input.to_s.rjust(pad, '0')
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ass_style_params'
|
4
|
+
require_relative 'validator'
|
5
|
+
require 'redgreenblue'
|
6
|
+
|
7
|
+
##
|
8
|
+
# This class defines an ASS style that can be applied on a subtitle line.
|
9
|
+
class ASSStyle
|
10
|
+
attr_reader :style_name
|
11
|
+
|
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, line_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 -= line_offset if style_name.include? 'Subtitle'
|
29
|
+
@is_italic = is_italic
|
30
|
+
@is_bold = is_bold
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# This method assigns the object values to an ASS style line and outputs it.
|
35
|
+
def to_s
|
36
|
+
# Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour,
|
37
|
+
# Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment,
|
38
|
+
# MarginL, MarginR, MarginV, Encoding
|
39
|
+
"Style: #{@style_name},#{@font_family},#{@font_size},#{@font_color},&H000000FF,&H00020713,&H00000000,"\
|
40
|
+
"#{@is_bold ? '-1' : '0'},#{@is_italic ? '-1' : '0'},0,0,100,100,0,0,1,2.0,2.0,#{@s_params.alignment},"\
|
41
|
+
"#{@s_params.horizontal_margin},0,#{@s_params.vertical_margin},1"
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# This method returns a ASS formated color value based on hex or color name value
|
46
|
+
def self.convert_color(color_value)
|
47
|
+
color_value.gsub!('#', '')
|
48
|
+
color = Validator.hex?(color_value) ? RGB.hex(color_value) : RGB.css(color_value)
|
49
|
+
format('&H00%<blue>02x%<green>02x%<red>02x', blue: color.b, green: color.g, red: color.r).upcase
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# This class defines the ASS style parameters from VTT cue settings.
|
5
|
+
class ASSStyleParams
|
6
|
+
attr_accessor :horizontal_margin, :vertical_margin, :alignment, :align
|
7
|
+
|
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
|
+
split_params(params)
|
14
|
+
create_alignment
|
15
|
+
create_horizontal_margin(width)
|
16
|
+
create_vertical_margin(height)
|
17
|
+
end
|
18
|
+
|
19
|
+
def split_params(params)
|
20
|
+
(params.split.map { |p| p.split(':') }).each do |p|
|
21
|
+
case p[0]
|
22
|
+
when 'position'
|
23
|
+
@position = p[1].gsub(/%/, '').to_i
|
24
|
+
when 'line'
|
25
|
+
@line = p[1].gsub(/%/, '').to_i
|
26
|
+
@line = @line == -1 ? 100 : @line
|
27
|
+
when 'align'
|
28
|
+
@align = p[1].chomp
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# This method decides the alignement value in a 9 position grid based of the
|
35
|
+
# values in cue settings "align" and "line".
|
36
|
+
def create_alignment
|
37
|
+
@alignment =
|
38
|
+
if defined?(@line) && !defined?(@position)
|
39
|
+
find_alignment(@align)
|
40
|
+
elsif defined?(@line) && defined?(@position)
|
41
|
+
1 # bottom left
|
42
|
+
else
|
43
|
+
find_default_alignment(@align)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# This method returns alignment when "line" value is specified but not "position"
|
49
|
+
def find_alignment(align)
|
50
|
+
if align.nil?
|
51
|
+
# If position is higher than 50% align to bottom center, else align to top center
|
52
|
+
@line >= 50 ? 2 : 8
|
53
|
+
else
|
54
|
+
case align
|
55
|
+
when 'left', 'start'
|
56
|
+
@line >= 50 ? 1 : 7
|
57
|
+
when 'right', 'end'
|
58
|
+
@line >= 50 ? 3 : 9
|
59
|
+
when 'center', 'middle'
|
60
|
+
@line >= 50 ? 2 : 8
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# This method returns alignment when "line" and "position" values are not specified
|
67
|
+
def find_default_alignment(align)
|
68
|
+
case align
|
69
|
+
when 'left', 'start'
|
70
|
+
1
|
71
|
+
when 'right', 'end'
|
72
|
+
3
|
73
|
+
# when 'center', 'middle'
|
74
|
+
else
|
75
|
+
2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# This method calculates the horizontal margin in px between the alignement position and
|
81
|
+
# and the content displayed by using the "position" cue setting.
|
82
|
+
def create_horizontal_margin(width)
|
83
|
+
steps = (width / 100).to_i
|
84
|
+
@horizontal_margin =
|
85
|
+
if defined?(@position)
|
86
|
+
@position * steps
|
87
|
+
else
|
88
|
+
0
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# This method calculates the vertical margin in px between the alignement position and
|
94
|
+
# and the content displayed by using the "line" cue setting.
|
95
|
+
def create_vertical_margin(height)
|
96
|
+
steps = (height / 100).to_i
|
97
|
+
@vertical_margin =
|
98
|
+
if defined?(@line)
|
99
|
+
if @alignment == 1
|
100
|
+
(100 - @line) * steps
|
101
|
+
else
|
102
|
+
@line >= 50 ? (100 - @line) * steps : @line * steps
|
103
|
+
end
|
104
|
+
else
|
105
|
+
50
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'css_parser'
|
4
|
+
require_relative 'css_rule'
|
5
|
+
|
6
|
+
##
|
7
|
+
# This class defines a CSS file for subtitles.
|
8
|
+
class CSSFile
|
9
|
+
attr_reader :rules
|
10
|
+
|
11
|
+
include CssParser
|
12
|
+
|
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?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
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
|
31
|
+
end
|
32
|
+
return_rule
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@file_path
|
37
|
+
end
|
38
|
+
end
|