src/uploader.py (6657 bytes)
1 #!/usr/bin/python 2 3 import httplib 4 import httplib2 5 import os 6 import random 7 import sys 8 import time 9 10 from apiclient.discovery import build 11 from apiclient.errors import HttpError 12 from apiclient.http import MediaFileUpload 13 from oauth2client.client import flow_from_clientsecrets 14 from oauth2client.file import Storage 15 from oauth2client.tools import argparser, run_flow 16 17 18 # Explicitly tell the underlying HTTP transport library not to retry, since 19 # we are handling retry logic ourselves. 20 httplib2.RETRIES = 1 21 22 # Maximum number of times to retry before giving up. 23 MAX_RETRIES = 10 24 25 # Always retry when these exceptions are raised. 26 RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError, httplib.NotConnected, 27 httplib.IncompleteRead, httplib.ImproperConnectionState, 28 httplib.CannotSendRequest, httplib.CannotSendHeader, 29 httplib.ResponseNotReady, httplib.BadStatusLine) 30 31 # Always retry when an apiclient.errors.HttpError with one of these status 32 # codes is raised. 33 RETRIABLE_STATUS_CODES = [500, 502, 503, 504] 34 35 # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains 36 # the OAuth 2.0 information for this application, including its client_id and 37 # client_secret. You can acquire an OAuth 2.0 client ID and client secret from 38 # the Google Developers Console at 39 # https://console.developers.google.com/. 40 # Please ensure that you have enabled the YouTube Data API for your project. 41 # For more information about using OAuth2 to access the YouTube Data API, see: 42 # https://developers.google.com/youtube/v3/guides/authentication 43 # For more information about the client_secrets.json file format, see: 44 # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets 45 CLIENT_SECRETS_FILE = "client_secrets.json" 46 47 # This OAuth 2.0 access scope allows an application to upload files to the 48 # authenticated user's YouTube channel, but doesn't allow other types of access. 49 YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload" 50 YOUTUBE_API_SERVICE_NAME = "youtube" 51 YOUTUBE_API_VERSION = "v3" 52 53 # This variable defines a message to display if the CLIENT_SECRETS_FILE is 54 # missing. 55 MISSING_CLIENT_SECRETS_MESSAGE = """ 56 WARNING: Please configure OAuth 2.0 57 58 To make this sample run you will need to populate the client_secrets.json file 59 found at: 60 61 %s 62 63 with information from the Developers Console 64 https://console.developers.google.com/ 65 66 For more information about the client_secrets.json file format, please visit: 67 https://developers.google.com/api-client-library/python/guide/aaa_client_secrets 68 """ % os.path.abspath(os.path.join(os.path.dirname(__file__), 69 CLIENT_SECRETS_FILE)) 70 71 VALID_PRIVACY_STATUSES = ("public", "private", "unlisted") 72 73 74 def get_authenticated_service(args): 75 flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, 76 scope=YOUTUBE_UPLOAD_SCOPE, 77 message=MISSING_CLIENT_SECRETS_MESSAGE) 78 79 storage = Storage("%s-oauth2.json" % sys.argv[0]) 80 credentials = storage.get() 81 82 if credentials is None or credentials.invalid: 83 credentials = run_flow(flow, storage, args) 84 85 return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, 86 http=credentials.authorize(httplib2.Http())) 87 88 def initialize_upload(youtube, options): 89 tags = None 90 if options.keywords: 91 tags = options.keywords.split(",") 92 93 body=dict( 94 snippet=dict( 95 title=options.title, 96 description=options.description, 97 tags=tags, 98 categoryId=options.category 99 ), 100 status=dict( 101 privacyStatus=options.privacyStatus 102 ) 103 ) 104 105 # Call the API's videos.insert method to create and upload the video. 106 insert_request = youtube.videos().insert( 107 part=",".join(body.keys()), 108 body=body, 109 # The chunksize parameter specifies the size of each chunk of data, in 110 # bytes, that will be uploaded at a time. Set a higher value for 111 # reliable connections as fewer chunks lead to faster uploads. Set a lower 112 # value for better recovery on less reliable connections. 113 # 114 # Setting "chunksize" equal to -1 in the code below means that the entire 115 # file will be uploaded in a single HTTP request. (If the upload fails, 116 # it will still be retried where it left off.) This is usually a best 117 # practice, but if you're using Python older than 2.6 or if you're 118 # running on App Engine, you should set the chunksize to something like 119 # 1024 * 1024 (1 megabyte). 120 media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True) 121 ) 122 123 resumable_upload(insert_request) 124 125 # This method implements an exponential backoff strategy to resume a 126 # failed upload. 127 def resumable_upload(insert_request): 128 response = None 129 error = None 130 retry = 0 131 while response is None: 132 try: 133 print "Uploading file..." 134 status, response = insert_request.next_chunk() 135 if 'id' in response: 136 print "Video id '%s' was successfully uploaded." % response['id'] 137 else: 138 exit("The upload failed with an unexpected response: %s" % response) 139 except HttpError, e: 140 if e.resp.status in RETRIABLE_STATUS_CODES: 141 error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status, 142 e.content) 143 else: 144 raise 145 except RETRIABLE_EXCEPTIONS, e: 146 error = "A retriable error occurred: %s" % e 147 148 if error is not None: 149 print error 150 retry += 1 151 if retry > MAX_RETRIES: 152 exit("No longer attempting to retry.") 153 154 max_sleep = 2 ** retry 155 sleep_seconds = random.random() * max_sleep 156 print "Sleeping %f seconds and then retrying..." % sleep_seconds 157 time.sleep(sleep_seconds) 158 159 if __name__ == '__main__': 160 argparser.add_argument("--file", required=True, help="Video file to upload") 161 argparser.add_argument("--title", help="Video title", default="Test Title") 162 argparser.add_argument("--description", help="Video description", 163 default="Test Description") 164 argparser.add_argument("--category", default="22", 165 help="Numeric video category. " + 166 "See https://developers.google.com/youtube/v3/docs/videoCategories/list") 167 argparser.add_argument("--keywords", help="Video keywords, comma separated", 168 default="") 169 argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES, 170 default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.") 171 args = argparser.parse_args() 172 173 if not os.path.exists(args.file): 174 exit("Please specify a valid file using the --file= parameter.") 175 176 youtube = get_authenticated_service(args) 177 try: 178 initialize_upload(youtube, args) 179 except HttpError, e: 180 print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content)