mirror of
https://git.lecygnenoir.info/LecygneNoir/prismedia.git
synced 2025-10-03 09:29:16 +02:00
Merge branch 'feature/thumbnail' into develop
This commit is contained in:
commit
dea06c6e8f
6 changed files with 95 additions and 23 deletions
15
README.md
15
README.md
|
@ -69,6 +69,12 @@ Specify description and tags:
|
||||||
./prismedia_upload.py --file="yourvideo.mp4" -d "My supa description" -t "tag1,tag2,foo"
|
./prismedia_upload.py --file="yourvideo.mp4" -d "My supa description" -t "tag1,tag2,foo"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Provide a thumbnail:
|
||||||
|
|
||||||
|
```
|
||||||
|
./prismedia_upload.py --file="yourvideo.mp4" -d "Video with thumbnail" --thumbnail="/path/to/your/thumbnail.jpg"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Use a NFO file to specify your video options:
|
Use a NFO file to specify your video options:
|
||||||
|
|
||||||
|
@ -111,7 +117,10 @@ Options:
|
||||||
--publishAt=DATE Publish the video at the given DATE using local server timezone.
|
--publishAt=DATE Publish the video at the given DATE using local server timezone.
|
||||||
DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00
|
DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00
|
||||||
DATE should be in the future
|
DATE should be in the future
|
||||||
For Peertube, requires the "atd", "curl" and "jq" utilities installed on the system
|
For Peertube, requires the "atd" and "curl utilities installed on the system
|
||||||
|
--thumbnail=STRING Path to a file to use as a thumbnail for the video.
|
||||||
|
Supported types are jpg and jpeg.
|
||||||
|
By default, prismedia search for an image based on video name followed by .jpg or .jpeg
|
||||||
-h --help Show this help.
|
-h --help Show this help.
|
||||||
--version Show version.
|
--version Show version.
|
||||||
|
|
||||||
|
@ -145,8 +154,8 @@ Languages:
|
||||||
- [x] enabling/disabling comment (Peertube only as Youtube API does not support it)
|
- [x] enabling/disabling comment (Peertube only as Youtube API does not support it)
|
||||||
- [x] nsfw (Peertube only as Youtube API does not support it)
|
- [x] nsfw (Peertube only as Youtube API does not support it)
|
||||||
- [x] set default language
|
- [x] set default language
|
||||||
- [ ] thumbnail/preview (YT workflow: upload video, upload thumbnail, add thumbnail to video)
|
- [x] thumbnail/preview
|
||||||
- [ ] multiple lines description (see [issue 4](https://git.lecygnenoir.info/LecygneNoir/prismedia/issues/4))
|
- [x] multiple lines description (see [issue 4](https://git.lecygnenoir.info/LecygneNoir/prismedia/issues/4))
|
||||||
- [ ] add videos to playlist (YT & PT workflow: upload video, find playlist id, add video to playlist)
|
- [ ] add videos to playlist (YT & PT workflow: upload video, find playlist id, add video to playlist)
|
||||||
- [x] Use a config file (NFO) file to retrieve videos arguments
|
- [x] Use a config file (NFO) file to retrieve videos arguments
|
||||||
- [x] Allow to choose peertube or youtube upload (to resume failed upload for example)
|
- [x] Allow to choose peertube or youtube upload (to resume failed upload for example)
|
||||||
|
|
|
@ -47,12 +47,11 @@ def upload_video(oauth, secret, options):
|
||||||
user_info = json.loads(oauth.get(url + "/api/v1/users/me").content)
|
user_info = json.loads(oauth.get(url + "/api/v1/users/me").content)
|
||||||
return str(user_info["id"])
|
return str(user_info["id"])
|
||||||
|
|
||||||
def get_videofile(path):
|
def get_file(path):
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
return (basename(path), open(abspath(path), 'rb'),
|
return (basename(path), open(abspath(path), 'rb'),
|
||||||
mimetypes.types_map[splitext(path)[1]])
|
mimetypes.types_map[splitext(path)[1]])
|
||||||
|
|
||||||
path = options.get('--file')
|
|
||||||
url = secret.get('peertube', 'peertube_url')
|
url = secret.get('peertube', 'peertube_url')
|
||||||
|
|
||||||
# We need to transform fields into tuple to deal with tags as
|
# We need to transform fields into tuple to deal with tags as
|
||||||
|
@ -60,12 +59,12 @@ def upload_video(oauth, secret, options):
|
||||||
# https://github.com/requests/toolbelt/issues/190 and
|
# https://github.com/requests/toolbelt/issues/190 and
|
||||||
# https://github.com/requests/toolbelt/issues/205
|
# https://github.com/requests/toolbelt/issues/205
|
||||||
fields = [
|
fields = [
|
||||||
("name", options.get('--name') or splitext(basename(path))[0]),
|
("name", options.get('--name') or splitext(basename(options.get('--file')))[0]),
|
||||||
("licence", "1"),
|
("licence", "1"),
|
||||||
("description", options.get('--description') or "default description"),
|
("description", options.get('--description') or "default description"),
|
||||||
("nsfw", str(int(options.get('--nsfw')) or "0")),
|
("nsfw", str(int(options.get('--nsfw')) or "0")),
|
||||||
("channelId", get_userinfo()),
|
("channelId", get_userinfo()),
|
||||||
("videofile", get_videofile(path))
|
("videofile", get_file(options.get('--file')))
|
||||||
]
|
]
|
||||||
|
|
||||||
if options.get('--tags'):
|
if options.get('--tags'):
|
||||||
|
@ -76,7 +75,7 @@ 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("Sorry, Peertube does not support tag with more than 30 characters, please reduce your tag size")
|
logging.warning("Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce your tag size")
|
||||||
exit(1)
|
exit(1)
|
||||||
# If Mastodon compatibility is enabled, clean tags from special characters
|
# If Mastodon compatibility is enabled, clean tags from special characters
|
||||||
if options.get('--mt'):
|
if options.get('--mt'):
|
||||||
|
@ -105,6 +104,9 @@ def upload_video(oauth, secret, options):
|
||||||
else:
|
else:
|
||||||
fields.append(("commentsEnabled", "1"))
|
fields.append(("commentsEnabled", "1"))
|
||||||
|
|
||||||
|
if options.get('--thumbnail'):
|
||||||
|
fields.append(("thumbnailfile", get_file(options.get('--thumbnail'))))
|
||||||
|
|
||||||
multipart_data = MultipartEncoder(fields)
|
multipart_data = MultipartEncoder(fields)
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
|
@ -120,13 +122,13 @@ def upload_video(oauth, secret, options):
|
||||||
jresponse = jresponse['video']
|
jresponse = jresponse['video']
|
||||||
uuid = jresponse['uuid']
|
uuid = jresponse['uuid']
|
||||||
idvideo = str(jresponse['id'])
|
idvideo = str(jresponse['id'])
|
||||||
template = ('Peertube : Video was successfully uploaded.\n'
|
logging.info('Peertube : Video was successfully uploaded.')
|
||||||
'Watch it at %s/videos/watch/%s.')
|
template = 'Peertube: Watch it at %s/videos/watch/%s.'
|
||||||
logging.info(template % (url, uuid))
|
logging.info(template % (url, uuid))
|
||||||
if options.get('--publishAt'):
|
if options.get('--publishAt'):
|
||||||
utils.publishAt(str(options.get('--publishAt')), oauth, url, idvideo, secret)
|
utils.publishAt(str(options.get('--publishAt')), oauth, url, idvideo, secret)
|
||||||
else:
|
else:
|
||||||
logging.error(('Peertube : The upload failed with an unexpected response: '
|
logging.error(('Peertube: The upload failed with an unexpected response: '
|
||||||
'%s') % response)
|
'%s') % response)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
@ -136,16 +138,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("Error loading " + str(PEERTUBE_SECRETS_FILE) + ": " + str(e))
|
logging.error("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 file...')
|
logging.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("Error: " + str(e.message))
|
logging.error("Peertube: Error: " + str(e.message))
|
||||||
else:
|
else:
|
||||||
logging.error("Error: " + str(e))
|
logging.error("Peertube: Error: " + str(e))
|
||||||
|
|
27
lib/utils.py
27
lib/utils.py
|
@ -98,6 +98,32 @@ def getLanguage(language, platform):
|
||||||
return PEERTUBE_LANGUAGE[language.lower()]
|
return PEERTUBE_LANGUAGE[language.lower()]
|
||||||
|
|
||||||
|
|
||||||
|
def remove_empty_kwargs(**kwargs):
|
||||||
|
good_kwargs = {}
|
||||||
|
if kwargs is not None:
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
if value:
|
||||||
|
good_kwargs[key] = value
|
||||||
|
return good_kwargs
|
||||||
|
|
||||||
|
def searchThumbnail(options):
|
||||||
|
video_directory = dirname(options.get('--file')) + "/"
|
||||||
|
# First, check for thumbnail based on videoname
|
||||||
|
if options.get('--name'):
|
||||||
|
if isfile(video_directory + options.get('--name') + ".jpg"):
|
||||||
|
options['--thumbnail'] = video_directory + options.get('--name') + ".jpg"
|
||||||
|
elif isfile(video_directory + options.get('--name') + ".jpeg"):
|
||||||
|
options['--thumbnail'] = video_directory + options.get('--name') + ".jpeg"
|
||||||
|
# Then, if we still not have thumbnail, check for thumbnail based on videofile name
|
||||||
|
if not options.get('--thumbnail'):
|
||||||
|
video_file = splitext(basename(options.get('--file')))[0]
|
||||||
|
if isfile(video_directory + video_file + ".jpg"):
|
||||||
|
options['--thumbnail'] = video_directory + video_file + ".jpg"
|
||||||
|
elif isfile(video_directory + video_file + ".jpeg"):
|
||||||
|
options['--thumbnail'] = video_directory + video_file + ".jpeg"
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
# return the nfo as a RawConfigParser object
|
# return the nfo as a RawConfigParser object
|
||||||
def loadNFO(options):
|
def loadNFO(options):
|
||||||
video_directory = dirname(options.get('--file')) + "/"
|
video_directory = dirname(options.get('--file')) + "/"
|
||||||
|
@ -117,7 +143,6 @@ def loadNFO(options):
|
||||||
else:
|
else:
|
||||||
if options.get('--name'):
|
if options.get('--name'):
|
||||||
nfo_file = video_directory + options.get('--name') + ".txt"
|
nfo_file = video_directory + options.get('--name') + ".txt"
|
||||||
print nfo_file
|
|
||||||
if isfile(nfo_file):
|
if isfile(nfo_file):
|
||||||
try:
|
try:
|
||||||
logging.info("Using " + nfo_file + " as NFO, loading...")
|
logging.info("Using " + nfo_file + " as NFO, loading...")
|
||||||
|
|
|
@ -20,6 +20,7 @@ from googleapiclient.errors import HttpError
|
||||||
from googleapiclient.http import MediaFileUpload
|
from googleapiclient.http import MediaFileUpload
|
||||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
|
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
|
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
|
||||||
|
@ -126,25 +127,44 @@ 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)
|
||||||
)
|
)
|
||||||
resumable_upload(insert_request)
|
video_id = resumable_upload(insert_request, 'video', 'insert')
|
||||||
|
|
||||||
|
# If we get a video_id, upload is successful and we are able to set thumbnail
|
||||||
|
if video_id and options.get('--thumbnail'):
|
||||||
|
set_thumbnail(youtube, options.get('--thumbnail'), videoId=video_id)
|
||||||
|
|
||||||
|
|
||||||
|
def set_thumbnail(youtube, media_file, **kwargs):
|
||||||
|
kwargs = utils.remove_empty_kwargs(**kwargs) # See full sample for function
|
||||||
|
request = youtube.thumbnails().set(
|
||||||
|
media_body=MediaFileUpload(media_file, chunksize=-1,
|
||||||
|
resumable=True),
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
# See full sample for function
|
||||||
|
return resumable_upload(request, 'thumbnail', 'set')
|
||||||
|
|
||||||
|
|
||||||
# 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):
|
def resumable_upload(request, resource, method):
|
||||||
response = None
|
response = None
|
||||||
error = None
|
error = None
|
||||||
retry = 0
|
retry = 0
|
||||||
while response is None:
|
while response is None:
|
||||||
try:
|
try:
|
||||||
logging.info('Youtube : Uploading file...')
|
template = 'Youtube: Uploading %s...'
|
||||||
|
logging.info(template % resource)
|
||||||
status, response = request.next_chunk()
|
status, response = request.next_chunk()
|
||||||
if response is not None:
|
if response is not None:
|
||||||
if 'id' in response:
|
if method == 'insert' and 'id' in response:
|
||||||
template = ('Youtube : Video was successfully '
|
logging.info('Youtube : Video was successfully uploaded.')
|
||||||
'uploaded.\n'
|
template = 'Youtube: Watch it at https://youtu.be/%s (post-encoding could take some time)'
|
||||||
'Watch it at https://youtu.be/%s (post-encoding could take some time)')
|
|
||||||
logging.info(template % response['id'])
|
logging.info(template % response['id'])
|
||||||
|
return response['id']
|
||||||
|
elif method != 'insert' or "id" not in response:
|
||||||
|
logging.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')
|
||||||
|
|
|
@ -17,6 +17,7 @@ category = Films
|
||||||
cca = True
|
cca = True
|
||||||
privacy = private
|
privacy = private
|
||||||
disable-comments = True
|
disable-comments = True
|
||||||
|
thumbnail = /path/to/your/thumbnail.jpg # Set the absolute path to your thumbnail
|
||||||
nsfw = True
|
nsfw = True
|
||||||
platform = youtube, peertube
|
platform = youtube, peertube
|
||||||
language = French
|
language = French
|
||||||
|
|
|
@ -34,6 +34,9 @@ Options:
|
||||||
DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00
|
DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00
|
||||||
DATE should be in the future
|
DATE should be in the future
|
||||||
For Peertube, requires the "atd" and "curl utilities installed on the system
|
For Peertube, requires the "atd" and "curl utilities installed on the system
|
||||||
|
--thumbnail=STRING Path to a file to use as a thumbnail for the video.
|
||||||
|
Supported types are jpg and jpeg.
|
||||||
|
By default, prismedia search for an image based on video name followed by .jpg or .jpeg
|
||||||
-h --help Show this help.
|
-h --help Show this help.
|
||||||
--version Show version.
|
--version Show version.
|
||||||
|
|
||||||
|
@ -151,6 +154,12 @@ def validatePublish(publish):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def validateThumbnail(thumbnail):
|
||||||
|
supported_types = ['image/jpg', 'image/jpeg']
|
||||||
|
if magic.from_file(thumbnail, mime=True) in supported_types:
|
||||||
|
return thumbnail
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
@ -199,12 +208,18 @@ if __name__ == '__main__':
|
||||||
Optional('--cca'): bool,
|
Optional('--cca'): bool,
|
||||||
Optional('--disable-comments'): bool,
|
Optional('--disable-comments'): bool,
|
||||||
Optional('--nsfw'): bool,
|
Optional('--nsfw'): bool,
|
||||||
|
Optional('--thumbnail'): Or(None, And(
|
||||||
|
str, validateThumbnail, error='thumbnail is not supported, please use jpg/jpeg'),
|
||||||
|
),
|
||||||
'--help': bool,
|
'--help': bool,
|
||||||
'--version': bool
|
'--version': bool
|
||||||
})
|
})
|
||||||
|
|
||||||
options = utils.parseNFO(options)
|
options = utils.parseNFO(options)
|
||||||
|
|
||||||
|
if not options.get('--thumbnail'):
|
||||||
|
options = utils.searchThumbnail(options)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
options = schema.validate(options)
|
options = schema.validate(options)
|
||||||
except SchemaError as e:
|
except SchemaError as e:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue