Recently Written · git

librivox

Automated youtube uploading of audiobooks

git clone https://github.com/equwal/librivox

Log | Files | Refs


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)