|
import os |
|
import sys |
|
import time |
|
import random |
|
import httplib2 |
|
|
|
from termcolor import colored |
|
from oauth2client.file import Storage |
|
from apiclient.discovery import build |
|
from apiclient.errors import HttpError |
|
from apiclient.http import MediaFileUpload |
|
from oauth2client.tools import argparser, run_flow |
|
from oauth2client.client import flow_from_clientsecrets |
|
|
|
|
|
|
|
httplib2.RETRIES = 1 |
|
|
|
|
|
MAX_RETRIES = 10 |
|
|
|
|
|
RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError, httplib2.ServerNotFoundError) |
|
|
|
|
|
|
|
RETRIABLE_STATUS_CODES = [500, 502, 503, 504] |
|
|
|
|
|
|
|
|
|
CLIENT_SECRETS_FILE = "./client_secret.json" |
|
|
|
|
|
|
|
|
|
SCOPES = ['https://www.googleapis.com/auth/youtube.upload', |
|
'https://www.googleapis.com/auth/youtube', |
|
'https://www.googleapis.com/auth/youtubepartner'] |
|
YOUTUBE_API_SERVICE_NAME = "youtube" |
|
YOUTUBE_API_VERSION = "v3" |
|
|
|
|
|
|
|
MISSING_CLIENT_SECRETS_MESSAGE = f""" |
|
WARNING: Please configure OAuth 2.0 |
|
|
|
To make this sample run you will need to populate the client_secrets.json file |
|
found at: |
|
|
|
{os.path.abspath(os.path.join(os.path.dirname(__file__), CLIENT_SECRETS_FILE))} |
|
|
|
with information from the API Console |
|
https://console.cloud.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 |
|
""" |
|
|
|
VALID_PRIVACY_STATUSES = ("public", "private", "unlisted") |
|
|
|
|
|
def get_authenticated_service(): |
|
""" |
|
This method retrieves the YouTube service. |
|
|
|
Returns: |
|
any: The authenticated YouTube service. |
|
""" |
|
flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, |
|
scope=SCOPES, |
|
message=MISSING_CLIENT_SECRETS_MESSAGE) |
|
|
|
storage = Storage(f"{sys.argv[0]}-oauth2.json") |
|
credentials = storage.get() |
|
|
|
if credentials is None or credentials.invalid: |
|
flags = argparser.parse_args() |
|
credentials = run_flow(flow, storage, flags) |
|
|
|
return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, |
|
http=credentials.authorize(httplib2.Http())) |
|
|
|
def initialize_upload(youtube: any, options: dict): |
|
""" |
|
This method uploads a video to YouTube. |
|
|
|
Args: |
|
youtube (any): The authenticated YouTube service. |
|
options (dict): The options to upload the video with. |
|
|
|
Returns: |
|
response: The response from the upload process. |
|
""" |
|
|
|
tags = None |
|
if options['keywords']: |
|
tags = options['keywords'].split(",") |
|
|
|
body = { |
|
'snippet': { |
|
'title': options['title'], |
|
'description': options['description'], |
|
'tags': tags, |
|
'categoryId': options['category'] |
|
}, |
|
'status': { |
|
'privacyStatus': options['privacyStatus'], |
|
'madeForKids': False, |
|
'selfDeclaredMadeForKids': False |
|
} |
|
} |
|
|
|
|
|
insert_request = youtube.videos().insert( |
|
part=",".join(body.keys()), |
|
body=body, |
|
media_body=MediaFileUpload(options['file'], chunksize=-1, resumable=True) |
|
) |
|
|
|
return resumable_upload(insert_request) |
|
|
|
def resumable_upload(insert_request: MediaFileUpload): |
|
""" |
|
This method implements an exponential backoff strategy to resume a |
|
failed upload. |
|
|
|
Args: |
|
insert_request (MediaFileUpload): The request to insert the video. |
|
|
|
Returns: |
|
response: The response from the upload process. |
|
""" |
|
response = None |
|
error = None |
|
retry = 0 |
|
while response is None: |
|
try: |
|
print(colored(" => Uploading file...", "magenta")) |
|
status, response = insert_request.next_chunk() |
|
if 'id' in response: |
|
print(f"Video id '{response['id']}' was successfully uploaded.") |
|
return response |
|
except HttpError as e: |
|
if e.resp.status in RETRIABLE_STATUS_CODES: |
|
error = f"A retriable HTTP error {e.resp.status} occurred:\n{e.content}" |
|
else: |
|
raise |
|
except RETRIABLE_EXCEPTIONS as e: |
|
error = f"A retriable error occurred: {e}" |
|
|
|
if error is not None: |
|
print(colored(error, "red")) |
|
retry += 1 |
|
if retry > MAX_RETRIES: |
|
raise Exception("No longer attempting to retry.") |
|
|
|
max_sleep = 2 ** retry |
|
sleep_seconds = random.random() * max_sleep |
|
print(colored(f" => Sleeping {sleep_seconds} seconds and then retrying...", "blue")) |
|
time.sleep(sleep_seconds) |
|
|
|
def upload_video(video_path, title, description, category, keywords, privacy_status): |
|
try: |
|
|
|
youtube = get_authenticated_service() |
|
|
|
|
|
channels_response = youtube.channels().list(mine=True, part='id').execute() |
|
for channel in channels_response['items']: |
|
print(colored(f" => Channel ID: {channel['id']}", "blue")) |
|
|
|
|
|
video_response = initialize_upload(youtube, { |
|
'file': video_path, |
|
'title': title, |
|
'description': description, |
|
'category': category, |
|
'keywords': keywords, |
|
'privacyStatus': privacy_status |
|
}) |
|
return video_response |
|
except HttpError as e: |
|
print(colored(f"[-] An HTTP error {e.resp.status} occurred:\n{e.content}", "red")) |
|
if e.resp.status in [401, 403]: |
|
|
|
youtube = get_authenticated_service() |
|
video_response = initialize_upload(youtube, { |
|
'file': video_path, |
|
'title': title, |
|
'description': description, |
|
'category': category, |
|
'keywords': keywords, |
|
'privacyStatus': privacy_status |
|
}) |
|
return video_response |
|
else: |
|
raise e |
|
|