youtube_it 0.0.1

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 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
+