msp-youtube-g 0.4.5
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 +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
data/History.txt
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
== trunk
|
2
|
+
|
3
|
+
* Fix issue with REXML parsing of video upload response. [FiXato]
|
4
|
+
* Fix issue with response code comparison. [FiXato]
|
5
|
+
* Authcode is now retrieved for video uploads. [FiXato]
|
6
|
+
* Add basic support for uploading videos [thanks Joe Damato]
|
7
|
+
* Add basic support for related videos [tmm1]
|
8
|
+
* Improve docs for order_by attribute [thanks Jason Arora]
|
9
|
+
* Added support for the "racy" parameter (choices are "include" or "exclude") [thanks Jason Arora]
|
10
|
+
* Add missing attribute reader for description [tmm1]
|
11
|
+
* Fix issue with missing yt:statistics and viewCount [tmm1]
|
12
|
+
* Allow Client#video_by to take either a url or a video id [tmm1]
|
13
|
+
|
14
|
+
== 0.4.1 / 2008-02-11
|
15
|
+
|
16
|
+
* Added 3GPP video format [shane]
|
17
|
+
* Fixed tests [shane]
|
18
|
+
|
19
|
+
== 0.4.0 / 2007-12-18
|
20
|
+
|
21
|
+
* Fixed API projection in search URL [Pete Higgins]
|
22
|
+
* Fixed embeddable video searching [Pete Higgins]
|
23
|
+
* Fixed video embeddable detection [Pete Higgins]
|
24
|
+
* Fixed unique id hyphen detection [Pete Higgins, Chris Taggart]
|
25
|
+
|
26
|
+
== 0.3.0 / 2007-09-17
|
27
|
+
|
28
|
+
* Initial public release
|
29
|
+
* Birthday!
|
30
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
TODO.txt
|
6
|
+
lib/youtube_g.rb
|
7
|
+
lib/youtube_g/client.rb
|
8
|
+
lib/youtube_g/logger.rb
|
9
|
+
lib/youtube_g/model/author.rb
|
10
|
+
lib/youtube_g/model/category.rb
|
11
|
+
lib/youtube_g/model/contact.rb
|
12
|
+
lib/youtube_g/model/content.rb
|
13
|
+
lib/youtube_g/model/playlist.rb
|
14
|
+
lib/youtube_g/model/rating.rb
|
15
|
+
lib/youtube_g/model/thumbnail.rb
|
16
|
+
lib/youtube_g/model/user.rb
|
17
|
+
lib/youtube_g/model/video.rb
|
18
|
+
lib/youtube_g/parser.rb
|
19
|
+
lib/youtube_g/record.rb
|
20
|
+
lib/youtube_g/request/video_search.rb
|
21
|
+
lib/youtube_g/request/video_upload.rb
|
22
|
+
lib/youtube_g/response/video_search.rb
|
23
|
+
test/test_client.rb
|
24
|
+
test/test_video.rb
|
25
|
+
test/test_video_search.rb
|
data/README.txt
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
youtube-g
|
2
|
+
by Shane Vitarana and Walter Korman
|
3
|
+
|
4
|
+
Rubyforge: http://rubyforge.org/projects/youtube-g/
|
5
|
+
RDoc: http://youtube-g.rubyforge.org/
|
6
|
+
Google Group: http://groups.google.com/group/ruby-youtube-library
|
7
|
+
|
8
|
+
== DESCRIPTION:
|
9
|
+
|
10
|
+
youtube-g is a pure Ruby client for the YouTube GData API. It provides an easy
|
11
|
+
way to access the latest YouTube video search results from your own programs.
|
12
|
+
In comparison with the earlier Youtube search interfaces, this new API and
|
13
|
+
library offers much-improved flexibility around executing complex search
|
14
|
+
queries to obtain well-targeted video search results.
|
15
|
+
|
16
|
+
More detail on the underlying source Google-provided API is available at:
|
17
|
+
|
18
|
+
http://code.google.com/apis/youtube/overview.html
|
19
|
+
|
20
|
+
== FEATURES/PROBLEMS:
|
21
|
+
|
22
|
+
* Aims to be in parity with Google's YouTube GData API. Core functionality
|
23
|
+
is currently present -- work is in progress to fill in the rest.
|
24
|
+
|
25
|
+
== SYNOPSIS:
|
26
|
+
|
27
|
+
Create a client:
|
28
|
+
|
29
|
+
require 'youtube_g'
|
30
|
+
client = YouTubeG::Client.new
|
31
|
+
|
32
|
+
Basic queries:
|
33
|
+
|
34
|
+
client.videos_by(:query => "penguin")
|
35
|
+
client.videos_by(:tags => ['tiger', 'leopard'])
|
36
|
+
client.videos_by(:categories => [:news, :sports])
|
37
|
+
client.videos_by(:categories => [:news, :sports], :tags => ['soccer', 'football'])
|
38
|
+
client.videos_by(:user => 'liz')
|
39
|
+
|
40
|
+
Standard feeds:
|
41
|
+
|
42
|
+
client.videos_by(:most_viewed)
|
43
|
+
client.videos_by(:top_rated, :time => :today)
|
44
|
+
|
45
|
+
Advanced queries (with boolean operators OR (either), AND (include), NOT (exclude)):
|
46
|
+
|
47
|
+
client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] }, :tags => { :include => ['football'], :exclude => ['soccer'] })
|
48
|
+
|
49
|
+
|
50
|
+
== REQUIREMENTS:
|
51
|
+
|
52
|
+
* None
|
53
|
+
|
54
|
+
== INSTALL:
|
55
|
+
|
56
|
+
* sudo gem install youtube-g
|
57
|
+
|
58
|
+
== LICENSE:
|
59
|
+
|
60
|
+
MIT License
|
61
|
+
|
62
|
+
Copyright (c) 2007 Shane Vitarana and Walter Korman
|
63
|
+
|
64
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
65
|
+
a copy of this software and associated documentation files (the
|
66
|
+
'Software'), to deal in the Software without restriction, including
|
67
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
68
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
69
|
+
permit persons to whom the Software is furnished to do so, subject to
|
70
|
+
the following conditions:
|
71
|
+
|
72
|
+
The above copyright notice and this permission notice shall be
|
73
|
+
included in all copies or substantial portions of the Software.
|
74
|
+
|
75
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
76
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
77
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
78
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
79
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
80
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
81
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
require 'lib/youtube_g'
|
4
|
+
|
5
|
+
Hoe.new('youtube-g', YouTubeG::VERSION) do |p|
|
6
|
+
p.rubyforge_name = 'youtube-g'
|
7
|
+
p.author = ["Shane Vitarana", "Walter Korman", "Aman Gupta", "Filip H.F. Slagter"]
|
8
|
+
p.email = 'shanev@gmail.com'
|
9
|
+
p.summary = 'Ruby client for the YouTube GData API'
|
10
|
+
p.description = p.paragraphs_of('README.txt', 2..8).join("\n\n")
|
11
|
+
p.url = 'http://rubyforge.org/projects/youtube-g/'
|
12
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
13
|
+
p.remote_rdoc_dir = ''
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Tag release'
|
17
|
+
task :tag do
|
18
|
+
svn_root = 'svn+ssh://drummr77@rubyforge.org/var/svn/youtube-g'
|
19
|
+
sh %(svn cp #{svn_root}/trunk #{svn_root}/tags/release-#{YouTubeG::VERSION} -m "Tag YouTubeG release #{YouTubeG::VERSION}")
|
20
|
+
end
|
data/TODO.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
[ ] stub out http request/response cycle for tests
|
2
|
+
[ ] consider defaulting the client to no-logger, rather than outputting to STDOUT.
|
3
|
+
[ ] allow specifying values as single items where you don't need to wrap in a list, e.g. :tags => :chickens instead of :tags => [ 'chickens' ]
|
4
|
+
[ ] make sure symbols will work as well as tags everywhere (again, :tags => :chickens is same as :tags => 'chickens')
|
5
|
+
[ ] figure out better structure for class/file (either rename request/video_search.rb or split into one class per file again)
|
6
|
+
[ ] restore spaces after method def names
|
7
|
+
[ ] use a proxy for testing with static sample result xml so we have repeatable tests
|
8
|
+
[ ] Clean up tests using Shoulda to define contexts
|
9
|
+
[ ] Consolidate requires
|
10
|
+
[ ] Allow :category and :categories for query DSL
|
11
|
+
[ ] Exception handling
|
12
|
+
|
13
|
+
== API Features TODO
|
14
|
+
|
15
|
+
[ ] Profile feed parsing
|
16
|
+
[ ] Playlist feeds
|
17
|
+
[ ] User subscriptions
|
18
|
+
[ ] Video comments
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class YouTubeG
|
4
|
+
class Client
|
5
|
+
attr_accessor :logger
|
6
|
+
|
7
|
+
def initialize(logger=Logger.new(STDOUT))
|
8
|
+
@logger = logger
|
9
|
+
end
|
10
|
+
|
11
|
+
# Params can be one of :most_viewed, :top_rated, :recently_featured, :watch_on_mobile
|
12
|
+
# Or :tags, :categories, :query, :user
|
13
|
+
def videos_by(params, options={})
|
14
|
+
if params.respond_to?(:to_hash) and not params[:user]
|
15
|
+
request = YouTubeG::Request::VideoSearch.new(params)
|
16
|
+
|
17
|
+
elsif (params.respond_to?(:to_hash) && params[:user]) || (params == :favorites)
|
18
|
+
request = YouTubeG::Request::UserSearch.new(params, options)
|
19
|
+
|
20
|
+
else
|
21
|
+
request = YouTubeG::Request::StandardSearch.new(params, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
logger.debug "Submitting request [url=#{request.url}]."
|
25
|
+
parser = YouTubeG::Parser::VideosFeedParser.new(request.url)
|
26
|
+
parser.parse
|
27
|
+
end
|
28
|
+
|
29
|
+
def video_by(vid)
|
30
|
+
video_id = vid =~ /^http/ ? vid : "http://gdata.youtube.com/feeds/videos/#{vid}"
|
31
|
+
parser = YouTubeG::Parser::VideoFeedParser.new(video_id)
|
32
|
+
parser.parse
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class YouTubeG
|
4
|
+
|
5
|
+
# TODO: Why is this needed? Does this happen if running standalone w/o Rails?
|
6
|
+
# Anyway, isn't it easier to debug w/o the really long timestamp & log level?
|
7
|
+
# How often do you look at the timestamp and log level? Wouldn't it be nice to
|
8
|
+
# see your logger output first?
|
9
|
+
|
10
|
+
# Extension of the base ruby Logger class to restore the default log
|
11
|
+
# level and timestamp formatting which is so rudely taken forcibly
|
12
|
+
# away from us by the Rails app's use of the ActiveSupport library
|
13
|
+
# that wholesale-ly modifies the Logger's format_message method.
|
14
|
+
#
|
15
|
+
class Logger < ::Logger
|
16
|
+
private
|
17
|
+
begin
|
18
|
+
# restore original log formatting to un-screw the screwage that is
|
19
|
+
# foisted upon us by the activesupport library's clean_logger.rb
|
20
|
+
alias format_message old_format_message
|
21
|
+
|
22
|
+
rescue NameError
|
23
|
+
# nothing for now -- this means we didn't need to alias since the
|
24
|
+
# method wasn't overridden
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Model
|
3
|
+
class User < YouTubeG::Record
|
4
|
+
attr_reader :age
|
5
|
+
attr_reader :books
|
6
|
+
attr_reader :company
|
7
|
+
attr_reader :gender
|
8
|
+
attr_reader :hobbies
|
9
|
+
attr_reader :hometown
|
10
|
+
attr_reader :location
|
11
|
+
attr_reader :movies
|
12
|
+
attr_reader :music
|
13
|
+
attr_reader :occupation
|
14
|
+
attr_reader :relationship
|
15
|
+
attr_reader :school
|
16
|
+
attr_reader :description
|
17
|
+
attr_reader :username
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Model
|
3
|
+
class Video < YouTubeG::Record
|
4
|
+
# Describes the various file formats in which a Youtube video may be
|
5
|
+
# made available and allows looking them up by format code number.
|
6
|
+
#
|
7
|
+
class Format
|
8
|
+
@@formats = Hash.new
|
9
|
+
|
10
|
+
def initialize(format_code, name)
|
11
|
+
@format_code = format_code
|
12
|
+
@name = name
|
13
|
+
|
14
|
+
@@formats[format_code] = self
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.by_code(format_code)
|
18
|
+
@@formats[format_code]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Flash format on YouTube site. All videos are available in this
|
22
|
+
# format.
|
23
|
+
#
|
24
|
+
FLASH = YouTubeG::Model::Video::Format.new(0, :flash)
|
25
|
+
|
26
|
+
# RTSP streaming URL for mobile video playback. H.263 video (176x144)
|
27
|
+
# and AMR audio.
|
28
|
+
#
|
29
|
+
RTSP = YouTubeG::Model::Video::Format.new(1, :rtsp)
|
30
|
+
|
31
|
+
# HTTP URL to the embeddable player (SWF) for this video. This format
|
32
|
+
# is not available for a video that is not embeddable.
|
33
|
+
#
|
34
|
+
SWF = YouTubeG::Model::Video::Format.new(5, :swf)
|
35
|
+
|
36
|
+
THREE_GPP = YouTubeG::Model::Video::Format.new(6, :three_gpp)
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :duration
|
40
|
+
attr_reader :noembed
|
41
|
+
attr_reader :position
|
42
|
+
attr_reader :racy
|
43
|
+
attr_reader :statistics
|
44
|
+
|
45
|
+
attr_reader :video_id
|
46
|
+
attr_reader :published_at
|
47
|
+
attr_reader :updated_at
|
48
|
+
attr_reader :categories
|
49
|
+
attr_reader :keywords
|
50
|
+
attr_reader :description
|
51
|
+
attr_reader :title
|
52
|
+
attr_reader :html_content
|
53
|
+
attr_reader :author
|
54
|
+
|
55
|
+
# YouTubeG::Model::Content records describing the individual media content
|
56
|
+
# data available for this video. Most, but not all, videos offer this.
|
57
|
+
attr_reader :media_content
|
58
|
+
|
59
|
+
attr_reader :thumbnails # YouTubeG::Model::Thumbnail records
|
60
|
+
attr_reader :player_url
|
61
|
+
attr_reader :rating
|
62
|
+
attr_reader :view_count
|
63
|
+
|
64
|
+
# TODO:
|
65
|
+
# self atom feed
|
66
|
+
# alternate youtube watch url
|
67
|
+
# responses feed
|
68
|
+
# comments feedLink
|
69
|
+
|
70
|
+
def related
|
71
|
+
YouTubeG::Parser::VideosFeedParser.new("http://gdata.youtube.com/feeds/api/videos/#{unique_id}/related").parse
|
72
|
+
end
|
73
|
+
|
74
|
+
# For convenience, the video_id with the URL stripped out, useful for searching for the video again
|
75
|
+
# without having to store it anywhere. A regular query search, with this id will return the same video.
|
76
|
+
# http://gdata.youtube.com/feeds/videos/ZTUVgYoeN_o
|
77
|
+
def unique_id
|
78
|
+
video_id[/videos\/([^<]+)/, 1]
|
79
|
+
end
|
80
|
+
|
81
|
+
def can_embed?
|
82
|
+
not @noembed
|
83
|
+
end
|
84
|
+
|
85
|
+
def default_media_content
|
86
|
+
@media_content.find { |c| c.is_default? }
|
87
|
+
end
|
88
|
+
|
89
|
+
def embed_html(width = 425, height = 350)
|
90
|
+
<<EDOC
|
91
|
+
<object width="#{width}" height="#{height}">
|
92
|
+
<param name="movie" value="#{embed_url}"></param>
|
93
|
+
<param name="wmode" value="transparent"></param>
|
94
|
+
<embed src="#{embed_url}" type="application/x-shockwave-flash"
|
95
|
+
wmode="transparent" width="#{width}" height="#{height}"></embed>
|
96
|
+
</object>
|
97
|
+
EDOC
|
98
|
+
end
|
99
|
+
|
100
|
+
def embed_url
|
101
|
+
@player_url.sub('watch?', '').sub('=', '/')
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -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
|