storyboard 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|