FiXato-youtube-g 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,165 @@
1
+ require 'cgi'
2
+ require 'open-uri'
3
+ require 'rexml/document'
4
+
5
+ class YouTubeG
6
+ module Parser
7
+ class FeedParser
8
+ def initialize(url)
9
+ @url = url
10
+ end
11
+
12
+ def parse
13
+ parse_content open(@url).read
14
+ end
15
+ end
16
+
17
+ class VideoFeedParser < FeedParser
18
+
19
+ def parse_content(content)
20
+ doc = REXML::Document.new(content)
21
+ entry = doc.elements["entry"]
22
+
23
+ parse_entry(entry)
24
+ end
25
+
26
+ protected
27
+ def parse_entry(entry)
28
+ video_id = entry.elements["id"].text
29
+ published_at = Time.parse(entry.elements["published"].text)
30
+ updated_at = Time.parse(entry.elements["updated"].text)
31
+
32
+ # parse the category and keyword lists
33
+ categories = []
34
+ keywords = []
35
+ entry.elements.each("category") do |category|
36
+ # determine if it's really a category, or just a keyword
37
+ scheme = category.attributes["scheme"]
38
+ if (scheme =~ /\/categories\.cat$/)
39
+ # it's a category
40
+ categories << YouTubeG::Model::Category.new(
41
+ :term => category.attributes["term"],
42
+ :label => category.attributes["label"])
43
+
44
+ elsif (scheme =~ /\/keywords\.cat$/)
45
+ # it's a keyword
46
+ keywords << category.attributes["term"]
47
+ end
48
+ end
49
+
50
+ title = entry.elements["title"].text
51
+ html_content = entry.elements["content"].text
52
+
53
+ # parse the author
54
+ author_element = entry.elements["author"]
55
+ author = nil
56
+ if author_element
57
+ author = YouTubeG::Model::Author.new(
58
+ :name => author_element.elements["name"].text,
59
+ :uri => author_element.elements["uri"].text)
60
+ end
61
+
62
+ media_group = entry.elements["media:group"]
63
+ description = media_group.elements["media:description"].text
64
+ duration = media_group.elements["yt:duration"].attributes["seconds"].to_i
65
+
66
+ media_content = []
67
+ media_group.elements.each("media:content") do |mce|
68
+ media_content << parse_media_content(mce)
69
+ end
70
+
71
+ player_url = media_group.elements["media:player"].attributes["url"]
72
+
73
+ # parse thumbnails
74
+ thumbnails = []
75
+ media_group.elements.each("media:thumbnail") do |thumb_element|
76
+ # TODO: convert time HH:MM:ss string to seconds?
77
+ thumbnails << YouTubeG::Model::Thumbnail.new(
78
+ :url => thumb_element.attributes["url"],
79
+ :height => thumb_element.attributes["height"].to_i,
80
+ :width => thumb_element.attributes["width"].to_i,
81
+ :time => thumb_element.attributes["time"])
82
+ end
83
+
84
+ rating_element = entry.elements["gd:rating"]
85
+ rating = nil
86
+ if rating_element
87
+ rating = YouTubeG::Model::Rating.new(
88
+ :min => rating_element.attributes["min"].to_i,
89
+ :max => rating_element.attributes["max"].to_i,
90
+ :rater_count => rating_element.attributes["numRaters"].to_i,
91
+ :average => rating_element.attributes["average"].to_f)
92
+ end
93
+
94
+ view_count = (el = entry.elements["yt:statistics"]) ? el.attributes["viewCount"].to_i : 0
95
+
96
+ noembed = entry.elements["yt:noembed"] ? true : false
97
+ racy = entry.elements["yt:racy"] ? true : false
98
+
99
+ YouTubeG::Model::Video.new(
100
+ :video_id => video_id,
101
+ :published_at => published_at,
102
+ :updated_at => updated_at,
103
+ :categories => categories,
104
+ :keywords => keywords,
105
+ :title => title,
106
+ :html_content => html_content,
107
+ :author => author,
108
+ :description => description,
109
+ :duration => duration,
110
+ :media_content => media_content,
111
+ :player_url => player_url,
112
+ :thumbnails => thumbnails,
113
+ :rating => rating,
114
+ :view_count => view_count,
115
+ :noembed => noembed,
116
+ :racy => racy)
117
+ end
118
+
119
+ def parse_media_content (media_content_element)
120
+ content_url = media_content_element.attributes["url"]
121
+ format_code = media_content_element.attributes["yt:format"].to_i
122
+ format = YouTubeG::Model::Video::Format.by_code(format_code)
123
+ duration = media_content_element.attributes["duration"].to_i
124
+ mime_type = media_content_element.attributes["type"]
125
+ default = (media_content_element.attributes["isDefault"] == "true")
126
+
127
+ YouTubeG::Model::Content.new(
128
+ :url => content_url,
129
+ :format => format,
130
+ :duration => duration,
131
+ :mime_type => mime_type,
132
+ :default => default)
133
+ end
134
+ end
135
+
136
+ class VideosFeedParser < VideoFeedParser
137
+
138
+ private
139
+ def parse_content(content)
140
+ doc = REXML::Document.new(content)
141
+ feed = doc.elements["feed"]
142
+
143
+ feed_id = feed.elements["id"].text
144
+ updated_at = Time.parse(feed.elements["updated"].text)
145
+ total_result_count = feed.elements["openSearch:totalResults"].text.to_i
146
+ offset = feed.elements["openSearch:startIndex"].text.to_i
147
+ max_result_count = feed.elements["openSearch:itemsPerPage"].text.to_i
148
+
149
+ videos = []
150
+ feed.elements.each("entry") do |entry|
151
+ videos << parse_entry(entry)
152
+ end
153
+
154
+ YouTubeG::Response::VideoSearch.new(
155
+ :feed_id => feed_id,
156
+ :updated_at => updated_at,
157
+ :total_result_count => total_result_count,
158
+ :offset => offset,
159
+ :max_result_count => max_result_count,
160
+ :videos => videos)
161
+ end
162
+ end
163
+
164
+ end
165
+ end
@@ -0,0 +1,12 @@
1
+ class YouTubeG
2
+ class Record
3
+ def initialize (params)
4
+ return if params.nil?
5
+
6
+ params.each do |key, value|
7
+ name = key.to_s
8
+ instance_variable_set("@#{name}", value) if respond_to?(name)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,168 @@
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
46
+ # From here: http://code.google.com/apis/youtube/reference.html#yt_format
47
+ ONLY_EMBEDDABLE = 5
48
+
49
+ attr_reader :max_results # max_results
50
+ attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
51
+ attr_reader :offset # start-index
52
+ attr_reader :query # vq
53
+ attr_reader :response_format # alt, ([atom], rss, json)
54
+ attr_reader :tags # /-/tag1/tag2
55
+ attr_reader :categories # /-/Category1/Category2
56
+ attr_reader :video_format # format (1=mobile devices)
57
+ attr_reader :racy # racy ([exclude], include)
58
+ attr_reader :author
59
+
60
+ def initialize(params={})
61
+ # XXX I think we want to delete the line below
62
+ return if params.nil?
63
+
64
+ # initialize our various member data to avoid warnings and so we'll
65
+ # automatically fall back to the youtube api defaults
66
+ @max_results = nil
67
+ @order_by = nil
68
+ @offset = nil
69
+ @query = nil
70
+ @response_format = nil
71
+ @video_format = nil
72
+ @racy = nil
73
+ @author = nil
74
+
75
+ # build up the url corresponding to this request
76
+ @url = base_url
77
+
78
+ # http://gdata.youtube.com/feeds/videos/T7YazwP8GtY
79
+ return @url << "/" << params[:video_id] if params[:video_id]
80
+
81
+ @url << "/-/" if (params[:categories] || params[:tags])
82
+ @url << categories_to_params(params.delete(:categories)) if params[:categories]
83
+ @url << tags_to_params(params.delete(:tags)) if params[:tags]
84
+
85
+ params.each do |key, value|
86
+ name = key.to_s
87
+ instance_variable_set("@#{name}", value) if respond_to?(name)
88
+ end
89
+
90
+ if( params[ :only_embeddable ] )
91
+ @video_format = ONLY_EMBEDDABLE
92
+ end
93
+
94
+ @url << build_query_params(to_youtube_params)
95
+ end
96
+
97
+ def base_url
98
+ super << "videos"
99
+ end
100
+
101
+ def to_youtube_params
102
+ {
103
+ 'max-results' => @max_results,
104
+ 'orderby' => @order_by,
105
+ 'start-index' => @offset,
106
+ 'vq' => @query,
107
+ 'alt' => @response_format,
108
+ 'format' => @video_format,
109
+ 'racy' => @racy,
110
+ 'author' => @author
111
+ }
112
+ end
113
+
114
+ private
115
+ # Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
116
+ # Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
117
+ # or like: categories => [:news, :sports]
118
+ def categories_to_params(categories)
119
+ if categories.respond_to?(:keys) and categories.respond_to?(:[])
120
+ s = ""
121
+ s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
122
+ s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
123
+ s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
124
+ s
125
+ else
126
+ categories.map { |c| c.to_s.capitalize }.join("/") << '/'
127
+ end
128
+ end
129
+
130
+ # Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
131
+ # or tags => [:football, :soccer]
132
+ def tags_to_params(tags)
133
+ if tags.respond_to?(:keys) and tags.respond_to?(:[])
134
+ s = ""
135
+ s << tags[:either].map { |t| CGI.escape(t.to_s) }.join("%7C") << '/' if tags[:either]
136
+ s << tags[:include].map { |t| CGI.escape(t.to_s) }.join("/") << '/' if tags[:include]
137
+ s << ("-" << tags[:exclude].map { |t| CGI.escape(t.to_s) }.join("/-")) << '/' if tags[:exclude]
138
+ s
139
+ else
140
+ tags.map { |t| CGI.escape(t.to_s) }.join("/") << '/'
141
+ end
142
+ end
143
+
144
+ def build_query_params(params)
145
+ # nothing to do if there are no params
146
+ return '' if (!params || params.empty?)
147
+
148
+ # build up the query param string, tacking on every key/value
149
+ # pair for which the value is non-nil
150
+ u = '?'
151
+ item_count = 0
152
+ params.keys.each do |key|
153
+ value = params[key]
154
+ next if value.nil?
155
+
156
+ u << '&' if (item_count > 0)
157
+ u << "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
158
+ item_count += 1
159
+ end
160
+
161
+ # if we found no non-nil values, we've got no params so just
162
+ # return an empty string
163
+ (item_count == 0) ? '' : u
164
+ end
165
+
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,112 @@
1
+ require 'net/https'
2
+ require 'digest/md5'
3
+ require 'rexml/document'
4
+ require 'cgi'
5
+
6
+ class YouTubeG
7
+
8
+ module Upload
9
+ class UploadError < Exception; end
10
+
11
+ # require 'youtube_g'
12
+ #
13
+ # uploader = YouTubeG::Upload::VideoUpload.new("user", "pass", "dev-key")
14
+ # uploader.upload File.open("test.m4v"), :title => 'test',
15
+ # :description => 'cool vid d00d',
16
+ # :category => 'People',
17
+ # :keywords => %w[cool blah test]
18
+
19
+ class VideoUpload
20
+
21
+ def initialize user, pass, dev_key, client_id = 'youtube_g'
22
+ @user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
23
+ end
24
+
25
+ #
26
+ # Upload "data" to youtube, where data is either an IO object or
27
+ # raw file data.
28
+ # The hash keys for opts (which specify video info) are as follows:
29
+ # :mime_type
30
+ # :filename
31
+ # :title
32
+ # :description
33
+ # :category
34
+ # :keywords
35
+ #
36
+
37
+ def upload data, opts = {}
38
+ data = data.respond_to?(:read) ? data.read : data
39
+ @opts = { :mime_type => 'video/mp4',
40
+ :filename => Digest::MD5.hexdigest(data),
41
+ :title => '',
42
+ :description => '',
43
+ :category => '',
44
+ :keywords => [] }.merge(opts)
45
+
46
+ uploadBody = generate_upload_body(boundary, video_xml, data)
47
+
48
+ uploadHeader = {
49
+ "Authorization" => "GoogleLogin auth=#{auth_token}",
50
+ "X-GData-Client" => "#{@client_id}",
51
+ "X-GData-Key" => "key=#{@dev_key}",
52
+ "Slug" => "#{@opts[:filename]}",
53
+ "Content-Type" => "multipart/related; boundary=#{boundary}",
54
+ "Content-Length" => "#{uploadBody.length}"
55
+ }
56
+
57
+ Net::HTTP.start(base_url) do |upload|
58
+ response = upload.post('/feeds/api/users/' << @user << '/uploads', uploadBody, uploadHeader)
59
+ xml = REXML::Document.new(response.body)
60
+ return xml.elements["//id"].text[/videos\/(.+)/, 1]
61
+ end
62
+
63
+ end
64
+
65
+ private
66
+
67
+ def base_url
68
+ "uploads.gdata.youtube.com"
69
+ end
70
+
71
+ def boundary
72
+ "An43094fu"
73
+ end
74
+
75
+ def auth_token
76
+ unless @auth_token
77
+ http = Net::HTTP.new("www.google.com", 443)
78
+ http.use_ssl = true
79
+ body = "Email=#{CGI::escape @user}&Passwd=#{CGI::escape @pass}&service=youtube&source=#{CGI::escape @client_id}"
80
+ response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
81
+ raise UploadError, response.body[/Error=(.+)/,1] if response.code.to_i != 200
82
+ @auth_token = response.body[/Auth=(.+)/, 1]
83
+
84
+ end
85
+ @auth_token
86
+ end
87
+
88
+ def video_xml
89
+ %[<?xml version="1.0"?>
90
+ <entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">
91
+ <media:group>
92
+ <media:title type="plain">#{@opts[:title]}</media:title>
93
+ <media:description type="plain">#{@opts[:description]}</media:description>
94
+ <media:category scheme="http://gdata.youtube.com/schemas/2007/categories.cat">#{@opts[:category]}</media:category>
95
+ <media:keywords>#{@opts[:keywords].join ","}</media:keywords>
96
+ </media:group></entry> ]
97
+ end
98
+
99
+ def generate_upload_body(boundary, video_xml, data)
100
+ uploadBody = ""
101
+ uploadBody << "--#{boundary}\r\n"
102
+ uploadBody << "Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n"
103
+ uploadBody << video_xml
104
+ uploadBody << "\r\n--#{boundary}\r\n"
105
+ uploadBody << "Content-Type: #{@opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n"
106
+ uploadBody << data
107
+ uploadBody << "\r\n--#{boundary}--\r\n"
108
+ end
109
+
110
+ end
111
+ end
112
+ end