Video Uploads with Rails and Ziggeo

Share this article

Video Uploads with Rails and Ziggeo
ziggeo

I remember times when virtually no one on the Internet actually watched videos because the connection speed was too slow. Then, the speed started to grow and I could download music tracks: it took roughly 4-5 minutes to download a single track while I was listening to another one. In 2005 YouTube emerged and video content started to spread. Nowadays videos are everywhere and many people actually prefer them over text (though there are many others who like to read the text instead).

I’ve already covered the process of working with the YouTube API in one of my previous articles, so today we will discuss another video hosting platform called Ziggeo. It provides an API to store and manage videos along with the ability to embed them (using a simple player). Also, there are additional features like comments, moderation, callbacks access rights, integrations with third-party services, and more. Ziggeo has both free and paid pricing plans. With the free plan, you can upload videos with the total length of 100 minutes, so it’s a great option for testing the service.

In this article I will show you how to upload videos to Ziggeo, associate them with a user, embed them, fetch meta information, setup server callbacks, and listen to events.

The source code is available on GitHub.

Special thanks to Ziggeo’s support team who provided quick and professional help.

Creating an Application

As usual, start off by creating a new Rails 5 application called ZigZag:

$ rails new ZigZag -T

Drop in some necessary gems:

Gemfile

# ...
gem 'Ziggeo'
gem 'dotenv-rails'
gem 'devise'
  • Ziggeo is a gem allowing you to easily work with the Ziggeo’s API. Note that its name starts with an uppercase “Z”!
  • Dotenv will be used to store environment variables for development.
  • Devise will provide an authentication solution.

Install these gems, create a basic configuration for Devise, and add a User model:

$ bundle install
$ rails generate devise:install
$ rails generate devise User
$ rails db:migrate

Create a VideosController and set up some routes:

videos_controller.rb

class VideosController < ApplicationController
end

config/routes.rb

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

Enforce user authentication inside the ApplicationController:

application_controller.rb

# ...
before_action :authenticate_user!

Also, let’s display flash messages inside the layout:

views/layouts/application.html.erb

# ...
<% flash.each do |key, value| %>
  <div class="alert alert-<%= key %>">
    <%= value %>
  </div>
<% end %>

That’s it. The next step is setting up Ziggeo.

Ziggeo Initial Setup

Sign up for Ziggeo before proceeding. Once you are done, an application with the name “Default” will be available for you, but, of course, another one may be created. Each application has its own settings, associated videos, and keys to work within the API. Ziggeo provides three keys, so place them inside the .env file:

.env

ZIGGEO_KEY=123
ZIGGEO_SECRET=abc
ZIGGEO_ENCRYPTION=345

Make sure you exclude this file from Git:

.gitignore

.env

Ziggeo also provides a nice quick start guide so you may want to browse it, as well. Next, include the necessary CSS and JavaScript files:

views/layouts/application.html.erb

<link rel="stylesheet" href="//assets-cdn.ziggeo.com/v1-stable/ziggeo.css" />
<script src="//assets-cdn.ziggeo.com/v1-stable/ziggeo.js"></script>
<script>ZiggeoApi.token = '<%= ENV['ZIGGEO_KEY'] %>';</script>

Basically, this is enough to start uploading and embedding videos on the website.

Uploading and Recording

Ziggeo instructs us to place a special tag (ziggeo) on the page. It acts as a video player, video recorder, and video uploader at the same time. To control the component’s behavior and appearance, multiple options can be set.

Here is the simplest variant:

views/videos/new.html.erb

<ziggeo ziggeo-width="320" ziggeo-height="240"></ziggeo>

This will create a special control to record and upload videos. I do not want videos to be too long, so set a ziggeo-limit parameter:

<ziggeo ziggeo-width="320" ziggeo-height="240" ziggeo-limit="60"></ziggeo>

60 here means “no more than 60 seconds”. Also, it would be nice to allow user to upload already recorded videos, so set ziggeo-perms to allowupload:

<ziggeo ziggeo-limit="60"
        ziggeo-width="320"
        ziggeo-height="240"
        ziggeo-perms="allowupload"></ziggeo>

Now you may test it out. Any uploaded video will appear inside the “Videos” and “Moderation” tabs on Ziggeo’s dashboard. Apart from the actual video file, you will see additional meta information, like its length, size, creation date, tags, and more. Also, there is a handy “Events” section listing all the events that have happened inside your application.

Embedding Videos

Use the same ziggeo tag to embed videos on the page. The main attribute is the ziggeo-video that accepts a video’s uid:

<ziggeo ziggeo-video='123abc'
        ziggeo-width="320"
        ziggeo-height="240">
</ziggeo>

What’s more, videos can be played in a popup. To do this, simply set the ziggeo-popup argument:

<ziggeo ziggeo-video='123abc'
        ziggeo-width="320"
        ziggeo-height="240" ziggeo-popup>
</ziggeo>

There is a sandbox available where you can see uploading and embedding in action as well as test various configuration options.

Also note that that Ziggeo’s player has some styling applied, but it does not look very nifty. Therefore, you may further style it as needed. Here is the playground to see styling in action.

Listening to Events

Ziggeo provides a whole bunch of Javascript events that you can listen to: play, pause, uploaded, and camera_nosignal to name a few. For example, let’s create a custom progress bar showing how the video upload process is going. This is easy to do with the upload_progress event that constantly fires saying how many bytes have been uploaded so far.

First of all, add the progress element to the page. I am using Bootstrap 4 to style it, but you may use any other CSS framework or write your own styles:

views/videos/new.html.erb

<!-- ... -->
<progress class="progress progress-striped hidden-xs-up" value="0" max="100"></progress>

The hidden-xs-up class will hide this progress bar on all screens. Alternatively, you may simply say display: none.

Now write some CoffeeScript code:

javascripts/videos.coffee

jQuery(document).on 'turbolinks:load', ->
  ZiggeoApi.Events.on "upload_progress", ( uploaded, total, data ) ->
    $('progress').removeClass('hidden-xs-up').attr 'value', (uploaded / total) * 100

ZiggeoApi is the global object available for us after the Ziggeo’s JS is loaded. The uploaded and total variables contains the number of bytes, so dividing them and multiplying by 100 gives us a percentage. The data variable contains information about the video that’s currently being uploaded.

Don’t forget to include this new file:

javascript/application.js

//= require videos

Another thing we may do is redirect the user to the root page after a video is uploaded:

javascripts/videos.coffee

ZiggeoApi.Events.on "submitted", ( data ) -> window.location.href = '/'

Simple, isn’t it?

Also note that Ziggeo provides some callable methods to manipulate the player and the uploader. These methods can be found in the docs.

Querying the API

So, now the videos can be uploaded, but we don’t display them anywhere. In order to fetch a list of uploaded videos, you may utilize the Ziggeo gem that we’ve included at the beginning of the article.

Working with it is very simple. You just need to instantiate the client by passing all three keys obtained earlier and then call the required method:

videos_controller.rb

# ...
def index
  ziggeo = Ziggeo.new(ENV['ZIGGEO_KEY'], ENV['ZIGGEO_SECRET'], ENV['ZIGGEO_ENCRYPTION'])
  @videos = ziggeo.videos.index
end

The index method accepts arguments like limit and skip to further customize the request. As a result, the @videos variable will contain an array of hashes, each of which store the video’s uid and other meta information. This array can now be rendered on the main page:

views/videos/index.html.erb

<h1>Videos</h1>

<%= link_to 'Add video', new_video_path %>

<%= render partial: 'video', collection: @videos, as: :video %>

Note that we can’t say render @videos as it’s a simple array. Now render the player inside the partial. Videos will be opened in a popup:

views/videos/_video.html.erb

<div class="card">
  <div class="card-block">
    <ziggeo ziggeo-video='<%= video['token'] %>'
            ziggeo-width="320"
            ziggeo-height="240" ziggeo-popup>
    </ziggeo>
</div>

The token field contains the video’s unique identifier.

Associating Videos With Users

One common piece of functionality is allowing each user to have their own videos. Currently, however, videos have no information about who owns them. Unfortunately, there is no special field to store this information, but it can be solved by using the video’s tags. Tags are passed in a form of a comma-delimited string that is later turned into an array. When fetching the videos later, we will simply pass the tags option.

We might utilize user ids as tags, but that’s not very secure. Instead, lets generate a unique token after a user is created:

models/user.rb

# ...
before_create -> { self.uid = generate_uid }

private

def generate_uid
  loop do
    uid = Digest::MD5.hexdigest(self.email + self.created_at.to_s + rand(10000).to_s)
    return uid unless User.exists?(uid: uid)
  end
end

We simply generate an MD5 hash based on the user’s email, creation date, and some random number. This token must be unique, so we make sure no other user already has it.

The uid field does not exist yet, so create the corresponding migration now:

$ rails g migration add_uid_to_users uid:string

Tweak the migration to include an index enforcing uniqueness:

db/migrate/xyz_add_uid_to_users.rb

class AddUidToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :uid, :string
    add_index :users, :uid, unique: true
  end
end

Now apply the migration:

$ rails db:migrate

Having this uid in place, we can set the token for the videos. This is as simple as providing the ziggeo-tags argument. Remember that it accepts a comma-separated string:

views/videos/new.html.erb

<ziggeo ziggeo-limit="60"
        ziggeo-width="320"
        ziggeo-height="240"
        ziggeo-perms="allowupload"
        ziggeo-tags="<%= current_user.uid %>"></ziggeo>

Great! Now in order to fetch only the current user’s videos, set the tags option for the index method:

videos_controller.rb

# ...
def index
  ziggeo = Ziggeo.new(ENV['ZIGGEO_KEY'], ENV['ZIGGEO_SECRET'], ENV['ZIGGEO_ENCRYPTION'])
  @videos = ziggeo.videos.index(tags: current_user.uid)
end

Nice, but we can do better. The problem with Ziggeo’s API is that there is no way to fetch only the videos that are approved by the moderator. That’s strange, but Ziggeo’s support team confirmed this information. Of course, we can filter the approved videos in our controller by using the keep_if method, but then if you’d like to employ a pagination mechanism, things will become pretty complex. Therefore, why don’t we set up server callbacks and store the videos’ information in our own database the way we see fit? Let’s do it in the next section!

Setting Up Callbacks

Preparations and Video Creation

Callbacks are configured per-application, so open your dashboard, choose an application, and click Manage > Web Hooks. Here type in a URL (I’ll go with https://sitepoint-ziggeo.herokuapp.com/api/video_callbacks) and choose “JSON encoding” from the dropdown. Now events will be forwarded to /api/video_callbacks in the form of a POST request. Here is the list of all callbacks that you can use. Note that not all events are forwarded – only the most important ones.

First of all, we want to track the addition of all videos, therefore a new model called Video will be required. It is going to contain the following fields:

  • uid (string, indexed, unique) – the video’s token
  • user_id (integer, indexed) – foreign key to establish the relation between a user and a video
  • duration (decimal) – the video’s duration in seconds
  • ziggeo_created_at (datetime) – date and time when the video was created on Ziggeo
  • approved (boolean, indexed) – whether a video was approved by moderator, default is false

Create the corresponding migration:

$ rails g model Video user:belongs_to uid:string duration:decimal ziggeo_created_at:datetime approved:boolean

Tweak the migration a bit:

db/migrate/xyz/create_videos.rb

# ...
create_table :videos do |t|
  t.string :uid
  t.belongs_to :user, foreign_key: true
  t.decimal :duration, scale: 2, precision: 5
  t.datetime :ziggeo_created_at
  t.boolean :approved, default: false

  t.timestamps
end

add_index :videos, :approved
add_index :videos, :uid, unique: true

Apply it:

$ rails db:migrate

Make sure the proper associations and validations are set:

models/user.rb

# ...
has_many :videos, dependent: :destroy

models/video.rb

# ...
belongs_to :user
validates :uid, presence: true, uniqueness: true

Now add a new route namespaced under :api:

config/routes.rb

namespace :api do
  resources :video_callbacks, only: [:create]
end

Create a new controller inside the api folder:

controllers/api/video_callbacks_controller.rb

class Api::VideoCallbacksController < ActionController::Base
  def create
  end
end

When a new event arrives, it has an event_type param set to some value. Currently we’ll be interested in the video_ready event. Lets just take the video’s data and create a new record based on it:

controllers/api/video_callbacks_controller.rb

class Api::VideoCallbacksController < ActionController::Base
  def create
    type = params['event_type']
        respond_to do |format|
          @result = if type == 'video_ready'
                      Video.from_api(params['data']['video'])
                    end
        end
  end
end

Video’s data is stored under the ['data']['video'] key.

Also respond with 204 (no content) status code if everything is okay, or with 500 (server error) if something has gone wrong:

controllers/api/video_callbacks_controller.rb

def create
  type = params['event_type']
  respond_to do |format|
    @result = if type == 'video_ready'
                Video.from_api(params['data']['video'])
              end
    format.html { @result ? head(:no_content) : head(500) }
  end
end

Now code the from_api class method. It should fetch a user based on the video’s tag (remember that we are utilizing the user’s UID as a tag) and create a new record that belongs to him:

models/video.rb

# ...
def self.from_api(data)
  user = User.find_by(uid: data['tags'][0])
  video = user.videos.find_or_initialize_by(uid: data['token'])
  video.ziggeo_created_at = Time.at(data['created'])
  video.duration = data['duration']
  video.save
end

tags contain an array, so we simply grab the first element. I’ve noticed that sometimes an event may be sent twice, so use find_or_initialize_by to avoid creation of duplicate records. Well, this won’t be possible as the index set for the uid enforces uniqueness, but still.

Video Approval

When a video is approved or rejected by a moderator, the corresponding event is sent as well. We will work with the video_approve event type. When it arrives, find the video in the database based on its token (uid) and set the approved attribute to true:

controllers/api/video_callbacks_controller.rb

def create
  type = params['event_type']
  respond_to do |format|
    @result = if type == 'video_ready'
                Video.from_api(params['data']['video'])
              else
                if type == 'video_approve'
                  video = Video.find_by(uid: params['data']['video']['token'])
                  video.approve! if video
                else
                  true
                end
              end
    format.html { @result ? head(:no_content) : head(500) }
  end
end

We simply assign true to the @result instance variable if the event is some other type. Here is the approve! method:

models/video.rb

# ...
def approve!
  self.approved = true
  self.save
end

Video Deletion

A video can be deleted using the Ziggeo dashboard. When it happens, we also want to remove this video from our database. The event type we are interested in is called video_delete. Once again, find the proper video and then just destroy it:

controllers/api/video_callbacks_controller.rb

# ...
def create
  type = params['event_type']
  respond_to do |format|
    @result = if type == 'video_ready'
                Video.from_api(params['data']['video'])
              else
                if type == 'video_approve' || type == 'video_delete'
                  video = Video.find_by(uid: params['data']['video']['token'])
                  if video
                    type == 'video_approve' ?
                        video.approve! :
                        video.destroy
                  end
                else
                  true
                end
              end
    format.html { @result ? head(:no_content) : head(500) }
  end
end

Nice! Now that we have these callbacks in place, the index action inside the VideosController can be re-written.

Displaying Videos and Meta Information

We don’t need Ziggeo client anymore inside the index action. Instead, simply grab the current user’s videos – only the ones that have been approved:

videos_controller.rb

# ...
def index
  @videos = current_user.videos.where(approved: true)
end

As long as each video now has additional meta information, we can render it on the main page as well:

views/videos/_video.html.erb

<div class="card">
  <div class="card-block">
    <ziggeo ziggeo-video='<%= video.uid %>'
            ziggeo-width="320"
            ziggeo-height="240" ziggeo-popup>
    </ziggeo>
    <p>
      <strong>Duration:</strong> <%= video.duration %>s<br>
      <strong>Created:</strong> <%= video.ziggeo_created_at  %>
    </p>
  </div>
</div>

Deleting Videos via API

The last piece of functionality we will code today is the ability to delete a video from our application. Only the user who owns the video will be able to perform this action.

First, present a “delete” link:

views/videos/_video.html.erb

<ziggeo ziggeo-video='<%= video.uid %>'
        ziggeo-width="320"
        ziggeo-height="240" ziggeo-popup>
</ziggeo>
<!-- ... -->
<p><%= link_to 'Delete', video_path(video.uid), method: :delete %></p>

Note that I am passing a video’s uid, not id – we won’t actually remove videos from the database inside the destroy action.

In order to remove a video, a Ziggeo API client is needed. The deletion is performed using the delete method that accepts a uid.

videos_controller.rb

# ...
def destroy
  video = current_user.videos.find_by(uid: params[:id])
  if video
    ziggeo = Ziggeo.new(ENV['ZIGGEO_KEY'], ENV['ZIGGEO_SECRET'], ENV['ZIGGEO_ENCRYPTION'])
    ziggeo.videos.delete(video.uid)
    flash[:success] = 'Video removed! It may take some time to reflect changes on the website.'
  else
    flash[:warning] = 'Cannot find such video...'
  end
  redirect_to root_path
end

We only remove a video from Ziggeo. After this operation is completed, a video_delete event will be sent to our callback and the corresponding record will be deleted from the database. This process is not instant, that’s why we are warning the user that it may take some time to reflect the change.

Conclusion

We’ve reached the end of this article! Ziggeo presents much more functionality then we went through today as we only discussed its basic features. Therefore, be sure to read more about this service and try it out for yourself. Also, note that Ziggeo is available as a Heroku add-on and can be integrated with such popular services as YouTube and Dropbox.

I hope you’ve enjoyed reading this article and I thank you for staying with me. Happy coding and see you!

Frequently Asked Questions (FAQs) about Video Uploads with Rails and Ziggeo

How can I integrate Ziggeo with Rails for video uploads?

Integrating Ziggeo with Rails for video uploads involves a few steps. First, you need to add the Ziggeo gem to your Gemfile and run the bundle install command. Next, you need to initialize Ziggeo with your application token, private key, and encryption key. You can then use the Ziggeo API to upload videos. The Ziggeo API provides methods for uploading videos, retrieving video information, and deleting videos. You can use these methods in your Rails controllers to handle video uploads and other video-related actions.

How can I display and play a video file in Rails?

To display and play a video file in Rails, you can use the HTML5 video tag. The video tag allows you to embed video content in a web page. You can specify the source of the video file using the src attribute. You can also include controls for play, pause, and volume using the controls attribute. Here’s an example of how to use the video tag to display a video in Rails:

<video controls>
<source src="<%= @video.url %>" type="video/mp4">
Your browser does not support the video tag.
</video>

In this example, @video.url is the URL of the video file. The type attribute specifies the format of the video file.

How can I handle file upload using Ruby on Rails 5 API?

Handling file upload using Ruby on Rails 5 API involves using the Active Storage framework. Active Storage provides methods for uploading files to a variety of cloud storage services, including Amazon S3, Google Cloud Storage, and Microsoft Azure Storage. To use Active Storage, you need to add the active_storage_blobs and active_storage_attachments tables to your database. You can then use the has_one_attached and has_many_attached methods in your models to associate files with records. You can use the attach method to attach a file to a record, and the attached? method to check if a file is attached to a record.

How can I create a VOD (Video on Demand) platform with Rails and FFmpeg?

Creating a VOD (Video on Demand) platform with Rails and FFmpeg involves several steps. First, you need to set up a Rails application and configure it to handle video uploads. You can use the Ziggeo API for this. Next, you need to install FFmpeg on your server. FFmpeg is a free and open-source software that can convert audio and video formats. You can use FFmpeg to convert uploaded videos to a standard format that can be played on most devices. You can also use FFmpeg to extract thumbnails from videos, which can be used as preview images on your VOD platform.

How can I use Rails API and Active Storage for file uploads?

Using Rails API and Active Storage for file uploads involves a few steps. First, you need to set up Active Storage in your Rails application. This involves running a migration to add the necessary tables to your database. Next, you need to associate files with records in your models using the has_one_attached or has_many_attached methods. You can then use the attach method in your controllers to attach files to records. You can use the attached? method to check if a file is attached to a record. Finally, you can use the url method to get the URL of an attached file, which can be used to display the file in a view.

Ilya Bodrov-KrukowskiIlya Bodrov-Krukowski
View Author

Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.

GlennGRuby on Railsvideo
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week