...

пятница, 13 марта 2015 г.

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

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

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


Рабочая версия этого приложения лежит по адресу fantozzi.dev2.workisfun.ru, код приложения из этого туториала можно посмотреть на Гитхабе: http://ift.tt/1Bd4mCI



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. Регистрируем наше новое приложение по адресу http://ift.tt/1xjOj5E



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



http://ift.tt/1xjOj5G

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



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://ift.tt/1xjOjTm]




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

GET http://ift.tt/1L6kGAx




Все эти запросы хорошо задокументированы, полный список можно посмотреть тут: http://ift.tt/1b7vP3M

Клиент




Каждый раз вручную делать запросы и добавлять токен неудобно, поэтому добавим модели 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://ift.tt/1L6kH7B}"
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://ift.tt/1xjOj5M"




По этому урлу можно зайти и посмотреть на синий экран “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://ift.tt/1Bd4lPf, там все то же самое, но видео готовится и закачивается асинхронно через delayed_job.


Исходники этого туториала лежат в гитхабе: http://ift.tt/1L6kHnP.


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий