Integração da API do YouTube: Upload de vídeos com o Django

Publicados: 2022-03-11

Há pouco tempo, eu estava trabalhando para um cliente, integrando resenhas de vídeo em seu site. Como qualquer desenvolvedor motivado resolvendo um problema novo, a primeira coisa que fiz foi pesquisar no Google, e encontrei uma infinidade de respostas inúteis ou equivocadas sobre como conseguir algo totalmente diferente, ou pacotes Python desatualizados e sem manutenção. Eventualmente, eu mordi a bala e a equipe e eu construímos tudo do zero: criamos as visualizações, aprendemos sobre a API do Google, criamos o cliente da API e, eventualmente, conseguimos enviar vídeos programaticamente do Django.

Neste post, tentarei guiá-lo passo a passo em como postar vídeos do YouTube a partir do seu aplicativo Django. Isso exigirá um pouco de brincar com as credenciais da API do Google - primeiro com a interface da Web, depois com o código. A parte do YouTube em si é muito simples. Precisamos entender como as coisas do Google funcionam porque às vezes é complicado e as informações se espalham por muitos lugares.

Pré-requisitos

Eu recomendo familiarizar-se com o seguinte antes de começarmos a trabalhar:

  • API de dados do YouTube: início rápido do Python
  • API de dados do YouTube: referência de API
  • API de dados do YouTube: exemplos de código
  • Biblioteca cliente da API Python do Google
  • Biblioteca de cliente da API do Google Python: documento de referência
  • Biblioteca de cliente da API do Google Python: exemplos de código
  • API do YouTube: exemplos de código Python

Um trecho de código interessante a ser observado é o seguinte trecho de Python do Google YouTube API Docs:

 # 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')

Começando

Depois de ler os pré-requisitos, é hora de começar. Vamos ver o que precisamos.

Cinto de ferramentas

Basicamente, vamos criar um ambiente virtual. Eu pessoalmente prefiro pyenv. Configurar ambos está fora do escopo deste post, então vou postar alguns comandos pyenv abaixo e, se sua preferência for virtualenv , sinta-se à vontade para substituir os comandos de acordo.

Vou usar Python 3.7 e Django 2.1 neste post.

 ➜ ~/projects $ mkdir django-youtube ➜ ~/projects $ cd django-youtube ➜ ~/projects/django-youtube $ pyenv virtualenv 3.7.0 djangoyt ➜ ~/projects/django-youtube $ vim .python-version

Vamos colocar isso no conteúdo (apenas se você usar pyenv, para que ele seja ativado automaticamente quando você entrar na pasta):

 djangoyt

Instalando dependências:

 ➜ ~/projects/django-youtube $ pip install google-api-python-client google-auth\ google-auth-oauthlib google-auth-httplib2 oauth2client Django unipath jsonpickle

Agora é hora de iniciar nosso projeto django:

 ➜ ~/projects/django-youtube $ django-admin startproject django_youtube .

Pausa para algumas configurações do Google

Vamos configurar nossas credenciais de projeto agora para podermos usar as APIs do Google.

Etapa 1. Acesse o seguinte URL:

https://console.developers.google.com/apis/library/youtube.googleapis.com

Etapa 2. Crie um novo projeto.

Criar um novo projeto

Etapa 3. Clique em "Ativar APIs e serviços".

Habilite APIs e Serviços.

Etapa 4. Procure a API de dados do YouTube v3 e clique em "Ativar".

Procure a API de dados do YouTube v3 e clique em "Ativar".

Etapa 5. Você deve receber uma mensagem sobre credenciais.

uma mensagem sobre credenciais

Etapa 6. Clique no botão azul “Criar credenciais” no lado direito e você deverá obter a seguinte tela:

Clique no botão azul "Criar credenciais"

Etapa 7. Escolha o servidor Web, Dados do usuário:

Escolha o servidor Web, dados do usuário

Etapa 8. Adicione origens JS autorizadas e redirecione URIs. Continue até o final:

Adicione origens JS autorizadas e redirecione URIs.

OK, terminamos com nossas credenciais configuradas. Você pode baixar as credenciais em um formato JSON ou copiar o Client ID e o Client Secret .

De volta ao Django

Vamos começar nosso primeiro aplicativo Django. Eu costumo chamá-lo de “núcleo”:

 (djangoyt) ➜ ~/projects/django-youtube $ python manage.py startapp core

Agora, vamos adicionar o seguinte ao nosso arquivo root urls.py para rotear as solicitações da página inicial para nosso aplicativo principal:

 # <root>/urls.py from django.urls import path, include path('', include(('core.urls', 'core'), namespace='core')),

No aplicativo principal, vamos ter outro arquivo urls.py, com algumas configurações também:

 # 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)

Veja que há um caminho vazio apontando para HomePageView . Hora de adicionar algum código.

Vamos fazer agora um TemplateView simples apenas para vê-lo rodando.

 # core/views.py from django.shortcuts import render from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'core/home.html'

E é claro que precisamos de um modelo básico:

 # core/templates/core/home.html <!DOCTYPE html> <html> <body> <h1>My First Heading</h1> <p>My first paragraph.</p> </body> </html>

Precisamos fazer alguns ajustes nas configurações:

 # 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/'

Vamos criar agora um YoutubeForm e adicioná-lo como form_class na view:

 # 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

Tente executar seu aplicativo agora e a página ficará assim:

Visualização da página

Pausar para fazer a autorização

Antes de tudo, você precisa criar um modelo para armazenar suas credenciais. Você pode usar um arquivo, sistema de cache ou qualquer outra solução de armazenamento, mas um banco de dados parece razoável e escalável, e também pode armazenar credenciais por usuários, se desejar.

Antes de prosseguir, um ajuste precisa ser feito—há um fork do oauth2client que suporta Django 2.1 que temos que usar. Em breve, teremos suporte oficial, mas enquanto isso, você pode inspecionar as alterações do fork. Eles são muito simples.

 pip install -e git://github.com/Schweigi/[email protected]#egg=oauth2client Because of compatibility with Django 2.1

Vá para o seu settings.py e coloque o ID do cliente e o Segredo do cliente que você recebeu do Google nas etapas anteriores.

 # settings.py GOOGLE_OAUTH2_CLIENT_ GOOGLE_OAUTH2_CLIENT_SECRET = '<your client secret>'

Atenção: não é recomendado armazenar segredos em seu código. Estou fazendo isso simplesmente como uma demonstração. Eu recomendo usar variáveis ​​de ambiente em seu aplicativo de produção, e não segredos de hardcoding em arquivos de aplicativo. Como alternativa, se você baixou o JSON do Google, também pode especificar seu caminho em vez das configurações acima:

 GOOGLE_OAUTH2_CLIENT_SECRETS_JSON = '/path/to/client_id.json'

O pacote oauth2client já oferece bastante funcionalidade, com um CredentialsField já feito que podemos usar. É possível adicionar mais campos, como uma chave estrangeira e datas criadas/modificadas para ficarmos mais robustos, mas vamos ser simples.

Modelo simples para armazenar credenciais:

 # core/models.py from django.db import models from oauth2client.contrib.django_util.models import CredentialsField class CredentialsModel(models.Model): credential = CredentialsField()

Hora de criar migrações e migrar:

 (djangoyt) ➜ ~/projects/django-youtube $ ./manage.py makemigrations core (djangoyt) ➜ ~/projects/django-youtube $ ./manage.py migrate

Agora vamos alterar nossas visualizações de API para poder autorizar nosso aplicativo:

Em nosso arquivo core/urls.py , vamos adicionar outra entrada para a primeira visualização de autorização:

 # core/urls.py from .views import AuthorizeView, HomePageView urlpatterns = [ # [...] path('authorize/', AuthorizeView.as_view(), name='authorize'), ]

Assim, a primeira parte do AuthorizeView será:

 # 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/')'''

E então a segunda parte:

 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('/')

Portanto, se não houver credencial ou a credencial for inválida, gere uma e redirecione-a para a URL de autorização. Caso contrário, basta ir à página inicial para que possamos enviar um vídeo!

Vamos acessar a view agora e ver o que acontece:

Erro de autorização

Vamos criar um usuário então, antes de ir para essa página.

 (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.

Vamos também fazer login com ele via /admin . Após, vamos acessar nossa visão /authorize/ novamente.

Vamos fazer login

Autorização

Então,

Erro 404

OK, ele tentou redirecionar para o URL de retorno de chamada que configuramos há muito tempo com o Google. Agora precisamos implementar a visualização de retorno de chamada.

Vamos adicionar mais uma entrada ao nosso core/urls.py:

 # core/urls.py from .views import AuthorizeView, HomePageView, Oauth2CallbackView urlpatterns = [ # [...] path('oauth2callback/', Oauth2CallbackView.as_view(), name='oauth2callback') ]

E outra visão:

 # 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('/')

Nota: O fluxo foi movido para fora do AuthorizeView, tornando-se global. Idealmente, você deve criá-lo no AuthorizeView e salvá-lo em um cache e recuperá-lo no retorno de chamada. Mas isso está fora do escopo deste post.

O método get de AuthorizeView agora é:

 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('/')

Você pode dar uma olhada em implementações semelhantes aqui. O próprio pacote oauth2client fornece visualizações, mas eu particularmente prefiro implementar minha visualização Oauth personalizada.

  • 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

Agora, se você tentar a URL /authorize/ novamente, o fluxo OAuth deverá funcionar. Hora de ver se esse trabalho vale a pena e fazer upload do nosso vídeo! O HomePageView primeiro verificará as credenciais e, se estiver tudo certo, estaremos prontos para enviar nosso vídeo.

Vamos verificar como ficará nosso novo código para o 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!')

E o novo modelo:

 {# 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>

Não se esqueça de adicionar o campo de vídeo ao YouTubeForm:

 class YouTubeForm(forms.Form): video = forms.FileField()

Aqui vamos nós!

Carregar formulário

E então, verificando na página do Studio da sua conta do YouTube (é importante ter um canal):

Vídeo enviado

Voilá!

Notas de encerramento

O código precisa de algumas melhorias, mas é um bom ponto de partida. Espero que tenha ajudado com a maioria dos problemas de integração da API do YouTube do Google. Aqui estão mais algumas coisas importantes a serem observadas:

  • Para autorização, é importante exigir login e permissões extras para o usuário que autorizará seu aplicativo a enviar vídeos.
  • A variável de fluxo precisa ser removida de ser global. Não é seguro em um ambiente de produção. É melhor armazenar em cache com base no ID do usuário ou na sessão que acessou a primeira visualização, por exemplo.
  • O Google só fornece um token de atualização quando você faz a primeira autorização. Portanto, depois de algum tempo, principalmente uma hora, seu token expirará e, se você não interagir com a API, começará a receber respostas invalid_grant . Reautorizar o mesmo usuário que já autorizou um cliente não garantirá seu token de atualização. Você precisa revogar o aplicativo na página de suas Contas do Google e, em seguida, fazer o processo de autorização novamente. Em alguns casos, pode ser necessário executar uma tarefa para continuar atualizando o token.
  • Precisamos exigir login em nossa visão, pois estamos usando uma credencial de usuário diretamente relacionada à solicitação.

Erro FlowExchange

O upload leva muito tempo, e fazê-lo no processo principal do aplicativo pode fazer com que todo o aplicativo bloqueie enquanto o upload acontece. O caminho certo seria movê-lo para seu próprio processo e lidar com uploads de forma assíncrona.

Confuso? Não seja, leia mais em Orchestrating a Background Job Workflow in Celery for Python .