subs 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/Rakefile +1 -0
- data/bin/subs +286 -0
- data/lib/subs/language.rb +538 -0
- data/lib/subs/providers/open_subtitles.rb +142 -0
- data/lib/subs/providers/provider.rb +58 -0
- data/lib/subs/providers/sub_db.rb +80 -0
- data/lib/subs/sub_rip_time.rb +47 -0
- data/lib/subs/version.rb +4 -0
- data/lib/subs.rb +192 -0
- data/subs.gemspec +32 -0
- metadata +120 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
module Subs
|
2
|
+
|
3
|
+
class Provider
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :uri
|
7
|
+
attr_reader :user_agent
|
8
|
+
|
9
|
+
def initialize(name, uri, user_agent)
|
10
|
+
@name = name
|
11
|
+
@uri = uri.is_a?(String) ? URI(uri) : uri
|
12
|
+
@user_agent = user_agent
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_result(io, result)
|
16
|
+
Subs.log.debug { "Processing '#{result.name}'"}
|
17
|
+
unless self.is_a?(result.provider)
|
18
|
+
Subs.log.error { "#{@name} cannot process #{result.provider_name} result"}
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module CredentialProvider
|
26
|
+
end
|
27
|
+
|
28
|
+
module HashSearcher
|
29
|
+
|
30
|
+
def compute_hash(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def hash_search(path, *languages)
|
34
|
+
Array.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module LoginProvider
|
39
|
+
|
40
|
+
def login(username, password)
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module FilenameSearcher
|
46
|
+
|
47
|
+
def filename_search(path, *larnguages)
|
48
|
+
Array.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module IMDbSearcher
|
53
|
+
|
54
|
+
def imdb_search(path, imdb_code, *languages)
|
55
|
+
Array.new
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
module Subs
|
3
|
+
|
4
|
+
class SubDB < Provider
|
5
|
+
|
6
|
+
include HashSearcher
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
agent = "SubDB/1.0 (subs/#{Subs::VERSION}; http://github.com/ForeverZer0/subs)"
|
10
|
+
super('TheSubDB.com', URI('http://api.thesubdb.com/'), agent)
|
11
|
+
yield self if block_given?
|
12
|
+
end
|
13
|
+
|
14
|
+
def compute_hash(path)
|
15
|
+
size = 64 * 1024
|
16
|
+
File.open(path, 'rb') do |io|
|
17
|
+
buffer = io.read(size)
|
18
|
+
io.seek(-size, IO::SEEK_END)
|
19
|
+
buffer << io.read
|
20
|
+
return Digest::MD5.hexdigest(buffer)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_result(io, result)
|
25
|
+
return false unless super
|
26
|
+
hash = result.data
|
27
|
+
lang = result.language.alpha2
|
28
|
+
request = Net::HTTP::Get.new(@uri.path + Subs.query_string(action: :download, hash: hash, language: lang ))
|
29
|
+
request['User-Agent'] = @user_agent
|
30
|
+
begin
|
31
|
+
Net::HTTP.start(@uri.host, @uri.port) do |net|
|
32
|
+
response = net.request(request)
|
33
|
+
return false unless response.code.to_i == 200
|
34
|
+
io.write(response.body)
|
35
|
+
Subs.log.debug { 'Success'.green }
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
rescue
|
39
|
+
Subs.log.debug { 'Failed'.red }
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def hash_search(path, *languages)
|
45
|
+
results = []
|
46
|
+
unless File.exist?(path)
|
47
|
+
Subs.log.error { "Cannot search, cannot find '#{path}'" }
|
48
|
+
return results
|
49
|
+
end
|
50
|
+
hash = compute_hash(path)
|
51
|
+
Subs.log.debug { "Searching #{@name.blue} by hash using '#{hash}'" }
|
52
|
+
supported = supported_languages(hash)
|
53
|
+
languages.each do |language|
|
54
|
+
next unless supported.include?(language)
|
55
|
+
name = File.basename(Subs.build_subtitle_path(path, language))
|
56
|
+
results << Subs::SearchResult.new(@name, self.class, name, language, path, hash)
|
57
|
+
end
|
58
|
+
Subs.log.debug { "Found #{results.size.to_s.light_blue} result(s)." }
|
59
|
+
results
|
60
|
+
end
|
61
|
+
|
62
|
+
def supported_languages(hash)
|
63
|
+
request = Net::HTTP::Get.new(@uri.path + Subs.query_string(action: 'search', hash: hash))
|
64
|
+
request['User-Agent'] = @user_agent
|
65
|
+
Net::HTTP.start(@uri.host, @uri.port) do |net|
|
66
|
+
body = net.request(request).body
|
67
|
+
body.split(',').map { |value| Subs::Language.from_alpha2(value) }.compact
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def all_supported_languages
|
72
|
+
request = Net::HTTP::Get.new(@uri.path + Subs.query_string(action: 'languages'))
|
73
|
+
request['User-Agent'] = @user_agent
|
74
|
+
Net::HTTP.start(@uri.host, @uri.port) do |net|
|
75
|
+
body = net.request(request).body
|
76
|
+
body.split(',').map { |value| Subs::Language.from_alpha2(value) }.compact
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Subs
|
2
|
+
class SubRipTime
|
3
|
+
|
4
|
+
def initialize(hour, minute, second, millisecond)
|
5
|
+
@ms = millisecond
|
6
|
+
@ms += second * 1000
|
7
|
+
@ms += minute * 60 * 1000
|
8
|
+
@ms += hour * 60 * 60 * 1000
|
9
|
+
end
|
10
|
+
|
11
|
+
ZERO = SubRipTime.new(0, 0, 0, 0).freeze
|
12
|
+
|
13
|
+
def total_ms
|
14
|
+
@ms
|
15
|
+
end
|
16
|
+
|
17
|
+
def -(amount)
|
18
|
+
value = amount.is_a?(SubRipTime) ? amount.total_ms : Integer(amount)
|
19
|
+
self.class.new(0, 0, 0, [@ms - value, 0].max)
|
20
|
+
end
|
21
|
+
|
22
|
+
def +(amount)
|
23
|
+
value = amount.is_a?(SubRipTime) ? amount.total_ms : Integer(amount)
|
24
|
+
self.class.new(0, 0, 0, [@ms + value, 0].max)
|
25
|
+
end
|
26
|
+
|
27
|
+
def hours
|
28
|
+
@ms / (1000 * 60 * 60)
|
29
|
+
end
|
30
|
+
|
31
|
+
def minutes
|
32
|
+
(@ms / (1000 * 60)) % 60
|
33
|
+
end
|
34
|
+
|
35
|
+
def seconds
|
36
|
+
(@ms / 1000) % 60
|
37
|
+
end
|
38
|
+
|
39
|
+
def milliseconds
|
40
|
+
@ms % 1000
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"%02d:%02d:%02d,%03d" % [hours, minutes, seconds, milliseconds]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/subs/version.rb
ADDED
data/lib/subs.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'zlib'
|
3
|
+
require 'digest'
|
4
|
+
require 'net/http'
|
5
|
+
require 'cgi'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
require_relative 'subs/version'
|
9
|
+
require_relative 'subs/language'
|
10
|
+
require_relative 'subs/sub_rip_time'
|
11
|
+
require_relative 'subs/providers/provider'
|
12
|
+
require_relative 'subs/providers/sub_db'
|
13
|
+
require_relative 'subs/providers/open_subtitles'
|
14
|
+
|
15
|
+
class String
|
16
|
+
|
17
|
+
# Defines methods for displaying colorized console output, unless already provided by another gem.
|
18
|
+
|
19
|
+
[:red, :green, :yellow, :blue, :pink, :light_blue].each_with_index do |color, i|
|
20
|
+
next if method_defined?(color)
|
21
|
+
class_eval("def #{color};\"\e[#{i + 31}m\#{self}\e[0m\";end", __FILE__ , __LINE__ )
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# @!method red
|
26
|
+
# @return [String]
|
27
|
+
|
28
|
+
##
|
29
|
+
# @!method green
|
30
|
+
# @return [String]
|
31
|
+
|
32
|
+
##
|
33
|
+
# @!method yellow
|
34
|
+
# @return [String]
|
35
|
+
|
36
|
+
##
|
37
|
+
# @!method blue
|
38
|
+
# @return [String]
|
39
|
+
|
40
|
+
##
|
41
|
+
# @!method pink
|
42
|
+
# @return [String]
|
43
|
+
|
44
|
+
##
|
45
|
+
# @!method light_blue
|
46
|
+
# @return [String]
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Top-level namespace for the gem.
|
51
|
+
module Subs
|
52
|
+
|
53
|
+
##
|
54
|
+
# Represents a generic search result for a subtitle
|
55
|
+
SearchResult = Struct.new(:provider_name, :provider, :name, :language, :video, :data)
|
56
|
+
|
57
|
+
##
|
58
|
+
# Represent a movie.
|
59
|
+
Movie = Struct.new(:name, :year, :imdb)
|
60
|
+
|
61
|
+
##
|
62
|
+
# Video extensions to search for.
|
63
|
+
VIDEO_EXTENSIONS = %w(.avi .mkv .mp4 .mov .mpg .wmv .rm .rmvb .divx).freeze
|
64
|
+
|
65
|
+
##
|
66
|
+
# Subtitle extensions to search for.
|
67
|
+
SUB_EXTENSIONS = %w(.srt .sub).freeze
|
68
|
+
|
69
|
+
##
|
70
|
+
# Creates the logger with the specified output stream and verbosity level.
|
71
|
+
#
|
72
|
+
# @param io [IO] An IO instance that can be written to.
|
73
|
+
# @param verbosity [:info|:warn|:error|:fatal|:debug] The logger verbosity level.
|
74
|
+
#
|
75
|
+
# @return [Logger] the created Logger instance.
|
76
|
+
def self.create_log(io = STDOUT, verbosity = :info)
|
77
|
+
unless @log
|
78
|
+
require 'logger'
|
79
|
+
@log = Logger.new(io)
|
80
|
+
@log.formatter = proc do |severity, datetime, _, msg|
|
81
|
+
"[%s] %s -- %s\n" % [datetime.strftime('%Y-%m-%d %H:%M:%S'), severity, msg]
|
82
|
+
end
|
83
|
+
@log.level = case verbosity
|
84
|
+
when :warn then Logger::WARN
|
85
|
+
when :error then Logger::ERROR
|
86
|
+
when :fatal then Logger::FATAL
|
87
|
+
when :debug then Logger::DEBUG
|
88
|
+
else Logger::INFO
|
89
|
+
end
|
90
|
+
end
|
91
|
+
@log
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# @return [Logger] the logger instance for the module.
|
96
|
+
def self.log
|
97
|
+
@log ||= create_log(STDOUT)
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Searches the specified directory for supported video files.
|
102
|
+
#
|
103
|
+
# @param directory [String] Path to a directory to search.
|
104
|
+
# @param recursive [Boolean] `true` to search recursively within nested directories, otherwise `false`.
|
105
|
+
#
|
106
|
+
# @return [Array<String>] paths to all found video files.
|
107
|
+
#
|
108
|
+
def self.video_search(directory, recursive)
|
109
|
+
VIDEO_EXTENSIONS.flat_map do |ext|
|
110
|
+
Dir.glob(File.join(directory, recursive ? "**/*#{ext}" : "*#{ext}"))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Checks the specified video file for the existence of a subtitles, using common naming conventions and optional
|
116
|
+
# language.
|
117
|
+
#
|
118
|
+
# @param video_path [String] The path to the video file to check.
|
119
|
+
# @param language [Language] A specific language to check.
|
120
|
+
#
|
121
|
+
# @return [Boolean] `true` if a matching subtitle was found, otherwise `false`.
|
122
|
+
#
|
123
|
+
def self.subtitle_exist?(video_path, language = nil)
|
124
|
+
dir = File.dirname(video_path)
|
125
|
+
# ex. '/home/me/Videos/MyFavoriteMovie.2019.mp4' => 'MyFavoriteMovie.2019'
|
126
|
+
base = File.basename(video_path, File.extname(video_path))
|
127
|
+
# Check each supported subtitle extension
|
128
|
+
SUB_EXTENSIONS.each do |ext|
|
129
|
+
# ex. MyFavoriteMovie.2019.srt
|
130
|
+
return true if File.exist?(File.join(dir, "#{base}#{ext}"))
|
131
|
+
next unless language
|
132
|
+
if language.alpha2
|
133
|
+
# ex. MyFavoriteMovie.2019.en.srt
|
134
|
+
return true if File.exist?(File.join(dir, "#{base}.#{language.alpha2}#{ext}"))
|
135
|
+
end
|
136
|
+
# ex. MyFavoriteMovie.2019.eng.srt
|
137
|
+
return true if File.exist?(File.join(dir, "#{base}.#{language.alpha3}#{ext}"))
|
138
|
+
end
|
139
|
+
# Not found
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Creates a query string to be used within a URI based on specified parameters.
|
145
|
+
#
|
146
|
+
# @param params [Hash<Symbol, Object>] A hash of keyword arguments that are used to build the query.
|
147
|
+
#
|
148
|
+
# @return [String] The constructed query string.
|
149
|
+
def self.query_string(**params)
|
150
|
+
query = ''
|
151
|
+
params.each_pair do |key, value|
|
152
|
+
next unless value
|
153
|
+
query << (query.size.zero? ? '?' : '&')
|
154
|
+
query << CGI.escape(key.to_s)
|
155
|
+
query << '='
|
156
|
+
query << CGI.escape(value.to_s)
|
157
|
+
end
|
158
|
+
query
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Convenience method to attempt getting basic movie information with the specified file.
|
163
|
+
#
|
164
|
+
# @param path [String] A path to a video file.
|
165
|
+
#
|
166
|
+
# @return [Movie?] Movie information with result, or `nil` if none could be found.
|
167
|
+
def self.fuzzy_search(path)
|
168
|
+
return nil unless File.exist?(path)
|
169
|
+
result = nil
|
170
|
+
OpenSubtitles.new { |provider| result = provider.fuzzy_search(File.basename(path)) }
|
171
|
+
result
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Uses the path of a video file to create a path to a matching subtitle file.
|
176
|
+
#
|
177
|
+
# @param path [String] The path to the video file.
|
178
|
+
# @param language [Language] A language for applying a 3-letter language suffix to the file, or `nil` to omit suffix.
|
179
|
+
# @param ext [String] The language file extension, including leading dot.
|
180
|
+
#
|
181
|
+
# @return [String] The created subtitle path.
|
182
|
+
def self.build_subtitle_path(path, language = nil, ext = '.srt')
|
183
|
+
dir = File.dirname(path)
|
184
|
+
base = File.basename(path, File.extname(path))
|
185
|
+
if language
|
186
|
+
File.join(dir, "#{base}.#{language.alpha3}#{ext}")
|
187
|
+
else
|
188
|
+
File.join(dir, "#{base}#{ext}")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
data/subs.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'subs/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'subs'
|
8
|
+
spec.version = Subs::VERSION
|
9
|
+
spec.authors = ['ForeverZer0']
|
10
|
+
spec.email = ['efreed09@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = %q{Simple and intuitive command-line program to automatically and accurately search and download subtitles for all of your favorite movies and television shows.}
|
13
|
+
spec.description = %q{Simple and intuitive command-line program to automatically and accurately search and download subtitles for all of your favorite movies and television shows. Utilizes multiple providers search algorithms ensure you always get the best result, and includes tools to resync SubRip files with ease.}
|
14
|
+
spec.homepage = 'https://github.com/ForeverZer0/subs'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
# Specify which files should be added to the gem when it is released.
|
18
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
end
|
22
|
+
|
23
|
+
spec.bindir = 'bin'
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ['lib']
|
26
|
+
|
27
|
+
spec.add_runtime_dependency 'xmlrpc', '~> 0.3'
|
28
|
+
spec.add_runtime_dependency 'thor', '~> 0.20'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
31
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: subs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ForeverZer0
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: xmlrpc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.20'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.20'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
description: Simple and intuitive command-line program to automatically and accurately
|
70
|
+
search and download subtitles for all of your favorite movies and television shows.
|
71
|
+
Utilizes multiple providers search algorithms ensure you always get the best result,
|
72
|
+
and includes tools to resync SubRip files with ease.
|
73
|
+
email:
|
74
|
+
- efreed09@gmail.com
|
75
|
+
executables:
|
76
|
+
- subs
|
77
|
+
extensions: []
|
78
|
+
extra_rdoc_files: []
|
79
|
+
files:
|
80
|
+
- ".gitignore"
|
81
|
+
- ".travis.yml"
|
82
|
+
- CODE_OF_CONDUCT.md
|
83
|
+
- Gemfile
|
84
|
+
- LICENSE.txt
|
85
|
+
- README.md
|
86
|
+
- Rakefile
|
87
|
+
- bin/subs
|
88
|
+
- lib/subs.rb
|
89
|
+
- lib/subs/language.rb
|
90
|
+
- lib/subs/providers/open_subtitles.rb
|
91
|
+
- lib/subs/providers/provider.rb
|
92
|
+
- lib/subs/providers/sub_db.rb
|
93
|
+
- lib/subs/sub_rip_time.rb
|
94
|
+
- lib/subs/version.rb
|
95
|
+
- subs.gemspec
|
96
|
+
homepage: https://github.com/ForeverZer0/subs
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubygems_version: 3.0.3
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Simple and intuitive command-line program to automatically and accurately
|
119
|
+
search and download subtitles for all of your favorite movies and television shows.
|
120
|
+
test_files: []
|