scripting your way to upload videos to peertube and youtube
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
5.8 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. #!/usr/bin/python
  2. # coding: utf-8
  3. # From Youtube samples : https://raw.githubusercontent.com/youtube/api-samples/master/python/upload_video.py # noqa
  4. import httplib
  5. import httplib2
  6. import random
  7. import time
  8. import copy
  9. import json
  10. from os.path import splitext, basename, exists
  11. import google.oauth2.credentials
  12. import datetime
  13. import pytz
  14. from tzlocal import get_localzone
  15. from googleapiclient.discovery import build
  16. from googleapiclient.errors import HttpError
  17. from googleapiclient.http import MediaFileUpload
  18. from google_auth_oauthlib.flow import InstalledAppFlow
  19. import utils
  20. # Explicitly tell the underlying HTTP transport library not to retry, since
  21. # we are handling retry logic ourselves.
  22. httplib2.RETRIES = 1
  23. # Maximum number of times to retry before giving up.
  24. MAX_RETRIES = 10
  25. # Youtube retriables cases
  26. RETRIABLE_EXCEPTIONS = (
  27. IOError,
  28. httplib2.HttpLib2Error,
  29. httplib.NotConnected,
  30. httplib.IncompleteRead,
  31. httplib.ImproperConnectionState,
  32. httplib.CannotSendRequest,
  33. httplib.CannotSendHeader,
  34. httplib.ResponseNotReady,
  35. httplib.BadStatusLine,
  36. )
  37. RETRIABLE_STATUS_CODES = [500, 502, 503, 504]
  38. CLIENT_SECRETS_FILE = 'youtube_secret.json'
  39. CREDENTIALS_PATH = ".youtube_credentials.json"
  40. SCOPES = ['https://www.googleapis.com/auth/youtube.upload']
  41. API_SERVICE_NAME = 'youtube'
  42. API_VERSION = 'v3'
  43. # Authorize the request and store authorization credentials.
  44. def get_authenticated_service():
  45. flow = InstalledAppFlow.from_client_secrets_file(
  46. CLIENT_SECRETS_FILE, SCOPES)
  47. if exists(CREDENTIALS_PATH):
  48. with open(CREDENTIALS_PATH, 'r') as f:
  49. credential_params = json.load(f)
  50. credentials = google.oauth2.credentials.Credentials(
  51. credential_params["token"],
  52. refresh_token=credential_params["_refresh_token"],
  53. token_uri=credential_params["_token_uri"],
  54. client_id=credential_params["_client_id"],
  55. client_secret=credential_params["_client_secret"]
  56. )
  57. else:
  58. credentials = flow.run_local_server()
  59. with open(CREDENTIALS_PATH, 'w') as f:
  60. p = copy.deepcopy(vars(credentials))
  61. del p["expiry"]
  62. json.dump(p, f)
  63. return build(API_SERVICE_NAME, API_VERSION, credentials=credentials)
  64. def initialize_upload(youtube, options):
  65. path = options.get('--file')
  66. tags = None
  67. if options.get('--tags'):
  68. tags = options.get('--tags').split(',')
  69. category = None
  70. if options.get('--category'):
  71. category = utils.getCategory(options.get('--category'), 'youtube')
  72. language = None
  73. if options.get('--language'):
  74. language = utils.getLanguage(options.get('--language'), "youtube")
  75. license = None
  76. if options.get('--cca'):
  77. license = "creativeCommon"
  78. body = {
  79. "snippet": {
  80. "title": options.get('--name') or splitext(basename(path))[0],
  81. "description": options.get('--description') or "default description",
  82. "tags": tags,
  83. # if no category, set default to 1 (Films)
  84. "categoryId": str(category or 1),
  85. "defaultAudioLanguage": str(language or 'en')
  86. },
  87. "status": {
  88. "privacyStatus": str(options.get('--privacy') or "private"),
  89. "license": str(license or "youtube"),
  90. }
  91. }
  92. if options.get('--publishAt'):
  93. # Youtube needs microsecond and the local timezone from ISO 8601
  94. publishAt = options.get('--publishAt') + ".000001"
  95. publishAt = datetime.datetime.strptime(publishAt, '%Y-%m-%dT%H:%M:%S.%f')
  96. tz = get_localzone()
  97. tz = pytz.timezone(str(tz))
  98. publishAt = tz.localize(publishAt).isoformat()
  99. body['status']['publishAt'] = str(publishAt)
  100. # Call the API's videos.insert method to create and upload the video.
  101. insert_request = youtube.videos().insert(
  102. part=','.join(body.keys()),
  103. body=body,
  104. media_body=MediaFileUpload(path, chunksize=-1, resumable=True)
  105. )
  106. resumable_upload(insert_request)
  107. # This method implements an exponential backoff strategy to resume a
  108. # failed upload.
  109. def resumable_upload(request):
  110. response = None
  111. error = None
  112. retry = 0
  113. while response is None:
  114. try:
  115. print('Youtube : Uploading file...')
  116. status, response = request.next_chunk()
  117. if response is not None:
  118. if 'id' in response:
  119. template = ('Youtube : Video was successfully '
  120. 'uploaded.\n'
  121. 'Watch it at https://youtu.be/%s (post-encoding could take some time)')
  122. print(template % response['id'])
  123. else:
  124. template = ('Youtube : The upload failed with an '
  125. 'unexpected response: %s')
  126. exit(template % response)
  127. except HttpError as e:
  128. if e.resp.status in RETRIABLE_STATUS_CODES:
  129. template = 'Youtube : A retriable HTTP error %d occurred:\n%s'
  130. error = template % (e.resp.status, e.content)
  131. else:
  132. raise
  133. except RETRIABLE_EXCEPTIONS as e:
  134. error = 'Youtube : A retriable error occurred: %s' % e
  135. if error is not None:
  136. print(error)
  137. retry += 1
  138. if retry > MAX_RETRIES:
  139. exit('Youtube : No longer attempting to retry.')
  140. max_sleep = 2 ** retry
  141. sleep_seconds = random.random() * max_sleep
  142. print('Youtube : Sleeping %f seconds and then retrying...'
  143. % sleep_seconds)
  144. time.sleep(sleep_seconds)
  145. def run(options):
  146. youtube = get_authenticated_service()
  147. try:
  148. initialize_upload(youtube, options)
  149. except HttpError as e:
  150. print('Youtube : An HTTP error %d occurred:\n%s' % (e.resp.status,
  151. e.content))