agiley-youtube-g 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ * Implement video update and video delete in Upload
2
+ * Refactor the uploader slightly
3
+ * Use Builder to generate XML packets
4
+ * Use a faster Camping-based URL escaper (also avoid cgi.rb)
5
+ * Removed the logger nightmare, use YouTubeG.logger for everything whatever that may be
6
+ * Use streams for file uploads instead of in-memory strings
7
+
8
+ == 0.5.0 / 2009-01-07
9
+
10
+ * Fixed bug in user favorites (thanks Pius Uzamere)
11
+
12
+ == 0.4.9.9 / 2008-09-01
13
+
14
+ * Add Geodata information (thanks Jose Galisteo)
15
+ * Added :page and :per_page options, this allows easier usage of the will_paginate
16
+ plugin with the library. The :offset and :max_results options are no longer available. [Daniel Insley]
17
+ * Added ability to get video responses on the instances of the YouTube::Model::Video object. [Daniel Insley]
18
+ * Added and improved the existing documentation [Daniel Insley]
19
+ * Fixed usage of deprecated yt:racy, now using media:rating [Daniel Insley]
20
+ * Renamed can_embed? method to embeddable? [Daniel Insley]
21
+ * Added ability for padingation and ordering on standard feeds. [Daniel Insley]
22
+ * Add error-handling for video upload errors. [FiXato]
23
+ * Add error-handling for authentication errors from YouTube during video upload. [FiXato]
24
+ * Add support for making videos private upon video upload. [FiXato]
25
+ * Fix issue with REXML parsing of video upload response. [FiXato]
26
+ * Fix issue with response code comparison. [FiXato]
27
+ * Authcode is now retrieved for video uploads. [FiXato]
28
+ * Add basic support for uploading videos [thanks Joe Damato]
29
+ * Add basic support for related videos [tmm1]
30
+ * Improve docs for order_by attribute [thanks Jason Arora]
31
+ * Added support for the "racy" parameter (choices are "include" or "exclude") [thanks Jason Arora]
32
+ * Add missing attribute reader for description [tmm1]
33
+ * Fix issue with missing yt:statistics and viewCount [tmm1]
34
+ * Allow Client#video_by to take either a url or a video id [tmm1]
35
+
36
+ == 0.4.1 / 2008-02-11
37
+
38
+ * Added 3GPP video format [shane]
39
+ * Fixed tests [shane]
40
+
41
+ == 0.4.0 / 2007-12-18
42
+
43
+ * Fixed API projection in search URL [Pete Higgins]
44
+ * Fixed embeddable video searching [Pete Higgins]
45
+ * Fixed video embeddable detection [Pete Higgins]
46
+ * Fixed unique id hyphen detection [Pete Higgins, Chris Taggart]
47
+
48
+ == 0.3.0 / 2007-09-17
49
+
50
+ * Initial public release
51
+ * Birthday!
52
+
@@ -0,0 +1,34 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ TODO.txt
6
+ lib/youtube_g.rb
7
+ lib/youtube_g/chain_io.rb
8
+ lib/youtube_g/client.rb
9
+ lib/youtube_g/model/author.rb
10
+ lib/youtube_g/model/category.rb
11
+ lib/youtube_g/model/comment.rb
12
+ lib/youtube_g/model/contact.rb
13
+ lib/youtube_g/model/content.rb
14
+ lib/youtube_g/model/playlist.rb
15
+ lib/youtube_g/model/rating.rb
16
+ lib/youtube_g/model/thumbnail.rb
17
+ lib/youtube_g/model/user.rb
18
+ lib/youtube_g/model/video.rb
19
+ lib/youtube_g/parser.rb
20
+ lib/youtube_g/record.rb
21
+ lib/youtube_g/request/base_search.rb
22
+ lib/youtube_g/request/comments_search.rb
23
+ lib/youtube_g/request/standard_search.rb
24
+ lib/youtube_g/request/user_search.rb
25
+ lib/youtube_g/request/video_search.rb
26
+ lib/youtube_g/request/video_upload.rb
27
+ lib/youtube_g/response/comments_search.rb
28
+ lib/youtube_g/response/video_search.rb
29
+ lib/youtube_g/version.rb
30
+ test/helper.rb
31
+ test/test_chain_io.rb
32
+ test/test_client.rb
33
+ test/test_video.rb
34
+ test/test_video_search.rb
@@ -0,0 +1,102 @@
1
+ = youtube-g
2
+
3
+ http://youtube-g.rubyforge.org/
4
+
5
+ == DESCRIPTION:
6
+
7
+ youtube-g is a pure Ruby client for the YouTube GData API. It provides an easy
8
+ way to access the latest YouTube video search results from your own programs.
9
+ In comparison with the earlier Youtube search interfaces, this new API and
10
+ library offers much-improved flexibility around executing complex search
11
+ queries to obtain well-targeted video search results.
12
+
13
+ More detail on the underlying source Google-provided API is available at:
14
+
15
+ http://code.google.com/apis/youtube/overview.html
16
+
17
+ == AUTHORS
18
+
19
+ Shane Vitarana and Walter Korman
20
+
21
+ == WHERE TO GET HELP
22
+
23
+ http://rubyforge.org/projects/youtube-g
24
+ http://groups.google.com/group/ruby-youtube-library
25
+
26
+ == FEATURES/PROBLEMS:
27
+
28
+ * Aims to be in parity with Google's YouTube GData API. Core functionality
29
+ is currently present -- work is in progress to fill in the rest.
30
+
31
+ == SYNOPSIS:
32
+
33
+ Create a client:
34
+
35
+ require 'youtube_g'
36
+ client = YouTubeG::Client.new
37
+
38
+ Basic queries:
39
+
40
+ client.videos_by(:query => "penguin")
41
+ client.videos_by(:query => "penguin", :page => 2, :per_page => 15)
42
+ client.videos_by(:tags => ['tiger', 'leopard'])
43
+ client.videos_by(:categories => [:news, :sports])
44
+ client.videos_by(:categories => [:news, :sports], :tags => ['soccer', 'football'])
45
+ client.videos_by(:user => 'liz')
46
+ client.videos_by(:favorites, :user => 'liz')
47
+
48
+ Standard feeds:
49
+
50
+ client.videos_by(:most_viewed)
51
+ client.videos_by(:most_linked, :page => 3)
52
+ client.videos_by(:top_rated, :time => :today)
53
+
54
+ Advanced queries (with boolean operators OR (either), AND (include), NOT (exclude)):
55
+
56
+ client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] }, :tags => { :include => ['football'], :exclude => ['soccer'] })
57
+
58
+ Comment feeds for videos:
59
+ client.comments_for('some_youtube_id').comments
60
+ video = client.video_by('some_youtube_id')
61
+ video.comments_seach(:page => 2, :per_page => 12).comments
62
+
63
+ == LOGGING
64
+
65
+ YouTubeG passes all logs through the logger variable on the class itself. In Rails context, assign the Rails logger to that variable to collect the messages
66
+ (don't forget to set the level to debug):
67
+
68
+ YouTubeG.logger = RAILS_DEFAULT_LOGGER
69
+ RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
70
+
71
+ == REQUIREMENTS:
72
+
73
+ * builder gem
74
+
75
+ == INSTALL:
76
+
77
+ * sudo gem install youtube-g
78
+
79
+ == LICENSE:
80
+
81
+ MIT License
82
+
83
+ Copyright (c) 2007 Shane Vitarana and Walter Korman
84
+
85
+ Permission is hereby granted, free of charge, to any person obtaining
86
+ a copy of this software and associated documentation files (the
87
+ 'Software'), to deal in the Software without restriction, including
88
+ without limitation the rights to use, copy, modify, merge, publish,
89
+ distribute, sublicense, and/or sell copies of the Software, and to
90
+ permit persons to whom the Software is furnished to do so, subject to
91
+ the following conditions:
92
+
93
+ The above copyright notice and this permission notice shall be
94
+ included in all copies or substantial portions of the Software.
95
+
96
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
97
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
98
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
99
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
100
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
101
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
102
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require 'lib/youtube_g/version'
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", "msp"]
8
+ p.email = 'shanev@gmail.com'
9
+ p.summary = 'Ruby client for the YouTube GData API'
10
+ p.url = 'http://rubyforge.org/projects/youtube-g/'
11
+ p.extra_deps << 'builder'
12
+ p.remote_rdoc_dir = ''
13
+ end
14
+
15
+ desc 'Tag release'
16
+ task :tag do
17
+ svn_root = 'svn+ssh://drummr77@rubyforge.org/var/svn/youtube-g'
18
+ sh %(svn cp #{svn_root}/trunk #{svn_root}/tags/release-#{YouTubeG::VERSION} -m "Tag YouTubeG release #{YouTubeG::VERSION}")
19
+ end
20
+
21
+ desc 'Load the library in an IRB session'
22
+ task :console do
23
+ sh %(irb -r lib/youtube_g.rb)
24
+ end
@@ -0,0 +1,16 @@
1
+ [ ] stub out http request/response cycle for tests
2
+ [ ] allow specifying values as single items where you don't need to wrap in a list, e.g. :tags => :chickens instead of :tags => [ 'chickens' ]
3
+ [ ] make sure symbols will work as well as tags everywhere (again, :tags => :chickens is same as :tags => 'chickens')
4
+ [ ] figure out better structure for class/file (either rename request/video_search.rb or split into one class per file again)
5
+ [ ] restore spaces after method def names
6
+ [ ] use a proxy for testing with static sample result xml so we have repeatable tests
7
+ [ ] Clean up tests using Shoulda to define contexts
8
+ [ ] Allow :category and :categories for query DSL
9
+ [ ] Exception handling
10
+
11
+ == API Features TODO
12
+
13
+ [ ] Profile feed parsing
14
+ [ ] Playlist feeds
15
+ [ ] User subscriptions
16
+ [ ] Video comments
@@ -0,0 +1,70 @@
1
+ require 'logger'
2
+ require 'open-uri'
3
+ require 'net/https'
4
+ require 'digest/md5'
5
+ require 'rexml/document'
6
+ require 'builder'
7
+
8
+ class YouTubeG
9
+
10
+ # Base error class for the extension
11
+ class Error < RuntimeError
12
+ end
13
+
14
+ # URL-escape a string. Stolen from Camping (wonder how many Ruby libs in the wild can say the same)
15
+ def self.esc(s) #:nodoc:
16
+ s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+')
17
+ end
18
+
19
+ # Set the logger for the library
20
+ def self.logger=(any_logger)
21
+ @logger = any_logger
22
+ end
23
+
24
+ # Get the logger for the library (by default will log to STDOUT). TODO: this is where we grab the Rails logger too
25
+ def self.logger
26
+ @logger ||= create_default_logger
27
+ end
28
+
29
+ # Gets mixed into the classes to provide the logger method
30
+ module Logging #:nodoc:
31
+
32
+ # Return the base logger set for the library
33
+ def logger
34
+ YouTubeG.logger
35
+ end
36
+ end
37
+
38
+ private
39
+ def self.create_default_logger
40
+ logger = Logger.new(STDOUT)
41
+ logger.level = Logger::DEBUG
42
+ logger
43
+ end
44
+ end
45
+
46
+ %w(
47
+ version
48
+ client
49
+ record
50
+ parser
51
+ model/author
52
+ model/category
53
+ model/comment
54
+ model/contact
55
+ model/content
56
+ model/playlist
57
+ model/rating
58
+ model/thumbnail
59
+ model/user
60
+ model/video
61
+ request/base_search
62
+ request/comments_search
63
+ request/user_search
64
+ request/standard_search
65
+ request/video_upload
66
+ request/video_search
67
+ response/comments_search
68
+ response/video_search
69
+ chain_io
70
+ ).each{|m| require File.dirname(__FILE__) + '/youtube_g/' + m }
@@ -0,0 +1,71 @@
1
+ require 'delegate'
2
+ #:stopdoc:
3
+
4
+ # Stream wrapper that reads IOs in succession. Can be fed to Net::HTTP as post body stream. We use it internally to stream file content
5
+ # instead of reading whole video files into memory. Strings passed to the constructor will be wrapped in StringIOs. By default it will auto-close
6
+ # file handles when they have been read completely to prevent our uploader from leaking file handles
7
+ #
8
+ # chain = ChainIO.new(File.open(__FILE__), File.open('/etc/passwd'), "abcd")
9
+ class YouTubeG::ChainIO
10
+ attr_accessor :autoclose
11
+
12
+ def initialize(*any_ios)
13
+ @autoclose = true
14
+ @chain = any_ios.flatten.map{|e| e.respond_to?(:read) ? e : StringIO.new(e.to_s) }
15
+ end
16
+
17
+ def read(buffer_size = 1024)
18
+ # Read off the first element in the stack
19
+ current_io = @chain.shift
20
+ return false if !current_io
21
+
22
+ buf = current_io.read(buffer_size)
23
+ if !buf && @chain.empty? # End of streams
24
+ release_handle(current_io) if @autoclose
25
+ false
26
+ elsif !buf # This IO is depleted, but next one is available
27
+ release_handle(current_io) if @autoclose
28
+ read(buffer_size)
29
+ elsif buf.length < buffer_size # This IO is depleted, but we were asked for more
30
+ release_handle(current_io) if @autoclose
31
+ buf + (read(buffer_size - buf.length) || '') # and recurse
32
+ else # just return the buffer
33
+ @chain.unshift(current_io) # put the current back
34
+ buf
35
+ end
36
+ end
37
+
38
+ # Predict the length of all embedded IOs. Will automatically send file size.
39
+ def expected_length
40
+ @chain.inject(0) do | len, io |
41
+ if io.respond_to?(:length)
42
+ len + (io.length - io.pos)
43
+ elsif io.is_a?(File)
44
+ len + File.size(io.path) - io.pos
45
+ else
46
+ raise "Cannot predict length of #{io.inspect}"
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+ def release_handle(io)
53
+ io.close if io.respond_to?(:close)
54
+ end
55
+ end
56
+
57
+ # Net::HTTP only can send chunks of 1024 bytes. This is very inefficient, so we have a spare IO that will send more when asked for 1024.
58
+ # We use delegation because the read call is recursive.
59
+ class YouTubeG::GreedyChainIO < DelegateClass(YouTubeG::ChainIO)
60
+ BIG_CHUNK = 512 * 1024 # 500 kb
61
+
62
+ def initialize(*with_ios)
63
+ __setobj__(YouTubeG::ChainIO.new(with_ios))
64
+ end
65
+
66
+ def read(any_buffer_size)
67
+ __getobj__.read(BIG_CHUNK)
68
+ end
69
+ end
70
+
71
+ #:startdoc:
@@ -0,0 +1,100 @@
1
+ class YouTubeG
2
+ class Client
3
+ include YouTubeG::Logging
4
+
5
+ # Previously this was a logger instance but we now do it globally
6
+ def initialize(legacy_debug_flag = nil)
7
+ end
8
+
9
+ # Retrieves an array of standard feed, custom query, or user videos.
10
+ #
11
+ # === Parameters
12
+ # If fetching videos for a standard feed:
13
+ # params<Symbol>:: Accepts a symbol of :top_rated, :top_favorites, :most_viewed,
14
+ # :most_popular, :most_recent, :most_discussed, :most_linked,
15
+ # :most_responded, :recently_featured, and :watch_on_mobile.
16
+ #
17
+ # You can find out more specific information about what each standard feed provides
18
+ # by visiting: http://code.google.com/apis/youtube/reference.html#Standard_feeds
19
+ #
20
+ # options<Hash> (optional):: Accepts the options of :time, :page (default is 1),
21
+ # and :per_page (default is 25). :offset and :max_results
22
+ # can also be passed for a custom offset.
23
+ #
24
+ # If fetching videos by tags, categories, query:
25
+ # params<Hash>:: Accepts the keys :tags, :categories, :query, :order_by,
26
+ # :author, :racy, :response_format, :video_format, :page (default is 1),
27
+ # and :per_page(default is 25)
28
+ #
29
+ # options<Hash>:: Not used. (Optional)
30
+ #
31
+ # If fetching videos for a particular user:
32
+ # params<Hash>:: Key of :user with a value of the username.
33
+ # options<Hash>:: Not used. (Optional)
34
+ # === Returns
35
+ # YouTubeG::Response::VideoSearch
36
+ def videos_by(params, options={})
37
+ request_params = params.respond_to?(:to_hash) ? params : options
38
+ request_params[:page] = integer_or_default(request_params[:page], 1)
39
+
40
+ unless request_params[:max_results]
41
+ request_params[:max_results] = integer_or_default(request_params[:per_page], 25)
42
+ end
43
+
44
+ unless request_params[:offset]
45
+ request_params[:offset] = calculate_offset(request_params[:page], request_params[:max_results] )
46
+ end
47
+
48
+ if params.respond_to?(:to_hash) and not params[:user]
49
+ request = YouTubeG::Request::VideoSearch.new(request_params)
50
+ elsif (params.respond_to?(:to_hash) && params[:user]) || (params == :favorites)
51
+ request = YouTubeG::Request::UserSearch.new(params, request_params)
52
+ else
53
+ request = YouTubeG::Request::StandardSearch.new(params, request_params)
54
+ end
55
+
56
+ logger.debug "Submitting request [url=#{request.url}]."
57
+ parser = YouTubeG::Parser::VideosFeedParser.new(request.url)
58
+ parser.parse
59
+ end
60
+
61
+ # Retrieves a single YouTube video.
62
+ #
63
+ # === Parameters
64
+ # vid<String>:: The ID or URL of the video that you'd like to retrieve.
65
+ #
66
+ # === Returns
67
+ # YouTubeG::Model::Video
68
+ def video_by(vid)
69
+ video_id = vid =~ /^http/ ? vid : "http://gdata.youtube.com/feeds/videos/#{vid}"
70
+ parser = YouTubeG::Parser::VideoFeedParser.new(video_id)
71
+ parser.parse
72
+ end
73
+
74
+ # Retrieves a single YouTube video.
75
+ #
76
+ # === Parameters
77
+ # vid<String>:: The ID of the video whose contents you'd like to retrieve
78
+ #
79
+ # options<Hash> (optional):: Accepts the options of :page (default is 1),
80
+ # and :per_page (default is 25). :offset and :max_results
81
+ # can also be passed for a custom offset.
82
+ # === Returns
83
+ # YouTubeG::Response::CommentsSearch
84
+ def comments_for(vid, options={})
85
+ url = YouTubeG::Request::CommentsSearch.new(vid, options).url
86
+ YouTubeG::Parser::CommentsFeedParser.new(url).parse
87
+ end
88
+
89
+ private
90
+
91
+ def calculate_offset(page, per_page)
92
+ page == 1 ? 1 : ((per_page * page) - per_page + 1)
93
+ end
94
+
95
+ def integer_or_default(value, default)
96
+ value = value.to_i
97
+ value > 0 ? value : default
98
+ end
99
+ end
100
+ end