addic7ed 3.0.0 → 4.0.0.pre.beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/addic7ed.rb +5 -10
  3. data/lib/addic7ed/common.rb +13 -1285
  4. data/lib/addic7ed/config.json +1417 -0
  5. data/lib/addic7ed/errors.rb +8 -7
  6. data/lib/addic7ed/models/episode.rb +112 -0
  7. data/lib/addic7ed/models/search.rb +90 -0
  8. data/lib/addic7ed/models/subtitle.rb +109 -0
  9. data/lib/addic7ed/models/subtitles_collection.rb +63 -0
  10. data/lib/addic7ed/models/video_file.rb +147 -0
  11. data/lib/addic7ed/service.rb +13 -0
  12. data/lib/addic7ed/services/check_compatibility.rb +44 -0
  13. data/lib/addic7ed/services/download_subtitle.rb +53 -0
  14. data/lib/addic7ed/services/get_shows_list.rb +29 -0
  15. data/lib/addic7ed/services/{addic7ed_comment_normalizer.rb → normalize_comment.rb} +4 -8
  16. data/lib/addic7ed/services/normalize_version.rb +22 -0
  17. data/lib/addic7ed/services/parse_page.rb +43 -0
  18. data/lib/addic7ed/services/parse_subtitle.rb +79 -0
  19. data/lib/addic7ed/services/url_encode_show_name.rb +46 -0
  20. data/lib/addic7ed/version.rb +1 -1
  21. metadata +63 -50
  22. data/bin/addic7ed +0 -144
  23. data/lib/addic7ed/episode.rb +0 -95
  24. data/lib/addic7ed/parser.rb +0 -105
  25. data/lib/addic7ed/services/addic7ed_version_normalizer.rb +0 -24
  26. data/lib/addic7ed/show_list.rb +0 -61
  27. data/lib/addic7ed/subtitle.rb +0 -72
  28. data/lib/addic7ed/video_file.rb +0 -41
  29. data/spec/lib/addic7ed/common_spec.rb +0 -21
  30. data/spec/lib/addic7ed/episode_spec.rb +0 -165
  31. data/spec/lib/addic7ed/services/addic7ed_comment_normalizer_spec.rb +0 -12
  32. data/spec/lib/addic7ed/services/addic7ed_version_normalizer_spec.rb +0 -73
  33. data/spec/lib/addic7ed/show_list_spec.rb +0 -42
  34. data/spec/lib/addic7ed/subtitle_spec.rb +0 -182
  35. data/spec/lib/addic7ed/video_file_spec.rb +0 -159
  36. data/spec/responses/basic_redirection.http +0 -13
  37. data/spec/responses/homepage.http +0 -921
  38. data/spec/responses/redirection_loop.http +0 -12
  39. data/spec/responses/walking-dead-3-2-1.http +0 -770
  40. data/spec/responses/walking-dead-3-2-48.http +0 -2117
  41. data/spec/responses/walking-dead-3-2-7.http +0 -659
  42. data/spec/responses/walking-dead-3-2-8.http +0 -815
  43. data/spec/responses/walking-dead-3-2-8_best_subtitle.http +0 -1928
  44. data/spec/responses/walking-dead-3-4-8.http +0 -732
  45. data/spec/responses/walking-dead-3-42-8.http +0 -13
  46. data/spec/spec_helper.rb +0 -26
@@ -1,144 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- def require_dependencies
4
- require 'optparse'
5
- require 'oga'
6
- require 'addic7ed'
7
- end
8
-
9
- begin
10
- require_dependencies # People don't all use rubygems, you know...
11
- rescue LoadError
12
- require 'rubygems' # But most do :-)
13
- require_dependencies
14
- end
15
-
16
- options = {}
17
- OptionParser.new do |opts|
18
- opts.banner = "Usage: addic7ed [options] <file1> [<file2>, <file3>, ...]"
19
-
20
- opts.on("-l [LANGUAGE]", "--language [LANGUAGE]", "Language code to look subtitles for (default: French)") do |l|
21
- options[:language] = l
22
- end
23
-
24
- opts.on("--no-hi", "Only download subtitles without Hearing Impaired lines") do |hi|
25
- options[:no_hi] = !hi
26
- end
27
-
28
- opts.on("-a", "--all-subtitles", "Display all available subtitles") do |a|
29
- options[:all] = a
30
- end
31
-
32
- opts.on("-n", "--do-not-download", "Do not download the subtitle") do |n|
33
- options[:nodownload] = n
34
- end
35
-
36
- opts.on("-f", "--force", "Overwrite existing subtitle") do |f|
37
- options[:force] = f
38
- end
39
-
40
- opts.on("-u", "--untagged", "Do not include language code in subtitle filename") do |u|
41
- options[:untagged] = u
42
- end
43
-
44
- opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
45
- options[:verbose] = v
46
- end
47
-
48
- opts.on("-q", "--quiet", "Run without output (cron-mode)") do |q|
49
- options[:quiet] = q
50
- end
51
-
52
- opts.on("-d", "--debug", "Debug mode [do not use]") do |d|
53
- options[:debug] = d
54
- end
55
-
56
- opts.on_tail("-h", "--help", "Show this message") do
57
- puts opts
58
- exit
59
- end
60
-
61
- opts.on_tail("-L", "--list-languages", "List all available languages") do
62
- puts "All available languages (with their corresponding ISO code):"
63
- Addic7ed::LANGUAGES.each do |lang, infos|
64
- puts "#{lang}:\t#{infos[:name]}"
65
- end
66
- exit
67
- end
68
-
69
- opts.on_tail("-V", "--version", "Show version number") do
70
- puts "This is addic7ed-ruby version #{Addic7ed::VERSION} by Michael Baudino (https://github.com/michaelbaudino)"
71
- puts "Licensed under the terms of the MIT License"
72
- exit
73
- end
74
- end.parse!
75
-
76
- options[:filenames] = ARGV
77
- options[:language] ||= 'fr'
78
-
79
- # Main loop over mandatory arguments (e.g. filenames)
80
-
81
- options[:filenames].each do |filename|
82
- unless File.file? filename or options[:debug]
83
- puts "Warning: #{filename} does not exist or is not a regular file. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
84
- next
85
- end
86
-
87
- begin
88
- ep = Addic7ed::Episode.new(filename, options[:untagged])
89
- puts "Searching subtitles for #{ep.video_file.basename}" if options[:verbose]
90
- if File.file?(filename.gsub(/\.\w{3}$/, '.srt')) and not options[:force]
91
- puts "A subtitle already exists (#{filename.gsub(/\.\w{3}$/, '.srt')}). Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
92
- next
93
- end
94
- puts ep.video_file.inspect.gsub(/^/, ' ') if options[:verbose]
95
- ep.subtitles(options[:language])
96
- if options[:all] or options[:verbose]
97
- puts 'Available subtitles:'.gsub(/^/, options[:verbose] ? ' ' : '')
98
- ep.subtitles(options[:language]).each do |sub|
99
- puts "#{sub}".gsub(/^/, options[:verbose] ? ' ' : ' ')
100
- end
101
- next if options[:all]
102
- end
103
- ep.best_subtitle(options[:language], options[:no_hi])
104
- if options[:verbose]
105
- puts ' Best subtitle:'
106
- puts " #{ep.best_subtitle(options[:language])}"
107
- end
108
- unless options[:nodownload]
109
- ep.download_best_subtitle!(options[:language], options[:no_hi])
110
- puts "New subtitle downloaded for #{filename}.\nEnjoy your show :-)".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
111
- end
112
- rescue Addic7ed::InvalidFilename
113
- puts "#{filename} does not seem to be a valid TV show filename. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
114
- next
115
- rescue Addic7ed::ShowNotFound
116
- puts "Show not found on Addic7ed : #{ep.video_file.filename}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
117
- next
118
- rescue Addic7ed::EpisodeNotFound
119
- puts "Episode not found on Addic7ed : #{ep.video_file.filename}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
120
- next
121
- rescue Addic7ed::LanguageNotSupported
122
- puts "Addic7ed does not support language '#{options[:language]}'. Exiting.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
123
- break
124
- rescue Addic7ed::ParsingError
125
- puts "HTML parsing failed. Either you've found a bug (please submit an issue) or Addic7ed website has been updated and cannot be crawled anymore (in this case, please wait for an update or submit a pull request). Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
126
- next
127
- rescue Addic7ed::NoSubtitleFound
128
- puts "No (acceptable) subtitle has been found on Addic7ed for #{filename}. Maybe try again later.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
129
- next
130
- rescue Addic7ed::DownloadError
131
- puts "The subtitle could not be downloaded. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
132
- next
133
- rescue Addic7ed::DownloadLimitReached
134
- puts "You exceeded your daily download count. Exiting.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
135
- break
136
- rescue Addic7ed::SubtitleCannotBeSaved
137
- puts "The downloaded subtitle could not be saved as #{filename.gsub(/\.\w{3}$/, '.srt')}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
138
- next
139
- rescue Addic7ed::HTTPError => e
140
- puts "Network error: #{e.message}".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet]
141
- next
142
- end
143
-
144
- end
@@ -1,95 +0,0 @@
1
- require 'net/http'
2
- require 'open-uri'
3
-
4
- module Addic7ed
5
- class Episode
6
-
7
- attr_reader :video_file, :untagged
8
-
9
- def initialize(filename, untagged = false)
10
- @video_file = Addic7ed::VideoFile.new(filename)
11
- @untagged = untagged
12
- end
13
-
14
- def url(lang = 'fr')
15
- check_language_availability(lang)
16
- @localized_urls ||= {}
17
- @localized_urls[lang] ||= "http://www.addic7ed.com/serie/#{ShowList.url_segment_for(video_file.showname)}/#{video_file.season}/#{video_file.episode}/#{LANGUAGES[lang][:id]}"
18
- end
19
-
20
- def subtitles(lang = 'fr')
21
- check_language_availability(lang)
22
- find_subtitles(lang) unless @subtitles and @subtitles[lang]
23
- return @subtitles[lang]
24
- end
25
-
26
- def best_subtitle(lang = 'fr', no_hi = false)
27
- check_language_availability(lang)
28
- find_best_subtitle(lang, no_hi) unless @best_subtitle and @best_subtitle[lang]
29
- return @best_subtitle[lang]
30
- end
31
-
32
- def download_best_subtitle!(lang, no_hi = false, http_redirect_limit = 8)
33
- raise HTTPError.new('Too many HTTP redirects') unless http_redirect_limit > 0
34
- uri = URI(best_subtitle(lang, no_hi).url)
35
- response = get_http_response(uri, url(lang))
36
- if response.kind_of?(Net::HTTPRedirection)
37
- follow_redirection(lang, no_hi, response['location'], http_redirect_limit)
38
- else
39
- save_subtitle(response.body, lang)
40
- end
41
- end
42
-
43
- protected
44
-
45
- def find_subtitles(lang)
46
- initialize_language(lang)
47
- parser = Addic7ed::Parser.new(self, lang)
48
- @subtitles[lang] = parser.extract_subtitles
49
- end
50
-
51
- def find_best_subtitle(lang, no_hi = false)
52
- @best_subtitle ||= {}
53
- subtitles(lang).each do |sub|
54
- @best_subtitle[lang] = sub if sub.works_for?(video_file.group, no_hi) and sub.can_replace? @best_subtitle[lang]
55
- end
56
- raise NoSubtitleFound unless @best_subtitle[lang]
57
- end
58
-
59
- def check_language_availability(lang)
60
- raise LanguageNotSupported unless LANGUAGES[lang]
61
- end
62
-
63
- def initialize_language(lang)
64
- @subtitles ||= {}
65
- @subtitles[lang] ||= []
66
- end
67
-
68
- def get_http_response(uri, referer)
69
- Net::HTTP.start(uri.hostname, uri.port) do |http|
70
- request = Net::HTTP::Get.new(uri.request_uri)
71
- # Addic7ed needs the Referer to be correct. User-agent is just here to fake a real browser request.
72
- request['Referer'] = referer
73
- request['User-Agent'] = USER_AGENTS.sample
74
- http.request(request)
75
- end
76
- rescue
77
- raise DownloadError
78
- end
79
-
80
- def follow_redirection(lang, no_hi, new_uri, http_redirect_limit)
81
- # Addic7ed is serving redirection URL not-encoded, but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396)
82
- best_subtitle(lang).url = URI.escape(new_uri)
83
- raise DownloadLimitReached if /^\/downloadexceeded.php/.match best_subtitle(lang).url
84
- download_best_subtitle!(lang, no_hi, http_redirect_limit - 1)
85
- end
86
-
87
- def save_subtitle(content, lang)
88
- Kernel.open "#{video_file}".gsub(/\.\w{3}$/, untagged ? ".srt" : ".#{lang}.srt"), 'w' do |f|
89
- f << content
90
- end
91
- rescue
92
- raise SubtitleCannotBeSaved
93
- end
94
- end
95
- end
@@ -1,105 +0,0 @@
1
- require 'oga'
2
- require 'net/http'
3
- require 'open-uri'
4
-
5
- module Addic7ed
6
- class Parser
7
-
8
- def initialize(episode, lang)
9
- @episode, @lang = episode, lang
10
- @subtitles = []
11
- end
12
-
13
- def extract_subtitles
14
- @dom = subtitles_page_dom
15
- check_subtitles_presence
16
- parse_subtitle_nodes_list
17
- @subtitles
18
- end
19
-
20
- protected
21
-
22
- def subtitles_page_dom
23
- uri = URI(@episode.url(@lang))
24
- response = Net::HTTP.start(uri.hostname, uri.port) do |http|
25
- request = Net::HTTP::Get.new(uri.request_uri)
26
- request["User-Agent"] = USER_AGENTS.sample
27
- http.request(request)
28
- end
29
- raise EpisodeNotFound unless response.body
30
- Oga.parse_html(response.body)
31
- end
32
-
33
- def check_subtitles_presence
34
- raise NoSubtitleFound unless @dom.css('select#filterlang ~ font[color="yellow"]').empty?
35
- end
36
-
37
- def parse_subtitle_nodes_list
38
- sublist_node = @dom.css('#container95m table.tabel95 table.tabel95')
39
- raise NoSubtitleFound if sublist_node.size == 0
40
- sublist_node.each do |sub_node|
41
- @subtitles << parse_subtitle_node(sub_node)
42
- end
43
- end
44
-
45
- def parse_subtitle_node(sub_node)
46
- Addic7ed::Subtitle.new(
47
- version: extract_version(sub_node),
48
- language: extract_language(sub_node),
49
- status: extract_status(sub_node),
50
- url: extract_url(sub_node),
51
- source: extract_source(sub_node),
52
- hi: extract_hi(sub_node),
53
- downloads: extract_downloads(sub_node),
54
- comment: extract_comment(sub_node)
55
- )
56
- end
57
-
58
- def extract_version(sub_node)
59
- version_node = sub_node.css('.NewsTitle').first
60
- raise Addic7ed::ParsingError unless version_node
61
- version_node.text
62
- end
63
-
64
- def extract_language(sub_node)
65
- language_node = sub_node.css('.language').first
66
- raise Addic7ed::ParsingError unless language_node
67
- language_node.text.gsub(/\A\W*/, '').gsub(/[^\w\)]*\z/, '')
68
- end
69
-
70
- def extract_status(sub_node)
71
- status_node = sub_node.css('tr:nth-child(3) td:nth-child(4) b').first
72
- raise Addic7ed::ParsingError unless status_node
73
- status_node.text.strip
74
- end
75
-
76
- def extract_url(sub_node)
77
- url_node = sub_node.css('a.buttonDownload').last
78
- raise Addic7ed::ParsingError unless url_node
79
- 'http://www.addic7ed.com' + url_node['href']
80
- end
81
-
82
- def extract_source(sub_node)
83
- source_node = sub_node.css('tr:nth-child(3) td:first-child a').first
84
- source_node['href'] if source_node
85
- end
86
-
87
- def extract_hi(sub_node)
88
- hi_node = sub_node.css('tr:nth-child(4) td.newsDate img').last
89
- raise Addic7ed::ParsingError unless hi_node
90
- !hi_node.attribute("title").nil?
91
- end
92
-
93
- def extract_downloads(sub_node)
94
- downloads_node = sub_node.css('tr:nth-child(4) td.newsDate').first
95
- raise Addic7ed::ParsingError unless downloads_node
96
- /(?<downloads>\d*) Downloads/.match(downloads_node.text)[:downloads]
97
- end
98
-
99
- def extract_comment(sub_node)
100
- comment_node = sub_node.css('tr:nth-child(2) td.newsDate').first
101
- raise Addic7ed::ParsingError unless comment_node
102
- comment_node.text.gsub(/<img[^>]+\>/i, "")
103
- end
104
- end
105
- end
@@ -1,24 +0,0 @@
1
- module Addic7ed
2
- class Addic7edVersionNormalizer
3
- attr_reader :version
4
-
5
- def initialize(version)
6
- @version = version || ""
7
- end
8
-
9
- def self.call(version)
10
- new(version).call
11
- end
12
-
13
- def call
14
- version.
15
- gsub(/[[:space:]]/, "").
16
- upcase.
17
- gsub(/,[\d\. ]+MBS$/, '').
18
- gsub(/(^VERSION *|720P|1080P|HDTV|PROPER|RERIP|INTERNAL|X\.?264)/, '').
19
- gsub(/[- \.]/, '')
20
- end
21
- end
22
-
23
- private
24
- end
@@ -1,61 +0,0 @@
1
- module Addic7ed
2
- class ShowList
3
- attr_reader :raw_name
4
-
5
- def initialize(raw_name)
6
- @raw_name = raw_name
7
- end
8
-
9
- def self.url_segment_for(raw_name)
10
- new(raw_name).url_segment_for
11
- end
12
-
13
- def url_segment_for
14
- shows_matching = shows_matching_exactly
15
- shows_matching = shows_matching_without_year if shows_matching.empty?
16
- raise ShowNotFound if shows_matching.empty?
17
- shows_matching.last.gsub(' ', '_')
18
- end
19
-
20
- private
21
-
22
- def shows_matching_exactly
23
- @shows_matching_exactly ||= addic7ed_shows.select{ |addic7ed_show| is_matching? addic7ed_show }
24
- end
25
-
26
- def shows_matching_without_year
27
- @shows_matching_without_year ||= addic7ed_shows.select{ |addic7ed_show| is_matching? addic7ed_show, :comparer_without_year }
28
- end
29
-
30
- def default_comparer(showname)
31
- showname.downcase.gsub("'", "").gsub(".", " ").strip
32
- end
33
-
34
- def comparer_without_year(showname)
35
- default_comparer(showname).gsub(/ \(\d{4}\)( |$)/, '\1')
36
- end
37
-
38
- def is_matching?(addic7ed_show, comparer = :default_comparer)
39
- [humanized_name, addic7ed_show].map(&method(comparer)).reduce(:==)
40
- end
41
-
42
- def humanized_name
43
- @humanized_name ||= raw_name.
44
- gsub(/[_\.]+/, ' ').
45
- gsub(/ (US|UK)( |$)/i, ' (\1)\2').
46
- gsub(/ (\d{4})( |$)/i, ' (\1)\2')
47
- end
48
-
49
- def addic7ed_shows
50
- @@addic7ed_shows ||= Oga.parse_html(addic7ed_homepage.body).css("select#qsShow option:not(:first-child)").map(&:text)
51
- end
52
-
53
- def addic7ed_homepage
54
- Net::HTTP.start("www.addic7ed.com") do |http|
55
- request = Net::HTTP::Get.new("/")
56
- request["User-Agent"] = USER_AGENTS.sample
57
- http.request(request)
58
- end
59
- end
60
- end
61
- end
@@ -1,72 +0,0 @@
1
- module Addic7ed
2
- class Subtitle
3
-
4
- attr_reader :version, :language, :status, :via, :downloads, :comment
5
- attr_accessor :url
6
-
7
- def initialize(options = {})
8
- @version = Addic7edVersionNormalizer.call(options[:version])
9
- @language = options[:language]
10
- @status = options[:status]
11
- @url = options[:url]
12
- @via = options[:via]
13
- @hi = options[:hi]
14
- @downloads = options[:downloads].to_i || 0
15
- @comment = Addic7edCommentNormalizer.call(options[:comment])
16
- end
17
-
18
- def to_s
19
- "#{url}\t->\t#{version} (#{language}, #{status}) [#{downloads} downloads]#{" (via #{via})" if via}"
20
- end
21
-
22
- def works_for?(version = '', no_hi = false)
23
- hi_works = !@hi || !no_hi
24
- is_completed? and is_compatible_with? version and hi_works
25
- end
26
-
27
- def can_replace?(other_subtitle)
28
- return false unless is_completed?
29
- return true if other_subtitle.nil?
30
- language == other_subtitle.language &&
31
- is_compatible_with?(other_subtitle.version) &&
32
- is_more_popular_than?(other_subtitle)
33
- end
34
-
35
- def is_featured?
36
- via == "http://addic7ed.com"
37
- end
38
-
39
- def is_completed?
40
- status == 'Completed'
41
- end
42
-
43
- protected
44
-
45
- def is_compatible_with?(other_version)
46
- defined_as_compatible_with(other_version) || generally_compatible_with?(other_version) || commented_as_compatible_with?(other_version)
47
- end
48
-
49
- def defined_as_compatible_with(other_version)
50
- version.split(",").include? other_version
51
- end
52
-
53
- def generally_compatible_with?(other_version)
54
- COMPATIBILITY_720P[version] == other_version || COMPATIBILITY_720P[other_version] == version
55
- end
56
-
57
- def commented_as_compatible_with?(other_version)
58
- return false if /(won't|doesn't|not) +work/i.match comment
59
- return false if /resync +(from|of)/i.match comment
60
- res = comment.include? other_version.downcase
61
- res ||= comment.include? COMPATIBILITY_720P[other_version].downcase if COMPATIBILITY_720P[other_version]
62
- res ||= comment.include? COMPATIBILITY_720P[version].downcase if COMPATIBILITY_720P[version]
63
- !!res
64
- end
65
-
66
- def is_more_popular_than?(other_subtitle)
67
- return true if other_subtitle.nil?
68
- return false if other_subtitle.is_featured?
69
- return downloads > other_subtitle.downloads
70
- end
71
- end
72
- end