unlimited-storage

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit ea23bcbcfb62ef2c6ba51ae02abf434c9d7b2e92
parent 36347a3b3d928cd8e359e7df3474527d2181f235
Author: Andrew Laack <andrew@laack.co>
Date:   Sun, 28 Sep 2025 14:38:18 -0500

Added more code for auto-uploading, this isn't super useful... yet'

Diffstat:
M.gitignore | 1+
Mpython/break_up.sh | 2+-
Apython/random.sh | 13+++++++++++++
Apython/send.sh | 3+++
Apython/to_vid.sh | 3+++
Apython/upload_video.py | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 204 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore @@ -3,3 +3,4 @@ *.tga output/* tests/artifacts +*.json diff --git a/python/break_up.sh b/python/break_up.sh @@ -1,3 +1,3 @@ #!/bin/bash rm -rf $2 -ffmpeg -i $1 -vf fps=1 $2out%d.png +ffmpeg -i "$1" -vf fps=1 -c:v png "${2}out%d.png" diff --git a/python/random.sh b/python/random.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +while [ true ]; do + + rm -rf /dev/shm/* + + dd if=/dev/urandom of=/dev/shm/random_noise bs=1000M count=1 + + python3 encode.py 3840 2160 /dev/shm/random_noise /dev/shm/ + + ./to_vid.sh /dev/shm/ /dev/shm + ./send.sh +done diff --git a/python/send.sh b/python/send.sh @@ -0,0 +1,3 @@ +#!/bin/bash +NAME=$(sha1sum /dev/shm/random_noise | awk '{print $1}') +python3 upload_video.py --file="/dev/shm/output.mkv" --title="coding_tutorial_v$NAME" --description="Had fun surfing in Santa Cruz" --keywords="surfing,Santa Cruz" --category="22" --privacyStatus="private" --noauth_local_webserver diff --git a/python/to_vid.sh b/python/to_vid.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +ffmpeg -pattern_type glob -framerate 1 -i "$1*.png" -vf "scale=426:240" -c:v ffv1 $2/output.mkv diff --git a/python/upload_video.py b/python/upload_video.py @@ -0,0 +1,183 @@ +#!/usr/bin/python + +import httplib2 +import os +import random +import sys +import time + +from apiclient.discovery import build +from apiclient.errors import HttpError +from apiclient.http import MediaFileUpload +from oauth2client.client import flow_from_clientsecrets +from oauth2client.file import Storage +from oauth2client.tools import argparser, run_flow + + +# Explicitly tell the underlying HTTP transport library not to retry, since +# we are handling retry logic ourselves. +httplib2.RETRIES = 1 + +# Maximum number of times to retry before giving up. +MAX_RETRIES = 10 + +# Always retry when these exceptions are raised. +RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError) + +# Always retry when an apiclient.errors.HttpError with one of these status +# codes is raised. +RETRIABLE_STATUS_CODES = [500, 502, 503, 504] + +# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains +# the OAuth 2.0 information for this application, including its client_id and +# client_secret. You can acquire an OAuth 2.0 client ID and client secret from +# the Google API Console at +# https://console.cloud.google.com/. +# Please ensure that you have enabled the YouTube Data API for your project. +# For more information about using OAuth2 to access the YouTube Data API, see: +# https://developers.google.com/youtube/v3/guides/authentication +# For more information about the client_secrets.json file format, see: +# https://developers.google.com/api-client-library/python/guide/aaa_client_secrets +CLIENT_SECRETS_FILE = "client_secrets.json" + +# This OAuth 2.0 access scope allows an application to upload files to the +# authenticated user's YouTube channel, but doesn't allow other types of access. +YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload" +YOUTUBE_API_SERVICE_NAME = "youtube" +YOUTUBE_API_VERSION = "v3" + +# This variable defines a message to display if the CLIENT_SECRETS_FILE is +# missing. +MISSING_CLIENT_SECRETS_MESSAGE = """ +WARNING: Please configure OAuth 2.0 + +To make this sample run you will need to populate the client_secrets.json file +found at: + + %s + +with information from the API Console +https://console.cloud.google.com/ + +For more information about the client_secrets.json file format, please visit: +https://developers.google.com/api-client-library/python/guide/aaa_client_secrets +""" % os.path.abspath(os.path.join(os.path.dirname(__file__), + CLIENT_SECRETS_FILE)) + +VALID_PRIVACY_STATUSES = ("public", "private", "unlisted") + + +def get_authenticated_service(args): + flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, + scope=YOUTUBE_UPLOAD_SCOPE, + message=MISSING_CLIENT_SECRETS_MESSAGE) + + storage = Storage("%s-oauth2.json" % sys.argv[0]) + credentials = storage.get() + + if credentials is None or credentials.invalid: + credentials = run_flow(flow, storage, args) + + return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, + http=credentials.authorize(httplib2.Http())) + + +def initialize_upload(youtube, options): + tags = None + if options.keywords: + tags = options.keywords.split(",") + + body = dict( + snippet=dict( + title=options.title, + description=options.description, + tags=tags, + categoryId=options.category + ), + status=dict( + privacyStatus=options.privacyStatus + ) + ) + + # Call the API's videos.insert method to create and upload the video. + insert_request = youtube.videos().insert( + part=",".join(body.keys()), + body=body, + # The chunksize parameter specifies the size of each chunk of data, in + # bytes, that will be uploaded at a time. Set a higher value for + # reliable connections as fewer chunks lead to faster uploads. Set a lower + # value for better recovery on less reliable connections. + # + # Setting "chunksize" equal to -1 in the code below means that the entire + # file will be uploaded in a single HTTP request. (If the upload fails, + # it will still be retried where it left off.) This is usually a best + # practice, but if you're using Python older than 2.6 or if you're + # running on App Engine, you should set the chunksize to something like + # 1024 * 1024 (1 megabyte). + media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True) + ) + + resumable_upload(insert_request) + +# This method implements an exponential backoff strategy to resume a +# failed upload. + + +def resumable_upload(insert_request): + response = None + error = None + retry = 0 + while response is None: + try: + print("Uploading file...") + status, response = insert_request.next_chunk() + if response is not None: + if 'id' in response: + print("Video id '%s' was successfully uploaded." % + response['id']) + else: + exit("The upload failed with an unexpected response: %s" % response) + except HttpError as e: + if e.resp.status in RETRIABLE_STATUS_CODES: + error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status, + e.content) + else: + raise + except RETRIABLE_EXCEPTIONS as e: + error = "A retriable error occurred: %s" % e + + if error is not None: + print(error) + retry += 1 + if retry > MAX_RETRIES: + exit("No longer attempting to retry.") + + max_sleep = 2 ** retry + sleep_seconds = random.random() * max_sleep + print("Sleeping %f seconds and then retrying..." % sleep_seconds) + time.sleep(sleep_seconds) + + +if __name__ == '__main__': + argparser.add_argument("--file", required=True, + help="Video file to upload") + argparser.add_argument("--title", help="Video title", default="Test Title") + argparser.add_argument("--description", help="Video description", + default="Test Description") + argparser.add_argument("--category", default="22", + help="Numeric video category. " + + "See https://developers.google.com/youtube/v3/docs/videoCategories/list") + argparser.add_argument("--keywords", help="Video keywords, comma separated", + default="") + argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES, + default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.") + args = argparser.parse_args() + + if not os.path.exists(args.file): + exit("Please specify a valid file using the --file= parameter.") + + youtube = get_authenticated_service(args) + try: + initialize_upload(youtube, args) + except HttpError as e: + print("An HTTP error %d occurred:\n%s" % (e.resp.status, e.content))