From a725e848ab85a74df7c290efc758f448035c0a66 Mon Sep 17 00:00:00 2001 From: Zykino Date: Mon, 8 Feb 2021 16:30:44 +0100 Subject: [PATCH 1/4] Add an option to use some credits easiely This tells youtube the apikey is still used and they should not remove all of the quota. --- prismedia/upload.py | 10 ++++++++++ prismedia/yt_upload.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/prismedia/upload.py b/prismedia/upload.py index 7220b7d..2ed5283 100755 --- a/prismedia/upload.py +++ b/prismedia/upload.py @@ -7,6 +7,7 @@ prismedia - tool to upload videos to Peertube and Youtube Usage: prismedia --file= [options] prismedia -f --tags=STRING [options] + prismedia --hearthbeat prismedia -h | --help prismedia --version @@ -50,6 +51,9 @@ Options: --playlistCreate Create the playlist if not exists. (default do not create) Only relevant if --playlist is set. --progress=STRING Set the progress bar view, one of percentage, bigFile (MB), accurate (KB). + + --hearthbeat Use some credits to show some activity for you apikey so the platform know it is used and would not put your quota to 0 (only Youtube currently) + -h --help Show this help. --version Show version. @@ -396,11 +400,17 @@ def main(): Optional('--playlist'): Or(None, str), Optional('--playlistCreate'): bool, Optional('--progress'): Or(None, And(str, validateProgress, error="Sorry, progress visualisation not supported")), + '--hearthbeat': bool, '--help': bool, '--version': bool, # This allow to return all other options for further use: https://github.com/keleshev/schema#extra-keys object: object }) + + if options.get('--hearthbeat'): + yt_upload.hearthbeat() + exit(0) + # We need to validate early options first as withNFO and logs options should be prioritized try: options = earlyoptionSchema.validate(options) diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index 22db640..d050336 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -331,6 +331,21 @@ def resumable_upload(request, resource, method, options): time.sleep(sleep_seconds) +def hearthbeat(): + """Use the minimums credits possibles of the API so google does not readuce to 0 the allowed credits. + This apparently happens after 90 days without any usage of credits. + For more info see the official documentations : + - General informations about quotas : https://developers.google.com/youtube/v3/getting-started#quota + - Quota costs for API requests : https://developers.google.com/youtube/v3/determine_quota_cost + - ToS (Americas) #Usage and Quotas : https://developers.google.com/youtube/terms/api-services-terms-of-service#usage-and-quotas""" + youtube = get_authenticated_service() + try: + get_playlist_by_name(youtube, "Foo") + except HttpError as e: + logger.error('Youtube : An HTTP error %d occurred on hearthbeat:\n%s' % + (e.resp.status, e.content)) + + def run(options): youtube = get_authenticated_service() try: From ea39fe9854f6f5bce4804d4272a788583bf60d0d Mon Sep 17 00:00:00 2001 From: Zykino Date: Mon, 8 Feb 2021 16:31:10 +0100 Subject: [PATCH 2/4] Fix some spacing --- prismedia/yt_upload.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index d050336..be23702 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -60,6 +60,7 @@ def get_authenticated_service(): check_authenticated_scopes() flow = InstalledAppFlow.from_client_secrets_file( CLIENT_SECRETS_FILE, SCOPES) + if exists(CREDENTIALS_PATH): with open(CREDENTIALS_PATH, 'r') as f: credential_params = json.load(f) @@ -76,7 +77,7 @@ def get_authenticated_service(): p = copy.deepcopy(vars(credentials)) del p["expiry"] json.dump(p, f) - return build(API_SERVICE_NAME, API_VERSION, credentials=credentials, cache_discovery=False) + return build(API_SERVICE_NAME, API_VERSION, credentials=credentials, cache_discovery=False) def check_authenticated_scopes(): From 29b1747c3ed17ddcb81a45bed24810bc80b12506 Mon Sep 17 00:00:00 2001 From: Zykino Date: Mon, 8 Feb 2021 16:33:41 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Visit=20all=20of=20Youtube=E2=80=99s=20play?= =?UTF-8?q?lists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prismedia/yt_upload.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index be23702..07c9688 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -182,14 +182,24 @@ def initialize_upload(youtube, options): def get_playlist_by_name(youtube, playlist_name): - response = youtube.playlists().list( - part='snippet,id', - mine=True, - maxResults=50 - ).execute() - for playlist in response["items"]: - if playlist["snippet"]['title'] == playlist_name: - return playlist['id'] + pageToken = "" + while pageToken != None: + response = youtube.playlists().list( + part='snippet,id', + mine=True, + maxResults=50, + pageToken=pageToken + ).execute() + + for playlist in response["items"]: + if playlist["snippet"]["title"] == playlist_name: + return playlist["id"] + + # Ask next page if there are any + if "nextPageToken" in response: + pageToken = response["nextPageToken"] + else: + pageToken = None def create_playlist(youtube, playlist_name): From a4f162320dcdfc6cc6109ae84115e8172b6cdda8 Mon Sep 17 00:00:00 2001 From: Zykino Date: Sun, 14 Feb 2021 00:58:52 +0100 Subject: [PATCH 4/4] Fix some spacing formatting and documentation --- README.md | 63 ++++++++++++++++++++++-------------------- prismedia/pt_upload.py | 2 +- prismedia/yt_upload.py | 26 ++++++++--------- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 4139c9f..c10e6f7 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,20 @@ Scripting your way to upload videos to peertube and youtube. Works with Python 3 ### From pip -Simply install with - -```bash +Simply install with +```sh pip install prismedia ``` -Upgrade with - -```bash +Upgrade with +```sh pip install --upgrade prismedia ``` ### From source -Get the source: - -```bash +Get the source: +```sh git clone https://git.lecygnenoir.info/LecygneNoir/prismedia.git prismedia ``` @@ -49,11 +46,10 @@ You may use pip to install requirements: `pip install -r requirements.txt` if yo Otherwise, you can use [poetry](https://python-poetry.org), which create a virtualenv for the project directly (Or use the existing virtualenv if one is activated) -``` +```sh poetry install ``` - ## Configuration Generate sample files with `python -m prismedia.genconfig`. @@ -72,7 +68,7 @@ Youtube uses combination of oauth and API access to identify. The first time you connect, prismedia will open your browser to ask you to authenticate to Youtube and allow the app to use your Youtube channel. **It is here you choose which channel you will upload to**. -Once authenticated, the token is stored inside the file ``.youtube_credentials.json``. +Once authenticated, the token is stored inside the file `.youtube_credentials.json`. Prismedia will try to use this file at each launch, and re-ask for authentication if it does not exist. **Oauth**: @@ -95,35 +91,42 @@ Support only mp4 for cross compatibility between Youtube and Peertube. Here are some demonstration of main usage you would like! Upload a video: -``` -prismedia --file="yourvideo.mp4" +```sh +python -m prismedia --file="yourvideo.mp4" ``` Specify description and tags: -``` -prismedia --file="yourvideo.mp4" -d "My supa description" -t "tag1,tag2,foo" +```sh +python -m prismedia --file="yourvideo.mp4" -d "My supa description" -t "tag1,tag2,foo" ``` Provide a thumbnail: -``` -prismedia --file="yourvideo.mp4" -d "Video with thumbnail" --thumbnail="/path/to/your/thumbnail.jpg" +```sh +python -m prismedia --file="yourvideo.mp4" -d "Video with thumbnail" --thumbnail="/path/to/your/thumbnail.jpg" ``` Publish on Peertube only, while using a channel and a playlist, creating them if they does not exist.: -``` -prismedia --file="yourvideo.mp4" --platform=peertube --channel="Cooking recipes" --playlist="Cake recipes" --channelCreate --playlistCreate +```sh +python -m prismedia --file="yourvideo.mp4" --platform=peertube --channel="Cooking recipes" --playlist="Cake recipes" --channelCreate --playlistCreate ``` Use a NFO file to specify your video options: (See [Enhanced NFO](#enhanced-use-of-nfo) for more precise example) -``` -prismedia --file="yourvideo.mp4" --nfo /path/to/your/nfo.txt +```sh +python -m prismedia --file="yourvideo.mp4" --nfo /path/to/your/nfo.txt ``` +Use some credits to show some activity for you apikey so the platform know it is used and would not put your quota to 0 (only Youtube currently). -Take a look at all available options with `--help`! +To prevent Youtube from inactivating your apikey after 90days of inactivity it is recommended to launch this command automatically from a script around once a month. It will mwke a call to use a few credits from your daily quota. +On Linux and MacOS, you can use cron, on Windows the "Task Scheduler". +```sh +python -m prismedia --hearthbeat ``` -prismedia --help + +Take a look at all available options with `--help`! +```sh +python -m prismedia --help ``` ## Enhanced use of NFO @@ -136,7 +139,7 @@ Basically, Prismedia will now load options in this order, using the last value f `nfo.txt < directory_name.txt < video_name.txt < command line NFO < command line argument` You'll find a complete set of samples in the [prismedia/samples](prismedia/samples) directory so let's take it as an example: -``` +```sh $ tree Recipes/ Recipes/ ├── cli_nfo.txt @@ -149,9 +152,9 @@ Recipes/ └── yourvideo2.txt ``` -By using -``` -prismedia --file=/path/to/Recipes/yourvideo1.mp4 --nfo=/path/to/Recipes/cli_nfo.txt --cca +By using +```sh +python -m prismedia --file=/path/to/Recipes/yourvideo1.mp4 --nfo=/path/to/Recipes/cli_nfo.txt --cca ``` Prismedia will: @@ -186,7 +189,7 @@ Available strict options: - --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 + - --withChannel Prevent upload if no channel ## Features @@ -224,4 +227,4 @@ Available strict options: Inspired by [peeror](https://git.rigelk.eu/rigelk/peeror) and [youtube-upload](https://github.com/tokland/youtube-upload) ## Contributors -Thanks to: @Zykino, @meewan, @rigelk 😘 \ No newline at end of file +Thanks to: @Zykino, @meewan, @rigelk 😘 diff --git a/prismedia/pt_upload.py b/prismedia/pt_upload.py index 6b43151..adb5a36 100644 --- a/prismedia/pt_upload.py +++ b/prismedia/pt_upload.py @@ -373,7 +373,7 @@ def create_callback(encoder, progress_type): else: # Print a blank line to not (partly) override the progress bar print() - logger.info("Peertube : Upload finish, Processing…") + logger.info("Peertube: Upload finish, Processing…") return callback diff --git a/prismedia/yt_upload.py b/prismedia/yt_upload.py index 07c9688..e8809c5 100644 --- a/prismedia/yt_upload.py +++ b/prismedia/yt_upload.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # coding: utf-8 -# From Youtube samples : https://raw.githubusercontent.com/youtube/api-samples/master/python/upload_video.py # noqa +# From Youtube samples: https://raw.githubusercontent.com/youtube/api-samples/master/python/upload_video.py # noqa import http.client import httplib2 @@ -304,7 +304,7 @@ def resumable_upload(request, resource, method, options): status, response = request.next_chunk() if response is not None: if method == 'insert' and 'id' in response: - logger.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)' logger.info(template % response['id']) template_stdout = 'https://youtu.be/%s' @@ -316,28 +316,28 @@ def resumable_upload(request, resource, method, options): elif method != 'insert' or "id" not in response: logger.info('Youtube: Thumbnail was successfully set.') else: - template = ('Youtube : The upload failed with an ' + template = ('Youtube: The upload failed with an ' 'unexpected response: %s') logger.critical(template % response) exit(1) except HttpError as e: if e.resp.status in RETRIABLE_STATUS_CODES: - template = 'Youtube : A retriable HTTP error %d occurred:\n%s' + template = 'Youtube: A retriable HTTP error %d occurred:\n%s' error = template % (e.resp.status, e.content) else: raise except RETRIABLE_EXCEPTIONS as e: - error = 'Youtube : A retriable error occurred: %s' % e + error = 'Youtube: A retriable error occurred: %s' % e if error is not None: logger.warning(error) retry += 1 if retry > MAX_RETRIES: - logger.error('Youtube : No longer attempting to retry.') + logger.error('Youtube: No longer attempting to retry.') max_sleep = 2 ** retry sleep_seconds = random.random() * max_sleep - logger.warning('Youtube : Sleeping %f seconds and then retrying...' + logger.warning('Youtube: Sleeping %f seconds and then retrying...' % sleep_seconds) time.sleep(sleep_seconds) @@ -345,15 +345,15 @@ def resumable_upload(request, resource, method, options): def hearthbeat(): """Use the minimums credits possibles of the API so google does not readuce to 0 the allowed credits. This apparently happens after 90 days without any usage of credits. - For more info see the official documentations : - - General informations about quotas : https://developers.google.com/youtube/v3/getting-started#quota - - Quota costs for API requests : https://developers.google.com/youtube/v3/determine_quota_cost - - ToS (Americas) #Usage and Quotas : https://developers.google.com/youtube/terms/api-services-terms-of-service#usage-and-quotas""" + For more info see the official documentations: + - General informations about quotas: https://developers.google.com/youtube/v3/getting-started#quota + - Quota costs for API requests: https://developers.google.com/youtube/v3/determine_quota_cost + - ToS (Americas) #Usage and Quotas: https://developers.google.com/youtube/terms/api-services-terms-of-service#usage-and-quotas""" youtube = get_authenticated_service() try: get_playlist_by_name(youtube, "Foo") except HttpError as e: - logger.error('Youtube : An HTTP error %d occurred on hearthbeat:\n%s' % + logger.error('Youtube: An HTTP error %d occurred on hearthbeat:\n%s' % (e.resp.status, e.content)) @@ -362,5 +362,5 @@ def run(options): try: initialize_upload(youtube, options) except HttpError as e: - logger.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))