Integración de la API de YouTube: carga de videos con Django
Publicado: 2022-03-11Hace poco tiempo, estaba trabajando para un cliente, integrando reseñas de videos en su sitio web. Como cualquier desarrollador motivado que resuelve un problema novedoso, lo primero que hice fue buscarlo en Google y encontré una plétora de respuestas inútiles o equivocadas sobre cómo lograr algo completamente diferente, o paquetes de Python obsoletos y sin mantenimiento. Eventualmente, mordí la bala y el equipo y yo construimos todo desde cero: creamos las vistas, aprendimos sobre la API de Google, creamos el cliente API y finalmente logramos cargar videos de Django mediante programación.
En esta publicación, intentaré guiarlo paso a paso sobre cómo publicar videos de YouTube desde su aplicación Django. Esto requerirá un poco de juego con las credenciales de la API de Google, primero con la interfaz web y luego con el código. La parte de YouTube en sí es muy sencilla. Necesitamos entender cómo funcionan las cosas de Google porque a veces es complicado y la información se difunde por muchos lugares.
requisitos previos
Recomiendo familiarizarse con lo siguiente antes de comenzar a trabajar:
- API de datos de YouTube: inicio rápido de Python
- API de datos de YouTube: referencia de la API
- API de datos de YouTube: ejemplos de código
- Biblioteca de cliente de la API de Google Python
- Biblioteca cliente de la API de Google Python: documento de referencia
- Biblioteca cliente de la API de Google Python: ejemplos de código
- API de YouTube: ejemplos de código de Python
Un fragmento de código interesante a tener en cuenta es el siguiente fragmento de código de Python de 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')
Empezando
Después de leer los requisitos previos, es hora de comenzar. Veamos qué necesitamos.
Cinturón para herramientas
Básicamente, vamos a crear un entorno virtual. Yo personalmente prefiero pyenv. La configuración de ambos está fuera del alcance de esta publicación, por lo que publicaré algunos comandos pyenv a continuación y, si su preferencia es virtualenv
, siéntase libre de reemplazar los comandos en consecuencia.
Voy a usar Python 3.7 y Django 2.1 en esta publicación.
➜ ~/projects $ mkdir django-youtube ➜ ~/projects $ cd django-youtube ➜ ~/projects/django-youtube $ pyenv virtualenv 3.7.0 djangoyt ➜ ~/projects/django-youtube $ vim .python-version
Pongamos esto en el contenido (solo si usa pyenv, para que se active automáticamente cuando ingrese a la carpeta):
djangoyt
Instalación de dependencias:
➜ ~/projects/django-youtube $ pip install google-api-python-client google-auth\ google-auth-oauthlib google-auth-httplib2 oauth2client Django unipath jsonpickle
Ahora es el momento de comenzar nuestro proyecto django:
➜ ~/projects/django-youtube $ django-admin startproject django_youtube .
Pausa para alguna configuración de Google
Configuremos las credenciales de nuestro proyecto ahora para que podamos usar las API de Google.
Paso 1. Ve a la siguiente URL:
https://console.developers.google.com/apis/library/youtube.googleapis.com
Paso 2. Crea un nuevo proyecto.
Paso 3. Haga clic en "Habilitar API y servicios".
Paso 4. Busque YouTube Data API v3 y haga clic en "Habilitar".
Paso 5. Debería recibir un mensaje sobre las credenciales.
Paso 6. Haga clic en el botón azul "Crear credenciales" en el lado derecho, y debería obtener la siguiente pantalla:
Paso 7. Elija servidor web, datos de usuario:
Paso 8. Agregar orígenes JS autorizados y redirigir URI. Continuar hasta el final:
Bien, hemos terminado con la configuración de nuestras credenciales. Puede descargar las credenciales en formato JSON o copiar el ID del cliente y el secreto del cliente .
Volver a Django
Comencemos nuestra primera aplicación Django. Normalmente lo llamo "núcleo":
(djangoyt) ➜ ~/projects/django-youtube $ python manage.py startapp core
Ahora, agreguemos lo siguiente a nuestro archivo raíz urls.py para enrutar las solicitudes de la página de inicio a nuestra aplicación principal:
# <root>/urls.py from django.urls import path, include path('', include(('core.urls', 'core'), namespace='core')),
En la aplicación principal, tengamos otro archivo urls.py, con alguna configuración también:
# 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)
Vea que hay una ruta vacía que apunta a HomePageView
. Es hora de agregar algo de código.
Hagamos ahora un TemplateView
simple solo para verlo en ejecución.
# core/views.py from django.shortcuts import render from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'core/home.html'
Y, por supuesto, necesitamos una plantilla básica:
# core/templates/core/home.html <!DOCTYPE html> <html> <body> <h1>My First Heading</h1> <p>My first paragraph.</p> </body> </html>
Necesitamos hacer algunos ajustes de configuración:
# 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 a crear ahora un YoutubeForm
y agregarlo como form_class
a la vista:
# 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
Intente ejecutar su aplicación ahora, y la página se verá así:
Pausa para hacer la autorización
En primer lugar, debe crear un modelo para almacenar sus credenciales. Puede hacerlo a través de un archivo, un sistema de caché o cualquier otra solución de almacenamiento, pero una base de datos parece razonable y escalable, y también puede almacenar credenciales por usuario si lo desea.
Antes de continuar, se debe realizar un ajuste: hay una bifurcación de oauth2client que admite Django 2.1 que debemos usar. Pronto tendremos soporte oficial, pero mientras tanto, puedes revisar los cambios en la bifurcación. Son muy simples.
pip install -e git://github.com/Schweigi/[email protected]#egg=oauth2client Because of compatibility with Django 2.1
Vaya a su settings.py
y coloque la identificación del cliente y el secreto del cliente que obtuvo de Google en los pasos anteriores.
# settings.py GOOGLE_OAUTH2_CLIENT_ GOOGLE_OAUTH2_CLIENT_SECRET = '<your client secret>'
Precaución: no se recomienda almacenar secretos en su código. Estoy haciendo esto simplemente como una demostración. Recomiendo usar variables de entorno en su aplicación de producción y no codificar secretos en los archivos de la aplicación. Alternativamente, si descargó el JSON de Google, también puede especificar su ruta en lugar de la configuración anterior:
GOOGLE_OAUTH2_CLIENT_SECRETS_JSON = '/path/to/client_id.json'
El paquete oauth2client ya proporciona muchas funciones, con un CredentialsField
ya hecho que podemos usar. Es posible agregar más campos, como una clave externa y fechas de creación/modificación para que seamos más sólidos, pero seamos simples.

Modelo simple para almacenar credenciales:
# core/models.py from django.db import models from oauth2client.contrib.django_util.models import CredentialsField class CredentialsModel(models.Model): credential = CredentialsField()
Hora de crear migraciones y migrar:
(djangoyt) ➜ ~/projects/django-youtube $ ./manage.py makemigrations core (djangoyt) ➜ ~/projects/django-youtube $ ./manage.py migrate
Ahora cambiemos nuestras vistas de API para poder autorizar nuestra aplicación:
En nuestro archivo core/urls.py
, agreguemos otra entrada para la primera vista de autorización:
# core/urls.py from .views import AuthorizeView, HomePageView urlpatterns = [ # [...] path('authorize/', AuthorizeView.as_view(), name='authorize'), ]
Entonces, la primera parte de 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/')'''
Y luego la 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('/')
Entonces, si no hay una credencial o la credencial no es válida, genere una y luego rediríjala a la URL de autorización. De lo contrario, solo ve a la página de inicio para que podamos subir un video.
Accedamos ahora a la vista y veamos qué sucede:
Vamos a crear un usuario entonces, antes de ir a esa 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.
Iniciemos sesión también con él a través de /admin
. Después, accedamos a nuestra vista /authorize/
nuevamente.
Luego,
Bien, intentó redirigir a la URL de devolución de llamada que configuramos hace mucho tiempo con Google. Ahora necesitamos implementar la vista de devolución de llamada.
Agreguemos una entrada más a nuestro core/urls.py:
# core/urls.py from .views import AuthorizeView, HomePageView, Oauth2CallbackView urlpatterns = [ # [...] path('oauth2callback/', Oauth2CallbackView.as_view(), name='oauth2callback') ]
Y otra vista:
# 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: El flujo se movió fuera de AuthorizeView, convirtiéndose en global. Idealmente, debe crearlo en AuthorizeView y guardarlo en un caché, luego recuperarlo en la devolución de llamada. Pero eso está fuera del alcance de esta publicación.
El método get de AuthorizeView ahora es:
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('/')
Puede echar un vistazo a implementaciones similares aquí. El paquete oauth2client
en sí proporciona vistas, pero en particular prefiero implementar mi vista personalizada de 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
Ahora, si vuelve a intentar la URL /authorize/
, el flujo de OAuth debería funcionar. ¡Es hora de ver si este trabajo vale la pena y subir nuestro video! El HomePageView
primero verificará las credenciales y, si todo está bien, estamos listos para cargar nuestro video.
Veamos cómo se verá nuestro nuevo código para 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!')
Y la nueva plantilla:
{# 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>
No olvide agregar el campo de video a YouTubeForm:
class YouTubeForm(forms.Form): video = forms.FileField()
¡Aquí vamos!
Y luego, revisando la página de Studio de tu cuenta de YouTube (es importante tener un canal):
¡Voila!
Notas de cierre
El código necesita algunas mejoras, pero es un buen punto de partida. Espero que haya ayudado con la mayoría de los problemas de integración de la API de YouTube de Google. Aquí hay algunas cosas más importantes a tener en cuenta:
- Para la autorización, es importante solicitar el inicio de sesión y permisos adicionales para el usuario que autorizará su aplicación para cargar videos.
- La variable de flujo debe dejar de ser global. No es seguro en un entorno de producción. Es mejor almacenar en caché según el ID de usuario o la sesión que accedió a la primera vista, por ejemplo.
- Google solo proporciona un token de actualización cuando realiza la primera autorización. Entonces, después de un tiempo, principalmente una hora, su token caducará y, si no interactuó con su API, comenzará a recibir respuestas
invalid_grant
. Volver a autorizar al mismo usuario que ya autorizó a un cliente no garantizará su token de actualización. Debe revocar la solicitud en su página de Cuentas de Google y luego realizar el proceso de autorización nuevamente. En algunos casos, es posible que deba ejecutar una tarea para seguir actualizando el token. - Necesitamos solicitar el inicio de sesión en nuestra vista, ya que estamos utilizando una credencial de usuario directamente relacionada con la solicitud.
La carga lleva mucho tiempo, y hacerlo en el proceso principal de la aplicación puede hacer que toda la aplicación se bloquee mientras se realiza la carga. La forma correcta sería moverlo a su propio proceso y manejar las cargas de forma asincrónica.
¿Confundido? No se preocupe, lea más en Orquestar un flujo de trabajo de trabajo en segundo plano en Celery para Python .