main.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/env python
  2. import os
  3. import sys
  4. import collections
  5. import googleapiclient.errors
  6. import oauth2client
  7. from . import auth
  8. from . import upload_video
  9. from . import categories
  10. from . import lib
  11. from . import playlists
  12. class InvalidCategory(Exception): pass
  13. class OptionsError(Exception): pass
  14. class AuthenticationError(Exception): pass
  15. class RequestError(Exception): pass
  16. EXIT_CODES = {
  17. OptionsError: 2,
  18. InvalidCategory: 3,
  19. RequestError: 3,
  20. AuthenticationError: 4,
  21. oauth2client.client.FlowExchangeError: 4,
  22. NotImplementedError: 5,
  23. }
  24. USE_DEBUG = False
  25. JOB = None
  26. #debug = lib.debug
  27. def debug(args):
  28. if USE_DEBUG:
  29. print(args)
  30. struct = collections.namedtuple
  31. class AttrDict(dict):
  32. def __init__(self, *args, **kwargs):
  33. super(AttrDict, self).__init__(*args, **kwargs)
  34. self.__dict__ = self
  35. def get_progress_info():
  36. """Return a function callback to update the progressbar."""
  37. progressinfo = struct("ProgressInfo", ["callback", "finish"])
  38. def _callback(total_size, completed):
  39. if JOB is not None:
  40. JOB.meta['status'] = 'uploading'
  41. JOB.meta['progress'] = "%.2f%%" % ((completed/total_size)*100)
  42. JOB.save()
  43. def _finish():
  44. if JOB is not None:
  45. JOB.meta['status'] = 'finished'
  46. JOB.meta['progress'] = 100
  47. JOB.save()
  48. return progressinfo(callback=_callback, finish=_finish)
  49. def get_category_id(category):
  50. """Return category ID from its name."""
  51. if category:
  52. if category in categories.IDS:
  53. ncategory = categories.IDS[category]
  54. debug("Using category: {0} (id={1})".format(category, ncategory))
  55. return str(categories.IDS[category])
  56. else:
  57. msg = "{0} is not a valid category".format(category)
  58. raise InvalidCategory(msg)
  59. def upload_youtube_video(youtube, options, video_path, total_videos, index):
  60. """Upload video with index (for split videos)."""
  61. u = lib.to_utf8
  62. title = u(options.title)
  63. if hasattr(u('string'), 'decode'):
  64. description = u(options.description or "").decode("string-escape")
  65. else:
  66. description = options.description
  67. if options.publish_at:
  68. debug("Your video will remain private until specified date.")
  69. tags = [u(s.strip()) for s in (options.tags or "").split(",")]
  70. ns = dict(title=title, n=index+1, total=total_videos)
  71. title_template = u(options.title_template)
  72. complete_title = (title_template.format(**ns) if total_videos > 1 else title)
  73. progress = get_progress_info()
  74. category_id = get_category_id(options.category)
  75. request_body = {
  76. "snippet": {
  77. "title": complete_title,
  78. "description": description,
  79. "categoryId": category_id,
  80. "tags": tags,
  81. "defaultLanguage": options.default_language,
  82. "defaultAudioLanguage": options.default_audio_language,
  83. },
  84. "status": {
  85. "privacyStatus": ("private" if options.publish_at else options.privacy),
  86. "publishAt": options.publish_at,
  87. },
  88. "recordingDetails": {
  89. "location": lib.string_to_dict(options.location),
  90. "recordingDate": options.recording_date,
  91. },
  92. }
  93. debug("Start upload: {0}".format(video_path))
  94. if JOB is not None:
  95. JOB.meta['status'] = 'starting upload'
  96. JOB.save()
  97. try:
  98. video_id = upload_video.upload(youtube, video_path,
  99. request_body, progress_callback=progress.callback)
  100. finally:
  101. progress.finish()
  102. return video_id
  103. def get_youtube_handler(options):
  104. """Return the API Youtube object."""
  105. home = os.path.expanduser("~")
  106. default_client_secrets = lib.get_first_existing_filename(
  107. [sys.prefix, os.path.join(sys.prefix, "local")],
  108. "share/youtube_upload/client_secrets.json")
  109. default_credentials = os.path.join(home, ".youtube-upload-credentials.json")
  110. client_secrets = options.client_secrets or default_client_secrets or \
  111. os.path.join(home, ".client_secrets.json")
  112. credentials = options.credentials_file or default_credentials
  113. debug("Using client secrets: {0}".format(client_secrets))
  114. debug("Using credentials file: {0}".format(credentials))
  115. get_code_callback = (auth.browser.get_code
  116. if options.auth_browser else auth.console.get_code)
  117. return auth.get_resource(client_secrets, credentials,
  118. get_code_callback=get_code_callback)
  119. def run_main(title,thumb,path,output=sys.stdout):
  120. """Run the main scripts from the parsed options/args."""
  121. options=AttrDict({ 'title': title, 'thumb': thumb ,
  122. 'privacy': 'unlisted', 'category': None,
  123. 'client_secrets': None, 'playlist': None, 'credentials_file': None, 'description': None, 'recording_date': None,
  124. 'tags': None, 'open_link': None, 'default_language': None, 'title_template': '{title} [{n}/{total}]', 'location': None,
  125. 'default_audio_language': None, 'publish_at': None, 'auth_browser': None
  126. })
  127. JOB.meta["type"]="Youtube"
  128. JOB.meta["target"]=title
  129. youtube = get_youtube_handler(options)
  130. video_path = path
  131. video_id=None
  132. if youtube:
  133. video_id = upload_youtube_video(youtube, options, video_path, 1, 1)
  134. if options.thumb:
  135. youtube.thumbnails().set(videoId=video_id, media_body=options.thumb).execute()
  136. output.write(video_id + "\n") #FIXME
  137. if JOB is not None:
  138. JOB.meta["video_id"]=video_id
  139. JOB.save()
  140. else:
  141. raise AuthenticationError("Cannot get youtube resource")
  142. return video_id
  143. def main(title,thumb,path,job=None):
  144. global JOB
  145. try:
  146. JOB = job
  147. return run_main(title,thumb,path)
  148. except googleapiclient.errors.HttpError as error:
  149. raise RequestError("Server response: {0}".format(bytes.decode(error.content).strip()))