Integracja z YouTube API: przesyłanie filmów za pomocą Django

Opublikowany: 2022-03-11

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

Utwórz nowy projekt

Krok 3. Kliknij „Włącz interfejsy API i usługi”.

Włącz interfejsy API i usługi.

Krok 4. Poszukaj interfejsu API danych YouTube w wersji 3 i kliknij „Włącz”.

Poszukaj interfejsu API danych YouTube w wersji 3 i kliknij „Włącz”.

Krok 5. Powinieneś otrzymać wiadomość o poświadczeniach.

wiadomość o poświadczeniach

Krok 6. Kliknij niebieski przycisk „Utwórz poświadczenia” po prawej stronie, a powinieneś otrzymać następujący ekran:

Kliknij niebieski przycisk „Utwórz dane uwierzytelniające”

Krok 7. Wybierz serwer WWW, Dane użytkownika:

Wybierz serwer WWW, Dane użytkownika

Krok 8. Dodaj autoryzowane źródła JS i przekieruj identyfikatory URI. Przejdź do końca:

Dodaj autoryzowane źródła JS i identyfikatory URI przekierowań.

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:

Podgląd strony

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:

Błąd autoryzacji

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

Zalogujmy się

Upoważnienie

Następnie,

Błąd 404

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!

Prześlij formularz

Następnie sprawdź na stronie swojego konta YouTube Studio (ważne, aby mieć kanał):

Przesłany film

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.

Błąd FlowExchange

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 .