captions 1.2.2 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/console +14 -14
- data/captions.gemspec +27 -27
- data/lib/captions/base.rb +2 -2
- data/lib/captions/cue.rb +93 -66
- data/lib/captions/formats/vtt.rb +121 -75
- data/lib/captions/list.rb +11 -0
- data/lib/captions/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aabb7f8d8e05f0bdd97f11c7d3d6ad2272daa4a8
|
4
|
+
data.tar.gz: 2487803e670a82b5280cbe710d8b1f53aeb7ad7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 247cbaedb31a2c5afc6d987b9521a62ef11e7c40460d1a5831093e00f1308ef682e86ee68832695110f85452dd3365fbdf3ea1d76219147b18838b0dd8812d22
|
7
|
+
data.tar.gz: ad8c0e97e9fb83e97facabfd17058427368672aa5ac2aa79f9f3b5908b5cf2c9e1e23c9a84c5e2ac8e3369e4e527144c6ab557e82126f15eb5cca828657cbe59
|
data/bin/console
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "captions"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "captions"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/captions.gemspec
CHANGED
@@ -1,27 +1,27 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'captions/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "captions"
|
8
|
-
spec.version = Captions::VERSION
|
9
|
-
spec.authors = ["navin"]
|
10
|
-
spec.email = ["navin@amagi.com"]
|
11
|
-
|
12
|
-
spec.summary = %q{Subtitle Editor and Converter written in Ruby}
|
13
|
-
spec.description = %q{Subtitle Editor and Converter written in Ruby. Captions can read/modify/export subtitles from one format to another }
|
14
|
-
spec.homepage = "https://github.com/navinre/captions"
|
15
|
-
spec.license = "MIT"
|
16
|
-
|
17
|
-
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
-
f.match(%r{^(test|spec|features)/})
|
19
|
-
end
|
20
|
-
spec.bindir = "exe"
|
21
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
-
spec.require_paths = ["lib"]
|
23
|
-
|
24
|
-
spec.add_development_dependency "rspec"
|
25
|
-
spec.add_development_dependency "bundler", "~> 1.13"
|
26
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
-
end
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'captions/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "captions"
|
8
|
+
spec.version = Captions::VERSION
|
9
|
+
spec.authors = ["navin"]
|
10
|
+
spec.email = ["navin@amagi.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Subtitle Editor and Converter written in Ruby}
|
13
|
+
spec.description = %q{Subtitle Editor and Converter written in Ruby. Captions can read/modify/export subtitles from one format to another }
|
14
|
+
spec.homepage = "https://github.com/navinre/captions"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
end
|
data/lib/captions/base.rb
CHANGED
@@ -170,9 +170,9 @@ module Captions
|
|
170
170
|
# block is passed, it returns the entire cuelist.
|
171
171
|
def fetch_result(&block)
|
172
172
|
if block_given?
|
173
|
-
@cue_list.select(&block)
|
173
|
+
return @cue_list.select(&block)
|
174
174
|
else
|
175
|
-
@cue_list
|
175
|
+
return @cue_list
|
176
176
|
end
|
177
177
|
end
|
178
178
|
end
|
data/lib/captions/cue.rb
CHANGED
@@ -1,66 +1,93 @@
|
|
1
|
-
module Captions
|
2
|
-
class Cue
|
3
|
-
include Util
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
self.
|
23
|
-
self.
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
self.
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
1
|
+
module Captions
|
2
|
+
class Cue
|
3
|
+
include Util
|
4
|
+
|
5
|
+
# Text Properties supported
|
6
|
+
ALIGNMENT = "alignment"
|
7
|
+
COLOR = "color"
|
8
|
+
POSITION = "position"
|
9
|
+
|
10
|
+
# List of Text Properties
|
11
|
+
TEXT_PROPERTIES = [ALIGNMENT, COLOR, POSITION]
|
12
|
+
|
13
|
+
attr_accessor :number, :start_time, :end_time, :duration, :text, :properties
|
14
|
+
|
15
|
+
# Creates a new Cue class
|
16
|
+
# Each cue denotes a subtitle.
|
17
|
+
def initialize
|
18
|
+
self.text = nil
|
19
|
+
self.start_time = nil
|
20
|
+
self.end_time = nil
|
21
|
+
self.duration = nil
|
22
|
+
self.number = nil
|
23
|
+
self.properties = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets the time for the cue. Both start-time and
|
27
|
+
# end-time can be passed together. This just assigns
|
28
|
+
# the value passed.
|
29
|
+
def set_time(start_time, end_time, duration = nil)
|
30
|
+
self.start_time = start_time
|
31
|
+
self.end_time = end_time
|
32
|
+
self.duration = duration
|
33
|
+
end
|
34
|
+
|
35
|
+
# Getter and Setter methods for Text Properties
|
36
|
+
# These are pre-defined properties. This is just to assign
|
37
|
+
# or access the properties of that text.
|
38
|
+
TEXT_PROPERTIES.each do |setting|
|
39
|
+
define_method :"#{setting}" do
|
40
|
+
if self.properties[setting].present?
|
41
|
+
return self.properties[setting]
|
42
|
+
end
|
43
|
+
return nil
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method :"#{setting}=" do |value|
|
47
|
+
self.properties[setting] = value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Serializes the values set for the cue.
|
52
|
+
# Converts start-time, end-time and duration to milliseconds
|
53
|
+
# If duration is not found, it will be calculated based on
|
54
|
+
# start-time and end-time.
|
55
|
+
def serialize(fps)
|
56
|
+
raise InvalidSubtitle, "Subtitle should have start time" if self.start_time.nil?
|
57
|
+
raise InvalidSubtitle, "Subtitle shold have end time" if self.end_time.nil?
|
58
|
+
|
59
|
+
begin
|
60
|
+
ms_per_frame = (1000.0 / fps)
|
61
|
+
self.start_time = convert_to_msec(self.start_time, ms_per_frame)
|
62
|
+
self.end_time = convert_to_msec(self.end_time, ms_per_frame)
|
63
|
+
if duration.nil?
|
64
|
+
self.duration = self.end_time - self.start_time
|
65
|
+
else
|
66
|
+
self.duration = convert_to_msec(self.duration, ms_per_frame)
|
67
|
+
end
|
68
|
+
rescue
|
69
|
+
raise InvalidSubtitle, "Cannot calculate start-time or end-time"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Changes start-time, end-time and duration based on new frame-rate
|
74
|
+
def change_frame_rate(old_rate, new_rate)
|
75
|
+
self.start_time = convert_frame_rate(self.start_time, old_rate, new_rate)
|
76
|
+
self.end_time = convert_frame_rate(self.end_time, old_rate, new_rate)
|
77
|
+
self.duration = convert_frame_rate(self.duration, old_rate, new_rate)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds text. If text is already found, new-line is appended.
|
81
|
+
def add_text(text)
|
82
|
+
if self.text.nil?
|
83
|
+
self.text = text
|
84
|
+
else
|
85
|
+
self.text += "\n" + text
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def <=>(other_cue)
|
90
|
+
self.start_time <=> other_cue.start_time
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/captions/formats/vtt.rb
CHANGED
@@ -1,75 +1,121 @@
|
|
1
|
-
module Captions
|
2
|
-
class VTT < Base
|
3
|
-
|
4
|
-
# Header used for all VTT files
|
5
|
-
VTT_HEADER = "WEBVTT"
|
6
|
-
|
7
|
-
# VTT file comments/style section
|
8
|
-
VTT_METADATA = /^NOTE|^STYLE/
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
1
|
+
module Captions
|
2
|
+
class VTT < Base
|
3
|
+
|
4
|
+
# Header used for all VTT files
|
5
|
+
VTT_HEADER = "WEBVTT"
|
6
|
+
|
7
|
+
# VTT file comments/style section
|
8
|
+
VTT_METADATA = /^NOTE|^STYLE/
|
9
|
+
|
10
|
+
# Auto Keyword used in Alignment
|
11
|
+
AUTO_KEYWORD = "auto"
|
12
|
+
|
13
|
+
# Alignment Data
|
14
|
+
ALIGNMENT_VALUES = {
|
15
|
+
"middle" => "middle",
|
16
|
+
"left" => "left",
|
17
|
+
"right" => "right",
|
18
|
+
"start" => "start",
|
19
|
+
"end" => "end",
|
20
|
+
}
|
21
|
+
|
22
|
+
# Parse VTT file and update CueList
|
23
|
+
def parse
|
24
|
+
base_parser do
|
25
|
+
count = 1
|
26
|
+
cue_count = 0
|
27
|
+
meta_data_section = false
|
28
|
+
cue = nil
|
29
|
+
raise InvalidSubtitle, "Invalid VTT Signature" unless validate_header(@file.gets)
|
30
|
+
while(line = @file.gets) do
|
31
|
+
line = line.strip
|
32
|
+
if line.empty?
|
33
|
+
meta_data_section = false
|
34
|
+
elsif is_meta_data?(line)
|
35
|
+
meta_data_section = true
|
36
|
+
elsif is_time?(line)
|
37
|
+
@cue_list.append(cue) if cue
|
38
|
+
cue_count += 1
|
39
|
+
cue = Cue.new
|
40
|
+
cue.number = cue_count
|
41
|
+
line = line.split
|
42
|
+
cue.set_time(line[0], line[2])
|
43
|
+
set_properties(cue, line[3..-1])
|
44
|
+
elsif !meta_data_section and is_text?(line)
|
45
|
+
cue.add_text(line)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@cue_list.append(cue) if cue
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Export CueList to VTT file
|
53
|
+
def dump(file)
|
54
|
+
base_dump(file) do |file|
|
55
|
+
file.write(VTT_HEADER)
|
56
|
+
@cue_list.each do |cue|
|
57
|
+
file.write("\n\n")
|
58
|
+
file.write(msec_to_timecode(cue.start_time))
|
59
|
+
file.write(" --> ")
|
60
|
+
file.write(msec_to_timecode(cue.end_time))
|
61
|
+
file.write("\n")
|
62
|
+
file.write(cue.text)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check whether its a VTT_HEADER or not
|
68
|
+
def validate_header(line)
|
69
|
+
!!line.strip.match(/^#{VTT_HEADER}/)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Check whether its a meta-data or not
|
73
|
+
def is_meta_data?(text)
|
74
|
+
!!text.match(VTT_METADATA)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Timecode format used in VTT file
|
78
|
+
def is_time?(text)
|
79
|
+
!!text.match(/^(\d{2}:)?\d{2}:\d{2}.\d{3}.*(\d{2}:)?\d{2}:\d{2}.\d{3}/)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Check whether if its subtilte text or not
|
83
|
+
def is_text?(text)
|
84
|
+
!text.empty? and text.is_a?(String) and text != VTT_HEADER
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_properties(cue, properties)
|
88
|
+
properties.each do |prop|
|
89
|
+
prop, value = prop.split(":")
|
90
|
+
value.gsub!("%","")
|
91
|
+
case prop
|
92
|
+
when "align"
|
93
|
+
cue.alignment = get_alignment(value)
|
94
|
+
when "line"
|
95
|
+
value = value.split(",")[0]
|
96
|
+
cue.position = get_line(value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_alignment(value)
|
102
|
+
raise InvalidSubtitle, "Invalid VTT Alignment Property" unless ALIGNMENT_VALUES[value]
|
103
|
+
return ALIGNMENT_VALUES[value]
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_line(value)
|
107
|
+
raise InvalidSubtitle, "VTT Line property should be a valid number" if !is_integer?(value) and value != AUTO_KEYWORD
|
108
|
+
return value.to_i
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_position(value)
|
112
|
+
raise InvalidSubtitle, "VTT Position should be a valid number" if !is_integer?(value)
|
113
|
+
raise InvalidSubtitle, "VTT Position should be a number between 0 to 100" if (value.to_i < 0) or (value.to_i > 100)
|
114
|
+
return value.to_i
|
115
|
+
end
|
116
|
+
|
117
|
+
def is_integer?(val)
|
118
|
+
val.to_i.to_s == val
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/captions/list.rb
CHANGED
@@ -13,6 +13,11 @@ module Captions
|
|
13
13
|
@fps
|
14
14
|
end
|
15
15
|
|
16
|
+
# Returns the parsed subtitles
|
17
|
+
def entries
|
18
|
+
@list
|
19
|
+
end
|
20
|
+
|
16
21
|
# Hide all cues when inspecting CueList
|
17
22
|
# Show only necessary info rather than printing everything
|
18
23
|
def inspect
|
@@ -36,7 +41,13 @@ module Captions
|
|
36
41
|
|
37
42
|
# Iterate through CueList
|
38
43
|
def each
|
44
|
+
return to_enum(:each) unless block_given?
|
39
45
|
@list.each { |c| yield(c) }
|
40
46
|
end
|
47
|
+
|
48
|
+
# Array based enumerables for cuelist
|
49
|
+
def [](index)
|
50
|
+
@list[index]
|
51
|
+
end
|
41
52
|
end
|
42
53
|
end
|
data/lib/captions/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: captions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- navin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|