Browse Source

Merge branch 'feature/logs_improvement' into develop

pull/51/head
LecygneNoir 3 years ago
parent
commit
f8ca4b093a
5 changed files with 202 additions and 81 deletions
  1. +30
    -2
      README.md
  2. +39
    -27
      prismedia/pt_upload.py
  3. +90
    -23
      prismedia/upload.py
  4. +14
    -5
      prismedia/utils.py
  5. +29
    -24
      prismedia/yt_upload.py

+ 30
- 2
README.md View File

@ -122,9 +122,8 @@ Use --help to get all available options:
``` ```
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) --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) -d, --description=STRING Description of the video. (default: default description)
-t, --tags=STRING Tags for the video. comma separated. -t, --tags=STRING Tags for the video. comma separated.
WARN: tags with punctuation (!, ', ", ?, ...) WARN: tags with punctuation (!, ', ", ?, ...)
@ -160,6 +159,34 @@ Options:
-h --help Show this help. -h --help Show this help.
--version Show version. --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: Categories:
Category is the type of video you upload. Default is films. Category is the type of video you upload. Default is films.
Here are available categories from Peertube and Youtube: Here are available categories from Peertube and Youtube:
@ -174,6 +201,7 @@ Languages:
Here are available languages from Peertube and Youtube: Here are available languages from Peertube and Youtube:
Arabic, English, French, German, Hindi, Italian, Arabic, English, French, German, Hindi, Italian,
Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish Japanese, Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish
``` ```
## Enhanced use of NFO ## Enhanced use of NFO

+ 39
- 27
prismedia/pt_upload.py View File

@ -5,6 +5,7 @@ import os
import mimetypes import mimetypes
import json import json
import logging import logging
import sys
import datetime import datetime
import pytz import pytz
from os.path import splitext, basename, abspath from os.path import splitext, basename, abspath
@ -16,6 +17,7 @@ from oauthlib.oauth2 import LegacyApplicationClient
from requests_toolbelt.multipart.encoder import MultipartEncoder from requests_toolbelt.multipart.encoder import MultipartEncoder
from . import utils from . import utils
logger = logging.getLogger('Prismedia')
PEERTUBE_SECRETS_FILE = 'peertube_secret' PEERTUBE_SECRETS_FILE = 'peertube_secret'
PEERTUBE_PRIVACY = { PEERTUBE_PRIVACY = {
@ -43,10 +45,10 @@ def get_authenticated_service(secret):
) )
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Peertube: Error: " + str(e.message))
logger.critical("Peertube: " + str(e.message))
exit(1) exit(1)
else: else:
logging.error("Peertube: Error: " + str(e))
logger.critical("Peertube: " + str(e))
exit(1) exit(1)
return oauth return oauth
@ -63,7 +65,7 @@ def get_channel_by_name(user_info, options):
def create_channel(oauth, url, options): def create_channel(oauth, url, options):
template = ('Peertube: Channel %s does not exist, creating it.') 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'))) channel_name = utils.cleanString(str(options.get('--channel')))
# Peertube allows 20 chars max for channel name # Peertube allows 20 chars max for channel name
channel_name = channel_name[:19] channel_name = channel_name[:19]
@ -81,23 +83,23 @@ def create_channel(oauth, url, options):
headers=headers) headers=headers)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Error: " + str(e))
logger.error("Peertube: " + str(e))
if response is not None: if response is not None:
if response.status_code == 200: if response.status_code == 200:
jresponse = response.json() jresponse = response.json()
jresponse = jresponse['videoChannel'] jresponse = jresponse['videoChannel']
return jresponse['id'] return jresponse['id']
if response.status_code == 409: 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 + '.' + channel_name + '.'
' Please beware Peertube internal name is compiled from 20 firsts characters of 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)' ' Also note that channel name are not case sensitive (no uppercase nor accent)'
' Please check your channel name and retry.') ' Please check your channel name and retry.')
exit(1) exit(1)
else: else:
logging.error(('Peertube: Creating channel failed with an unexpected response: '
logger.critical(('Peertube: Creating channel failed with an unexpected response: '
'%s') % response) '%s') % response)
exit(1) exit(1)
@ -114,7 +116,7 @@ def get_playlist_by_name(user_playlists, options):
def create_playlist(oauth, url, options, channel): def create_playlist(oauth, url, options, channel):
template = ('Peertube: Playlist %s does not exist, creating it.') 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 # We use files for form-data Content
# see https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file # see https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file
# None is used to mute "filename" field # None is used to mute "filename" field
@ -128,22 +130,22 @@ def create_playlist(oauth, url, options, channel):
files=files) files=files)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Error: " + str(e))
logger.error("Peertube: " + str(e))
if response is not None: if response is not None:
if response.status_code == 200: if response.status_code == 200:
jresponse = response.json() jresponse = response.json()
jresponse = jresponse['videoPlaylist'] jresponse = jresponse['videoPlaylist']
return jresponse['id'] return jresponse['id']
else: 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) '%s') % response)
exit(1) exit(1)
def set_playlist(oauth, url, video_id, playlist_id): 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) + '"}' data = '{"videoId":"' + str(video_id) + '"}'
headers = { headers = {
@ -155,14 +157,14 @@ def set_playlist(oauth, url, video_id, playlist_id):
headers=headers) headers=headers)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Error: " + str(e))
logger.error("Peertube: " + str(e))
if response is not None: if response is not None:
if response.status_code == 200: 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: 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) '%s') % response)
exit(1) exit(1)
@ -205,8 +207,9 @@ def upload_video(oauth, secret, options):
continue continue
# Tag more than 30 chars crashes Peertube, so exit and check tags # Tag more than 30 chars crashes Peertube, so exit and check tags
if len(strtag) >= 30: 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)) fields.append(("tags[]", strtag))
if options.get('--category'): if options.get('--category'):
@ -256,7 +259,7 @@ def upload_video(oauth, secret, options):
if not channel_id and options.get('--channelCreate'): if not channel_id and options.get('--channelCreate'):
channel_id = create_channel(oauth, url, options) channel_id = create_channel(oauth, url, options)
elif not channel_id: 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) channel_id = get_default_channel(user_info)
else: else:
channel_id = get_default_channel(user_info) 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'): if not playlist_id and options.get('--playlistCreate'):
playlist_id = create_playlist(oauth, url, options, channel_id) playlist_id = create_playlist(oauth, url, options, channel_id)
elif not playlist_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") " if you want to create it")
exit(1) exit(1)
logger_stdout = None
if options.get('--url-only') or options.get('--batch'):
logger_stdout = logging.getLogger('stdoutlogs')
multipart_data = MultipartEncoder(fields) multipart_data = MultipartEncoder(fields)
headers = { headers = {
@ -286,14 +293,19 @@ def upload_video(oauth, secret, options):
jresponse = jresponse['video'] jresponse = jresponse['video']
uuid = jresponse['uuid'] uuid = jresponse['uuid']
video_id = str(jresponse['id']) 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.' 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 # Upload is successful we may set playlist
if options.get('--playlist'): if options.get('--playlist'):
set_playlist(oauth, url, video_id, playlist_id) set_playlist(oauth, url, video_id, playlist_id)
else: else:
logging.error(('Peertube: The upload failed with an unexpected response: '
logger.critical(('Peertube: The upload failed with an unexpected response: '
'%s') % response) '%s') % response)
exit(1) exit(1)
@ -303,16 +315,16 @@ def run(options):
try: try:
secret.read(PEERTUBE_SECRETS_FILE) secret.read(PEERTUBE_SECRETS_FILE)
except Exception as e: 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) exit(1)
insecure_transport = secret.get('peertube', 'OAUTHLIB_INSECURE_TRANSPORT') insecure_transport = secret.get('peertube', 'OAUTHLIB_INSECURE_TRANSPORT')
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = insecure_transport os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = insecure_transport
oauth = get_authenticated_service(secret) oauth = get_authenticated_service(secret)
try: try:
logging.info('Peertube: Uploading video...')
logger.info('Peertube: Uploading video...')
upload_video(oauth, secret, options) upload_video(oauth, secret, options)
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Peertube: Error: " + str(e.message))
logger.error("Peertube: " + str(e.message))
else: else:
logging.error("Peertube: Error: " + str(e))
logger.error("Peertube: " + str(e))

+ 90
- 23
prismedia/upload.py View File

@ -11,9 +11,8 @@ Usage:
prismedia --version prismedia --version
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) --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) -d, --description=STRING Description of the video. (default: default description)
-t, --tags=STRING Tags for the video. comma separated. -t, --tags=STRING Tags for the video. comma separated.
WARN: tags with punctuation (!, ', ", ?, ...) WARN: tags with punctuation (!, ', ", ?, ...)
@ -49,6 +48,14 @@ Options:
-h --help Show this help. -h --help Show this help.
--version Show version. --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:
Strict options allow you to force some option to be present when uploading a video. It's useful to be sure you do not 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, 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 os
import datetime import datetime
import logging 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 from docopt import docopt
@ -102,9 +115,9 @@ from . import utils
try: try:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from schema import Schema, And, Or, Optional, SchemaError, Hook
from schema import Schema, And, Or, Optional, SchemaError, Hook, Use
except ImportError: 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' ' is installed: \n'
'see https://github.com/halst/schema\n') 'see https://github.com/halst/schema\n')
exit(1) exit(1)
@ -112,7 +125,7 @@ try:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import magic import magic
except ImportError: 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' ' is installed, NOT the Python bindings to libmagic API \n'
'see https://github.com/ahupp/python-magic\n') 'see https://github.com/ahupp/python-magic\n')
exit(1) exit(1)
@ -198,19 +211,68 @@ def validateThumbnail(thumbnail):
return False 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): def _optionnalOrStrict(key, scope, error):
option = key.replace('-', '') option = key.replace('-', '')
option = option[0].upper() + option[1:] option = option[0].upper() + option[1:]
if scope["--with" + option] is True and scope[key] is None: 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) exit(1)
return True 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(): def main():
options = docopt(__doc__, version=VERSION) 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('--withNFO', default=False): bool,
Optional('--withThumbnail', default=False): bool, Optional('--withThumbnail', default=False): bool,
Optional('--withName', default=False): bool, Optional('--withName', default=False): bool,
@ -222,7 +284,8 @@ def main():
Optional('--withCategory', default=False): bool, Optional('--withCategory', default=False): bool,
Optional('--withLanguage', default=False): bool, Optional('--withLanguage', default=False): bool,
Optional('--withChannel', 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({ schema = Schema({
@ -286,7 +349,6 @@ def main():
validatePublish, validatePublish,
error="DATE should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") 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('--cca'): bool,
Optional('--disable-comments'): bool, Optional('--disable-comments'): bool,
Optional('--nsfw'): bool, Optional('--nsfw'): bool,
@ -299,22 +361,28 @@ def main():
Optional('--playlistCreate'): bool, Optional('--playlistCreate'): bool,
'--help': bool, '--help': bool,
'--version': 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: try:
options = strictoptionSchema.validate(options)
options = earlyoptionSchema.validate(options)
configureLogs(options)
except SchemaError as e: 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) options = utils.parseNFO(options)
# Once NFO are loaded, we need to revalidate strict options in case some were in NFO # Once NFO are loaded, we need to revalidate strict options in case some were in NFO
try: try:
options = strictoptionSchema.validate(options)
options = earlyoptionSchema.validate(options)
except SchemaError as e: except SchemaError as e:
exit(e)
logger.critical(e)
exit(1)
if not options.get('--thumbnail'): if not options.get('--thumbnail'):
options = utils.searchThumbnail(options) options = utils.searchThumbnail(options)
@ -322,11 +390,11 @@ def main():
try: try:
options = schema.validate(options) options = schema.validate(options)
except SchemaError as e: 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'): if options.get('--platform') is None or "peertube" in options.get('--platform'):
pt_upload.run(options) pt_upload.run(options)
@ -335,6 +403,5 @@ def main():
if __name__ == '__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() main()

+ 14
- 5
prismedia/utils.py View File

@ -9,6 +9,8 @@ from subprocess import check_call, CalledProcessError, STDOUT
import unidecode import unidecode
import logging import logging
logger = logging.getLogger('Prismedia')
### CATEGORIES ### ### CATEGORIES ###
YOUTUBE_CATEGORY = { YOUTUBE_CATEGORY = {
"music": 10, "music": 10,
@ -123,18 +125,25 @@ def searchThumbnail(options):
options['--thumbnail'] = video_directory + video_file + ".jpg" options['--thumbnail'] = video_directory + video_file + ".jpg"
elif isfile(video_directory + video_file + ".jpeg"): elif isfile(video_directory + video_file + ".jpeg"):
options['--thumbnail'] = 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 options
# return the nfo as a RawConfigParser object # return the nfo as a RawConfigParser object
def loadNFO(filename): def loadNFO(filename):
try: try:
logging.info("Loading " + filename + " as NFO")
logger.info("Loading " + filename + " as NFO")
nfo = RawConfigParser() nfo = RawConfigParser()
nfo.read(filename, encoding='utf-8') nfo.read(filename, encoding='utf-8')
return nfo return nfo
except Exception as e: except Exception as e:
logging.error("Problem loading NFO file " + filename + ": " + str(e))
logger.critical("Problem loading NFO file " + filename + ": " + str(e))
exit(1) exit(1)
return False return False
@ -168,7 +177,7 @@ def parseNFO(options):
if isfile(options.get('--nfo')): if isfile(options.get('--nfo')):
nfo_cli = loadNFO(options.get('--nfo')) nfo_cli = loadNFO(options.get('--nfo'))
else: 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) exit(1)
# If there is no NFO and strict option is enabled, then stop there # 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_videoname, RawConfigParser) and \
not isinstance(nfo_directory, RawConfigParser) and \ not isinstance(nfo_directory, RawConfigParser) and \
not isinstance(nfo_txt, RawConfigParser): 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) exit(1)
# We need to load NFO in this exact order to keep the priorities # We need to load NFO in this exact order to keep the priorities
@ -198,7 +207,7 @@ def parseNFO(options):
except NoOptionError: except NoOptionError:
continue continue
except NoSectionError: 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) exit(1)
return options return options

+ 29
- 24
prismedia/yt_upload.py View File

@ -23,9 +23,7 @@ from google_auth_oauthlib.flow import InstalledAppFlow
from . import utils 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 # Explicitly tell the underlying HTTP transport library not to retry, since
# we are handling retry logic ourselves. # we are handling retry logic ourselves.
@ -87,7 +85,7 @@ def check_authenticated_scopes():
credential_params = json.load(f) credential_params = json.load(f)
# Check if all scopes are present # Check if all scopes are present
if credential_params["_scopes"] != SCOPES: 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) os.remove(CREDENTIALS_PATH)
@ -144,8 +142,8 @@ def initialize_upload(youtube, options):
if not playlist_id and options.get('--playlistCreate'): if not playlist_id and options.get('--playlistCreate'):
playlist_id = create_playlist(youtube, options.get('--playlist')) playlist_id = create_playlist(youtube, options.get('--playlist'))
elif not playlist_id: 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 = "" playlist_id = ""
else: else:
playlist_id = "" playlist_id = ""
@ -156,7 +154,7 @@ def initialize_upload(youtube, options):
body=body, body=body,
media_body=MediaFileUpload(path, chunksize=-1, resumable=True) 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 we get a video_id, upload is successful and we are able to set thumbnail
if video_id and options.get('--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): 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, resources = build_resource({'snippet.title': playlist_name,
'snippet.description': '', 'snippet.description': '',
'status.privacyStatus': 'public'}) 'status.privacyStatus': 'public'})
@ -244,7 +242,7 @@ def set_thumbnail(youtube, media_file, **kwargs):
def set_playlist(youtube, playlist_id, video_id): 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, resource = build_resource({'snippet.playlistId': playlist_id,
'snippet.resourceId.kind': 'youtube#video', 'snippet.resourceId.kind': 'youtube#video',
'snippet.resourceId.videoId': video_id, 'snippet.resourceId.videoId': video_id,
@ -257,37 +255,45 @@ def set_playlist(youtube, playlist_id, video_id):
).execute() ).execute()
except Exception as e: except Exception as e:
if hasattr(e, 'message'): if hasattr(e, 'message'):
logging.error("Youtube: Error: " + str(e.message))
logger.critical("Youtube: " + str(e.message))
exit(1) exit(1)
else: else:
logging.error("Youtube: Error: " + str(e))
logger.critical("Youtube: " + str(e))
exit(1) 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 # This method implements an exponential backoff strategy to resume a
# failed upload. # failed upload.
def resumable_upload(request, resource, method):
def resumable_upload(request, resource, method, options):
response = None response = None
error = None error = None
retry = 0 retry = 0
logger_stdout = None
if options.get('--url-only') or options.get('--batch'):
logger_stdout = logging.getLogger('stdoutlogs')
while response is None: while response is None:
try: try:
template = 'Youtube: Uploading %s...' template = 'Youtube: Uploading %s...'
logging.info(template % resource)
logger.info(template % resource)
status, response = request.next_chunk() status, response = request.next_chunk()
if response is not None: if response is not None:
if method == 'insert' and 'id' in response: 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)' 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'] return response['id']
elif method != 'insert' or "id" not in response: elif method != 'insert' or "id" not in response:
logging.info('Youtube: Thumbnail was successfully set.')
logger.info('Youtube: Thumbnail was successfully set.')
else: else:
template = ('Youtube : The upload failed with an ' template = ('Youtube : The upload failed with an '
'unexpected response: %s') 'unexpected response: %s')
logging.error(template % response)
logger.critical(template % response)
exit(1) exit(1)
except HttpError as e: except HttpError as e:
if e.resp.status in RETRIABLE_STATUS_CODES: 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 error = 'Youtube : A retriable error occurred: %s' % e
if error is not None: if error is not None:
logging.warning(error)
logger.warning(error)
retry += 1 retry += 1
if retry > MAX_RETRIES: 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 max_sleep = 2 ** retry
sleep_seconds = random.random() * max_sleep 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) % sleep_seconds)
time.sleep(sleep_seconds) time.sleep(sleep_seconds)
@ -317,5 +322,5 @@ def run(options):
try: try:
initialize_upload(youtube, options) initialize_upload(youtube, options)
except HttpError as e: 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)) e.content))

Loading…
Cancel
Save