How to insert video youtube api v3 through service

2019-01-20 17:30发布

问题:

My goal is to let users of my app upload to my youtube account through my server. I have a developer acount, with youtube data api enabled and a service account client setup and for some reason I am not authorized. I dug through the net to try and figure out why and found many things. I tried giving permissions in the admin security settings with the projects client id and scope. Many people were saying that this error is caused because I don't have a youtube account associated with my account. but I have no idea how to associate these.

This is my upload_video.rb script:

require 'rubygems'
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/file_storage'
require 'google/api_client/auth/installed_app'
require 'certified'

DEVELOPER_KEY = "MY-DEVELOPER-KEY"
YOUTUBE_UPLOAD_SCOPE = 'https://www.googleapis.com/auth/youtube.upload'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'

def get_authenticated_service
  puts @PROGRAM_NAME
  client = Google::APIClient.new(
    :application_name => $PROGRAM_NAME,
    :application_version => '1.0.0'
)

key = Google::APIClient::KeyUtils.load_from_pkcs12('oauth2service.p12', 'notasecret')
  auth_client = Signet::OAuth2::Client.new(
  :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
  :audience => 'https://accounts.google.com/o/oauth2/token',
  :scope => YOUTUBE_UPLOAD_SCOPE,
  :issuer => 'SERVICE ACCOUNT EMAIL ADDRESS',
  :signing_key => key)
 auth_client.fetch_access_token!
 client.authorization = auth_client
 youtube = client.discovered_api(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION)
 return client, youtube
end

def upload2youtube file, title, description, category_id, keywords, privacy_status

  client, youtube = get_authenticated_service
  begin
    body = {
  :snippet => {
    :title => title,
    :description => description,
    :tags => keywords.split(','),
    :categoryId => category_id,
  },
  :status => {
    :privacyStatus => privacy_status
  }
}

videos_insert_response = client.execute!(
  :api_method => youtube.videos.insert,
  :body_object => body,
  :media => Google::APIClient::UploadIO.new(file, 'video/*'),
  :parameters => {
    :uploadType => 'resumable',
    :part => body.keys.join(',')
  }
 )

 videos_insert_response.resumable_upload.send_all(client)

 puts "Video id '#{videos_insert_response.data.id}' was successfully uploaded."
 rescue Google::APIClient::TransmissionError => e
 puts e.result.body
 end
end

This is how I am running it from another script:

require 'google/api_client'
require_relative 'upload_video'

$PROGRAM_NAME = 'MY-PROJECT-NAME'
upload2youtube File.open("somevideo.avi"), "title", "description", 'categoryid', 'tag1,tag2,tag3', 'unlisted'

And this is the result:

{
 "error": {
  "errors": [
   {
    "domain": "youtube.header",
    "reason": "youtubeSignupRequired",
    "message": "Unauthorized",
    "locationType": "header",
    "location": "Authorization"
   }
  ],
  "code": 401,
  "message": "Unauthorized"
 }
}

What am I doing wrong

回答1:

#!/usr/bin/ruby

require 'rubygems'
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/file_storage'
require 'google/api_client/auth/installed_app'
require 'certified'


class Youtube_Helper

  @@client_email = '' #email id from service account (that really long email address...)
  @@youtube_email = '' #email associated with youtube account
  @@p12_file_path = '' #path to the file downloaded from the service account (Generate new p12 key button)
  @@p12_password = '' # password to the file usually 'notasecret'
  @@api_key = nil # The API key for non authenticated things like lists
  YOUTUBE_UPLOAD_SCOPE = 'https://www.googleapis.com/auth/youtube.upload'
  YOUTUBE_API_SERVICE_NAME = 'youtube'
  YOUTUBE_API_VERSION = 'v3'
  @@client = nil
  @@youtube = nil
  FILE_POSTFIX = '-oauth2.json'

  def initialize(client_email, youtube_email, p12_file_path, p12_password, api_key)
    @@client_email=client_email
    @@youtube_email=youtube_email
    @@p12_file_path=p12_file_path
    @@p12_password=p12_password
    @@api_key = api_key
  end

  def get_authenticated_service


    credentialsFile = $0 + FILE_POSTFIX

    needtoauthenticate = false

    @api_client = Google::APIClient.new(
      :application_name => $PROGRAM_NAME,
      :application_version => '1.0.0'
    )

    key = Google::APIClient::KeyUtils.load_from_pkcs12(@@p12_file_path, @@p12_password)
    @auth_client = Signet::OAuth2::Client.new(
        :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
        :audience => 'https://accounts.google.com/o/oauth2/token',
        :scope => YOUTUBE_UPLOAD_SCOPE,
        :issuer => @@client_email,
        :person => @@youtube_email,
        :signing_key => key)


    if File.exist? credentialsFile
      puts 'credential file exists'
      puts credentialsFile.to_s
      File.open(credentialsFile, 'r') do |file|
        credentials = JSON.load(file)
        if !credentials.nil?
          puts 'get credentials from file'
          @auth_client.access_token = credentials['access_token']
          @auth_client.client_id = credentials['client_id']
          @auth_client.client_secret = credentials['client_secret']
          @auth_client.refresh_token = credentials['refresh_token']
          @auth_client.expires_in = (Time.parse(credentials['token_expiry']) - Time.now).ceil
          @api_client.authorization = @auth_client
          if @auth_client.expired?
            puts 'authorization expired'
            needtoauthenticate = true
          end
        else
          needtoauthenticate = true
        end
      end
    else
      needtoauthenticate = true
    end

    if needtoauthenticate
      @auth_client.fetch_access_token!
      @api_client.authorization = @auth_client
      save(credentialsFile)
    end

    youtube = @api_client.discovered_api(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION)
    @@client = @api_client
    @@youtube = youtube
    return @api_client, youtube
  end

  def save(credentialsFile)
    File.open(credentialsFile, 'w', 0600) do |file|
      json = JSON.dump({
        :access_token => @auth_client.access_token,
        :client_id => @auth_client.client_id,
        :client_secret => @auth_client.client_secret,
        :refresh_token => @auth_client.refresh_token,
        :token_expiry => @auth_client.expires_at
      })
      file.write(json)
    end
  end

  def upload2youtube file, title, description, category_id, keywords, privacy_status
    puts 'begin'
    begin
      body = {
        :snippet => {
          :title => title,
          :description => description,
          :tags => keywords.split(','),
          :categoryId => category_id,
        },
        :status => {
          :privacyStatus => privacy_status
        }
      }
      puts body.keys.join(',')

      # Call the API's videos.insert method to create and upload the video.
      videos_insert_response = @@client.execute!(
        :api_method => @@youtube.videos.insert,
        :body_object => body,
        :media => Google::APIClient::UploadIO.new(file, 'video/*'),
        :parameters => {
          'uploadType' => 'multipart',
          :part => body.keys.join(',')
        }
      )

      puts'inserted'

      puts "'#{videos_insert_response.data.snippet.title}' (video id: #{videos_insert_response.data.id}) was successfully uploaded."

    rescue Google::APIClient::TransmissionError => e
      puts e.result.body
    end

    return videos_insert_response.data.id #video id

  end

  def upload_thumbnail  video_id, thumbnail_file, thumbnail_size
    puts 'uploading thumbnail'
    begin
      thumbnail_upload_response = @@client.execute({ :api_method => @@youtube.thumbnails.set,
                            :parameters => { :videoId => video_id,
                                             'uploadType' => 'media',
                                             :onBehalfOfContentOwner => @@youtube_email},
                            :media => thumbnail_file,
                            :headers => { 'Content-Length' => thumbnail_size.to_s,
                                          'Content-Type' => 'image/jpg' }
                            })
    rescue Google::APIClient::TransmissionError => e
        puts e.result.body 
    end 
  end

  def get_video_statistics video_id
    client = Google::APIClient.new(:key => @@api_key,
                                    :application_name => $PROGRAM_NAME,
                                    :application_version => '1.0.0',
                                    :authorization => nil)
    youtube = client.discovered_api(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION)
    stats_response = client.execute!( :api_method => youtube.videos.list,
                    :parameters => {:part => 'statistics', :id => video_id }
                    )
    return stats_response
  end
end