vibedeck-youtube_it 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|