vibedeck-youtube_it 0.0.1
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/README.rdoc +234 -0
- data/Rakefile +35 -0
- data/lib/youtube_it/chain_io.rb +76 -0
- data/lib/youtube_it/client.rb +367 -0
- data/lib/youtube_it/middleware/faraday_authheader.rb +24 -0
- data/lib/youtube_it/middleware/faraday_oauth.rb +21 -0
- data/lib/youtube_it/middleware/faraday_youtubeit.rb +30 -0
- data/lib/youtube_it/model/author.rb +13 -0
- data/lib/youtube_it/model/category.rb +11 -0
- data/lib/youtube_it/model/comment.rb +16 -0
- data/lib/youtube_it/model/contact.rb +16 -0
- data/lib/youtube_it/model/content.rb +18 -0
- data/lib/youtube_it/model/playlist.rb +11 -0
- data/lib/youtube_it/model/rating.rb +23 -0
- data/lib/youtube_it/model/subscription.rb +7 -0
- data/lib/youtube_it/model/thumbnail.rb +17 -0
- data/lib/youtube_it/model/user.rb +26 -0
- data/lib/youtube_it/model/video.rb +225 -0
- data/lib/youtube_it/parser.rb +357 -0
- data/lib/youtube_it/record.rb +12 -0
- data/lib/youtube_it/request/base_search.rb +72 -0
- data/lib/youtube_it/request/error.rb +15 -0
- data/lib/youtube_it/request/standard_search.rb +43 -0
- data/lib/youtube_it/request/user_search.rb +47 -0
- data/lib/youtube_it/request/video_search.rb +102 -0
- data/lib/youtube_it/request/video_upload.rb +415 -0
- data/lib/youtube_it/response/video_search.rb +41 -0
- data/lib/youtube_it/version.rb +4 -0
- data/lib/youtube_it.rb +75 -0
- data/test/helper.rb +10 -0
- data/test/test_chain_io.rb +63 -0
- data/test/test_client.rb +418 -0
- data/test/test_field_search.rb +48 -0
- data/test/test_video.rb +43 -0
- data/test/test_video_feed_parser.rb +271 -0
- data/test/test_video_search.rb +141 -0
- metadata +150 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
# TODO
|
2
|
+
# * self atom feed
|
3
|
+
# * alternate youtube watch url
|
4
|
+
# * comments feedLink
|
5
|
+
|
6
|
+
class YouTubeIt
|
7
|
+
module Model
|
8
|
+
class Video < YouTubeIt::Record
|
9
|
+
# Describes the various file formats in which a Youtube video may be
|
10
|
+
# made available and allows looking them up by format code number.
|
11
|
+
class Format
|
12
|
+
@@formats = Hash.new
|
13
|
+
|
14
|
+
# Instantiates a new video format object.
|
15
|
+
#
|
16
|
+
# == Parameters
|
17
|
+
# :format_code<Fixnum>:: The Youtube Format code of the object.
|
18
|
+
# :name<Symbol>:: The name of the format
|
19
|
+
#
|
20
|
+
# == Returns
|
21
|
+
# YouTubeIt::Model::Video::Format: Video format object
|
22
|
+
def initialize(format_code, name)
|
23
|
+
@format_code = format_code
|
24
|
+
@name = name
|
25
|
+
|
26
|
+
@@formats[format_code] = self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Allows you to get the video format for a specific format code.
|
30
|
+
#
|
31
|
+
# A full list of format codes is available at:
|
32
|
+
#
|
33
|
+
# http://code.google.com/apis/youtube/reference.html#youtube_data_api_tag_media:content
|
34
|
+
#
|
35
|
+
# == Parameters
|
36
|
+
# :format_code<Fixnum>:: The Youtube Format code of the object.
|
37
|
+
#
|
38
|
+
# == Returns
|
39
|
+
# YouTubeIt::Model::Video::Format: Video format object
|
40
|
+
def self.by_code(format_code)
|
41
|
+
@@formats[format_code]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Flash format on YouTube site. All videos are available in this format.
|
45
|
+
FLASH = YouTubeIt::Model::Video::Format.new(0, :flash)
|
46
|
+
|
47
|
+
# RTSP streaming URL for mobile video playback. H.263 video (176x144) and AMR audio.
|
48
|
+
RTSP = YouTubeIt::Model::Video::Format.new(1, :rtsp)
|
49
|
+
|
50
|
+
# HTTP URL to the embeddable player (SWF) for this video. This format
|
51
|
+
# is not available for a video that is not embeddable.
|
52
|
+
SWF = YouTubeIt::Model::Video::Format.new(5, :swf)
|
53
|
+
|
54
|
+
# RTSP streaming URL for mobile video playback. MPEG-4 SP video (up to 176x144) and AAC audio.
|
55
|
+
THREE_GPP = YouTubeIt::Model::Video::Format.new(6, :three_gpp)
|
56
|
+
end
|
57
|
+
|
58
|
+
# *Fixnum*:: Duration of a video in seconds.
|
59
|
+
attr_reader :duration
|
60
|
+
|
61
|
+
# *Boolean*:: Specifies that a video may or may not be 16:9 ratio.
|
62
|
+
attr_reader :widescreen
|
63
|
+
|
64
|
+
# *Boolean*:: Specifies that a video may or may not be embedded on other websites.
|
65
|
+
attr_reader :noembed
|
66
|
+
|
67
|
+
# *Fixnum*:: Specifies the order in which the video appears in a playlist.
|
68
|
+
attr_reader :position
|
69
|
+
|
70
|
+
# *Boolean*:: Specifies that a video is flagged as adult or not.
|
71
|
+
attr_reader :racy
|
72
|
+
|
73
|
+
# *String*: Specifies a URI that uniquely and permanently identifies the video.
|
74
|
+
attr_reader :video_id
|
75
|
+
|
76
|
+
# *Time*:: When the video was published on Youtube.
|
77
|
+
attr_reader :published_at
|
78
|
+
|
79
|
+
# *Time*:: When the video's data was last updated.
|
80
|
+
attr_reader :updated_at
|
81
|
+
|
82
|
+
# *Array*:: A array of YouTubeIt::Model::Category objects that describe the videos categories.
|
83
|
+
attr_reader :categories
|
84
|
+
|
85
|
+
# *Array*:: An array of words associated with the video.
|
86
|
+
attr_reader :keywords
|
87
|
+
|
88
|
+
# *String*:: Description of the video.
|
89
|
+
attr_reader :description
|
90
|
+
|
91
|
+
# *String*:: Title for the video.
|
92
|
+
attr_reader :title
|
93
|
+
|
94
|
+
# *String*:: Description of the video.
|
95
|
+
attr_reader :html_content
|
96
|
+
|
97
|
+
# YouTubeIt::Model::Author:: Information about the YouTube user who owns a piece of video content.
|
98
|
+
attr_reader :author
|
99
|
+
|
100
|
+
# *Array*:: An array of YouTubeIt::Model::Content objects describing the individual media content data available for this video. Most, but not all, videos offer this.
|
101
|
+
attr_reader :media_content
|
102
|
+
|
103
|
+
# *Array*:: An array of YouTubeIt::Model::Thumbnail objects that contain information regarding the videos thumbnail images.
|
104
|
+
attr_reader :thumbnails
|
105
|
+
|
106
|
+
# *String*:: The link to watch the URL on YouTubes website.
|
107
|
+
attr_reader :player_url
|
108
|
+
|
109
|
+
# YouTubeIt::Model::Rating:: Information about the videos rating.
|
110
|
+
attr_reader :rating
|
111
|
+
|
112
|
+
# *Fixnum*:: Number of times that the video has been viewed
|
113
|
+
attr_reader :view_count
|
114
|
+
|
115
|
+
# *Fixnum*:: Number of times that the video has been favorited
|
116
|
+
attr_reader :favorite_count
|
117
|
+
|
118
|
+
# *String*:: State of the video (processing, restricted, deleted, rejected and failed)
|
119
|
+
attr_reader :state
|
120
|
+
|
121
|
+
|
122
|
+
# Geodata
|
123
|
+
attr_reader :where
|
124
|
+
attr_reader :position
|
125
|
+
attr_reader :latitude
|
126
|
+
attr_reader :longitude
|
127
|
+
|
128
|
+
# Videos related to the current video.
|
129
|
+
#
|
130
|
+
# === Returns
|
131
|
+
# YouTubeIt::Response::VideoSearch
|
132
|
+
def related
|
133
|
+
YouTubeIt::Parser::VideosFeedParser.new("http://gdata.youtube.com/feeds/api/videos/#{unique_id}/related").parse
|
134
|
+
end
|
135
|
+
|
136
|
+
# Video responses to the current video.
|
137
|
+
#
|
138
|
+
# === Returns
|
139
|
+
# YouTubeIt::Response::VideoSearch
|
140
|
+
def responses
|
141
|
+
YouTubeIt::Parser::VideosFeedParser.new("http://gdata.youtube.com/feeds/api/videos/#{unique_id}/responses").parse
|
142
|
+
end
|
143
|
+
|
144
|
+
# The ID of the video, useful for searching for the video again without having to store it anywhere.
|
145
|
+
# A regular query search, with this id will return the same video.
|
146
|
+
#
|
147
|
+
# === Example
|
148
|
+
# >> video.unique_id
|
149
|
+
# => "ZTUVgYoeN_o"
|
150
|
+
#
|
151
|
+
# === Returns
|
152
|
+
# String: The Youtube video id.
|
153
|
+
def unique_id
|
154
|
+
video_id[/videos\/([^<]+)/, 1] || video_id[/video\:([^<]+)/, 1]
|
155
|
+
end
|
156
|
+
|
157
|
+
# Allows you to check whether the video can be embedded on a webpage.
|
158
|
+
#
|
159
|
+
# === Returns
|
160
|
+
# Boolean: True if the video can be embedded, false if not.
|
161
|
+
def embeddable?
|
162
|
+
not @noembed
|
163
|
+
end
|
164
|
+
|
165
|
+
# Allows you to check whether the video is widescreen (16:9) or not.
|
166
|
+
#
|
167
|
+
# === Returns
|
168
|
+
# Boolean: True if the video is (approximately) 16:9, false if not.
|
169
|
+
def widescreen?
|
170
|
+
@widescreen
|
171
|
+
end
|
172
|
+
|
173
|
+
# Provides a URL and various other types of information about a video.
|
174
|
+
#
|
175
|
+
# === Returns
|
176
|
+
# YouTubeIt::Model::Content: Data about the embeddable video.
|
177
|
+
def default_media_content
|
178
|
+
@media_content.find { |c| c.is_default? }
|
179
|
+
end
|
180
|
+
|
181
|
+
# Gives you the HTML to embed the video on your website.
|
182
|
+
#
|
183
|
+
# === Returns
|
184
|
+
# String: The HTML for embedding the video on your website.
|
185
|
+
def embed_html(width = 425, height = 350)
|
186
|
+
<<EDOC
|
187
|
+
<object width="#{width}" height="#{height}">
|
188
|
+
<param name="movie" value="#{embed_url}"></param>
|
189
|
+
<param name="wmode" value="transparent"></param>
|
190
|
+
<embed src="#{embed_url}" type="application/x-shockwave-flash"
|
191
|
+
wmode="transparent" width="#{width}" height="#{height}"></embed>
|
192
|
+
</object>
|
193
|
+
EDOC
|
194
|
+
end
|
195
|
+
|
196
|
+
# Gives you the HTML to embed the video on your website.
|
197
|
+
#
|
198
|
+
# === Returns
|
199
|
+
# String: The HTML for embedding the video on your website.
|
200
|
+
def embed_html_with_width(width = 1280)
|
201
|
+
height = (widescreen? ? width * 9/16 : width * 3/4) + 25
|
202
|
+
|
203
|
+
<<EDOC
|
204
|
+
<object width="#{width}" height="#{height}">
|
205
|
+
<param name="movie" value="#{embed_url}"></param>
|
206
|
+
<param name="wmode" value="transparent"></param>
|
207
|
+
<embed src="#{embed_url}" type="application/x-shockwave-flash"
|
208
|
+
wmode="transparent" width="#{width}" height="#{height}"></embed>
|
209
|
+
</object>
|
210
|
+
EDOC
|
211
|
+
end
|
212
|
+
|
213
|
+
# The URL needed for embedding the video in a page.
|
214
|
+
#
|
215
|
+
# === Returns
|
216
|
+
# String: Absolute URL for embedding video
|
217
|
+
def embed_url
|
218
|
+
@player_url.sub('watch?', '').sub('=', '/').sub('feature/', 'feature=')
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
@@ -0,0 +1,357 @@
|
|
1
|
+
class YouTubeIt
|
2
|
+
module Parser #:nodoc:
|
3
|
+
class FeedParser #:nodoc:
|
4
|
+
def initialize(content)
|
5
|
+
@content = open(content).read
|
6
|
+
|
7
|
+
rescue OpenURI::HTTPError => e
|
8
|
+
raise OpenURI::HTTPError.new(e.io.status[0],e)
|
9
|
+
rescue
|
10
|
+
@content = content
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
parse_content @content
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_videos
|
19
|
+
doc = REXML::Document.new(@content)
|
20
|
+
videos = []
|
21
|
+
doc.elements.each("*/entry") do |video|
|
22
|
+
videos << parse_entry(video)
|
23
|
+
end
|
24
|
+
videos
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class CommentsFeedParser < FeedParser #:nodoc:
|
29
|
+
# return array of comments
|
30
|
+
def parse_content(content)
|
31
|
+
doc = REXML::Document.new(content.body)
|
32
|
+
feed = doc.elements["feed"]
|
33
|
+
|
34
|
+
comments = []
|
35
|
+
feed.elements.each("entry") do |entry|
|
36
|
+
comments << parse_entry(entry)
|
37
|
+
end
|
38
|
+
return comments
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def parse_entry(entry)
|
43
|
+
author = YouTubeIt::Model::Author.new(
|
44
|
+
:name => entry.elements["author"].elements["name"].text,
|
45
|
+
:uri => entry.elements["author"].elements["uri"].text,
|
46
|
+
:thumbnail_url => entry.elements["media:thumbnail"].attributes["url"]
|
47
|
+
)
|
48
|
+
YouTubeIt::Model::Comment.new(
|
49
|
+
:author => author,
|
50
|
+
:content => entry.elements["content"].text,
|
51
|
+
:published => entry.elements["published"].text,
|
52
|
+
:title => entry.elements["title"].text,
|
53
|
+
:updated => entry.elements["updated "].text,
|
54
|
+
:url => entry.elements["id"].text
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class PlaylistFeedParser < FeedParser #:nodoc:
|
60
|
+
|
61
|
+
def parse_content(content)
|
62
|
+
xml = REXML::Document.new(content.body)
|
63
|
+
entry = xml.elements["entry"] || xml.elements["feed"]
|
64
|
+
YouTubeIt::Model::Playlist.new(
|
65
|
+
:title => entry.elements["title"].text,
|
66
|
+
:summary => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
|
67
|
+
:description => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
|
68
|
+
:playlist_id => entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':',''),
|
69
|
+
:published => entry.elements["published"] ? entry.elements["published"].text : nil,
|
70
|
+
:response_code => content.status,
|
71
|
+
:xml => content.body)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class PlaylistsFeedParser < FeedParser #:nodoc:
|
76
|
+
|
77
|
+
# return array of playlist objects
|
78
|
+
def parse_content(content)
|
79
|
+
doc = REXML::Document.new(content.body)
|
80
|
+
feed = doc.elements["feed"]
|
81
|
+
|
82
|
+
playlists = []
|
83
|
+
feed.elements.each("entry") do |entry|
|
84
|
+
playlists << parse_entry(entry)
|
85
|
+
end
|
86
|
+
return playlists
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def parse_entry(entry)
|
92
|
+
YouTubeIt::Model::Playlist.new(
|
93
|
+
:title => entry.elements["title"].text,
|
94
|
+
:summary => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
|
95
|
+
:description => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
|
96
|
+
:playlist_id => entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':',''),
|
97
|
+
:published => entry.elements["published"] ? entry.elements["published"].text : nil,
|
98
|
+
:response_code => nil,
|
99
|
+
:xml => nil)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class ProfileFeedParser < FeedParser #:nodoc:
|
104
|
+
def parse_content(content)
|
105
|
+
xml = REXML::Document.new(content.body)
|
106
|
+
entry = xml.elements["entry"] || xml.elements["feed"]
|
107
|
+
YouTubeIt::Model::User.new(
|
108
|
+
:age => entry.elements["yt:age"] ? entry.elements["yt:age"].text : nil,
|
109
|
+
:username => entry.elements["yt:username"] ? entry.elements["yt:username"].text : nil,
|
110
|
+
:company => entry.elements["yt:company"] ? entry.elements["yt:company"].text : nil,
|
111
|
+
:gender => entry.elements["yt:gender"] ? entry.elements["yt:gender"].text : nil,
|
112
|
+
:hobbies => entry.elements["yt:hobbies"] ? entry.elements["yt:hobbies"].text : nil,
|
113
|
+
:hometown => entry.elements["yt:hometown"] ? entry.elements["yt:hometown"].text : nil,
|
114
|
+
:location => entry.elements["yt:location"] ? entry.elements["yt:location"].text : nil,
|
115
|
+
:last_login => entry.elements["yt:statistics"].attributes["lastWebAccess"],
|
116
|
+
:join_date => entry.elements["published"] ? entry.elements["published"].text : nil,
|
117
|
+
:movies => entry.elements["yt:movies"] ? entry.elements["yt:movies"].text : nil,
|
118
|
+
:music => entry.elements["yt:music"] ? entry.elements["yt:music"].text : nil,
|
119
|
+
:occupation => entry.elements["yt:occupation"] ? entry.elements["yt:occupation"].text : nil,
|
120
|
+
:relationship => entry.elements["yt:relationship"] ? entry.elements["yt:relationship"].text : nil,
|
121
|
+
:school => entry.elements["yt:school"] ? entry.elements["yt:school"].text : nil,
|
122
|
+
:subscribers => entry.elements["yt:statistics"].attributes["subscriberCount"],
|
123
|
+
:videos_watched => entry.elements["yt:statistics"].attributes["videoWatchCount"],
|
124
|
+
:view_count => entry.elements["yt:statistics"].attributes["viewCount"],
|
125
|
+
:upload_views => entry.elements["yt:statistics"].attributes["totalUploadViews"]
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class SubscriptionFeedParser < FeedParser #:nodoc:
|
131
|
+
|
132
|
+
def parse_content(content)
|
133
|
+
doc = REXML::Document.new(content.body)
|
134
|
+
feed = doc.elements["feed"]
|
135
|
+
|
136
|
+
subscriptions = []
|
137
|
+
feed.elements.each("entry") do |entry|
|
138
|
+
subscriptions << parse_entry(entry)
|
139
|
+
end
|
140
|
+
return subscriptions
|
141
|
+
end
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
def parse_entry(entry)
|
146
|
+
YouTubeIt::Model::Subscription.new(
|
147
|
+
:title => entry.elements["title"].text,
|
148
|
+
:id => entry.elements["id"].text[/subscription([^<]+)/, 1].sub(':',''),
|
149
|
+
:published => entry.elements["published"] ? entry.elements["published"].text : nil
|
150
|
+
)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
class VideoFeedParser < FeedParser #:nodoc:
|
156
|
+
|
157
|
+
def parse_content(content)
|
158
|
+
doc = REXML::Document.new(content)
|
159
|
+
entry = doc.elements["entry"]
|
160
|
+
parse_entry(entry)
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
def parse_entry(entry)
|
165
|
+
video_id = entry.elements["id"].text
|
166
|
+
published_at = entry.elements["published"] ? Time.parse(entry.elements["published"].text) : nil
|
167
|
+
updated_at = entry.elements["updated"] ? Time.parse(entry.elements["updated"].text) : nil
|
168
|
+
|
169
|
+
# parse the category and keyword lists
|
170
|
+
categories = []
|
171
|
+
keywords = []
|
172
|
+
entry.elements.each("category") do |category|
|
173
|
+
# determine if it's really a category, or just a keyword
|
174
|
+
scheme = category.attributes["scheme"]
|
175
|
+
if (scheme =~ /\/categories\.cat$/)
|
176
|
+
# it's a category
|
177
|
+
categories << YouTubeIt::Model::Category.new(
|
178
|
+
:term => category.attributes["term"],
|
179
|
+
:label => category.attributes["label"])
|
180
|
+
|
181
|
+
elsif (scheme =~ /\/keywords\.cat$/)
|
182
|
+
# it's a keyword
|
183
|
+
keywords << category.attributes["term"]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
title = entry.elements["title"].text
|
188
|
+
html_content = entry.elements["content"] ? entry.elements["content"].text : nil
|
189
|
+
|
190
|
+
# parse the author
|
191
|
+
author_element = entry.elements["author"]
|
192
|
+
author = nil
|
193
|
+
if author_element
|
194
|
+
author = YouTubeIt::Model::Author.new(
|
195
|
+
:name => author_element.elements["name"].text,
|
196
|
+
:uri => author_element.elements["uri"].text)
|
197
|
+
end
|
198
|
+
media_group = entry.elements["media:group"]
|
199
|
+
|
200
|
+
# if content is not available on certain region, there is no media:description, media:player or yt:duration
|
201
|
+
description = ""
|
202
|
+
unless media_group.elements["media:description"].nil?
|
203
|
+
description = media_group.elements["media:description"].text
|
204
|
+
end
|
205
|
+
|
206
|
+
# if content is not available on certain region, there is no media:description, media:player or yt:duration
|
207
|
+
duration = 0
|
208
|
+
unless media_group.elements["yt:duration"].nil?
|
209
|
+
duration = media_group.elements["yt:duration"].attributes["seconds"].to_i
|
210
|
+
end
|
211
|
+
|
212
|
+
# if content is not available on certain region, there is no media:description, media:player or yt:duration
|
213
|
+
player_url = ""
|
214
|
+
unless media_group.elements["media:player"].nil?
|
215
|
+
player_url = media_group.elements["media:player"].attributes["url"]
|
216
|
+
end
|
217
|
+
|
218
|
+
unless media_group.elements["yt:aspectRatio"].nil?
|
219
|
+
widescreen = media_group.elements["yt:aspectRatio"].text == 'widescreen' ? true : false
|
220
|
+
end
|
221
|
+
|
222
|
+
media_content = []
|
223
|
+
media_group.elements.each("media:content") do |mce|
|
224
|
+
media_content << parse_media_content(mce)
|
225
|
+
end
|
226
|
+
|
227
|
+
# parse thumbnails
|
228
|
+
thumbnails = []
|
229
|
+
media_group.elements.each("media:thumbnail") do |thumb_element|
|
230
|
+
# TODO: convert time HH:MM:ss string to seconds?
|
231
|
+
thumbnails << YouTubeIt::Model::Thumbnail.new(
|
232
|
+
:url => thumb_element.attributes["url"],
|
233
|
+
:height => thumb_element.attributes["height"].to_i,
|
234
|
+
:width => thumb_element.attributes["width"].to_i,
|
235
|
+
:time => thumb_element.attributes["time"])
|
236
|
+
end
|
237
|
+
|
238
|
+
rating_element = entry.elements["gd:rating"]
|
239
|
+
extended_rating_element = entry.elements["yt:rating"]
|
240
|
+
|
241
|
+
rating = nil
|
242
|
+
if rating_element
|
243
|
+
rating_values = {
|
244
|
+
:min => rating_element.attributes["min"].to_i,
|
245
|
+
:max => rating_element.attributes["max"].to_i,
|
246
|
+
:rater_count => rating_element.attributes["numRaters"].to_i,
|
247
|
+
:average => rating_element.attributes["average"].to_f
|
248
|
+
}
|
249
|
+
|
250
|
+
if extended_rating_element
|
251
|
+
rating_values[:likes] = extended_rating_element.attributes["numLikes"].to_i
|
252
|
+
rating_values[:dislikes] = extended_rating_element.attributes["numDislikes"].to_i
|
253
|
+
end
|
254
|
+
|
255
|
+
rating = YouTubeIt::Model::Rating.new(rating_values)
|
256
|
+
end
|
257
|
+
|
258
|
+
if (el = entry.elements["yt:statistics"])
|
259
|
+
view_count, favorite_count = el.attributes["viewCount"].to_i, el.attributes["favoriteCount"].to_i
|
260
|
+
else
|
261
|
+
view_count, favorite_count = 0,0
|
262
|
+
end
|
263
|
+
|
264
|
+
noembed = entry.elements["yt:noembed"] ? true : false
|
265
|
+
racy = entry.elements["media:rating"] ? true : false
|
266
|
+
|
267
|
+
if where = entry.elements["georss:where"]
|
268
|
+
position = where.elements["gml:Point"].elements["gml:pos"].text
|
269
|
+
latitude, longitude = position.split(" ")
|
270
|
+
end
|
271
|
+
|
272
|
+
control = entry.elements["app:control"]
|
273
|
+
state = { :name => "published" }
|
274
|
+
if control && control.elements["yt:state"]
|
275
|
+
state = {
|
276
|
+
:name => control.elements["yt:state"].attributes["name"],
|
277
|
+
:reason_code => control.elements["yt:state"].attributes["reasonCode"],
|
278
|
+
:help_url => control.elements["yt:state"].attributes["helpUrl"],
|
279
|
+
:copy => control.elements["yt:state"].text
|
280
|
+
}
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
YouTubeIt::Model::Video.new(
|
285
|
+
:video_id => video_id,
|
286
|
+
:published_at => published_at,
|
287
|
+
:updated_at => updated_at,
|
288
|
+
:categories => categories,
|
289
|
+
:keywords => keywords,
|
290
|
+
:title => title,
|
291
|
+
:html_content => html_content,
|
292
|
+
:author => author,
|
293
|
+
:description => description,
|
294
|
+
:duration => duration,
|
295
|
+
:media_content => media_content,
|
296
|
+
:player_url => player_url,
|
297
|
+
:thumbnails => thumbnails,
|
298
|
+
:rating => rating,
|
299
|
+
:view_count => view_count,
|
300
|
+
:favorite_count => favorite_count,
|
301
|
+
:widescreen => widescreen,
|
302
|
+
:noembed => noembed,
|
303
|
+
:racy => racy,
|
304
|
+
:where => where,
|
305
|
+
:position => position,
|
306
|
+
:latitude => latitude,
|
307
|
+
:longitude => longitude,
|
308
|
+
:state => state)
|
309
|
+
end
|
310
|
+
|
311
|
+
def parse_media_content (media_content_element)
|
312
|
+
content_url = media_content_element.attributes["url"]
|
313
|
+
format_code = media_content_element.attributes["yt:format"].to_i
|
314
|
+
format = YouTubeIt::Model::Video::Format.by_code(format_code)
|
315
|
+
duration = media_content_element.attributes["duration"].to_i
|
316
|
+
mime_type = media_content_element.attributes["type"]
|
317
|
+
default = (media_content_element.attributes["isDefault"] == "true")
|
318
|
+
|
319
|
+
YouTubeIt::Model::Content.new(
|
320
|
+
:url => content_url,
|
321
|
+
:format => format,
|
322
|
+
:duration => duration,
|
323
|
+
:mime_type => mime_type,
|
324
|
+
:default => default)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
class VideosFeedParser < VideoFeedParser #:nodoc:
|
329
|
+
|
330
|
+
private
|
331
|
+
def parse_content(content)
|
332
|
+
videos = []
|
333
|
+
doc = REXML::Document.new(content)
|
334
|
+
feed = doc.elements["feed"]
|
335
|
+
if feed
|
336
|
+
feed_id = feed.elements["id"].text
|
337
|
+
updated_at = Time.parse(feed.elements["updated"].text)
|
338
|
+
total_result_count = feed.elements["openSearch:totalResults"].text.to_i
|
339
|
+
offset = feed.elements["openSearch:startIndex"].text.to_i
|
340
|
+
max_result_count = feed.elements["openSearch:itemsPerPage"].text.to_i
|
341
|
+
|
342
|
+
feed.elements.each("entry") do |entry|
|
343
|
+
videos << parse_entry(entry)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
YouTubeIt::Response::VideoSearch.new(
|
347
|
+
:feed_id => feed_id || nil,
|
348
|
+
:updated_at => updated_at || nil,
|
349
|
+
:total_result_count => total_result_count || nil,
|
350
|
+
:offset => offset || nil,
|
351
|
+
:max_result_count => max_result_count || nil,
|
352
|
+
:videos => videos)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class YouTubeIt
|
2
|
+
module Request #:nodoc:
|
3
|
+
class BaseSearch #:nodoc:
|
4
|
+
attr_reader :url
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def base_url
|
9
|
+
"http://gdata.youtube.com/feeds/api/"
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_instance_variables( variables )
|
13
|
+
variables.each do |key, value|
|
14
|
+
name = key.to_s
|
15
|
+
instance_variable_set("@#{name}", value) if respond_to?(name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_query_params(params)
|
20
|
+
qs = params.to_a.map { | k, v | v.nil? ? nil : "#{YouTubeIt.esc(k)}=#{YouTubeIt.esc(v)}" }.compact.sort.join('&')
|
21
|
+
qs.empty? ? '' : (@dev_key ? "?#{qs}&key=#{@dev_key}" : "?#{qs}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
module FieldSearch
|
27
|
+
def default_fields
|
28
|
+
"id,updated,openSearch:totalResults,openSearch:startIndex,openSearch:itemsPerPage"
|
29
|
+
end
|
30
|
+
|
31
|
+
def fields_to_params(fields)
|
32
|
+
return "" unless fields
|
33
|
+
|
34
|
+
fields_param = [default_fields]
|
35
|
+
|
36
|
+
if fields[:recorded]
|
37
|
+
if fields[:recorded].is_a? Range
|
38
|
+
fields_param << "entry[xs:date(yt:recorded) > xs:date('#{formatted_date(fields[:recorded].first)}') and xs:date(yt:recorded) < xs:date('#{formatted_date(fields[:recorded].last)}')]"
|
39
|
+
else
|
40
|
+
fields_param << "entry[xs:date(yt:recorded) = xs:date('#{formatted_date(fields[:recorded])}')]"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if fields[:published]
|
45
|
+
if fields[:published].is_a? Range
|
46
|
+
fields_param << "entry[xs:dateTime(published) > xs:dateTime('#{formatted_date(fields[:published].first)}T00:00:00') and xs:dateTime(published) < xs:dateTime('#{formatted_date(fields[:published].last)}T00:00:00')]"
|
47
|
+
else
|
48
|
+
fields_param << "entry[xs:date(published) = xs:date('#{formatted_date(fields[:published])}')]"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if fields[:view_count]
|
53
|
+
fields_param << "entry[yt:statistics/@viewCount > #{fields[:view_count]}]"
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
return "&fields=#{URI.escape(fields_param.join(","))}"
|
58
|
+
end
|
59
|
+
|
60
|
+
#youtube taked dates that look like 'YYYY-MM-DD'
|
61
|
+
def formatted_date(date)
|
62
|
+
return date if date.is_a? String
|
63
|
+
if date.respond_to? :strftime
|
64
|
+
date.strftime("%Y-%m-%d")
|
65
|
+
else
|
66
|
+
""
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class UploadError < YouTubeIt::Error
|
2
|
+
attr_reader :code
|
3
|
+
def initialize(msg, code = 0)
|
4
|
+
super(msg)
|
5
|
+
@code = code
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class AuthenticationError < YouTubeIt::Error
|
10
|
+
attr_reader :code
|
11
|
+
def initialize(msg, code = 0)
|
12
|
+
super(msg)
|
13
|
+
@code = code
|
14
|
+
end
|
15
|
+
end
|