addic7ed 3.0.0 → 4.0.0.pre.beta.5

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.
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