Интеграция API YouTube: загрузка видео с помощью Django
Опубликовано: 2022-03-11Некоторое время назад я работал на клиента, интегрируя видеообзоры на их сайт. Как и любой мотивированный разработчик, решающий новую проблему, я первым делом погуглил и нашел множество бесполезных или ошибочных ответов о том, как добиться чего-то совершенно другого, или устаревших и неподдерживаемых пакетов Python. В конце концов, я стиснул зубы, и мы с командой построили все с нуля: мы создали представления, узнали об API Google, создали клиент API и, в конце концов, успешно программно загрузили видео из Django.
В этом посте я постараюсь шаг за шагом рассказать вам, как публиковать видео на YouTube из вашего приложения Django. Для этого потребуется немного поиграть с учетными данными Google API — сначала с веб-интерфейсом, а затем с кодом. Сама часть YouTube очень проста. Нам нужно понять, как работает Google, потому что иногда это сложно, и информация распространяется во многих местах.
Предпосылки
Перед началом работы рекомендую ознакомиться со следующим:
- API данных YouTube: краткое руководство по Python
- API данных YouTube: справочник по API
- API данных YouTube: примеры кода
- Клиентская библиотека Google Python API
- Клиентская библиотека Google Python API: справочный документ
- Клиентская библиотека Google Python API: примеры кода
- API YouTube: примеры кода Python
Интересным фрагментом кода, на который следует обратить внимание, является следующий фрагмент кода Python из документации API Google YouTube:
# Sample python code for videos.insert def videos_insert(client, properties, media_file, **kwargs): resource = build_resource(properties) # See full sample for function kwargs = remove_empty_kwargs(**kwargs) # See full sample for function request = client.videos().insert( body=resource, media_body=MediaFileUpload(media_file, chunksize=-1, resumable=True), **kwargs ) # See full sample for function return resumable_upload(request, 'video', 'insert') media_file = 'sample_video.flv' if not os.path.exists(media_file): exit('Please specify a valid file location.') videos_insert(client, {'snippet.categoryId': '22', 'snippet.defaultLanguage': '', 'snippet.description': 'Description of uploaded video.', 'snippet.tags[]': '', 'snippet.title': 'Test video upload', 'status.embeddable': '', 'status.license': '', 'status.privacyStatus': 'private', 'status.publicStatsViewable': ''}, media_file, part='snippet,status')
Начиная
После того, как вы ознакомились с предварительными условиями, пришло время приступить к работе. Давайте посмотрим, что нам нужно.
Пояс с инструментами
В общем, давайте создадим виртуальную среду. Я лично предпочитаю pyenv. Настройка обоих выходит за рамки этого поста, поэтому я собираюсь опубликовать некоторые команды pyenv ниже, и, если вы предпочитаете virtualenv
, не стесняйтесь заменять команды соответствующим образом.
В этом посте я буду использовать Python 3.7 и Django 2.1.
➜ ~/projects $ mkdir django-youtube ➜ ~/projects $ cd django-youtube ➜ ~/projects/django-youtube $ pyenv virtualenv 3.7.0 djangoyt ➜ ~/projects/django-youtube $ vim .python-version
Давайте поместим это в содержимое (только если вы используете pyenv, чтобы он активировался автоматически при входе в папку):
djangoyt
Установка зависимостей:
➜ ~/projects/django-youtube $ pip install google-api-python-client google-auth\ google-auth-oauthlib google-auth-httplib2 oauth2client Django unipath jsonpickle
Теперь пришло время запустить наш проект django:
➜ ~/projects/django-youtube $ django-admin startproject django_youtube .
Пауза для некоторых настроек Google
Теперь давайте настроим учетные данные нашего проекта, чтобы мы могли использовать API Google.
Шаг 1. Перейдите по следующему URL-адресу:
https://console.developers.google.com/apis/library/youtube.googleapis.com
Шаг 2. Создайте новый проект.
Шаг 3. Нажмите «Включить API и службы».
Шаг 4. Найдите YouTube Data API v3 и нажмите «Включить».
Шаг 5. Вы должны получить сообщение об учетных данных.
Шаг 6. Нажмите синюю кнопку «Создать учетные данные» справа, и вы должны получить следующий экран:
Шаг 7. Выберите веб-сервер, данные пользователя:
Шаг 8. Добавьте авторизованные источники JS и URI перенаправления. Продолжайте до конца:
Хорошо, мы закончили настройку учетных данных. Вы можете загрузить учетные данные в формате JSON или скопировать идентификатор клиента и секрет клиента .
Вернуться к Джанго
Давайте запустим наше самое первое приложение Django. Обычно я называю его «ядро»:
(djangoyt) ➜ ~/projects/django-youtube $ python manage.py startapp core
Теперь давайте добавим следующее в наш корневой файл urls.py, чтобы направлять запросы домашней страницы в наше основное приложение:
# <root>/urls.py from django.urls import path, include path('', include(('core.urls', 'core'), namespace='core')),
В основном приложении давайте создадим еще один файл urls.py с некоторой конфигурацией:
# core/urls.py from django.conf import settings from django.conf.urls.static import static from django.urls import path from .views import HomePageView urlpatterns = [ path('', HomePageView.as_view(), name='home') ] if settings.DEBUG: urlpatterns += static( settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static( settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Видите, есть пустой путь, указывающий на HomePageView
. Время добавить немного кода.
Давайте теперь создадим простой TemplateView
, чтобы увидеть, как он работает.
# core/views.py from django.shortcuts import render from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'core/home.html'
И, конечно же, нам нужен базовый шаблон:
# core/templates/core/home.html <!DOCTYPE html> <html> <body> <h1>My First Heading</h1> <p>My first paragraph.</p> </body> </html>
Нам нужно внести некоторые изменения в настройки:
# settings.py from unipath import Path # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = Path(__file__).parent INSTALLED_APPS 'core', STATIC_ROOT = BASE_DIR.parent.child('staticfiles') STATIC_URL = '/static/' MEDIA_ROOT = BASE_DIR.parent.child('uploads') MEDIA_URL = '/media/'
Давайте теперь создадим YoutubeForm
и добавим его как form_class
в представление:
# core/views.py from django import forms from django.views.generic.edit import FormView class YouTubeForm(forms.Form): pass class HomePageView(FormView): template_name = 'core/home.html' form_class = YouTubeForm
Попробуйте запустить приложение сейчас, и страница будет выглядеть так:
Пауза для авторизации
Прежде всего, вам нужно создать модель для хранения ваших учетных данных. Вы можете использовать файл, систему кэширования или любое другое решение для хранения, но база данных кажется разумной и масштабируемой, и вы также можете хранить учетные данные для каждого пользователя, если хотите.
Прежде чем продолжить, необходимо внести поправки — существует форк oauth2client, поддерживающий Django 2.1, который мы должны использовать. Скоро у нас будет официальная поддержка, а пока вы можете ознакомиться с изменениями форка. Они очень простые.
pip install -e git://github.com/Schweigi/[email protected]#egg=oauth2client Because of compatibility with Django 2.1
Перейдите в свой settings.py
и поместите идентификатор клиента и секрет клиента , которые вы получили от Google на предыдущих шагах.
# settings.py GOOGLE_OAUTH2_CLIENT_ GOOGLE_OAUTH2_CLIENT_SECRET = '<your client secret>'
Внимание: хранить секреты в коде не рекомендуется. Я делаю это просто как демонстрацию. Я рекомендую использовать переменные среды в вашем рабочем приложении, а не жестко кодировать секреты в файлах приложения. В качестве альтернативы, если вы загрузили JSON из Google, вы также можете указать его путь вместо настроек выше:
GOOGLE_OAUTH2_CLIENT_SECRETS_JSON = '/path/to/client_id.json'
Пакет oauth2client уже предоставляет множество функций с уже готовым полем CredentialsField
, которое мы можем использовать. Можно добавить больше полей, таких как внешний ключ и даты создания/изменения, чтобы мы стали более надежными, но давайте останемся простыми.
Простая модель для хранения учетных данных:
# core/models.py from django.db import models from oauth2client.contrib.django_util.models import CredentialsField class CredentialsModel(models.Model): credential = CredentialsField()
Время создавать миграции и мигрировать:

(djangoyt) ➜ ~/projects/django-youtube $ ./manage.py makemigrations core (djangoyt) ➜ ~/projects/django-youtube $ ./manage.py migrate
Теперь давайте изменим наши представления API, чтобы иметь возможность авторизовать наше приложение:
В наш файл core/urls.py
добавим еще одну запись для первого представления авторизации:
# core/urls.py from .views import AuthorizeView, HomePageView urlpatterns = [ # [...] path('authorize/', AuthorizeView.as_view(), name='authorize'), ]
Итак, первая часть AuthorizeView будет:
# core/views.py from django.conf import settings from django.shortcuts import render, redirect from django.views.generic.base import View from oauth2client.client import flow_from_clientsecrets, OAuth2WebServerFlow from oauth2client.contrib import xsrfutil from oauth2client.contrib.django_util.storage import DjangoORMStorage from .models import CredentialsModel # [...] class AuthorizeView(View): def get(self, request, *args, **kwargs): storage = DjangoORMStorage( CredentialsModel, 'id', request.user.id, 'credential') credential = storage.get() flow = OAuth2WebServerFlow( client_id=settings.GOOGLE_OAUTH2_CLIENT_ID, client_secret=settings.GOOGLE_OAUTH2_CLIENT_SECRET, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/') # or if you downloaded the client_secrets file '''flow = flow_from_clientsecrets( settings.GOOGLE_OAUTH2_CLIENT_SECRETS_JSON, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/')'''
А потом вторая часть:
if credential is None or credential.invalid == True: flow.params['state'] = xsrfutil.generate_token( settings.SECRET_KEY, request.user) authorize_url = flow.step1_get_authorize_url() return redirect(authorize_url) return redirect('/')
Поэтому, если учетных данных нет или они недействительны, сгенерируйте их, а затем перенаправьте на URL-адрес авторизации. В противном случае просто перейдите на домашнюю страницу, чтобы мы могли загрузить видео!
Давайте теперь получим доступ к представлению и посмотрим, что произойдет:
Давайте создадим пользователя, прежде чем переходить на эту страницу.
(djangoyt) ➜ ~/projects/django-youtube $ python manage.py createsuperuser Username (leave blank to use 'ivan'): ivan Email address: ivan***@mail.com Password: Password (again): This password is too short. It must contain at least 8 characters. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully.
Давайте также войдем с ним через /admin
. После этого давайте снова получим доступ к нашему представлению /authorize/
.
Потом,
Хорошо, он попытался перенаправить на URL-адрес обратного вызова, который мы давно настроили в Google. Теперь нам нужно реализовать представление обратного вызова.
Добавим еще одну запись в наш core/urls.py:
# core/urls.py from .views import AuthorizeView, HomePageView, Oauth2CallbackView urlpatterns = [ # [...] path('oauth2callback/', Oauth2CallbackView.as_view(), name='oauth2callback') ]
И еще один вид:
# core/views.py # the following variable stays as global for now flow = OAuth2WebServerFlow( client_id=settings.GOOGLE_OAUTH2_CLIENT_ID, client_secret=settings.GOOGLE_OAUTH2_CLIENT_SECRET, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/') # or if you downloaded the client_secrets file '''flow = flow_from_clientsecrets( settings.GOOGLE_OAUTH2_CLIENT_SECRETS_JSON, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/')''' # [...] class Oauth2CallbackView(View): def get(self, request, *args, **kwargs): if not xsrfutil.validate_token( settings.SECRET_KEY, request.GET.get('state').encode(), request.user): return HttpResponseBadRequest() credential = flow.step2_exchange(request.GET) storage = DjangoORMStorage( CredentialsModel, 'id', request.user.id, 'credential') storage.put(credential) return redirect('/')
Примечание. Поток был перемещен за пределы AuthorizeView и стал глобальным. В идеале вы должны создать его в AuthorizeView и сохранить в кеше, а затем получить в обратном вызове. Но это выходит за рамки данного поста.
Метод get AuthorizeView теперь:
def get(self, request, *args, **kwargs): storage = DjangoORMStorage( CredentialsModel, 'id', request.user.id, 'credential') credential = storage.get() if credential is None or credential.invalid == True: flow.params['state'] = xsrfutil.generate_token( settings.SECRET_KEY, request.user) authorize_url = flow.step1_get_authorize_url() return redirect(authorize_url) return redirect('/')
Посмотреть похожие реализации можно здесь. Пакет oauth2client
сам предоставляет представления, но я особенно предпочитаю реализовать собственное представление Oauth.
- https://github.com/google/google-api-python-client/blob/master/samples/django_sample/plus/views.py
- https://github.com/google/oauth2client/blob/master/oauth2client/contrib/django_util/views.py
Теперь, если вы снова попробуете URL-адрес /authorize/
, поток OAuth должен работать. Время проверить, стоит ли эта работа того, и загрузить наше видео! HomePageView
сначала проверит учетные данные, и если все в порядке, мы готовы к загрузке нашего видео.
Давайте проверим, как будет выглядеть наш новый код для HomePageView:
import tempfile from django.http import HttpResponse, HttpResponseBadRequest from googleapiclient.discovery import build from googleapiclient.http import MediaFileUpload class HomePageView(FormView): template_name = 'core/home.html' form_class = YouTubeForm def form_valid(self, form): fname = form.cleaned_data['video'].temporary_file_path() storage = DjangoORMStorage( CredentialsModel, 'id', self.request.user.id, 'credential') credentials = storage.get() client = build('youtube', 'v3', credentials=credentials) body = { 'snippet': { 'title': 'My Django Youtube Video', 'description': 'My Django Youtube Video Description', 'tags': 'django,howto,video,api', 'categoryId': '27' }, 'status': { 'privacyStatus': 'unlisted' } } with tempfile.NamedTemporaryFile('wb', suffix='yt-django') as tmpfile: with open(fname, 'rb') as fileobj: tmpfile.write(fileobj.read()) insert_request = client.videos().insert( part=','.join(body.keys()), body=body, media_body=MediaFileUpload( tmpfile.name, chunksize=-1, resumable=True) ) insert_request.execute() return HttpResponse('It worked!')
И новый шаблон:
{# core/templates/core/home.html #} <!DOCTYPE html> <html> <body> <h1>Upload your video</h1> <p>Here is the form:</p> <form action="." method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="Submit"> </form> </body> </html>
Не забудьте добавить поле видео в YouTubeForm:
class YouTubeForm(forms.Form): video = forms.FileField()
Вот так!
А затем, заглянув на страницу Studio аккаунта YouTube (важно иметь канал):
Вуаля!
Заключительные примечания
Код нуждается в некоторой доработке, но это хорошая отправная точка. Я надеюсь, что это помогло с большинством проблем с интеграцией Google YouTube API. Вот еще несколько важных моментов, на которые стоит обратить внимание:
- Для авторизации важно требовать логин и дополнительные разрешения для пользователя, который разрешит вашему приложению загружать видео.
- Переменная потока должна быть перемещена из глобальной. В производственной среде это небезопасно. Например, лучше кэшировать на основе идентификатора пользователя или сеанса, который получил доступ к первому представлению.
- Google предоставляет токен обновления только при первой авторизации. Таким образом, через некоторое время, в основном через час, срок действия вашего токена истечет, и если вы не взаимодействовали с их API, вы начнете получать ответы
invalid_grant
. Повторная авторизация того же пользователя, который уже авторизовал клиента, не гарантирует ваш токен обновления. Вы должны отозвать заявку на странице своих учетных записей Google, а затем снова выполнить процесс авторизации. В некоторых случаях вам может потребоваться запустить задачу, чтобы постоянно обновлять токен. - Нам нужно требовать входа в систему в нашем представлении, поскольку мы используем учетные данные пользователя, непосредственно связанные с запросом.
Загрузка занимает много времени, и ее выполнение в основном процессе приложения может привести к блокировке всего приложения во время загрузки. Правильным способом было бы переместить его в отдельный процесс и обрабатывать загрузки асинхронно.
Смущенный? Не будьте, читайте больше в Оркестрировании рабочего процесса фонового задания в Celery для Python .