概要
youtubeのAPIを利用して動画をアップロードする記事です。言語はPython3です。
以下の公式ドキュメントに沿って進めれば良いのですが、一部ハマった点などを追記しています。
環境
- MacOS
- Python3.8
- Pycharm
前提として、GCPでプロジェクトは作成済みとします。
事前準備
google-api-python-clientのインストール
googleのAPIを利用するためのライブラリをインストールします。
$ pip install google-api-python-client
GCPでのアプリケーション登録
1. 承認情報の作成
以下のリンクを開き、GCPのコンソールから認証情報を作成します。今回はOAuth2.0を利用します。
GCPコンソール - 認証情報
(1) 認証情報を作成>OAuthクライアントID と進む (左上のプロジェクトは選びましょう)
(3) クライアントID, クライアントシークレットが取得できる
アプリケーション名、サポートメール欄を埋めて、画面下の作成ボタンを押します。
2. YouTubeAPIの有効化
以下のリンクを開き、GCPのコンソールからYouTube Data APIを検索します。
2020.1時点では「YouTube Data API v3」です。開いたら有効化のボタンがあるため有効化します。
GCPコンソール - APIライブラリ
アップロード用ソースの作成
フォルダ構成
$ tree
.
├── movies
│ └── sample001.MP4 # アップロード対象の動画
├── client_secrets.json
└── upload_video.py
ソース
ソースは認証情報用のJSONとPythonの2つを用意します。
認証情報
client_secrets.jsonを作成します。client_id
とclient_secret
には、「1. 承認情報の作成」で作成したクライアントID, クライアントシークレットの値を設定します。
{
"web": {
"client_id": "[[INSERT CLIENT ID HERE]]",
"client_secret": "[[INSERT CLIENT SECRET HERE]]",
"redirect_uris": [],
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token"
}
}
Python
基本は公式のソースを使えば良いのですが、Python2系で書かれていてPython3だとそのままでは動きません。なので、Python3で動くように少し書き換えます。書き換え箇所は以下の2つ。
・httplibからhttp.clientへ移行
・print文の書き換え
(余談ですが、インデントを4バイトにも変更)
import http.client # httplibはPython3はhttp.clientへ移行
import httplib2
import os
import random
import sys
import time
from apiclient.discovery import build
from apiclient.errors import HttpError
from apiclient.http import MediaFileUpload
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import argparser, run_flow
httplib2.RETRIES = 1
MAX_RETRIES = 10
RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error,
IOError,
http.client.NotConnected,
http.client.IncompleteRead,
http.client.ImproperConnectionState,
http.client.CannotSendRequest,
http.client.CannotSendHeader,
http.client.ResponseNotReady,
http.client.BadStatusLine)
RETRIABLE_STATUS_CODES = [500, 502, 503, 504]
CLIENT_SECRETS_FILE = "client_secrets.json"
MISSING_CLIENT_SECRETS_MESSAGE = """
WARNING: Please configure OAuth 2.0
To make this sample run you will need to populate the client_secrets.json file
found at:
%s
with information from the API Console
https://console.developers.google.com/
For more information about the client_secrets.json file format, please visit:
https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
""" % os.path.abspath(os.path.join(os.path.dirname(__file__),
CLIENT_SECRETS_FILE))
YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload"
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"
VALID_PRIVACY_STATUSES = ("public", "private", "unlisted")
def get_authenticated_service(args):
flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE,
scope=YOUTUBE_UPLOAD_SCOPE,
message=MISSING_CLIENT_SECRETS_MESSAGE)
storage = Storage("%s-oauth2.json" % sys.argv[0])
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = run_flow(flow, storage, args)
return build(YOUTUBE_API_SERVICE_NAME,
YOUTUBE_API_VERSION,
http=credentials.authorize(httplib2.Http()))
def initialize_upload(youtube, options):
tags = None
if options.keywords:
tags = options.keywords.split(",")
body = dict(
snippet=dict(
title=options.title,
description=options.description,
tags=tags,
categoryId=options.category
),
status=dict(
privacyStatus=options.privacyStatus
)
)
insert_request = youtube.videos().insert(
part=",".join(body.keys()),
body=body,
media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True)
)
resumable_upload(insert_request)
def resumable_upload(insert_request):
response = None
error = None
retry = 0
while response is None:
try:
print("Uploading file...") # print文
status, response = insert_request.next_chunk()
if response is not None:
if 'id' in response:
print("Video id '%s' was successfully uploaded." % response['id'])
else:
exit("The upload failed with an unexpected response: %s" % response)
except HttpError as e:
if e.resp.status in RETRIABLE_STATUS_CODES:
error = "A retriable HTTP error %d occurred:\n%s" % \
(e.resp.status, e.content)
else:
raise
except RETRIABLE_EXCEPTIONS as e:
error = "A retriable error occurred: %s" % e
if error is not None:
print(error)
retry += 1
if retry > MAX_RETRIES:
exit("No longer attempting to retry.")
max_sleep = 2 ** retry
sleep_seconds = random.random() * max_sleep
print("Sleeping %f seconds and then retrying..." % sleep_seconds)
time.sleep(sleep_seconds)
if __name__ == '__main__':
argparser.add_argument("--file", required=True, help="Video file to upload")
argparser.add_argument("--title", help="Video title", default="Test Title")
argparser.add_argument("--description",
help="Video description",
default="Test Description")
argparser.add_argument("--category", default="22",
help="Numeric video category. " +
"See https://developers.google.com/youtube/v3/docs/videoCategories/list")
argparser.add_argument("--keywords", help="Video keywords, comma separated",
default="")
argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES,
default=VALID_PRIVACY_STATUSES[0],
help="Video privacy status.")
args = argparser.parse_args()
if not os.path.exists(args.file):
exit("Please specify a valid file using the --file= parameter.")
youtube = get_authenticated_service(args)
try:
initialize_upload(youtube, args)
except HttpError as e:
print("An HTTP error %d occurred:\n%s" % (e.resp.status, e.content))
少しソース解説
- YouTube Data APIはsnippetに設定した内容がAPIに渡されます。
initialize_upload
内のbodyでsnippetとして渡す値のdictを作っています。 - snippetに渡す値は引数から
args
として取得しています。 -
get_authenticated_service
では、YouTube Data APIのリソース名と操作名(insert等)から、APIを利用するオブジェクト(?)をbuildしてreturnしています。今回の場合、buildしたオブジェクトをinitialize_upload
に渡して、youtube.videos().insert
でアップロードを実行します。
アップロードの実行
コマンドラインからだと以下の通り実行します。引数が多いので、何回か実行する場合シェルスクリプトにした方が楽かもしれません。
$ python upload_video.py --file="./movies/sample001.MP4" \
--title="Sample Movie" \
--description="This is a sample movie." \
--category="22" \
--privacyStatus="private"
引数の意味は以下の通りです。
- file: アップロードする動画のパス
- title: アップロード後の動画タイトル
- description: 動画の説明
- category: カテゴリー。デフォルトが22
- privacyStatus: 動画の公開設定。privateだと非公開で投稿
snippetとして指定できる項目や意味は以下に記載があります。
YouTube Data API Videos
YouTube Data API Videos: insert
なお、初めて実行した際に、ブラウザに遷移してGoogleアカウントの選択とYouTubeの管理許可を求める画面が表示されるので、画面に従って許可してください。
Chromeで以下のような画面が表示された場合は、詳細を表示して左下の安全ではないページに移動
動画の管理を許可する。(もう一画面表示されることがあるが、引き続き許可する。)
動画のサイズにもよりますが、少し経つと以下の通りアップロードが完了します。
Uploading file...
Video id '[video id]' was successfully uploaded.
YouTubeの画面からも動画が投稿されていることが確認できます。
※イメージは投稿途中
ハマったこと:APIの利用制限
もともと、大量の動画を一括でアップロードするためにAPIを利用したかったのですが、繰り返しAPIを実行するようなプログラムを書いて実行すると、以下のエラーが発生しました。
Uploading file...
An HTTP error 403 occurred:
b'{\n "error": {\n "errors": [\n {\n "domain": "youtube.quota",\n "reason": "quotaExceeded",\n "message": "The request cannot be completed because you have exceeded your \\u003ca href=\\"/youtube/v3/getting-started#quota\\"\\u003equota\\u003c/a\\u003e."\n }\n ],\n "code": 403,\n "message": "The request cannot be completed because you have exceeded your \\u003ca href=\\"/youtube/v3/getting-started#quota\\"\\u003equota\\u003c/a\\u003e."\n }\n}\n'
調べてみると、APIでは1日あたり10,000で制限されており、動画のアップロードでは約1,600ユニットを利用するとのこと。なので、YouTubeにAPIを経由して動画をあげられるのは、おおよそ6本/1日のようです...
YouTube Data API - エラー
YouTube Data API の概要 - クォータの使用量
これを引き上げるには申請が必要となりますが、ただの個人利用だったため断念しました。せめて、30本/1日だったらやりたいことはできたのですが...6本だと手作業の方が早いかもですね...
アプリケーションの中で動画のアップロードとかしたい場合に役立つかもしれません。