osdb 0.0.10 → 0.1.0
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.
- data/bin/getsub +122 -160
- data/lib/osdb.rb +10 -5
- data/lib/osdb/finder.rb +8 -0
- data/lib/osdb/finder/first.rb +13 -0
- data/lib/osdb/finder/interactive.rb +21 -0
- data/lib/osdb/finder/score.rb +13 -0
- data/lib/osdb/movie.rb +8 -53
- data/lib/osdb/movie_file.rb +61 -0
- data/lib/osdb/search.rb +9 -0
- data/lib/osdb/search/imdb.rb +24 -0
- data/lib/osdb/search/movie_hash.rb +21 -0
- data/lib/osdb/search/name.rb +28 -0
- data/lib/osdb/search/path.rb +20 -0
- data/lib/osdb/selector.rb +7 -0
- data/lib/osdb/selector/format.rb +17 -0
- data/lib/osdb/selector/movie.rb +31 -0
- data/lib/osdb/server.rb +4 -0
- data/lib/osdb/subtitle_finder.rb +31 -0
- data/lib/osdb/version.rb +1 -1
- data/osdb.gemspec +2 -0
- data/spec/fixtures/http/check_movie_hash.yml +225 -0
- data/spec/fixtures/http/get_imdb_movie_details.yml +342 -0
- data/spec/fixtures/http/log_in.yml +86 -0
- data/spec/fixtures/http/log_out.yml +68 -0
- data/spec/fixtures/http/search_imdb.yml +761 -0
- data/spec/fixtures/http/search_subtitles_for_himym.yml +1189 -0
- data/spec/fixtures/http/search_subtitles_for_the_rock.yml +4124 -0
- data/spec/fixtures/http/server_info.yml +492 -0
- data/spec/osdb/{movie_spec.rb → movie_file_spec.rb} +2 -2
- data/spec/osdb/server_spec.rb +61 -36
- data/spec/spec_helper.rb +9 -1
- metadata +87 -9
data/bin/getsub
CHANGED
@@ -1,197 +1,159 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# TODO: getsub is becomming a bit to complexe, rewrite it OO or move it in another gem
|
3
2
|
require 'optparse'
|
4
3
|
require 'uri'
|
5
4
|
require 'rubygems'
|
6
5
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'osdb'))
|
7
6
|
|
8
|
-
def env_lang
|
9
|
-
OSDb::Language.from_locale(ENV['LANG'])
|
10
|
-
end
|
11
7
|
|
12
|
-
|
13
|
-
@parser ||= OptionParser.new do |opts|
|
14
|
-
opts.banner = "Automatically download subs for your video files using opensubtitle.org"
|
15
|
-
opts.separator ""
|
16
|
-
opts.separator "Usage: getsub [options] DIRECTORY | VIDEO_FILE [VIDEO_FILE ...]"
|
17
|
-
opts.separator ""
|
18
|
-
opts.separator "Main options:"
|
19
|
-
|
20
|
-
opts.on("-l", "--language LANGUAGE", "Sub language ISO 639-2 code like fre or eng. Default: env $LANG (#{env_lang.to_iso639_2b})") do |language|
|
21
|
-
if language.to_s.length != 3
|
22
|
-
STDERR.puts "Language should specified as ISO 639-2 (ie, 3 letters, like 'eng' or 'fre')"
|
23
|
-
exit 1
|
24
|
-
end
|
25
|
-
@options[:language] = language.to_s
|
26
|
-
end
|
8
|
+
class GetSub
|
27
9
|
|
28
|
-
|
29
|
-
@options
|
30
|
-
|
31
|
-
|
32
|
-
|
10
|
+
def initialize
|
11
|
+
@options = {:language => env_lang.to_iso639_2b, :force => false, :dir => nil, :methods => 'h'}
|
12
|
+
@parser ||= OptionParser.new do |opts|
|
13
|
+
opts.banner = "Automatically download subs for your video files using opensubtitle.org"
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "Usage: getsub [options] DIRECTORY | VIDEO_FILE [VIDEO_FILE ...]"
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator "Main options:"
|
33
18
|
|
34
|
-
|
19
|
+
opts.on("-a", "--auto", "Do not ask user to resolve hash conflicts.") { @options[:auto] = true }
|
35
20
|
|
36
|
-
|
37
|
-
|
21
|
+
opts.on("-l", "--language LANGUAGE", "Sub language ISO 639-2 code like fre or eng. Default: env $LANG (#{env_lang.to_iso639_2b})") do |language|
|
22
|
+
if language.to_s.length != 3
|
23
|
+
STDERR.puts "Invalid argument: Language should specified as ISO 639-2 (ie, 3 letters, like 'eng' or 'fre')"
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
@options[:language] = language.to_s
|
27
|
+
end
|
38
28
|
|
39
|
-
|
40
|
-
%x{ curl --version 2> /dev/null > /dev/null }
|
41
|
-
$?.success?
|
42
|
-
end
|
29
|
+
opts.on("-f", "--force", "Download sub even if video already has one") { @options[:force] = true }
|
43
30
|
|
44
|
-
|
45
|
-
%x{ wget --version 2> /dev/null > /dev/null }
|
46
|
-
$?.success?
|
47
|
-
end
|
31
|
+
opts.on("-t", "--type FORMATS", "Select only subtitles in specified formats. e.g -t srt,sub") { |formats| @options[:formats] = formats.to_s.split(',') }
|
48
32
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
puts url
|
58
|
-
end
|
59
|
-
end
|
33
|
+
methods_help = "Ordered list of search methods. h: by movie hash, i: by name on IMDB, n: by name on OSDb, p: by filename on OSDb. e.g -s hi . Default: h"
|
34
|
+
opts.on("-s", "--search-by METHODS", methods_help) do |methods|
|
35
|
+
unless methods =~ /^[hinp]+$/
|
36
|
+
STDERR.puts "Invalid argument: Available search methods are: h, i, n and p."
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
@options[:methods] = methods
|
40
|
+
end
|
60
41
|
|
61
|
-
|
62
|
-
subs.inject({}) do |hash, sub|
|
63
|
-
hash[sub.movie_name] ||= []
|
64
|
-
hash[sub.movie_name] << sub
|
65
|
-
hash
|
42
|
+
end
|
66
43
|
end
|
67
|
-
end
|
68
44
|
|
69
|
-
def
|
70
|
-
|
71
|
-
puts " #{index} - #{name}"
|
45
|
+
def env_lang
|
46
|
+
OSDb::Language.from_locale(ENV['LANG'])
|
72
47
|
end
|
73
|
-
print 'id: '
|
74
|
-
str = STDIN.gets # TODO: rule #1, never trust user input
|
75
|
-
puts
|
76
|
-
movies[movies.keys[str.to_i]] || []
|
77
|
-
end
|
78
|
-
|
79
|
-
def normalize_name(name)
|
80
|
-
name.downcase.gsub(/[\s\.\-\_]+/, ' ')
|
81
|
-
end
|
82
48
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
49
|
+
def run!(files)
|
50
|
+
@parser.parse!
|
51
|
+
|
52
|
+
movie_files = files.map{ |path| OSDb::MovieFile.new(path) }
|
53
|
+
|
54
|
+
movie_files.each do |movie_file|
|
55
|
+
begin
|
56
|
+
puts "* Search subtitles for #{movie_file.name}"
|
57
|
+
if movie_file.has_sub? && !@options[:force]
|
58
|
+
puts "* Sub already there. To override it use --force"
|
59
|
+
puts
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
if sub = subtitle_finder.find_sub_for(movie_file, @options[:language])
|
64
|
+
download_sub!(sub, movie_file)
|
65
|
+
else
|
66
|
+
puts "* No sub found"
|
67
|
+
end
|
68
|
+
puts
|
69
|
+
rescue Exception => e
|
70
|
+
report_exception(e)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
107
74
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
75
|
+
def report_exception(exception)
|
76
|
+
puts "Something crashed."
|
77
|
+
puts "Feel free to report the error here: https://github.com/byroot/ruby-osdb/issues"
|
78
|
+
puts "With the following debug informations:"
|
79
|
+
puts
|
80
|
+
puts "#{exception.class.name}: #{exception.message}:"
|
81
|
+
puts exception.backtrace
|
82
|
+
puts
|
83
|
+
end
|
112
84
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
server = OSDb::Server.new(
|
117
|
-
:host => 'api.opensubtitles.org',
|
118
|
-
:path => '/xml-rpc',
|
119
|
-
:timeout => 90,
|
120
|
-
:useragent => "SubDownloader 2.0.10" # register useragent ? WTF ? too boring.
|
121
|
-
)
|
122
|
-
STDOUT.sync = true
|
123
|
-
|
124
|
-
if movies.empty?
|
125
|
-
puts "No file provided"
|
126
|
-
puts @parser.help
|
127
|
-
exit 1
|
128
|
-
end
|
85
|
+
def subtitle_finder
|
86
|
+
@subtitle_finder ||= OSDb::SubtitleFinder.new(search_engines, finders, selectors)
|
87
|
+
end
|
129
88
|
|
130
|
-
|
131
|
-
|
132
|
-
|
89
|
+
SEARCH_ENGINES = {
|
90
|
+
'h' => OSDb::Search::MovieHash,
|
91
|
+
'i' => OSDb::Search::IMDB,
|
92
|
+
'n' => OSDb::Search::Name,
|
93
|
+
'p' => OSDb::Search::Path
|
94
|
+
}
|
133
95
|
|
134
|
-
def
|
135
|
-
|
136
|
-
|
96
|
+
def search_engines
|
97
|
+
@options[:methods].to_s.each_char.to_a.uniq.map do |char|
|
98
|
+
SEARCH_ENGINES[char.to_s].new(server)
|
99
|
+
end
|
100
|
+
end
|
137
101
|
|
138
|
-
def
|
139
|
-
|
140
|
-
normalized_movie_name = normalize_name(movie.name)
|
141
|
-
subs.select! do |sub|
|
142
|
-
normalize_name(sub.filename).index(normalized_movie_name) # MAYBE: Levenshtein ?
|
102
|
+
def finders
|
103
|
+
[OSDb::Finder::Score.new]
|
143
104
|
end
|
144
|
-
subs if subs.any?
|
145
|
-
end
|
146
105
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
150
|
-
if imdb_results.length == 1
|
151
|
-
imdb_result = imdb_results.first
|
152
|
-
puts "* found on IMDB with ID: #{imdb_result.imdbid}"
|
106
|
+
def selectors
|
107
|
+
movie_finder = if @options[:auto]
|
108
|
+
OSDb::Finder::First.new # TODO: try to match subtitle movie name with filename
|
153
109
|
else
|
154
|
-
|
155
|
-
imdb_result = ask_user_to_identify_movie(movies)
|
110
|
+
OSDb::Finder::Interactive.new
|
156
111
|
end
|
157
|
-
|
112
|
+
|
113
|
+
selectors = [
|
114
|
+
OSDb::Selector::Movie.new(movie_finder)
|
115
|
+
]
|
116
|
+
selectors << OSDb::Selector::Format.new(@options[:formats]) if @options[:formats]
|
117
|
+
selectors
|
158
118
|
end
|
159
|
-
end
|
160
119
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
120
|
+
def server
|
121
|
+
@server ||= OSDb::Server.new(
|
122
|
+
:host => 'api.opensubtitles.org',
|
123
|
+
:path => '/xml-rpc',
|
124
|
+
:timeout => 90,
|
125
|
+
:useragent => "SubDownloader 2.0.10" # register useragent ? WTF ? too boring.
|
126
|
+
)
|
127
|
+
end
|
165
128
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
129
|
+
def download_sub!(sub, movie_file)
|
130
|
+
sub_path = movie_file.sub_path(sub.format)
|
131
|
+
download!(sub.url, sub_path)
|
132
|
+
end
|
170
133
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end
|
134
|
+
def curl_available?
|
135
|
+
%x{ curl --version 2> /dev/null > /dev/null }
|
136
|
+
$?.success?
|
137
|
+
end
|
176
138
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
end
|
139
|
+
def wget_available?
|
140
|
+
%x{ wget --version 2> /dev/null > /dev/null }
|
141
|
+
$?.success?
|
142
|
+
end
|
182
143
|
|
183
|
-
|
184
|
-
|
144
|
+
def download!(url, local_path)
|
145
|
+
# TODO: use Net::HTTP and ZLib to avoid shell dependency
|
146
|
+
puts "* download #{url} to #{local_path}"
|
147
|
+
if curl_available?
|
148
|
+
%x{ curl -s '#{url}' | gunzip > "#{local_path}" }
|
149
|
+
elsif wget_available?
|
150
|
+
%x{ wget -O - -q '#{url}' | gunzip > "#{local_path}"}
|
185
151
|
else
|
186
|
-
puts "
|
152
|
+
puts "Can't found any curl or wget please install one of them or manualy download your sub"
|
153
|
+
puts url
|
187
154
|
end
|
188
|
-
puts
|
189
|
-
rescue Exception => e
|
190
|
-
puts "Something crashed."
|
191
|
-
puts "Feel free to report the error here: https://github.com/byroot/ruby-osdb/issues"
|
192
|
-
puts "With the following debug informations:"
|
193
|
-
puts
|
194
|
-
puts "#{e.class.name}: #{e.message}:"
|
195
|
-
puts e.backtrace
|
196
155
|
end
|
156
|
+
|
197
157
|
end
|
158
|
+
|
159
|
+
GetSub.new.run!(ARGV)
|
data/lib/osdb.rb
CHANGED
@@ -2,8 +2,13 @@ require 'xmlrpc/client'
|
|
2
2
|
|
3
3
|
module OSDb
|
4
4
|
base_path = File.expand_path(File.dirname(__FILE__) + '/osdb')
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
autoload :
|
9
|
-
|
5
|
+
autoload :Finder, "#{base_path}/finder"
|
6
|
+
autoload :Language, "#{base_path}/language"
|
7
|
+
autoload :Movie, "#{base_path}/movie"
|
8
|
+
autoload :MovieFile, "#{base_path}/movie_file"
|
9
|
+
autoload :Search, "#{base_path}/search"
|
10
|
+
autoload :Selector, "#{base_path}/selector"
|
11
|
+
autoload :Server, "#{base_path}/server"
|
12
|
+
autoload :Sub, "#{base_path}/sub"
|
13
|
+
autoload :SubtitleFinder, "#{base_path}/subtitle_finder"
|
14
|
+
end
|
data/lib/osdb/finder.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module OSDb
|
2
|
+
module Finder
|
3
|
+
|
4
|
+
class Interactive
|
5
|
+
|
6
|
+
def chose(items)
|
7
|
+
puts "D'oh! You stumbled upon a hash conflict, please resolve it:"
|
8
|
+
puts
|
9
|
+
items.each_with_index do |name, index|
|
10
|
+
puts " #{index} - #{name}"
|
11
|
+
end
|
12
|
+
print 'id: '
|
13
|
+
str = STDIN.gets # TODO: rule #1, never trust user input
|
14
|
+
puts
|
15
|
+
items[str.to_i]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/osdb/movie.rb
CHANGED
@@ -1,61 +1,16 @@
|
|
1
1
|
module OSDb
|
2
2
|
class Movie
|
3
|
-
|
4
|
-
EXTENSIONS = %w(avi mpg m4v mkv mov ogv mp4)
|
5
|
-
|
6
|
-
attr_reader :path
|
7
|
-
|
8
|
-
def initialize(path)
|
9
|
-
@path = path
|
10
|
-
end
|
11
3
|
|
12
|
-
|
13
|
-
exist = false
|
14
|
-
%w(.srt .sub).each{ |ext| exist ||= File.exist?(path.gsub(File.extname(path), ext)) }
|
15
|
-
exist
|
16
|
-
end
|
4
|
+
attr_reader :id, :title, :year, :cover, :rating, :raw_data
|
17
5
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@
|
24
|
-
|
25
|
-
|
26
|
-
def size
|
27
|
-
@size ||= File.size(path)
|
6
|
+
def initialize(data)
|
7
|
+
@id = data['id']
|
8
|
+
@title = data['title']
|
9
|
+
@year = data['year'] && data['year'].to_i
|
10
|
+
@cover = data['cover']
|
11
|
+
@rating = data['rating'] && data['rating'].to_f
|
12
|
+
@raw_data = data
|
28
13
|
end
|
29
14
|
|
30
|
-
def name
|
31
|
-
@name ||= File.basename(path, File.extname(path))
|
32
|
-
end
|
33
|
-
|
34
|
-
CHUNK_SIZE = 64 * 1024 # in bytes
|
35
|
-
|
36
|
-
# from http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes
|
37
|
-
def self.compute_hash(path)
|
38
|
-
filesize = File.size(path)
|
39
|
-
hash = filesize
|
40
|
-
|
41
|
-
# Read 64 kbytes, divide up into 64 bits and add each
|
42
|
-
# to hash. Do for beginning and end of file.
|
43
|
-
File.open(path, 'rb') do |f|
|
44
|
-
# Q = unsigned long long = 64 bit
|
45
|
-
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
46
|
-
hash = hash + n & 0xffffffffffffffff # to remain as 64 bit number
|
47
|
-
end
|
48
|
-
|
49
|
-
f.seek([0, filesize - CHUNK_SIZE].max, IO::SEEK_SET)
|
50
|
-
|
51
|
-
# And again for the end of the file
|
52
|
-
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
53
|
-
hash = hash + n & 0xffffffffffffffff
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
sprintf("%016x", hash)
|
58
|
-
end
|
59
|
-
|
60
15
|
end
|
61
16
|
end
|