scripting your way to upload videos to peertube and youtube
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

313 lines
11 KiB

#!/usr/bin/env python2
# coding: utf-8
# From Youtube samples : https://raw.githubusercontent.com/youtube/api-samples/master/python/upload_video.py # noqa
import httplib
import httplib2
import random
import time
import copy
import json
from os.path import splitext, basename, exists
import os
import google.oauth2.credentials
import datetime
import pytz
import logging
from tzlocal import get_localzone
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload
from google_auth_oauthlib.flow import InstalledAppFlow
import utils
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
# 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
# Youtube retriables cases
RETRIABLE_EXCEPTIONS = (
IOError,
httplib2.HttpLib2Error,
httplib.NotConnected,
httplib.IncompleteRead,
httplib.ImproperConnectionState,
httplib.CannotSendRequest,
httplib.CannotSendHeader,
httplib.ResponseNotReady,
httplib.BadStatusLine,
)
RETRIABLE_STATUS_CODES = [500, 502, 503, 504]
CLIENT_SECRETS_FILE = 'youtube_secret.json'
CREDENTIALS_PATH = ".youtube_credentials.json"
SCOPES = ['https://www.googleapis.com/auth/youtube.upload', 'https://www.googleapis.com/auth/youtube.force-ssl']
API_SERVICE_NAME = 'youtube'
API_VERSION = 'v3'
# Authorize the request and store authorization credentials.
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)
credentials = google.oauth2.credentials.Credentials(
credential_params["token"],
refresh_token=credential_params["_refresh_token"],
token_uri=credential_params["_token_uri"],
client_id=credential_params["_client_id"],
client_secret=credential_params["_client_secret"]
)
else:
credentials = flow.run_console()
with open(CREDENTIALS_PATH, 'w') as f:
p = copy.deepcopy(vars(credentials))
del p["expiry"]
json.dump(p, f)
return build(API_SERVICE_NAME, API_VERSION, credentials=credentials, cache_discovery=False)
def check_authenticated_scopes():
if exists(CREDENTIALS_PATH):
with open(CREDENTIALS_PATH, 'r') as f:
credential_params = json.load(f)
# Check if all scopes are present
if credential_params["_scopes"] != SCOPES:
logging.warning("Youtube: Credentials are obsolete, need to re-authenticate.")
os.remove(CREDENTIALS_PATH)
def initialize_upload(youtube, options):
path = options.get('--file')
tags = None
if options.get('--tags'):
tags = options.get('--tags').split(',')
category = None
if options.get('--category'):
category = utils.getCategory(options.get('--category'), 'youtube')
language = None
if options.get('--language'):
language = utils.getLanguage(options.get('--language'), "youtube")
license = None
if options.get('--cca'):
license = "creativeCommon"
body = {
"snippet": {
"title": options.get('--name') or splitext(basename(path))[0],
"description": options.get('--description') or "default description",
"tags": tags,
# if no category, set default to 1 (Films)
"categoryId": str(category or 1),
"defaultAudioLanguage": str(language or 'en')
},
"status": {
"privacyStatus": str(options.get('--privacy') or "private"),
"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)
if options.get('--playlist'):
playlist_id = get_playlist_by_name(youtube, options.get('--playlist'))
if not playlist_id and options.get('--playlistCreate'):
playlist_id = create_playlist(youtube, options.get('--playlist'))
elif not playlist_id:
logging.warning("Youtube: Playlist `" + options.get('--playlist') + "` is unknown.")
logging.warning("If you want to create it, set the --playlistCreate option.")
playlist_id = ""
else:
playlist_id = ""
# Call the API's videos.insert method to create and upload the video.
insert_request = youtube.videos().insert(
part=','.join(body.keys()),
body=body,
media_body=MediaFileUpload(path, chunksize=-1, resumable=True)
)
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)
# If we get a video_id, upload is successful and we are able to set playlist
if video_id and options.get('--playlist'):
set_playlist(youtube, playlist_id, video_id)
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']
def create_playlist(youtube, playlist_name):
template = ('Youtube: Playlist %s does not exist, creating it.')
logging.info(template % (str(playlist_name)))
resources = build_resource({'snippet.title': playlist_name,
'snippet.description': '',
'status.privacyStatus': 'public'})
response = youtube.playlists().insert(
body=resources,
part='status,snippet,id'
).execute()
return response["id"]
def build_resource(properties):
resource = {}
for p in properties:
# Given a key like "snippet.title", split into "snippet" and "title", where
# "snippet" will be an object and "title" will be a property in that object.
prop_array = p.split('.')
ref = resource
for pa in range(0, len(prop_array)):
is_array = False
key = prop_array[pa]
# For properties that have array values, convert a name like
# "snippet.tags[]" to snippet.tags, and set a flag to handle
# the value as an array.
if key[-2:] == '[]':
key = key[0:len(key)-2:]
is_array = True
if pa == (len(prop_array) - 1):
# Leave properties without values out of inserted resource.
if properties[p]:
if is_array:
ref[key] = properties[p].split(',')
else:
ref[key] = properties[p]
elif key not in ref:
# For example, the property is "snippet.title", but the resource does
# not yet have a "snippet" object. Create the snippet object here.
# Setting "ref = ref[key]" means that in the next time through the
# "for pa in range ..." loop, we will be setting a property in the
# resource's "snippet" object.
ref[key] = {}
ref = ref[key]
else:
# For example, the property is "snippet.description", and the resource
# already has a "snippet" object.
ref = ref[key]
return resource
def set_thumbnail(youtube, media_file, **kwargs):
kwargs = utils.remove_empty_kwargs(**kwargs)
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')
def set_playlist(youtube, playlist_id, video_id):
logging.info('Youtube: Configuring playlist...')
resource = build_resource({'snippet.playlistId': playlist_id,
'snippet.resourceId.kind': 'youtube#video',
'snippet.resourceId.videoId': video_id,
'snippet.position': ''}
)
try:
youtube.playlistItems().insert(
body=resource,
part='snippet'
).execute()
except Exception as e:
if hasattr(e, 'message'):
logging.error("Youtube: Error: " + str(e.message))
else:
logging.error("Youtube: Error: " + str(e))
logging.info('Youtube: Video is correctly added to the playlist.')
# This method implements an exponential backoff strategy to resume a
# failed upload.
def resumable_upload(request, resource, method):
response = None
error = None
retry = 0
while response is None:
try:
template = 'Youtube: Uploading %s...'
logging.info(template % resource)
status, response = request.next_chunk()
if response is not None:
if method == 'insert' and 'id' in response:
logging.info('Youtube : Video was successfully uploaded.')
template = 'Youtube: Watch it at https://youtu.be/%s (post-encoding could take some time)'
logging.info(template % response['id'])
return response['id']
elif method != 'insert' or "id" not in response:
logging.info('Youtube: Thumbnail was successfully set.')
else:
template = ('Youtube : The upload failed with an '
'unexpected response: %s')
logging.error(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'
error = template % (e.resp.status, e.content)
else:
raise
except RETRIABLE_EXCEPTIONS as e:
error = 'Youtube : A retriable error occurred: %s' % e
if error is not None:
logging.warning(error)
retry += 1
if retry > MAX_RETRIES:
logging.error('Youtube : No longer attempting to retry.')
exit(1)
max_sleep = 2 ** retry
sleep_seconds = random.random() * max_sleep
logging.warning('Youtube : Sleeping %f seconds and then retrying...'
% sleep_seconds)
time.sleep(sleep_seconds)
def run(options):
youtube = get_authenticated_service()
try:
initialize_upload(youtube, options)
except HttpError as e:
logging.error('Youtube : An HTTP error %d occurred:\n%s' % (e.resp.status,
e.content))