|
|
@ -9,6 +9,7 @@ import time |
|
|
|
import copy |
|
|
|
import json |
|
|
|
from os.path import splitext, basename, exists |
|
|
|
import os |
|
|
|
import google.oauth2.credentials |
|
|
|
import datetime |
|
|
|
import pytz |
|
|
@ -51,13 +52,14 @@ 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'] |
|
|
|
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): |
|
|
@ -71,7 +73,7 @@ def get_authenticated_service(): |
|
|
|
client_secret=credential_params["_client_secret"] |
|
|
|
) |
|
|
|
else: |
|
|
|
credentials = flow.run_local_server() |
|
|
|
credentials = flow.run_console() |
|
|
|
with open(CREDENTIALS_PATH, 'w') as f: |
|
|
|
p = copy.deepcopy(vars(credentials)) |
|
|
|
del p["expiry"] |
|
|
@ -79,6 +81,16 @@ def get_authenticated_service(): |
|
|
|
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 |
|
|
@ -121,6 +133,17 @@ def initialize_upload(youtube, options): |
|
|
|
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()), |
|
|
@ -133,9 +156,77 @@ def initialize_upload(youtube, options): |
|
|
|
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) # See full sample for function |
|
|
|
kwargs = utils.remove_empty_kwargs(**kwargs) |
|
|
|
request = youtube.thumbnails().set( |
|
|
|
media_body=MediaFileUpload(media_file, chunksize=-1, |
|
|
|
resumable=True), |
|
|
@ -146,6 +237,26 @@ def set_thumbnail(youtube, media_file, **kwargs): |
|
|
|
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 correclty added to the playlist.') |
|
|
|
|
|
|
|
|
|
|
|
# This method implements an exponential backoff strategy to resume a |
|
|
|
# failed upload. |
|
|
|
def resumable_upload(request, resource, method): |
|
|
|