mirror of
https://git.lecygnenoir.info/LecygneNoir/prismedia.git
synced 2025-10-06 02:29:58 +02:00
Merge branch 'feature/logs_improvement' into develop
This commit is contained in:
commit
f8ca4b093a
5 changed files with 202 additions and 81 deletions
32
README.md
32
README.md
|
@ -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
|
||||||
|
|
|
@ -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)
|
logger.error("Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag)
|
||||||
exit(1)
|
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))
|
||||||
|
|
|
@ -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 early options first as withNFO and logs options should be prioritized
|
||||||
# We need to validate strict options first as withNFO should be validated before NFO parsing
|
|
||||||
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'):
|
logger.debug("Python " + sys.version)
|
||||||
print("Python " + sys.version)
|
logger.debug(options)
|
||||||
print(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
|
logger.warning("DEPRECATION: use 'python -m prismedia', not 'python -m prismedia.upload'")
|
||||||
warnings.warn("use 'python -m prismedia', not 'python -m prismedia.upload'", DeprecationWarning)
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,7 @@ from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
|
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
logger = logging.getLogger('Prismedia')
|
||||||
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
# 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.")
|
logger.warning("Youtube: Playlist `" + options.get('--playlist') + "` is unknown.")
|
||||||
logging.warning("If you want to create it, set the --playlistCreate option.")
|
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.')
|
template = 'Youtube: Playlist %s does not exist, creating it.'
|
||||||
logging.info(template % (str(playlist_name)))
|
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.')
|
logger.error('Youtube : No longer attempting to retry.')
|
||||||
exit(1)
|
|
||||||
|
|
||||||
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…
Add table
Add a link
Reference in a new issue