youtube_it 0.0.3 → 0.0.7

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/History.txt CHANGED
@@ -1,52 +1,15 @@
1
- * Implement video update and video delete in Upload
2
- * Refactor the uploader slightly
3
- * Use Builder to generate XML packets
4
- * Use a faster Camping-based URL escaper (also avoid cgi.rb)
5
- * Removed the logger nightmare, use YouTubeIt.logger for everything whatever that may be
6
- * Use streams for file uploads instead of in-memory strings
1
+ * Initial fork
7
2
 
8
- == 0.5.0 / 2009-01-07
3
+ == 0.0.1 / 2010-07-05
9
4
 
10
- * Fixed bug in user favorites (thanks Pius Uzamere)
5
+ * Enhanced youtube-g to support:
6
+ ~> direct uploads to youtube
7
+ ~> update video on youtube
8
+ ~> comment on youtube video
9
+ ~> delete youtube video
10
+
11
+ == 0.0.2 / 2010-07-07
11
12
 
12
- == 0.4.9.9 / 2008-09-01
13
-
14
- * Add Geodata information (thanks Jose Galisteo)
15
- * Added :page and :per_page options, this allows easier usage of the will_paginate
16
- plugin with the library. The :offset and :max_results options are no longer available. [Daniel Insley]
17
- * Added ability to get video responses on the instances of the YouTube::Model::Video object. [Daniel Insley]
18
- * Added and improved the existing documentation [Daniel Insley]
19
- * Fixed usage of deprecated yt:racy, now using media:rating [Daniel Insley]
20
- * Renamed can_embed? method to embeddable? [Daniel Insley]
21
- * Added ability for padingation and ordering on standard feeds. [Daniel Insley]
22
- * Add error-handling for video upload errors. [FiXato]
23
- * Add error-handling for authentication errors from YouTube during video upload. [FiXato]
24
- * Add support for making videos private upon video upload. [FiXato]
25
- * Fix issue with REXML parsing of video upload response. [FiXato]
26
- * Fix issue with response code comparison. [FiXato]
27
- * Authcode is now retrieved for video uploads. [FiXato]
28
- * Add basic support for uploading videos [thanks Joe Damato]
29
- * Add basic support for related videos [tmm1]
30
- * Improve docs for order_by attribute [thanks Jason Arora]
31
- * Added support for the "racy" parameter (choices are "include" or "exclude") [thanks Jason Arora]
32
- * Add missing attribute reader for description [tmm1]
33
- * Fix issue with missing yt:statistics and viewCount [tmm1]
34
- * Allow Client#video_by to take either a url or a video id [tmm1]
35
-
36
- == 0.4.1 / 2008-02-11
37
-
38
- * Added 3GPP video format [shane]
39
- * Fixed tests [shane]
40
-
41
- == 0.4.0 / 2007-12-18
42
-
43
- * Fixed API projection in search URL [Pete Higgins]
44
- * Fixed embeddable video searching [Pete Higgins]
45
- * Fixed video embeddable detection [Pete Higgins]
46
- * Fixed unique id hyphen detection [Pete Higgins, Chris Taggart]
47
-
48
- == 0.3.0 / 2007-09-17
49
-
50
- * Initial public release
51
- * Birthday!
13
+ * ACL support
52
14
 
15
+ == 0.0.3 / 2010-08-12
data/README.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  == DESCRIPTION:
2
2
 
3
- youtube_it is a pure Ruby client for the YouTube GData API. It provides an easy
3
+ youtube_it is the most complete Ruby client for the YouTube GData API. It provides an easy
4
4
  way to access the latest YouTube video search results from your own programs.
5
5
  In comparison with the earlier Youtube search interfaces, this new API and
6
6
  library offers much-improved flexibility around executing complex search
@@ -42,6 +42,11 @@ Upload videos:
42
42
 
43
43
  client = YouTubeIt::Client.new("youtube_username", "youtube_passwd", "developer_key")
44
44
 
45
+ Or better yet, you can use OAuth
46
+
47
+ client = YouTubeIt::OAuthClient.new("consumer_key", "consumer_secret", "youtube_username", "developer_key")
48
+ client.authorize_from_access("access_token", "access_secret")
49
+
45
50
  * upload video
46
51
 
47
52
  client.video_upload(File.open("test.mov"), :title => "test",:description => 'some description', :category => 'People',:keywords => %w[cool blah test])
@@ -71,7 +76,7 @@ Comments
71
76
 
72
77
  Favorites
73
78
 
74
- You can add, del or list your favorites videos:
79
+ You can add, delete or list your favorites videos:
75
80
 
76
81
  client = YouTubeIt::Client.new("youtube_username", "youtube_passwd", "developer_key")
77
82
 
@@ -83,9 +88,46 @@ Favorites
83
88
 
84
89
  client.add_favorite(video_id)
85
90
 
86
- * del favorite:
91
+ * delete favorite:
92
+
93
+ client.delete_favorite(video_id)
94
+
95
+ Playlist
96
+
97
+ You can add, delete or list your playlists:
98
+
99
+ client = YouTubeIt::Client.new("youtube_username", "youtube_passwd", "developer_key")
100
+
101
+ * get all playlists:
102
+
103
+ client.playlists
104
+
105
+ * select your playlist:
106
+
107
+ client.playlist(playlist_id)
108
+
109
+ * get all videos from your playlist:
110
+
111
+ my_playlist = client.playlist(playlist_id)
112
+
113
+ my_playlist.videos
114
+
115
+ * create new playlist:
116
+
117
+ my_playlist = client.add_playlist(:title => "new playlist", :description => "playlist description")
118
+
119
+ * delete a playlist:
120
+
121
+ client.delete_playlist(playlist_id)
122
+
123
+ * add video to playlist:
124
+
125
+ client.add_video_to_playlist(playlist_id, video_id)
126
+
127
+ * remove video from playlist:
128
+
129
+ client.remove_video_from_playlist(playlist_id, playlist_entry_id)
87
130
 
88
- client.del_favorite(video_id)
89
131
 
90
132
  Access Control List
91
133
 
@@ -145,11 +187,12 @@ YouTubeIt passes all logs through the logger variable on the class itself. In Ra
145
187
  == INSTALL:
146
188
 
147
189
  * sudo gem install youtube_it
190
+
148
191
  == LICENSE:
149
192
 
150
193
  MIT License
151
194
 
152
- Copyright (c) 2007 Shane Vitarana and Walter Korman
195
+ Copyright (c) 2010 Kyle J. Ginavan
153
196
 
154
197
  Permission is hereby granted, free of charge, to any person obtaining
155
198
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -5,11 +5,11 @@ begin
5
5
  require 'jeweler'
6
6
  Jeweler::Tasks.new do |gem|
7
7
  gem.name = "youtube_it"
8
- gem.summary = %Q{the one stop shop for working with youtube apis}
9
- gem.description = %Q{the one stop shop for working with youtube apis}
8
+ gem.summary = %Q{The most complete Ruby wrapper for youtube api's}
9
+ gem.description = %Q{Upload, delete, update, comment on youtube videos all from one gem.}
10
10
  gem.email = "kylejginavan@gmail.com"
11
11
  gem.homepage = "http://github.com/kylejginavan/youtube_it"
12
- gem.authors = ["Mauro Torres & Kyle Ginavan"]
12
+ gem.authors = ["chebyte","kylejginavan"]
13
13
  end
14
14
  Jeweler::GemcutterTasks.new
15
15
  rescue LoadError
@@ -18,16 +18,16 @@ end
18
18
 
19
19
  require 'rake/testtask'
20
20
  Rake::TestTask.new(:test) do |test|
21
- test.libs << 'spec'
22
- test.pattern = 'spec/**/*_spec.rb'
21
+ test.libs << 'test'
22
+ test.pattern = 'test/**/test_*.rb'
23
23
  test.verbose = true
24
24
  end
25
25
 
26
26
  begin
27
27
  require 'rcov/rcovtask'
28
28
  Rcov::RcovTask.new do |test|
29
- test.libs << 'spec'
30
- test.pattern = 'spec/**/*_spec.rb'
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
31
  test.verbose = true
32
32
  end
33
33
  rescue LoadError
@@ -50,10 +50,3 @@ Rake::RDocTask.new do |rdoc|
50
50
  rdoc.rdoc_files.include('lib/**/*.rb')
51
51
  end
52
52
 
53
- require 'spec/rake/spectask'
54
- desc "Run all specs"
55
- Spec::Rake::SpecTask.new('spec') do |t|
56
- t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
57
- t.spec_files = FileList['spec/**/*_spec.rb']
58
- end
59
-
data/VERSION CHANGED
@@ -1 +1,2 @@
1
- 0.0.3
1
+ 0.0.7
2
+
@@ -66,6 +66,11 @@ class YouTubeIt::GreedyChainIO < DelegateClass(YouTubeIt::ChainIO)
66
66
  def read(any_buffer_size)
67
67
  __getobj__.read(BIG_CHUNK)
68
68
  end
69
+
70
+ def length()
71
+ __getobj__.expected_length
72
+ end
73
+
69
74
  end
70
75
 
71
- #:startdoc:
76
+ #:startdoc:
@@ -6,7 +6,6 @@ class YouTubeIt
6
6
  @user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
7
7
  end
8
8
 
9
-
10
9
  # Retrieves an array of standard feed, custom query, or user videos.
11
10
  #
12
11
  # === Parameters
@@ -107,14 +106,42 @@ class YouTubeIt
107
106
  client.add_favorite(video_id)
108
107
  end
109
108
 
110
- def del_favorite(video_id)
111
- client.del_favorite(video_id)
109
+ def delete_favorite(video_id)
110
+ client.delete_favorite(video_id)
112
111
  end
113
112
 
114
113
  def favorites
115
114
  client.favorites
116
115
  end
117
116
 
117
+ def playlist(playlist_id)
118
+ client.playlist playlist_id
119
+ end
120
+
121
+ def playlists
122
+ client.playlists
123
+ end
124
+
125
+ def add_playlist(options)
126
+ client.add_playlist(options)
127
+ end
128
+
129
+ def update_playlist(playlist_id, options)
130
+ client.update_playlist(playlist_id, options)
131
+ end
132
+
133
+ def add_video_to_playlist(playlist_id, video_id)
134
+ client.add_video_to_playlist(playlist_id, video_id)
135
+ end
136
+
137
+ def delete_video_from_playlist(playlist_id, playlist_entry_id)
138
+ client.delete_video_from_playlist(playlist_id, playlist_entry_id)
139
+ end
140
+
141
+ def delete_playlist(playlist_id)
142
+ client.delete_playlist(playlist_id)
143
+ end
144
+
118
145
  def enable_http_debugging
119
146
  client.enable_http_debugging
120
147
  end
@@ -134,5 +161,45 @@ class YouTubeIt
134
161
  value > 0 ? value : default
135
162
  end
136
163
  end
164
+
165
+ class OAuthClient < Client
166
+ def initialize ctoken = nil, csecret = nil, user = nil, dev_key = nil, client_id = 'youtube_it', legacy_debug_flag = nil
167
+ @consumer_key, @consumer_secret, @user, @dev_key, @client_id = ctoken, csecret, user, dev_key, client_id
168
+ end
169
+
170
+ def consumer
171
+ @consumer ||= OAuth::Consumer.new(@consumer_key,@consumer_secret,{
172
+ :site=>"https://www.google.com",
173
+ :request_token_path=>"/accounts/OAuthGetRequestToken",
174
+ :authorize_path=>"/accounts/OAuthAuthorizeToken",
175
+ :access_token_path=>"/accounts/OAuthGetAccessToken"})
176
+ end
177
+
178
+ def request_token(callback)
179
+ @request_token = consumer.get_request_token({:oauth_callback => callback},{:scope => "http://gdata.youtube.com"})
180
+ end
181
+
182
+ def access_token
183
+ @access_token = OAuth::AccessToken.new(consumer, @atoken, @asecret)
184
+ end
185
+
186
+ def authorize_from_request(rtoken,rsecret,verifier)
187
+ request_token = OAuth::RequestToken.new(consumer,rtoken,rsecret)
188
+ access_token = request_token.get_access_token({:oauth_verifier => verifier})
189
+ @atoken,@asecret = access_token.token, access_token.secret
190
+ end
191
+
192
+ def authorize_from_access(atoken,asecret)
193
+ @atoken,@asecret = atoken, asecret
194
+ end
195
+
196
+ private
197
+
198
+ def client
199
+ # IMPORTANT: make sure authorize_from_access is called before client is fetched
200
+ @client ||= YouTubeIt::Upload::VideoUpload.new(@user, "", @dev_key, "youtube_it", access_token)
201
+ end
202
+
203
+ end
137
204
  end
138
205
 
@@ -1,8 +1,11 @@
1
1
  class YouTubeIt
2
2
  module Model
3
3
  class Playlist < YouTubeIt::Record
4
- # *String*:: User entered description for the playlist.
5
- attr_reader :description
4
+ attr_reader :title, :description, :summary, :playlist_id, :xml, :published, :response_code
5
+ def videos
6
+ YouTubeIt::Parser::VideosFeedParser.new("http://gdata.youtube.com/feeds/api/playlists/#{playlist_id}?v=2").parse_videos
7
+ end
6
8
  end
7
9
  end
8
10
  end
11
+
@@ -1,12 +1,37 @@
1
1
  class YouTubeIt
2
2
  module Parser #:nodoc:
3
3
  class FeedParser #:nodoc:
4
- def initialize(url)
5
- @url = open(url).read rescue url
4
+ def initialize(content)
5
+ @content = open(content).read rescue content
6
6
  end
7
7
 
8
8
  def parse
9
- parse_content @url
9
+ parse_content @content
10
+ end
11
+
12
+ def parse_videos
13
+ doc = REXML::Document.new(@content)
14
+ videos = []
15
+ doc.elements.each("*/entry") do |video|
16
+ videos << parse_entry(video)
17
+ end
18
+ videos
19
+ end
20
+ end
21
+
22
+ class PlaylistFeedParser < FeedParser #:nodoc:
23
+
24
+ def parse_content(content)
25
+ xml = REXML::Document.new(content.body)
26
+ entry = xml.elements["entry"] || xml.elements["feed"]
27
+ YouTubeIt::Model::Playlist.new(
28
+ :title => entry.elements["title"].text,
29
+ :summary => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
30
+ :description => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
31
+ :playlist_id => entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':',''),
32
+ :published => entry.elements["published"] ? entry.elements["published"].text : nil,
33
+ :response_code => content.code,
34
+ :xml => content.body)
10
35
  end
11
36
  end
12
37
 
@@ -21,8 +46,8 @@ class YouTubeIt
21
46
  protected
22
47
  def parse_entry(entry)
23
48
  video_id = entry.elements["id"].text
24
- published_at = Time.parse(entry.elements["published"].text)
25
- updated_at = Time.parse(entry.elements["updated"].text)
49
+ published_at = entry.elements["published"] ? Time.parse(entry.elements["published"].text) : nil
50
+ updated_at = entry.elements["updated"] ? Time.parse(entry.elements["updated"].text) : nil
26
51
 
27
52
  # parse the category and keyword lists
28
53
  categories = []
@@ -43,7 +68,7 @@ class YouTubeIt
43
68
  end
44
69
 
45
70
  title = entry.elements["title"].text
46
- html_content = entry.elements["content"].text
71
+ html_content = entry.elements["content"] ? entry.elements["content"].text : nil
47
72
 
48
73
  # parse the author
49
74
  author_element = entry.elements["author"]
@@ -16,8 +16,8 @@ class YouTubeIt
16
16
  #
17
17
  class VideoUpload
18
18
  include YouTubeIt::Logging
19
- def initialize user, pass, dev_key, client_id = 'youtube_it'
20
- @user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
19
+ def initialize user, pass, dev_key, client_id = 'youtube_it', access_token = nil
20
+ @user, @pass, @dev_key, @client_id, @access_token = user, pass, dev_key, client_id, access_token
21
21
  @http_debugging = false
22
22
  end
23
23
 
@@ -52,6 +52,7 @@ class YouTubeIt
52
52
  #
53
53
  # When the authentication credentials are incorrect, an AuthenticationError will be raised.
54
54
  def upload data, opts = {}
55
+ response = ""
55
56
  @opts = { :mime_type => 'video/mp4',
56
57
  :title => '',
57
58
  :description => '',
@@ -62,28 +63,31 @@ class YouTubeIt
62
63
 
63
64
  post_body_io = generate_upload_io(video_xml, data)
64
65
 
65
- upload_headers = authorization_headers.merge({
66
+ upload_headers = {
66
67
  "Slug" => "#{@opts[:filename]}",
67
68
  "Content-Type" => "multipart/related; boundary=#{boundary}",
68
69
  "Content-Length" => "#{post_body_io.expected_length}", # required per YouTube spec
69
70
  # "Transfer-Encoding" => "chunked" # We will stream instead of posting at once
70
- })
71
-
72
- path = '/feeds/api/users/%s/uploads' % @user
73
-
74
- http = Net::HTTP.new(uploads_url)
75
- http.set_debug_output(logger) if @http_debugging
76
- http.start do | session |
77
-
78
- # Use the chained IO as body so that Net::HTTP reads into the socket for us
79
- post = Net::HTTP::Post.new(path, upload_headers)
80
- post.body_stream = post_body_io
81
-
82
- response = session.request(post)
83
- raise_on_faulty_response(response)
71
+ }
84
72
 
85
- return uploaded_video_id_from(response.body)
73
+ if @access_token.nil?
74
+ upload_headers.merge!(authorization_headers)
75
+ http = Net::HTTP.new(uploads_url)
76
+ http.set_debug_output(logger) if @http_debugging
77
+ path = '/feeds/api/users/%s/uploads' % @user
78
+ http.start do | session |
79
+ # Use the chained IO as body so that Net::HTTP reads into the socket for us
80
+ post = Net::HTTP::Post.new(path, upload_headers)
81
+ post.body_stream = post_body_io
82
+ response = session.request(post)
83
+ end
84
+ else
85
+ upload_headers.merge!(authorization_headers_for_soap)
86
+ url = 'http://%s/feeds/api/users/%s/uploads' % [uploads_url, @user]
87
+ response = @access_token.post(url, post_body_io, upload_headers)
86
88
  end
89
+ raise_on_faulty_response(response)
90
+ return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
87
91
  end
88
92
 
89
93
  # Updates a video in YouTube. Requires:
@@ -95,42 +99,55 @@ class YouTubeIt
95
99
  # :private
96
100
  # When the authentication credentials are incorrect, an AuthenticationError will be raised.
97
101
  def update(video_id, options)
102
+ response = ""
98
103
  @opts = options
99
104
 
100
105
  update_body = video_xml
101
106
 
102
- update_header = authorization_headers.merge({
107
+ update_header = {
103
108
  "GData-Version" => "2",
104
109
  "Content-Type" => "application/atom+xml",
105
110
  "Content-Length" => "#{update_body.length}",
106
- })
111
+ }
107
112
 
108
113
  update_url = "/feeds/api/users/#{@user}/uploads/#{video_id}"
109
114
 
110
- http = Net::HTTP.new(base_url)
111
- http.set_debug_output(logger) if @http_debugging
112
- http.start do | session |
113
- response = session.put(update_url, update_body, update_header)
114
- raise_on_faulty_response(response)
115
-
116
- return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
115
+ if @access_token.nil?
116
+ update_header.merge!(authorization_headers)
117
+ http_connection do |session|
118
+ response = session.put(update_url, update_body, update_header)
119
+ end
120
+ else
121
+ upload_headers.merge!(authorization_headers_for_soap)
122
+ response = @access_token.put("http://"+base_url+update_url, update_body, update_header)
117
123
  end
124
+
125
+ raise_on_faulty_response(response)
126
+ return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
118
127
  end
119
128
 
120
129
  # Delete a video on YouTube
121
130
  def delete(video_id)
122
- delete_header = authorization_headers.merge({
131
+ delete_header = {
123
132
  "Content-Type" => "application/atom+xml; charset=UTF-8",
124
133
  "Content-Length" => "0",
125
- })
134
+ }
126
135
 
127
136
  delete_url = "/feeds/api/users/#{@user}/uploads/#{video_id}"
128
137
 
129
- Net::HTTP.start(base_url) do |session|
130
- response = session.delete(delete_url, delete_header)
138
+ if @access_token.nil?
139
+ delete_header.merge!(authorization_headers)
140
+ http_connection do |session|
141
+ response = session.delete(delete_url, delete_header)
142
+ raise_on_faulty_response(response)
143
+ end
144
+ else
145
+ upload_headers.merge!(authorization_headers_for_soap)
146
+ response = @access_token.delete("http://"+base_url+delete_url, delete_header)
131
147
  raise_on_faulty_response(response)
132
- return true
133
148
  end
149
+
150
+ return true
134
151
  end
135
152
 
136
153
  def get_upload_token(options, nexturl)
@@ -143,7 +160,7 @@ class YouTubeIt
143
160
  })
144
161
  token_url = "/action/GetUploadToken"
145
162
 
146
- Net::HTTP.start(base_url) do | session |
163
+ http_connection do |session|
147
164
  response = session.post(token_url, token_body, token_header)
148
165
  raise_on_faulty_response(response)
149
166
  return {:url => "#{response.body[/<url>(.+)<\/url>/, 1]}?nexturl=#{nexturl}",
@@ -161,23 +178,19 @@ class YouTubeIt
161
178
 
162
179
  comment_url = "/feeds/api/videos/#{video_id}/comments"
163
180
 
164
- http = Net::HTTP.new(base_url)
165
- http.set_debug_output(logger) if @http_debugging
166
- http.start do | session |
181
+ http_connection do |session|
167
182
  response = session.post(comment_url, comment_body, comment_header)
168
183
  raise_on_faulty_response(response)
169
- response = {:code => response.code, :body => response.body}
184
+ {:code => response.code, :body => response.body}
170
185
  end
171
186
  end
172
187
 
173
188
  def comments(video_id)
174
189
  comment_url = "/feeds/api/videos/#{video_id}/comments"
175
- http = Net::HTTP.new(base_url)
176
- http.set_debug_output(logger) if @http_debugging
177
- http.start do | session |
190
+ http_connection do |session|
178
191
  response = session.get(comment_url)
179
192
  raise_on_faulty_response(response)
180
- response = {:code => response.code, :body => response.body}
193
+ {:code => response.code, :body => response.body}
181
194
  end
182
195
  end
183
196
 
@@ -191,16 +204,14 @@ class YouTubeIt
191
204
 
192
205
  favorite_url = "/feeds/api/users/#{@user}/favorites"
193
206
 
194
- http = Net::HTTP.new(base_url)
195
- http.set_debug_output(logger) if @http_debugging
196
- http.start do | session |
207
+ http_connection do |session|
197
208
  response = session.post(favorite_url, favorite_body, favorite_header)
198
209
  raise_on_faulty_response(response)
199
- return true
210
+ {:code => response.code, :body => response.body}
200
211
  end
201
212
  end
202
213
 
203
- def del_favorite(video_id)
214
+ def delete_favorite(video_id)
204
215
  favorite_header = authorization_headers.merge({
205
216
  "Content-Type" => "application/atom+xml; charset=UTF-8",
206
217
  "Content-Length" => "0",
@@ -208,20 +219,116 @@ class YouTubeIt
208
219
 
209
220
  favorite_url = "/feeds/api/users/#{@user}/favorites/#{video_id}"
210
221
 
211
- http = Net::HTTP.new(base_url)
212
- http.set_debug_output(logger) if @http_debugging
213
- http.start do | session |
222
+ http_connection do |session|
214
223
  response = session.delete(favorite_url, favorite_header)
215
224
  raise_on_faulty_response(response)
216
225
  return true
217
226
  end
218
227
  end
219
228
 
229
+ def playlist(playlist_id)
230
+ playlist_url = "/feeds/api/playlists/#{playlist_id}?v=2"
231
+ http_connection do |session|
232
+ response = session.get(playlist_url)
233
+ raise_on_faulty_response(response)
234
+ return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
235
+ end
236
+ end
237
+
238
+ def playlists
239
+ playlist_url = "/feeds/api/users/#{@user}/playlists?v=2"
240
+ http_connection do |session|
241
+ response = session.get(playlist_url)
242
+ raise_on_faulty_response(response)
243
+ return response.body
244
+ end
245
+ end
246
+
247
+ def add_playlist(options)
248
+ playlist_body = video_xml_for_playlist(options)
249
+ playlist_header = authorization_headers.merge({
250
+ "GData-Version" => "2",
251
+ "Content-Type" => "application/atom+xml",
252
+ "Content-Length" => "#{playlist_body.length}",
253
+ })
254
+
255
+ playlist_url = "/feeds/api/users/#{@user}/playlists"
256
+
257
+ http_connection do |session|
258
+ response = session.post(playlist_url, playlist_body, playlist_header)
259
+ raise_on_faulty_response(response)
260
+ return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
261
+ end
262
+ end
263
+
264
+ def add_video_to_playlist(playlist_id, video_id)
265
+ playlist_body = video_xml_for(:playlist => video_id)
266
+ playlist_header = authorization_headers.merge({
267
+ "GData-Version" => "2",
268
+ "Content-Type" => "application/atom+xml",
269
+ "Content-Length" => "#{playlist_body.length}",
270
+ })
271
+
272
+ playlist_url = "/feeds/api/playlists/#{playlist_id}"
273
+
274
+ http_connection do |session|
275
+ response = session.post(playlist_url, playlist_body, playlist_header)
276
+ raise_on_faulty_response(response)
277
+ {:code => response.code, :body => response.body, :playlist_entry_id => playlist_entry_id_from_playlist(response.body)}
278
+ end
279
+ end
280
+
281
+ def update_playlist(playlist_id, options)
282
+ playlist_body = video_xml_for_playlist(options)
283
+ playlist_header = authorization_headers.merge({
284
+ "GData-Version" => "2",
285
+ "Content-Type" => "application/atom+xml",
286
+ "Content-Length" => "#{playlist_body.length}",
287
+ })
288
+ playlist_url = "/feeds/api/users/#{@user}/playlists/#{playlist_id}"
289
+
290
+ http_connection do |session|
291
+ response = session.put(playlist_url, playlist_body, playlist_header)
292
+ raise_on_faulty_response(response)
293
+ return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
294
+ end
295
+ end
296
+
297
+
298
+ def delete_video_from_playlist(playlist_id, playlist_entry_id)
299
+ playlist_header = {
300
+ "Content-Type" => "application/atom+xml",
301
+ "GData-Version" => "2",
302
+ "X-GData-Key" => "key=#{@dev_key}",
303
+ "Authorization" => "GoogleLogin auth=#{auth_token}",
304
+ }
305
+
306
+ playlist_url = "/feeds/api/playlists/#{playlist_id}/#{playlist_entry_id}"
307
+
308
+ http_connection do |session|
309
+ response = session.delete(playlist_url, playlist_header)
310
+ raise_on_faulty_response(response)
311
+ return true
312
+ end
313
+ end
314
+
315
+ def delete_playlist(playlist_id)
316
+ playlist_header = authorization_headers.merge({
317
+ "Content-Type" => "application/atom+xml; charset=UTF-8",
318
+ "GData-Version" => "2"
319
+ })
320
+
321
+ playlist_url = "/feeds/api/users/#{@user}/playlists/#{playlist_id}"
322
+ http_connection do |session|
323
+ response = session.delete(playlist_url, playlist_header)
324
+ raise_on_faulty_response(response)
325
+ return true
326
+ end
327
+ end
328
+
220
329
  def favorites
221
330
  favorite_url = "/feeds/api/users/#{@user}/favorites"
222
- http = Net::HTTP.new(base_url)
223
- http.set_debug_output(logger) if @http_debugging
224
- http.start do | session |
331
+ http_connection do |session|
225
332
  response = session.get(favorite_url)
226
333
  raise_on_faulty_response(response)
227
334
  return response.body
@@ -242,6 +349,12 @@ class YouTubeIt
242
349
  "An43094fu"
243
350
  end
244
351
 
352
+ def authorization_headers_for_soap
353
+ {
354
+ "X-GData-Key" => "key=#{@dev_key}"
355
+ }
356
+ end
357
+
245
358
  def authorization_headers
246
359
  {
247
360
  "Authorization" => "GoogleLogin auth=#{auth_token}",
@@ -251,10 +364,18 @@ class YouTubeIt
251
364
  end
252
365
 
253
366
  def parse_upload_error_from(string)
254
- REXML::Document.new(string).elements["//errors"].inject('') do | all_faults, error|
255
- location = error.elements["location"].text[/media:group\/media:(.*)\/text\(\)/,1]
256
- code = error.elements["code"].text
257
- all_faults + sprintf("%s: %s\n", location, code)
367
+ begin
368
+ REXML::Document.new(string).elements["//errors"].inject('') do | all_faults, error|
369
+ if error.elements["internalReason"]
370
+ msg_error = error.elements["internalReason"].text
371
+ elsif error.elements["location"]
372
+ msg_error = error.elements["location"].text[/media:group\/media:(.*)\/text\(\)/,1]
373
+ end
374
+ code = error.elements["code"].text if error.elements["code"]
375
+ all_faults + sprintf("%s: %s\n", msg_error, code)
376
+ end
377
+ rescue
378
+ string
258
379
  end
259
380
  end
260
381
 
@@ -262,7 +383,7 @@ class YouTubeIt
262
383
  if response.code.to_i == 403
263
384
  raise AuthenticationError, response.body[/<TITLE>(.+)<\/TITLE>/, 1]
264
385
  elsif response.code.to_i / 10 != 20 # Response in 20x means success
265
- raise UploadError, parse_upload_error_from(response.body)
386
+ raise UploadError, parse_upload_error_from(response.body.gsub(/\n/,''))
266
387
  end
267
388
  end
268
389
 
@@ -271,6 +392,12 @@ class YouTubeIt
271
392
  xml.elements["//id"].text[/videos\/(.+)/, 1]
272
393
  end
273
394
 
395
+ def playlist_id_from(string)
396
+ xml = REXML::Document.new(string)
397
+ entry = xml.elements["entry"]
398
+ entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':','')
399
+ end
400
+
274
401
  # If data can be read, use the first 1024 bytes as filename. If data
275
402
  # is a file, use path. If data is a string, checksum it
276
403
  def generate_uniq_filename_from(data)
@@ -323,15 +450,17 @@ class YouTubeIt
323
450
  b.instruct!
324
451
  b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
325
452
  m.content(data[:comment]) if data[:comment]
326
- m.id(data[:favorite]) if data[:favorite]
453
+ m.id(data[:favorite] || data[:playlist]) if data[:favorite] || data[:playlist]
327
454
  end.to_s
328
455
  end
329
456
 
330
- def video_xml_for_favorite(video_id)
457
+ def video_xml_for_playlist(data)
331
458
  b = Builder::XmlMarkup.new
332
459
  b.instruct!
333
460
  b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
334
- m.id(video_id)
461
+ m.title(data[:title]) if data[:title]
462
+ m.summary(data[:description] || data[:summary]) if data[:description] || data[:summary]
463
+ m.tag!('yt:private') if data[:private]
335
464
  end.to_s
336
465
  end
337
466
 
@@ -350,6 +479,20 @@ class YouTubeIt
350
479
  YouTubeIt::GreedyChainIO.new(post_body)
351
480
  end
352
481
 
482
+ def playlist_entry_id_from_playlist(string)
483
+ playlist_xml = REXML::Document.new(string)
484
+ playlist_xml.elements.each("/entry") do |item|
485
+ return item.elements["id"].text[/^.*:([^:]+)$/,1]
486
+ end
487
+ end
488
+
489
+ def http_connection
490
+ http = Net::HTTP.new(base_url)
491
+ http.set_debug_output(logger) if @http_debugging
492
+ http.start do |session|
493
+ yield(session)
494
+ end
495
+ end
353
496
  end
354
497
  end
355
498
  end
data/test/test_client.rb CHANGED
@@ -45,7 +45,7 @@ class TestClient < Test::Unit::TestCase
45
45
  assert_equal 25, response.max_result_count
46
46
  assert_equal 1, response.offset
47
47
 
48
- response = @client.videos_by(:query => "penguin", :page => 2)
48
+ response = @client.videos_by(:query => "penguin", :page => 2)
49
49
  assert_equal "http://gdata.youtube.com/feeds/api/videos", response.feed_id
50
50
  assert_equal 25, response.max_result_count
51
51
  assert_equal 26, response.offset
@@ -145,13 +145,13 @@ class TestClient < Test::Unit::TestCase
145
145
 
146
146
  def test_should_get_videos_for_query_search_with_categories_excluded
147
147
  video = @client.video_by("EkF4JD2rO3Q")
148
- assert_equal "<object width=\"425\" height=\"350\">\n <param name=\"movie\" value=\"http://www.youtube.com/v/EkF4JD2rO3Q&feature=youtube_gdata\"></param>\n <param name=\"wmode\" value=\"transparent\"></param>\n <embed src=\"http://www.youtube.com/v/EkF4JD2rO3Q&feature=youtube_gdata\" type=\"application/x-shockwave-flash\" \n wmode=\"transparent\" width=\"425\" height=\"350\"></embed>\n</object>\n", video.embed_html
148
+ assert_equal "<object width=\"425\" height=\"350\">\n <param name=\"movie\" value=\"http://www.youtube.com/v/EkF4JD2rO3Q&feature=youtube_gdata_player\"></param>\n <param name=\"wmode\" value=\"transparent\"></param>\n <embed src=\"http://www.youtube.com/v/EkF4JD2rO3Q&feature=youtube_gdata_player\" type=\"application/x-shockwave-flash\" \n wmode=\"transparent\" width=\"425\" height=\"350\"></embed>\n</object>\n", video.embed_html
149
149
  assert_valid_video video
150
150
  end
151
151
 
152
152
  def test_should_get_video_from_user
153
153
  video = @client.video_by_user("chebyte","FQK1URcxmb4")
154
- assert_equal "<object width=\"425\" height=\"350\">\n <param name=\"movie\" value=\"http://www.youtube.com/v/FQK1URcxmb4&feature=youtube_gdata\"></param>\n <param name=\"wmode\" value=\"transparent\"></param>\n <embed src=\"http://www.youtube.com/v/FQK1URcxmb4&feature=youtube_gdata\" type=\"application/x-shockwave-flash\" \n wmode=\"transparent\" width=\"425\" height=\"350\"></embed>\n</object>\n", video.embed_html
154
+ assert_equal "<object width=\"425\" height=\"350\">\n <param name=\"movie\" value=\"http://www.youtube.com/v/FQK1URcxmb4&feature=youtube_gdata_player\"></param>\n <param name=\"wmode\" value=\"transparent\"></param>\n <embed src=\"http://www.youtube.com/v/FQK1URcxmb4&feature=youtube_gdata_player\" type=\"application/x-shockwave-flash\" \n wmode=\"transparent\" width=\"425\" height=\"350\"></embed>\n</object>\n", video.embed_html
155
155
  assert_valid_video video
156
156
  end
157
157
 
@@ -190,51 +190,45 @@ class TestClient < Test::Unit::TestCase
190
190
  end
191
191
 
192
192
  def test_should_upload_a_video
193
- video_id = @client.video_upload(File.open("test/test.mov"), OPTIONS)
194
- video = @client.video_by_user(ACCOUNT[:user], video_id)
193
+ video = @client.video_upload(File.open("test/test.mov"), OPTIONS)
195
194
  assert_valid_video video
196
- @client.video_delete(video_id)
195
+ @client.video_delete(video.unique_id)
197
196
  end
198
197
 
199
198
  def test_should_update_a_video
200
199
  OPTIONS[:title] = "title changed"
201
- @client.video_update("BhTw20Lr4v8", OPTIONS)
202
- video = @client.video_by("BhTw20Lr4v8")
200
+ video = @client.video_update("iKqJ8z1DPrQ", OPTIONS)
203
201
  assert video.title == "title changed"
204
- OPTIONS[:title] = "maddie"
205
- @client.video_update("BhTw20Lr4v8", OPTIONS)
202
+ OPTIONS[:title] = "test rails"
203
+ @client.video_update("iKqJ8z1DPrQ", OPTIONS)
206
204
  end
207
205
 
208
206
  def test_should_delete_video
209
- video_id = @client.video_upload(File.open("test/test.mov"), OPTIONS)
210
- video = @client.video_by_user(ACCOUNT[:user], video_id)
207
+ video = @client.video_upload(File.open("test/test.mov"), OPTIONS)
211
208
  assert_valid_video video
212
- assert @client.video_delete(video_id)
209
+ assert @client.video_delete(video.unique_id)
213
210
  end
214
211
 
215
212
  def test_should_denied_comments
216
- video_id = @client.video_upload(File.open("test/test.mov"), OPTIONS.merge(:comment => "denied"))
217
- video = @client.video_by_user(ACCOUNT[:user], video_id)
213
+ video = @client.video_upload(File.open("test/test.mov"), OPTIONS.merge(:comment => "denied"))
218
214
  assert_valid_video video
219
- doc = Nokogiri::HTML(open("http://www.youtube.com/watch?v=#{video_id}"))
215
+ doc = Nokogiri::HTML(open("http://www.youtube.com/watch?v=#{video.unique_id}"))
220
216
  doc.css('.comments-disabled').each{|tag| assert (tag.content.strip == "Adding comments has been disabled for this video.")}
221
- @client.video_delete(video_id)
217
+ @client.video_delete(video.unique_id)
222
218
  end
223
219
 
224
220
  def test_should_denied_rate
225
- video_id = @client.video_upload(File.open("test/test.mov"), OPTIONS.merge(:rate => "denied"))
226
- video = @client.video_by_user(ACCOUNT[:user], video_id)
221
+ video = @client.video_upload(File.open("test/test.mov"), OPTIONS.merge(:rate => "denied"))
227
222
  assert_valid_video video
228
- doc = Nokogiri::HTML(open("http://www.youtube.com/watch?v=#{video_id}"))
223
+ doc = Nokogiri::HTML(open("http://www.youtube.com/watch?v=#{video.unique_id}"))
229
224
  doc.css('#watch-like').each{|tag|; assert (tag.attributes["title"].to_s == "Ratings have been disabled for this video.")}
230
- @client.video_delete(video_id)
225
+ @client.video_delete(video.unique_id)
231
226
  end
232
227
 
233
228
  def test_should_denied_embed
234
- video_id = @client.video_upload(File.open("test/test.mov"), OPTIONS.merge(:embed => "denied"))
235
- video = @client.video_by_user(ACCOUNT[:user], video_id)
229
+ video = @client.video_upload(File.open("test/test.mov"), OPTIONS.merge(:embed => "denied"))
236
230
  assert video.noembed
237
- @client.video_delete(video_id)
231
+ @client.video_delete(video.unique_id)
238
232
  end
239
233
 
240
234
  def test_should_add_new_comment
@@ -244,10 +238,33 @@ class TestClient < Test::Unit::TestCase
244
238
  assert comment.match(/test comment/)
245
239
  end
246
240
 
247
- def test_shoul_add_and_del_video_to_favorite
241
+ def test_shoul_add_and_delete_video_to_favorite
248
242
  video_id ="H1TrfM3xbgc"
249
- assert @client.add_favorite(video_id)
250
- assert @client.del_favorite(video_id)
243
+ result = @client.add_favorite(video_id)
244
+ assert result[:code], 201
245
+ sleep 4
246
+ assert @client.delete_favorite(video_id)
247
+ end
248
+
249
+ def test_should_add_and_del_video_from_playlist
250
+ playlist = @client.add_playlist(:title => "youtube_it test!", :description => "test playlist")
251
+ video = @client.add_video_to_playlist(playlist.playlist_id,"iKqJ8z1DPrQ")
252
+ assert_equal video[:code].to_i, 201
253
+ assert @client.delete_video_from_playlist(playlist.playlist_id, video[:playlist_entry_id])
254
+ assert @client.delete_playlist(playlist.playlist_id)
255
+ end
256
+
257
+ def test_should_add_and_del_new_playlist
258
+ result = @client.add_playlist(:title => "youtube_it test!", :description => "test playlist")
259
+ assert result.title, "youtube_it test!"
260
+ assert @client.delete_playlist(result.playlist_id)
261
+ end
262
+
263
+ def test_should_update_playlist
264
+ playlist = @client.add_playlist(:title => "youtube_it test!", :description => "test playlist")
265
+ playlist_updated = @client.update_playlist(playlist.playlist_id, :title => "title changed")
266
+ assert_equal playlist_updated.title, "title changed"
267
+ assert @client.delete_playlist(playlist.playlist_id)
251
268
  end
252
269
 
253
270
  private
@@ -297,7 +314,7 @@ class TestClient < Test::Unit::TestCase
297
314
 
298
315
  assert_instance_of Time, video.updated_at
299
316
  # http://gdata.youtube.com/feeds/videos/IHVaXG1thXM
300
- assert_valid_url video.video_id
317
+ assert_valid_url video.unique_id
301
318
  assert_instance_of Fixnum, video.view_count
302
319
  assert_instance_of Fixnum, video.favorite_count
303
320
 
data/test/test_video.rb CHANGED
@@ -22,17 +22,18 @@ class TestVideo < Test::Unit::TestCase
22
22
  assert(response.total_result_count > 0)
23
23
  assert_instance_of Time, response.updated_at
24
24
  end
25
-
25
+
26
26
  def test_should_have_response_videos
27
27
  video = YouTubeIt::Model::Video.new(:video_id => "http://gdata.youtube.com/feeds/videos/BDqs-OZWw9o")
28
28
  response = video.responses
29
29
 
30
30
  assert_equal "http://gdata.youtube.com/feeds/api/videos/BDqs-OZWw9o/responses", response.feed_id
31
31
  assert_equal 25, response.max_result_count
32
- assert_equal 25, response.videos.length
32
+ assert_equal 24, response.videos.length
33
33
  assert_equal 1, response.offset
34
34
  assert(response.total_result_count > 0)
35
35
  assert_instance_of Time, response.updated_at
36
36
  end
37
-
37
+
38
38
  end
39
+
data/youtube_it.gemspec CHANGED
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{youtube_it}
8
- s.version = "0.0.3"
8
+ s.version = "0.0.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Mauro Torres & Kyle Ginavan"]
12
- s.date = %q{2010-08-12}
13
- s.description = %q{the one stop shop for working with youtube apis}
11
+ s.authors = ["chebyte", "kylejginavan"]
12
+ s.date = %q{2010-10-18}
13
+ s.description = %q{Upload, delete, update, comment on youtube videos all from one gem.}
14
14
  s.email = %q{kylejginavan@gmail.com}
15
15
  s.extra_rdoc_files = [
16
16
  "README.txt"
@@ -43,23 +43,19 @@ Gem::Specification.new do |s|
43
43
  "lib/youtube_it/request/video_upload.rb",
44
44
  "lib/youtube_it/response/video_search.rb",
45
45
  "lib/youtube_it/version.rb",
46
- "pkg/youtube_it-0.0.1.gem",
47
- "pkg/youtube_it-0.0.2.gem",
48
- "pkg/youtube_it-0.0.3.gem",
49
46
  "test/helper.rb",
50
47
  "test/test.mov",
51
48
  "test/test_chain_io.rb",
52
49
  "test/test_client.rb",
53
50
  "test/test_video.rb",
54
51
  "test/test_video_search.rb",
55
- "youtube_it.gemspec",
56
- "youtube_it.tmproj"
52
+ "youtube_it.gemspec"
57
53
  ]
58
54
  s.homepage = %q{http://github.com/kylejginavan/youtube_it}
59
55
  s.rdoc_options = ["--charset=UTF-8"]
60
56
  s.require_paths = ["lib"]
61
57
  s.rubygems_version = %q{1.3.6}
62
- s.summary = %q{the one stop shop for working with youtube apis}
58
+ s.summary = %q{The most complete Ruby wrapper for youtube api's}
63
59
  s.test_files = [
64
60
  "test/helper.rb",
65
61
  "test/test_chain_io.rb",
metadata CHANGED
@@ -5,20 +5,21 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 3
9
- version: 0.0.3
8
+ - 7
9
+ version: 0.0.7
10
10
  platform: ruby
11
11
  authors:
12
- - Mauro Torres & Kyle Ginavan
12
+ - chebyte
13
+ - kylejginavan
13
14
  autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-08-12 00:00:00 -05:00
18
+ date: 2010-10-18 00:00:00 -05:00
18
19
  default_executable:
19
20
  dependencies: []
20
21
 
21
- description: the one stop shop for working with youtube apis
22
+ description: Upload, delete, update, comment on youtube videos all from one gem.
22
23
  email: kylejginavan@gmail.com
23
24
  executables: []
24
25
 
@@ -54,9 +55,6 @@ files:
54
55
  - lib/youtube_it/request/video_upload.rb
55
56
  - lib/youtube_it/response/video_search.rb
56
57
  - lib/youtube_it/version.rb
57
- - pkg/youtube_it-0.0.1.gem
58
- - pkg/youtube_it-0.0.2.gem
59
- - pkg/youtube_it-0.0.3.gem
60
58
  - test/helper.rb
61
59
  - test/test.mov
62
60
  - test/test_chain_io.rb
@@ -64,7 +62,6 @@ files:
64
62
  - test/test_video.rb
65
63
  - test/test_video_search.rb
66
64
  - youtube_it.gemspec
67
- - youtube_it.tmproj
68
65
  has_rdoc: true
69
66
  homepage: http://github.com/kylejginavan/youtube_it
70
67
  licenses: []
@@ -94,7 +91,7 @@ rubyforge_project:
94
91
  rubygems_version: 1.3.6
95
92
  signing_key:
96
93
  specification_version: 3
97
- summary: the one stop shop for working with youtube apis
94
+ summary: The most complete Ruby wrapper for youtube api's
98
95
  test_files:
99
96
  - test/helper.rb
100
97
  - test/test_chain_io.rb
Binary file
Binary file
Binary file
data/youtube_it.tmproj DELETED
@@ -1,27 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>documents</key>
6
- <array>
7
- <dict>
8
- <key>expanded</key>
9
- <true/>
10
- <key>name</key>
11
- <string>youtube_it</string>
12
- <key>regexFolderFilter</key>
13
- <string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
14
- <key>sourceDirectory</key>
15
- <string></string>
16
- </dict>
17
- </array>
18
- <key>fileHierarchyDrawerWidth</key>
19
- <integer>200</integer>
20
- <key>metaData</key>
21
- <dict/>
22
- <key>showFileHierarchyDrawer</key>
23
- <true/>
24
- <key>windowFrame</key>
25
- <string>{{1769, 105}, {1144, 1070}}</string>
26
- </dict>
27
- </plist>