youtube_it 2.1.4 → 2.1.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.
- data/README.rdoc +57 -5
- data/lib/youtube_it.rb +3 -3
- data/lib/youtube_it/chain_io.rb +1 -1
- data/lib/youtube_it/client.rb +48 -11
- data/lib/youtube_it/model/comment.rb +3 -3
- data/lib/youtube_it/model/playlist.rb +1 -1
- data/lib/youtube_it/model/user.rb +7 -0
- data/lib/youtube_it/model/video.rb +37 -4
- data/lib/youtube_it/parser.rb +269 -218
- data/lib/youtube_it/request/base_search.rb +4 -0
- data/lib/youtube_it/request/video_search.rb +20 -4
- data/lib/youtube_it/request/video_upload.rb +166 -70
- data/lib/youtube_it/version.rb +1 -1
- data/youtube_it.gemspec +20 -91
- metadata +72 -134
- data/Gemfile +0 -12
- data/Gemfile.lock +0 -43
- data/Manifest.txt +0 -37
- data/Rakefile +0 -58
- data/VERSION +0 -1
- data/test/files/recorded_response.xml +0 -1
- data/test/files/youtube_video_response.xml +0 -53
- data/test/helper.rb +0 -9
- data/test/test.mov +0 -0
- data/test/test_chain_io.rb +0 -63
- data/test/test_client.rb +0 -454
- data/test/test_field_search.rb +0 -48
- data/test/test_video.rb +0 -48
- data/test/test_video_feed_parser.rb +0 -271
- data/test/test_video_search.rb +0 -147
data/lib/youtube_it/parser.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
class YouTubeIt
|
2
4
|
module Parser #:nodoc:
|
3
5
|
class FeedParser #:nodoc:
|
4
6
|
def initialize(content)
|
5
7
|
@content = (content =~ URI::regexp(%w(http https)) ? open(content).read : content)
|
6
|
-
|
8
|
+
|
7
9
|
rescue OpenURI::HTTPError => e
|
8
10
|
raise OpenURI::HTTPError.new(e.io.status[0],e)
|
9
11
|
rescue
|
@@ -16,23 +18,27 @@ class YouTubeIt
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def parse_videos
|
19
|
-
doc =
|
21
|
+
doc = Nokogiri::XML(@content)
|
20
22
|
videos = []
|
21
|
-
doc.
|
23
|
+
doc.css("entry").each do |video|
|
22
24
|
videos << parse_entry(video)
|
23
25
|
end
|
24
26
|
videos
|
25
27
|
end
|
28
|
+
|
29
|
+
def remove_bom str
|
30
|
+
str.gsub /\xEF\xBB\xBF|/, ''
|
31
|
+
end
|
26
32
|
end
|
27
33
|
|
28
34
|
class CommentsFeedParser < FeedParser #:nodoc:
|
29
35
|
# return array of comments
|
30
36
|
def parse_content(content)
|
31
|
-
doc =
|
32
|
-
feed = doc.
|
37
|
+
doc = Nokogiri::XML(content.body)
|
38
|
+
feed = doc.at("feed")
|
33
39
|
|
34
40
|
comments = []
|
35
|
-
feed.
|
41
|
+
feed.css("entry").each do |entry|
|
36
42
|
comments << parse_entry(entry)
|
37
43
|
end
|
38
44
|
return comments
|
@@ -41,31 +47,38 @@ class YouTubeIt
|
|
41
47
|
protected
|
42
48
|
def parse_entry(entry)
|
43
49
|
author = YouTubeIt::Model::Author.new(
|
44
|
-
:name => (entry.
|
45
|
-
:uri => (entry.
|
50
|
+
:name => (entry.at("author/name").text rescue nil),
|
51
|
+
:uri => (entry.at("author/uri").text rescue nil)
|
46
52
|
)
|
47
53
|
YouTubeIt::Model::Comment.new(
|
48
54
|
:author => author,
|
49
|
-
:content => entry.
|
50
|
-
:published => entry.
|
51
|
-
:title => entry.
|
52
|
-
:updated => entry.
|
53
|
-
:url => entry.
|
55
|
+
:content => remove_bom(entry.at("content").text),
|
56
|
+
:published => entry.at("published").text,
|
57
|
+
:title => remove_bom(entry.at("title").text),
|
58
|
+
:updated => entry.at("updated").text,
|
59
|
+
:url => entry.at("id").text,
|
60
|
+
:reply_to => parse_reply(entry)
|
54
61
|
)
|
55
62
|
end
|
63
|
+
|
64
|
+
def parse_reply(entry)
|
65
|
+
if link = entry.at_xpath("xmlns:link[@rel='http://gdata.youtube.com/schemas/2007#in-reply-to']")
|
66
|
+
link["href"].split('/').last.gsub(/\?client.*/, '')
|
67
|
+
end
|
68
|
+
end
|
56
69
|
end
|
57
70
|
|
58
71
|
class PlaylistFeedParser < FeedParser #:nodoc:
|
59
72
|
|
60
73
|
def parse_content(content)
|
61
|
-
xml =
|
62
|
-
entry = xml.
|
74
|
+
xml = Nokogiri::XML(content.body)
|
75
|
+
entry = xml.at("entry") || xml.at("feed")
|
63
76
|
YouTubeIt::Model::Playlist.new(
|
64
|
-
:title => entry.
|
65
|
-
:summary => (entry.
|
66
|
-
:description => (entry.
|
67
|
-
:playlist_id => entry.
|
68
|
-
:published => entry.
|
77
|
+
:title => entry.at("title") && entry.at("title").text,
|
78
|
+
:summary => ((entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text rescue nil),
|
79
|
+
:description => ((entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text rescue nil),
|
80
|
+
:playlist_id => (entry.at("id").text[/playlist:(\w+)/, 1] rescue nil),
|
81
|
+
:published => entry.at("published") ? entry.at("published").text : nil,
|
69
82
|
:response_code => content.status,
|
70
83
|
:xml => content.body)
|
71
84
|
end
|
@@ -75,398 +88,433 @@ class YouTubeIt
|
|
75
88
|
|
76
89
|
# return array of playlist objects
|
77
90
|
def parse_content(content)
|
78
|
-
doc =
|
79
|
-
feed = doc.
|
80
|
-
|
91
|
+
doc = Nokogiri::XML(content.body)
|
92
|
+
feed = doc.at("feed")
|
93
|
+
|
81
94
|
playlists = []
|
82
|
-
feed.
|
95
|
+
feed.css("entry").each do |entry|
|
83
96
|
playlists << parse_entry(entry)
|
84
97
|
end
|
85
98
|
return playlists
|
86
99
|
end
|
87
|
-
|
100
|
+
|
88
101
|
protected
|
89
|
-
|
102
|
+
|
90
103
|
def parse_entry(entry)
|
91
104
|
YouTubeIt::Model::Playlist.new(
|
92
|
-
:title => entry.
|
93
|
-
:summary => (entry.
|
94
|
-
:description => (entry.
|
95
|
-
:playlist_id => entry.
|
96
|
-
:published => entry.
|
105
|
+
:title => entry.at("title").text,
|
106
|
+
:summary => (entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text,
|
107
|
+
:description => (entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text,
|
108
|
+
:playlist_id => entry.at("id").text[/playlist([^<]+)/, 1].sub(':',''),
|
109
|
+
:published => entry.at("published") ? entry.at("published").text : nil,
|
97
110
|
:response_code => nil,
|
98
111
|
:xml => nil)
|
99
112
|
end
|
100
113
|
end
|
101
|
-
|
114
|
+
|
102
115
|
# Returns an array of the user's activity
|
103
116
|
class ActivityParser < FeedParser
|
104
117
|
def parse_content(content)
|
105
|
-
doc =
|
106
|
-
feed = doc.
|
107
|
-
|
108
|
-
|
109
|
-
feed.
|
110
|
-
parsed_activity = parse_activity(entry)
|
111
|
-
|
112
|
-
activity << parsed_activity
|
118
|
+
doc = Nokogiri::XML(content.body)
|
119
|
+
feed = doc.at("feed")
|
120
|
+
|
121
|
+
activities = []
|
122
|
+
feed.css("entry").each do |entry|
|
123
|
+
if parsed_activity = parse_activity(entry)
|
124
|
+
activities << parsed_activity
|
113
125
|
end
|
114
126
|
end
|
115
|
-
|
116
|
-
return
|
127
|
+
|
128
|
+
return activities
|
117
129
|
end
|
118
|
-
|
130
|
+
|
119
131
|
protected
|
120
|
-
|
132
|
+
|
121
133
|
# Parses the user's activity feed.
|
122
134
|
def parse_activity(entry)
|
123
135
|
# Figure out what kind of activity we have
|
124
136
|
video_type = nil
|
125
137
|
parsed_activity = nil
|
126
|
-
entry.
|
127
|
-
if category_tag
|
128
|
-
video_type = category_tag
|
138
|
+
entry.css("category").each do |category_tag|
|
139
|
+
if category_tag["scheme"] == "http://gdata.youtube.com/schemas/2007/userevents.cat"
|
140
|
+
video_type = category_tag["term"]
|
129
141
|
end
|
130
142
|
end
|
131
|
-
|
143
|
+
|
132
144
|
if video_type
|
133
145
|
case video_type
|
134
146
|
when "video_rated"
|
135
147
|
parsed_activity = YouTubeIt::Model::Activity.new(
|
136
148
|
:type => "video_rated",
|
137
|
-
:time => entry.
|
138
|
-
:author => entry.
|
149
|
+
:time => entry.at("updated") ? entry.at("updated").text : nil,
|
150
|
+
:author => entry.at("author/name") ? entry.at("author/name").text : nil,
|
139
151
|
:videos => parse_activity_videos(entry),
|
140
|
-
:video_id => entry.
|
152
|
+
:video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
|
141
153
|
)
|
142
154
|
when "video_shared"
|
143
155
|
parsed_activity = YouTubeIt::Model::Activity.new(
|
144
156
|
:type => "video_shared",
|
145
|
-
:time => entry.
|
146
|
-
:author => entry.
|
157
|
+
:time => entry.at("updated") ? entry.at("updated").text : nil,
|
158
|
+
:author => entry.at("author/name") ? entry.at("author/name").text : nil,
|
147
159
|
:videos => parse_activity_videos(entry),
|
148
|
-
:video_id => entry.
|
160
|
+
:video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
|
149
161
|
)
|
150
162
|
when "video_favorited"
|
151
163
|
parsed_activity = YouTubeIt::Model::Activity.new(
|
152
164
|
:type => "video_favorited",
|
153
|
-
:time => entry.
|
154
|
-
:author => entry.
|
165
|
+
:time => entry.at("updated") ? entry.at("updated").text : nil,
|
166
|
+
:author => entry.at("author/name") ? entry.at("author/name").text : nil,
|
155
167
|
:videos => parse_activity_videos(entry),
|
156
|
-
:video_id => entry.
|
168
|
+
:video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
|
157
169
|
)
|
158
170
|
when "video_commented"
|
159
171
|
# Load the comment and video URL
|
160
172
|
comment_thread_url = nil
|
161
173
|
video_url = nil
|
162
|
-
entry.
|
163
|
-
case link_tag
|
174
|
+
entry.css("link").each do |link_tag|
|
175
|
+
case link_tag["rel"]
|
164
176
|
when "http://gdata.youtube.com/schemas/2007#comments"
|
165
|
-
comment_thread_url = link_tag
|
177
|
+
comment_thread_url = link_tag["href"]
|
166
178
|
when "http://gdata.youtube.com/schemas/2007#video"
|
167
|
-
video_url = link_tag
|
179
|
+
video_url = link_tag["href"]
|
168
180
|
else
|
169
181
|
# Invalid rel type, do nothing
|
170
182
|
end
|
171
183
|
end
|
172
|
-
|
184
|
+
|
173
185
|
parsed_activity = YouTubeIt::Model::Activity.new(
|
174
186
|
:type => "video_commented",
|
175
|
-
:time => entry.
|
176
|
-
:author => entry.
|
187
|
+
:time => entry.at("updated") ? entry.at("updated").text : nil,
|
188
|
+
:author => entry.at("author/name") ? entry.at("author/name").text : nil,
|
177
189
|
:videos => parse_activity_videos(entry),
|
178
|
-
:video_id => entry.
|
190
|
+
:video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil,
|
179
191
|
:comment_thread_url => comment_thread_url,
|
180
192
|
:video_url => video_url
|
181
193
|
)
|
182
194
|
when "video_uploaded"
|
183
195
|
parsed_activity = YouTubeIt::Model::Activity.new(
|
184
196
|
:type => "video_uploaded",
|
185
|
-
:time => entry.
|
186
|
-
:author => entry.
|
197
|
+
:time => entry.at("updated") ? entry.at("updated").text : nil,
|
198
|
+
:author => entry.at("author/name") ? entry.at("author/name").text : nil,
|
187
199
|
:videos => parse_activity_videos(entry),
|
188
|
-
:video_id => entry.
|
200
|
+
:video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
|
189
201
|
)
|
190
202
|
when "friend_added"
|
191
203
|
parsed_activity = YouTubeIt::Model::Activity.new(
|
192
204
|
:type => "friend_added",
|
193
|
-
:time => entry.
|
194
|
-
:author => entry.
|
195
|
-
:username => entry.
|
205
|
+
:time => entry.at("updated") ? entry.at("updated").text : nil,
|
206
|
+
:author => entry.at("author/name") ? entry.at("author/name").text : nil,
|
207
|
+
:username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil
|
196
208
|
)
|
197
209
|
when "user_subscription_added"
|
198
210
|
parsed_activity = YouTubeIt::Model::Activity.new(
|
199
211
|
:type => "user_subscription_added",
|
200
|
-
:time => entry.
|
201
|
-
:author => entry.
|
202
|
-
:username => entry.
|
212
|
+
:time => entry.at("updated") ? entry.at("updated").text : nil,
|
213
|
+
:author => entry.at("author/name") ? entry.at("author/name").text : nil,
|
214
|
+
:username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil
|
203
215
|
)
|
204
216
|
else
|
205
217
|
# Invalid activity type, just let it return nil
|
206
218
|
end
|
207
219
|
end
|
208
|
-
|
220
|
+
|
209
221
|
return parsed_activity
|
210
222
|
end
|
211
|
-
|
223
|
+
|
212
224
|
# If a user enabled inline attribute videos may be included in results.
|
213
225
|
def parse_activity_videos(entry)
|
214
226
|
videos = []
|
215
|
-
|
216
|
-
entry.
|
217
|
-
if link_tag.
|
218
|
-
videos << YouTubeIt::Parser::VideoFeedParser.new(link_tag).parse
|
219
|
-
end
|
227
|
+
|
228
|
+
entry.css("link").each do |link_tag|
|
229
|
+
videos << YouTubeIt::Parser::VideoFeedParser.new(link_tag).parse if link_tag.at("entry")
|
220
230
|
end
|
221
|
-
|
231
|
+
|
222
232
|
if videos.size <= 0
|
223
233
|
videos = nil
|
224
234
|
end
|
225
|
-
|
235
|
+
|
226
236
|
return videos
|
227
237
|
end
|
228
238
|
end
|
229
|
-
|
239
|
+
|
230
240
|
# Returns an array of the user's contacts
|
231
241
|
class ContactsParser < FeedParser
|
232
242
|
def parse_content(content)
|
233
|
-
doc =
|
234
|
-
feed = doc.
|
235
|
-
|
243
|
+
doc = Nokogiri::XML(content.body)
|
244
|
+
feed = doc.at("feed")
|
245
|
+
|
236
246
|
contacts = []
|
237
|
-
feed.
|
247
|
+
feed.css("entry").each do |entry|
|
238
248
|
temp_contact = YouTubeIt::Model::Contact.new(
|
239
|
-
:title => entry.
|
240
|
-
:username => entry.
|
241
|
-
:status => entry.
|
249
|
+
:title => entry.at("title") ? entry.at("title").text : nil,
|
250
|
+
:username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil,
|
251
|
+
:status => entry.at_xpath("yt:status") ? entry.at_xpath("yt:status").text : nil
|
242
252
|
)
|
243
|
-
|
253
|
+
|
244
254
|
contacts << temp_contact
|
245
255
|
end
|
246
|
-
|
256
|
+
|
247
257
|
return contacts
|
248
258
|
end
|
249
259
|
end
|
250
|
-
|
260
|
+
|
251
261
|
# Returns an array of the user's messages
|
252
262
|
class MessagesParser < FeedParser
|
253
263
|
def parse_content(content)
|
254
|
-
doc =
|
255
|
-
|
256
|
-
|
257
|
-
puts doc.inspect
|
258
|
-
feed = doc.elements["feed"]
|
259
|
-
|
264
|
+
doc = Nokogiri::XML(content.body)
|
265
|
+
feed = doc.at("feed")
|
266
|
+
|
260
267
|
messages = []
|
261
|
-
feed.
|
262
|
-
author = entry.
|
268
|
+
feed.css("entry").each do |entry|
|
269
|
+
author = entry.at("author")
|
263
270
|
temp_message = YouTubeIt::Model::Message.new(
|
264
|
-
:id => entry.
|
265
|
-
:title => entry.
|
266
|
-
:name => author && author.
|
267
|
-
:summary => entry.
|
268
|
-
:published => entry.
|
271
|
+
:id => entry.at("id") ? entry.at("id").text.gsub(/.+:inbox:/, "") : nil,
|
272
|
+
:title => entry.at("title") ? entry.at("title").text : nil,
|
273
|
+
:name => author && author.at("name") ? author.at("name").text : nil,
|
274
|
+
:summary => entry.at("summary") ? entry.at("summary").text : nil,
|
275
|
+
:published => entry.at("published") ? entry.at("published").text : nil
|
269
276
|
)
|
270
|
-
|
277
|
+
|
271
278
|
messages << temp_message
|
272
279
|
end
|
273
|
-
|
280
|
+
|
274
281
|
return messages
|
275
282
|
end
|
276
283
|
end
|
277
284
|
|
278
285
|
class ProfileFeedParser < FeedParser #:nodoc:
|
279
286
|
def parse_content(content)
|
280
|
-
xml =
|
281
|
-
entry = xml.
|
287
|
+
xml = Nokogiri::XML(content.body)
|
288
|
+
entry = xml.at("entry") || xml.at("feed")
|
289
|
+
parse_entry(entry)
|
290
|
+
end
|
291
|
+
def parse_entry(entry)
|
282
292
|
YouTubeIt::Model::User.new(
|
283
|
-
:age => entry.
|
284
|
-
:username => entry.
|
285
|
-
:
|
286
|
-
:
|
287
|
-
:
|
288
|
-
:
|
289
|
-
:
|
290
|
-
:
|
291
|
-
:
|
292
|
-
:
|
293
|
-
:
|
294
|
-
:
|
295
|
-
:
|
296
|
-
:
|
297
|
-
:
|
298
|
-
:
|
299
|
-
:
|
300
|
-
:
|
301
|
-
:
|
293
|
+
:age => entry.at_xpath("yt:age") ? entry.at_xpath("yt:age").text : nil,
|
294
|
+
:username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil,
|
295
|
+
:username_display => (entry.at_xpath("yt:username")['display'] rescue nil),
|
296
|
+
:user_id => (entry.at_xpath("xmlns:author/yt:userId").text rescue nil),
|
297
|
+
:last_name => (entry.at_xpath("yt:lastName").text rescue nil),
|
298
|
+
:first_name => (entry.at_xpath("yt:firstName").text rescue nil),
|
299
|
+
:company => entry.at_xpath("yt:company") ? entry.at_xpath("yt:company").text : nil,
|
300
|
+
:gender => entry.at_xpath("yt:gender") ? entry.at_xpath("yt:gender").text : nil,
|
301
|
+
:hobbies => entry.at_xpath("yt:hobbies") ? entry.at_xpath("yt:hobbies").text : nil,
|
302
|
+
:hometown => entry.at_xpath("yt:hometown") ? entry.at_xpath("yt:hometown").text : nil,
|
303
|
+
:location => entry.at_xpath("yt:location") ? entry.at_xpath("yt:location").text : nil,
|
304
|
+
:last_login => entry.at_xpath("yt:statistics")["lastWebAccess"],
|
305
|
+
:join_date => entry.at("published") ? entry.at("published").text : nil,
|
306
|
+
:movies => entry.at_xpath("yt:movies") ? entry.at_xpath("yt:movies").text : nil,
|
307
|
+
:music => entry.at_xpath("yt:music") ? entry.at_xpath("yt:music").text : nil,
|
308
|
+
:occupation => entry.at_xpath("yt:occupation") ? entry.at_xpath("yt:occupation").text : nil,
|
309
|
+
:relationship => entry.at_xpath("yt:relationship") ? entry.at_xpath("yt:relationship").text : nil,
|
310
|
+
:school => entry.at_xpath("yt:school") ? entry.at_xpath("yt:school").text : nil,
|
311
|
+
:avatar => entry.at_xpath("media:thumbnail") ? entry.at_xpath("media:thumbnail")["url"] : nil,
|
312
|
+
:upload_count => (entry.at_xpath('gd:feedLink[@rel="http://gdata.youtube.com/schemas/2007#user.uploads"]')['countHint'].to_i rescue nil),
|
313
|
+
:max_upload_duration => (entry.at_xpath("yt:maxUploadDuration")['seconds'].to_i rescue nil),
|
314
|
+
:subscribers => entry.at_xpath("yt:statistics")["subscriberCount"],
|
315
|
+
:videos_watched => entry.at_xpath("yt:statistics")["videoWatchCount"],
|
316
|
+
:view_count => entry.at_xpath("yt:statistics")["viewCount"],
|
317
|
+
:upload_views => entry.at_xpath("yt:statistics")["totalUploadViews"],
|
318
|
+
:insight_uri => (entry.at_xpath('xmlns:link[@rel="http://gdata.youtube.com/schemas/2007#insight.views"]')['href'] rescue nil)
|
302
319
|
)
|
303
320
|
end
|
304
321
|
end
|
305
|
-
|
322
|
+
|
323
|
+
class BatchProfileFeedParser < ProfileFeedParser
|
324
|
+
def parse_content(content)
|
325
|
+
Nokogiri::XML(content.body).xpath("//xmlns:entry").map do |entry|
|
326
|
+
entry.namespaces.each {|name, url| entry.document.root.add_namespace name, url }
|
327
|
+
username = entry.at_xpath('batch:id', entry.namespaces).text
|
328
|
+
result = catch(:result) do
|
329
|
+
case entry.at_xpath('batch:status', entry.namespaces)['code'].to_i
|
330
|
+
when 200...300 then parse_entry(entry)
|
331
|
+
else nil
|
332
|
+
end
|
333
|
+
end
|
334
|
+
{ username => result }
|
335
|
+
end.reduce({},:merge)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
306
339
|
class SubscriptionFeedParser < FeedParser #:nodoc:
|
307
340
|
|
308
341
|
def parse_content(content)
|
309
|
-
doc =
|
310
|
-
feed = doc.
|
311
|
-
|
342
|
+
doc = Nokogiri::XML(content.body)
|
343
|
+
feed = doc.at("feed")
|
344
|
+
|
312
345
|
subscriptions = []
|
313
|
-
feed.
|
346
|
+
feed.css("entry").each do |entry|
|
314
347
|
subscriptions << parse_entry(entry)
|
315
348
|
end
|
316
349
|
return subscriptions
|
317
350
|
end
|
318
|
-
|
351
|
+
|
319
352
|
protected
|
320
|
-
|
353
|
+
|
321
354
|
def parse_entry(entry)
|
322
355
|
YouTubeIt::Model::Subscription.new(
|
323
|
-
:title => entry.
|
324
|
-
:id => entry.
|
325
|
-
:published => entry.
|
356
|
+
:title => entry.at("title").text,
|
357
|
+
:id => entry.at("id").text[/subscription([^<]+)/, 1].sub(':',''),
|
358
|
+
:published => entry.at("published") ? entry.at("published").text : nil
|
326
359
|
)
|
327
360
|
end
|
328
361
|
end
|
329
|
-
|
362
|
+
|
330
363
|
|
331
364
|
class VideoFeedParser < FeedParser #:nodoc:
|
332
365
|
|
333
366
|
def parse_content(content)
|
334
|
-
doc = (content.is_a?(
|
335
|
-
|
336
|
-
entry = doc.
|
367
|
+
doc = (content.is_a?(Nokogiri::XML::Document)) ? content : Nokogiri::XML(content)
|
368
|
+
|
369
|
+
entry = doc.at "entry"
|
337
370
|
parse_entry(entry)
|
338
371
|
end
|
339
372
|
|
340
373
|
protected
|
341
374
|
def parse_entry(entry)
|
342
|
-
video_id = entry.
|
343
|
-
published_at
|
344
|
-
|
375
|
+
video_id = entry.at("id").text
|
376
|
+
published_at = entry.at("published") ? Time.parse(entry.at("published").text) : nil
|
377
|
+
uploaded_at = entry.at_xpath("media:group/yt:uploaded") ? Time.parse(entry.at_xpath("media:group/yt:uploaded").text) : nil
|
378
|
+
updated_at = entry.at("updated") ? Time.parse(entry.at("updated").text) : nil
|
345
379
|
|
346
380
|
# parse the category and keyword lists
|
347
381
|
categories = []
|
348
382
|
keywords = []
|
349
|
-
entry.
|
383
|
+
entry.css("category").each do |category|
|
350
384
|
# determine if it's really a category, or just a keyword
|
351
|
-
scheme = category
|
385
|
+
scheme = category["scheme"]
|
352
386
|
if (scheme =~ /\/categories\.cat$/)
|
353
387
|
# it's a category
|
354
388
|
categories << YouTubeIt::Model::Category.new(
|
355
|
-
:term => category
|
356
|
-
:label => category
|
389
|
+
:term => category["term"],
|
390
|
+
:label => category["label"])
|
357
391
|
|
358
392
|
elsif (scheme =~ /\/keywords\.cat$/)
|
359
393
|
# it's a keyword
|
360
|
-
keywords << category
|
394
|
+
keywords << category["term"]
|
361
395
|
end
|
362
396
|
end
|
363
397
|
|
364
|
-
title = entry.
|
365
|
-
html_content = entry.
|
398
|
+
title = entry.at("title").text
|
399
|
+
html_content = nil #entry.at("content") ? entry.at("content").text : nil
|
366
400
|
|
367
401
|
# parse the author
|
368
|
-
author_element = entry.
|
402
|
+
author_element = entry.at("author")
|
369
403
|
author = nil
|
370
404
|
if author_element
|
371
405
|
author = YouTubeIt::Model::Author.new(
|
372
|
-
:name => author_element.
|
373
|
-
:uri => author_element.
|
406
|
+
:name => author_element.at("name").text,
|
407
|
+
:uri => author_element.at("uri").text)
|
374
408
|
end
|
375
|
-
media_group = entry.
|
409
|
+
media_group = entry.at_xpath('media:group')
|
376
410
|
|
377
411
|
ytid = nil
|
378
|
-
unless media_group.
|
379
|
-
ytid = media_group.
|
380
|
-
end
|
412
|
+
unless media_group.at_xpath("yt:videoid").nil?
|
413
|
+
ytid = media_group.at_xpath("yt:videoid").text
|
414
|
+
end
|
381
415
|
|
382
416
|
# if content is not available on certain region, there is no media:description, media:player or yt:duration
|
383
417
|
description = ""
|
384
|
-
unless media_group.
|
385
|
-
description = media_group.
|
418
|
+
unless media_group.at_xpath("media:description").nil?
|
419
|
+
description = media_group.at_xpath("media:description").text
|
386
420
|
end
|
387
421
|
|
388
422
|
# if content is not available on certain region, there is no media:description, media:player or yt:duration
|
389
423
|
duration = 0
|
390
|
-
unless media_group.
|
391
|
-
duration = media_group.
|
424
|
+
unless media_group.at_xpath("yt:duration").nil?
|
425
|
+
duration = media_group.at_xpath("yt:duration")["seconds"].to_i
|
392
426
|
end
|
393
427
|
|
394
428
|
# if content is not available on certain region, there is no media:description, media:player or yt:duration
|
395
429
|
player_url = ""
|
396
|
-
unless media_group.
|
397
|
-
player_url = media_group.
|
430
|
+
unless media_group.at_xpath("media:player").nil?
|
431
|
+
player_url = media_group.at_xpath("media:player")["url"]
|
398
432
|
end
|
399
433
|
|
400
|
-
unless media_group.
|
401
|
-
widescreen = media_group.
|
434
|
+
unless media_group.at_xpath("yt:aspectRatio").nil?
|
435
|
+
widescreen = media_group.at_xpath("yt:aspectRatio").text == 'widescreen' ? true : false
|
402
436
|
end
|
403
437
|
|
404
438
|
media_content = []
|
405
|
-
media_group.
|
439
|
+
media_group.xpath("media:content").each do |mce|
|
406
440
|
media_content << parse_media_content(mce)
|
407
441
|
end
|
408
442
|
|
409
443
|
# parse thumbnails
|
410
444
|
thumbnails = []
|
411
|
-
media_group.
|
445
|
+
media_group.xpath("media:thumbnail").each do |thumb_element|
|
412
446
|
# TODO: convert time HH:MM:ss string to seconds?
|
413
447
|
thumbnails << YouTubeIt::Model::Thumbnail.new(
|
414
|
-
:url => thumb_element
|
415
|
-
:height => thumb_element
|
416
|
-
:width => thumb_element
|
417
|
-
:time => thumb_element
|
448
|
+
:url => thumb_element["url"],
|
449
|
+
:height => thumb_element["height"].to_i,
|
450
|
+
:width => thumb_element["width"].to_i,
|
451
|
+
:time => thumb_element["time"])
|
418
452
|
end
|
419
453
|
|
420
|
-
rating_element = entry.
|
421
|
-
extended_rating_element = entry.
|
454
|
+
rating_element = entry.at_xpath("gd:rating")
|
455
|
+
extended_rating_element = entry.at_xpath("yt:rating")
|
422
456
|
|
423
457
|
rating = nil
|
424
458
|
if rating_element
|
425
459
|
rating_values = {
|
426
|
-
:min => rating_element
|
427
|
-
:max => rating_element
|
428
|
-
:rater_count => rating_element
|
429
|
-
:average => rating_element
|
460
|
+
:min => rating_element["min"].to_i,
|
461
|
+
:max => rating_element["max"].to_i,
|
462
|
+
:rater_count => rating_element["numRaters"].to_i,
|
463
|
+
:average => rating_element["average"].to_f
|
430
464
|
}
|
431
465
|
|
432
466
|
if extended_rating_element
|
433
|
-
rating_values[:likes] = extended_rating_element
|
434
|
-
rating_values[:dislikes] = extended_rating_element
|
467
|
+
rating_values[:likes] = extended_rating_element["numLikes"].to_i
|
468
|
+
rating_values[:dislikes] = extended_rating_element["numDislikes"].to_i
|
435
469
|
end
|
436
470
|
|
437
471
|
rating = YouTubeIt::Model::Rating.new(rating_values)
|
438
472
|
end
|
439
473
|
|
440
|
-
if (el = entry.
|
441
|
-
view_count, favorite_count = el
|
474
|
+
if (el = entry.at_xpath("yt:statistics"))
|
475
|
+
view_count, favorite_count = el["viewCount"].to_i, el["favoriteCount"].to_i
|
442
476
|
else
|
443
477
|
view_count, favorite_count = 0,0
|
444
478
|
end
|
445
479
|
|
446
|
-
|
447
|
-
|
480
|
+
comment_feed = entry.at_xpath('gd:comments/gd:feedLink[@rel="http://gdata.youtube.com/schemas/2007#comments"]')
|
481
|
+
comment_count = comment_feed ? comment_feed['countHint'].to_i : 0
|
482
|
+
#puts comment_count.inspect
|
483
|
+
|
484
|
+
access_control = entry.xpath('yt:accessControl').map do |e|
|
485
|
+
{ e['action'] => e['permission'] }
|
486
|
+
end.compact.reduce({},:merge)
|
448
487
|
|
449
|
-
|
450
|
-
|
451
|
-
|
488
|
+
noembed = entry.at_xpath("yt:noembed") ? true : false
|
489
|
+
safe_search = entry.at_xpath("media:rating") ? true : false
|
490
|
+
|
491
|
+
if entry.namespaces['xmlns:georss'] and where = entry.at_xpath("georss:where")
|
492
|
+
position = where.at_xpath("gml:Point").at_xpath("gml:pos").text
|
493
|
+
latitude, longitude = position.split.map &:to_f
|
452
494
|
end
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
495
|
+
|
496
|
+
if entry.namespaces['xmlns:app']
|
497
|
+
control = entry.at_xpath("app:control")
|
498
|
+
state = { :name => "published" }
|
499
|
+
if control && control.at_xpath("yt:state")
|
500
|
+
state = {
|
501
|
+
:name => control.at_xpath("yt:state")["name"],
|
502
|
+
:reason_code => control.at_xpath("yt:state")["reasonCode"],
|
503
|
+
:help_url => control.at_xpath("yt:state")["helpUrl"],
|
504
|
+
:copy => control.at_xpath("yt:state").text
|
505
|
+
}
|
506
|
+
end
|
464
507
|
end
|
465
508
|
|
509
|
+
insight_uri = (entry.at_xpath('xmlns:link[@rel="http://gdata.youtube.com/schemas/2007#insight.views"]')['href'] rescue nil)
|
510
|
+
|
511
|
+
perm_private = media_group.at_xpath("yt:private") ? true : false
|
512
|
+
|
466
513
|
YouTubeIt::Model::Video.new(
|
467
514
|
:video_id => video_id,
|
468
515
|
:published_at => published_at,
|
469
516
|
:updated_at => updated_at,
|
517
|
+
:uploaded_at => uploaded_at,
|
470
518
|
:categories => categories,
|
471
519
|
:keywords => keywords,
|
472
520
|
:title => title,
|
@@ -480,24 +528,27 @@ class YouTubeIt
|
|
480
528
|
:rating => rating,
|
481
529
|
:view_count => view_count,
|
482
530
|
:favorite_count => favorite_count,
|
531
|
+
:comment_count => comment_count,
|
532
|
+
:access_control => access_control,
|
483
533
|
:widescreen => widescreen,
|
484
534
|
:noembed => noembed,
|
485
|
-
:
|
486
|
-
:where => where,
|
535
|
+
:safe_search => safe_search,
|
487
536
|
:position => position,
|
488
537
|
:latitude => latitude,
|
489
538
|
:longitude => longitude,
|
490
539
|
:state => state,
|
491
|
-
:
|
540
|
+
:insight_uri => insight_uri,
|
541
|
+
:unique_id => ytid,
|
542
|
+
:perm_private => perm_private)
|
492
543
|
end
|
493
544
|
|
494
|
-
def parse_media_content (
|
495
|
-
content_url =
|
496
|
-
format_code =
|
545
|
+
def parse_media_content (elem)
|
546
|
+
content_url = elem["url"]
|
547
|
+
format_code = elem["format"].to_i
|
497
548
|
format = YouTubeIt::Model::Video::Format.by_code(format_code)
|
498
|
-
duration =
|
499
|
-
mime_type =
|
500
|
-
default = (
|
549
|
+
duration = elem["duration"].to_i
|
550
|
+
mime_type = elem["type"]
|
551
|
+
default = (elem["isDefault"] == "true")
|
501
552
|
|
502
553
|
YouTubeIt::Model::Content.new(
|
503
554
|
:url => content_url,
|
@@ -513,16 +564,16 @@ class YouTubeIt
|
|
513
564
|
private
|
514
565
|
def parse_content(content)
|
515
566
|
videos = []
|
516
|
-
doc =
|
517
|
-
feed = doc.
|
567
|
+
doc = Nokogiri::XML(content)
|
568
|
+
feed = doc.at "feed"
|
518
569
|
if feed
|
519
|
-
feed_id = feed.
|
520
|
-
updated_at = Time.parse(feed.
|
521
|
-
total_result_count = feed.
|
522
|
-
offset = feed.
|
523
|
-
max_result_count = feed.
|
570
|
+
feed_id = feed.at("id").text
|
571
|
+
updated_at = Time.parse(feed.at("updated").text)
|
572
|
+
total_result_count = feed.at_xpath("openSearch:totalResults").text.to_i
|
573
|
+
offset = feed.at_xpath("openSearch:startIndex").text.to_i
|
574
|
+
max_result_count = feed.at_xpath("openSearch:itemsPerPage").text.to_i
|
524
575
|
|
525
|
-
feed.
|
576
|
+
feed.css("entry").each do |entry|
|
526
577
|
videos << parse_entry(entry)
|
527
578
|
end
|
528
579
|
end
|