msp-youtube-g 0.4.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +30 -0
- data/Manifest.txt +25 -0
- data/README.txt +81 -0
- data/Rakefile +20 -0
- data/TODO.txt +18 -0
- data/lib/youtube_g/client.rb +36 -0
- data/lib/youtube_g/logger.rb +27 -0
- data/lib/youtube_g/model/author.rb +8 -0
- data/lib/youtube_g/model/category.rb +8 -0
- data/lib/youtube_g/model/contact.rb +8 -0
- data/lib/youtube_g/model/content.rb +13 -0
- data/lib/youtube_g/model/playlist.rb +7 -0
- data/lib/youtube_g/model/rating.rb +10 -0
- data/lib/youtube_g/model/thumbnail.rb +10 -0
- data/lib/youtube_g/model/user.rb +20 -0
- data/lib/youtube_g/model/video.rb +106 -0
- data/lib/youtube_g/parser.rb +165 -0
- data/lib/youtube_g/record.rb +12 -0
- data/lib/youtube_g/request/video_search.rb +168 -0
- data/lib/youtube_g/request/video_upload.rb +126 -0
- data/lib/youtube_g/response/video_search.rb +23 -0
- data/lib/youtube_g.rb +19 -0
- data/test/test_client.rb +226 -0
- data/test/test_video.rb +30 -0
- data/test/test_video_search.rb +118 -0
- metadata +86 -0
@@ -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,126 @@
|
|
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
|
+
upload_body = generate_upload_body(boundary, video_xml, data)
|
47
|
+
|
48
|
+
upload_header = {
|
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" => "#{upload_body.length}",
|
55
|
+
}
|
56
|
+
puts("MSP upload_header [#{upload_header}]")
|
57
|
+
|
58
|
+
direct_upload_url = "/feeds/api/users/#{@user}/uploads"
|
59
|
+
puts("MSP direct_upload_url [#{direct_upload_url}]")
|
60
|
+
|
61
|
+
Net::HTTP.start(base_url) do |upload|
|
62
|
+
response = upload.post(direct_upload_url, upload_body, upload_header)
|
63
|
+
xml = REXML::Document.new(response.body)
|
64
|
+
if (xml.elements["//id"])
|
65
|
+
puts("MSP response xml [#{xml}]")
|
66
|
+
return xml.elements["//id"].text[/videos\/(.+)/, 1]
|
67
|
+
else
|
68
|
+
return xml
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def base_url
|
79
|
+
"uploads.gdata.youtube.com"
|
80
|
+
end
|
81
|
+
|
82
|
+
def boundary
|
83
|
+
"An43094fu"
|
84
|
+
end
|
85
|
+
|
86
|
+
def auth_token
|
87
|
+
unless @auth_token
|
88
|
+
http = Net::HTTP.new("www.google.com", 443)
|
89
|
+
http.use_ssl = true
|
90
|
+
body = "Email=#{CGI::escape @user}&Passwd=#{CGI::escape @pass}&service=youtube&source=#{CGI::escape @client_id}"
|
91
|
+
puts("MSP auth body [#{body}]")
|
92
|
+
response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
|
93
|
+
raise UploadError, "MSP "+response.body[/Error=(.+)/,1] if response.code.to_i != 200
|
94
|
+
puts("MSP response.body [#{response.body}]")
|
95
|
+
@auth_token = response.body[/Auth=(.+)/, 1]
|
96
|
+
|
97
|
+
end
|
98
|
+
puts "MSP auth_token [#{@auth_token}]"
|
99
|
+
@auth_token
|
100
|
+
end
|
101
|
+
|
102
|
+
def video_xml
|
103
|
+
%[<?xml version="1.0"?>
|
104
|
+
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">
|
105
|
+
<media:group>
|
106
|
+
<media:title type="plain">#{@opts[:title]}</media:title>
|
107
|
+
<media:description type="plain">#{@opts[:description]}</media:description>
|
108
|
+
<media:category scheme="http://gdata.youtube.com/schemas/2007/categories.cat">#{@opts[:category]}</media:category>
|
109
|
+
<media:keywords>#{@opts[:keywords].join ","}</media:keywords>
|
110
|
+
</media:group></entry> ]
|
111
|
+
end
|
112
|
+
|
113
|
+
def generate_upload_body(boundary, video_xml, data)
|
114
|
+
upload_body = ""
|
115
|
+
upload_body << "--#{boundary}\r\n"
|
116
|
+
upload_body << "Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n"
|
117
|
+
upload_body << video_xml
|
118
|
+
upload_body << "\r\n--#{boundary}\r\n"
|
119
|
+
upload_body << "Content-Type: #{@opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n"
|
120
|
+
upload_body << data
|
121
|
+
upload_body << "\r\n--#{boundary}--\r\n"
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Response
|
3
|
+
class VideoSearch < YouTubeG::Record
|
4
|
+
# the unique feed identifying url
|
5
|
+
attr_reader :feed_id
|
6
|
+
|
7
|
+
# the number of results per page
|
8
|
+
attr_reader :max_result_count
|
9
|
+
|
10
|
+
# the 1-based offset index into the full result set
|
11
|
+
attr_reader :offset
|
12
|
+
|
13
|
+
# the total number of results available for the original request
|
14
|
+
attr_reader :total_result_count
|
15
|
+
|
16
|
+
# the date and time at which the feed was last updated
|
17
|
+
attr_reader :updated_at
|
18
|
+
|
19
|
+
# the list of Video records
|
20
|
+
attr_reader :videos
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/youtube_g.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/youtube_g/client'
|
2
|
+
require File.dirname(__FILE__) + '/youtube_g/record'
|
3
|
+
require File.dirname(__FILE__) + '/youtube_g/parser'
|
4
|
+
require File.dirname(__FILE__) + '/youtube_g/model/author'
|
5
|
+
require File.dirname(__FILE__) + '/youtube_g/model/category'
|
6
|
+
require File.dirname(__FILE__) + '/youtube_g/model/contact'
|
7
|
+
require File.dirname(__FILE__) + '/youtube_g/model/content'
|
8
|
+
require File.dirname(__FILE__) + '/youtube_g/model/playlist'
|
9
|
+
require File.dirname(__FILE__) + '/youtube_g/model/rating'
|
10
|
+
require File.dirname(__FILE__) + '/youtube_g/model/thumbnail'
|
11
|
+
require File.dirname(__FILE__) + '/youtube_g/model/user'
|
12
|
+
require File.dirname(__FILE__) + '/youtube_g/model/video'
|
13
|
+
require File.dirname(__FILE__) + '/youtube_g/request/video_upload'
|
14
|
+
require File.dirname(__FILE__) + '/youtube_g/request/video_search'
|
15
|
+
require File.dirname(__FILE__) + '/youtube_g/response/video_search'
|
16
|
+
|
17
|
+
class YouTubeG
|
18
|
+
VERSION = '0.4.5'
|
19
|
+
end
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
require 'youtube_g'
|
6
|
+
|
7
|
+
class TestClient < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
@client = YouTubeG::Client.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_respond_to_a_basic_query
|
13
|
+
response = @client.videos_by(:query => "penguin")
|
14
|
+
|
15
|
+
assert_equal "http://gdata.youtube.com/feeds/api/videos?start-index=1&max-results=25&vq=penguin", response.feed_id
|
16
|
+
assert_equal 25, response.max_result_count
|
17
|
+
assert_equal 25, response.videos.length
|
18
|
+
assert_equal 1, response.offset
|
19
|
+
assert(response.total_result_count > 100)
|
20
|
+
assert_instance_of Time, response.updated_at
|
21
|
+
|
22
|
+
response.videos.each { |v| assert_valid_video v }
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_should_get_videos_for_multiword_metasearch_query
|
26
|
+
response = @client.videos_by(:query => 'christina ricci')
|
27
|
+
|
28
|
+
assert_equal "http://gdata.youtube.com/feeds/api/videos?start-index=1&max-results=25&vq=christina+ricci", response.feed_id
|
29
|
+
assert_equal 25, response.max_result_count
|
30
|
+
assert_equal 25, response.videos.length
|
31
|
+
assert_equal 1, response.offset
|
32
|
+
assert(response.total_result_count > 100)
|
33
|
+
assert_instance_of Time, response.updated_at
|
34
|
+
|
35
|
+
response.videos.each { |v| assert_valid_video v }
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_should_handle_video_not_yet_viewed
|
39
|
+
response = @client.videos_by(:query => "YnqHZDh_t2Q")
|
40
|
+
|
41
|
+
assert_equal 1, response.videos.length
|
42
|
+
response.videos.each { |v| assert_valid_video v }
|
43
|
+
end
|
44
|
+
|
45
|
+
# TODO: this doesn't work because the returned feed is in an unknown format
|
46
|
+
# def test_should_get_video_for_search_by_video_id
|
47
|
+
# response = @client.videos_by(:video_id => "T7YazwP8GtY")
|
48
|
+
# response.videos.each { |v| assert_valid_video v }
|
49
|
+
# end
|
50
|
+
|
51
|
+
def test_should_get_videos_for_one_tag
|
52
|
+
response = @client.videos_by(:tags => ['panther'])
|
53
|
+
response.videos.each { |v| assert_valid_video v }
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_should_get_videos_for_multiple_tags
|
57
|
+
response = @client.videos_by(:tags => ['tiger', 'leopard'])
|
58
|
+
response.videos.each { |v| assert_valid_video v }
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_should_get_videos_for_one_category
|
62
|
+
response = @client.videos_by(:categories => [:news])
|
63
|
+
response.videos.each { |v| assert_valid_video v }
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_should_get_videos_for_multiple_categories
|
67
|
+
response = @client.videos_by(:categories => [:news, :sports])
|
68
|
+
response.videos.each { |v| assert_valid_video v }
|
69
|
+
end
|
70
|
+
|
71
|
+
# TODO: Need to do more specific checking in these tests
|
72
|
+
# Currently, if a URL is valid, and videos are found, the test passes regardless of search criteria
|
73
|
+
def test_should_get_videos_for_categories_and_tags
|
74
|
+
response = @client.videos_by(:categories => [:news, :sports], :tags => ['soccer', 'football'])
|
75
|
+
response.videos.each { |v| assert_valid_video v }
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_should_get_most_viewed_videos
|
79
|
+
response = @client.videos_by(:most_viewed)
|
80
|
+
response.videos.each { |v| assert_valid_video v }
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_should_get_top_rated_videos_for_today
|
84
|
+
response = @client.videos_by(:top_rated, :time => :today)
|
85
|
+
response.videos.each { |v| assert_valid_video v }
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_should_get_videos_for_categories_and_tags_with_category_boolean_operators
|
89
|
+
response = @client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] },
|
90
|
+
:tags => { :include => ['football'], :exclude => ['soccer'] })
|
91
|
+
response.videos.each { |v| assert_valid_video v }
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_should_get_videos_for_categories_and_tags_with_tag_boolean_operators
|
95
|
+
response = @client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] },
|
96
|
+
:tags => { :either => ['football', 'soccer', 'polo'] })
|
97
|
+
response.videos.each { |v| assert_valid_video v }
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_should_get_videos_by_user
|
101
|
+
response = @client.videos_by(:user => 'liz')
|
102
|
+
response.videos.each { |v| assert_valid_video v }
|
103
|
+
end
|
104
|
+
|
105
|
+
# HTTP 403 Error
|
106
|
+
# def test_should_get_favorite_videos_by_user
|
107
|
+
# response = @client.videos_by(:favorites, :user => 'liz')
|
108
|
+
# response.videos.each { |v| assert_valid_video v }
|
109
|
+
# end
|
110
|
+
|
111
|
+
def test_should_get_videos_for_query_search_with_categories_excluded
|
112
|
+
response = @client.videos_by(:query => 'bench press', :categories => { :exclude => [:comedy, :entertainment] },
|
113
|
+
:max_results => 10)
|
114
|
+
assert_equal "<object width=\"425\" height=\"350\">\n <param name=\"movie\" value=\"http://www.youtube.com/v/BlDWdfTAx8o\"></param>\n <param name=\"wmode\" value=\"transparent\"></param>\n <embed src=\"http://www.youtube.com/v/BlDWdfTAx8o\" type=\"application/x-shockwave-flash\" \n wmode=\"transparent\" width=\"425\" height=\"350\"></embed>\n</object>\n", response.videos.first.embed_html
|
115
|
+
response.videos.each { |v| assert_valid_video v }
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_should_be_able_to_pass_in_logger
|
119
|
+
@client = YouTubeG::Client.new(Logger.new(STDOUT))
|
120
|
+
assert_not_nil @client.logger
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_should_create_logger_if_not_passed_in
|
124
|
+
@client = YouTubeG::Client.new
|
125
|
+
assert_not_nil @client.logger
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_should_determine_if_nonembeddable_video_is_embeddable
|
129
|
+
response = @client.videos_by(:query => "avril lavigne girlfriend")
|
130
|
+
|
131
|
+
video = response.videos.first
|
132
|
+
assert !video.can_embed?
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_should_determine_if_embeddable_video_is_embeddable
|
136
|
+
response = @client.videos_by(:query => "strongbad")
|
137
|
+
|
138
|
+
video = response.videos.first
|
139
|
+
assert video.can_embed?
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_should_retrieve_video_by_id
|
143
|
+
video = @client.video_by("http://gdata.youtube.com/feeds/videos/EkF4JD2rO3Q")
|
144
|
+
assert_valid_video video
|
145
|
+
|
146
|
+
video = @client.video_by("EkF4JD2rO3Q")
|
147
|
+
assert_valid_video video
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def assert_valid_video (video)
|
153
|
+
# pp video
|
154
|
+
|
155
|
+
# check general attributes
|
156
|
+
assert_instance_of YouTubeG::Model::Video, video
|
157
|
+
assert_instance_of Fixnum, video.duration
|
158
|
+
assert(video.duration > 0)
|
159
|
+
#assert_match(/^<div style=.*?<\/div>/m, video.html_content)
|
160
|
+
assert_instance_of String, video.html_content
|
161
|
+
|
162
|
+
# validate media content records
|
163
|
+
video.media_content.each do |media_content|
|
164
|
+
# http://www.youtube.com/v/IHVaXG1thXM
|
165
|
+
assert_valid_url media_content.url
|
166
|
+
assert(media_content.duration > 0)
|
167
|
+
assert_instance_of YouTubeG::Model::Video::Format, media_content.format
|
168
|
+
assert_instance_of String, media_content.mime_type
|
169
|
+
assert_match(/^[^\/]+\/[^\/]+$/, media_content.mime_type)
|
170
|
+
end
|
171
|
+
|
172
|
+
default_content = video.default_media_content
|
173
|
+
if default_content
|
174
|
+
assert_instance_of YouTubeG::Model::Content, default_content
|
175
|
+
assert default_content.is_default?
|
176
|
+
end
|
177
|
+
|
178
|
+
# validate keywords
|
179
|
+
video.keywords.each { |kw| assert_instance_of(String, kw) }
|
180
|
+
|
181
|
+
# http://www.youtube.com/watch?v=IHVaXG1thXM
|
182
|
+
assert_valid_url video.player_url
|
183
|
+
assert_instance_of Time, video.published_at
|
184
|
+
|
185
|
+
# validate optionally-present rating
|
186
|
+
if video.rating
|
187
|
+
assert_instance_of YouTubeG::Model::Rating, video.rating
|
188
|
+
assert_instance_of Float, video.rating.average
|
189
|
+
assert_instance_of Fixnum, video.rating.max
|
190
|
+
assert_instance_of Fixnum, video.rating.min
|
191
|
+
assert_instance_of Fixnum, video.rating.rater_count
|
192
|
+
end
|
193
|
+
|
194
|
+
# validate thumbnails
|
195
|
+
assert(video.thumbnails.size > 0)
|
196
|
+
|
197
|
+
assert_not_nil video.title
|
198
|
+
assert_instance_of String, video.title
|
199
|
+
assert(video.title.length > 0)
|
200
|
+
|
201
|
+
assert_instance_of Time, video.updated_at
|
202
|
+
# http://gdata.youtube.com/feeds/videos/IHVaXG1thXM
|
203
|
+
assert_valid_url video.video_id
|
204
|
+
assert_instance_of Fixnum, video.view_count
|
205
|
+
|
206
|
+
# validate author
|
207
|
+
assert_instance_of YouTubeG::Model::Author, video.author
|
208
|
+
assert_instance_of String, video.author.name
|
209
|
+
assert(video.author.name.length > 0)
|
210
|
+
assert_valid_url video.author.uri
|
211
|
+
|
212
|
+
# validate categories
|
213
|
+
video.categories.each do |cat|
|
214
|
+
assert_instance_of YouTubeG::Model::Category, cat
|
215
|
+
assert_instance_of String, cat.label
|
216
|
+
assert_instance_of String, cat.term
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def assert_valid_url (url)
|
221
|
+
URI::parse(url)
|
222
|
+
return true
|
223
|
+
rescue
|
224
|
+
return false
|
225
|
+
end
|
226
|
+
end
|
data/test/test_video.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
require 'youtube_g'
|
6
|
+
|
7
|
+
class TestVideo < Test::Unit::TestCase
|
8
|
+
def test_should_extract_unique_id_from_video_id
|
9
|
+
video = YouTubeG::Model::Video.new(:video_id => "http://gdata.youtube.com/feeds/videos/ZTUVgYoeN_o")
|
10
|
+
assert_equal "ZTUVgYoeN_o", video.unique_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_should_extract_unique_id_with_hypen_from_video_id
|
14
|
+
video = YouTubeG::Model::Video.new(:video_id => "http://gdata.youtube.com/feeds/videos/BDqs-OZWw9o")
|
15
|
+
assert_equal "BDqs-OZWw9o", video.unique_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_should_have_related_videos
|
19
|
+
video = YouTubeG::Model::Video.new(:video_id => "http://gdata.youtube.com/feeds/videos/BDqs-OZWw9o")
|
20
|
+
response = video.related
|
21
|
+
|
22
|
+
assert_equal "http://gdata.youtube.com/feeds/api/videos/BDqs-OZWw9o/related", response.feed_id
|
23
|
+
assert_equal 25, response.max_result_count
|
24
|
+
assert_equal 25, response.videos.length
|
25
|
+
assert_equal 1, response.offset
|
26
|
+
puts response.total_result_count
|
27
|
+
assert(response.total_result_count > 0)
|
28
|
+
assert_instance_of Time, response.updated_at
|
29
|
+
end
|
30
|
+
end
|