Статьи

Загрузка видео на YouTube с помощью Rails

Значок приложения Media Player

В моих предыдущих статьях я уже объяснял, как интегрировать загрузку в ваше приложение и как сделать процесс загрузки асинхронным (а также загружать несколько файлов). Сегодня мы сделаем еще один шаг вперед и создадим приложение, которое позволит пользователям загружать видео на YouTube.

Мы будем использовать протокол OAuth2, а также гем youtube_it для работы с API YouTube (если вы хотите узнать больше о YT API, посмотрите мою статью, YouTube on Rails ). В конце я расскажу о некоторых потенциальных ошибках, с которыми вы можете столкнуться (поверьте, некоторые из них довольно раздражающие).

Исходный код демонстрационного приложения доступен на GitHub .

Работающую демонстрацию можно найти по адресу http://sitepoint-yt-uploader.herokuapp.com/ .

Давайте начнем!

Препараты

Rails 4.1.4 будет использоваться для этой демонстрации, но такое же решение может быть реализовано с Rails 3.

Прежде чем углубиться в код, давайте на секунду остановимся и поговорим о приложении, которое мы собираемся создать. Пользовательский сценарий прост:

  1. Пользователь заходит на главную страницу и видит список уже загруженных видео вместе с большой кнопкой, побуждающей пользователя загрузить видео.

  2. Пользователь (надеюсь) нажимает эту кнопку и переходит на другую страницу, где его просят пройти аутентификацию (мы поговорим об аутентификации более подробно позже).

  3. После успешной аутентификации появляется форма, позволяющая пользователю выбрать видео со своего жесткого диска. Пользователь также может предоставить название и описание для видео.

  4. После отправки формы видео загружается на YouTube. Наше приложение получает уникальный идентификатор видео и сохраняет его в базе данных (этот уникальный идентификатор будет использоваться для отображения видео на главной странице).

  5. Пользователь перенаправляется на главную страницу, где он видит недавно загруженное видео.

По сути, мы должны решить три задачи:
* Выполните некоторые наземные работы (создание контроллеров, моделей, представлений, базовый дизайн)
* Создать какую-то аутентификацию
* Реализовать механизм загрузки видео.

Начнем с земляных работ. Создайте новое приложение Rails без набора тестов по умолчанию:

 $ rails new yt_uploder -T 

Мы собираемся использовать Twitter Bootstrap для создания нашего базового дизайна, поэтому поместите этот драгоценный камень в свой Gemfile :

 [...] gem 'bootstrap-sass' [...] 

Затем переименуйте application.css (в каталоге стилей ) в application.css.scss и измените его содержимое на:

 @import 'bootstrap'; @import 'bootstrap/theme'; 

Измените макет, чтобы использовать преимущества стилей Bootstrap:

макеты / application.html.erb

 [...] <body> <div class="navbar navbar-inverse"> <div class="container"> <div class="navbar-header"> <%= link_to 'YT Uploader', root_path, class: 'navbar-brand' %> </div> <ul class="nav navbar-nav"> <li><%= link_to 'Videos', root_path %></li> <li><%= link_to 'Add Video', new_video_path %></li> </ul> </div> </div> <div class="container"> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> <%= yield %> </div> </body> [...] 

Мы используем некоторые несуществующие маршруты, поэтому давайте исправим это:

routes.rb

 [...] resources :videos, only: [:new, :index] root to: 'videos#index' [...] 

Также создайте соответствующий контроллер (мы создадим его методы позже):

videos_controller.rb

 class VideosController < ApplicationController def new end def index end end 

Наконец, создайте представления (мы опишем их позже):

видео / new.html.erb

 <div class="page-header"> <h1>Upload Video</h1> </div> 

видео / index.html.erb

 <div class="jumbotron"> <h1>YouTube Uploader</h1> <p>Upload your video to YouTube and share it with the world!</p> <p><%= link_to 'Upload video now!', new_video_path, class: 'btn btn-primary btn-lg' %></p> </div> 

Хорошо, теперь у нас есть с чем поработать.

Аутентификация пользователя

На данный момент мы готовы подумать об аутентификации. Очевидно, чтобы иметь возможность загружать видео на канал пользователя, нам нужен доступ. Как мы можем сделать это?

Google API поддерживает протокол OAuth 2, который позволяет стороннему приложению получать ограниченный доступ к своим службам (подробнее о доступе к API Google с помощью OAuth 2 можно прочитать здесь ).

Крутая вещь в этом протоколе заключается в том, что пользователь не предоставляет стороннему приложению доступ ко всей своей учетной записи. Скорее, ему предоставляется доступ только к необходимым услугам (это контролируется областью ). Кроме того, стороннее приложение никогда не получает пароль пользователя — вместо этого он получает токен, который используется для выполнения вызовов API. Этот токен можно использовать только для выполнения действий, перечисленных в области доступа, и срок его службы ограничен. Более того, пользователь может вручную аннулировать доступ к любому ранее авторизованному стороннему приложению со страницы настроек учетной записи. OAuth 2 — очень популярный протокол, поддерживаемый различными веб-сайтами (такими как Twitter, Facebook, Vkontakte и многими другими).

Для Rails есть гем omniauth-google-oauth2, созданный Джошем Эллиторпом. Он представляет стратегию аутентификации в Google с использованием OAuth2 и OmniAuth. OmniAuth , в свою очередь, представляет собой библиотеку, которая стандартизирует многопрофильную аутентификацию для веб-приложений, созданных Michael Bleigh и Intridea Inc. OmniAuth позволяет использовать столько разных стратегий аутентификации, сколько вы хотите, чтобы пользователи могли проходить аутентификацию через Google, Facebook, Twitter, и т.д. Вам нужно только подключить соответствующую стратегию (есть множество доступных) или создать свою собственную .

Хорошо, как мы все это соберем? На самом деле, это довольно просто. Прежде всего, добавьте необходимый драгоценный камень в Gemfile :

Gemfile

 [...] gem 'omniauth-google-oauth2' [...] 

Теперь создайте пару маршрутов:

routes.rb

 get '/auth/:provider/callback', to: 'sessions#create' get '/auth/failure', to: 'sessions#fail' 

Маршрут /auth/:provider/callback — это обратный вызов, куда пользователь перенаправляется после успешного завершения.
аутентификация. Часть /auth определяется OmniAuth. В нашем случае :provider является google_oauth2 (как указано в документации по omniauth-google-oauth2 ). В приложении с множеством разных стратегий :provider будет принимать другие значения (например, twitter или facebook ). /auth/failure , как вы, наверное, догадались, используется при возникновении ошибки на этапе аутентификации.

Теперь настройте актуальную стратегию OmniAuth:

Инициализаторы / omniauth.rb

 Rails.application.config.middleware.use OmniAuth::Builder do provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], scope: 'userinfo.profile,youtube' end 

Мы используем стратегию google_oauth2 здесь. Идентификатор клиента и секрет клиента можно получить, зарегистрировав ваше приложение с помощью консоли Google. Посетите (console.developers.google.com) [https://console.developers.google.com/], нажмите «Создать проект» и введите имя своего проекта. После того, как он создан, откройте страницу «Экран согласия» (из левого меню) и заполните «Название продукта» (я назвал его «Sitepoint YT Upload Demo») — это то, что увидят ваши пользователи при аутентификации через приложение. Все остальные поля являются необязательными. Обязательно нажмите «Сохранить».

Теперь откройте страницу «APIs». Здесь вы можете настроить API, к которым ваше приложение получит доступ. Включите Google+ API и API данных YouTube v3. Откройте страницу «Учетные данные» и нажмите кнопку «Создать новый идентификатор клиента». Во всплывающем окне выберите «Веб-приложение». В поле «Авторизованные источники JavaScript» введите URL своего приложения (для этой демонстрации я ввел «http://sitepoint-yt-uploader.herokuapp.com»).

В «URI авторизованного перенаправления» введите URL своего приложения плюс /auth/google_oauth2/callback (помните наш файл маршрутов и маршрут обратного вызова /auth/:provider/callback ?). Для этой демонстрации я ввел http://sitepoint-yt-uploader.herokuapp.com/auth/google_oauth2/callback . Нажмите «Создать идентификатор клиента», и вы увидите вновь созданный идентификатор клиента для веб-приложения. Здесь обратите внимание на два значения: идентификатор клиента и секрет клиента. Это значения, которые нужно вставить в инициализатор omniauth.rb .

Как насчет scope ? Как я уже упоминал, scope содержит список служб, которые требуются приложению. Весь список возможных значений может быть на Google OAuth 2 Playground . Есть и другие опции, которые можно передать методу provider . Весь список доступен на странице omniauth-google-oauth2 .

Последнее, что мы здесь сделаем, это перехватим любые ошибки, которые происходят во время аутентификации, и перенаправим пользователя на ранее созданный маршрут /auth/failure :

Инициализаторы / omniauth.rb

 [...] OmniAuth.config.on_failure do |env| error_type = env['omniauth.error.type'] new_path = "#{env['SCRIPT_NAME']}#{OmniAuth.config.path_prefix}/failure?message=#{error_type}" [301, {'Location' => new_path, 'Content-Type' => 'text/html'}, []] end [...] 

Фу, мы здесь закончили!

Предоставьте пользователям актуальную ссылку для аутентификации:

видео / new.html.erb

 <div class="page-header"> <h1>Upload Video</h1> </div> <p>Please <%= link_to 'authorize', '/auth/google_oauth2' %> via your Google account to continue.</p> 

После нажатия на эту ссылку пользователь будет перенаправлен на страницу аутентификации Google для входа в систему. Google рассмотрит список разрешений, которые запрашивает наше приложение (в нашей демонстрационной версии эти разрешения будут иметь доступ к основной информации учетной записи и управлению учетной записью YouTube).

После утверждения этого запроса пользователь перенаправляется обратно в наше приложение (на маршрут обратного вызова, который мы настроили в консоли Google). Наш метод обратного вызова также получает хэш-данные аутентификации, которые могут содержать различную информацию в зависимости от стратегии и запрошенных разрешений. В нашем случае он содержит имя пользователя, фотографию, URL-адрес профиля, токен и некоторые другие данные (посмотрите здесь примерный хэш аутентификации).

Нам нужен контроллер для обработки этого обратного вызова:

sessions_controller.rb

 class SessionsController < ApplicationController def create auth = request.env['omniauth.auth'] user = User.find_or_initialize_by(uid: auth['uid']) user.token = auth['credentials']['token'] user.name = auth['info']['name'] user.save session[:user_id] = user.id flash[:success] = "Welcome, #{user.name}!" redirect_to new_video_path end def fail render text: "Sorry, but the following error has occured: #{params[:message]}. Please try again or contact administrator." end end 

request.env['omniauth.auth'] содержит хеш аутентификации. Три значения, которые нам нужны, — это уникальный идентификатор пользователя (чтобы мы могли найти этого пользователя позже в таблице, когда он еще раз аутентифицируется), полное имя пользователя и, что самое важное, токен для отправки запросов в Google API. Кроме того, мы храним идентификатор пользователя в сеансе, чтобы аутентифицировать его в нашем приложении.

Нам также нужен метод для проверки подлинности пользователя:

application_controller.rb

 [...] private def current_user @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id] end helper_method :current_user [...] 

Этот метод теперь можно использовать в представлении:

видео / new.html.erb

 <div class="page-header"> <h1>Upload Video</h1> </div> <% if current_user %> <%# TODO: create form partial %> <% else %> <p>Please <%= link_to 'authorize', '/auth/google_oauth2' %> via your Google account to continue.</p> <% end %> 

Мы еще не создали модель User , поэтому давайте сделаем это сейчас:

 $ rails g model User name:string token:string uid:string $ rake db:migrate 

Большой! На этом этапе наши пользователи могут проходить аутентификацию через Google и предоставлять нашему приложению токен доступа для выдачи запросов API. Следующим шагом является реализация функции загрузки видео.

Загрузка видео на YouTube

Для начала нам нужна модель Video . Он будет содержать следующие поля (не говоря уже об id по умолчанию,
created_at и updated_at ):

  • title ( string ) — предоставляется пользователем
  • description ( text ) — предоставляется пользователем
  • uid ( string ) — будет содержать уникальный идентификатор видео, о котором мы говорили ранее. Этот идентификатор генерируется YouTube при загрузке видео. Мы будем использовать этот идентификатор для получения некоторой информации через API YouTube, а также для создания ссылки.
  • user_id ( integer ) — ключевое поле, чтобы связать видео с пользователем (один пользователь может иметь много видео).

Создайте миграцию и примените ее:

 $ rails g model Video uid:string title:string description:text user:references $ rake db:migrate 

Не забудьте установить отношение «один ко многим» на стороне пользователя:

модели / user.rb

 [...] has_many :videos [...] 

Теперь мы готовы перейти к форме видео:

видео / new.html.erb

 <div class="page-header"> <h1>Upload Video</h1> </div> <% if current_user %> <%= render 'form' %> <% else %> <p>Please <%= link_to 'authorize', '/auth/google_oauth2' %> via your Google account to continue.</p> <% end %> 

К сожалению, здесь все немного сложнее. Похоже, нам нужно создать не одну, а две формы, чтобы иметь возможность загружать видео на YouTube. Это почему?

API YouTube требует, чтобы мы сначала отправили видеоданные, такие как заголовок и описание, без фактического видеофайла. В ответ он отвечает специальным токеном загрузки и URL-адресом. Затем мы должны отправить еще одну форму с токеном и файлом по этому URL. Довольно грязно, не правда ли?

Конечно, мы могли бы попросить наших пользователей отправить две формы, но мы не собираемся этого делать. Пользователи не должны быть
надоела такая сложность. Давайте использовать немного AJAX вместо этого. Я буду действовать постепенно, чтобы вы поняли, что происходит.

Немного подправить контроллер:

videos_controller.rb

 [...] def new @pre_upload_info = {} end [...] 

Этот хэш @pre_upload_info будет хранить информацию о видео — заголовок и описание. Мы собираемся использовать его в первом виде.

Теперь создайте две формы в частичном:

видео / _form.html.erb

 <%= form_tag '', id: 'video_pre_upload' do %> <div class="form-group"> <%= text_field_tag :title, @pre_upload_info[:title], required: true, placeholder: 'Title', class: 'form-control' %> </div> <div class="form-group"> <%= text_area_tag :description, @pre_upload_info[:description], required: true, placeholder: 'Description', class: 'form-control' %> </div> <% end %> <%= form_tag '', id: 'video_upload', multipart: true do %> <%= hidden_field_tag :token, '' %> <div class="form-group"> <%= file_field_tag :file, required: true %> </div> <% end %> <button id="submit_pre_upload_form" class="btn btn-lg btn-primary">Upload</button> <%= image_tag 'ajax-loader.gif', class: 'preloader', alt: 'Uploading...', title: 'Uploading...' %> 

Как вы можете видеть, есть только одна кнопка, которую мы оснастим некоторым JavaScript в данный момент. Ни одна из форм не имеет URL-адреса действия — она ​​также будет исходить из JavaScript. Первая форма ( #video_pre_upload ) будет отправлена ​​первой для получения токена загрузки и URL-адреса для второй ( #video_upload ) формы.

Мы также используем образ загрузчика AJAX, сгенерированный из ajaxload.info/ или уже созданный ). Загрузчик не должен отображаться при загрузке страницы, поэтому примените этот стиль:

таблицы стилей / application.css.scss

 .preloader { display: none; } 

Мы также используем заполнители для входных данных. Они не поддерживаются старыми браузерами, поэтому вы можете использовать плагин jquery-placeholder от Mathias Bynens для обратной совместимости.

Также, если вы используете Turbolinks, возможно, стоит добавить гем jquery-turbolinks в ваше приложение.

Хорошо, теперь пришло время для некоторых jQuery:

видео / _form.html.erb

 <script> $(document).ready(function() { var submit_button = $('#submit_pre_upload_form'); var video_upload = $('#video_upload'); submit_button.click(function () { $.ajax({ type: 'POST', url: '<%= get_upload_token_path %>', data: $('#video_pre_upload').serialize(), dataType: 'json', success: function(response) { video_upload.find('#token').val(response.token); video_upload.attr('action', response.url.replace(/^http:/i, window.location.protocol)).submit(); submit_button.unbind('click').hide(); $('.preloader').css('display', 'block'); } }); }); }); </script> 

Мы привязываем обработчик события click к кнопке. Когда нажата эта кнопка, отправьте первую форму на маршрут get_upload_token (который мы создадим в ближайшее время), ожидая получить JSON в ответ.

После успешного ответа получите токен и URL и установите значения формы для #token и #video_upload соответственно. Также обратите внимание на response.url.replace(/^http:/i, window.location.protocol) . Это необязательно, но если к вашей странице можно получить доступ как по протоколам http и по протоколу https , вам понадобится этот фрагмент кода. Это связано с тем, что URL-адрес, возвращаемый API YouTube, использует http, и современные браузеры будут препятствовать отправке формы с использованием https .

После отправки формы спрячьте кнопку и покажите PacMan (загрузчик) пользователю. На данный момент мы закончили с представлением и можем перейти к маршрутам и контроллеру.

Создайте два новых маршрута:

routes.rb

 [...] post '/videos/get_upload_token', to: 'videos#get_upload_token', as: :get_upload_token get '/videos/get_video_uid', to: 'videos#get_video_uid', as: :get_video_uid [...] 

Первый — get_upload_token — используется в нашей части при отправке первой формы. Второй маршрут будет использован через мгновение.

На контроллере:

videos_controller.rb

 [...] def get_upload_token temp_params = { title: params[:title], description: params[:description], category: 'Education', keywords: [] } if current_user youtube = YouTubeIt::OAuth2Client.new(client_access_token: current_user.token, dev_key: ENV['GOOGLE_DEV_KEY']) upload_info = youtube.upload_token(temp_params, get_video_uid_url) render json: {token: upload_info[:token], url: upload_info[:url]} else render json: {error_type: 'Not authorized.', status: :unprocessable_entity} end end [...] 

Это метод для запроса токена загрузки и URL. Обратите внимание, что в temp_params мы можем предоставить
больше видео информация: категория и ключевые слова. Для этой демонстрации я использую категорию «Образование», но в реальном приложении вы можете предоставить пользователю выпадающий список для выбора одной из доступных категорий.

Вот где в игру вступает гем youtube_it, поэтому давайте поместим его в Gemfile :

Gemfile

 [...] gem 'youtube_it', github: 'bodrovis/youtube_it' [...] 

Я использую свою разветвленную версию этого драгоценного камня, потому что 2.4.0 json заблокирован для более старой версии.

Мы должны передать ключ разработчика при запросе токена загрузки. Этот ключ также можно получить с помощью консоли Google . Откройте ваш проект, который мы создали в предыдущем разделе, перейдите к «APIs & Auth» — «Credentials» и нажмите «Создать новый ключ». Во всплывающем окне выберите «Ключ браузера», заполните «Принимать запросы от этих HTTP-рефереров» и нажмите «Создать». Недавно сгенерированный «ключ API» — это то, что вы ищете.

С помощью youtube_it мы запрашиваем токен и URL, отправляя JSON-ответ клиенту.

Также обратите внимание, что в методе upload_token мы предоставляем get_video_uid_url — это URL-адрес обратного вызова, куда пользователь будет перенаправлен после загрузки видео. Как вы помните, мы уже создали этот маршрут, поэтому нам просто нужно добавить соответствующий метод:

videos_controller.rb

 [...] def get_video_uid video_uid = params[:id] v = current_user.videos.build(uid: video_uid) youtube = YouTubeIt::OAuth2Client.new(dev_key: ENV['GOOGLE_DEV_KEY']) yt_video = youtube.video_by(video_uid) v.title = yt_video.title v.description = yt_video.description v.save flash[:success] = 'Thanks for sharing your video!' redirect_to root_url end [...] 

id параметра GET, содержащий уникальный идентификатор видео, добавляется к URL-адресу обратного вызова, который хранится в базе данных. Мы также можем получить другую информацию о видео, такую ​​как заголовок, описание, продолжительность и т. Д. (Подробнее об этом читайте в моей статье на YouTube на Rails ).

Здорово! Остался один маленький кусочек: страница index — нам нужно отобразить все загруженные видео. Это самая простая часть.

videos_controller.rb

 [...] def index @videos = Video.all end [...] 

видео / index.html.erb

 <div class="jumbotron"> <h1>YouTube Uploader</h1> <p>Upload your video to YouTube and share it with the world!</p> <p><%= link_to 'Upload video now!', new_video_path, class: 'btn btn-primary btn-lg' %></p> </div> <div class="page-header"> <h1>Videos</h1> </div> <ul class="row" id="videos-list"> <% @videos.each do |video| %> <li class="col-sm-3"> <div class="thumbnail"> <%= link_to image_tag("https://img.youtube.com/vi/#{video.uid}/mqdefault.jpg", alt: video.title, class: 'img-rounded'), "https://www.youtube.com/watch?v=#{video.uid}", target: '_blank' %> <div class="caption"> <h4><%= video.title %></h4> <p><%= truncate video.description, length: 100 %></p> </div> </div> </li> <% end %> </ul> 

Для создания эскиза видео и "https://www.youtube.com/watch?v=#{video.uid}" мы используем image_tag("https://img.youtube.com/vi/#{video.uid}/mqdefault.jpg") "https://www.youtube.com/watch?v=#{video.uid}" чтобы создать ссылку для просмотра видео. Как видите, UID видео очень удобно.

Некоторые стили:

таблицы стилей / application.css.scss

 [...] #videos-list { margin: 0; padding: 0; li { padding: 0; list-style-type: none; word-wrap: break-word; .caption { text-align: center; } } } 

Вот и все! Идите вперед и загрузите несколько видео с вашим новым блестящим приложением!

PS: пара Gotchas

Вы должны знать о двух ошибках. Первый связан с аутентификацией. Когда пользователь, который не включил YouTube в своем канале, пытается выполнить аутентификацию с помощью вашего приложения (которое запрашивает доступ к API YouTube), будет возвращена ошибка (к счастью, у нас есть механизм для обнаружения этих ошибок). Поэтому вам может потребоваться проверить эту точную ошибку и дать указание своим пользователям посетить сайт www.youtube.com, создать канал, а затем снова попытаться пройти проверку подлинности.

Второй связан с загрузкой. Как вы знаете, YouTube требуется некоторое время после загрузки, чтобы проанализировать видео и создать эскизы; Чем длиннее видео, тем больше времени требуется для его анализа. Таким образом, сразу после того, как пользователь загрузил видео, миниатюра по умолчанию будет использоваться на главной странице. Есть как минимум два решения этой проблемы:

  1. Вы можете просто предупредить пользователей об этом на странице «Новое видео», объяснив, что эскиз появится через 3-5 минут.
  2. Вы можете создать некоторый фоновый процесс, который будет периодически пытаться получить доступ к миниатюре и показывать видео на главной странице только тогда, когда миниатюра будет доступна (опять же, пользователь должен быть предупрежден об этом факте).

Вывод

Наконец, мы достигли конца этой статьи. Об этом было много, а? Вы когда-нибудь пытались реализовать подобные функции в своих приложениях? Как вы решили эту задачу? Поделитесь своими мыслями в комментариях!

Спасибо за чтение и до скорой встречи!