|
@@ -0,0 +1,166 @@
|
|
|
|
|
+#!/usr/bin/env python
|
|
|
|
|
+import os
|
|
|
|
|
+import sys
|
|
|
|
|
+import collections
|
|
|
|
|
+
|
|
|
|
|
+import googleapiclient.errors
|
|
|
|
|
+import oauth2client
|
|
|
|
|
+
|
|
|
|
|
+from . import auth
|
|
|
|
|
+from . import upload_video
|
|
|
|
|
+from . import categories
|
|
|
|
|
+from . import lib
|
|
|
|
|
+from . import playlists
|
|
|
|
|
+
|
|
|
|
|
+class InvalidCategory(Exception): pass
|
|
|
|
|
+class OptionsError(Exception): pass
|
|
|
|
|
+class AuthenticationError(Exception): pass
|
|
|
|
|
+class RequestError(Exception): pass
|
|
|
|
|
+
|
|
|
|
|
+EXIT_CODES = {
|
|
|
|
|
+ OptionsError: 2,
|
|
|
|
|
+ InvalidCategory: 3,
|
|
|
|
|
+ RequestError: 3,
|
|
|
|
|
+ AuthenticationError: 4,
|
|
|
|
|
+ oauth2client.client.FlowExchangeError: 4,
|
|
|
|
|
+ NotImplementedError: 5,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+USE_DEBUG = False
|
|
|
|
|
+JOB = None
|
|
|
|
|
+#debug = lib.debug
|
|
|
|
|
+def debug(args):
|
|
|
|
|
+ if USE_DEBUG:
|
|
|
|
|
+ print(args)
|
|
|
|
|
+struct = collections.namedtuple
|
|
|
|
|
+
|
|
|
|
|
+class AttrDict(dict):
|
|
|
|
|
+ def __init__(self, *args, **kwargs):
|
|
|
|
|
+ super(AttrDict, self).__init__(*args, **kwargs)
|
|
|
|
|
+ self.__dict__ = self
|
|
|
|
|
+
|
|
|
|
|
+def get_progress_info():
|
|
|
|
|
+ """Return a function callback to update the progressbar."""
|
|
|
|
|
+ progressinfo = struct("ProgressInfo", ["callback", "finish"])
|
|
|
|
|
+ def _callback(total_size, completed):
|
|
|
|
|
+ if JOB is not None:
|
|
|
|
|
+ JOB.meta['status'] = 'uploading'
|
|
|
|
|
+ JOB.meta['progress'] = (completed/total_size)*100
|
|
|
|
|
+ JOB.save()
|
|
|
|
|
+ def _finish():
|
|
|
|
|
+ if JOB is not None:
|
|
|
|
|
+ JOB.meta['status'] = 'finished'
|
|
|
|
|
+ JOB.meta['progress'] = 100
|
|
|
|
|
+ JOB.save()
|
|
|
|
|
+ return progressinfo(callback=_callback, finish=_finish)
|
|
|
|
|
+
|
|
|
|
|
+def get_category_id(category):
|
|
|
|
|
+ """Return category ID from its name."""
|
|
|
|
|
+ if category:
|
|
|
|
|
+ if category in categories.IDS:
|
|
|
|
|
+ ncategory = categories.IDS[category]
|
|
|
|
|
+ debug("Using category: {0} (id={1})".format(category, ncategory))
|
|
|
|
|
+ return str(categories.IDS[category])
|
|
|
|
|
+ else:
|
|
|
|
|
+ msg = "{0} is not a valid category".format(category)
|
|
|
|
|
+ raise InvalidCategory(msg)
|
|
|
|
|
+
|
|
|
|
|
+def upload_youtube_video(youtube, options, video_path, total_videos, index):
|
|
|
|
|
+ """Upload video with index (for split videos)."""
|
|
|
|
|
+ u = lib.to_utf8
|
|
|
|
|
+ title = u(options.title)
|
|
|
|
|
+ if hasattr(u('string'), 'decode'):
|
|
|
|
|
+ description = u(options.description or "").decode("string-escape")
|
|
|
|
|
+ else:
|
|
|
|
|
+ description = options.description
|
|
|
|
|
+ if options.publish_at:
|
|
|
|
|
+ debug("Your video will remain private until specified date.")
|
|
|
|
|
+
|
|
|
|
|
+ tags = [u(s.strip()) for s in (options.tags or "").split(",")]
|
|
|
|
|
+ ns = dict(title=title, n=index+1, total=total_videos)
|
|
|
|
|
+ title_template = u(options.title_template)
|
|
|
|
|
+ complete_title = (title_template.format(**ns) if total_videos > 1 else title)
|
|
|
|
|
+ progress = get_progress_info()
|
|
|
|
|
+ category_id = get_category_id(options.category)
|
|
|
|
|
+ request_body = {
|
|
|
|
|
+ "snippet": {
|
|
|
|
|
+ "title": complete_title,
|
|
|
|
|
+ "description": description,
|
|
|
|
|
+ "categoryId": category_id,
|
|
|
|
|
+ "tags": tags,
|
|
|
|
|
+ "defaultLanguage": options.default_language,
|
|
|
|
|
+ "defaultAudioLanguage": options.default_audio_language,
|
|
|
|
|
+
|
|
|
|
|
+ },
|
|
|
|
|
+ "status": {
|
|
|
|
|
+ "privacyStatus": ("private" if options.publish_at else options.privacy),
|
|
|
|
|
+ "publishAt": options.publish_at,
|
|
|
|
|
+
|
|
|
|
|
+ },
|
|
|
|
|
+ "recordingDetails": {
|
|
|
|
|
+ "location": lib.string_to_dict(options.location),
|
|
|
|
|
+ "recordingDate": options.recording_date,
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ debug("Start upload: {0}".format(video_path))
|
|
|
|
|
+ if JOB is not None:
|
|
|
|
|
+ JOB.meta['status'] = 'starting upload'
|
|
|
|
|
+ JOB.save()
|
|
|
|
|
+ try:
|
|
|
|
|
+ video_id = upload_video.upload(youtube, video_path,
|
|
|
|
|
+ request_body, progress_callback=progress.callback)
|
|
|
|
|
+ finally:
|
|
|
|
|
+ progress.finish()
|
|
|
|
|
+ return video_id
|
|
|
|
|
+
|
|
|
|
|
+def get_youtube_handler(options):
|
|
|
|
|
+ """Return the API Youtube object."""
|
|
|
|
|
+ home = os.path.expanduser("~")
|
|
|
|
|
+ default_client_secrets = lib.get_first_existing_filename(
|
|
|
|
|
+ [sys.prefix, os.path.join(sys.prefix, "local")],
|
|
|
|
|
+ "share/youtube_upload/client_secrets.json")
|
|
|
|
|
+ default_credentials = os.path.join(home, ".youtube-upload-credentials.json")
|
|
|
|
|
+ client_secrets = options.client_secrets or default_client_secrets or \
|
|
|
|
|
+ os.path.join(home, ".client_secrets.json")
|
|
|
|
|
+ credentials = options.credentials_file or default_credentials
|
|
|
|
|
+ debug("Using client secrets: {0}".format(client_secrets))
|
|
|
|
|
+ debug("Using credentials file: {0}".format(credentials))
|
|
|
|
|
+ get_code_callback = (auth.browser.get_code
|
|
|
|
|
+ if options.auth_browser else auth.console.get_code)
|
|
|
|
|
+ return auth.get_resource(client_secrets, credentials,
|
|
|
|
|
+ get_code_callback=get_code_callback)
|
|
|
|
|
+
|
|
|
|
|
+def run_main(title,thumb,path,output=sys.stdout):
|
|
|
|
|
+ """Run the main scripts from the parsed options/args."""
|
|
|
|
|
+ options=AttrDict({ 'title': title, 'thumb': thumb ,
|
|
|
|
|
+ 'privacy': 'unlisted', 'category': None,
|
|
|
|
|
+ 'client_secrets': None, 'playlist': None, 'credentials_file': None, 'description': None, 'recording_date': None,
|
|
|
|
|
+ 'tags': None, 'open_link': None, 'default_language': None, 'title_template': '{title} [{n}/{total}]', 'location': None,
|
|
|
|
|
+ 'default_audio_language': None, 'publish_at': None, 'auth_browser': None
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ youtube = get_youtube_handler(options)
|
|
|
|
|
+ video_path = path
|
|
|
|
|
+ video_id=None
|
|
|
|
|
+ if youtube:
|
|
|
|
|
+ video_id = upload_youtube_video(youtube, options, video_path, 1, 1)
|
|
|
|
|
+ if options.thumb:
|
|
|
|
|
+ youtube.thumbnails().set(videoId=video_id, media_body=options.thumb).execute()
|
|
|
|
|
+
|
|
|
|
|
+ output.write(video_id + "\n") #FIXME
|
|
|
|
|
+ if JOB is not None:
|
|
|
|
|
+ JOB.meta["video_id"]=video_id
|
|
|
|
|
+ JOB.save()
|
|
|
|
|
+ else:
|
|
|
|
|
+ raise AuthenticationError("Cannot get youtube resource")
|
|
|
|
|
+
|
|
|
|
|
+ return video_id
|
|
|
|
|
+
|
|
|
|
|
+def main(title,thumb,path,job=None):
|
|
|
|
|
+ global JOB
|
|
|
|
|
+ try:
|
|
|
|
|
+ JOB = job
|
|
|
|
|
+ return run_main(title,thumb,path)
|
|
|
|
|
+ except googleapiclient.errors.HttpError as error:
|
|
|
|
|
+ raise RequestError("Server response: {0}".format(bytes.decode(error.content).strip()))
|