- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

OAuth

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

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

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

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

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

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

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

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

Туториал по 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 [9].

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

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

$ 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 [7], проверяем. Должно выглядеть примерно вот так:

Теперь у нас в базе есть пользователь с токеном, который может делать запросы через 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 [1]

Клиент

Каждый раз вручную делать запросы и добавлять токен неудобно, поэтому добавим модели 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 [2], там все то же самое, но видео готовится и закачивается асинхронно через delayed_job.

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

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

Автор: workisfun

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ruby/85685

Ссылки в тексте:

[1] Coub API: http://coub.com/dev/docs/Coub+API/Overview

[2] fantozzi.dev2.workisfun.ru: http://fantozzi.dev2.workisfun.ru

[3] github.com/igorgladkoborodov/memegenerator: https://github.com/igorgladkoborodov/memegenerator

[4] OAuth 2.0: http://oauth.net/

[5] omniauth-oauth2: https://github.com/intridea/omniauth-oauth2

[6] omniauth-coub: https://github.com/coub/omniauth-coub

[7] memegenerator.dev: http://memegenerator.dev

[8] coub.com/dev/applications/: http://coub.com/dev/applications/

[9] документации к API: http://coub.com/dev/docs/Coub+API/Authentication

[10] RailsCasts: http://railscasts.com/episodes/304-omniauth-identity

[11] https://github.com/igorgladkoborodov/memegenerator/: https://github.com/igorgladkoborodov/memegenerator/

[12] igor@coub.com: mailto:igor@coub.com

[13] Источник: http://habrahabr.ru/post/252803/