diff --git a/README.md b/README.md index 23c9319..f20ea67 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,8 @@ Use --help to get all available options: ``` Options: - -f, --file=STRING Path to the video file to upload in mp4 + -f, --file=STRING Path to the video file to upload in mp4. This is the only mandatory option. --name=NAME Name of the video to upload. (default to video filename) - --debug Trigger some debug information like options used (default: no) -d, --description=STRING Description of the video. (default: default description) -t, --tags=STRING Tags for the video. comma separated. WARN: tags with punctuation (!, ', ", ?, ...) @@ -160,6 +159,34 @@ Options: -h --help Show this help. --version Show version. +Logging options + -q --quiet Suppress any log except Critical (alias for --log=critical). + --log=STRING Log level, between debug, info, warning, error, critical. Ignored if --quiet is set (default to info) + -u --url-only Display generated URL after upload directly on stdout, implies --quiet + --batch Display generated URL after upload with platform information for easier parsing. Implies --quiet + Be careful --batch and --url-only are mutually exclusives. + --debug (Deprecated) Alias for --log=debug. Ignored if --log is set + +Strict options: + Strict options allow you to force some option to be present when uploading a video. It's useful to be sure you do not + forget something when uploading a video, for example if you use multiples NFO. You may force the presence of description, + tags, thumbnail, ... + All strict option are optionals and are provided only to avoid errors when uploading :-) + All strict options can be specified in NFO directly, the only strict option mandatory on cli is --withNFO + All strict options are off by default + + --withNFO Prevent the upload without a NFO, either specified via cli or found in the directory + --withThumbnail Prevent the upload without a thumbnail + --withName Prevent the upload if no name are found + --withDescription Prevent the upload without description + --withTags Prevent the upload without tags + --withPlaylist Prevent the upload if no playlist + --withPublishAt Prevent the upload if no schedule + --withPlatform Prevent the upload if at least one platform is not specified + --withCategory Prevent the upload if no category + --withLanguage Prevent upload if no language + --withChannel Prevent upload if no channel + Categories: Category is the type of video you upload. Default is films. Here are available categories from Peertube and Youtube: @@ -174,6 +201,7 @@ Languages: Here are available languages from Peertube and Youtube: Arabic, English, French, German, Hindi, Italian, Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish + ``` ## Enhanced use of NFO diff --git a/prismedia/pt_upload.py b/prismedia/pt_upload.py index 8e1aa1f..b95e550 100644 --- a/prismedia/pt_upload.py +++ b/prismedia/pt_upload.py @@ -5,6 +5,7 @@ import os import mimetypes import json import logging +import sys import datetime import pytz from os.path import splitext, basename, abspath @@ -16,6 +17,7 @@ from oauthlib.oauth2 import LegacyApplicationClient from requests_toolbelt.multipart.encoder import MultipartEncoder from . import utils +logger = logging.getLogger('Prismedia') PEERTUBE_SECRETS_FILE = 'peertube_secret' PEERTUBE_PRIVACY = { @@ -43,10 +45,10 @@ def get_authenticated_service(secret): ) except Exception as e: if hasattr(e, 'message'): - logging.error("Peertube: Error: " + str(e.message)) + logger.critical("Peertube: " + str(e.message)) exit(1) else: - logging.error("Peertube: Error: " + str(e)) + logger.critical("Peertube: " + str(e)) exit(1) return oauth @@ -63,7 +65,7 @@ def get_channel_by_name(user_info, options): def create_channel(oauth, url, options): template = ('Peertube: Channel %s does not exist, creating it.') - logging.info(template % (str(options.get('--channel')))) + logger.info(template % (str(options.get('--channel')))) channel_name = utils.cleanString(str(options.get('--channel'))) # Peertube allows 20 chars max for channel name channel_name = channel_name[:19] @@ -81,23 +83,23 @@ def create_channel(oauth, url, options): headers=headers) except Exception as e: if hasattr(e, 'message'): - logging.error("Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Error: " + str(e)) + logger.error("Peertube: " + str(e)) if response is not None: if response.status_code == 200: jresponse = response.json() jresponse = jresponse['videoChannel'] return jresponse['id'] if response.status_code == 409: - logging.error('Peertube: Error: It seems there is a conflict with an existing channel named ' + logger.critical('Peertube: It seems there is a conflict with an existing channel named ' + channel_name + '.' ' Please beware Peertube internal name is compiled from 20 firsts characters of channel name.' ' Also note that channel name are not case sensitive (no uppercase nor accent)' ' Please check your channel name and retry.') exit(1) else: - logging.error(('Peertube: Creating channel failed with an unexpected response: ' + logger.critical(('Peertube: Creating channel failed with an unexpected response: ' '%s') % response) exit(1) @@ -114,7 +116,7 @@ def get_playlist_by_name(user_playlists, options): def create_playlist(oauth, url, options, channel): template = ('Peertube: Playlist %s does not exist, creating it.') - logging.info(template % (str(options.get('--playlist')))) + logger.info(template % (str(options.get('--playlist')))) # We use files for form-data Content # see https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file # None is used to mute "filename" field @@ -128,22 +130,22 @@ def create_playlist(oauth, url, options, channel): files=files) except Exception as e: if hasattr(e, 'message'): - logging.error("Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Error: " + str(e)) + logger.error("Peertube: " + str(e)) if response is not None: if response.status_code == 200: jresponse = response.json() jresponse = jresponse['videoPlaylist'] return jresponse['id'] else: - logging.error(('Peertube: Creating the playlist failed with an unexpected response: ' + logger.critical(('Peertube: Creating the playlist failed with an unexpected response: ' '%s') % response) exit(1) def set_playlist(oauth, url, video_id, playlist_id): - logging.info('Peertube: add video to playlist.') + logger.info('Peertube: add video to playlist.') data = '{"videoId":"' + str(video_id) + '"}' headers = { @@ -155,14 +157,14 @@ def set_playlist(oauth, url, video_id, playlist_id): headers=headers) except Exception as e: if hasattr(e, 'message'): - logging.error("Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Error: " + str(e)) + logger.error("Peertube: " + str(e)) if response is not None: if response.status_code == 200: - logging.info('Peertube: Video is successfully added to the playlist.') + logger.info('Peertube: Video is successfully added to the playlist.') else: - logging.error(('Peertube: Configuring the playlist failed with an unexpected response: ' + logger.critical(('Peertube: Configuring the playlist failed with an unexpected response: ' '%s') % response) exit(1) @@ -205,8 +207,9 @@ def upload_video(oauth, secret, options): continue # Tag more than 30 chars crashes Peertube, so exit and check tags if len(strtag) >= 30: - logging.warning("Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag) - exit(1) + logger.error("Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag) + logger.error("Peertube: Meanwhile, this tag will be skipped") + continue fields.append(("tags[]", strtag)) if options.get('--category'): @@ -256,7 +259,7 @@ def upload_video(oauth, secret, options): if not channel_id and options.get('--channelCreate'): channel_id = create_channel(oauth, url, options) elif not channel_id: - logging.warning("Channel `" + options.get('--channel') + "` is unknown, using default channel.") + logger.warning("Peertube: Channel `" + options.get('--channel') + "` is unknown, using default channel.") channel_id = get_default_channel(user_info) else: channel_id = get_default_channel(user_info) @@ -268,10 +271,14 @@ def upload_video(oauth, secret, options): if not playlist_id and options.get('--playlistCreate'): playlist_id = create_playlist(oauth, url, options, channel_id) elif not playlist_id: - logging.warning("Playlist `" + options.get('--playlist') + "` does not exist, please set --playlistCreate" + logger.critical("Peertube: Playlist `" + options.get('--playlist') + "` does not exist, please set --playlistCreate" " if you want to create it") exit(1) + logger_stdout = None + if options.get('--url-only') or options.get('--batch'): + logger_stdout = logging.getLogger('stdoutlogs') + multipart_data = MultipartEncoder(fields) headers = { @@ -286,14 +293,19 @@ def upload_video(oauth, secret, options): jresponse = jresponse['video'] uuid = jresponse['uuid'] video_id = str(jresponse['id']) - logging.info('Peertube : Video was successfully uploaded.') + logger.info('Peertube : Video was successfully uploaded.') template = 'Peertube: Watch it at %s/videos/watch/%s.' - logging.info(template % (url, uuid)) + logger.info(template % (url, uuid)) + template_stdout = '%s/videos/watch/%s' + if options.get('--url-only'): + logger_stdout.info(template_stdout % (url, uuid)) + elif options.get('--batch'): + logger_stdout.info("Peertube: " + template_stdout % (url, uuid)) # Upload is successful we may set playlist if options.get('--playlist'): set_playlist(oauth, url, video_id, playlist_id) else: - logging.error(('Peertube: The upload failed with an unexpected response: ' + logger.critical(('Peertube: The upload failed with an unexpected response: ' '%s') % response) exit(1) @@ -303,16 +315,16 @@ def run(options): try: secret.read(PEERTUBE_SECRETS_FILE) except Exception as e: - logging.error("Peertube: Error loading " + str(PEERTUBE_SECRETS_FILE) + ": " + str(e)) + logger.critical("Peertube: Error loading " + str(PEERTUBE_SECRETS_FILE) + ": " + str(e)) exit(1) insecure_transport = secret.get('peertube', 'OAUTHLIB_INSECURE_TRANSPORT') os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = insecure_transport oauth = get_authenticated_service(secret) try: - logging.info('Peertube: Uploading video...') + logger.info('Peertube: Uploading video...') upload_video(oauth, secret, options) except Exception as e: if hasattr(e, 'message'): - logging.error("Peertube: Error: " + str(e.message)) + logger.error("Peertube: " + str(e.message)) else: - logging.error("Peertube: Error: " + str(e)) + logger.error("Peertube: " + str(e)) diff --git a/prismedia/upload.py b/prismedia/upload.py index 1a5fcb3..d91cafa 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -11,9 +11,8 @@ Usage: prismedia --version Options: - -f, --file=STRING Path to the video file to upload in mp4 + -f, --file=STRING Path to the video file to upload in mp4. This is the only mandatory option. --name=NAME Name of the video to upload. (default to video filename) - --debug Trigger some debug information like options used (default: no) -d, --description=STRING Description of the video. (default: default description) -t, --tags=STRING Tags for the video. comma separated. WARN: tags with punctuation (!, ', ", ?, ...) @@ -49,6 +48,14 @@ Options: -h --help Show this help. --version Show version. +Logging options + -q --quiet Suppress any log except Critical (alias for --log=critical). + --log=STRING Log level, between debug, info, warning, error, critical. Ignored if --quiet is set (default to info) + -u --url-only Display generated URL after upload directly on stdout, implies --quiet + --batch Display generated URL after upload with platform information for easier parsing. Implies --quiet + Be careful --batch and --url-only are mutually exclusives. + --debug (Deprecated) Alias for --log=debug. Ignored if --log is set + Strict options: Strict options allow you to force some option to be present when uploading a video. It's useful to be sure you do not forget something when uploading a video, for example if you use multiples NFO. You may force the presence of description, @@ -92,7 +99,13 @@ if sys.version_info[0] < 3: import os import datetime import logging -logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) +logger = logging.getLogger('Prismedia') +logger.setLevel(logging.INFO) +ch = logging.StreamHandler() +ch.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s') +ch.setFormatter(formatter) +logger.addHandler(ch) from docopt import docopt @@ -102,9 +115,9 @@ from . import utils try: # noinspection PyUnresolvedReferences - from schema import Schema, And, Or, Optional, SchemaError, Hook + from schema import Schema, And, Or, Optional, SchemaError, Hook, Use except ImportError: - logging.error('This program requires that the `schema` data-validation library' + logger.critical('This program requires that the `schema` data-validation library' ' is installed: \n' 'see https://github.com/halst/schema\n') exit(1) @@ -112,7 +125,7 @@ try: # noinspection PyUnresolvedReferences import magic except ImportError: - logging.error('This program requires that the `python-magic` library' + logger.critical('This program requires that the `python-magic` library' ' is installed, NOT the Python bindings to libmagic API \n' 'see https://github.com/ahupp/python-magic\n') exit(1) @@ -198,19 +211,68 @@ def validateThumbnail(thumbnail): return False +def validateLogLevel(loglevel): + numeric_level = getattr(logging, loglevel, None) + if not isinstance(numeric_level, int): + return False + return True + def _optionnalOrStrict(key, scope, error): option = key.replace('-', '') option = option[0].upper() + option[1:] if scope["--with" + option] is True and scope[key] is None: - logging.error("Prismedia: you have required the strict presence of " + key + " but none is found") + logger.critical("Prismedia: you have required the strict presence of " + key + " but none is found") exit(1) return True +def configureLogs(options): + if options.get('--batch') and options.get('--url-only'): + logger.critical("Prismedia: Please use either --batch OR --url-only, not both.") + exit(1) + # batch and url-only implies quiet + if options.get('--batch') or options.get('--url-only'): + options['--quiet'] = True + + if options.get('--quiet'): + # We need to set both log level in the same time + logger.setLevel(50) + ch.setLevel(50) + elif options.get('--log'): + numeric_level = getattr(logging, options["--log"], None) + # We need to set both log level in the same time + logger.setLevel(numeric_level) + ch.setLevel(numeric_level) + elif options.get('--debug'): + logger.warning("DEPRECATION: --debug is deprecated, please use --log=debug instead") + logger.setLevel(10) + ch.setLevel(10) + + +def configureStdoutLogs(): + logger_stdout = logging.getLogger('stdoutlogs') + logger_stdout.setLevel(logging.INFO) + ch_stdout = logging.StreamHandler(stream=sys.stdout) + ch_stdout.setLevel(logging.INFO) + # Default stdout logs is url only + formatter_stdout = logging.Formatter('%(message)s') + ch_stdout.setFormatter(formatter_stdout) + logger_stdout.addHandler(ch_stdout) + def main(): options = docopt(__doc__, version=VERSION) - strictoptionSchema = Schema({ + earlyoptionSchema = Schema({ + Optional('--log'): Or(None, And( + str, + Use(str.upper), + validateLogLevel, + error="Log level not recognized") + ), + Optional('--quiet', default=False): bool, + Optional('--debug'): bool, + Optional('--url-only', default=False): bool, + Optional('--batch', default=False): bool, Optional('--withNFO', default=False): bool, Optional('--withThumbnail', default=False): bool, Optional('--withName', default=False): bool, @@ -222,7 +284,8 @@ def main(): Optional('--withCategory', default=False): bool, Optional('--withLanguage', default=False): bool, Optional('--withChannel', default=False): bool, - object: object # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + object: object }) schema = Schema({ @@ -286,7 +349,6 @@ def main(): validatePublish, error="DATE should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") ), - Optional('--debug'): bool, Optional('--cca'): bool, Optional('--disable-comments'): bool, Optional('--nsfw'): bool, @@ -299,22 +361,28 @@ def main(): Optional('--playlistCreate'): bool, '--help': bool, '--version': bool, - object: object # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys + object: object }) - - # We need to validate strict options first as withNFO should be validated before NFO parsing + # We need to validate early options first as withNFO and logs options should be prioritized try: - options = strictoptionSchema.validate(options) + options = earlyoptionSchema.validate(options) + configureLogs(options) except SchemaError as e: - exit(e) + logger.critical(e) + exit(1) + + if options.get('--url-only') or options.get('--batch'): + configureStdoutLogs() options = utils.parseNFO(options) # Once NFO are loaded, we need to revalidate strict options in case some were in NFO try: - options = strictoptionSchema.validate(options) + options = earlyoptionSchema.validate(options) except SchemaError as e: - exit(e) + logger.critical(e) + exit(1) if not options.get('--thumbnail'): options = utils.searchThumbnail(options) @@ -322,11 +390,11 @@ def main(): try: options = schema.validate(options) except SchemaError as e: - exit(e) + logger.critical(e) + exit(1) - if options.get('--debug'): - print("Python " + sys.version) - print(options) + logger.debug("Python " + sys.version) + logger.debug(options) if options.get('--platform') is None or "peertube" in options.get('--platform'): pt_upload.run(options) @@ -335,6 +403,5 @@ def main(): if __name__ == '__main__': - import warnings - warnings.warn("use 'python -m prismedia', not 'python -m prismedia.upload'", DeprecationWarning) + logger.warning("DEPRECATION: use 'python -m prismedia', not 'python -m prismedia.upload'") main() diff --git a/prismedia/utils.py b/prismedia/utils.py index 36b54fb..c2239c6 100644 --- a/prismedia/utils.py +++ b/prismedia/utils.py @@ -9,6 +9,8 @@ from subprocess import check_call, CalledProcessError, STDOUT import unidecode import logging +logger = logging.getLogger('Prismedia') + ### CATEGORIES ### YOUTUBE_CATEGORY = { "music": 10, @@ -123,18 +125,25 @@ def searchThumbnail(options): options['--thumbnail'] = video_directory + video_file + ".jpg" elif isfile(video_directory + video_file + ".jpeg"): options['--thumbnail'] = video_directory + video_file + ".jpeg" + + # Display some info after research + if not options.get('--thumbnail'): + logger.debug("No thumbnail has been found, continuing") + else: + logger.info("Using " + options.get('--thumbnail') + "as thumbnail") + return options # return the nfo as a RawConfigParser object def loadNFO(filename): try: - logging.info("Loading " + filename + " as NFO") + logger.info("Loading " + filename + " as NFO") nfo = RawConfigParser() nfo.read(filename, encoding='utf-8') return nfo except Exception as e: - logging.error("Problem loading NFO file " + filename + ": " + str(e)) + logger.critical("Problem loading NFO file " + filename + ": " + str(e)) exit(1) return False @@ -168,7 +177,7 @@ def parseNFO(options): if isfile(options.get('--nfo')): nfo_cli = loadNFO(options.get('--nfo')) else: - logging.error("Given NFO file does not exist, please check your path.") + logger.critical("Given NFO file does not exist, please check your path.") exit(1) # If there is no NFO and strict option is enabled, then stop there @@ -178,7 +187,7 @@ def parseNFO(options): not isinstance(nfo_videoname, RawConfigParser) and \ not isinstance(nfo_directory, RawConfigParser) and \ not isinstance(nfo_txt, RawConfigParser): - logging.error("Prismedia: you have required the strict presence of NFO but none is found, please use a NFO.") + logger.critical("You have required the strict presence of NFO but none is found, please use a NFO.") exit(1) # We need to load NFO in this exact order to keep the priorities @@ -198,7 +207,7 @@ def parseNFO(options): except NoOptionError: continue except NoSectionError: - logging.error("Prismedia: " + nfo + " misses section [video], please check syntax of your NFO.") + logger.critical(nfo + " misses section [video], please check syntax of your NFO.") exit(1) return options diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index da21ccc..5ed432d 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -23,9 +23,7 @@ from google_auth_oauthlib.flow import InstalledAppFlow from . import utils - -logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) - +logger = logging.getLogger('Prismedia') # Explicitly tell the underlying HTTP transport library not to retry, since # we are handling retry logic ourselves. @@ -87,7 +85,7 @@ def check_authenticated_scopes(): credential_params = json.load(f) # Check if all scopes are present if credential_params["_scopes"] != SCOPES: - logging.warning("Youtube: Credentials are obsolete, need to re-authenticate.") + logger.warning("Youtube: Credentials are obsolete, need to re-authenticate.") os.remove(CREDENTIALS_PATH) @@ -144,8 +142,8 @@ def initialize_upload(youtube, options): if not playlist_id and options.get('--playlistCreate'): playlist_id = create_playlist(youtube, options.get('--playlist')) elif not playlist_id: - logging.warning("Youtube: Playlist `" + options.get('--playlist') + "` is unknown.") - logging.warning("If you want to create it, set the --playlistCreate option.") + logger.warning("Youtube: Playlist `" + options.get('--playlist') + "` is unknown.") + logger.warning("Youtube: If you want to create it, set the --playlistCreate option.") playlist_id = "" else: playlist_id = "" @@ -156,7 +154,7 @@ def initialize_upload(youtube, options): body=body, media_body=MediaFileUpload(path, chunksize=-1, resumable=True) ) - video_id = resumable_upload(insert_request, 'video', 'insert') + video_id = resumable_upload(insert_request, 'video', 'insert', options) # If we get a video_id, upload is successful and we are able to set thumbnail if video_id and options.get('--thumbnail'): @@ -179,8 +177,8 @@ def get_playlist_by_name(youtube, playlist_name): def create_playlist(youtube, playlist_name): - template = ('Youtube: Playlist %s does not exist, creating it.') - logging.info(template % (str(playlist_name))) + template = 'Youtube: Playlist %s does not exist, creating it.' + logger.info(template % (str(playlist_name))) resources = build_resource({'snippet.title': playlist_name, 'snippet.description': '', 'status.privacyStatus': 'public'}) @@ -244,7 +242,7 @@ def set_thumbnail(youtube, media_file, **kwargs): def set_playlist(youtube, playlist_id, video_id): - logging.info('Youtube: Configuring playlist...') + logger.info('Youtube: Configuring playlist...') resource = build_resource({'snippet.playlistId': playlist_id, 'snippet.resourceId.kind': 'youtube#video', 'snippet.resourceId.videoId': video_id, @@ -257,37 +255,45 @@ def set_playlist(youtube, playlist_id, video_id): ).execute() except Exception as e: if hasattr(e, 'message'): - logging.error("Youtube: Error: " + str(e.message)) + logger.critical("Youtube: " + str(e.message)) exit(1) else: - logging.error("Youtube: Error: " + str(e)) + logger.critical("Youtube: " + str(e)) exit(1) - logging.info('Youtube: Video is correctly added to the playlist.') + logger.info('Youtube: Video is correctly added to the playlist.') # This method implements an exponential backoff strategy to resume a # failed upload. -def resumable_upload(request, resource, method): +def resumable_upload(request, resource, method, options): response = None error = None retry = 0 + logger_stdout = None + if options.get('--url-only') or options.get('--batch'): + logger_stdout = logging.getLogger('stdoutlogs') while response is None: try: template = 'Youtube: Uploading %s...' - logging.info(template % resource) + logger.info(template % resource) status, response = request.next_chunk() if response is not None: if method == 'insert' and 'id' in response: - logging.info('Youtube : Video was successfully uploaded.') + logger.info('Youtube : Video was successfully uploaded.') template = 'Youtube: Watch it at https://youtu.be/%s (post-encoding could take some time)' - logging.info(template % response['id']) + logger.info(template % response['id']) + template_stdout = 'https://youtu.be/%s' + if options.get('--url-only'): + logger_stdout.info(template_stdout % response['id']) + elif options.get('--batch'): + logger_stdout.info("Youtube: " + template_stdout % response['id']) return response['id'] elif method != 'insert' or "id" not in response: - logging.info('Youtube: Thumbnail was successfully set.') + logger.info('Youtube: Thumbnail was successfully set.') else: template = ('Youtube : The upload failed with an ' 'unexpected response: %s') - logging.error(template % response) + logger.critical(template % response) exit(1) except HttpError as e: if e.resp.status in RETRIABLE_STATUS_CODES: @@ -299,15 +305,14 @@ def resumable_upload(request, resource, method): error = 'Youtube : A retriable error occurred: %s' % e if error is not None: - logging.warning(error) + logger.warning(error) retry += 1 if retry > MAX_RETRIES: - logging.error('Youtube : No longer attempting to retry.') - exit(1) + logger.error('Youtube : No longer attempting to retry.') max_sleep = 2 ** retry sleep_seconds = random.random() * max_sleep - logging.warning('Youtube : Sleeping %f seconds and then retrying...' + logger.warning('Youtube : Sleeping %f seconds and then retrying...' % sleep_seconds) time.sleep(sleep_seconds) @@ -317,5 +322,5 @@ def run(options): try: initialize_upload(youtube, options) except HttpError as e: - logging.error('Youtube : An HTTP error %d occurred:\n%s' % (e.resp.status, + logger.error('Youtube : An HTTP error %d occurred:\n%s' % (e.resp.status, e.content))