msp-youtube-g 0.4.8.1 → 0.4.8.3
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 +2 -1
- data/lib/youtube_g.rb +1 -1
- data/lib/youtube_g/client.rb +23 -5
- data/lib/youtube_g/model/video.rb +16 -1
- data/lib/youtube_g/parser.rb +12 -3
- data/lib/youtube_g/request/video_upload.rb +5 -1
- data/test/status.xml +47 -0
- data/test/test_parser.rb +25 -3
- data/test/test_video.rb +26 -0
- metadata +3 -1
data/History.txt
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
== trunk
|
1
|
+
== trunk
|
2
|
+
* Create a method to check the status of the uploaded video by status URL [msp]
|
2
3
|
* Move network dependent tests to integration-test [msp]
|
3
4
|
* Create a parser and a model for upload errors [msp]
|
4
5
|
* Stub driven tests for VideoFeedParser, VideosFeedParser, UploadErrorParser [msp]
|
data/lib/youtube_g.rb
CHANGED
data/lib/youtube_g/client.rb
CHANGED
@@ -61,16 +61,34 @@ class YouTubeG
|
|
61
61
|
# Retrieves a single YouTube video.
|
62
62
|
#
|
63
63
|
# === Parameters
|
64
|
-
# vid<String>:: The ID or URL of the video that you'd like to retrieve.
|
64
|
+
# vid<String>:: The ID or URL of the video that you'd like to retrieve. If your uncertain
|
65
|
+
# of the processed state use video_processed_by_youtube? first else this will fail.
|
65
66
|
#
|
66
67
|
# === Returns
|
67
68
|
# YouTubeG::Model::Video
|
68
69
|
def video_by(vid)
|
69
|
-
video_id = vid =~ /^http/ ? vid : "http://gdata.youtube.com/feeds/videos/#{vid}"
|
70
|
+
video_id = vid =~ /^http/ ? vid : "http://gdata.youtube.com/feeds/api/videos/#{vid}"
|
70
71
|
parser = YouTubeG::Parser::VideoFeedParser.new(video_id)
|
71
|
-
parser.parse
|
72
|
-
end
|
73
|
-
|
72
|
+
parser.parse
|
73
|
+
end
|
74
|
+
|
75
|
+
# Checks the status of a users uploaded video.
|
76
|
+
#
|
77
|
+
# === Parameters
|
78
|
+
# status_url<String>:: The URL returned by YT when the video was uploaded e.g.
|
79
|
+
#
|
80
|
+
# http://gdata.youtube.com/feeds/api/users/speechboxmatt/uploads/QR1sWOVqxoM
|
81
|
+
#
|
82
|
+
# This is stored on upload as Video.status_url
|
83
|
+
# === Returns
|
84
|
+
# Boolean
|
85
|
+
def video_by_status_url(status_url)
|
86
|
+
parser = YouTubeG::Parser::VideoFeedParser.new(status_url)
|
87
|
+
video = parser.parse
|
88
|
+
return video
|
89
|
+
end
|
90
|
+
|
91
|
+
|
74
92
|
private
|
75
93
|
|
76
94
|
def calculate_offset(page, per_page)
|
@@ -59,7 +59,10 @@ class YouTubeG
|
|
59
59
|
# Used in video uploads only to check the state of the upload
|
60
60
|
class AppControl < YouTubeG::Record
|
61
61
|
attr_reader :draft
|
62
|
-
attr_reader :state
|
62
|
+
attr_reader :state
|
63
|
+
attr_reader :reason
|
64
|
+
attr_reader :help_url
|
65
|
+
attr_reader :description
|
63
66
|
end
|
64
67
|
|
65
68
|
# *Fixnum*:: Duration of a video in seconds.
|
@@ -113,6 +116,9 @@ class YouTubeG
|
|
113
116
|
# *String*:: The link to watch the URL on YouTubes website.
|
114
117
|
attr_reader :player_url
|
115
118
|
|
119
|
+
# *String*:: The URL to check the processing status of the video at YT.
|
120
|
+
attr_reader :status_url
|
121
|
+
|
116
122
|
# YouTubeG::Model::Rating:: Information about the videos rating.
|
117
123
|
attr_reader :rating
|
118
124
|
|
@@ -187,6 +193,15 @@ EDOC
|
|
187
193
|
# String: Absolute URL for embedding video
|
188
194
|
def embed_url
|
189
195
|
@player_url.sub('watch?', '').sub('=', '/')
|
196
|
+
end
|
197
|
+
|
198
|
+
# Has this vid been processed by YT?
|
199
|
+
#
|
200
|
+
# === Returns
|
201
|
+
# Boolean
|
202
|
+
def processed?
|
203
|
+
return true if self.app_control == nil
|
204
|
+
return false if self.app_control.state.upcase == 'PROCESSING' || self.app_control.state.upcase == 'REJECTED' || self.app_control.state.upcase == 'FAILED'
|
190
205
|
end
|
191
206
|
|
192
207
|
end
|
data/lib/youtube_g/parser.rb
CHANGED
@@ -68,7 +68,10 @@ class YouTubeG
|
|
68
68
|
if app_control_element
|
69
69
|
app_control = YouTubeG::Model::Video::AppControl.new(
|
70
70
|
:draft => app_control_element.elements["app:draft"].text,
|
71
|
-
:state => app_control_element.elements["yt:state"].attributes["name"]
|
71
|
+
:state => app_control_element.elements["yt:state"].attributes["name"],
|
72
|
+
:reason => app_control_element.elements["yt:state"].attributes["reasonCode"],
|
73
|
+
:help_url => app_control_element.elements["yt:state"].attributes["helpUrl"],
|
74
|
+
:description => app_control_element.elements["yt:state"].text)
|
72
75
|
end
|
73
76
|
|
74
77
|
# parse the category and keyword lists
|
@@ -136,7 +139,12 @@ class YouTubeG
|
|
136
139
|
view_count = (el = entry.elements["yt:statistics"]) ? el.attributes["viewCount"].to_i : 0
|
137
140
|
|
138
141
|
noembed = entry.elements["yt:noembed"] ? true : false
|
139
|
-
racy = entry.elements["media:rating"] ? true : false
|
142
|
+
racy = entry.elements["media:rating"] ? true : false
|
143
|
+
|
144
|
+
status_url = nil
|
145
|
+
entry.elements.each("link") do |el|
|
146
|
+
status_url = el.attributes["href"] if el.attributes["rel"] == "self"
|
147
|
+
end
|
140
148
|
|
141
149
|
YouTubeG::Model::Video.new(
|
142
150
|
:video_id => video_id,
|
@@ -156,7 +164,8 @@ class YouTubeG
|
|
156
164
|
:rating => rating,
|
157
165
|
:view_count => view_count,
|
158
166
|
:noembed => noembed,
|
159
|
-
:racy => racy
|
167
|
+
:racy => racy,
|
168
|
+
:status_url => status_url)
|
160
169
|
end
|
161
170
|
|
162
171
|
def parse_media_content (media_content_element)
|
@@ -79,9 +79,13 @@ class YouTubeG
|
|
79
79
|
response = upload.post(direct_upload_url, upload_body, upload_header)
|
80
80
|
# todo parse this out also
|
81
81
|
if response.code.to_i == 403
|
82
|
+
logger.error("ERROR: #{response.code}")
|
82
83
|
raise AuthenticationError, response.body[/<TITLE>(.+)<\/TITLE>/, 1]
|
83
|
-
elsif response.code.to_i != 201
|
84
|
+
elsif response.code.to_i != 201
|
85
|
+
logger.error("ERROR: #{response.code}")
|
86
|
+
logger.debug("response: #{response.body}")
|
84
87
|
upload_errors = YouTubeG::Parser::UploadErrorParser.new(response.body).parse
|
88
|
+
logger.debug("upload_errors: #{upload_errors}")
|
85
89
|
raise UploadError, upload_errors.inspect
|
86
90
|
end
|
87
91
|
return YouTubeG::Parser::VideoFeedParser.new(response.body).parse
|
data/test/status.xml
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
2
|
+
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gml='http://www.opengis.net/gml'
|
3
|
+
xmlns:georss='http://www.georss.org/georss' xmlns:media='http://search.yahoo.com/mrss/'
|
4
|
+
xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:yt='http://gdata.youtube.com/schemas/2007'
|
5
|
+
xmlns:gd='http://schemas.google.com/g/2005'>
|
6
|
+
<id>http://gdata.youtube.com/feeds/api/videos/QR1sWOVqxoM</id>
|
7
|
+
<published>2008-08-22T04:07:16.000-07:00</published>
|
8
|
+
<updated>2008-08-22T04:07:16.000-07:00</updated>
|
9
|
+
<app:control xmlns:app='http://purl.org/atom/app#'>
|
10
|
+
<app:draft>yes</app:draft>
|
11
|
+
<yt:state name='rejected'
|
12
|
+
reasonCode='tooLong'
|
13
|
+
helpUrl='http://www.youtube.com/t/community_guidelines'>Video is too long.</yt:state>
|
14
|
+
</app:control>
|
15
|
+
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='speechbox'/>
|
16
|
+
<category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' term='Film' label='Film & Animation'/>
|
17
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/>
|
18
|
+
<category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='wordia'/>
|
19
|
+
<title type='text'>discombobulate [08/22/2008 at 12:06PM]</title>
|
20
|
+
<content type='text'>To throw into a state of confusion; to befuddle or perplex</content>
|
21
|
+
<link rel='alternate' type='text/html' href='http://www.youtube.com/my_videos_edit?ns=1&video_id=QR1sWOVqxoM'/>
|
22
|
+
<link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml'
|
23
|
+
href='http://gdata.youtube.com/feeds/api/videos/QR1sWOVqxoM/responses'/>
|
24
|
+
<link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml'
|
25
|
+
href='http://gdata.youtube.com/feeds/api/videos/QR1sWOVqxoM/related'/>
|
26
|
+
<link rel='self' type='application/atom+xml'
|
27
|
+
href='http://gdata.youtube.com/feeds/api/users/speechboxmatt/uploads/QR1sWOVqxoM'/>
|
28
|
+
<author>
|
29
|
+
<name>speechboxmatt</name>
|
30
|
+
<uri>http://gdata.youtube.com/feeds/api/users/speechboxmatt</uri>
|
31
|
+
</author>
|
32
|
+
<media:group>
|
33
|
+
<media:title type='plain'>discombobulate [08/22/2008 at 12:06PM]</media:title>
|
34
|
+
<media:description type='plain'>To throw into a state of confusion; to befuddle or perplex</media:description>
|
35
|
+
<media:keywords>speechbox, wordia</media:keywords>
|
36
|
+
<yt:duration seconds='0'/>
|
37
|
+
<media:category label='Film & Animation' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>
|
38
|
+
Film
|
39
|
+
</media:category>
|
40
|
+
<media:content url='http://www.youtube.com/v/QR1sWOVqxoM&f=gdata_user_uploads'
|
41
|
+
type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full'
|
42
|
+
yt:format='5'/>
|
43
|
+
</media:group>
|
44
|
+
<gd:comments>
|
45
|
+
<gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/QR1sWOVqxoM/comments' countHint='0'/>
|
46
|
+
</gd:comments>
|
47
|
+
</entry>
|
data/test/test_parser.rb
CHANGED
@@ -4,7 +4,8 @@ require File.dirname(__FILE__) + '/../lib/youtube_g'
|
|
4
4
|
class TestParser < Test::Unit::TestCase
|
5
5
|
|
6
6
|
UPLOAD_XML = File.new(File.dirname(__FILE__) +"/upload.xml")
|
7
|
-
SEARCH_XML = File.new(File.dirname(__FILE__) +"/search.xml")
|
7
|
+
SEARCH_XML = File.new(File.dirname(__FILE__) +"/search.xml")
|
8
|
+
YOUTUBE_STATUS_XML = File.new(File.dirname(__FILE__) +"/status.xml")
|
8
9
|
|
9
10
|
YT_VALIDATION = "yt:validation"
|
10
11
|
REQUIRED = "required"
|
@@ -18,6 +19,10 @@ class TestParser < Test::Unit::TestCase
|
|
18
19
|
@vf_parser = YouTubeG::Parser::VideoFeedParser.new(UPLOAD_XML.path)
|
19
20
|
@vf_parser_result = @vf_parser.parse
|
20
21
|
|
22
|
+
# video feed error
|
23
|
+
@vfe_parser = YouTubeG::Parser::VideoFeedParser.new(YOUTUBE_STATUS_XML.path)
|
24
|
+
@vfe_parser_result = @vfe_parser.parse
|
25
|
+
|
21
26
|
@vfs_parser = YouTubeG::Parser::VideosFeedParser.new(SEARCH_XML.path)
|
22
27
|
@vfs_parser_result = @vfs_parser.parse
|
23
28
|
end
|
@@ -93,7 +98,9 @@ class TestParser < Test::Unit::TestCase
|
|
93
98
|
assert_equal(320,video.thumbnails[3].width)
|
94
99
|
assert_equal("00:00:07.500",video.thumbnails[3].time)
|
95
100
|
|
96
|
-
assert_equal("http://www.youtube.com/watch?v=32_mQ0PRT9I",video.player_url)
|
101
|
+
assert_equal("http://www.youtube.com/watch?v=32_mQ0PRT9I",video.player_url)
|
102
|
+
|
103
|
+
assert_equal("http://gdata.youtube.com/feeds/api/users/speechboxmatt/uploads/32_mQ0PRT9I",video.status_url);
|
97
104
|
end
|
98
105
|
end
|
99
106
|
|
@@ -143,6 +150,21 @@ class TestParser < Test::Unit::TestCase
|
|
143
150
|
assert_equal("application/x-shockwave-flash",video.media_content[0].mime_type)
|
144
151
|
end
|
145
152
|
|
153
|
+
def test_video_feed_parser_with_error_status
|
154
|
+
video = @vfe_parser_result
|
155
|
+
assert_instance_of(YouTubeG::Model::Video, video)
|
156
|
+
|
157
|
+
assert_equal("http://gdata.youtube.com/feeds/api/videos/QR1sWOVqxoM",video.video_id)
|
158
|
+
|
159
|
+
# App control - state of the upload
|
160
|
+
assert_equal("yes", video.app_control.draft)
|
161
|
+
assert_equal("rejected", video.app_control.state)
|
162
|
+
# these populated only if errored
|
163
|
+
assert_equal("tooLong", video.app_control.reason)
|
164
|
+
assert_equal("http://www.youtube.com/t/community_guidelines", video.app_control.help_url)
|
165
|
+
assert_equal("Video is too long.", video.app_control.description)
|
166
|
+
end
|
167
|
+
|
146
168
|
def well_formed_errors_xml
|
147
169
|
errors_xml = ''
|
148
170
|
errors_xml << "<?xml version='1.0' encoding='UTF-8'?>"
|
@@ -169,4 +191,4 @@ class TestParser < Test::Unit::TestCase
|
|
169
191
|
errors_xml << " <location type='xpath'>media:group/media:title/text()</location>"
|
170
192
|
errors_xml << "</errors>"
|
171
193
|
end
|
172
|
-
end
|
194
|
+
end
|
data/test/test_video.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/youtube_g'
|
3
|
+
|
4
|
+
class TestVideo < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_processed_method_for_nil_state
|
7
|
+
# we assume no app_control from YT means processed.
|
8
|
+
video = YouTubeG::Model::Video.new({})
|
9
|
+
assert_equal(true, video.processed?)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_processed_method_for_processing_state
|
13
|
+
video = YouTubeG::Model::Video.new({:app_control => YouTubeG::Model::Video::AppControl.new({:state => 'processing'})})
|
14
|
+
assert_equal(false, video.processed?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_processed_method_for_rejected_state
|
18
|
+
video = YouTubeG::Model::Video.new({:app_control => YouTubeG::Model::Video::AppControl.new({:state => 'rejected'})})
|
19
|
+
assert_equal(false, video.processed?)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_processed_method_for_failed_state
|
23
|
+
video = YouTubeG::Model::Video.new({:app_control => YouTubeG::Model::Video::AppControl.new({:state => 'failed'})})
|
24
|
+
assert_equal(false, video.processed?)
|
25
|
+
end
|
26
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: msp-youtube-g
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.8.
|
4
|
+
version: 0.4.8.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Vitarana
|
@@ -58,9 +58,11 @@ files:
|
|
58
58
|
- integration-test/test_video.rb
|
59
59
|
- integration-test/test_video_search.rb
|
60
60
|
- test/test_parser.rb
|
61
|
+
- test/test_video.rb
|
61
62
|
- test/test_upload.rb
|
62
63
|
- test/search.xml
|
63
64
|
- test/upload.xml
|
65
|
+
- test/status.xml
|
64
66
|
has_rdoc: true
|
65
67
|
homepage: http://youtube-g.rubyforge.org/
|
66
68
|
post_install_message:
|