Туториал по Coub API
На днях мы выпустили 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/
В поле Website указываем урл нашего тестового приложения, в поле Callback URL пишем
http://memegenerator.dev/auth/coub/callback После создания приложения Коб даст нам 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://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 %>
You«re logged in as <%= current_user.name %>. <%= link_to "Log out", logout_path, method: :delete %>
<%= form_for Coub.new, url: {action: "create"} do |f| %> <%= f.text_field :text1, placeholder: "To me", maxlength: 30, size: 50 %><%= f.text_field :text2, placeholder: "Your Coub API", maxlength: 30, size: 50 %>
<%= f.text_field :text3, placeholder: "Is a piece of shit", maxlength: 30, size: 50 %>
<%= f.select :visibility_type, options_for_select(["public", "friends", "unlisted", "private"]) %>
<%= f.text_field :tags, placeholder: "first tag, second tag" %>
<%= f.submit "Create Coub" %> <% end %> <% else %>
Please log in via Coub.
<% end %> Все, теперь заходим в браузер, и проверяем, что все работает: В этом туториале я описал только принцип работы API. Разумеется, для настоящего приложения надо еще много чего дописать: валидации, проверки ответов API, обработки ошибок, интерфейс нарисовать, тесты. Чуть-чуть допиленная версия этого скрипта лежит вот тут http://fantozzi.dev2.workisfun.ru, там все то же самое, но видео готовится и закачивается асинхронно через delayed_job.Исходники этого туториала лежат в гитхабе: https://github.com/igorgladkoborodov/memegenerator/.
Если есть какие-то вопросы по этому туториалу или по API, пишите на igor@coub.com.