Integracja z YouTube API: przesyłanie filmów za pomocą Django
Opublikowany: 2022-03-11Niedawno pracowałem dla klienta, integrując recenzje wideo na jego stronie internetowej. Jak każdy zmotywowany programista rozwiązujący nowatorski problem, pierwszą rzeczą, którą zrobiłem, było Google, i znalazłem mnóstwo nieprzydatnych lub błędnych odpowiedzi na temat tego, jak osiągnąć coś zupełnie innego lub przestarzałego i nieutrzymywanego pakietu Pythona. W końcu ugryzłem kulkę i zespół i zbudowałem wszystko od podstaw: stworzyliśmy widoki, dowiedzieliśmy się o API Google, stworzyliśmy klienta API i ostatecznie udało nam się programowo przesłać filmy z Django.
W tym poście postaram się poprowadzić Cię krok po kroku, jak publikować filmy na YouTube z Twojej aplikacji Django. Będzie to wymagało trochę zabawy z danymi uwierzytelniającymi Google API — najpierw z interfejsem internetowym, a następnie z kodem. Sama część YouTube jest bardzo prosta. Musimy zrozumieć, jak działa Google, ponieważ czasami jest to trudne, a informacje są rozpowszechniane w wielu miejscach.
Warunki wstępne
Zalecam zapoznanie się z poniższymi informacjami przed rozpoczęciem pracy:
- YouTube Data API: Szybki start w Pythonie
- Interfejs API danych YouTube: odniesienie do interfejsu API
- YouTube Data API: przykłady kodu
- Biblioteka klienta Google Python API
- Biblioteka klienta interfejsu API Google Python: dokument referencyjny
- Biblioteka klienta Google Python API: przykłady kodu
- YouTube API: przykłady kodu w Pythonie
Ciekawym fragmentem kodu, na który warto zwrócić uwagę, jest następujący fragment kodu Pythona z 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')
Pierwsze kroki
Po przeczytaniu wymagań wstępnych czas zacząć. Zobaczmy, czego potrzebujemy.
Pas z narzędziami
Zasadniczo stwórzmy wirtualne środowisko. Osobiście wolę pyenv. Konfiguracja obu jest poza zakresem tego postu, więc opublikuję kilka poleceń pyenv poniżej i, jeśli wolisz virtualenv
, możesz odpowiednio je zastąpić.
W tym poście zamierzam używać Pythona 3.7 i 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
Umieśćmy to w treści (tylko jeśli używasz pyenv, więc aktywuje się automatycznie po wejściu do folderu):
djangoyt
Instalowanie zależności:
➜ ~/projects/django-youtube $ pip install google-api-python-client google-auth\ google-auth-oauthlib google-auth-httplib2 oauth2client Django unipath jsonpickle
Teraz czas na rozpoczęcie naszego projektu django:
➜ ~/projects/django-youtube $ django-admin startproject django_youtube .
Wstrzymaj dla niektórych konfiguracji Google
Skonfigurujmy teraz poświadczenia naszego projektu, abyśmy mogli korzystać z interfejsów Google API.
Krok 1. Przejdź do następującego adresu URL:
https://console.developers.google.com/apis/library/youtube.googleapis.com
Krok 2. Utwórz nowy projekt.
Krok 3. Kliknij „Włącz interfejsy API i usługi”.
Krok 4. Poszukaj interfejsu API danych YouTube w wersji 3 i kliknij „Włącz”.
Krok 5. Powinieneś otrzymać wiadomość o poświadczeniach.
Krok 6. Kliknij niebieski przycisk „Utwórz poświadczenia” po prawej stronie, a powinieneś otrzymać następujący ekran:
Krok 7. Wybierz serwer WWW, Dane użytkownika:
Krok 8. Dodaj autoryzowane źródła JS i przekieruj identyfikatory URI. Przejdź do końca:
OK, skończyliśmy z konfiguracją naszych poświadczeń. Możesz pobrać dane uwierzytelniające w formacie JSON lub skopiować identyfikator klienta i klucz tajny klienta .
Powrót do Django
Zacznijmy naszą pierwszą aplikację Django. Zwykle nazywam to „rdzeniem”:
(djangoyt) ➜ ~/projects/django-youtube $ python manage.py startapp core
Teraz dodajmy następujące elementy do naszego głównego pliku urls.py, aby przekierować żądania strony głównej do naszej podstawowej aplikacji:
# <root>/urls.py from django.urls import path, include path('', include(('core.urls', 'core'), namespace='core')),
W podstawowej aplikacji przygotujmy kolejny plik urls.py, z pewną konfiguracją:
# 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)
Zobacz, że istnieje pusta ścieżka wskazująca na HomePageView
. Czas dodać trochę kodu.
Zróbmy teraz prosty TemplateView
, aby zobaczyć, jak działa.
# core/views.py from django.shortcuts import render from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'core/home.html'
I oczywiście potrzebujemy podstawowego szablonu:
# core/templates/core/home.html <!DOCTYPE html> <html> <body> <h1>My First Heading</h1> <p>My first paragraph.</p> </body> </html>
Musimy wprowadzić kilka poprawek w ustawieniach:
# 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/'
Utwórzmy teraz YoutubeForm
i dodajmy go jako form_class
do widoku:
# 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
Spróbuj teraz uruchomić swoją aplikację, a strona będzie wyglądać tak:
Wstrzymaj, aby wykonać autoryzację
Przede wszystkim musisz stworzyć model do przechowywania twoich danych uwierzytelniających. Możesz skorzystać z systemu plików, pamięci podręcznej lub dowolnego innego rozwiązania do przechowywania, ale baza danych wydaje się rozsądna i skalowalna, a także możesz przechowywać poświadczenia dla użytkowników, jeśli chcesz.
Zanim przejdziemy dalej, należy dokonać korekty — musimy użyć rozwidlenia oauth2client obsługującego Django 2.1. Wkrótce otrzymamy oficjalne wsparcie, ale w międzyczasie możesz sprawdzić zmiany w widelcu. Są bardzo proste.
pip install -e git://github.com/Schweigi/[email protected]#egg=oauth2client Because of compatibility with Django 2.1
Przejdź do pliku settings.py
i umieść identyfikator klienta i tajny klucz klienta otrzymany od Google w poprzednich krokach.
# settings.py GOOGLE_OAUTH2_CLIENT_ GOOGLE_OAUTH2_CLIENT_SECRET = '<your client secret>'
Uwaga: przechowywanie sekretów w kodzie nie jest zalecane. Robię to po prostu jako demonstrację. Zalecam używanie zmiennych środowiskowych w aplikacji produkcyjnej, a nie tajne kodowanie na sztywno w plikach aplikacji. Alternatywnie, jeśli pobrałeś plik JSON z Google, możesz również określić jego ścieżkę zamiast powyższych ustawień:
GOOGLE_OAUTH2_CLIENT_SECRETS_JSON = '/path/to/client_id.json'
Pakiet oauth2client zapewnia już mnóstwo funkcji, z gotowym polem CredentialsField
, którego możemy użyć. Możliwe jest dodanie większej liczby pól, takich jak klucz obcy i daty utworzenia/modyfikowania, dzięki czemu uzyskamy większą niezawodność, ale pozostańmy prości.
Prosty model do przechowywania poświadczeń:

# core/models.py from django.db import models from oauth2client.contrib.django_util.models import CredentialsField class CredentialsModel(models.Model): credential = CredentialsField()
Czas na tworzenie migracji i migrację:
(djangoyt) ➜ ~/projects/django-youtube $ ./manage.py makemigrations core (djangoyt) ➜ ~/projects/django-youtube $ ./manage.py migrate
Teraz zmieńmy nasze widoki API, aby móc autoryzować naszą aplikację:
W naszym pliku core/urls.py
dodajmy kolejny wpis dla pierwszego widoku autoryzacji:
# core/urls.py from .views import AuthorizeView, HomePageView urlpatterns = [ # [...] path('authorize/', AuthorizeView.as_view(), name='authorize'), ]
Tak więc pierwszą częścią AuthorizeView będzie:
# 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/')'''
A potem druga część:
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('/')
Jeśli więc nie ma poświadczeń lub poświadczenia są nieprawidłowe, wygeneruj je, a następnie przekieruj je na adres URL autoryzacji. W przeciwnym razie po prostu przejdź do strony głównej, abyśmy mogli przesłać film!
Przejdźmy teraz do widoku i zobaczmy, co się stanie:
Stwórzmy zatem użytkownika, zanim przejdziemy do tej strony.
(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.
Zalogujmy się też za pomocą /admin
. Następnie ponownie przejdźmy do naszego widoku /authorize/
.
Następnie,
OK, próbował przekierować do adresu URL wywołania zwrotnego, który skonfigurowaliśmy dawno temu w Google. Teraz musimy zaimplementować widok wywołań zwrotnych.
Dodajmy jeszcze jeden wpis do naszego core/urls.py:
# core/urls.py from .views import AuthorizeView, HomePageView, Oauth2CallbackView urlpatterns = [ # [...] path('oauth2callback/', Oauth2CallbackView.as_view(), name='oauth2callback') ]
I inny widok:
# 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('/')
Uwaga: przepływ został przeniesiony poza AuthorizeView, stając się globalnym. Najlepiej byłoby utworzyć go pod AuthorizeView i zapisać w pamięci podręcznej, a następnie pobrać w wywołaniu zwrotnym. Ale to wykracza poza zakres tego postu.
Metoda get AuthorizeView to teraz:
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('/')
Możesz przyjrzeć się podobnym realizacjom tutaj. Sam pakiet oauth2client
udostępnia widoki, ale szczególnie wolę zaimplementować mój niestandardowy widok 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
Teraz, jeśli spróbujesz ponownie użyć adresu URL /authorize/
, przepływ OAuth powinien działać. Czas sprawdzić, czy ta praca jest tego warta i przesłać nasz film! HomePageView
najpierw sprawdzi poświadczenia i jeśli wszystko jest w porządku, jesteśmy gotowi do przesłania naszego filmu.
Sprawdźmy jak będzie wyglądał nasz nowy kod dla 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!')
I nowy szablon:
{# 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>
Nie zapomnij dodać pola wideo do formularza YouTube:
class YouTubeForm(forms.Form): video = forms.FileField()
No to ruszamy!
Następnie sprawdź na stronie swojego konta YouTube Studio (ważne, aby mieć kanał):
Voila!
Uwagi końcowe
Kod wymaga poprawy, ale to dobry punkt wyjścia. Mam nadzieję, że pomogło to w większości problemów z integracją interfejsu API YouTube Google. Oto kilka ważnych rzeczy, o których należy pamiętać:
- W celu autoryzacji ważne jest, aby wymagać od użytkownika loginu i dodatkowych uprawnień, które upoważnią Twoją aplikację do przesyłania filmów.
- Zmienna przepływu musi zostać usunięta z globalnego. Nie jest bezpieczny w środowisku produkcyjnym. Lepiej jest buforować na przykład na podstawie identyfikatora użytkownika lub sesji, która uzyskała dostęp do pierwszego widoku.
- Google udostępnia token odświeżania tylko podczas pierwszej autoryzacji. Tak więc po pewnym czasie, przeważnie jednej godzinie, Twój token wygaśnie i jeśli nie wejdziesz w interakcję z ich interfejsem API, zaczniesz otrzymywać odpowiedzi
invalid_grant
. Ponowna autoryzacja tego samego użytkownika, który już autoryzował klienta, nie gwarantuje Twojego tokena odświeżania. Musisz cofnąć wniosek na stronie Konta Google, a następnie ponownie przeprowadzić proces autoryzacji. W niektórych przypadkach może być konieczne uruchomienie zadania, aby nadal odświeżać token. - Z naszego punktu widzenia musimy wymagać logowania, ponieważ używamy danych uwierzytelniających użytkownika bezpośrednio związanych z żądaniem.
Przesyłanie zajmuje dużo czasu, a wykonanie tego w głównym procesie aplikacji może spowodować zablokowanie całej aplikacji podczas przesyłania. Właściwym sposobem byłoby przeniesienie go do własnego procesu i asynchroniczna obsługa przesyłania.
Zdezorientowany? Nie bądź, przeczytaj więcej w Orkiestrowaniu przepływu pracy w tle w Seler dla Pythona .