Туториал по Coub API

в 11:52, , рубрики: api, coub, ffmpeg, http, oauth, oauth 2.0, ruby, ruby on rails, авторизация, Блог компании Coub, Программирование, Работа с видео

На днях мы выпустили Coub API. Теперь можно делать приложения, смотреть ленту, лайкать, рекобить, то есть практически все, что можно сделать на сайте, можно делать через API. Но самое главное — теперь можно из сторонних приложений через API создавать кобы.

В этом туториале я покажу, как можно сделать простейший клиент коба на Ruby on Rails. Приложение позволяет залогиниться через коб и сгенерить такой коб с любым текстом:

Рабочая версия этого приложения лежит по адресу fantozzi.dev2.workisfun.ru, код приложения из этого туториала можно посмотреть на Гитхабе: github.com/igorgladkoborodov/memegenerator

OAuth

Коб использует стандартный протокол для авторизации OAuth 2.0. Он используется в очень многих сервисах, которые предоставляют внешний API (Фейсбук, например), по нему очень много документации и библиотек для любой платформы.

Работает авторизация примерно так: приложение со своим уникальным ключом заходит на специальную страницу на coub.com, там Коб спрашивает, согласен ли пользователь дать приложению доступ. Если пользователь разрешает, то Коб возвращает пользователя обратно в приложение, и отдает вместе с запросом токен пользователя, который потом уже используется при всех API-запросах пользователя. То же самое происходит, например, при авторизации через Фейсбук или Твиттер.

Мы будем писать на RoR и для авторизации через OAuth для рельсов все уже давно написано, мы будем использовать для этого гем omniauth-oauth2 и официальный кобовский гем omniauth-coub.

Создание приложения и авторизация

Создаем приложение с красноречивым названием memegenerator и прикручиваем его к Pow (или кто чем пользуется):

$ cd ~/apps/
$ rails new memegenerator
$ ln -s ~/apps/memegenerator/ ~/.pow/memegenerator

Проверяем в браузере, что у нас по адресу memegenerator.dev живет пустое рельсовое приложение.

2. Регистрируем наше новое приложение по адресу coub.com/dev/applications/

Туториал по Coub API - 1

В поле Website указываем урл нашего тестового приложения, в поле Callback URL пишем

http://memegenerator.dev/auth/coub/callback

После создания приложения Коб даст нам Application ID и Secret, они нам понадобятся дальше:

Туториал по Coub API - 2

3. Устанавливаем гем omniauth-coub:

Gemfile:

gem "omniauth-coub"

$ bundle install

4. Добавляем коб в провайдеры omniauth:

config/initializers/omniauth.rb:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :coub, ENV["COUB_KEY"], ENV["COUB_SECRET"], scope: "logged_in,create"
end

COUB_KEY и COUB_SECRET — это Application ID и Secret из прошлого шага, можно добавить их в ENV переменные или пока для теста вставить строки прямо тут, хотя оставлять в коде ключи нежелательно, ну вы понимаете.

Если у вас Pow, то добавить переменные можно в файле .powenv в корне приложения:

.powenv:

export COUB_KEY="[Application ID]"
export COUB_SECRET="[Secret]"

В scope вы можете указать, какими правами будет обладать приложение. Наше приложение нужно только для создания кобов, поэтому лишнего мы ничего не будем просить, только разрешение на авторизацию и создание коба: logged_in, create. Полный список режимов доступа можно посмотреть в документации к API.

5. Создаем модель пользователя с методом from_omniauth, который создает или находит в базе пользователя по данным, которые передал нам сервер авторизации на Кобе.

Что происходит в этом пункте и в паре следующих пунктов, хорошо объяснено в одном из эпизодов RailsCasts.

$ rails g model user provider:string uid:string auth_token:string name:string
$ rake db:migrate

app/models/user.rb:

class User < ActiveRecord::Base
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
      user.auth_token = auth.credentials.token
      user.name = auth.extra.raw_info.name
      user.save!
    end
  end
end

6. Создаем контроллер сессий. Через него мы создаем и удаляем сессию.

Метод create — это то место, куда возвращается пользователь с его токеном и данными авторизации. Тут мы создаем пользователя через метод from_omniauth, который мы написали в прошлом шаге, и сохраняем его токен, записываем его в куку, чтобы при перезагрузке браузера можно было вернуть эту сессию.

$ rails g controller sessions

app/controllers/sessions_controller.rb:

class SessionsController < ApplicationController
  def create
    user = User.from_omniauth(env["omniauth.auth"])
    cookies.permanent[:auth_token] = user.auth_token
    redirect_to root_url
  end
  def destroy
    cookies.delete(:auth_token)
    redirect_to root_url
  end
  def index
  end
end

Морда приложения пускай пока поживет в контроллере сессий, поэтому тут метод index.

7. Чтобы иметь доступ к текущему пользователю, добавляем в ApplicationController метод current_user, который ищет пользователя в базе данных, если у нас есть кука с его токеном.

app/controllers/application_controller.rb:

helper_method :current_user
def current_user
  @current_user ||= User.find_by_auth_token(cookies[:auth_token]) if cookies[:auth_token]
end

8. Выводим на морде ссылку на логин или показываем текущего пользователя с ссылкой на выход.

app/views/sessions/index.html.erb:

<% if current_user %>
  <%= current_user.name %>
  <%= link_to "Log out", logout_path, method: :delete %>
<% else %>
  <%= link_to "Auth with Coub", "/auth/coub" %>
<% end %>

По пути /auth/coub гем omniauth-oauth2 перебросит на страницу авторизации на coub.com.

9. Прописываем роуты:

config/routes.rb:

Rails.application.routes.draw do
  root "sessions#index"
  get "/auth/:provider/callback" => "sessions#create"
  delete "logout" => "sessions#destroy"
end

С авторизацией все. Заходим на memegenerator.dev, проверяем. Должно выглядеть примерно вот так:

Теперь у нас в базе есть пользователь с токеном, который может делать запросы через Coub API.

Запросы к API

Имея токен можно делать запросы к API. Это обычные запросы по протоколу HTTP, как в браузере. GET запросы можно в браузере же и тестировать. Каждый запрос кроме своих параметров должен содержать параметр access_token с токеном пользователя, который нам до этого выдал сервер авторизации.

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

POST http://coub.com/api/v2/likes?id=[coub_id]&channel_id=[channel_id]&access_token=[users_token]

Некоторые запросы можно делать и без токена, например, инфа о кобе (без токена доступны только публичные кобы). Это GET запрос, эту ссылку можно открыть просто в браузере:

GET http://coub.com/api/v2/coubs/4yp6r

Все эти запросы хорошо задокументированы, полный список можно посмотреть тут: coub.com/dev/docs/Coub+API/Overview

Клиент

Каждый раз вручную делать запросы и добавлять токен неудобно, поэтому добавим модели User метод client, через который будем делать запросы:

app/models/user.rb:

def client
  @client ||= Faraday.new(:url => "http://coub.com/api/v2/", :params => {:access_token => auth_token})
end

Gemfile:

gem "faraday"

$ bundle install

Запросы мы делаем через Faraday, это HTTP клиент на Ruby.

Запустим консоль, потестим запросы к апи:

$ rails c
> user = User.first
> user.client.get "coubs/4yp6r"

Ответы выдаются в формате JSON, поэтому, если нам хочется прочитать, что вернул сервер, надо ответ распарсить стандартной JSON библиотекой:

> coub_info = JSON.parse(user.client.get("coubs/4yp6r").body)
> coub_info["id"]
=> 9090841

У нас есть айдишник коба, давайте его лайкнем:

> user.client.post "likes?id=#{coub_info["id"]}"

Генерим видео

У нас есть видео, кадр из фильма, нам надо на него три раза положить текст в разное время. Для работы с видео через консоль есть программа ffmpeg, в ней через консоль можно делать с видео практически что угодно.

На маке через Homebrew он ставится вот так:

$ brew install ffmpeg --with-freetype

Накладываем текст через ffmpeg фильтром drawtext:

$ ffmpeg -i template.mp4 -vf "drawtext=enable='between(t,1,2)':text=Blah:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th)" output.mp4

Эта строчка означает:

1. ffmpeg -i template.mp4 -vf: берем видео файла template.mp4. Файл для этого туториала можно скачать вот тут.

2. drawtext=enable: накладываем текст

3. between(t,1,2): с первой по вторую секунду

4. fontfile=PFDinTextCondPro-XBlack.ttf: используем файл с шрифтом в формате TTF

5. text=Blah: пишем текст Blah

6. fontsize=40: размер шрифта

7. fontcolor=white: белого цвета

8. x=(w-tw)/2:y=(h*0.9-th): положение текста в центре внизу (w и h — это размер видео, tw и th — это размер блока с текстом)

9. output.mp4: записываем все в этот файл

Чтобы написать три текста, надо просто через запятую три drawtext написать:

$ ffmpeg -i template.mp4 -vf "drawtext=enable='between(t,1,2)':text=Text1:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th), drawtext=enable='between(t,3,5)':text=Text2:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th), drawtext=enable='between(t,6.3,8)':text=Text3:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th)" output.mp4

В темповой папке можно сложить файл template.mp4 и файл шрифта (можно взять любой TTF из папки со шрифтами) и попробовать в консоли запустить, оно должно сгенерить правильное видео.

Закачиваем видео

Видео у нас есть, теперь надо из него сделать коб.

Коб через API закачивается в три этапа:

1. Сначала инициализируем закачку запросом POST coubs/init_upload, в ответе мы получаем id коба и его permalink.

$ rails c
> user = User.first
> init_response = JSON.parse(user.client.post("coubs/init_upload").body)
> coub_id = init_response["id"]
> permalink = init_response["id"]

2. Закачиваем видео запросом POST coubs/:id/upload_video. Файл передается в теле запроса, в хедере Content-Type надо передать video/mp4:

> user.client.post do |r|
   r.url "coubs/#{coub_id}/upload_video"
   r.headers["Content-Type"] = "video/mp4"
   r.body = File.open("tmp/output.mp4", "r").read
 end

Если мы хотим загрузить отдельный саундтрек к кобу, то это можно сделать отдельным запросом coubs/:id/upload_audio. Нам в этот раз этого не нужно, поэтому мы опускаем этот запрос.

3. Финализируем создание коба запросом POST coubs/:id/finalize_upload, в параметрах передаем тайтл, настройки приватности, теги, включен ли звук.

> user.client.post "coubs/#{coub_id}/finalize_upload", title: "Test coub", original_visibility_type: "private", tags: "tag1, tag2, tag3", sound_enabled: true

После закачки коба, он будет какое-то время процесситься: видео на серверах Коба будет конвертироваться в несколько форматов для разных платформ, будут генериться превьюшки и куча всяких таких ресурсоемких вещей. Прогресс конвертирования можно проверить GET запросом coubs/:id/finalize_status. Он отдает примерно такой JSON { percent_done: 20, done: false}.

> user.client.get "coubs/#{coub_id}/finalize_status"

Ок. Мы протестировали это в консоли, теперь все это надо собрать в приложение.

Модель Coub

Создаем модель Coub:

$ rails g model coub user:belongs_to title:string visibility_type:string tags:string permalink:string coub_id:string text1:string text2:string text3:string
$ rake db:migrate

2. Делаем метод generate_video_file, который геренит видео из видео-шаблона и трех текстов, находящихся в полях text1, text2, text3. Шаблон видео и шрифт кладем в ассеты. Готовое видео кладем в папку tmp.

app/models/coub.rb:

def escape_ffmpeg_text(text)
  text.to_s.gsub("'", "\\\\\\\\\\\\'").gsub(":", "\\\\\\\\:").mb_chars.upcase # Crazy ffmpeg escaping
end
def ffmpeg_drawtext(text, from, to)
  font_file = File.join(Rails.root, "app", "assets", "fonts", "PFDinTextCondPro-XBlack.ttf")
  "drawtext=enable='between(t,#{from},#{to})':text=#{escape_ffmpeg_text(text)}:fontfile=#{font_file}:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th)"
end
def generate_video_file
  self.video_file = File.join(Rails.root, "tmp", "output-#{Time.now.to_i}.mp4")
  template_file = File.join(Rails.root, "app", "assets", "videos", "template.mp4")
  `ffmpeg -i #{template_file} -vf "#{ffmpeg_drawtext(text1, 1, 2)}, #{ffmpeg_drawtext(text2, 3, 5)}, #{ffmpeg_drawtext(text3, 6.3, 8)}" #{video_file}`
  return video_file
end

3. Делаем метод, который в три этапа закачивает видео на Коб:

app/models/coub.rb:

def upload_video
  self.title ||= text2
  self.visibility_type ||= "private"
  self.tags ||= ""
  
  init_response = JSON.parse(client.post("coubs/init_upload").body)
  self.coub_id = init_response["id"]
  self.permalink = init_response["permalink"]
  
  save
  client.post do |r|
    r.url "coubs/#{coub_id}/upload_video"
    r.headers["Content-Type"] = "video/mp4"
    r.body = File.open(video_file, "r").read
  end
  client.post "coubs/#{coub_id}/finalize_upload",
    title: title,
    original_visibility_type: visibility_type,
    tags: tags,
    sound_enabled: true
end
def generate_and_upload_video
  generate_video_file
  upload_video
end

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

app/models/coub.rb:

belongs_to :user
def client
  @client ||= user.client
end

5. Метод url отдает урл коба по пермалинку:

app/models/coub.rb:

def url
  "http://coub.com/view/#{permalink}"
end

Проверим, все ли работает:

$ rails c
> coub = Coub.new
> coub.user = User.first
> coub.text1 = 'Text 1'
> coub.text2 = 'Text 2'
> coub.text3 = 'Text 3'
> coub.tags = 'tag1, tag2, tag3'
> coub.visibility_type = 'unlisted'
> coub.generate_and_upload_video
> coub.url
=> "http://coub.com/view/5dbyc"

По этому урлу можно зайти и посмотреть на синий экран “Your coub is being processed”.

Осталось сделать для всего этого контроллер:

1. Создаем контроллер coubs. Он будет состоять из двух методов: index (это будет новая морда, вместо sessions#index) и create. При создании коба мы сразу редиректим на него.

$ rails g controller coubs

app/controllers/coubs_controller.rb:

class CoubsController < ApplicationController
  def index
  end
  def create
    @coub = Coub.create(coub_params.merge(:user => current_user))
    @coub.generate_and_upload_video
    redirect_to @coub.url
  end
private
  def coub_params
    params.require(:coub).permit(:text1, :text2, :text3, :visibility_type, :tags)
  end
end

2. Перетаскиваем index.html.erb из sessions в coubs и прикручиваем туда форму:

app/views/coubs/index.html.erb:

<% if current_user %>
  <p>You’re logged in as <%= current_user.name %>. <%= link_to "Log out", logout_path, method: :delete %></p>
    <%= form_for Coub.new, url: {action: "create"} do |f| %>
    <%= f.text_field :text1, placeholder: "To me", maxlength: 30, size: 50 %><br />
    <%= f.text_field :text2, placeholder: "Your Coub API", maxlength: 30, size: 50 %><br />
    <%= f.text_field :text3, placeholder: "Is a piece of shit", maxlength: 30, size: 50 %><br />
    <%= f.select :visibility_type, options_for_select(["public", "friends", "unlisted", "private"]) %><br />
    <%= f.text_field :tags, placeholder: "first tag, second tag" %><br />
    <%= f.submit "Create Coub" %>
  <% end %>
<% else %>
  <p>Please <a href="/auth/coub">log in via Coub</a>.</p>
<% end %>

Все, теперь заходим в браузер, и проверяем, что все работает:


В этом туториале я описал только принцип работы API. Разумеется, для настоящего приложения надо еще много чего дописать: валидации, проверки ответов API, обработки ошибок, интерфейс нарисовать, тесты. Чуть-чуть допиленная версия этого скрипта лежит вот тут http://fantozzi.dev2.workisfun.ru, там все то же самое, но видео готовится и закачивается асинхронно через delayed_job.

Исходники этого туториала лежат в гитхабе: https://github.com/igorgladkoborodov/memegenerator/.

Если есть какие-то вопросы по этому туториалу или по API, пишите на igor@coub.com.

Автор: workisfun

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js