youtube-g 0.4.1 → 0.4.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +24 -0
- data/Manifest.txt +4 -0
- data/README.txt +6 -2
- data/TODO.txt +0 -2
- data/lib/youtube_g.rb +13 -2
- data/lib/youtube_g/client.rb +64 -14
- data/lib/youtube_g/logger.rb +0 -2
- data/lib/youtube_g/model/author.rb +3 -0
- data/lib/youtube_g/model/category.rb +4 -1
- data/lib/youtube_g/model/contact.rb +8 -0
- data/lib/youtube_g/model/content.rb +5 -0
- data/lib/youtube_g/model/playlist.rb +1 -0
- data/lib/youtube_g/model/rating.rb +7 -0
- data/lib/youtube_g/model/thumbnail.rb +7 -0
- data/lib/youtube_g/model/video.rb +116 -28
- data/lib/youtube_g/parser.rb +20 -14
- data/lib/youtube_g/record.rb +1 -1
- data/lib/youtube_g/request/base_search.rb +43 -0
- data/lib/youtube_g/request/standard_search.rb +40 -0
- data/lib/youtube_g/request/user_search.rb +39 -0
- data/lib/youtube_g/request/video_search.rb +46 -115
- data/lib/youtube_g/request/video_upload.rb +130 -0
- data/lib/youtube_g/response/video_search.rb +25 -7
- data/test/test_client.rb +60 -14
- data/test/test_video.rb +25 -1
- data/test/test_video_search.rb +30 -6
- data/youtube-g.gemspec +52 -0
- metadata +55 -52
- data/Rakefile +0 -20
data/lib/youtube_g/parser.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
|
-
require 'cgi'
|
2
|
-
require 'open-uri'
|
3
|
-
require 'rexml/document'
|
4
|
-
|
5
1
|
class YouTubeG
|
6
|
-
module Parser
|
7
|
-
class FeedParser
|
2
|
+
module Parser #:nodoc:
|
3
|
+
class FeedParser #:nodoc:
|
8
4
|
def initialize(url)
|
9
5
|
@url = url
|
10
6
|
end
|
@@ -14,17 +10,16 @@ class YouTubeG
|
|
14
10
|
end
|
15
11
|
end
|
16
12
|
|
17
|
-
class VideoFeedParser < FeedParser
|
13
|
+
class VideoFeedParser < FeedParser #:nodoc:
|
18
14
|
|
19
15
|
def parse_content(content)
|
20
16
|
doc = REXML::Document.new(content)
|
21
17
|
entry = doc.elements["entry"]
|
22
|
-
|
23
18
|
parse_entry(entry)
|
24
19
|
end
|
25
20
|
|
26
21
|
protected
|
27
|
-
def parse_entry(entry)
|
22
|
+
def parse_entry(entry)
|
28
23
|
video_id = entry.elements["id"].text
|
29
24
|
published_at = Time.parse(entry.elements["published"].text)
|
30
25
|
updated_at = Time.parse(entry.elements["updated"].text)
|
@@ -91,9 +86,15 @@ class YouTubeG
|
|
91
86
|
:average => rating_element.attributes["average"].to_f)
|
92
87
|
end
|
93
88
|
|
94
|
-
view_count = entry.elements["yt:statistics"].attributes["viewCount"].to_i
|
89
|
+
view_count = (el = entry.elements["yt:statistics"]) ? el.attributes["viewCount"].to_i : 0
|
95
90
|
|
96
91
|
noembed = entry.elements["yt:noembed"] ? true : false
|
92
|
+
racy = entry.elements["media:rating"] ? true : false
|
93
|
+
|
94
|
+
if where = entry.elements["georss:where"]
|
95
|
+
position = where.elements["gml:Point"].elements["gml:pos"].text
|
96
|
+
latitude, longitude = position.split(" ")
|
97
|
+
end
|
97
98
|
|
98
99
|
YouTubeG::Model::Video.new(
|
99
100
|
:video_id => video_id,
|
@@ -111,10 +112,15 @@ class YouTubeG
|
|
111
112
|
:thumbnails => thumbnails,
|
112
113
|
:rating => rating,
|
113
114
|
:view_count => view_count,
|
114
|
-
:noembed => noembed
|
115
|
+
:noembed => noembed,
|
116
|
+
:racy => racy,
|
117
|
+
:where => where,
|
118
|
+
:position => position,
|
119
|
+
:latitude => latitude,
|
120
|
+
:longitude => longitude)
|
115
121
|
end
|
116
122
|
|
117
|
-
def parse_media_content (media_content_element)
|
123
|
+
def parse_media_content (media_content_element)
|
118
124
|
content_url = media_content_element.attributes["url"]
|
119
125
|
format_code = media_content_element.attributes["yt:format"].to_i
|
120
126
|
format = YouTubeG::Model::Video::Format.by_code(format_code)
|
@@ -131,10 +137,10 @@ class YouTubeG
|
|
131
137
|
end
|
132
138
|
end
|
133
139
|
|
134
|
-
class VideosFeedParser < VideoFeedParser
|
140
|
+
class VideosFeedParser < VideoFeedParser #:nodoc:
|
135
141
|
|
136
142
|
private
|
137
|
-
def parse_content(content)
|
143
|
+
def parse_content(content) #:nodoc:
|
138
144
|
doc = REXML::Document.new(content)
|
139
145
|
feed = doc.elements["feed"]
|
140
146
|
|
data/lib/youtube_g/record.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Request #:nodoc:
|
3
|
+
class BaseSearch #:nodoc:
|
4
|
+
attr_reader :url
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def base_url #:nodoc:
|
9
|
+
"http://gdata.youtube.com/feeds/api/"
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_instance_variables( variables ) #:nodoc:
|
13
|
+
variables.each do |key, value|
|
14
|
+
name = key.to_s
|
15
|
+
instance_variable_set("@#{name}", value) if respond_to?(name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_query_params(params) #:nodoc:
|
20
|
+
# nothing to do if there are no params
|
21
|
+
return '' if (!params || params.empty?)
|
22
|
+
|
23
|
+
# build up the query param string, tacking on every key/value
|
24
|
+
# pair for which the value is non-nil
|
25
|
+
u = '?'
|
26
|
+
item_count = 0
|
27
|
+
params.keys.sort.each do |key|
|
28
|
+
value = params[key]
|
29
|
+
next if value.nil?
|
30
|
+
|
31
|
+
u << '&' if (item_count > 0)
|
32
|
+
u << "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
33
|
+
item_count += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# if we found no non-nil values, we've got no params so just
|
37
|
+
# return an empty string
|
38
|
+
(item_count == 0) ? '' : u
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Request #:nodoc:
|
3
|
+
class StandardSearch < BaseSearch #:nodoc:
|
4
|
+
attr_reader :max_results # max_results
|
5
|
+
attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
|
6
|
+
attr_reader :offset # start-index
|
7
|
+
attr_reader :time # time
|
8
|
+
|
9
|
+
TYPES = [ :top_rated, :top_favorites, :most_viewed, :most_popular,
|
10
|
+
:most_recent, :most_discussed, :most_linked, :most_responded,
|
11
|
+
:recently_featured, :watch_on_mobile ]
|
12
|
+
|
13
|
+
def initialize(type, options={})
|
14
|
+
if TYPES.include?(type)
|
15
|
+
@max_results, @order_by, @offset, @time = nil
|
16
|
+
set_instance_variables(options)
|
17
|
+
@url = base_url + type.to_s << build_query_params(to_youtube_params)
|
18
|
+
else
|
19
|
+
raise "Invalid type, must be one of: #{ TYPES.map { |t| t.to_s }.join(", ") }"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def base_url #:nodoc:
|
26
|
+
super << "standardfeeds/"
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_youtube_params #:nodoc:
|
30
|
+
{
|
31
|
+
'max-results' => @max_results,
|
32
|
+
'orderby' => @order_by,
|
33
|
+
'start-index' => @offset,
|
34
|
+
'time' => @time
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Request #:nodoc:
|
3
|
+
class UserSearch < BaseSearch #:nodoc:
|
4
|
+
attr_reader :max_results # max_results
|
5
|
+
attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
|
6
|
+
attr_reader :offset # start-index
|
7
|
+
|
8
|
+
def initialize(params, options={})
|
9
|
+
@max_results, @order_by, @offset = nil
|
10
|
+
@url = base_url
|
11
|
+
|
12
|
+
if params == :favorites
|
13
|
+
@url << "#{options[:user]}/favorites"
|
14
|
+
set_instance_variables(options)
|
15
|
+
elsif params[:user]
|
16
|
+
@url << "#{params[:user]}/uploads"
|
17
|
+
set_instance_variables(params)
|
18
|
+
end
|
19
|
+
|
20
|
+
@url << build_query_params(to_youtube_params)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def base_url #:nodoc:
|
26
|
+
super << "users/"
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_youtube_params #:nodoc:
|
30
|
+
{
|
31
|
+
'max-results' => @max_results,
|
32
|
+
'orderby' => @order_by,
|
33
|
+
'start-index' => @offset
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -1,87 +1,37 @@
|
|
1
1
|
class YouTubeG
|
2
|
-
|
3
|
-
|
4
|
-
module Request
|
5
|
-
|
6
|
-
class BaseSearch
|
7
|
-
attr_reader :url
|
8
|
-
|
9
|
-
def base_url
|
10
|
-
"http://gdata.youtube.com/feeds/api/"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class UserSearch < BaseSearch
|
15
|
-
|
16
|
-
def initialize(params, options={})
|
17
|
-
@url = base_url
|
18
|
-
return @url << "#{options[:user]}/favorites" if params == :favorites
|
19
|
-
@url << "#{params[:user]}/uploads" if params[:user]
|
20
|
-
end
|
21
|
-
|
22
|
-
def base_url
|
23
|
-
super << "users/"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
class StandardSearch < BaseSearch
|
28
|
-
TYPES = [ :most_viewed, :top_rated, :recently_featured, :watch_on_mobile ]
|
29
|
-
TIMES = [ :all_time, :today, :this_week, :this_month ]
|
30
|
-
|
31
|
-
def initialize(type, options={})
|
32
|
-
if TYPES.include?(type)
|
33
|
-
@url = base_url << type.to_s
|
34
|
-
@url << "?time=#{CGI.escape(options.delete(:time).to_s)}" if TIMES.include?(options[:time])
|
35
|
-
else
|
36
|
-
raise "Invalid type, must be one of: #{ TYPES.map { |t| t.to_s }.join(", ") }"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def base_url
|
41
|
-
super << "standardfeeds/"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class VideoSearch < BaseSearch
|
2
|
+
module Request #:nodoc:
|
3
|
+
class VideoSearch < BaseSearch #:nodoc:
|
46
4
|
# From here: http://code.google.com/apis/youtube/reference.html#yt_format
|
47
5
|
ONLY_EMBEDDABLE = 5
|
48
6
|
|
49
7
|
attr_reader :max_results # max_results
|
50
|
-
attr_reader :order_by # orderby, ([relevance], viewCount)
|
8
|
+
attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
|
51
9
|
attr_reader :offset # start-index
|
52
10
|
attr_reader :query # vq
|
53
11
|
attr_reader :response_format # alt, ([atom], rss, json)
|
54
12
|
attr_reader :tags # /-/tag1/tag2
|
55
13
|
attr_reader :categories # /-/Category1/Category2
|
56
14
|
attr_reader :video_format # format (1=mobile devices)
|
15
|
+
attr_reader :racy # racy ([exclude], include)
|
16
|
+
attr_reader :author
|
57
17
|
|
58
18
|
def initialize(params={})
|
59
|
-
#
|
60
|
-
return if params.nil?
|
61
|
-
|
62
|
-
# initialize our various member data to avoid warnings and so we'll
|
19
|
+
# Initialize our various member data to avoid warnings and so we'll
|
63
20
|
# automatically fall back to the youtube api defaults
|
64
|
-
@max_results
|
65
|
-
@
|
66
|
-
@
|
67
|
-
@
|
68
|
-
@response_format = nil
|
69
|
-
@video_format = nil
|
70
|
-
|
71
|
-
# build up the url corresponding to this request
|
21
|
+
@max_results, @order_by,
|
22
|
+
@offset, @query,
|
23
|
+
@response_format, @video_format,
|
24
|
+
@racy, @author = nil
|
72
25
|
@url = base_url
|
73
26
|
|
74
|
-
#
|
27
|
+
# Return a single video (base_url + /T7YazwP8GtY)
|
75
28
|
return @url << "/" << params[:video_id] if params[:video_id]
|
76
29
|
|
77
30
|
@url << "/-/" if (params[:categories] || params[:tags])
|
78
31
|
@url << categories_to_params(params.delete(:categories)) if params[:categories]
|
79
32
|
@url << tags_to_params(params.delete(:tags)) if params[:tags]
|
80
33
|
|
81
|
-
params
|
82
|
-
name = key.to_s
|
83
|
-
instance_variable_set("@#{name}", value) if respond_to?(name)
|
84
|
-
end
|
34
|
+
set_instance_variables(params)
|
85
35
|
|
86
36
|
if( params[ :only_embeddable ] )
|
87
37
|
@video_format = ONLY_EMBEDDABLE
|
@@ -90,72 +40,53 @@ class YouTubeG
|
|
90
40
|
@url << build_query_params(to_youtube_params)
|
91
41
|
end
|
92
42
|
|
93
|
-
|
43
|
+
private
|
44
|
+
|
45
|
+
def base_url #:nodoc:
|
94
46
|
super << "videos"
|
95
47
|
end
|
96
48
|
|
97
|
-
def to_youtube_params
|
49
|
+
def to_youtube_params #:nodoc:
|
98
50
|
{
|
99
51
|
'max-results' => @max_results,
|
100
52
|
'orderby' => @order_by,
|
101
53
|
'start-index' => @offset,
|
102
54
|
'vq' => @query,
|
103
55
|
'alt' => @response_format,
|
104
|
-
'format' => @video_format
|
56
|
+
'format' => @video_format,
|
57
|
+
'racy' => @racy,
|
58
|
+
'author' => @author
|
105
59
|
}
|
106
60
|
end
|
107
|
-
|
108
|
-
private
|
109
|
-
# Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
|
110
|
-
# Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
|
111
|
-
# or like: categories => [:news, :sports]
|
112
|
-
def categories_to_params(categories)
|
113
|
-
if categories.respond_to?(:keys) and categories.respond_to?(:[])
|
114
|
-
s = ""
|
115
|
-
s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
|
116
|
-
s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
|
117
|
-
s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
|
118
|
-
s
|
119
|
-
else
|
120
|
-
categories.map { |c| c.to_s.capitalize }.join("/") << '/'
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
|
125
|
-
# or tags => [:football, :soccer]
|
126
|
-
def tags_to_params(tags)
|
127
|
-
if tags.respond_to?(:keys) and tags.respond_to?(:[])
|
128
|
-
s = ""
|
129
|
-
s << tags[:either].map { |t| CGI.escape(t.to_s) }.join("%7C") << '/' if tags[:either]
|
130
|
-
s << tags[:include].map { |t| CGI.escape(t.to_s) }.join("/") << '/' if tags[:include]
|
131
|
-
s << ("-" << tags[:exclude].map { |t| CGI.escape(t.to_s) }.join("/-")) << '/' if tags[:exclude]
|
132
|
-
s
|
133
|
-
else
|
134
|
-
tags.map { |t| CGI.escape(t.to_s) }.join("/") << '/'
|
135
|
-
end
|
136
|
-
end
|
137
61
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
u << '&' if (item_count > 0)
|
151
|
-
u << "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
152
|
-
item_count += 1
|
153
|
-
end
|
154
|
-
|
155
|
-
# if we found no non-nil values, we've got no params so just
|
156
|
-
# return an empty string
|
157
|
-
(item_count == 0) ? '' : u
|
62
|
+
# Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
|
63
|
+
# Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
|
64
|
+
# or like: categories => [:news, :sports]
|
65
|
+
def categories_to_params(categories) #:nodoc:
|
66
|
+
if categories.respond_to?(:keys) and categories.respond_to?(:[])
|
67
|
+
s = ""
|
68
|
+
s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
|
69
|
+
s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
|
70
|
+
s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
|
71
|
+
s
|
72
|
+
else
|
73
|
+
categories.map { |c| c.to_s.capitalize }.join("/") << '/'
|
158
74
|
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
|
78
|
+
# or tags => [:football, :soccer]
|
79
|
+
def tags_to_params(tags) #:nodoc:
|
80
|
+
if tags.respond_to?(:keys) and tags.respond_to?(:[])
|
81
|
+
s = ""
|
82
|
+
s << tags[:either].map { |t| CGI.escape(t.to_s) }.join("%7C") << '/' if tags[:either]
|
83
|
+
s << tags[:include].map { |t| CGI.escape(t.to_s) }.join("/") << '/' if tags[:include]
|
84
|
+
s << ("-" << tags[:exclude].map { |t| CGI.escape(t.to_s) }.join("/-")) << '/' if tags[:exclude]
|
85
|
+
s
|
86
|
+
else
|
87
|
+
tags.map { |t| CGI.escape(t.to_s) }.join("/") << '/'
|
88
|
+
end
|
89
|
+
end
|
159
90
|
|
160
91
|
end
|
161
92
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
|
3
|
+
module Upload
|
4
|
+
class UploadError < Exception; end
|
5
|
+
class AuthenticationError < Exception; end
|
6
|
+
|
7
|
+
# require 'youtube_g'
|
8
|
+
#
|
9
|
+
# uploader = YouTubeG::Upload::VideoUpload.new("user", "pass", "dev-key")
|
10
|
+
# uploader.upload File.open("test.m4v"), :title => 'test',
|
11
|
+
# :description => 'cool vid d00d',
|
12
|
+
# :category => 'People',
|
13
|
+
# :keywords => %w[cool blah test]
|
14
|
+
|
15
|
+
class VideoUpload
|
16
|
+
|
17
|
+
def initialize user, pass, dev_key, client_id = 'youtube_g'
|
18
|
+
@user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Upload "data" to youtube, where data is either an IO object or
|
23
|
+
# raw file data.
|
24
|
+
# The hash keys for opts (which specify video info) are as follows:
|
25
|
+
# :mime_type
|
26
|
+
# :filename
|
27
|
+
# :title
|
28
|
+
# :description
|
29
|
+
# :category
|
30
|
+
# :keywords
|
31
|
+
# :private
|
32
|
+
# Specifying :private will make the video private, otherwise it will be public.
|
33
|
+
#
|
34
|
+
# When one of the fields is invalid according to YouTube,
|
35
|
+
# an UploadError will be returned. Its message contains a list of newline separated
|
36
|
+
# errors, containing the key and its error code.
|
37
|
+
#
|
38
|
+
# When the authentication credentials are incorrect, an AuthenticationError will be raised.
|
39
|
+
def upload data, opts = {}
|
40
|
+
data = data.respond_to?(:read) ? data.read : data
|
41
|
+
@opts = { :mime_type => 'video/mp4',
|
42
|
+
:filename => Digest::MD5.hexdigest(data),
|
43
|
+
:title => '',
|
44
|
+
:description => '',
|
45
|
+
:category => '',
|
46
|
+
:keywords => [] }.merge(opts)
|
47
|
+
|
48
|
+
uploadBody = generate_upload_body(boundary, video_xml, data)
|
49
|
+
|
50
|
+
uploadHeader = {
|
51
|
+
"Authorization" => "GoogleLogin auth=#{auth_token}",
|
52
|
+
"X-GData-Client" => "#{@client_id}",
|
53
|
+
"X-GData-Key" => "key=#{@dev_key}",
|
54
|
+
"Slug" => "#{@opts[:filename]}",
|
55
|
+
"Content-Type" => "multipart/related; boundary=#{boundary}",
|
56
|
+
"Content-Length" => "#{uploadBody.length}"
|
57
|
+
}
|
58
|
+
|
59
|
+
Net::HTTP.start(base_url) do |upload|
|
60
|
+
response = upload.post('/feeds/api/users/' << @user << '/uploads', uploadBody, uploadHeader)
|
61
|
+
if response.code.to_i == 403
|
62
|
+
raise AuthenticationError, response.body[/<TITLE>(.+)<\/TITLE>/, 1]
|
63
|
+
elsif response.code.to_i != 201
|
64
|
+
upload_error = ''
|
65
|
+
xml = REXML::Document.new(response.body)
|
66
|
+
errors = xml.elements["//errors"]
|
67
|
+
errors.each do |error|
|
68
|
+
location = error.elements["location"].text[/media:group\/media:(.*)\/text\(\)/,1]
|
69
|
+
code = error.elements["code"].text
|
70
|
+
upload_error << sprintf("%s: %s\r\n", location, code)
|
71
|
+
end
|
72
|
+
raise UploadError, upload_error
|
73
|
+
end
|
74
|
+
xml = REXML::Document.new(response.body)
|
75
|
+
return xml.elements["//id"].text[/videos\/(.+)/, 1]
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def base_url #:nodoc:
|
83
|
+
"uploads.gdata.youtube.com"
|
84
|
+
end
|
85
|
+
|
86
|
+
def boundary #:nodoc:
|
87
|
+
"An43094fu"
|
88
|
+
end
|
89
|
+
|
90
|
+
def auth_token #:nodoc:
|
91
|
+
unless @auth_token
|
92
|
+
http = Net::HTTP.new("www.google.com", 443)
|
93
|
+
http.use_ssl = true
|
94
|
+
body = "Email=#{CGI::escape @user}&Passwd=#{CGI::escape @pass}&service=youtube&source=#{CGI::escape @client_id}"
|
95
|
+
response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
|
96
|
+
raise UploadError, response.body[/Error=(.+)/,1] if response.code.to_i != 200
|
97
|
+
@auth_token = response.body[/Auth=(.+)/, 1]
|
98
|
+
|
99
|
+
end
|
100
|
+
@auth_token
|
101
|
+
end
|
102
|
+
|
103
|
+
def video_xml #:nodoc:
|
104
|
+
video_xml = ''
|
105
|
+
video_xml << '<?xml version="1.0"?>'
|
106
|
+
video_xml << '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">'
|
107
|
+
video_xml << '<media:group>'
|
108
|
+
video_xml << '<media:title type="plain">%s</media:title>' % @opts[:title]
|
109
|
+
video_xml << '<media:description type="plain">%s</media:description>' % @opts[:description]
|
110
|
+
video_xml << '<media:keywords>%s</media:keywords>' % @opts[:keywords].join(",")
|
111
|
+
video_xml << '<media:category scheme="http://gdata.youtube.com/schemas/2007/categories.cat">%s</media:category>' % @opts[:category]
|
112
|
+
video_xml << '<yt:private/>' if @opts[:private]
|
113
|
+
video_xml << '</media:group>'
|
114
|
+
video_xml << '</entry>'
|
115
|
+
end
|
116
|
+
|
117
|
+
def generate_upload_body(boundary, video_xml, data) #:nodoc:
|
118
|
+
uploadBody = ""
|
119
|
+
uploadBody << "--#{boundary}\r\n"
|
120
|
+
uploadBody << "Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n"
|
121
|
+
uploadBody << video_xml
|
122
|
+
uploadBody << "\r\n--#{boundary}\r\n"
|
123
|
+
uploadBody << "Content-Type: #{@opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n"
|
124
|
+
uploadBody << data
|
125
|
+
uploadBody << "\r\n--#{boundary}--\r\n"
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|