subscene 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/jassa/subscene.png)](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>
|