youtube_it 0.0.3 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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>