beef-youtube-g 0.4.9.9

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.
@@ -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