youtube_it 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -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 YouTubeIt.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
+
data/Manifest.txt ADDED
@@ -0,0 +1,32 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ TODO.txt
6
+ lib/youtube_it.rb
7
+ lib/youtube_it/chain_io.rb
8
+ lib/youtube_it/client.rb
9
+ lib/youtube_it/model/author.rb
10
+ lib/youtube_it/model/category.rb
11
+ lib/youtube_it/model/contact.rb
12
+ lib/youtube_it/model/content.rb
13
+ lib/youtube_it/model/playlist.rb
14
+ lib/youtube_it/model/rating.rb
15
+ lib/youtube_it/model/thumbnail.rb
16
+ lib/youtube_it/model/user.rb
17
+ lib/youtube_it/model/video.rb
18
+ lib/youtube_it/parser.rb
19
+ lib/youtube_it/record.rb
20
+ lib/youtube_it/request/base_search.rb
21
+ lib/youtube_it/request/standard_search.rb
22
+ lib/youtube_it/request/user_search.rb
23
+ lib/youtube_it/request/video_search.rb
24
+ lib/youtube_it/request/video_upload.rb
25
+ lib/youtube_it/response/video_search.rb
26
+ lib/youtube_it/version.rb
27
+ test/helper.rb
28
+ test/test_chain_io.rb
29
+ test/test_client.rb
30
+ test/test_video.rb
31
+ test/test_video_search.rb
32
+
data/README.txt ADDED
@@ -0,0 +1,123 @@
1
+ == DESCRIPTION:
2
+
3
+ youtube_it is a pure Ruby client for the YouTube GData API. It provides an easy
4
+ way to access the latest YouTube video search results from your own programs.
5
+ In comparison with the earlier Youtube search interfaces, this new API and
6
+ library offers much-improved flexibility around executing complex search
7
+ queries to obtain well-targeted video search results.
8
+
9
+ == SYNOPSIS:
10
+
11
+ Create a client:
12
+
13
+ require 'youtube_it'
14
+ client = YouTubeIt::Client.new
15
+
16
+ Basic queries:
17
+
18
+ client.videos_by(:query => "penguin")
19
+ client.videos_by(:query => "penguin", :page => 2, :per_page => 15)
20
+ client.videos_by(:tags => ['tiger', 'leopard'])
21
+ client.videos_by(:categories => [:news, :sports])
22
+ client.videos_by(:categories => [:news, :sports], :tags => ['soccer', 'football'])
23
+ client.videos_by(:user => 'liz')
24
+ client.videos_by(:favorites, :user => 'liz')
25
+ client.video_by("FQK1URcxmb4")
26
+ client.video_by("chebyte","FQK1URcxmb4")
27
+
28
+ Standard feeds:
29
+
30
+ client.videos_by(:most_viewed)
31
+ client.videos_by(:most_linked, :page => 3)
32
+ client.videos_by(:top_rated, :time => :today)
33
+
34
+ Advanced queries (with boolean operators OR (either), AND (include), NOT (exclude)):
35
+
36
+ client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] }, :tags => { :include => ['football'], :exclude => ['soccer'] })
37
+
38
+
39
+ Upload videos:
40
+ You need on youtube account and developer key
41
+ You can get these keys at the http://code.google.com/apis/youtube/dashboard/
42
+
43
+ client = YouTubeIt::Client.new("youtube_username", "youtube_passwd", "developer_key")
44
+
45
+ * upload video
46
+
47
+ client.video_upload(File.open("test.mov"), :title => "test",:description => 'some description', :category => 'People',:keywords => %w[cool blah test])
48
+
49
+ * update video
50
+
51
+ client.video_update("FQK1URcxmb4", :title => "new test",:description => 'new description', :category => 'People',:keywords => %w[cool blah test])
52
+
53
+ * delete video
54
+
55
+ client.video_delete("FQK1URcxmb4")
56
+
57
+
58
+ == Upload videos from browser:
59
+
60
+ For upload a video from browser you need make a form upload with the followings params
61
+
62
+ upload_token(params, nexturl)
63
+
64
+ params => params like :title => "title", :description => "description", :category => "People", :tags => ["test"]
65
+ nexturl => redirect to this url after upload
66
+
67
+ Example
68
+
69
+ Controller:
70
+
71
+ def upload
72
+ @upload_info = YouTubeIt::Client.new.upload_token(params, videos_url)
73
+ end
74
+
75
+ Views: upload.html.erb
76
+
77
+ <% form_tag @upload_info[:url], :multipart => true do %>
78
+ <%= hidden_field_tag :token, @upload_info[:token] %>
79
+ <%= label_tag :file %>
80
+ <%= file_field_tag :file %>
81
+ <%= submit_tag "Upload video" %>
82
+ <% end %>
83
+
84
+ == LOGGING
85
+
86
+ YouTubeIt 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
87
+ (don't forget to set the level to debug):
88
+
89
+ YouTubeIt.logger = RAILS_DEFAULT_LOGGER
90
+ RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
91
+
92
+ == REQUIREMENTS:
93
+
94
+ * builder gem
95
+
96
+ == INSTALL:
97
+
98
+ * sudo gem install youtube_it
99
+ == LICENSE:
100
+
101
+ MIT License
102
+
103
+ Copyright (c) 2007 Shane Vitarana and Walter Korman
104
+
105
+ Permission is hereby granted, free of charge, to any person obtaining
106
+ a copy of this software and associated documentation files (the
107
+ 'Software'), to deal in the Software without restriction, including
108
+ without limitation the rights to use, copy, modify, merge, publish,
109
+ distribute, sublicense, and/or sell copies of the Software, and to
110
+ permit persons to whom the Software is furnished to do so, subject to
111
+ the following conditions:
112
+
113
+ The above copyright notice and this permission notice shall be
114
+ included in all copies or substantial portions of the Software.
115
+
116
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
117
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
118
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
119
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
120
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
121
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
122
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
123
+
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "youtube_it"
8
+ gem.summary = %Q{the one stop shop for working with youtube apis}
9
+ gem.description = %Q{the one stop shop for working with youtube apis}
10
+ gem.email = "maurotorres@gmail.com"
11
+ gem.homepage = "http://github.com/kylejginavan/youtube_it"
12
+ gem.authors = ["Mauro Torres & Kyle Ginavan"]
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "youtube_it (or a dependency) not available. Install it with: gem install youtube_it"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'spec'
22
+ test.pattern = 'spec/**/*_spec.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'spec'
30
+ test.pattern = 'spec/**/*_spec.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+ task :test => :check_dependencies
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "constantations #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+
53
+ require 'spec/rake/spectask'
54
+ desc "Run all specs"
55
+ Spec::Rake::SpecTask.new('spec') do |t|
56
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
57
+ t.spec_files = FileList['spec/**/*_spec.rb']
58
+ end
59
+
data/TODO.txt ADDED
@@ -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
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/youtube_it.rb ADDED
@@ -0,0 +1,67 @@
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 YouTubeIt
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
+ YouTubeIt.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/contact
54
+ model/content
55
+ model/playlist
56
+ model/rating
57
+ model/thumbnail
58
+ model/user
59
+ model/video
60
+ request/base_search
61
+ request/user_search
62
+ request/standard_search
63
+ request/video_upload
64
+ request/video_search
65
+ response/video_search
66
+ chain_io
67
+ ).each{|m| require File.dirname(__FILE__) + '/youtube_it/' + 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 YouTubeIt::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 YouTubeIt::GreedyChainIO < DelegateClass(YouTubeIt::ChainIO)
60
+ BIG_CHUNK = 512 * 1024 # 500 kb
61
+
62
+ def initialize(*with_ios)
63
+ __setobj__(YouTubeIt::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,114 @@
1
+ class YouTubeIt
2
+ class Client
3
+ include YouTubeIt::Logging
4
+ # Previously this was a logger instance but we now do it globally
5
+ def initialize user = nil, pass = nil, dev_key = nil, client_id = 'youtube_it', legacy_debug_flag = nil
6
+ @user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
7
+ end
8
+
9
+
10
+ # Retrieves an array of standard feed, custom query, or user videos.
11
+ #
12
+ # === Parameters
13
+ # If fetching videos for a standard feed:
14
+ # params<Symbol>:: Accepts a symbol of :top_rated, :top_favorites, :most_viewed,
15
+ # :most_popular, :most_recent, :most_discussed, :most_linked,
16
+ # :most_responded, :recently_featured, and :watch_on_mobile.
17
+ #
18
+ # You can find out more specific information about what each standard feed provides
19
+ # by visiting: http://code.google.com/apis/youtube/reference.html#Standard_feeds
20
+ #
21
+ # options<Hash> (optional):: Accepts the options of :time, :page (default is 1),
22
+ # and :per_page (default is 25). :offset and :max_results
23
+ # can also be passed for a custom offset.
24
+ #
25
+ # If fetching videos by tags, categories, query:
26
+ # params<Hash>:: Accepts the keys :tags, :categories, :query, :order_by,
27
+ # :author, :racy, :response_format, :video_format, :page (default is 1),
28
+ # and :per_page(default is 25)
29
+ #
30
+ # options<Hash>:: Not used. (Optional)
31
+ #
32
+ # If fetching videos for a particular user:
33
+ # params<Hash>:: Key of :user with a value of the username.
34
+ # options<Hash>:: Not used. (Optional)
35
+ # === Returns
36
+ # YouTubeIt::Response::VideoSearch
37
+ def videos_by(params, options={})
38
+ request_params = params.respond_to?(:to_hash) ? params : options
39
+ request_params[:page] = integer_or_default(request_params[:page], 1)
40
+
41
+ unless request_params[:max_results]
42
+ request_params[:max_results] = integer_or_default(request_params[:per_page], 25)
43
+ end
44
+
45
+ unless request_params[:offset]
46
+ request_params[:offset] = calculate_offset(request_params[:page], request_params[:max_results] )
47
+ end
48
+
49
+ if params.respond_to?(:to_hash) and not params[:user]
50
+ request = YouTubeIt::Request::VideoSearch.new(request_params)
51
+ elsif (params.respond_to?(:to_hash) && params[:user]) || (params == :favorites)
52
+ request = YouTubeIt::Request::UserSearch.new(params, request_params)
53
+ else
54
+ request = YouTubeIt::Request::StandardSearch.new(params, request_params)
55
+ end
56
+
57
+ logger.debug "Submitting request [url=#{request.url}]."
58
+ parser = YouTubeIt::Parser::VideosFeedParser.new(request.url)
59
+ parser.parse
60
+ end
61
+
62
+ # Retrieves a single YouTube video.
63
+ #
64
+ # === Parameters
65
+ # vid<String>:: The ID or URL of the video that you'd like to retrieve.
66
+ # user<String>:: The user that uploaded the video that you'd like to retrieve.
67
+ #
68
+ # === Returns
69
+ # YouTubeIt::Model::Video
70
+ def video_by(vid)
71
+ video_id = vid =~ /^http/ ? vid : "http://gdata.youtube.com/feeds/videos/#{vid}"
72
+ parser = YouTubeIt::Parser::VideoFeedParser.new(video_id)
73
+ parser.parse
74
+ end
75
+
76
+ def video_by_user(user, vid)
77
+ video_id = "http://gdata.youtube.com/feeds/api/users/#{user}/uploads/#{vid}"
78
+ parser = YouTubeIt::Parser::VideoFeedParser.new(video_id)
79
+ parser.parse
80
+ end
81
+
82
+ def video_upload(data, opts = {})
83
+ client.upload(data, opts)
84
+ end
85
+
86
+ def video_update(video_id, opts = {})
87
+ client.update(video_id, opts)
88
+ end
89
+
90
+ def video_delete(video_id)
91
+ client.delete(video_id)
92
+ end
93
+
94
+ def upload_token(options, nexturl = "http://www.youtube.com/my_videos")
95
+ client.get_upload_token(options, nexturl)
96
+ end
97
+
98
+ private
99
+
100
+ def client
101
+ @client ||= YouTubeIt::Upload::VideoUpload.new(@user, @pass, @dev_key)
102
+ end
103
+
104
+ def calculate_offset(page, per_page)
105
+ page == 1 ? 1 : ((per_page * page) - per_page + 1)
106
+ end
107
+
108
+ def integer_or_default(value, default)
109
+ value = value.to_i
110
+ value > 0 ? value : default
111
+ end
112
+ end
113
+ end
114
+