Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.

Add Uploading from File-Like objects #16

Merged
merged 7 commits into from
Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/YoutubeUploader.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- __init__
- authenticate
- upload
- upload_stream
- close
rendering:
show_root_heading: false
Expand Down
112 changes: 90 additions & 22 deletions youtube_upload/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import httplib2
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload
from googleapiclient.http import MediaFileUpload, MediaIoBaseUpload
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run_flow
Expand Down Expand Up @@ -162,26 +162,83 @@ def upload(self, file_path, options=None, chunksize=(-1)):
The parameter, `chunk_size` is the max size of the HTTP request to send the video. This parameter is in bytes, and if set to `-1`, which is the default, it
will send the video in one large request. Set this to a different value if you are having issues with the upload failing.

You can also add a callback function for the case where the upload were to fail. This is mainly for custom error handling and prompting users to re-authenticate.

The callback function is the parameter `noauth_callback`. The function passed should be able to accept a tuple of arguments. The parameter `noauth_args` is the arguments for the function. Here
is an example of the callback function being used.
Will return the response from YouTube, as well as the response of the thumbnail upload as a tuple.

```Python
from youtube_upload.client import YoutubeUploader
response, thumbnail_response = client.upload(file_path, options)
```

'''
if options is None:
options = {}
body = {
'snippet': {
'title': options.get('title', 'Test Title'),
'description': options.get('description', 'Test Description'),
'tags': options.get('tags'),
'categoryId': options.get('category', '22')
},
'status': {
'privacyStatus': options.get('privacyStatus', VALID_PRIVACY_STATUSES[0]),
'selfDeclaredMadeForKids': options.get('kids', False)
}
}

def callback(*args):
print(args[0], args[1])
insert_request = self.youtube.videos().insert(
part=",".join(
list(
body.keys())), body=body, media_body=MediaFileUpload(
file_path, chunksize=chunksize, resumable=True))

with YoutubeUploader() as youtube:
youtube.authenticate()
youtube.upload("test.mp4", noauth_callback=callback, noauth_args=("It failed."))
```
return self._resumable_upload(
insert_request, bool(
options.get('thumbnailLink')), options)

def upload_stream(self, file_object, options=None, chunksize=(-1)):
'''
Uploads the file to YouTube from the specified file-like object.

We are using this to stream files from S3 to YouTube!

```python
import s3fs
from youtube_upload.client import YouTubeUploader
fs = s3fs.S3FileSystem(anon=True)

And here is the example output in the case that the upload would fail.
video = fs.open('s3://bucket/video.mp4')

client = YouTubeUploader()

client.authenticate()

client.upload_stream(video)

client.close()
video.close()

```
It failed. invalid_grant: Bad Request

The `options` parameter is a dictionary of options. The items are pretty self explanatory, here is an example options dictionary:
```Python
# Video options
options = {
title : "Example title",
description : "Example description",
tags : ["tag1", "tag2", "tag3"],
categoryId : "22",
privacyStatus : "private",
kids : False
thumbnailLink : "https://cdn.havecamerawilltravel.com/photographer/files/2020/01/youtube-logo-new-1068x510.jpg"
}
```

The parameter, `chunk_size` is the max size of the HTTP request to send the video. This parameter is in bytes, and if set to `-1`, which is the default, it
will send the video in one large request. Set this to a different value if you are having issues with the upload failing.

Will return the response from YouTube, as well as the response of the thumbnail upload as a tuple.

```Python
response, thumbnail_response = client.upload(file_path, options)
```
'''
if options is None:
Expand All @@ -199,53 +256,64 @@ def callback(*args):
}
}

media = MediaIoBaseUpload(
file_object,
"application/octet-stream",
chunksize=chunksize,
resumable=True)

insert_request = self.youtube.videos().insert(
part=",".join(
list(
body.keys())), body=body, media_body=MediaFileUpload(
file_path, chunksize=chunksize, resumable=True))
body.keys())), body=body, media_body=media)

self._resumable_upload(
return self._resumable_upload(
insert_request, bool(
options.get('thumbnailLink')), options)

def _resumable_upload(self, insert_request, uploadThumbnail, options):
response = None
thumbnail_response = None
error = None
retry = 0

while response is None:
try:
_, response = insert_request.next_chunk()
#skipcq: PYL-R1723
if 'id' in response:
video_id = response.get('id')
if uploadThumbnail:
request = self.youtube.thumbnails().set(
videoId=video_id, media_body=MediaFileUpload(
options.get('thumbnailLink')))
response = request.execute()
print(response)
thumbnail_response = request.execute()
break

else:
# skipcq: PYL-E1120
raise HttpError(f'Unexpected response: {response}')
raise Exception(f'Unexpected response: {response}')
except HttpError as e:
if e.resp.status in RETRYABLE_STATUS_CODES:
error = "A retryable HTTP error %d occurred:\n%s" % (
e.resp.status, e.content)
else:
raise
raise e
except RETRYABLE_EXCEPTIONS as e:
error = "A retryable error occurred: %s" % e

if error is not None:
print(error)
retry += 1
if retry > self.max_retry:
sys.exit("No longer attempting to retry.")
#skipcq: PYL-E1120
raise Exception('Exceeded max retries. ' + error)

print("Sleeping 5 seconds and then retrying...")
time.sleep(5)

return response, thumbnail_response

def close(self):
'''
Tears down and closes the class cleanly.
Expand Down