youtube_it 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +50 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/youtube_it/client.rb +24 -0
- data/lib/youtube_it/request/video_upload.rb +122 -3
- data/pkg/youtube_it-0.0.1.gem +0 -0
- data/pkg/youtube_it-0.0.2.gem +0 -0
- data/pkg/youtube_it-0.0.3.gem +0 -0
- data/test/helper.rb +4 -1
- data/test/test_client.rb +42 -18
- data/youtube_it.gemspec +7 -4
- data/youtube_it.tmproj +27 -0
- metadata +7 -4
data/README.txt
CHANGED
@@ -23,7 +23,7 @@ Basic queries:
|
|
23
23
|
client.videos_by(:user => 'liz')
|
24
24
|
client.videos_by(:favorites, :user => 'liz')
|
25
25
|
client.video_by("FQK1URcxmb4")
|
26
|
-
client.
|
26
|
+
client.video_by_user("chebyte","FQK1URcxmb4")
|
27
27
|
|
28
28
|
Standard feeds:
|
29
29
|
|
@@ -55,6 +55,55 @@ Upload videos:
|
|
55
55
|
client.video_delete("FQK1URcxmb4")
|
56
56
|
|
57
57
|
|
58
|
+
Comments
|
59
|
+
|
60
|
+
You can add or list comments with the following way:
|
61
|
+
|
62
|
+
client = YouTubeIt::Client.new("youtube_username", "youtube_passwd", "developer_key")
|
63
|
+
|
64
|
+
* get all comments:
|
65
|
+
|
66
|
+
client.comments(video_id)
|
67
|
+
|
68
|
+
* add a new comment:
|
69
|
+
|
70
|
+
client.add_comment(video_id, "test comment!")
|
71
|
+
|
72
|
+
Favorites
|
73
|
+
|
74
|
+
You can add, del or list your favorites videos:
|
75
|
+
|
76
|
+
client = YouTubeIt::Client.new("youtube_username", "youtube_passwd", "developer_key")
|
77
|
+
|
78
|
+
* get all favorites:
|
79
|
+
|
80
|
+
client.favorites
|
81
|
+
|
82
|
+
* add a new favorite:
|
83
|
+
|
84
|
+
client.add_favorite(video_id)
|
85
|
+
|
86
|
+
* del favorite:
|
87
|
+
|
88
|
+
client.del_favorite(video_id)
|
89
|
+
|
90
|
+
Access Control List
|
91
|
+
|
92
|
+
You can give permissions in your videos, for example denied comments, rate, etc...
|
93
|
+
you can read more there http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:accessControl
|
94
|
+
you have available the followings options:
|
95
|
+
|
96
|
+
* :rate, :comment, :commentVote, :videoRespond, :list, :embed, :syndicate
|
97
|
+
|
98
|
+
Example
|
99
|
+
|
100
|
+
client = YouTubeIt::Client.new("youtube_username", "youtube_passwd", "developer_key")
|
101
|
+
|
102
|
+
* upload video with denied comments
|
103
|
+
|
104
|
+
client.video_upload(File.open("test.mov"), :title => "test",:description => 'some description', :category => 'People',:keywords => %w[cool blah test], :comment => "denied")
|
105
|
+
|
106
|
+
|
58
107
|
== Upload videos from browser:
|
59
108
|
|
60
109
|
For upload a video from browser you need make a form upload with the followings params
|
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ begin
|
|
7
7
|
gem.name = "youtube_it"
|
8
8
|
gem.summary = %Q{the one stop shop for working with youtube apis}
|
9
9
|
gem.description = %Q{the one stop shop for working with youtube apis}
|
10
|
-
gem.email = "
|
10
|
+
gem.email = "kylejginavan@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/kylejginavan/youtube_it"
|
12
12
|
gem.authors = ["Mauro Torres & Kyle Ginavan"]
|
13
13
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/youtube_it/client.rb
CHANGED
@@ -95,6 +95,30 @@ class YouTubeIt
|
|
95
95
|
client.get_upload_token(options, nexturl)
|
96
96
|
end
|
97
97
|
|
98
|
+
def add_comment(video_id, comment)
|
99
|
+
client.add_comment(video_id, comment)
|
100
|
+
end
|
101
|
+
|
102
|
+
def comments(video_id)
|
103
|
+
client.comments(video_id)
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_favorite(video_id)
|
107
|
+
client.add_favorite(video_id)
|
108
|
+
end
|
109
|
+
|
110
|
+
def del_favorite(video_id)
|
111
|
+
client.del_favorite(video_id)
|
112
|
+
end
|
113
|
+
|
114
|
+
def favorites
|
115
|
+
client.favorites
|
116
|
+
end
|
117
|
+
|
118
|
+
def enable_http_debugging
|
119
|
+
client.enable_http_debugging
|
120
|
+
end
|
121
|
+
|
98
122
|
private
|
99
123
|
|
100
124
|
def client
|
@@ -15,9 +15,14 @@ class YouTubeIt
|
|
15
15
|
# :keywords => %w[cool blah test]
|
16
16
|
#
|
17
17
|
class VideoUpload
|
18
|
-
|
18
|
+
include YouTubeIt::Logging
|
19
19
|
def initialize user, pass, dev_key, client_id = 'youtube_it'
|
20
20
|
@user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
|
21
|
+
@http_debugging = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def enable_http_debugging
|
25
|
+
@http_debugging = true
|
21
26
|
end
|
22
27
|
|
23
28
|
#
|
@@ -31,6 +36,14 @@ class YouTubeIt
|
|
31
36
|
# :category
|
32
37
|
# :keywords
|
33
38
|
# :private
|
39
|
+
# New V2 api hash keys for accessControl:
|
40
|
+
# :rate
|
41
|
+
# :comment
|
42
|
+
# :commentVote
|
43
|
+
# :videoRespond
|
44
|
+
# :list
|
45
|
+
# :embed
|
46
|
+
# :syndicate
|
34
47
|
# Specifying :private will make the video private, otherwise it will be public.
|
35
48
|
#
|
36
49
|
# When one of the fields is invalid according to YouTube,
|
@@ -58,7 +71,9 @@ class YouTubeIt
|
|
58
71
|
|
59
72
|
path = '/feeds/api/users/%s/uploads' % @user
|
60
73
|
|
61
|
-
Net::HTTP.
|
74
|
+
http = Net::HTTP.new(uploads_url)
|
75
|
+
http.set_debug_output(logger) if @http_debugging
|
76
|
+
http.start do | session |
|
62
77
|
|
63
78
|
# Use the chained IO as body so that Net::HTTP reads into the socket for us
|
64
79
|
post = Net::HTTP::Post.new(path, upload_headers)
|
@@ -85,13 +100,16 @@ class YouTubeIt
|
|
85
100
|
update_body = video_xml
|
86
101
|
|
87
102
|
update_header = authorization_headers.merge({
|
103
|
+
"GData-Version" => "2",
|
88
104
|
"Content-Type" => "application/atom+xml",
|
89
105
|
"Content-Length" => "#{update_body.length}",
|
90
106
|
})
|
91
107
|
|
92
108
|
update_url = "/feeds/api/users/#{@user}/uploads/#{video_id}"
|
93
109
|
|
94
|
-
Net::HTTP.
|
110
|
+
http = Net::HTTP.new(base_url)
|
111
|
+
http.set_debug_output(logger) if @http_debugging
|
112
|
+
http.start do | session |
|
95
113
|
response = session.put(update_url, update_body, update_header)
|
96
114
|
raise_on_faulty_response(response)
|
97
115
|
|
@@ -133,6 +151,83 @@ class YouTubeIt
|
|
133
151
|
end
|
134
152
|
end
|
135
153
|
|
154
|
+
def add_comment(video_id, comment)
|
155
|
+
comment_body = video_xml_for(:comment => comment)
|
156
|
+
comment_header = authorization_headers.merge({
|
157
|
+
"GData-Version" => "2",
|
158
|
+
"Content-Type" => "application/atom+xml",
|
159
|
+
"Content-Length" => "#{comment_body.length}",
|
160
|
+
})
|
161
|
+
|
162
|
+
comment_url = "/feeds/api/videos/#{video_id}/comments"
|
163
|
+
|
164
|
+
http = Net::HTTP.new(base_url)
|
165
|
+
http.set_debug_output(logger) if @http_debugging
|
166
|
+
http.start do | session |
|
167
|
+
response = session.post(comment_url, comment_body, comment_header)
|
168
|
+
raise_on_faulty_response(response)
|
169
|
+
response = {:code => response.code, :body => response.body}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def comments(video_id)
|
174
|
+
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 |
|
178
|
+
response = session.get(comment_url)
|
179
|
+
raise_on_faulty_response(response)
|
180
|
+
response = {:code => response.code, :body => response.body}
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def add_favorite(video_id)
|
185
|
+
favorite_body = video_xml_for(:favorite => video_id)
|
186
|
+
favorite_header = authorization_headers.merge({
|
187
|
+
"GData-Version" => "2",
|
188
|
+
"Content-Type" => "application/atom+xml",
|
189
|
+
"Content-Length" => "#{favorite_body.length}",
|
190
|
+
})
|
191
|
+
|
192
|
+
favorite_url = "/feeds/api/users/#{@user}/favorites"
|
193
|
+
|
194
|
+
http = Net::HTTP.new(base_url)
|
195
|
+
http.set_debug_output(logger) if @http_debugging
|
196
|
+
http.start do | session |
|
197
|
+
response = session.post(favorite_url, favorite_body, favorite_header)
|
198
|
+
raise_on_faulty_response(response)
|
199
|
+
return true
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def del_favorite(video_id)
|
204
|
+
favorite_header = authorization_headers.merge({
|
205
|
+
"Content-Type" => "application/atom+xml; charset=UTF-8",
|
206
|
+
"Content-Length" => "0",
|
207
|
+
})
|
208
|
+
|
209
|
+
favorite_url = "/feeds/api/users/#{@user}/favorites/#{video_id}"
|
210
|
+
|
211
|
+
http = Net::HTTP.new(base_url)
|
212
|
+
http.set_debug_output(logger) if @http_debugging
|
213
|
+
http.start do | session |
|
214
|
+
response = session.delete(favorite_url, favorite_header)
|
215
|
+
raise_on_faulty_response(response)
|
216
|
+
return true
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def favorites
|
221
|
+
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 |
|
225
|
+
response = session.get(favorite_url)
|
226
|
+
raise_on_faulty_response(response)
|
227
|
+
return response.body
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
136
231
|
private
|
137
232
|
|
138
233
|
def uploads_url
|
@@ -213,6 +308,30 @@ class YouTubeIt
|
|
213
308
|
mg.tag!('media:category', @opts[:category], :scheme => "http://gdata.youtube.com/schemas/2007/categories.cat")
|
214
309
|
mg.tag!('yt:private') if @opts[:private]
|
215
310
|
end
|
311
|
+
m.tag!("yt:accessControl", :action => "rate", :permission => @opts[:rate]) if @opts[:rate]
|
312
|
+
m.tag!("yt:accessControl", :action => "comment", :permission => @opts[:comment]) if @opts[:comment]
|
313
|
+
m.tag!("yt:accessControl", :action => "commentVote", :permission => @opts[:commentVote]) if @opts[:commentVote]
|
314
|
+
m.tag!("yt:accessControl", :action => "videoRespond", :permission => @opts[:videoRespond]) if @opts[:videoRespond]
|
315
|
+
m.tag!("yt:accessControl", :action => "list", :permission => @opts[:list]) if @opts[:list]
|
316
|
+
m.tag!("yt:accessControl", :action => "embed", :permission => @opts[:embed]) if @opts[:embed]
|
317
|
+
m.tag!("yt:accessControl", :action => "syndicate", :permission => @opts[:syndicate]) if @opts[:syndicate]
|
318
|
+
end.to_s
|
319
|
+
end
|
320
|
+
|
321
|
+
def video_xml_for(data)
|
322
|
+
b = Builder::XmlMarkup.new
|
323
|
+
b.instruct!
|
324
|
+
b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
|
325
|
+
m.content(data[:comment]) if data[:comment]
|
326
|
+
m.id(data[:favorite]) if data[:favorite]
|
327
|
+
end.to_s
|
328
|
+
end
|
329
|
+
|
330
|
+
def video_xml_for_favorite(video_id)
|
331
|
+
b = Builder::XmlMarkup.new
|
332
|
+
b.instruct!
|
333
|
+
b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
|
334
|
+
m.id(video_id)
|
216
335
|
end.to_s
|
217
336
|
end
|
218
337
|
|
data/pkg/youtube_it-0.0.1.gem
CHANGED
Binary file
|
Binary file
|
Binary file
|
data/test/helper.rb
CHANGED
data/test/test_client.rb
CHANGED
@@ -76,12 +76,6 @@ class TestClient < Test::Unit::TestCase
|
|
76
76
|
response.videos.each { |v| assert_valid_video v }
|
77
77
|
end
|
78
78
|
|
79
|
-
# TODO: this doesn't work because the returned feed is in an unknown format
|
80
|
-
# def test_should_get_video_for_search_by_video_id
|
81
|
-
# response = @client.videos_by(:video_id => "T7YazwP8GtY")
|
82
|
-
# response.videos.each { |v| assert_valid_video v }
|
83
|
-
# end
|
84
|
-
|
85
79
|
def test_should_get_videos_for_one_tag
|
86
80
|
response = @client.videos_by(:tags => ['panther'])
|
87
81
|
response.videos.each { |v| assert_valid_video v }
|
@@ -171,13 +165,6 @@ class TestClient < Test::Unit::TestCase
|
|
171
165
|
assert_nothing_raised { YouTubeIt::Client.new(true) }
|
172
166
|
end
|
173
167
|
|
174
|
-
def test_should_determine_if_nonembeddable_video_is_embeddable
|
175
|
-
response = @client.videos_by(:query => "avril lavigne girlfriend")
|
176
|
-
|
177
|
-
video = response.videos.first
|
178
|
-
assert !video.embeddable?
|
179
|
-
end
|
180
|
-
|
181
168
|
def test_should_determine_if_embeddable_video_is_embeddable
|
182
169
|
response = @client.videos_by(:query => "strongbad")
|
183
170
|
|
@@ -211,12 +198,11 @@ class TestClient < Test::Unit::TestCase
|
|
211
198
|
|
212
199
|
def test_should_update_a_video
|
213
200
|
OPTIONS[:title] = "title changed"
|
214
|
-
|
215
|
-
@client.
|
216
|
-
video = @client.video_by_user(ACCOUNT[:user], video_id)
|
217
|
-
assert_valid_video video
|
201
|
+
@client.video_update("BhTw20Lr4v8", OPTIONS)
|
202
|
+
video = @client.video_by("BhTw20Lr4v8")
|
218
203
|
assert video.title == "title changed"
|
219
|
-
|
204
|
+
OPTIONS[:title] = "maddie"
|
205
|
+
@client.video_update("BhTw20Lr4v8", OPTIONS)
|
220
206
|
end
|
221
207
|
|
222
208
|
def test_should_delete_video
|
@@ -226,6 +212,44 @@ class TestClient < Test::Unit::TestCase
|
|
226
212
|
assert @client.video_delete(video_id)
|
227
213
|
end
|
228
214
|
|
215
|
+
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)
|
218
|
+
assert_valid_video video
|
219
|
+
doc = Nokogiri::HTML(open("http://www.youtube.com/watch?v=#{video_id}"))
|
220
|
+
doc.css('.comments-disabled').each{|tag| assert (tag.content.strip == "Adding comments has been disabled for this video.")}
|
221
|
+
@client.video_delete(video_id)
|
222
|
+
end
|
223
|
+
|
224
|
+
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)
|
227
|
+
assert_valid_video video
|
228
|
+
doc = Nokogiri::HTML(open("http://www.youtube.com/watch?v=#{video_id}"))
|
229
|
+
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)
|
231
|
+
end
|
232
|
+
|
233
|
+
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)
|
236
|
+
assert video.noembed
|
237
|
+
@client.video_delete(video_id)
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_should_add_new_comment
|
241
|
+
video_id ="H1TrfM3xbgc"
|
242
|
+
@client.add_comment(video_id, "test comment")
|
243
|
+
comment = @client.comments(video_id)[:body]
|
244
|
+
assert comment.match(/test comment/)
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_shoul_add_and_del_video_to_favorite
|
248
|
+
video_id ="H1TrfM3xbgc"
|
249
|
+
assert @client.add_favorite(video_id)
|
250
|
+
assert @client.del_favorite(video_id)
|
251
|
+
end
|
252
|
+
|
229
253
|
private
|
230
254
|
|
231
255
|
def assert_valid_video (video)
|
data/youtube_it.gemspec
CHANGED
@@ -5,13 +5,13 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{youtube_it}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Mauro Torres & Kyle Ginavan"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-08-12}
|
13
13
|
s.description = %q{the one stop shop for working with youtube apis}
|
14
|
-
s.email = %q{
|
14
|
+
s.email = %q{kylejginavan@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"README.txt"
|
17
17
|
]
|
@@ -44,13 +44,16 @@ Gem::Specification.new do |s|
|
|
44
44
|
"lib/youtube_it/response/video_search.rb",
|
45
45
|
"lib/youtube_it/version.rb",
|
46
46
|
"pkg/youtube_it-0.0.1.gem",
|
47
|
+
"pkg/youtube_it-0.0.2.gem",
|
48
|
+
"pkg/youtube_it-0.0.3.gem",
|
47
49
|
"test/helper.rb",
|
48
50
|
"test/test.mov",
|
49
51
|
"test/test_chain_io.rb",
|
50
52
|
"test/test_client.rb",
|
51
53
|
"test/test_video.rb",
|
52
54
|
"test/test_video_search.rb",
|
53
|
-
"youtube_it.gemspec"
|
55
|
+
"youtube_it.gemspec",
|
56
|
+
"youtube_it.tmproj"
|
54
57
|
]
|
55
58
|
s.homepage = %q{http://github.com/kylejginavan/youtube_it}
|
56
59
|
s.rdoc_options = ["--charset=UTF-8"]
|
data/youtube_it.tmproj
ADDED
@@ -0,0 +1,27 @@
|
|
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>
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mauro Torres & Kyle Ginavan
|
@@ -14,12 +14,12 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-08-12 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
21
21
|
description: the one stop shop for working with youtube apis
|
22
|
-
email:
|
22
|
+
email: kylejginavan@gmail.com
|
23
23
|
executables: []
|
24
24
|
|
25
25
|
extensions: []
|
@@ -55,6 +55,8 @@ files:
|
|
55
55
|
- lib/youtube_it/response/video_search.rb
|
56
56
|
- lib/youtube_it/version.rb
|
57
57
|
- pkg/youtube_it-0.0.1.gem
|
58
|
+
- pkg/youtube_it-0.0.2.gem
|
59
|
+
- pkg/youtube_it-0.0.3.gem
|
58
60
|
- test/helper.rb
|
59
61
|
- test/test.mov
|
60
62
|
- test/test_chain_io.rb
|
@@ -62,6 +64,7 @@ files:
|
|
62
64
|
- test/test_video.rb
|
63
65
|
- test/test_video_search.rb
|
64
66
|
- youtube_it.gemspec
|
67
|
+
- youtube_it.tmproj
|
65
68
|
has_rdoc: true
|
66
69
|
homepage: http://github.com/kylejginavan/youtube_it
|
67
70
|
licenses: []
|