youtube 0.1.1 → 0.8.0

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/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