Ruby
Article

YouTube API, Version 3 on Rails

By Ilya Bodrov-Krukowski

Old YouTube Icon

A while ago, I penned an article on using YouTube on Rails, explaining the basics of interacting with the YouTube API. The previous article covered how to fetch video information and use the YouTube IFrame API to manipulate video player. Later, another article, Uploading Videos to YouTube with Rails, was released showing how to create an app that allows users to upload videos directly to YouTube.

The Youtube_it gem was used for both demos. Both of my posts garnered quite an bit of comments, which I appreciate.

youtube_it is a great gem, however, it employs version 2 of the YouTube API, which is now officially deprecated and will no longer be supported after April 20th, 2015. Ouch.

Fortunately, Claudiofullscreen saved the day by creating a new gem called yt (now that’s a short name) that utilizes version 3 of the YouTube API.

In this article, we are going to build an app similar to the one that was introduced in the “YouTube on Rails” and “Uploading Videos to YouTube with Rails” posts, but make it work with version 3 of the YouTube API.

The working demo is available at sitepoint-ytv3.herokuapp.com.

The source code is available at GitHub.

Changes in V3

There are some notable changes in v3:

  • Authentication. Whereas API v2 allowed authentication via OAuth, OAuth 2, AuthSub or Developer Key (to perform read-only requests), API v3 only supports OAuth 2. With OAuth 2, you can access user’s private data and manipulate it via API. Read-only access, not requiring user authentication, is also supported. You need to provide an API key that identifies your project, which I’ll show you in a bit.
  • Fetching videos. Fetching videos by tags and finding most-linked videos is not supported anymore. Advanced queries with boolean operators were removed as well.
  • No more comments for you. At least for now, as YouTube is re-working its commenting system, so comments are not currently part of the API. This means that you can’t list or manage video comments anymore.
  • Video responses were retired. RIP. As this announcement states, video responses were used about 0.0004% of the time, so the YouTube team decided to remove it.
  • Access control lists. Most of this functionality was removed; the only one that remains is embeddable, however there are reports that it does not work as well.

You can refer to the Migration Guide to learn more. By the way, there is a special guide for those who are migrating from youtube_it gem to yt.

Preparing the App

I am going to build an app that will provide the following features:

  • Users should be able to add their videos that already exist on YouTube.
  • Videos should be displayed on the main page of the site, along with some basic information (like title).
  • Users should be able to upload their videos to YouTube via the app. Uploaded videos should also be saved in the app’s database.

In this guide, I am going to stick with Rails 4.2.0, but the same solution (with a very few modifications) can be implemented with Rails 3 and 4.1.

Start by creating a new app without the default testing suite:

$ rails new YtVideosV3 -T

Drop the following gems into your Gemfile:

Gemfile

[...]
gem 'yt', '~> 0.13.7'
gem 'bootstrap-sass', '~> 3.3.0.1'
gem 'autoprefixer-rails'
[...]

The main star here is yt. I am using Bootstrap for styling purposes, but it’s not required. autoprefixer-rails is recommended for use with Bootstrap to automatically add browser vendor prefixes.

Don’t forget to run

$ bundle install

Hook up Bootstrap:

application.scss

@import "bootstrap-sprockets";
@import "bootstrap";
@import 'bootstrap/theme';

Okay, now tweak the layout a bit:

layouts/application.html.erb

[...]
<div class="navbar navbar-inverse">
  <div class="container">
    <div class="navbar-header">
      <%= link_to 'YT APIv3', 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 %>
</div>
[...]

Next, proceed with the model, called Video, which will store the users’ videos. It it going to contain the following attributes:

  • link (string) – a link to the video on YouTube
  • uid (string) – video’s unqiue identifier presented by YouTube. It is a good idea to add a database index here
  • title (string) – video’s title
  • published_at (datetime) – a date when the video was published on YT
  • likes (integer) – likes count for the video
  • dislikes (integer) – dislikes count for the video

Create and apply the corresponding migration:

$ rails g model Video link:string title:string published_at:datetime likes:integer dislikes:integer uid:string:index
$ rake db:migrate

Don’t forget to set up the routes:

config/routes.rb

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

Create the controller:

videos_controller.rb

class VideosController < ApplicationController
  def index
    @videos = Video.order('created_at DESC')
  end

  def new
    @video = Video.new
  end

  def create
  end
end

On the index page, display all the videos that were added by the user. There will also be a new page that presents a form to add a new video. The create action will be fleshed out in the next section.

Lastly, create an index view with a single button:

views/videos/index.html.erb

<div class="jumbotron">
  <div class="container">
    <h1>Share your videos with the world!</h1>
    <p>Click the button below to share your video from YouTube.</p>
    <p>
      <%= link_to 'Add video now!', new_video_path, class: 'btn btn-primary btn-lg' %>
    </p>
  </div>
</div>

Adding Videos From YouTube

The next step is creating a form to add videos that have previously been uploaded to YouTube, meaning, outside our application. The only thing that we need to know is the link to the video that the user wishes to add. All other information about it will be found using the YouTube API. As such, the form is very simple:

views/videos/new.html.erb

<div class="container">
  <h1>New video</h1>

  <%= form_for @video do |f| %>
    <%= render 'shared/errors', object: @video %>

    <div class="form-group">
      <%= f.label :link %>
      <%= f.text_field :link, class: 'form-control', required: true %>
      <span class="help-block">A link to the video on YouTube.</span>
    </div>

    <%= f.submit class: 'btn btn-primary' %>
  <% end %>
</div>

The shared/_errors.html.erb partial is used here:

views/shared/_errors.html.erb

<% if object.errors.any? %>
  <div class="panel panel-danger">
    <div class="panel-heading">
      <h3 class="panel-title">The following errors were found while submitting the form:</h3>
    </div>

    <div class="panel-body">
      <ul>
        <% object.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  </div>
<% end %>

Now, the create action:

videos_controller.rb

[...]
def create
  @video = Video.new(video_params)
  if @video.save
    flash[:success] = 'Video added!'
    redirect_to root_url
  else
    render :new
  end
end

private

def video_params
  params.require(:video).permit(:link)
end
[...]

Really simple. Let’s also set up validation so that users cannot enter invalid links:

models/video.rb

class Video < ActiveRecord::Base
  YT_LINK_FORMAT = /\A.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/i

  validates :link, presence: true, format: YT_LINK_FORMAT
end

Setting up the YT API

Before moving on, we have to configure the yt gem so it can communicate with the YouTube API. First of all, navigate to the Google Developers Console and create a new application. Call it whatever you like, but keep in mind that users will see this name when authenticating via OAuth 2. Next, navigate to the Consent screen page (APIs & Auth section) and provide basic information about your app.

Open the APIs page and enable the following:

  • Google+ API
  • YouTube Analytics API
  • YouTube Data API v3

If you forget to enable some of these, errors will be produced when trying to communicate with the YT API. Also note that the APIs have usage quotas, so be aware of how much your sending to the API.

The last step is obtaining a server key for public API requests as, currently, we do not need any user interaction – only basic actions will be performed. Navigate to Credentials and click “Create new key” in the “Public API” access section. Then choose Server key and enter your server’s IP address so that requests cannot be sent from other IPs.

If you are not sure which IP to enter, simply leave this field blank for now (which effectively means
that any IP is allowed to send requests with the provided server key) – we are building a demo app after all. Lastly click “Create” – a new Key for server applications will be added. The API key value is what we need.

Create an initializer file to set up yt:

config/initializers/yt.rb

Yt.configure do |config|
  config.api_key = 'your_server_key'
end

This API key should be kept safe – I am using an environment variable to store it.

yt is now configured and can issue API requests to fetch basic information, such as a video’s title or publishing date.

Querying the YT API

Before the video is saved in the database, information about it should be loaded from YouTube. In the “YouTube on Rails” article, I used a before_create callback to fetch all the required info, but one of the readers noted that observer can be also used for this task, so let’s try that instead.

In Rails 4, observers are not part of the framework’s core anymore, so we need a separate gem to bring them back:

Gemfile

[...]
gem 'rails-observers'
[...]

Run

$ bundle install

and tweak the application’s configuration like this

config/application.rb

[...]
config.active_record.observers = :video_observer
[...]

to register a new observer.

Create the observer in the models directory:

models/video_observer.rb

class VideoObserver < ActiveRecord::Observer
  def before_save(resource)
    video = Yt::Video.new url: resource.link
    resource.uid = video.id
    resource.title = video.title
    resource.likes = video.like_count
    resource.dislikes = video.dislike_count
    resource.published_at = video.published_at
  rescue Yt::Errors::NoItems
    resource.title = ''
  end
end

The before_save method will run only before the record is saved. This method accepts a resource as an argument. Inside the method, I am using Yt::Video.new to fetch the specified video via the API by its URL. Then, we simply use yt’s methods to get all the necessary info.

I am also rescuing from the Yt::Errors::NoItems error – it will occur when the requested video was not found.

That’s all! You can go ahead and add some videos of your choice to check if everything is working correctly.

Displaying Videos

Let’s spend a couple of minutes modifying the index page so videos are being shown. Use the following layout:

views/videos/index.html.erb

<div class="jumbotron">
  <div class="container">
    <h1>Share your videos with the world!</h1>
    <p>Click the button below to share your video from YouTube.</p>
    <p>
      <%= link_to 'Add video now!', new_video_path, class: 'btn btn-primary btn-lg' %>
    </p>
  </div>
</div>

<% if @videos.any? %>
  <div class="container">
    <h1>Latest videos</h1>
    <div id="player-wrapper"></div>
    <% @videos.in_groups_of(3) do |group| %>
      <div class="row">
        <% group.each do |video| %>
          <% if video %>
            <div class="col-md-4">
              <div class="yt_video 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">
                  <h5><%= video.title %></h5>
                  <p>Published at <%= video.published_at.strftime('%-d %B %Y %H:%M:%S') %></p>
                  <p>
                    <span class="glyphicon glyphicon glyphicon-thumbs-up"></span> <%= video.likes %>
                    <span class="glyphicon glyphicon glyphicon-thumbs-down"></span> <%= video.dislikes %>
                  </p>
                </div>
              </div>
            </div>
          <% end %>
        <% end %>
      </div>
    <% end %>
  </div>
<% end %>

in_groups_of is a Rails method that will divide the array of videos into groups of 3 elements. We then iterate over each group to render them. Notice the if video condition – it is required because if you have, for example, 5 elements in the array and divide them into groups of 3, the last element will be set to nil.

For each video, display its thumbnail image, which YouTube generates for us. mqdefault.jpg means that we want to fetch a 320×180 image with no black stripes above and below the picture. There is also a hqdefault.jpg (480×360 image with black stripes above and below the picture) and <1,2,3>.jpg (120×90 image with different scenes from the video with black stripes above and below the picture).

Each thumbnail acts as a link to the video on YouTube. In the “YouTube on Rails” article, I showed how to implement the YouTube IFrame API in order to add the player to your page (and manipulate it). This process has not changed at all, so no need to duplicate the same code here. Browse the Displaying the Videos section in that article for more information.

Uploading Videos to YouTube

Right now, users are able to add their videos into our app easily. What if someone has a video that is not yet uploaded to YouTube? Should we instruct this person to first use the YT Video Manager to upload the file and then use our app’s interface to share the video? It would be more convenient if users could just upload their videos directly via our app.

Authenticating via Google+

First of all, we need an authentication system in place. As you may remember, YT APIv3 only allows the OAuth 2 protocol for authentication and this protocol requires us to pass a special token that is generated when a user logs in via our app.

We are going to use the omniauth-google-oauth2 that provides a Google OAuth 2 strategy for OmniAuth.

Add the new gem into the Gemfile:

Gemfile

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

and run

$ bundle install

Create an initializer that will contain settings for our authentication strategy:

config/initializers/omniauth.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2, 'YT_CLIENT_ID', 'YT_CLIENT_SECRET', scope: 'userinfo.profile,youtube'
end

We are registering a new strategy called google_oauth2. YT_CLIENT_ID and YT_CLIENT_SECRET can be obtained via the Google Developer Console, which we used a few minutes ago. Return there, open the app, that you’ve created earlier, and navigate to Credentials. Click the “Create new Client ID” button and select “Web application”. Put your site’s URL in the “Authorized JavaScript” origins field (use “http://127.0.0.1:3000” if working on a developer’s machine).

For the Authorized redirect URIs, provide the site URL plus “/auth/google_oauth2/callback” (for example,
“http://127.0.0.1:3000/auth/google_oauth2/callback”). A new Client ID for the web application will be created. The Client ID and Client Secret fields are what you want. Once again, those keys should not be available in your version control system.

The last parameter for our strategy is the scope, which specifies which actions the app would like to perform. userinfo.profile means that we want to be able to fetch basic information about the user’s account (like name, unique identifier and that stuff). youtube means that the app will be able to manage the user’s YouTube account (because we need to be able to upload new videos).

Add a couple more routes:

config/routes.rb

[...]
get '/auth/:provider/callback', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy', as: :logout
[...]

The first one is the callback route where a user is redirected after a successful authentication. The second route will be used for logging out.

We need to store the user’s data somewhere, so a new table will be required. Let’s call it users. For now it will contain the following fields:

  • name (string) – user’s name (with a surname perhaps).
  • token (string) – token to perform API requests.
  • uid (string) – user’s unique identifier. We are going to add an index with a uniqueness constraint here.

Create the migration:

$ rails g model User name:string token:string uid:string

and append this line right after the create_table method:

xxx_create_users.rb

[...]
create_table :users do |t|
  [...]
end
add_index :users, :uid, unique: true
[...]

We also need a new controller with two actions:

sessions_controller.rb

class SessionsController < ApplicationController
  def create
    user = User.from_omniauth(request.env['omniauth.auth'])
    session[:user_id] = user.id
    flash[:success] = "Welcome, #{user.name}"
    redirect_to root_url
  end

  def destroy
    session[:user_id] = nil
    flash[:success] = "Goodbye!"
    redirect_to root_url
  end
end

request.env['omniauth.auth'] contains all the information sent by the server to our app (it is called the “auth hash”). Now, let’s create the from_omniauth method:

models/user.rb

class User < ActiveRecord::Base
  class << self
    def from_omniauth(auth)
      user = User.find_or_initialize_by(uid: auth['uid'])
      user.name = auth['info']['name']
      user.token = auth['credentials']['token']
      user.save!
      user
    end
  end
end

Here the find_or_initialize_by method is used. It tries to find a user with the provided uid and, if found, the record is returned as a result. If it is not found, a new object is created and returned. This is done to avoid situations when the same user is being created multiple times. We then fetch the user’s name and token, save the record, and return it.

Here is a sample auth hash that you can use as a reference.

It is time to create a new method to test if the user is logged in. We are going to call it current_user, which is a common idiom in Rails projects.

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
[...]

It simply checks if the session contains the user_id and, if it does, try to find the user with the specified id. helper_method ensures that this method can also be used in views.

Lastly, let’s expand our menu a bit:

views/layouts/application.html.erb

[...]
<div class="navbar navbar-inverse">
  <div class="container">
    <div class="navbar-header">
      <%= link_to 'YT APIv3', 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>
      <% if current_user %>
        <li><%= link_to 'Upload Video', new_video_upload_path %></li>
      <% end %>
    </ul>
    <ul class="nav navbar-nav pull-right">
      <% if current_user %>
        <li><span><%= current_user.name %></span></li>
        <li><%= link_to 'Log Out', logout_path, method: :delete %></li>
      <% else %>
        <li><%= link_to 'Log In', '/auth/google_oauth2' %></li>
      <% end %>
    </ul>
  </div>
</div>
[...]

I’ve also added some styles to pretty-up those links:

application.scss

[...]
.nav > li > span {
  display: block;
  padding-top: 15px;
  padding-bottom: 15px;
  color: #9d9d9d;
}

Uploading

Great, only one step is left. We have to create another form allowing the user to select a video and provide the title and description for it.

We could utilize the same VideosController, but I’ve decided to use another approach here that follows REST principles and makes validation really simple.

Create a new controller:

video_uploads_controller.rb

class VideoUploadsController < ApplicationController
  def new
    @video_upload = VideoUpload.new
  end

  def create
  end
end

We’ll come back to the create method soon enough. For now, add the new routes:

config/routes.rb

[...]
resources :video_uploads, only: [:new, :create]
[...]

and yet another menu item:

views/layouts/application.html.erb

[...]
<ul class="nav navbar-nav">
  <li><%= link_to 'Videos', root_path %></li>
  <% if current_user %>
    <li><%= link_to 'Add Video', new_video_upload_path %></li>
  <% end %>
</ul>
[...]

Now, the actual form:

views/video_uploads/new.html.erb

<div class="container">
  <h1>Upload video</h1>
  <% if current_user %>
    <%= form_for @video_upload do |f| %>
      <%= render 'shared/errors', object: @video_upload %>

      <div class="form-group">
        <%= f.label :file %>
        <%= f.file_field :file, class: 'form-control', required: true %>
      </div>

      <div class="form-group">
        <%= f.label :title %>
        <%= f.text_field :title, class: 'form-control', required: true %>
      </div>

      <div class="form-group">
        <%= f.label :description %>
        <%= f.text_area :description, class: 'form-control', cols: 3 %>
      </div>

      <%= f.submit 'Upload', class: 'btn btn-primary' %>
    <% end %>
  <% else %>
    <p>Please <%= link_to 'sign in', '/auth/google_oauth2' %>.</p>
  <% end %>
</div>

We have to check if the user is logged in, otherwise trying to upload a video will result in an error. The form contains three fields: file, title, and description. You can expand it further, allowing users to provide, for example, tags or a category for their video (don’t forget to tweak controller and a model, accordingly).

We need to think about validation. Obviously, we don’t need a separate table here because information about the uploaded video will be saved to the same videos table that already exists. As such, it seems the model is not required, but then all the validation logic has to be put into the controller, which is not the best idea:

def create
  if params[:file].present? && params[:title].present? # ... and more checks here
    # upload video
  else
    # display an error (and user won't even understand what exactly is wrong)
  end
end

Instead, create a Ruby class and call it VideoUpload – all the validation logic can be put there. However, it would also be nice if this class borrowed some cool ActiveRecord features. We can do this, we have the technology. Meet active_type created by the folks from Makandra. ActiveType makes Ruby objects quack like ActiveRecord.

Drop this gem into the Gemfile

[...]
gem 'active_type', '0.3.1'
[...]

and run

$ bundle install

Now create the video_upload.rb file in the models directory:

models/video_upload.rb

class VideoUpload < ActiveType::Object
  attribute :file, :string
  attribute :title, :string
  attribute :description, :text

  validates :file, presence: true
  validates :title, presence: true
end

Unfortunately there is an issue with Postgres and Rails 4.2 (maybe with some other version of Rails as well) that required me to modify the second and third lines like this:

models/video_upload.rb

[...]
attribute :file, :varchar
attribute :title, :varchar
[...]

This is a simple Ruby class that inherits from ActiveType::Object, whcih grants it super powers. With the help of the attribute method, we specify attributes and types. The validates method comes directly from ActiveRecord and you can use it the same way. Pretty cool!

At this point, we can return to the controller

video_uploads_controller.rb

def create
  @video_upload = VideoUpload.new(title: params[:video_upload][:title],
                                  description: params[:video_upload][:description],
                                  file: params[:video_upload][:file].try(:tempfile).try(:to_path))
  if @video_upload.save
    uploaded_video = @video_upload.upload!(current_user)

    # check if the video was uploaded or not

    redirect_to root_url
  else
    render :new
  end
end

This is just a basic controller method. Of course, calling save on the @video_upload does not actually save anything – it only runs validations. The upload! method does not exist, yet so let’s fix that:

models/video_upload.rb

[...]
def upload!(user)
  account = Yt::Account.new access_token: user.token
  account.upload_video self.file, title: self.title, description: self.description
end
[...]

This method creates a new yt client with the access token that we’ve received earlier. The upload_video method starts the actual upload. It accepts file and video parameters, like title and description. If you have read my “Uploading Videos to YouTube with Rails” article, then you probably noticed that the uploading process is now much easier. For YT API v2, you had to actually perform two requests: the first one returned the upload token and the second one allowed the upload to start. That was really messy and, thank Google, they’ve simplified things.

The last piece of create‘s logic:

[...]
def create
  @video_upload = VideoUpload.new(title: params[:video_upload][:title], description: params[:video_upload][:description], file: params[:video_upload][:file].try(:tempfile).try(:to_path))

  if @video_upload.save
    uploaded_video = @video_upload.upload!(current_user)

    if uploaded_video.failed?
      flash[:error] = 'There was an error while uploading your video...'
    else
      Video.create({link: "https://www.youtube.com/watch?v=#{uploaded_video.id}"})
      flash[:success] = 'Your video has been uploaded!'
    end
    redirect_to root_url
  else
    render :new
  end
end
[...]

Display an error if the video failed to upload. Otherwise add the video’s info to the database and redirect to
the root_url. Feel free to refactor this code further.

By the way, there are other status checks other than failed? – check out the examples here.

Some Gotchas

You should remember that YouTube will need some time to digest the video and, the longer the video is, the longer this process will take. Why should you care? Because if you try to fetch the video’s duration right after the upload process finishes, zero seconds will be returned as a result.

The same applies to thumbnail images – they will not be available for a few minutes and you’ll see the default, boring grey image instead.

To overcome this issue you can set up some kind of background process that periodically checks if newly uploaded videos were digested (use processed? method). If yes, fetch all their information. You may even want to hide those videos and display them on the main page only after the parsing is finished. Just don’t forget to warn your users about this fact (parsing of long videos may take more than 10 minutes).

Also, don’t forget that YouType can reject videos for many reasons: it is too long, too short, duplicated, violates copyrights, has unsupported codec, etc., So, use extensive status checks.

Conclusion

That’s all for today, folks! I hope you find this article as useful as the original versions. The yt gem has many more fascinating features, so browse its readme! Have you already used the YT API v3? What do you think about it? Have you encountered any specific problems?

Also don’t hesitate to post your questions or requests topics that you want me to cover. See you!

Comments
Hadi_Winata

hello mr. bodrova

maybe you can help me...i got an message
uninitialized constant OpenSSL::SSL::SSLErrorWaitReadable when i try to upload a video..

bodrovis

Hello!

Its "Bodrov", but call me Ilya plz smile

I and some other coders stumbled upon this issue and it is still unclear what is the root cause. See this: https://github.com/Fullscreen/yt/issues/103 and https://github.com/Fullscreen/yt/pull/110 The author of the gem said that he had when YT API was experiencing some problems and after a while it is gone. I'd recommend checking that all required APIs are enabled (check the second screenshot here https://github.com/Fullscreen/yt#configuring-your-app).

You might re-open the #103 issue and describe when the problem occurs. Hopefully this we be solved this way or another.

Hadi_Winata

smile ok Ilya thank you and i will check that..

winst43

This is a great tutorial. Thanks very much for providing it. Do you know if the Youtube API quotas apply here? If quotas apply (I would think they would for some actions), do you know if the quota is applied per the site that is calling the api or per the user that is causing the api to be called--obviously, if the quotas apply per user, that would enable a lot more activity than if they apply every time the site calls the api. It is a little confusing how using the gem maps to the different types of APIs Youtube describes, which have their own quota limits.

I assume the quotas apply, but am not sure how. It looks like there are separate quotas for anyone using Youtube Data API and Youtube Analytics API (not sure about Youtube Player API, which might be most on point). There might not be a quota for simply embedding a video, but I am pretty sure there is a quota for uploading a video from a site to youtube. Seems like this could be called "insert": https://developers.google.com/youtube/v3/getting-started#quota. If so, by the quota rules (getting pretty specific here), that costs about 1600 units, and you are alotted 50,000,000 units a day. That would translate to about 30,000 videos allowed to be uploaded per day. That is obviously a lot and not a concern for building something just for learning, but if your site starts getting a lot of traction, it could be a little bit of a concern if users could at most do 30,000 a day (3 mil a month).

Any way that quota might be taken per user? (so your site could upload any amount, but each user would be limited to 30,000 a day--obviously way more than enough)

And, as said, do you think the quotas apply for both just embedding an already existing quota and uploading a new video, or just uploading a new video?

There are a few unanswered Stack Overflow questions on this, so I think it is a point of confusion.

Thanks again!

bodrovis

That is a really good question, but unfortunately I can't answer it 100%. Quota is really being applied for any API interaction, however it is not applied to Iframe API when you embed videos (because you are not providing any keys or tokens). If you open Google Developers Console and navigate to API section of any project, there will be Enabled APIs tab. Inside you will see quota per API. Therefore interacting with YT Analytics in not the same as interacting with YT Data API. These quotas are counted per project.

What I do not know is how to set up those per user and seems like from this console there is no way to do that (moreover it seems that there is no way to do it at all, see below). I believe you can search for such topics in Google groups or contact them directly. I have heard somewhere (though I am really not sure if it was related to Google APIs) that if you need much more API calls for a project, then you may contact Google team and discuss this personally. For example, here is some info on usage limits for Google maps https://developers.google.com/maps/documentation/javascript/usage It clearly states that if your app generates too many traffic, Google team will contact you to discuss payment options. Some more info on billing: https://developers.google.com/console/help/new/#billing Money makes the world go around, as they say.

Hopefully that helps and thank you for the feedback!

winst43

Thanks. Your reply adds clarity to how this might work with the Google quotas--and I doubt there is much more to know without talking to Google directly. I appreciate it.

oofman

Hey Ilya (@bodrovis)

This tutorial was my introduction to ruby and may I say that it was very well done, everything made perfect sense and was easy to implement and all is working perfectly.

However I need my system to do something a little differently, and I can't find to many articles regarding this on rails.
I need my site to have one central youtube account, that all the videos are submitted too, regardless of the end-users on my site. I believe its like a content owner account but not 100%, maybe you know of a couple of links you can point me too.

I know their is a lot of content copy right issues involved, but we would own the rights to the videos submitted, so that is not an issue in this case.

Any assistance would be appreciated.

bodrovis

Thank you for the feedback!

Well, the only thing that I can suggest is service accounts https://developers.google.com/identity/protocols/OAuth2ServiceAccount - it might help in your case. I don't know about any other possible solutions frowning

oofman

Hey Ilya (@bodrovis)

That is exactly what I'm busy with now, and once you have the access token from your service account, you can use the yt gem as per normal.

Thanks a lot for your quick feedback smile

bodrovis

Good luck then!

Alexander_Handy

@bodrovis - Hi Ilya,

Thanks so much for this tutorial, incredibly helpful for the project I am currently working on.

One minor point which my team got stuck on for a couple of hours together around the Google+ OAuth step.
We were trying to run in local host following your exact implementation instructions but continued to get redirect_url, CSRF detected and SSL errors throughout troubleshooting.

2 things worked for us that are worth flagging to other people following:

  1. Deployed to heroku - this may have been implicit in your instructions but this was only scenario that the specific route matching worked for us
  2. For local deployment, we had to change our callback url reference in our Google project credentials to localhost vs /127.0.0.1 AND in our omniauth initializer add the line OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE if Rails.env.development? to disable SSL certificate verification
bodrovis
  1. Yeah, that was implicit
  2. That's interesting, as I've never met this problem with certificate verification.

Thank you for these tips, should be helpful for other readers! smile

Alexander_Handy

@bodrovis - no problem

I have one follow-up question around validating video uploads.
With this structure, how would you implement file size validations for video uploads?
We only want to allow short videos e.g. < 60 secs in length and to begin with want to set an arbitrary file size limit of 50MB.

We have explored trying to add explicit validations within the video_upload.rb ActiveType::Object but to no avail.
e.g. validates_size_of :file, maximum: 50.megabytes, message: "should be less than 50MB"
I understand how you could achieve this using a third party uploader like CarrierWave but struggling to see how you could do this here.

Any thoughts would be greatly appreciated!

Thanks,
Alex

bodrovis

That's an interesting question. I will try to look into it and let you know the results. Judging by this https://github.com/Fullscreen/yt/issues/20 yt currently loads the whole file into memory so we may work with it somehow.

chidumaga

Thank you @bodrovis for this tutorial. Has helped me and the project I am working on tremendously.

I am running into an issue with the YouTube authentication however. This is the error I am getting:

A request to YouTube API was sent without a valid authentication: {"error"=>{"errors"=>[{"domain"=>"global", "reason"=>"authError", "message"=>"Invalid Credentials", "locationType"=>"header", "location"=>"Authorization"}],

According to the application trace this is coming from the upload! method in video_upload.rb; the access token seems to be inauthentic. I'm not exactly sure if this is the case or how to go about fixing it because it does work in some instances (e.g when the db is empty).

Any advice would be greatly appreciated. Thank you!

bodrovis

Hi! As far as I see you've opened this issue here https://github.com/Fullscreen/yt/issues/230 so I'll monitor it and discuss there.

chidumaga

Thanks! I've identified the problem and responded accordingly https://github.com/Fullscreen/yt/issues/230#issuecomment-130236596

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Ruby, once a week, for free.