vtt2ass 0.3.2 → 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/vtt2ass.svg)](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
|