beef-youtube-g 0.4.9.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ class YouTubeG
2
+ module Request #:nodoc:
3
+ class UserSearch < BaseSearch #:nodoc:
4
+ attr_reader :max_results # max_results
5
+ attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
6
+ attr_reader :offset # start-index
7
+
8
+ def initialize(params, options={})
9
+ @max_results, @order_by, @offset = nil
10
+ @url = base_url
11
+
12
+ if params == :favorites
13
+ @url << "#{options[:user]}/favorites"
14
+ set_instance_variables(options)
15
+ elsif params[:user]
16
+ @url << "#{params[:user]}/uploads"
17
+ set_instance_variables(params)
18
+ end
19
+
20
+ @url << build_query_params(to_youtube_params)
21
+ end
22
+
23
+ private
24
+
25
+ def base_url #:nodoc:
26
+ super << "users/"
27
+ end
28
+
29
+ def to_youtube_params #:nodoc:
30
+ {
31
+ 'max-results' => @max_results,
32
+ 'orderby' => @order_by,
33
+ 'start-index' => @offset
34
+ }
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,93 @@
1
+ class YouTubeG
2
+ module Request #:nodoc:
3
+ class VideoSearch < BaseSearch #:nodoc:
4
+ # From here: http://code.google.com/apis/youtube/reference.html#yt_format
5
+ ONLY_EMBEDDABLE = 5
6
+
7
+ attr_reader :max_results # max_results
8
+ attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
9
+ attr_reader :offset # start-index
10
+ attr_reader :query # vq
11
+ attr_reader :response_format # alt, ([atom], rss, json)
12
+ attr_reader :tags # /-/tag1/tag2
13
+ attr_reader :categories # /-/Category1/Category2
14
+ attr_reader :video_format # format (1=mobile devices)
15
+ attr_reader :racy # racy ([exclude], include)
16
+ attr_reader :author
17
+
18
+ def initialize(params={})
19
+ # Initialize our various member data to avoid warnings and so we'll
20
+ # automatically fall back to the youtube api defaults
21
+ @max_results, @order_by,
22
+ @offset, @query,
23
+ @response_format, @video_format,
24
+ @racy, @author = nil
25
+ @url = base_url
26
+
27
+ # Return a single video (base_url + /T7YazwP8GtY)
28
+ return @url << "/" << params[:video_id] if params[:video_id]
29
+
30
+ @url << "/-/" if (params[:categories] || params[:tags])
31
+ @url << categories_to_params(params.delete(:categories)) if params[:categories]
32
+ @url << tags_to_params(params.delete(:tags)) if params[:tags]
33
+
34
+ set_instance_variables(params)
35
+
36
+ if( params[ :only_embeddable ] )
37
+ @video_format = ONLY_EMBEDDABLE
38
+ end
39
+
40
+ @url << build_query_params(to_youtube_params)
41
+ end
42
+
43
+ private
44
+
45
+ def base_url #:nodoc:
46
+ super << "videos"
47
+ end
48
+
49
+ def to_youtube_params #:nodoc:
50
+ {
51
+ 'max-results' => @max_results,
52
+ 'orderby' => @order_by,
53
+ 'start-index' => @offset,
54
+ 'vq' => @query,
55
+ 'alt' => @response_format,
56
+ 'format' => @video_format,
57
+ 'racy' => @racy,
58
+ 'author' => @author
59
+ }
60
+ end
61
+
62
+ # Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
63
+ # Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
64
+ # or like: categories => [:news, :sports]
65
+ def categories_to_params(categories) #:nodoc:
66
+ if categories.respond_to?(:keys) and categories.respond_to?(:[])
67
+ s = ""
68
+ s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
69
+ s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
70
+ s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
71
+ s
72
+ else
73
+ categories.map { |c| c.to_s.capitalize }.join("/") << '/'
74
+ end
75
+ end
76
+
77
+ # Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
78
+ # or tags => [:football, :soccer]
79
+ def tags_to_params(tags) #:nodoc:
80
+ if tags.respond_to?(:keys) and tags.respond_to?(:[])
81
+ s = ""
82
+ s << tags[:either].map { |t| CGI.escape(t.to_s) }.join("%7C") << '/' if tags[:either]
83
+ s << tags[:include].map { |t| CGI.escape(t.to_s) }.join("/") << '/' if tags[:include]
84
+ s << ("-" << tags[:exclude].map { |t| CGI.escape(t.to_s) }.join("/-")) << '/' if tags[:exclude]
85
+ s
86
+ else
87
+ tags.map { |t| CGI.escape(t.to_s) }.join("/") << '/'
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,130 @@
1
+ class YouTubeG
2
+
3
+ module Upload
4
+ class UploadError < Exception; end
5
+ class AuthenticationError < Exception; end
6
+
7
+ # require 'youtube_g'
8
+ #
9
+ # uploader = YouTubeG::Upload::VideoUpload.new("user", "pass", "dev-key")
10
+ # uploader.upload File.open("test.m4v"), :title => 'test',
11
+ # :description => 'cool vid d00d',
12
+ # :category => 'People',
13
+ # :keywords => %w[cool blah test]
14
+
15
+ class VideoUpload
16
+
17
+ def initialize user, pass, dev_key, client_id = 'youtube_g'
18
+ @user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
19
+ end
20
+
21
+ #
22
+ # Upload "data" to youtube, where data is either an IO object or
23
+ # raw file data.
24
+ # The hash keys for opts (which specify video info) are as follows:
25
+ # :mime_type
26
+ # :filename
27
+ # :title
28
+ # :description
29
+ # :category
30
+ # :keywords
31
+ # :private
32
+ # Specifying :private will make the video private, otherwise it will be public.
33
+ #
34
+ # When one of the fields is invalid according to YouTube,
35
+ # an UploadError will be returned. Its message contains a list of newline separated
36
+ # errors, containing the key and its error code.
37
+ #
38
+ # When the authentication credentials are incorrect, an AuthenticationError will be raised.
39
+ def upload data, opts = {}
40
+ data = data.respond_to?(:read) ? data.read : data
41
+ @opts = { :mime_type => 'video/mp4',
42
+ :filename => Digest::MD5.hexdigest(data),
43
+ :title => '',
44
+ :description => '',
45
+ :category => '',
46
+ :keywords => [] }.merge(opts)
47
+
48
+ uploadBody = generate_upload_body(boundary, video_xml, data)
49
+
50
+ uploadHeader = {
51
+ "Authorization" => "GoogleLogin auth=#{auth_token}",
52
+ "X-GData-Client" => "#{@client_id}",
53
+ "X-GData-Key" => "key=#{@dev_key}",
54
+ "Slug" => "#{@opts[:filename]}",
55
+ "Content-Type" => "multipart/related; boundary=#{boundary}",
56
+ "Content-Length" => "#{uploadBody.length}"
57
+ }
58
+
59
+ Net::HTTP.start(base_url) do |upload|
60
+ response = upload.post('/feeds/api/users/' << @user << '/uploads', uploadBody, uploadHeader)
61
+ if response.code.to_i == 403
62
+ raise AuthenticationError, response.body[/<TITLE>(.+)<\/TITLE>/, 1]
63
+ elsif response.code.to_i != 201
64
+ upload_error = ''
65
+ xml = REXML::Document.new(response.body)
66
+ errors = xml.elements["//errors"]
67
+ errors.each do |error|
68
+ location = error.elements["location"].text[/media:group\/media:(.*)\/text\(\)/,1]
69
+ code = error.elements["code"].text
70
+ upload_error << sprintf("%s: %s\r\n", location, code)
71
+ end
72
+ raise UploadError, upload_error
73
+ end
74
+ xml = REXML::Document.new(response.body)
75
+ return xml.elements["//id"].text[/videos\/(.+)/, 1]
76
+ end
77
+
78
+ end
79
+
80
+ private
81
+
82
+ def base_url #:nodoc:
83
+ "uploads.gdata.youtube.com"
84
+ end
85
+
86
+ def boundary #:nodoc:
87
+ "An43094fu"
88
+ end
89
+
90
+ def auth_token #:nodoc:
91
+ unless @auth_token
92
+ http = Net::HTTP.new("www.google.com", 443)
93
+ http.use_ssl = true
94
+ body = "Email=#{CGI::escape @user}&Passwd=#{CGI::escape @pass}&service=youtube&source=#{CGI::escape @client_id}"
95
+ response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
96
+ raise UploadError, response.body[/Error=(.+)/,1] if response.code.to_i != 200
97
+ @auth_token = response.body[/Auth=(.+)/, 1]
98
+
99
+ end
100
+ @auth_token
101
+ end
102
+
103
+ def video_xml #:nodoc:
104
+ video_xml = ''
105
+ video_xml << '<?xml version="1.0"?>'
106
+ video_xml << '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">'
107
+ video_xml << '<media:group>'
108
+ video_xml << '<media:title type="plain">%s</media:title>' % @opts[:title]
109
+ video_xml << '<media:description type="plain">%s</media:description>' % @opts[:description]
110
+ video_xml << '<media:keywords>%s</media:keywords>' % @opts[:keywords].join(",")
111
+ video_xml << '<media:category scheme="http://gdata.youtube.com/schemas/2007/categories.cat">%s</media:category>' % @opts[:category]
112
+ video_xml << '<yt:private/>' if @opts[:private]
113
+ video_xml << '</media:group>'
114
+ video_xml << '</entry>'
115
+ end
116
+
117
+ def generate_upload_body(boundary, video_xml, data) #:nodoc:
118
+ uploadBody = ""
119
+ uploadBody << "--#{boundary}\r\n"
120
+ uploadBody << "Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n"
121
+ uploadBody << video_xml
122
+ uploadBody << "\r\n--#{boundary}\r\n"
123
+ uploadBody << "Content-Type: #{@opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n"
124
+ uploadBody << data
125
+ uploadBody << "\r\n--#{boundary}--\r\n"
126
+ end
127
+
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,41 @@
1
+ class YouTubeG
2
+ module Response
3
+ class VideoSearch < YouTubeG::Record
4
+ # *String*:: Unique feed identifying url.
5
+ attr_reader :feed_id
6
+
7
+ # *Fixnum*:: Number of results per page.
8
+ attr_reader :max_result_count
9
+
10
+ # *Fixnum*:: 1-based offset index into the full result set.
11
+ attr_reader :offset
12
+
13
+ # *Fixnum*:: Total number of results available for the original request.
14
+ attr_reader :total_result_count
15
+
16
+ # *Time*:: Date and time at which the feed was last updated
17
+ attr_reader :updated_at
18
+
19
+ # *Array*:: Array of YouTubeG::Model::Video records
20
+ attr_reader :videos
21
+
22
+ def current_page
23
+ ((offset - 1) / max_result_count) + 1
24
+ end
25
+
26
+ # current_page + 1 or nil if there is no next page
27
+ def next_page
28
+ current_page < total_pages ? (current_page + 1) : nil
29
+ end
30
+
31
+ # current_page - 1 or nil if there is no previous page
32
+ def previous_page
33
+ current_page > 1 ? (current_page - 1) : nil
34
+ end
35
+
36
+ def total_pages
37
+ (total_result_count / max_result_count.to_f).ceil
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,262 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'pp'
4
+
5
+ require 'youtube_g'
6
+
7
+ class TestClient < Test::Unit::TestCase
8
+ def setup
9
+ @client = YouTubeG::Client.new
10
+ end
11
+
12
+ def test_should_respond_to_a_basic_query
13
+ response = @client.videos_by(:query => "penguin")
14
+
15
+ assert_equal "http://gdata.youtube.com/feeds/api/videos", response.feed_id
16
+ assert_equal 25, response.max_result_count
17
+ assert_equal 25, response.videos.length
18
+ assert_equal 1, response.offset
19
+ assert(response.total_result_count > 100)
20
+ assert_instance_of Time, response.updated_at
21
+
22
+ response.videos.each { |v| assert_valid_video v }
23
+ end
24
+
25
+ def test_should_respond_to_a_basic_query_with_offset_and_max_results
26
+ response = @client.videos_by(:query => "penguin", :offset => 15, :max_results => 30)
27
+
28
+ assert_equal "http://gdata.youtube.com/feeds/api/videos", response.feed_id
29
+ assert_equal 30, response.max_result_count
30
+ assert_equal 30, response.videos.length
31
+ assert_equal 15, response.offset
32
+ assert(response.total_result_count > 100)
33
+ assert_instance_of Time, response.updated_at
34
+
35
+ response.videos.each { |v| assert_valid_video v }
36
+ end
37
+
38
+ def test_should_respond_to_a_basic_query_with_paging
39
+ response = @client.videos_by(:query => "penguin")
40
+ assert_equal "http://gdata.youtube.com/feeds/api/videos", response.feed_id
41
+ assert_equal 25, response.max_result_count
42
+ assert_equal 1, response.offset
43
+
44
+ response = @client.videos_by(:query => "penguin", :page => 2)
45
+ assert_equal "http://gdata.youtube.com/feeds/api/videos", response.feed_id
46
+ assert_equal 25, response.max_result_count
47
+ assert_equal 26, response.offset
48
+
49
+ response2 = @client.videos_by(:query => "penguin", :page => 3)
50
+ assert_equal "http://gdata.youtube.com/feeds/api/videos", response2.feed_id
51
+ assert_equal 25, response2.max_result_count
52
+ assert_equal 51, response2.offset
53
+ end
54
+
55
+ def test_should_get_videos_for_multiword_metasearch_query
56
+ response = @client.videos_by(:query => 'christina ricci')
57
+
58
+ assert_equal "http://gdata.youtube.com/feeds/api/videos", response.feed_id
59
+ assert_equal 25, response.max_result_count
60
+ assert_equal 25, response.videos.length
61
+ assert_equal 1, response.offset
62
+ assert(response.total_result_count > 100)
63
+ assert_instance_of Time, response.updated_at
64
+
65
+ response.videos.each { |v| assert_valid_video v }
66
+ end
67
+
68
+ def test_should_handle_video_not_yet_viewed
69
+ response = @client.videos_by(:query => "YnqHZDh_t2Q")
70
+
71
+ assert_equal 1, response.videos.length
72
+ response.videos.each { |v| assert_valid_video v }
73
+ end
74
+
75
+ # TODO: this doesn't work because the returned feed is in an unknown format
76
+ # def test_should_get_video_for_search_by_video_id
77
+ # response = @client.videos_by(:video_id => "T7YazwP8GtY")
78
+ # response.videos.each { |v| assert_valid_video v }
79
+ # end
80
+
81
+ def test_should_get_videos_for_one_tag
82
+ response = @client.videos_by(:tags => ['panther'])
83
+ response.videos.each { |v| assert_valid_video v }
84
+ end
85
+
86
+ def test_should_get_videos_for_multiple_tags
87
+ response = @client.videos_by(:tags => ['tiger', 'leopard'])
88
+ response.videos.each { |v| assert_valid_video v }
89
+ end
90
+
91
+ def test_should_get_videos_for_one_category
92
+ response = @client.videos_by(:categories => [:news])
93
+ response.videos.each { |v| assert_valid_video v }
94
+ end
95
+
96
+ def test_should_get_videos_for_multiple_categories
97
+ response = @client.videos_by(:categories => [:news, :sports])
98
+ response.videos.each { |v| assert_valid_video v }
99
+ end
100
+
101
+ # TODO: Need to do more specific checking in these tests
102
+ # Currently, if a URL is valid, and videos are found, the test passes regardless of search criteria
103
+ def test_should_get_videos_for_categories_and_tags
104
+ response = @client.videos_by(:categories => [:news, :sports], :tags => ['soccer', 'football'])
105
+ response.videos.each { |v| assert_valid_video v }
106
+ end
107
+
108
+ def test_should_get_most_viewed_videos
109
+ response = @client.videos_by(:most_viewed)
110
+ response.videos.each { |v| assert_valid_video v }
111
+ end
112
+
113
+ def test_should_get_top_rated_videos_for_today
114
+ response = @client.videos_by(:top_rated, :time => :today)
115
+ response.videos.each { |v| assert_valid_video v }
116
+ end
117
+
118
+ def test_should_get_videos_for_categories_and_tags_with_category_boolean_operators
119
+ response = @client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] },
120
+ :tags => { :include => ['football'], :exclude => ['soccer'] })
121
+ response.videos.each { |v| assert_valid_video v }
122
+ end
123
+
124
+ def test_should_get_videos_for_categories_and_tags_with_tag_boolean_operators
125
+ response = @client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] },
126
+ :tags => { :either => ['football', 'soccer', 'polo'] })
127
+ response.videos.each { |v| assert_valid_video v }
128
+ end
129
+
130
+ def test_should_get_videos_by_user
131
+ response = @client.videos_by(:user => 'liz')
132
+ response.videos.each { |v| assert_valid_video v }
133
+ end
134
+
135
+ def test_should_get_videos_by_user_with_pagination_and_ordering
136
+ response = @client.videos_by(:user => 'liz', :page => 2, :per_page => '2', :order_by => 'published')
137
+ response.videos.each { |v| assert_valid_video v }
138
+ assert_equal 3, response.offset
139
+ assert_equal 2, response.max_result_count
140
+ end
141
+
142
+ # HTTP 403 Error
143
+ # def test_should_get_favorite_videos_by_user
144
+ # response = @client.videos_by(:favorites, :user => 'liz')
145
+ # response.videos.each { |v| assert_valid_video v }
146
+ # end
147
+
148
+ def test_should_get_videos_for_query_search_with_categories_excluded
149
+ video = @client.video_by("EkF4JD2rO3Q")
150
+ assert_equal "<object width=\"425\" height=\"350\">\n <param name=\"movie\" value=\"http://www.youtube.com/v/EkF4JD2rO3Q\"></param>\n <param name=\"wmode\" value=\"transparent\"></param>\n <embed src=\"http://www.youtube.com/v/EkF4JD2rO3Q\" type=\"application/x-shockwave-flash\" \n wmode=\"transparent\" width=\"425\" height=\"350\"></embed>\n</object>\n", video.embed_html
151
+ assert_valid_video video
152
+ end
153
+
154
+ def test_should_disable_debug_if_debug_is_set_to_false
155
+ @client = YouTubeG::Client.new
156
+ assert_nil @client.logger
157
+ end
158
+
159
+ def test_should_enable_logger_if_debug_is_true
160
+ @client = YouTubeG::Client.new(true)
161
+ assert_not_nil @client.logger
162
+ end
163
+
164
+ def test_should_determine_if_nonembeddable_video_is_embeddable
165
+ response = @client.videos_by(:query => "avril lavigne girlfriend")
166
+
167
+ video = response.videos.first
168
+ assert !video.embeddable?
169
+ end
170
+
171
+ def test_should_determine_if_embeddable_video_is_embeddable
172
+ response = @client.videos_by(:query => "strongbad")
173
+
174
+ video = response.videos.first
175
+ assert video.embeddable?
176
+ end
177
+
178
+ def test_should_retrieve_video_by_id
179
+ video = @client.video_by("http://gdata.youtube.com/feeds/videos/EkF4JD2rO3Q")
180
+ assert_valid_video video
181
+
182
+ video = @client.video_by("EkF4JD2rO3Q")
183
+ assert_valid_video video
184
+ end
185
+
186
+ private
187
+
188
+ def assert_valid_video (video)
189
+ # pp video
190
+
191
+ # check general attributes
192
+ assert_instance_of YouTubeG::Model::Video, video
193
+ assert_instance_of Fixnum, video.duration
194
+ assert(video.duration > 0)
195
+ #assert_match(/^<div style=.*?<\/div>/m, video.html_content)
196
+ assert_instance_of String, video.html_content
197
+
198
+ # validate media content records
199
+ video.media_content.each do |media_content|
200
+ # http://www.youtube.com/v/IHVaXG1thXM
201
+ assert_valid_url media_content.url
202
+ assert(media_content.duration > 0)
203
+ assert_instance_of YouTubeG::Model::Video::Format, media_content.format
204
+ assert_instance_of String, media_content.mime_type
205
+ assert_match(/^[^\/]+\/[^\/]+$/, media_content.mime_type)
206
+ end
207
+
208
+ default_content = video.default_media_content
209
+ if default_content
210
+ assert_instance_of YouTubeG::Model::Content, default_content
211
+ assert default_content.is_default?
212
+ end
213
+
214
+ # validate keywords
215
+ video.keywords.each { |kw| assert_instance_of(String, kw) }
216
+
217
+ # http://www.youtube.com/watch?v=IHVaXG1thXM
218
+ assert_valid_url video.player_url
219
+ assert_instance_of Time, video.published_at
220
+
221
+ # validate optionally-present rating
222
+ if video.rating
223
+ assert_instance_of YouTubeG::Model::Rating, video.rating
224
+ assert_instance_of Float, video.rating.average
225
+ assert_instance_of Fixnum, video.rating.max
226
+ assert_instance_of Fixnum, video.rating.min
227
+ assert_instance_of Fixnum, video.rating.rater_count
228
+ end
229
+
230
+ # validate thumbnails
231
+ assert(video.thumbnails.size > 0)
232
+
233
+ assert_not_nil video.title
234
+ assert_instance_of String, video.title
235
+ assert(video.title.length > 0)
236
+
237
+ assert_instance_of Time, video.updated_at
238
+ # http://gdata.youtube.com/feeds/videos/IHVaXG1thXM
239
+ assert_valid_url video.video_id
240
+ assert_instance_of Fixnum, video.view_count
241
+
242
+ # validate author
243
+ assert_instance_of YouTubeG::Model::Author, video.author
244
+ assert_instance_of String, video.author.name
245
+ assert(video.author.name.length > 0)
246
+ assert_valid_url video.author.uri
247
+
248
+ # validate categories
249
+ video.categories.each do |cat|
250
+ assert_instance_of YouTubeG::Model::Category, cat
251
+ assert_instance_of String, cat.label
252
+ assert_instance_of String, cat.term
253
+ end
254
+ end
255
+
256
+ def assert_valid_url (url)
257
+ URI::parse(url)
258
+ return true
259
+ rescue
260
+ return false
261
+ end
262
+ end