subscene 0.0.1 → 0.0.2
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 +1 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/README.md +52 -3
- data/Rakefile +7 -0
- data/lib/subscene.rb +103 -1
- data/lib/subscene/error.rb +3 -0
- data/lib/subscene/response.rb +22 -0
- data/lib/subscene/response/html.rb +20 -0
- data/lib/subscene/response/raise_error.rb +20 -0
- data/lib/subscene/subtitle.rb +56 -0
- data/lib/subscene/subtitle_result.rb +35 -0
- data/lib/subscene/subtitle_result_set.rb +19 -0
- data/lib/subscene/version.rb +1 -1
- data/spec/fixtures/result_sample.html +31 -0
- data/spec/fixtures/result_set_sample.html +96 -0
- data/spec/fixtures/search_with_results.html +966 -0
- data/spec/fixtures/subtitle.zip +0 -0
- data/spec/fixtures/subtitle_sample.html +485 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/subscene/response/raise_error_spec.rb +12 -0
- data/spec/subscene/subtitle_result_set_spec.rb +20 -0
- data/spec/subscene/subtitle_result_spec.rb +20 -0
- data/spec/subscene/subtitle_spec.rb +35 -0
- data/spec/subscene_spec.rb +43 -0
- data/subscene.gemspec +5 -1
- metadata +100 -5
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
-
# Subscene
|
1
|
+
# Subscene [](https://travis-ci.org/jassa/subscene)
|
2
2
|
|
3
3
|
Ruby API Client for Subscene.com
|
4
4
|
|
5
|
+
## Introduction
|
6
|
+
|
7
|
+
Subscene.com is a very complete and reliable catalog of subtitles.
|
8
|
+
If you're reading this you probably know they don't have an API.
|
9
|
+
|
10
|
+
This gem will help you communicate with Subscene.com easily.
|
11
|
+
|
5
12
|
## Installation
|
6
13
|
|
7
14
|
Add this line to your application's Gemfile:
|
@@ -16,9 +23,51 @@ Or install it yourself as:
|
|
16
23
|
|
17
24
|
$ gem install subscene
|
18
25
|
|
19
|
-
##
|
26
|
+
## Searching
|
27
|
+
|
28
|
+
Subscene.com handles two kinds of searches:
|
29
|
+
|
30
|
+
1. Search by Film or TV Show 'title'
|
31
|
+
|
32
|
+
e.g. "The Big Bang Theory", "The Hobbit", etc.
|
33
|
+
|
34
|
+
2. Search for a particular 'release'
|
35
|
+
|
36
|
+
e.g. "The Big Bang Theory s01e01" or "The Hobbit HDTV"
|
37
|
+
|
38
|
+
There are certain keywords that trigger one search or the other,
|
39
|
+
this gem initially will support the second type (by release)
|
40
|
+
so make sure to format your queries properly.
|
41
|
+
|
42
|
+
## Examples
|
43
|
+
|
44
|
+
The `search` method will return subtitles that match the query.
|
45
|
+
|
46
|
+
Subscene.search('The Big Bang Theory s01e01')
|
47
|
+
# => [#<Subscene::SubtitleResult @attributes={:id=>"136037", :name=>"The.Big.Bang.Theory..",
|
48
|
+
#<Subscene::SubtitleResult @attributes={:id=>"138042", :name=>"The Big Bang.."]
|
49
|
+
|
50
|
+
Once you get the results, you can iterate over them and review your options.
|
51
|
+
|
52
|
+
The information within these results is limited to what the Subscene.com
|
53
|
+
search reveals: http://subscene.com/subtitles/release.aspx?q=the%20big%20bang%20theory%20s01e01
|
54
|
+
|
55
|
+
You can obtain additional information by `find`ing the id.
|
56
|
+
|
57
|
+
Subscene.find(subtitles.last.id) # or Subscene.find(151582) directly
|
58
|
+
=> #<Subscene::Subtitle @id="151582", @name="Fringe.S01E01.DVDSCR.XviD-MEDiEVAL-EN", @lang="English", @user="Jap", @user_id="20904", @comment="Has no comment.", @rating="8", @downloads="9,549", @framerate="Not available", @created_at="6/18/2008 3:09 PM", @download_url="/subtitle/download?mac=yoPjbFZ9WFbUmWTWpOvaGbXDYN2b4yBreI8TJIBfpdynT-4hzba446VvrVyxamBM0", @hearing_impaired=false>
|
59
|
+
|
60
|
+
## Language Preferences
|
61
|
+
|
62
|
+
You can set the language id for the search filter.
|
63
|
+
Ids can be found at http://subscene.com/filter.
|
64
|
+
Maximum 3, comma separated.
|
20
65
|
|
21
|
-
|
66
|
+
# Examples
|
67
|
+
Subscene.language = 13 # English
|
68
|
+
Subscene.search("...") # Results will be only English subtitles
|
69
|
+
Subscene.language = "13,38" # English, Spanish
|
70
|
+
...
|
22
71
|
|
23
72
|
## Contributing
|
24
73
|
|
data/Rakefile
CHANGED
data/lib/subscene.rb
CHANGED
@@ -1,5 +1,107 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "nokogiri"
|
3
|
+
|
1
4
|
require "subscene/version"
|
5
|
+
require "subscene/error"
|
6
|
+
require "subscene/response"
|
7
|
+
require "subscene/response/raise_error"
|
8
|
+
require "subscene/response/html"
|
9
|
+
require "subscene/subtitle_result_set"
|
10
|
+
require "subscene/subtitle"
|
2
11
|
|
3
12
|
module Subscene
|
4
|
-
|
13
|
+
|
14
|
+
# Subscene.com is a very complete and reliable catalog of subtitles.
|
15
|
+
# If you're reading this you probably know they don't have an API.
|
16
|
+
#
|
17
|
+
# This gem will help you communicate with Subscene.com easily.
|
18
|
+
#
|
19
|
+
# == Searches
|
20
|
+
#
|
21
|
+
# Subscene.com handles two kinds of searches:
|
22
|
+
#
|
23
|
+
# a) Search by Film or TV Show 'title'
|
24
|
+
# e.g. "The Big Bang Theory", "The Hobbit", etc.
|
25
|
+
#
|
26
|
+
# b) Search for a particular 'release'
|
27
|
+
# e.g. "The Big Bang Theory s01e01" or "The Hobbit HDTV"
|
28
|
+
#
|
29
|
+
# There are certain keywords that trigger one search or the other,
|
30
|
+
# this gem initially will support the second type (by release)
|
31
|
+
# so make sure to format your queries properly.
|
32
|
+
#
|
33
|
+
extend self
|
34
|
+
|
35
|
+
ENDPOINT = "http://subscene.com"
|
36
|
+
RELEASE_PATH = "subtitles/release.aspx"
|
37
|
+
|
38
|
+
# Public: Search for a particular release.
|
39
|
+
#
|
40
|
+
# query - The String to be found.
|
41
|
+
#
|
42
|
+
# Examples
|
43
|
+
#
|
44
|
+
# Subscene.search('The Big Bang Theory s01e01')
|
45
|
+
# # => [#<Subscene::SubtitleResult:0x007feb7c9473b0
|
46
|
+
# @attributes={:id=>"136037", :name=>"The.Big.Bang.Theory.."]
|
47
|
+
#
|
48
|
+
# Returns the Subtitles found.
|
49
|
+
def search(query=nil)
|
50
|
+
params = { q: query } unless query.nil?
|
51
|
+
params ||= {}
|
52
|
+
|
53
|
+
response = connection.get do |req|
|
54
|
+
req.url RELEASE_PATH, params
|
55
|
+
req.headers['Cookie'] = "LanguageFilter=#{@lang_id};" if @lang_id
|
56
|
+
end
|
57
|
+
|
58
|
+
html = response.body
|
59
|
+
SubtitleResultSet.build(html).instances
|
60
|
+
end
|
61
|
+
|
62
|
+
# Public: Find a subtitle by id.
|
63
|
+
#
|
64
|
+
# id - The id of the subtitle.
|
65
|
+
#
|
66
|
+
# Examples
|
67
|
+
#
|
68
|
+
# Subscene.find(136037)
|
69
|
+
# # => TODO: display example result
|
70
|
+
#
|
71
|
+
# Returns the complete information of the Subtitle.
|
72
|
+
def find(id)
|
73
|
+
response = connection.get(id.to_s)
|
74
|
+
html = response.body
|
75
|
+
|
76
|
+
subtitle = Subtitle.build(html)
|
77
|
+
subtitle.id = id
|
78
|
+
subtitle
|
79
|
+
end
|
80
|
+
|
81
|
+
# Public: Set the language id for the search filter.
|
82
|
+
#
|
83
|
+
# lang_id - The id of the language. Maximum 3, comma separated.
|
84
|
+
# Ids can be found at http://subscene.com/filter
|
85
|
+
#
|
86
|
+
# Examples
|
87
|
+
#
|
88
|
+
# Subscene.language = 13 # English
|
89
|
+
# Subscene.search("...") # Results will be only English subtitles
|
90
|
+
# Subscene.language = "13,38" # English, Spanish
|
91
|
+
# ...
|
92
|
+
#
|
93
|
+
def language=(lang_id)
|
94
|
+
@lang_id = lang_id
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def connection
|
100
|
+
@connection ||= Faraday.new(url: ENDPOINT) do |faraday|
|
101
|
+
faraday.response :logger if ENV['DEBUG']
|
102
|
+
faraday.adapter Faraday.default_adapter
|
103
|
+
faraday.use Subscene::Response::HTML
|
104
|
+
faraday.use Subscene::Response::RaiseError
|
105
|
+
end
|
106
|
+
end
|
5
107
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Subscene
|
2
|
+
class Response < Faraday::Response::Middleware
|
3
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :parser
|
7
|
+
end
|
8
|
+
|
9
|
+
# Store a Proc that receives the body and returns the parsed result.
|
10
|
+
def self.define_parser(&block)
|
11
|
+
@parser = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def response_type(env)
|
15
|
+
env[:response_headers][CONTENT_TYPE].to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_response?(env)
|
19
|
+
env[:body].respond_to? :to_str
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Subscene
|
2
|
+
class Response::HTML < Response
|
3
|
+
|
4
|
+
dependency 'nokogiri'
|
5
|
+
|
6
|
+
define_parser do |body|
|
7
|
+
Nokogiri::HTML body
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(body)
|
11
|
+
case body
|
12
|
+
when String
|
13
|
+
self.class.parser.call body
|
14
|
+
else
|
15
|
+
body
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end # Response::HTML
|
20
|
+
end # Subscene
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Subscene
|
2
|
+
class Response::RaiseError < Faraday::Response::Middleware
|
3
|
+
ClientErrorStatuses = 400...600
|
4
|
+
|
5
|
+
def on_complete(env)
|
6
|
+
case env[:status]
|
7
|
+
when 301, 302
|
8
|
+
raise Subscene::SearchNotSupported, response_values(env)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def response_values(env)
|
13
|
+
{
|
14
|
+
:status => env[:status],
|
15
|
+
:headers => env[:response_headers],
|
16
|
+
:body => env[:body]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Subscene::Subtitle
|
2
|
+
|
3
|
+
attr_accessor :attributes, :id, :name, :lang, :user, :user_id, :comment, :rating,
|
4
|
+
:downloads, :download_url, :framerate, :created_at, :hearing_impaired
|
5
|
+
|
6
|
+
def initialize(attributes = {})
|
7
|
+
@attributes = attributes
|
8
|
+
@id = @attributes[:id]
|
9
|
+
@name = @attributes[:name]
|
10
|
+
@lang = @attributes[:lang]
|
11
|
+
@user = @attributes[:user]
|
12
|
+
@user_id = @attributes[:user_id]
|
13
|
+
@comment = @attributes[:comment]
|
14
|
+
@rating = @attributes[:rating]
|
15
|
+
@downloads = @attributes[:downloads]
|
16
|
+
@framerate = @attributes[:framerate]
|
17
|
+
@created_at = @attributes[:created_at]
|
18
|
+
@download_url = @attributes[:download_url]
|
19
|
+
@hearing_impaired = @attributes[:hearing_impaired]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Public: Download a Subtitle file.
|
24
|
+
#
|
25
|
+
# Examples
|
26
|
+
#
|
27
|
+
# Subscene.find(136037).download
|
28
|
+
# # => #<Faraday::Response #@env={:body=>"PK\u00...",
|
29
|
+
# :response_headers=>{"content-type"=>"application/x-zip-compressed"} [..]>
|
30
|
+
#
|
31
|
+
# Returns a Faraday::Response instance.
|
32
|
+
def download
|
33
|
+
conn = Faraday.new(url: Subscene::ENDPOINT)
|
34
|
+
conn.post do |req|
|
35
|
+
req.url download_url
|
36
|
+
req.headers['Referer'] = "#{Subscene::ENDPOINT}/#{id}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.build(html)
|
41
|
+
new({
|
42
|
+
id: (html.css("nav.comment-sub a").to_s.match(/subtitleId=(\d+)/)[1] rescue nil),
|
43
|
+
name: (html.css("li.release").children.last.text.strip rescue nil),
|
44
|
+
lang: (html.css("a#downloadButton").text.match(/Download (.*)\n/)[1] rescue nil),
|
45
|
+
user: (html.css("li.author").text.strip rescue nil),
|
46
|
+
user_id: (html.css("li.author a").attribute("href").value.match(/\d+/).to_s rescue nil),
|
47
|
+
comment: (html.css("div.comment").text.strip rescue nil),
|
48
|
+
rating: (html.css("div.rating").text.strip rescue nil),
|
49
|
+
downloads: (html.css("div.details li[contains('Downloads')]").children.last.text.strip rescue nil),
|
50
|
+
framerate: (html.css("div.details li[contains('Framerate')]").children.last.text.strip rescue nil),
|
51
|
+
created_at: (html.css("div.details li[contains('Online')]").children.last.text.strip rescue nil),
|
52
|
+
download_url: (html.css("a#downloadButton").attr("href").value rescue nil),
|
53
|
+
hearing_impaired: (html.css("div.details li[contains('Hearing')]").children.last.text.strip != "No" rescue nil)
|
54
|
+
})
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Subscene::SubtitleResult
|
2
|
+
|
3
|
+
attr_accessor :attributes, :id, :name, :url, :lang,
|
4
|
+
:user, :user_id, :comment, :files_count, :hearing_impaired
|
5
|
+
|
6
|
+
def initialize(attributes)
|
7
|
+
@attributes = attributes
|
8
|
+
|
9
|
+
@id = @attributes[:id]
|
10
|
+
@name = @attributes[:name]
|
11
|
+
@url = @attributes[:url]
|
12
|
+
@lang = @attributes[:lang]
|
13
|
+
@user = @attributes[:user]
|
14
|
+
@user_id = @attributes[:user_id]
|
15
|
+
@comment = @attributes[:comment]
|
16
|
+
@files_count = @attributes[:files_count]
|
17
|
+
@hearing_impaired = @attributes[:hearing_impaired]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.build(html)
|
21
|
+
subtitle_url = (html.css("td.a1 a").attribute("href").value rescue nil)
|
22
|
+
|
23
|
+
new({
|
24
|
+
id: (subtitle_url.match(/\d+$/).to_s rescue nil),
|
25
|
+
name: (html.css("td.a1 span[2]").text.strip rescue nil),
|
26
|
+
url: subtitle_url,
|
27
|
+
lang: (html.css("td.a1 span[1]").text.strip rescue nil),
|
28
|
+
user: (html.css("td.a5").text.strip rescue nil),
|
29
|
+
user_id: (html.css("td.a5 a").attribute("href").value.match(/\d+/).to_s rescue nil),
|
30
|
+
comment: (html.css("td.a6").text.strip rescue nil),
|
31
|
+
files_count: (html.css("td.a3").text.to_i rescue nil),
|
32
|
+
hearing_impaired: html.at_css("td.a40").nil?
|
33
|
+
})
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "subscene/subtitle_result"
|
2
|
+
|
3
|
+
class Subscene::SubtitleResultSet
|
4
|
+
attr_reader :instances
|
5
|
+
|
6
|
+
def initialize(attributes)
|
7
|
+
@instances = attributes[:instances]
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.build(html)
|
11
|
+
instances = html.css("tbody > tr").collect do |item|
|
12
|
+
Subscene::SubtitleResult.build(item)
|
13
|
+
end
|
14
|
+
|
15
|
+
new({
|
16
|
+
instances: instances
|
17
|
+
})
|
18
|
+
end
|
19
|
+
end
|
data/lib/subscene/version.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
<tr>
|
2
|
+
<td class="a1">
|
3
|
+
<a href="/subtitles/the-big-bang-theory-first-season/english/136037">
|
4
|
+
<div class="visited">
|
5
|
+
<span class="l r positive-icon">
|
6
|
+
English
|
7
|
+
</span>
|
8
|
+
<span>
|
9
|
+
The.Big.Bang.Theory.S01E01-08.HDTV.XviD-XOR
|
10
|
+
</span>
|
11
|
+
</div>
|
12
|
+
</a>
|
13
|
+
</td>
|
14
|
+
<td class="a3">
|
15
|
+
8
|
16
|
+
</td>
|
17
|
+
<td class="a40">
|
18
|
+
|
19
|
+
</td>
|
20
|
+
<td class="a5">
|
21
|
+
|
22
|
+
<a href="/u/26733">
|
23
|
+
rogard
|
24
|
+
</a>
|
25
|
+
</td>
|
26
|
+
<td class="a6">
|
27
|
+
<div>
|
28
|
+
Episodes 1 to 8
|
29
|
+
</div>
|
30
|
+
</td>
|
31
|
+
</tr>
|
@@ -0,0 +1,96 @@
|
|
1
|
+
<tbody>
|
2
|
+
<tr>
|
3
|
+
<td class="a1">
|
4
|
+
<a href="/subtitles/the-big-bang-theory-first-season/english/136037">
|
5
|
+
<div class="visited">
|
6
|
+
<span class="l r positive-icon">
|
7
|
+
English
|
8
|
+
</span>
|
9
|
+
<span>
|
10
|
+
The.Big.Bang.Theory.S01E01-08.HDTV.XviD-XOR
|
11
|
+
</span>
|
12
|
+
</div>
|
13
|
+
</a>
|
14
|
+
</td>
|
15
|
+
<td class="a3">
|
16
|
+
8
|
17
|
+
</td>
|
18
|
+
<td class="a40">
|
19
|
+
|
20
|
+
</td>
|
21
|
+
<td class="a5">
|
22
|
+
|
23
|
+
<a href="/u/26733">
|
24
|
+
rogard
|
25
|
+
</a>
|
26
|
+
</td>
|
27
|
+
<td class="a6">
|
28
|
+
<div>
|
29
|
+
Episodes 1 to 8
|
30
|
+
</div>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
|
34
|
+
<tr>
|
35
|
+
<td class="a1">
|
36
|
+
<a href="/subtitles/the-big-bang-theory-first-season/english/171327">
|
37
|
+
<div class="visited">
|
38
|
+
<span class="l r positive-icon">
|
39
|
+
English
|
40
|
+
</span>
|
41
|
+
<span>
|
42
|
+
The.Big.Bang.Theory.S01.DVDRop.XviD-FoV
|
43
|
+
</span>
|
44
|
+
</div>
|
45
|
+
</a>
|
46
|
+
</td>
|
47
|
+
<td class="a3">
|
48
|
+
17
|
49
|
+
</td>
|
50
|
+
<td class="a41">
|
51
|
+
|
52
|
+
</td>
|
53
|
+
<td class="a5">
|
54
|
+
<a href="/u/93617">
|
55
|
+
ray148
|
56
|
+
</a>
|
57
|
+
</td>
|
58
|
+
<td class="a6">
|
59
|
+
<div>
|
60
|
+
|
61
|
+
</div>
|
62
|
+
</td>
|
63
|
+
</tr>
|
64
|
+
|
65
|
+
<tr>
|
66
|
+
<td class="a1">
|
67
|
+
<a href="/subtitles/the-big-bang-theory-second-season/english/173831">
|
68
|
+
<div class="visited">
|
69
|
+
<span class="l r positive-icon">
|
70
|
+
English
|
71
|
+
</span>
|
72
|
+
<span>
|
73
|
+
The.Big.Bang.Theory.S02E02.HDTV.XviD-DOT [Edited]
|
74
|
+
</span>
|
75
|
+
</div>
|
76
|
+
</a>
|
77
|
+
</td>
|
78
|
+
<td class="a3">
|
79
|
+
1
|
80
|
+
</td>
|
81
|
+
<td class="a40">
|
82
|
+
|
83
|
+
</td>
|
84
|
+
<td class="a5">
|
85
|
+
|
86
|
+
<a href="/u/51235">
|
87
|
+
verdikt
|
88
|
+
</a>
|
89
|
+
</td>
|
90
|
+
<td class="a6">
|
91
|
+
<div>
|
92
|
+
The edited version of S02E02. Enjoi :)
|
93
|
+
</div>
|
94
|
+
</td>
|
95
|
+
</tr>
|
96
|
+
</tbody>
|