Skip to content

Commit

Permalink
Small mods
Browse files Browse the repository at this point in the history
  • Loading branch information
julik committed Mar 2, 2009
1 parent 0a23614 commit 395b8a4
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 37 deletions.
10 changes: 7 additions & 3 deletions lib/youtube_g/chain_io.rb
@@ -1,10 +1,12 @@
require 'delegate'
#:stopdoc:

# 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
# instead of reading whole video files into memory. Strings passed to the constructor will be wrapped in StringIOs. By default it will auto-close
# file handles when they have been read completely to prevent our uploader from leaking file handles
#
# chain = ChainIO.new(File.open(__FILE__), File.open('/etc/passwd'), "abcd")
class YouTubeG::ChainIO #:nodoc:
class YouTubeG::ChainIO
attr_accessor :autoclose

def initialize(*any_ios)
Expand Down Expand Up @@ -54,7 +56,7 @@ def release_handle(io)

# 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.
# We use delegation because the read call is recursive.
class YouTubeG::GreedyChainIO < DelegateClass(YouTubeG::ChainIO) #:nodoc:
class YouTubeG::GreedyChainIO < DelegateClass(YouTubeG::ChainIO)
BIG_CHUNK = 512 * 1024 # 500 kb

def initialize(*with_ios)
Expand All @@ -64,4 +66,6 @@ def initialize(*with_ios)
def read(any_buffer_size)
__getobj__.read(BIG_CHUNK)
end
end
end

#:startdoc:
76 changes: 42 additions & 34 deletions lib/youtube_g/request/video_upload.rb
Expand Up @@ -3,21 +3,21 @@ class YouTubeG
module Upload
class UploadError < YouTubeG::Error; end
class AuthenticationError < YouTubeG::Error; end


# Implements a video upload
# require 'youtube_g'
#
# uploader = YouTubeG::Upload::VideoUpload.new("user", "pass", "dev-key")
# uploader.upload File.open("test.m4v"), :title => 'test',
# :description => 'cool vid d00d',
# :category => 'People',
# :keywords => %w[cool blah test]

class VideoUpload

def initialize user, pass, dev_key, client_id = 'youtube_g'
@user, @pass, @dev_key, @client_id = user, pass, dev_key, client_id
end

#
# Upload "data" to youtube, where data is either an IO object or
# raw file data.
Expand All @@ -37,23 +37,13 @@ def initialize user, pass, dev_key, client_id = 'youtube_g'
#
# When the authentication credentials are incorrect, an AuthenticationError will be raised.
def upload data, opts = {}
# If data can be read, use the first 1024 bytes as filename. If data
# is a file, use path. If data is a string, checksum it
opst[:filename] ||= if data.respond_to?(:path)
Digest::MD5.hexdigest(data.path)
elsif data.respond_to?(:read)
chunk = data.read(1024)
data.rewind
Digest::MD5.hexdigest(chunk)
else
Digest::MD5.hexdigest(data)
end

@opts = { :mime_type => 'video/mp4',
:title => '',
:description => '',
:category => '',
:keywords => [] }.merge(opts)

@opts[:filename] ||= generate_uniq_filename_from(data)

post_body_io = generate_upload_body(boundary, video_xml, data)

Expand All @@ -64,7 +54,7 @@ def upload data, opts = {}
"Slug" => "#{@opts[:filename]}",
"Content-Type" => "multipart/related; boundary=#{boundary}",
"Content-Length" => "#{post_body_io.expected_length}", # required per YouTube spec
"Transfer-Encoding" => "chunked" # We will stream instead of posting at once
# "Transfer-Encoding" => "chunked" # We will stream instead of posting at once
}

Net::HTTP.start(base_url) do | session |
Expand All @@ -79,43 +69,60 @@ def upload data, opts = {}
if response.code.to_i == 403
raise AuthenticationError, response.body[/<TITLE>(.+)<\/TITLE>/, 1]
elsif response.code.to_i != 201
upload_error = ''
xml = REXML::Document.new(response.body)
errors = xml.elements["//errors"]
errors.each do |error|
location = error.elements["location"].text[/media:group\/media:(.*)\/text\(\)/,1]
code = error.elements["code"].text
upload_error << sprintf("%s: %s\r\n", location, code)
end
raise UploadError, upload_error
raise UploadError, parse_upload_error_from(response.body)
end
xml = REXML::Document.new(response.body)
return xml.elements["//id"].text[/videos\/(.+)/, 1]

return uploaded_video_id_from(response.body)
end

end

private

def base_url
"uploads.gdata.youtube.com"
end

def boundary
"An43094fu"
end


def parse_upload_error_from(string)
REXML::Document.new(string).elements["//errors"].inject('') do | all_faults, error|
location = error.elements["location"].text[/media:group\/media:(.*)\/text\(\)/,1]
code = error.elements["code"].text
all_faults + sprintf("%s: %s\n", location, code)
end
end

def uploaded_video_id_from(string)
xml = REXML::Document.new(string)
xml.elements["//id"].text[/videos\/(.+)/, 1]
end

# If data can be read, use the first 1024 bytes as filename. If data
# is a file, use path. If data is a string, checksum it
def generate_uniq_filename_from(data)
if data.respond_to?(:path)
Digest::MD5.hexdigest(data.path)
elsif data.respond_to?(:read)
chunk = data.read(1024)
data.rewind
Digest::MD5.hexdigest(chunk)
else
Digest::MD5.hexdigest(data)
end
end

def auth_token
unless @auth_token
@auth_token ||= begin
http = Net::HTTP.new("www.google.com", 443)
http.use_ssl = true
body = "Email=#{CGI::escape @user}&Passwd=#{CGI::escape @pass}&service=youtube&source=#{CGI::escape @client_id}"
body = "Email=#{YouTubeG.esc @user}&Passwd=#{YouTubeG.esc @pass}&service=youtube&source=#{YouTubeG.esc @client_id}"
response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
raise UploadError, response.body[/Error=(.+)/,1] if response.code.to_i != 200
@auth_token = response.body[/Auth=(.+)/, 1]

end
@auth_token
end

def video_xml
Expand Down Expand Up @@ -143,6 +150,7 @@ def generate_upload_body(boundary, video_xml, data)
"\r\n--#{boundary}--\r\n",
]

# Use Greedy IO to not be limited by 1K chunks
YouTubeG::GreedyChainIO.new(post_body)
end

Expand Down

0 comments on commit 395b8a4

Please sign in to comment.