youtube 0.1.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1,2 @@
1
+ Shane Vitarana <shanev@gmail.com>
2
+ Walter Korman <shaper@wgks.org>
data/CHANGELOG ADDED
@@ -0,0 +1,50 @@
1
+ * 2006/11/15
2
+
3
+ - [drummr77] Applied a modified version of the patch sent in by Lucas
4
+ Carlson that uses a more functional style of programming in the API
5
+ calls.
6
+
7
+ * 2006/11/14
8
+
9
+ - [drummr77] Changed video_details to take in a video id instead of
10
+ the whole object.
11
+
12
+ * 2006/11/04
13
+
14
+ - [shaper] Restructured to follow standard RubyGems directory structure.
15
+
16
+ - [shaper] Created a Rakefile.
17
+
18
+ - [shaper] Created initial unit tests.
19
+
20
+ - [shaper] Fixed bug wherein responses with only a single result
21
+ (e.g. from a videos_by_tag call) would fail to successfully parse.
22
+
23
+ - [shaper] Moved all classes within a YouTube module for cleaner namespace
24
+ management and in particular to avoid potential conflicts.
25
+
26
+ - [shaper] Added Video.embed_html method to allow easy retrieval of HTML
27
+ to embed the video in a web page conforming to the HTML specified on
28
+ YouTube video pages, with width/height of video optionally specifiable.
29
+
30
+ - [shaper] Merged dirtywork.rb into the main youtube.rb file and added
31
+ support for optionally specifying the host and api path for future
32
+ flexibility without requiring code modifications should YouTube change
33
+ their API access details.
34
+
35
+ - [shaper] Modified parsing response payload data to translate to the most
36
+ appropriate Ruby objects (e.g. integers via to_i(), boolean strings to
37
+ TrueClass/FalseClass, time strings to Time) wherever applicable.
38
+
39
+ - [shaper] Moved Video.details() into Client.video_details() for
40
+ consistency and to avoid having to store a reference to the client in
41
+ every Video object.
42
+
43
+ - [shaper] Updated existing and added more RDoc documentation.
44
+
45
+ - [shaper] Changed API hostname to fully-qualified www.youtube.com from
46
+ youtube.com.
47
+
48
+ * 2006/09/28
49
+
50
+ - [drummr77] Initial Release.
data/README ADDED
@@ -0,0 +1,56 @@
1
+ = YouTube
2
+
3
+ A pure Ruby object-oriented interface to the YouTube REST API documented
4
+ at http://www.youtube.com/dev.
5
+
6
+ API access requires a developer id. You can obtain one for free at
7
+ http://www.youtube.com/my_profile_dev.
8
+
9
+ The RubyForge project is at http://rubyforge.org/projects/youtube.
10
+
11
+ == About
12
+
13
+ Implements version 2 of YouTube's API.
14
+
15
+ == Installing
16
+
17
+ Install the gem via:
18
+
19
+ % gem install youtube
20
+
21
+ == Usage
22
+
23
+ An example as:
24
+
25
+ require 'rubygems'
26
+ require 'youtube'
27
+
28
+ youtube = YouTube::Client.new 'DEVELOPER_ID'
29
+
30
+ profile = youtube.profile('br0wnpunk')
31
+ puts "age: " + profile.age.to_s
32
+
33
+ favorites = youtube.favorite_videos('br0wnpunk')
34
+ puts "number of favorite videos: " + favorites.size.to_s
35
+
36
+ friends = youtube.friends('paolodona')
37
+ puts "number of friends: " + friends.size.to_s
38
+ puts "friend name: " + friends[0].user
39
+
40
+ videos = youtube.videos_by_tag('iron maiden')
41
+ puts "number of videos by tag iron maiden: " + videos.size.to_s
42
+
43
+ videos = youtube.videos_by_user('whytheluckystiff')
44
+ puts "number of videos by why: " + videos.size.to_s
45
+ puts "title: " + videos[0].title
46
+
47
+ videos = youtube.featured_videos
48
+ puts "number of featured videos: " + videos.size.to_s
49
+ puts "title: " + videos[0].title
50
+ puts "url: " + videos[0].url
51
+ puts "embed url: " + videos[0].embed_url
52
+ puts "embed html: \n" + videos[0].embed_html
53
+
54
+ details = youtube.video_details(videos[0])
55
+ puts "detailed description: " + details.description
56
+ puts "thumbnail url: " + details.thumbnail_url
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = 'youtube'
9
+ s.version = '0.8.0'
10
+ s.author = 'Shane Vitarana'
11
+ s.email = 'shanev@gmail.com'
12
+ s.platform = Gem::Platform::RUBY
13
+ s.summary = 'A Ruby object-oriented interface to the YouTube REST API.'
14
+ s.rubyforge_project = 'youtube'
15
+ s.has_rdoc = true
16
+ s.extra_rdoc_files = [ 'README' ]
17
+ s.rdoc_options << '--main' << 'README'
18
+ s.files = Dir.glob("{examples,lib,test}/**/*") +
19
+ [ 'AUTHORS', 'CHANGELOG', 'README', 'Rakefile', 'TODO' ]
20
+ s.add_dependency("xml-simple", ">= 1.0.9")
21
+ end
22
+
23
+ desc 'Run tests'
24
+ task :default => [ :test ]
25
+
26
+ Rake::TestTask.new('test') do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/test_*.rb'
29
+ t.verbose = true
30
+ end
31
+
32
+ desc 'Generate RDoc'
33
+ Rake::RDocTask.new :rdoc do |rd|
34
+ rd.rdoc_dir = 'doc'
35
+ rd.rdoc_files.add 'lib', 'README'
36
+ rd.main = 'README'
37
+ end
38
+
39
+ desc 'Build Gem'
40
+ Rake::GemPackageTask.new spec do |pkg|
41
+ pkg.need_tar = true
42
+ end
43
+
44
+ desc 'Clean up'
45
+ task :clean => [ :clobber_rdoc, :clobber_package ]
46
+
47
+ desc 'Clean up'
48
+ task :clobber => [ :clean ]
data/TODO ADDED
@@ -0,0 +1,15 @@
1
+ - consider creating YouTubeException to raise on failures rather than just
2
+ a string so that we can pass our request params back up to higher
3
+ levels should they be interested.
4
+
5
+ - look into object types stored for comments and channel list and see if
6
+ we're doing the Right/Best Thing. It looks like we've got hashes with
7
+ one key e.g. "comments" that then map to a list of actual comments.
8
+
9
+ - look at what we store for fields with no actual value; appears we're
10
+ storing empty hashes in at least some cases.
11
+
12
+ - add unit tests for client.videos_by_tag page and per_page params.
13
+
14
+ - add unit tests for specifying alternate api host/path in client
15
+ constructor.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'youtube'
5
+
6
+ youtube = YouTube::Client.new 'DEVELOPER_ID' # Get one here: <http://youtube.com/my_profile_dev>.
7
+
8
+ profile = youtube.profile('br0wnpunk')
9
+ puts "age: " + profile.age.to_s
10
+
11
+ favorites = youtube.favorite_videos('br0wnpunk')
12
+ puts "number of favorite videos: " + favorites.size.to_s
13
+
14
+ friends = youtube.friends('paolodona')
15
+ puts "number of friends: " + friends.size.to_s
16
+ puts "friend name: " + friends.first.user
17
+
18
+ videos = youtube.videos_by_tag('iron maiden')
19
+ puts "number of videos by tag iron maiden: " + videos.size.to_s
20
+
21
+ videos = youtube.videos_by_user('whytheluckystiff')
22
+ puts "number of videos by why: " + videos.size.to_s
23
+ puts "title: " + videos.first.title
24
+
25
+ videos = youtube.featured_videos
26
+ puts "number of featured videos: " + videos.size.to_s
27
+ puts "title: " + videos.first.title
28
+ puts "url: " + videos.first.url
29
+ puts "embed url: " + videos.first.embed_url
30
+ puts "embed html: \n" + videos.first.embed_html
31
+
32
+ details = youtube.video_details(videos.first.id)
33
+ puts "detailed description: " + details.description
34
+ puts "thumbnail url: " + details.thumbnail_url
data/lib/youtube.rb ADDED
@@ -0,0 +1,299 @@
1
+ #--
2
+ # Copyright (c) 2006 Shane Vitarana
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'net/http'
25
+ require 'uri'
26
+ require 'xmlsimple'
27
+
28
+ module YouTube
29
+
30
+ # Main client class managing all interaction with the YouTube server.
31
+ # Server communication is handled via method_missing() emulating an
32
+ # RPC-like call and performing all of the work to send out the HTTP
33
+ # request and retrieve the XML response. Inspired by the Flickr
34
+ # interface by Scott Raymond <http://redgreenblu.com/flickr/>.
35
+ class Client
36
+ # the default hostname at which the YouTube API is hosted
37
+ DEFAULT_HOST = 'http://www.youtube.com'
38
+ # the default api path to the YouTube API
39
+ DEFAULT_API_PATH = '/api2_rest'
40
+
41
+ def initialize(dev_id = nil, host = DEFAULT_HOST, api_path = DEFAULT_API_PATH)
42
+ raise "developer id required" unless dev_id
43
+
44
+ @host = host
45
+ @api_path = api_path
46
+ @dev_id = dev_id
47
+ end
48
+
49
+ # Returns a YouTube::Profile object detailing the profile information
50
+ # regarding the supplied +username+.
51
+ def profile(username)
52
+ response = users_get_profile(:user => username)
53
+ Profile.new response['user_profile']
54
+ end
55
+
56
+ # Returns a list of YouTube::Video objects detailing the favorite
57
+ # videos of the supplied +username+.
58
+ def favorite_videos(username)
59
+ response = users_list_favorite_videos(:user => username)
60
+ response['video_list']['video'].compact.map { |video| Video.new(video) }
61
+ end
62
+
63
+ # Returns a list of YouTube::Friend objects detailing the friends of
64
+ # the supplied +username+.
65
+ def friends(username)
66
+ response = users_list_friends(:user => username)
67
+ response['friend_list']['friend'].compact.map { |friend| Friend.new(friend) }
68
+ end
69
+
70
+ # Returns a list of YouTube::Video objects detailing the videos
71
+ # matching the supplied +tag+.
72
+ #
73
+ # Optional parameters are:
74
+ # +page+ = the "page" of results to retrieve (e.g. 1, 2, 3)
75
+ # +per_page+ = the number of results per page (default: 20, max 100).
76
+ def videos_by_tag(tag, page = 1, per_page = 20)
77
+ response = videos_list_by_tag(:tag => tag, :page => page, :per_page => per_page)
78
+ response['video_list']['video'].compact.map { |video| Video.new(video) }
79
+ end
80
+
81
+ # Returns a list of YouTube::Video objects detailing the videos
82
+ # uploaded by the specified +username+.
83
+ def videos_by_user(username)
84
+ response = videos_list_by_user(:user => username)
85
+ response['video_list']['video'].compact.map { |video| Video.new(video) }
86
+ end
87
+
88
+ # Returns a list of YouTube::Video objects detailing the current
89
+ # global set of featured videos on YouTube.
90
+ def featured_videos
91
+ response = videos_list_featured
92
+ response['video_list']['video'].compact.map { |video| Video.new(video) }
93
+ end
94
+
95
+ # Returns a YouTube::VideoDetails object detailing additional
96
+ # information on the supplied video id, obtained from a
97
+ # YouTube::Video object from a previous client call.
98
+ def video_details(video_id)
99
+ raise ArgumentError.new("invalid video id parameter, must be string") unless video_id.is_a?(String)
100
+ response = videos_get_details(:video_id => video_id)
101
+ VideoDetails.new(response['video_details'])
102
+ end
103
+
104
+ private
105
+ # All API methods are implemented with this method. This method is
106
+ # like a remote method call, it encapsulates the request/response
107
+ # cycle to the remote host. It extracts the remote method API name
108
+ # based on the ruby method name.
109
+ def method_missing(method_id, *params)
110
+ _request(method_id.to_s.sub('_', '.'), *params)
111
+ end
112
+
113
+ def _request(method, *params)
114
+ url = _request_url(method, *params)
115
+ response = XmlSimple.xml_in(_http_get(url),
116
+ { 'ForceArray' => [ 'video', 'friend' ] })
117
+ unless response['status'] == 'ok'
118
+ raise response['error']['description'] + " : url=#{url}"
119
+ end
120
+ response
121
+ end
122
+
123
+ def _request_url(method, *params)
124
+ param_list = String.new
125
+ unless params.empty?
126
+ params.first.each_pair { |k, v| param_list << "&#{k.to_s}=#{URI.encode(v.to_s)}" }
127
+ end
128
+ url = "#{@host}#{@api_path}?method=youtube.#{method}&dev_id=#{@dev_id}#{param_list}"
129
+ end
130
+
131
+ def _http_get(url)
132
+ Net::HTTP.get_response(URI.parse(url)).body.to_s
133
+ end
134
+ end
135
+
136
+ class Friend
137
+ attr_reader :favorite_count
138
+ attr_reader :friend_count
139
+ attr_reader :user
140
+ attr_reader :video_upload_count
141
+
142
+ def initialize(payload)
143
+ @favorite_count = payload['favorite_count'].to_i
144
+ @friend_count = payload['friend_count'].to_i
145
+ @user = payload['user']
146
+ @video_upload_count = payload['video_upload_count'].to_i
147
+ end
148
+ end
149
+
150
+ class Profile
151
+ attr_reader :about_me
152
+ attr_reader :age
153
+ attr_reader :books
154
+ attr_reader :city
155
+ attr_reader :companies
156
+ attr_reader :country
157
+ attr_reader :currently_on
158
+ attr_reader :favorite_video_count
159
+ attr_reader :first_name
160
+ attr_reader :friend_count
161
+ attr_reader :gender
162
+ attr_reader :hobbies
163
+ attr_reader :homepage
164
+ attr_reader :hometown
165
+ attr_reader :last_name
166
+ attr_reader :movies
167
+ attr_reader :occupations
168
+ attr_reader :relationship
169
+ attr_reader :video_upload_count
170
+ attr_reader :video_watch_count
171
+
172
+ def initialize(payload)
173
+ @about_me = payload['about_me']
174
+ @age = payload['age'].to_i
175
+ @books = payload['books']
176
+ @city = payload['city']
177
+ @companies = payload['companies']
178
+ @country = payload['country']
179
+ @currently_on = YouTube._string_to_boolean(payload['currently_on'])
180
+ @favorite_video_count = payload['favorite_video_count'].to_i
181
+ @first_name = payload['first_name']
182
+ @friend_count = payload['friend_count'].to_i
183
+ @gender = payload['gender']
184
+ @hobbies = payload['hobbies']
185
+ @homepage = payload['homepage']
186
+ @hometown = payload['hometown']
187
+ @last_name = payload['last_name']
188
+ @movies = payload['movies']
189
+ @occupations = payload['occupations']
190
+ @relationship = payload['relationship']
191
+ @video_upload_count = payload['video_upload_count'].to_i
192
+ @video_watch_count = payload['video_watch_count'].to_i
193
+ end
194
+ end
195
+
196
+ class Video
197
+ attr_reader :author
198
+ attr_reader :comment_count
199
+ attr_reader :description
200
+ attr_reader :embed_url
201
+ attr_reader :id
202
+ attr_reader :length
203
+ attr_reader :rating_avg
204
+ attr_reader :rating_count
205
+ attr_reader :tags
206
+ attr_reader :thumbnail_url
207
+ attr_reader :title
208
+ attr_reader :upload_time
209
+ attr_reader :url
210
+ attr_reader :view_count
211
+
212
+ def initialize(payload)
213
+ @author = payload['author']
214
+ @comment_count = payload['comment_count'].to_i
215
+ @description = payload['description']
216
+ @id = payload['id']
217
+ @length = payload['length']
218
+ @rating_avg = payload['rating_avg'].to_f
219
+ @rating_count = payload['rating_count'].to_i
220
+ @tags = payload['tags']
221
+ @thumbnail_url = payload['thumbnail_url']
222
+ @title = payload['title']
223
+ @upload_time = YouTube._string_to_time(payload['upload_time'])
224
+ @url = payload['url']
225
+ @view_count = payload['view_count'].to_i
226
+
227
+ # the url provided via the API links to the video page -- for
228
+ # convenience, generate the url used to embed in a page
229
+ @embed_url = @url.delete('?').sub('=', '/')
230
+ end
231
+
232
+ # Returns HTML analogous to that provided by the YouTube web site to
233
+ # allow for easy embedding of this video in a web page. Optional
234
+ # +width+ and +height+ parameters allow specifying the dimensions of
235
+ # the video for display.
236
+ def embed_html(width = 425, height = 350)
237
+ <<edoc
238
+ <object width="#{width}" height="#{height}">
239
+ <param name="movie" value="#{embed_url}"></param>
240
+ <param name="wmode" value="transparent"></param>
241
+ <embed src="#{embed_url}" type="application/x-shockwave-flash"
242
+ wmode="transparent" width="#{width}" height="#{height}"></embed>
243
+ </object>
244
+ edoc
245
+ end
246
+ end
247
+
248
+ class VideoDetails
249
+ attr_reader :author
250
+ attr_reader :channel_list
251
+ attr_reader :comment_list
252
+ attr_reader :description
253
+ attr_reader :length_seconds
254
+ attr_reader :rating_avg
255
+ attr_reader :rating_count
256
+ attr_reader :recoding_location
257
+ attr_reader :recording_country
258
+ attr_reader :recording_date
259
+ attr_reader :tags
260
+ attr_reader :thumbnail_url
261
+ attr_reader :title
262
+ attr_reader :update_time
263
+ attr_reader :upload_time
264
+ attr_reader :view_count
265
+
266
+ def initialize(payload)
267
+ @author = payload['author']
268
+ @channel_list = payload['channel_list']
269
+ @comment_list = payload['comment_list']
270
+ @description = payload['description']
271
+ @length_seconds = payload['length_seconds'].to_i
272
+ @rating_avg = payload['rating_avg'].to_f
273
+ @rating_count = payload['rating_count'].to_i
274
+ @recording_country = payload['recording_country']
275
+ @recording_date = payload['recording_date']
276
+ @recording_location = payload['recording_location']
277
+ @tags = payload['tags']
278
+ @thumbnail_url = payload['thumbnail_url']
279
+ @title = payload['title']
280
+ @update_time = YouTube._string_to_time(payload['update_time'])
281
+ @upload_time = YouTube._string_to_time(payload['upload_time'])
282
+ @view_count = payload['view_count'].to_i
283
+ end
284
+ end
285
+
286
+ private
287
+ # Returns the Ruby boolean object as TrueClass or FalseClass based on
288
+ # the supplied string value. TrueClass is returned if the value is
289
+ # non-nil and "true" (case-insensitive), else FalseClass is returned.
290
+ def self._string_to_boolean(bool_str)
291
+ (bool_str && bool_str.downcase == "true")
292
+ end
293
+
294
+ # Returns a Time object corresponding to the specified time string
295
+ # representing seconds since the epoch, or nil if the string is nil.
296
+ def self._string_to_time(time_str)
297
+ (time_str) ? Time.at(time_str.to_i) : nil
298
+ end
299
+ end
data/test/test_api.rb ADDED
@@ -0,0 +1,158 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'youtube'
4
+
5
+ # This test class assumes an active internet connection
6
+ class TestAPI < Test::Unit::TestCase
7
+ @@DEVELOPER_API_KEY = 'DEVELOPER_ID'
8
+
9
+ def setup
10
+ @client = YouTube::Client.new @@DEVELOPER_API_KEY
11
+ end
12
+
13
+ def test_profile
14
+ profile = @client.profile('nutria42')
15
+
16
+ assert_kind_of YouTube::Profile, profile
17
+ assert_not_nil profile
18
+ assert (profile.age >= 33)
19
+ assert (profile.gender == 'm')
20
+ assert (profile.country == 'US')
21
+ end
22
+
23
+ def test_favorite_videos
24
+ favorites = @client.favorite_videos('br0wnpunk')
25
+
26
+ # make sure we got some favorites
27
+ assert_not_nil favorites
28
+ assert (favorites.length > 0)
29
+
30
+ # pull out one to scrutinize
31
+ sample = favorites.first
32
+ _test_video(sample)
33
+ end
34
+
35
+ def test_friends
36
+ friends = @client.friends('paolodona')
37
+
38
+ # make sure we got some friends
39
+ assert_not_nil friends
40
+ assert (friends.length > 0)
41
+
42
+ # pull one out to scrutinize
43
+ sample = friends.first
44
+ assert_kind_of YouTube::Friend, sample
45
+
46
+ # sanity-check some attributes to make sure we parsed properly
47
+ assert (sample.favorite_count > 0)
48
+ assert (sample.friend_count > 0)
49
+ assert (sample.user && sample.user.length > 0)
50
+ end
51
+
52
+ def test_videos_by_tag
53
+ videos = @client.videos_by_tag('iron maiden')
54
+ _test_video_list(videos)
55
+
56
+ videos = @client.videos_by_tag('caffe trieste')
57
+ _test_video_list(videos)
58
+ end
59
+
60
+ def test_videos_by_user
61
+ videos = @client.videos_by_user('whytheluckystiff')
62
+ _test_video_list(videos)
63
+ end
64
+
65
+ def test_featured_videos
66
+ videos = @client.featured_videos
67
+ _test_video_list(videos)
68
+ end
69
+
70
+ def test_video_details
71
+ videos = @client.featured_videos
72
+ _test_video_list(videos)
73
+
74
+ videos.each do |video|
75
+ details = @client.video_details(video.id)
76
+
77
+ assert_not_nil details
78
+ assert_kind_of YouTube::VideoDetails, details
79
+ assert (details.author && details.author.length > 0)
80
+ assert (details.length_seconds > 0)
81
+ assert (details.title && details.title.length > 0)
82
+ assert (details.description && details.description.length > 0)
83
+ end
84
+
85
+ # make sure parameter validation is operating correctly
86
+ assert_raise ArgumentError do
87
+ @client.video_details(5)
88
+ end
89
+ end
90
+
91
+ def test_embed_html
92
+ videos = @client.videos_by_tag('iron maiden')
93
+ sample_video = videos.first
94
+
95
+ embed_html = sample_video.embed_html
96
+ embed_url = sample_video.embed_url
97
+
98
+ # make sure embed url is present twice in the html
99
+ assert (_match_count(embed_url, embed_html) == 2)
100
+
101
+ # make sure the default width and height are present
102
+ dimension_text = "width=\"425\" height=\"350\""
103
+ assert (_match_count(dimension_text, embed_html) == 2)
104
+
105
+ # try changing the width and height in the embed html
106
+ custom_embed_html = sample_video.embed_html(200, 100)
107
+ dimension_text = "width=\"200\" height=\"100\""
108
+
109
+ # make sure the customized width and height are present
110
+ entries = custom_embed_html.find_all { |t| t == dimension_text }
111
+ assert (_match_count(dimension_text, custom_embed_html) == 2)
112
+ end
113
+
114
+ private
115
+
116
+ # Returns the number of times +substr+ exists within +text+.
117
+ def _match_count (substr, text)
118
+ return 0 if (!text || !substr)
119
+
120
+ count = 0
121
+ offset = 0
122
+ while (result = text.index(substr, offset))
123
+ count += 1
124
+ offset = result + 1
125
+ end
126
+ count
127
+ end
128
+
129
+ def _assert_youtube_url (url)
130
+ (url =~ /^http:\/\/www.youtube.com\//)
131
+ end
132
+
133
+ def _test_video_list (videos)
134
+ # make sure we got some videos
135
+ assert_not_nil videos
136
+ assert (videos.length > 0)
137
+
138
+ # make sure all video records look good
139
+ videos.each { |video| _test_video(video) }
140
+ end
141
+
142
+ def _test_video (video)
143
+ assert_kind_of YouTube::Video, video
144
+
145
+ # sanity-check embed url to make sure it looks ok
146
+ assert_not_nil video.embed_url
147
+ _assert_youtube_url video.embed_url
148
+
149
+ # check other attributes
150
+ assert (video.thumbnail_url =~ /\.jpg$/)
151
+ assert (video.title && video.title.length > 0)
152
+ assert (video.upload_time && video.upload_time.is_a?(Time))
153
+ _assert_youtube_url video.url
154
+ assert (video.view_count > 0)
155
+ assert (video.tags && video.tags.length > 0)
156
+ assert (video.author && video.author.length > 0)
157
+ end
158
+ end
metadata CHANGED
@@ -3,19 +3,19 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: youtube
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2006-09-27 00:00:00 -05:00
8
- summary: An interface in Ruby to the YouTube REST API. By Shane Vitarana.
6
+ version: 0.8.0
7
+ date: 2006-11-18 00:00:00 -06:00
8
+ summary: A Ruby object-oriented interface to the YouTube REST API.
9
9
  require_paths:
10
- - .
10
+ - lib
11
11
  email: shanev@gmail.com
12
- homepage: http://shanesbrain.net
12
+ homepage:
13
13
  rubyforge_project: youtube
14
14
  description:
15
- autorequire: youtube
15
+ autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
- has_rdoc: false
18
+ has_rdoc: true
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
21
  - - ">"
@@ -28,20 +28,27 @@ cert_chain:
28
28
  authors:
29
29
  - Shane Vitarana
30
30
  files:
31
- - youtube.rb
32
- - dirtywork.rb
31
+ - examples/example.rb
32
+ - lib/youtube.rb
33
+ - test/test_api.rb
34
+ - AUTHORS
35
+ - CHANGELOG
36
+ - README
37
+ - Rakefile
38
+ - TODO
33
39
  test_files: []
34
40
 
35
- rdoc_options: []
36
-
37
- extra_rdoc_files: []
38
-
41
+ rdoc_options:
42
+ - --main
43
+ - README
44
+ extra_rdoc_files:
45
+ - README
39
46
  executables: []
40
47
 
41
48
  extensions: []
42
49
 
43
- requirements:
44
- - YouTube Developer ID
50
+ requirements: []
51
+
45
52
  dependencies:
46
53
  - !ruby/object:Gem::Dependency
47
54
  name: xml-simple
@@ -50,5 +57,5 @@ dependencies:
50
57
  requirements:
51
58
  - - ">="
52
59
  - !ruby/object:Gem::Version
53
- version: 1.0.7
60
+ version: 1.0.9
54
61
  version:
data/dirtywork.rb DELETED
@@ -1,64 +0,0 @@
1
- #--
2
- # Copyright (c) 2006 Shane Vitarana
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- require 'net/http'
25
- require 'xmlsimple'
26
- require 'uri'
27
-
28
- HOST = 'http://youtube.com'
29
- API = '/api2_rest'
30
-
31
- # Module that does all the work to send out the HTTP request and retrieve
32
- # the XML response. Inspired by the Flickr interface by Scott Raymond
33
- # <http://redgreenblu.com/flickr/>.
34
-
35
- module DirtyWork
36
-
37
- # All API methods are implemented with this method.
38
- # This method is like a remote method call, it encapsulates
39
- # the request/response cycle to the remote host. It extracts
40
- # the remote method API name based on the ruby method name.
41
- private
42
- def method_missing(method_id, *params)
43
- request(method_id.to_s.sub('_', '.'), *params)
44
- end
45
-
46
- private
47
- def request(method, *params)
48
- response = XmlSimple.xml_in(http_get(request_url(method, *params)), { 'ForceArray' => false })
49
- raise response['error']['description'] if response['status'] != 'ok'
50
- response
51
- end
52
-
53
- private
54
- def request_url(method, *params)
55
- param_list = String.new
56
- params[0].each_pair { |k,v| param_list << "&"+k.to_s+"="+URI.encode(v.to_s) } if !params.empty?
57
- url = "#{HOST}#{API}?method=youtube."+method+"&dev_id="+@dev_id+param_list
58
- end
59
-
60
- private
61
- def http_get(url)
62
- Net::HTTP.get_response(URI.parse(url)).body.to_s
63
- end
64
- end
data/youtube.rb DELETED
@@ -1,204 +0,0 @@
1
- #--
2
- # Copyright (c) 2006 Shane Vitarana
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
- #++
23
-
24
- require 'dirtywork'
25
-
26
- # YouTube client class. Requires a Developer ID.
27
- # Get one here: <http://youtube.com/my_profile_dev>.
28
-
29
- class YouTube
30
- include DirtyWork
31
-
32
- def initialize(dev_id)
33
- @dev_id = dev_id
34
- end
35
-
36
- # username = the user to get the profile for
37
- def profile(username)
38
- response = users_get_profile(:user => username)
39
- Profile.new response['user_profile']
40
- end
41
-
42
- # username = the user to get favorite videos for
43
- def favorite_videos(username)
44
- videos = Array.new
45
- response = users_list_favorite_videos(:user => username)
46
- if !response['video_list'].empty?
47
- response['video_list']['video'].each do |video|
48
- videos << Video.new(video, @dev_id)
49
- end
50
- end
51
- videos
52
- end
53
-
54
- #username = the user to get list of friends from
55
- def friends(username)
56
- friends = Array.new
57
- response = users_list_friends(:user => username)
58
- if !response['friend_list'].empty?
59
- response['friend_list'].each do |friend|
60
- friends << Friend.new(friend[1])
61
- end
62
- end
63
- friends
64
- end
65
-
66
- # tag = the tag to search for, must be one word
67
- # optional: page = the "page" of results to retrieve (e.g. 1, 2, 3)
68
- # optional: per_page = the number of results per page (default: 20, max 100)
69
- def videos_by_tag(tag, page=1, per_page=20)
70
- videos = Array.new
71
- response = videos_list_by_tag(:tag => tag, :page => page, :per_page => per_page)
72
- if !response['video_list'].empty?
73
- response['video_list']['video'].each do |video|
74
- videos << Video.new(video, @dev_id)
75
- end
76
- end
77
- videos
78
- end
79
-
80
- # username = the user to get videos for
81
- def videos_by_user(username)
82
- videos = Array.new
83
- response = videos_list_by_user(:user => username)
84
- if !response['video_list'].empty?
85
- response['video_list']['video'].each do |video|
86
- videos << Video.new(video, @dev_id)
87
- end
88
- end
89
- videos
90
- end
91
-
92
- # this method takes no arguments
93
- def featured_videos
94
- videos = Array.new
95
- response = videos_list_featured
96
- if !response['video_list'].empty?
97
- response['video_list']['video'].each do |video|
98
- videos << Video.new(video, @dev_id)
99
- end
100
- end
101
- videos
102
- end
103
-
104
- class Friend
105
- attr_reader :user, :video_upload_count, :favorite_count, :friend_count
106
-
107
- def initialize(friend)
108
- @user = friend['user']
109
- @video_upload_count = friend['video_upload_count']
110
- @favorite_count = friend['favorite_count']
111
- @friend_count = friend['friend_count']
112
- end
113
- end
114
-
115
- class Profile
116
- attr_reader :first_name, :last_name, :about_me, :age, :video_upload_count,
117
- :video_watch_count, :homepage, :hometown, :gender, :occupations,
118
- :companies, :city, :country, :books, :hobbies, :movies,
119
- :relationship, :friend_count, :favorite_video_count, :currently_on
120
-
121
- def initialize(profile)
122
- @first_name = profile['first_name']
123
- @last_name = profile['last_name']
124
- @about_me = profile['about_me']
125
- @age = profile['age']
126
- @video_upload_count = profile['video_upload_count']
127
- @video_watch_count = profile['video_watch_count']
128
- @homepage = profile['homepage']
129
- @hometown = profile['hometown']
130
- @gender = profile['gender']
131
- @occupations = profile['occupations']
132
- @companies = profile['companies']
133
- @city = profile['city']
134
- @country = profile['country']
135
- @books = profile['books']
136
- @hobbies = profile['hobbies']
137
- @movies = profile['movies']
138
- @relationship = profile['relationship']
139
- @friend_count = profile['friend_count']
140
- @favorite_video_count = profile['favorite_video_count']
141
- @currently_on = profile['currently_on']
142
- end
143
- end
144
-
145
- class Video
146
- include DirtyWork
147
- attr_reader :author, :id, :title, :length, :rating_avg, :rating_count,
148
- :description, :view_count, :upload_time, :comment_count,
149
- :tags, :url, :thumbnail_url, :embed_url
150
-
151
- def initialize(video, dev_id)
152
- @dev_id = dev_id
153
- @author = video['author']
154
- @id = video['id']
155
- @title = video['title']
156
- @length = video['length']
157
- @rating_avg = video['rating_avg']
158
- @rating_count = video['rating_count']
159
- @description = video['description']
160
- @view_count = video['view_count']
161
- @upload_time = video['upload_time']
162
- @comment_count = video['comment_count']
163
- @tags = video['tags']
164
- @url = video['url']
165
- @thumbnail_url = video['thumbnail_url']
166
-
167
- # the url from the API is made for viewing, not for embedding
168
- # fix url to allow embedding
169
- @embed_url = @url.delete('?').sub('=', '/')
170
- end
171
-
172
- def details
173
- response = videos_get_details(:video_id => @id)
174
- VideoDetails.new response['video_details']
175
- end
176
- end
177
-
178
- class VideoDetails
179
- attr_reader :author, :title, :rating_avg, :rating_count, :tags, :description,
180
- :update_time, :view_count, :upload_time, :length_seconds,
181
- :recording_date, :recoding_location, :recording_country,
182
- :comment_list, :channel_list, :thumbnail_url
183
-
184
- def initialize(details)
185
- @author = details['author']
186
- @title = details['title']
187
- @rating_avg = details['rating_avg']
188
- @rating_count = details['rating_count']
189
- @tags = details['tags']
190
- @description = details['description']
191
- @update_time = details['update_time']
192
- @view_count = details['view_count']
193
- @upload_time = details['upload_time']
194
- @length_seconds = details['length_seconds']
195
- @recording_date = details['recording_date']
196
- @recording_location = details['recording_location']
197
- @recording_country = details['recording_country']
198
- @comment_list = details['comment_list']
199
- @channel_list = details['channel_list']
200
- @thumbnail_url = details['thumbnail_url']
201
- end
202
- end
203
-
204
- end