From d2c615473852a36af2d55a5bfdf23087cb9fa151 Mon Sep 17 00:00:00 2001 From: LecygneNoir Date: Tue, 1 May 2018 13:41:14 +0200 Subject: [PATCH] Add the publishAt option to allow planned publication for your videos. See README for prerequisites --- README.md | 10 ++++++++++ lib/pt_upload.py | 11 ++++++++--- lib/utils.py | 42 ++++++++++++++++++++++++++++++++++++++++++ lib/yt_upload.py | 14 +++++++++++++- prismedia_upload.py | 22 ++++++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 75b72f4..0f9d909 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,11 @@ Search in your package manager, otherwise use ``pip install --upgrade`` - schema - python-magic - requests-toolbelt + - tzlocal + +For Peertube and if you want to use the publishAt option, you also need some utilities on you local system + - [atd](https://linux.die.net/man/8/atd) daemon + - [curl](https://linux.die.net/man/1/curl) ## Configuration @@ -102,6 +107,10 @@ Options: --platform=STRING List of platform(s) to upload to, comma separated. Supported platforms are youtube and peertube (default is both) --language=STRING Specify the default language for video. See below for supported language. (default is English) + --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 in the future + For Peertube, requires the "atd" and "curl utilities installed on the system -h --help Show this help. --version Show version. @@ -138,6 +147,7 @@ Languages: - ~~thumbnail/preview~~ Canceled, waiting for Youtube's API support - [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] Add publishAt option to plan your videos (need the [atd](https://linux.die.net/man/8/atd) daemon and [curl](https://linux.die.net/man/1/curl)) - [ ] Record and forget: put the video in a directory, and the script uploads it for you - [ ] Usable on Desktop (Linux and/or Windows and/or MacOS) - [ ] Graphical User Interface diff --git a/lib/pt_upload.py b/lib/pt_upload.py index 5b27fee..968b9ae 100644 --- a/lib/pt_upload.py +++ b/lib/pt_upload.py @@ -36,6 +36,7 @@ def get_authenticated_service(secret): client_id=str(secret.get('peertube', 'client_id')), client_secret=str(secret.get('peertube', 'client_secret')) ) + return oauth @@ -113,9 +114,10 @@ def upload_video(oauth, secret, options): headers=headers) if response is not None: if response.status_code == 200: - uuid = response.json() - uuid = uuid['video'] - uuid = uuid['uuid'] + jresponse = response.json() + jresponse = jresponse['video'] + uuid = jresponse['uuid'] + idvideo = str(jresponse['id']) template = ('Peertube : Video was successfully uploaded.\n' 'Watch it at %s/videos/watch/%s.') print(template % (url, uuid)) @@ -123,6 +125,9 @@ def upload_video(oauth, secret, options): exit(('Peertube : The upload failed with an unexpected response: ' '%s') % response) + if options.get('--publishAt'): + utils.publishAt(str(options.get('--publishAt')), oauth, url, idvideo) + def run(options): secret = RawConfigParser() diff --git a/lib/utils.py b/lib/utils.py index 9682dc6..eac5568 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -3,6 +3,8 @@ from ConfigParser import RawConfigParser, NoOptionError, NoSectionError from os.path import dirname, splitext, basename, isfile +from os import devnull +from subprocess import check_call, CalledProcessError, STDOUT import unicodedata ### CATEGORIES ### @@ -161,6 +163,46 @@ def upcaseFirstLetter(s): return s[0].upper() + s[1:] +def publishAt(publishAt, oauth, url, idvideo): + try: + FNULL = open(devnull, 'w') + check_call(["at", "-V"], stdout=FNULL, stderr=STDOUT) + except CalledProcessError: + exit("You need to install the atd daemon to use the publishAt option.") + try: + FNULL = open(devnull, 'w') + check_call(["curl", "-V"], stdout=FNULL, stderr=STDOUT) + except CalledProcessError: + exit("You need to install the curl command line to use the publishAt option.") + time = publishAt.split("T") + # Remove leading seconds that atd does not manage + if time[1].count(":") == 2: + time[1] = time[1][:-3] + + atTime = time[1] + " " + time[0] + token=str(oauth.__dict__['_client'].__dict__['access_token']) + atFile = "/tmp/peertube_" + idvideo + "_" + publishAt + ".at" + try: + file = open(atFile,"w") + file.write("curl '" + url + "/api/v1/videos/" + idvideo + "' -X PUT -H 'Authorization: Bearer " + token + "' -H 'Content-Type: multipart/form-data' -F 'privacy=1'") + file.write(" ") # atd needs an empty line at the end of the file to load... + file.close() + except Exception as e: + if hasattr(e, 'message'): + print("Error: " + str(e.message)) + else: + print("Error: " + str(e)) + + try: + FNULL = open(devnull, 'w') + check_call(["at", "-M", "-f", atFile, atTime], stdout=FNULL, stderr=STDOUT) + except Exception as e: + if hasattr(e, 'message'): + print("Error: " + str(e.message)) + else: + print("Error: " + str(e)) + + def mastodonTag(tag): tags = tag.split(' ') mtag = '' diff --git a/lib/yt_upload.py b/lib/yt_upload.py index 7368871..2bc3488 100644 --- a/lib/yt_upload.py +++ b/lib/yt_upload.py @@ -10,6 +10,9 @@ import copy import json from os.path import splitext, basename, exists import google.oauth2.credentials +import datetime +import pytz +from tzlocal import get_localzone from googleapiclient.discovery import build from googleapiclient.errors import HttpError @@ -100,10 +103,19 @@ def initialize_upload(youtube, options): }, "status": { "privacyStatus": str(options.get('--privacy') or "private"), - "license": str(license or "youtube") + "license": str(license or "youtube"), } } + if options.get('--publishAt'): + # Youtube needs microsecond and the local timezone from ISO 8601 + publishAt = options.get('--publishAt') + ".000001" + publishAt = datetime.datetime.strptime(publishAt, '%Y-%m-%dT%H:%M:%S.%f') + tz = get_localzone() + tz = pytz.timezone(str(tz)) + publishAt = tz.localize(publishAt).isoformat() + body['status']['publishAt'] = str(publishAt) + # Call the API's videos.insert method to create and upload the video. insert_request = youtube.videos().insert( part=','.join(body.keys()), diff --git a/prismedia_upload.py b/prismedia_upload.py index deb039a..84519b9 100755 --- a/prismedia_upload.py +++ b/prismedia_upload.py @@ -30,6 +30,10 @@ Options: --platform=STRING List of platform(s) to upload to, comma separated. Supported platforms are youtube and peertube (default is both) --language=STRING Specify the default language for video. See below for supported language. (default is English) + --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 in the future + For Peertube, requires the "atd" and "curl utilities installed on the system -h --help Show this help. --version Show version. @@ -51,6 +55,7 @@ Languages: """ from os.path import dirname, realpath import sys +import datetime from docopt import docopt @@ -131,6 +136,18 @@ def validateLanguage(language): return False +def validatePublish(publish): + # Check date format and if date is future + try: + now = datetime.datetime.now() + publishAt = datetime.datetime.strptime(publish, '%Y-%m-%dT%H:%M:%S') + if now >= publishAt: + return False + except ValueError: + return False + return True + + if __name__ == '__main__': options = docopt(__doc__, version=VERSION) @@ -170,6 +187,11 @@ if __name__ == '__main__': ), Optional('--nfo'): Or(None, str), Optional('--platform'): Or(None, And(str, validatePlatform, error="Sorry, upload platform not supported")), + Optional('--publishAt'): Or(None, And( + str, + validatePublish, + error="DATE should be the form YYYY-MM-DDThh:mm:ss and has to be in the future") + ), Optional('--cca'): bool, Optional('--disable-comments'): bool, Optional('--nsfw'): bool,