storyboard 0.2.3
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/.gitignore +3 -0
- data/.gitmodules +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +55 -0
- data/README.md +40 -0
- data/TODO +6 -0
- data/bin/storyboard +82 -0
- data/bin/storyboard-ffprobe +0 -0
- data/lib/.DS_Store +0 -0
- data/lib/storyboard/generators/pdf.rb +46 -0
- data/lib/storyboard/generators/sub.rb +32 -0
- data/lib/storyboard/subtitles.rb +96 -0
- data/lib/storyboard/thread-util.rb +308 -0
- data/lib/storyboard/time.rb +34 -0
- data/lib/storyboard/version.rb +3 -0
- data/lib/storyboard.rb +119 -0
- data/storyboard.gemspec +56 -0
- data/vendor/suby/.gitignore +3 -0
- data/vendor/suby/LICENSE +19 -0
- data/vendor/suby/README.md +27 -0
- data/vendor/suby/bin/suby +30 -0
- data/vendor/suby/lib/suby/downloader/addic7ed.rb +65 -0
- data/vendor/suby/lib/suby/downloader/opensubtitles.rb +83 -0
- data/vendor/suby/lib/suby/downloader/tvsubtitles.rb +90 -0
- data/vendor/suby/lib/suby/downloader.rb +177 -0
- data/vendor/suby/lib/suby/filename_parser.rb +103 -0
- data/vendor/suby/lib/suby/interface.rb +17 -0
- data/vendor/suby/lib/suby/movie_hasher.rb +31 -0
- data/vendor/suby/lib/suby.rb +89 -0
- data/vendor/suby/spec/fixtures/.gitkeep +0 -0
- data/vendor/suby/spec/mock_http.rb +22 -0
- data/vendor/suby/spec/spec_helper.rb +3 -0
- data/vendor/suby/spec/suby/downloader/addict7ed_spec.rb +28 -0
- data/vendor/suby/spec/suby/downloader/opensubtitles_spec.rb +33 -0
- data/vendor/suby/spec/suby/downloader/tvsubtitles_spec.rb +50 -0
- data/vendor/suby/spec/suby/downloader_spec.rb +11 -0
- data/vendor/suby/spec/suby/filename_parser_spec.rb +66 -0
- data/vendor/suby/spec/suby_spec.rb +27 -0
- data/vendor/suby/suby.gemspec +20 -0
- metadata +232 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'cgi/util'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'xmlrpc/client'
|
5
|
+
require 'zlib'
|
6
|
+
require 'stringio'
|
7
|
+
|
8
|
+
module Suby
|
9
|
+
class Downloader
|
10
|
+
DOWNLOADERS = []
|
11
|
+
def self.inherited(downloader)
|
12
|
+
DOWNLOADERS << downloader
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :show, :season, :episode, :video_data, :file, :lang
|
16
|
+
|
17
|
+
def initialize(file, *args)
|
18
|
+
@file = file
|
19
|
+
@lang = (args.last || 'en').to_sym
|
20
|
+
@video_data = FilenameParser.parse(file)
|
21
|
+
if video_data[:type] == :tvshow
|
22
|
+
@show, @season, @episode = video_data.values_at(:show, :season, :episode)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def support_video_type?
|
27
|
+
self.class::SUBTITLE_TYPES.include? video_data[:type]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
self.class.name.sub(/^.+::/, '')
|
32
|
+
end
|
33
|
+
|
34
|
+
def http
|
35
|
+
@http ||= Net::HTTP.new(self.class::SITE).start
|
36
|
+
end
|
37
|
+
|
38
|
+
def xmlrpc
|
39
|
+
@xmlrpc ||= XMLRPC::Client.new(self.class::SITE, self.class::XMLRPC_PATH)
|
40
|
+
end
|
41
|
+
|
42
|
+
def get(path, initheader = {}, parse_response = true)
|
43
|
+
response = http.get(path, initheader)
|
44
|
+
if parse_response
|
45
|
+
unless Net::HTTPSuccess === response
|
46
|
+
raise DownloaderError, "Invalid response for #{path}: #{response}"
|
47
|
+
end
|
48
|
+
response.body
|
49
|
+
else
|
50
|
+
response
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def post(path, data = {}, initheader = {})
|
55
|
+
post = Net::HTTP::Post.new(path, initheader)
|
56
|
+
post.form_data = data
|
57
|
+
response = http.request(post)
|
58
|
+
unless Net::HTTPSuccess === response
|
59
|
+
raise DownloaderError, "Invalid response for #{path}(#{data}): " +
|
60
|
+
response.inspect
|
61
|
+
end
|
62
|
+
response.body
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_redirection(path, initheader = {})
|
66
|
+
response = http.get(path, initheader)
|
67
|
+
location = response['Location']
|
68
|
+
unless (Net::HTTPFound === response or
|
69
|
+
Net::HTTPSuccess === response) and location
|
70
|
+
raise DownloaderError, "Invalid response for #{path}: " +
|
71
|
+
"#{response}: location: #{location.inspect}, #{response.body}"
|
72
|
+
end
|
73
|
+
location
|
74
|
+
end
|
75
|
+
|
76
|
+
def download
|
77
|
+
save extract download_url
|
78
|
+
end
|
79
|
+
|
80
|
+
def subtitles(url_or_response = download_url)
|
81
|
+
if Net::HTTPSuccess === url_or_response
|
82
|
+
url_or_response.body
|
83
|
+
else
|
84
|
+
get(url_or_response)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def save(contents)
|
89
|
+
sub_name(contents).write
|
90
|
+
end
|
91
|
+
|
92
|
+
def extract(url_or_response)
|
93
|
+
contents = subtitles(url_or_response)
|
94
|
+
http.finish
|
95
|
+
format = self.class::FORMAT
|
96
|
+
case format
|
97
|
+
when :file
|
98
|
+
# nothing special to do
|
99
|
+
when :gz
|
100
|
+
begin
|
101
|
+
gz = Zlib::GzipReader.new(StringIO.new(contents))
|
102
|
+
contents = gz.read
|
103
|
+
ensure
|
104
|
+
gz.close if gz
|
105
|
+
end
|
106
|
+
when :zip
|
107
|
+
TEMP_ARCHIVE.write contents
|
108
|
+
Suby.extract_sub_from_archive(TEMP_ARCHIVE, format, TEMP_SUBTITLES)
|
109
|
+
contents = TEMP_SUBTITLES.read
|
110
|
+
else
|
111
|
+
raise "unknown subtitles format: #{format}"
|
112
|
+
end
|
113
|
+
encode contents
|
114
|
+
end
|
115
|
+
|
116
|
+
def sub_name(contents)
|
117
|
+
file.sub_ext sub_extension(contents)
|
118
|
+
end
|
119
|
+
|
120
|
+
def sub_extension(contents)
|
121
|
+
if contents[0..10] =~ /1\r?\n/
|
122
|
+
'srt'
|
123
|
+
else
|
124
|
+
'sub'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def imdbid
|
129
|
+
@imdbid ||= begin
|
130
|
+
nfo_file = find_nfo_file
|
131
|
+
convert_to_utf8_from_latin1(nfo_file.read)[%r!imdb\.[^/]+/title/tt(\d+)!i, 1] if nfo_file
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def find_nfo_file
|
136
|
+
@file.dir.children.find { |file| file.ext == "nfo" }
|
137
|
+
end
|
138
|
+
|
139
|
+
def convert_to_utf8_from_latin1(content)
|
140
|
+
if content.valid_encoding?
|
141
|
+
content
|
142
|
+
else
|
143
|
+
enc = content.encoding
|
144
|
+
if content.force_encoding("ISO-8859-1").valid_encoding?
|
145
|
+
yield if block_given?
|
146
|
+
content.encode("UTF-8")
|
147
|
+
else
|
148
|
+
# restore original encoding
|
149
|
+
subtitles.force_encoding(enc)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def success_message
|
155
|
+
"Found"
|
156
|
+
end
|
157
|
+
|
158
|
+
def encode(subtitles)
|
159
|
+
if @lang == :fr
|
160
|
+
convert_to_utf8_from_latin1(subtitles) do
|
161
|
+
def self.success_message
|
162
|
+
"#{super} (transcoded from ISO-8859-1)"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
else
|
166
|
+
subtitles
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Defines downloader order
|
173
|
+
%w[
|
174
|
+
opensubtitles
|
175
|
+
tvsubtitles
|
176
|
+
addic7ed
|
177
|
+
].each { |downloader| require_relative "downloader/#{downloader}" }
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Suby
|
2
|
+
module FilenameParser
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# from tvnamer @ ab2c6c, with author's agreement, adapted
|
6
|
+
# See https://github.com/dbr/tvnamer/blob/master/tvnamer/config_defaults.py
|
7
|
+
TVSHOW_PATTERNS = [
|
8
|
+
# foo.s0101
|
9
|
+
/^(?<show>.+?)
|
10
|
+
[ \._\-]
|
11
|
+
[Ss](?<season>[0-9]{2})
|
12
|
+
[\.\- ]?
|
13
|
+
(?<episode>[0-9]{2})
|
14
|
+
[^0-9]*$/x,
|
15
|
+
|
16
|
+
# foo.1x09*
|
17
|
+
/^(?<show>.+?)
|
18
|
+
[ \._\-]
|
19
|
+
\[?
|
20
|
+
(?<season>[0-9]+)
|
21
|
+
[xX]
|
22
|
+
(?<episode>[0-9]+)
|
23
|
+
\]?
|
24
|
+
[^\/]*$/x,
|
25
|
+
|
26
|
+
# foo.s01.e01, foo.s01_e01
|
27
|
+
/^(?<show>.+?)
|
28
|
+
[ \._\-]
|
29
|
+
\[?
|
30
|
+
[Ss](?<season>[0-9]+)[\. _-]?
|
31
|
+
[Ee]?(?<episode>[0-9]+)
|
32
|
+
\]?
|
33
|
+
[^\/]*$/x,
|
34
|
+
|
35
|
+
# foo - [01.09]
|
36
|
+
/^(?<show>.+?)
|
37
|
+
[ \._\-]?
|
38
|
+
\[
|
39
|
+
(?<season>[0-9]+?)
|
40
|
+
[.]
|
41
|
+
(?<episode>[0-9]+?)
|
42
|
+
\]
|
43
|
+
[ \._\-]?
|
44
|
+
[^\/]*$/x,
|
45
|
+
|
46
|
+
# Foo - S2 E 02 - etc
|
47
|
+
/^(?<show>.+?)
|
48
|
+
[ ]?[ \._\-][ ]?
|
49
|
+
[Ss](?<season>[0-9]+)[\.\- ]?
|
50
|
+
[Ee]?[ ]?(?<episode>[0-9]+)
|
51
|
+
[^\/]*$/x,
|
52
|
+
|
53
|
+
# Show - Episode 9999 [S 12 - Ep 131] - etc
|
54
|
+
/(?<show>.+)
|
55
|
+
[ ]-[ ]
|
56
|
+
[Ee]pisode[ ]\d+
|
57
|
+
[ ]
|
58
|
+
\[
|
59
|
+
[sS][ ]?(?<season>\d+)
|
60
|
+
([ ]|[ ]-[ ]|-)
|
61
|
+
([eE]|[eE]p)[ ]?(?<episode>\d+)
|
62
|
+
\]
|
63
|
+
.*$/x,
|
64
|
+
|
65
|
+
# foo.103*
|
66
|
+
/^(?<show>.+)
|
67
|
+
[ \._\-]
|
68
|
+
(?<season>[0-9]{1})
|
69
|
+
(?<episode>[0-9]{2})
|
70
|
+
[\._ -][^\/]*$/x,
|
71
|
+
]
|
72
|
+
MOVIE_PATTERN = /^(?<movie>.*)[.\[( ](?<year>(?:19|20)\d{2})/
|
73
|
+
|
74
|
+
def parse(file)
|
75
|
+
filename = file.basename.to_s
|
76
|
+
if TVSHOW_PATTERNS.find { |pattern| pattern.match(filename) }
|
77
|
+
m = $~
|
78
|
+
{ type: :tvshow, show: clean_show_name(m[:show]), season: m[:season].to_i, episode: m[:episode].to_i }
|
79
|
+
elsif m = MOVIE_PATTERN.match(filename)
|
80
|
+
{ type: :movie, name: clean_show_name(m[:movie]), year: m[:year].to_i }
|
81
|
+
else
|
82
|
+
{ type: :unknown, name: filename }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# from https://github.com/dbr/tvnamer/blob/master/tvnamer/utils.py#L78-95
|
87
|
+
# Cleans up series name by removing any . and _
|
88
|
+
# characters, along with any trailing hyphens.
|
89
|
+
#
|
90
|
+
# Is basically equivalent to replacing all _ and . with a
|
91
|
+
# space, but handles decimal numbers in string.
|
92
|
+
#
|
93
|
+
# clean_show_name("an.example.1.0.test") # => "an example 1.0 test"
|
94
|
+
# clean_show_name("an_example_1.0_test") # => "an example 1.0 test"
|
95
|
+
def clean_show_name show
|
96
|
+
show.gsub!(/(?<!\d)[.]|[.](?!\d)/, ' ')
|
97
|
+
show.tr!('_', ' ')
|
98
|
+
show.chomp!('-')
|
99
|
+
show.strip!
|
100
|
+
show
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
module Suby
|
4
|
+
module Interface
|
5
|
+
def success message
|
6
|
+
puts Term::ANSIColor.green message
|
7
|
+
end
|
8
|
+
|
9
|
+
def failure message
|
10
|
+
puts Term::ANSIColor.blue message
|
11
|
+
end
|
12
|
+
|
13
|
+
def error message
|
14
|
+
puts Term::ANSIColor.red message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Suby
|
2
|
+
# from http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes
|
3
|
+
module MovieHasher
|
4
|
+
|
5
|
+
CHUNK_SIZE = 64 * 1024 # in bytes
|
6
|
+
MASK64 = 0xffffffffffffffff # 2^64 - 1
|
7
|
+
|
8
|
+
def self.compute_hash(file)
|
9
|
+
filesize = file.size
|
10
|
+
hash = filesize
|
11
|
+
|
12
|
+
# Read 64 kbytes, divide up into 64 bits and add each
|
13
|
+
# to hash. Do for beginning and end of file.
|
14
|
+
file.open('rb') do |f|
|
15
|
+
# Q = unsigned long long = 64 bit
|
16
|
+
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
17
|
+
hash = (hash + n) & MASK64
|
18
|
+
end
|
19
|
+
|
20
|
+
f.seek([0, filesize - CHUNK_SIZE].max, IO::SEEK_SET)
|
21
|
+
|
22
|
+
# And again for the end of the file
|
23
|
+
f.read(CHUNK_SIZE).unpack("Q*").each do |n|
|
24
|
+
hash = (hash + n) & MASK64
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
"%016x" % hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'path'
|
2
|
+
require 'zip/zip'
|
3
|
+
require 'mime/types'
|
4
|
+
|
5
|
+
Path.require_tree 'suby', except: %w[downloader/]
|
6
|
+
|
7
|
+
module Suby
|
8
|
+
NotFoundError = Class.new StandardError
|
9
|
+
DownloaderError = Class.new StandardError
|
10
|
+
|
11
|
+
SUB_EXTENSIONS = %w[srt sub]
|
12
|
+
TMPDIR = Path.tmpdir
|
13
|
+
TEMP_ARCHIVE = TMPDIR / 'archive'
|
14
|
+
TEMP_SUBTITLES = TMPDIR / 'subtitles'
|
15
|
+
|
16
|
+
class << self
|
17
|
+
include Interface
|
18
|
+
|
19
|
+
def download_subtitles(files, options = {})
|
20
|
+
Zip.options[:on_exists_proc] = options[:force]
|
21
|
+
files.each { |file|
|
22
|
+
file = Path(file)
|
23
|
+
if file.dir?
|
24
|
+
download_subtitles(file.children, options)
|
25
|
+
elsif SUB_EXTENSIONS.include?(file.ext)
|
26
|
+
# ignore already downloaded subtitles
|
27
|
+
elsif !options[:force] and SUB_EXTENSIONS.any? { |ext| f = file.sub_ext(ext) and f.exist? and !f.empty? }
|
28
|
+
puts "Skipping: #{file}"
|
29
|
+
elsif !file.exist? or video?(file)
|
30
|
+
download_subtitles_for_file(file, options)
|
31
|
+
end
|
32
|
+
}
|
33
|
+
ensure
|
34
|
+
TMPDIR.rm_rf
|
35
|
+
end
|
36
|
+
|
37
|
+
def video?(file)
|
38
|
+
MIME::Types.type_for(file.path).any? { |type| type.media_type == "video" }
|
39
|
+
end
|
40
|
+
|
41
|
+
def download_subtitles_for_file(file, options)
|
42
|
+
begin
|
43
|
+
puts file
|
44
|
+
success = Downloader::DOWNLOADERS.find { |downloader_class|
|
45
|
+
try_downloader(downloader_class.new(file, options[:lang]))
|
46
|
+
}
|
47
|
+
error "\nNo downloader could find subtitles for #{file}" unless success
|
48
|
+
rescue
|
49
|
+
error "\nThe download of the subtitles failed for #{file}:"
|
50
|
+
error "#{$!.class}: #{$!.message}"
|
51
|
+
$stderr.puts $!.backtrace
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def try_downloader(downloader)
|
56
|
+
return false unless downloader.support_video_type?
|
57
|
+
begin
|
58
|
+
print " #{downloader.to_s.ljust(20)}"
|
59
|
+
downloader.download
|
60
|
+
rescue Suby::NotFoundError => error
|
61
|
+
failure "Failed: #{error.message}"
|
62
|
+
false
|
63
|
+
rescue Suby::DownloaderError => error
|
64
|
+
error "Error: #{error.message}"
|
65
|
+
false
|
66
|
+
else
|
67
|
+
success downloader.success_message
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_sub_from_archive(archive, format, file)
|
73
|
+
case format
|
74
|
+
when :zip
|
75
|
+
Zip::ZipFile.open(archive.to_s) { |zip|
|
76
|
+
sub = zip.entries.find { |entry|
|
77
|
+
entry.to_s =~ /\.#{Regexp.union SUB_EXTENSIONS}$/
|
78
|
+
}
|
79
|
+
raise "no subtitles in #{archive}" unless sub
|
80
|
+
sub.extract(file.to_s)
|
81
|
+
}
|
82
|
+
else
|
83
|
+
raise "unknown archive type (#{archive})"
|
84
|
+
end
|
85
|
+
ensure
|
86
|
+
archive.unlink if archive.exist?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
File without changes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Suby::Downloader
|
2
|
+
caches = {}
|
3
|
+
[:get, :post, :get_redirection].each { |meth|
|
4
|
+
original_method = instance_method(meth)
|
5
|
+
remove_method meth
|
6
|
+
define_method(meth) { |*args|
|
7
|
+
file = 'spec/fixtures/' + self.class::SITE.downcase + '.marshal'
|
8
|
+
caches[file] ||= File.exist?(file) ? Marshal.load(IO.read(file)) : {}
|
9
|
+
data = caches[file]
|
10
|
+
|
11
|
+
if data[args]
|
12
|
+
data[args]
|
13
|
+
else
|
14
|
+
puts "doing the real request: #{meth}(#{args * ', '})"
|
15
|
+
value = original_method.bind(self).call(*args)
|
16
|
+
data[args] = value
|
17
|
+
File.write(file, Marshal.dump(data))
|
18
|
+
value
|
19
|
+
end
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Suby::Downloader::Addic7ed do
|
4
|
+
file = Path('The Glee Project 01x03.avi')
|
5
|
+
downloader = Suby::Downloader::Addic7ed.new file
|
6
|
+
|
7
|
+
it 'finds the right subtitles' do
|
8
|
+
begin
|
9
|
+
downloader.subtitles[0..100].should include "Ellis, best first kiss?"
|
10
|
+
rescue Suby::NotFoundError => e
|
11
|
+
if e.message == "download exceeded"
|
12
|
+
pending e.message
|
13
|
+
else
|
14
|
+
raise e
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'fails gently when the show or the episode does not exist' do
|
20
|
+
d = Suby::Downloader::Addic7ed.new(Path('Not Existing Show 1x1.mkv'))
|
21
|
+
-> { d.download_url }.should raise_error(Suby::NotFoundError, "show/season/episode not found")
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'fails gently when there is no subtitles available' do
|
25
|
+
d = Suby::Downloader::Addic7ed.new(file, :es)
|
26
|
+
-> { p d.download_url }.should raise_error(Suby::NotFoundError, "no subtitles available")
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Suby::Downloader::OpenSubtitles do
|
4
|
+
file = Path("breaking.bad.s05e04.hdtv.x264-fqm.mp4")
|
5
|
+
downloader = Suby::Downloader::OpenSubtitles.new file
|
6
|
+
correct_query = [{ moviehash: "709b9ff887cf987d", moviebytesize: "308412149", sublanguageid: "eng" }]
|
7
|
+
wrong_query = correct_query.first.merge({ sublanguageid: "wrong_language" })
|
8
|
+
|
9
|
+
it 'finds the right subtitles' do
|
10
|
+
response = downloader.search_subtitles(correct_query)['data']
|
11
|
+
response.should_not be_false
|
12
|
+
response.first['MovieName'].should == '"Breaking Bad" Fifty-One'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'finds the right download link' do
|
16
|
+
url = downloader.download_url
|
17
|
+
url.should match(%r{http(s)?://.*opensubtitles.org/en/download/.*})
|
18
|
+
end
|
19
|
+
|
20
|
+
it "doesn't find anything for bad query" do
|
21
|
+
response = downloader.search_subtitles(wrong_query)['data']
|
22
|
+
response.should be_false
|
23
|
+
end
|
24
|
+
|
25
|
+
it "gets right token" do
|
26
|
+
downloader.token.should match(/\A[a-z0-9]{26}\z/)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'fails gently when there is no subtitles available' do
|
30
|
+
d = Suby::Downloader::OpenSubtitles.new(Path("Not Existing Show 1x1.mkv"), :eawdad)
|
31
|
+
-> { d.download_url }.should raise_error(Suby::NotFoundError, "no subtitles available")
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe Suby::Downloader::TVSubtitles do
|
4
|
+
file = Path('How I Met Your Mother 3x9 - Slapsgiving.mkv')
|
5
|
+
downloader = Suby::Downloader::TVSubtitles.new(file)
|
6
|
+
downloader_fr = Suby::Downloader::TVSubtitles.new(file, :fr)
|
7
|
+
|
8
|
+
it 'finds the right show url' do
|
9
|
+
downloader.show_url.should == '/tvshow-110.html'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'finds the right season url' do
|
13
|
+
downloader.season_url.should == '/tvshow-110-3.html'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'finds the right episode url' do
|
17
|
+
downloader.episode_url.should == '/episode-7517-en.html'
|
18
|
+
downloader_fr.episode_url.should == '/episode-7517-fr.html'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'finds the right subtitles url' do
|
22
|
+
downloader.subtitles_url.should == '/subtitle-9339.html'
|
23
|
+
downloader_fr.subtitles_url.should == '/subtitle-31249.html'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'finds the right download url' do
|
27
|
+
downloader.download_url.should == '/files/How%20I%20Met%20Your%20Mother_3x09_en.zip'
|
28
|
+
downloader_fr.download_url.should == '/files/How%20I%20Met%20Your%20Mother_3x09_fr.zip'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'fails gently when the show does not exist' do
|
32
|
+
d = Suby::Downloader::TVSubtitles.new(Path('Not Existing Show 1x1.mkv'))
|
33
|
+
-> { d.show_url }.should raise_error(Suby::NotFoundError, "show not found")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'fails gently when the season does not exist' do
|
37
|
+
d = Suby::Downloader::TVSubtitles.new(Path('How I Met Your Mother 99x1.mkv'))
|
38
|
+
-> { d.episode_url }.should raise_error(Suby::NotFoundError, "season not found")
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'fails gently when the episode does not exist' do
|
42
|
+
d = Suby::Downloader::TVSubtitles.new(Path('How I Met Your Mother 3x99.mkv'))
|
43
|
+
-> { d.episode_url }.should raise_error(Suby::NotFoundError, "episode not found")
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'fails gently when there is no subtitles available' do
|
47
|
+
d = Suby::Downloader::TVSubtitles.new(Path('Batman: The Animated Series 1x03.mkv'))
|
48
|
+
-> { d.subtitles_url }.should raise_error(Suby::NotFoundError, "no subtitles available")
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Suby::Downloader do
|
4
|
+
downloader = Suby::Downloader.new Path('How I Met Your Mother 3x9 - Slapsgiving.mkv')
|
5
|
+
it 'guess the right type of subtitles from the contents' do
|
6
|
+
downloader.sub_extension("1\r\n00").should == 'srt'
|
7
|
+
downloader.sub_extension("\xEF\xBB\xBF1\r\n00:00:14,397").should == 'srt'
|
8
|
+
downloader.sub_extension("\xEF\xBB\xBF1\r\n00:00:14,397").should == 'srt'
|
9
|
+
downloader.sub_extension("{346}{417}informa").should == 'sub'
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Suby::Downloader do
|
4
|
+
show = 'How I Met Your Mother'
|
5
|
+
season, episode = 3, 9
|
6
|
+
title = 'Slapsgiving'
|
7
|
+
ext = 'mkv'
|
8
|
+
dot_show = show.tr(' ', '.')
|
9
|
+
und_show = show.tr(' ', '_')
|
10
|
+
movie_name = "Somemovie.2002.720p.BluRay"
|
11
|
+
unknown_name = "crf-hp4"
|
12
|
+
|
13
|
+
context "cleans correctly the show name" do
|
14
|
+
{
|
15
|
+
dot_show => show,
|
16
|
+
und_show => show,
|
17
|
+
"an.example.1.0.test" => "an example 1.0 test",
|
18
|
+
"an_example_1.0_test" => "an example 1.0 test",
|
19
|
+
"an_3.0.1.example_1.0_test" => "an 3.0.1 example 1.0 test",
|
20
|
+
}.each_pair { |raw_show, true_show|
|
21
|
+
it raw_show do
|
22
|
+
# dup because a literal String is frozen and no point to keep the raw show
|
23
|
+
Suby::FilenameParser.clean_show_name(raw_show.dup).should == true_show
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
context "parse correctly the file name" do
|
29
|
+
[
|
30
|
+
{
|
31
|
+
examples:
|
32
|
+
[
|
33
|
+
"#{show} #{season}x#{episode}",
|
34
|
+
"#{show} #{season}x#{"%.2d" % episode}",
|
35
|
+
"#{show} #{season}x#{episode} - #{title}",
|
36
|
+
"#{show} #{season}x#{"%.2d" % episode} - #{title}",
|
37
|
+
"#{dot_show}.s0309",
|
38
|
+
"#{dot_show}.3x09",
|
39
|
+
"#{dot_show}.s03.e09",
|
40
|
+
"#{und_show}.s03_e09",
|
41
|
+
"#{show} - [03.09]",
|
42
|
+
"#{show} - S3 E 09",
|
43
|
+
"#{show} - Episode 9999 [S 3 - Ep 9]",
|
44
|
+
"#{show} - Episode 9999 [S 3 - Ep 9] - ",
|
45
|
+
"#{dot_show}.309",
|
46
|
+
],
|
47
|
+
expected: { type: :tvshow, show: show, season: season, episode: episode }
|
48
|
+
},
|
49
|
+
{
|
50
|
+
examples: [movie_name],
|
51
|
+
expected: { type: :movie, name: "Somemovie", year: 2002 }
|
52
|
+
},
|
53
|
+
{
|
54
|
+
examples: [unknown_name],
|
55
|
+
expected: { type: :unknown, name: "#{unknown_name}.#{ext}"}
|
56
|
+
}
|
57
|
+
].each do |type|
|
58
|
+
type[:examples].each do |filename|
|
59
|
+
it filename do
|
60
|
+
file = Path(filename).add_ext(ext)
|
61
|
+
Suby::FilenameParser.parse(file).should == type[:expected]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
describe Suby do
|
6
|
+
file = 'How I Met Your Mother 3x9 - Slapsgiving.mkv'
|
7
|
+
srt = 'How I Met Your Mother 3x9 - Slapsgiving.srt'
|
8
|
+
|
9
|
+
it 'works :D', full: true do
|
10
|
+
Dir.mktmpdir do |dir|
|
11
|
+
suby = File.expand_path('../../bin/suby', __FILE__)
|
12
|
+
Dir.chdir(dir) do
|
13
|
+
system suby, file
|
14
|
+
Digest::MD5.hexdigest(File.read(srt)).should == '7c4b89452782c20c6086ba5c1f4e9d74'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can detect videos' do
|
20
|
+
%w[avi mp4 mkv].each { |ext|
|
21
|
+
Suby.should be_a_video(Path("file").add_ext(ext))
|
22
|
+
}
|
23
|
+
%w[txt srt sub].each { |ext|
|
24
|
+
Suby.should_not be_a_video(Path("file").add_ext(ext))
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'suby'
|
3
|
+
s.summary = "Subtitles' downloader"
|
4
|
+
s.description = "Find and download subtitles"
|
5
|
+
s.author = 'eregon'
|
6
|
+
s.email = 'eregontp@gmail.com'
|
7
|
+
s.homepage = 'https://github.com/eregon/suby'
|
8
|
+
|
9
|
+
s.files = Dir['bin/*', 'lib/**/*.rb', '.gitignore', 'README.md', 'suby.gemspec']
|
10
|
+
s.executables = ['suby']
|
11
|
+
|
12
|
+
s.required_ruby_version = '>= 1.9.2'
|
13
|
+
s.add_dependency 'path', '>= 1.3.0'
|
14
|
+
s.add_dependency 'nokogiri'
|
15
|
+
s.add_dependency 'rubyzip'
|
16
|
+
s.add_dependency 'term-ansicolor'
|
17
|
+
s.add_dependency 'mime-types', '>= 1.19'
|
18
|
+
|
19
|
+
s.version = '0.4.0'
|
20
|
+
end
|