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.
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  class YouTubeG
2
- class Record
2
+ class Record #:nodoc:
3
3
  def initialize (params)
4
4
  return if params.nil?
5
5
 
@@ -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
- # The goal of the classes in this module is to build the request URLs for each type of search
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
- # XXX I think we want to delete the line below
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 = nil
65
- @order_by = nil
66
- @offset = nil
67
- @query = nil
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
- # http://gdata.youtube.com/feeds/videos/T7YazwP8GtY
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.each do |key, value|
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
- def base_url
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
- def build_query_params(params)
139
- # nothing to do if there are no params
140
- return '' if (!params || params.empty?)
141
-
142
- # build up the query param string, tacking on every key/value
143
- # pair for which the value is non-nil
144
- u = '?'
145
- item_count = 0
146
- params.keys.each do |key|
147
- value = params[key]
148
- next if value.nil?
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