Tạo nhiều version chất lượng cho video như Youtube mà không làm ảnh hưởng performance của web-app

Trong các loại assets của web-app thì video là một trong những loại asests nặng và chiếm nhiều băng thông nhất. Vì vậy, để cung ứng được nhiều người sử dụng hơn thì tạo nhiều version chất lượng cho video như cách Youtube đã làm là một điều rất cần. Nhưng việc xử lý video thường tốn nhiều thời gian và có thể dẫn đến request time out, và bản thân mình cũng đã gặp phải chuyện này trong dự án thực tế, vì vậy hôm nay mình sẽ giới thiệu tới các bạn cách để tạo nhiều version cho video mà không ảnh hưởng đến performance của web-app. Ý tưởng chính của cách thức này là tạo ra 2 hay nhiều version nhưng chưa xử lý gì, sau khi khi video đã được upload lên server(ví dụ S3 Amazone) thì ta sẽ thực hiện xử lý video trong background job và tiếp theo replace file trên server.

1. Tạo ra nhiều version chưa được xử lý

Đầu tiên ta cần add gem "carrierwave" để upload file, gem "aws-sdk" và "fog" nếu bạn lưu video ở S3-server, gem "streamio-ffmpeg" và cài đặt ffmpeg để có thể dùng thư viện này xử lý video

Gemfile gem "carrierwave" gem "streamio-ffmpeg" gem "fog" gem "aws-sdk", "~> 3" gem "fog-aws" 

trên terminal

bundle install  sudo apt-get update sudo apt-get dist-upgrade sudo apt-get install ffmpeg 

khai báo version và chưa xử lý, ở đây mình sẽ tạo 2 version là medium và low

class VideoUploader < CarrierWave::Uploader::Base    version :medium do   end    version :low do   end 

mount uploader vào trường lưu video trong DB

class Video < ApplicationRecord   mount_uploader :video_file, VideoUploader end 

2. thực hiện xử lý video ở background job

Ở đây, mình sẽ một worker để xử lý video, worker này sẽ lấy ra tất cả version được khai báo trong uploader và gọi đến một service để xư lý đã có lần version một.

video_process/create_version_worker.rb class VideoProcess::CreateVersionWorker   include Sidekiq::Worker    def perform video_id     video = Video.find video_id     VideoUploader.versions.keys.each do |version|        VideoProcess::CreateVersionService.new(video: video, version: version,         resolution: Settings.video_version.to_h[version], preserve_aspect_ratio: :height).perform     end   end end 

Trong đoạn vận chuyển tận nhàe trên mình chọn preserve_aspect_ratio là height, có nghĩa là mình sẽ resize chiều cao về kích cỡ được đặt trong settings.yml còn chiều rộng sẽ được resize theo tỉ lệ của video được upload lên
quan tâm, bạn cần lưu kích cỡ các version ở settings.yml hợp lý để có thể gọi theo tên version. Ví dụ đây là file settings.yml của mình:

video_version:   medium: "858x480"   low: "427x240"   height:     medium: 480     low: 240 

tiếp nữa mình sẽ tạo service để xử lý video, đây sẽ là nơi chưa những đoạn vận chuyển tận nhàe trọng yếu nhất để xử lý video

class VideoProcess::CreateVersionService   require "aws-sdk"   attr_reader :video, :version, :resolution, :preserve_aspect_ratio    def initialize args     @video = args[:video].decorate     @version = args[:version].to_sym     @resolution = args[:resolution]     @preserve_aspect_ratio = args[:preserve_aspect_ratio]   end    def perform   end end 

tiếp nữa mình sẽ tạo một thư mục tạm trong thư mục tmp để lưu video mà ta đã xử lý được. Và tiếp theo mình sẽ tạo một object FFMPEG của video đang cần xử lý, trong tình thế này mình viết một hàm tên là store_path để lấy ra url của video hoặc path của video tuỳ theo môi trường lưu video là S3 hay local, cụ thể hàm mình viết ở phía dưới. tiếp theo mình thực hiện xử lý video và lưu ở thư mục tạm nếu độ phân giải của video lớn hơn độ phân giải của version.

  def perform     Dir.mkdir(Rails.root.join "tmp/video_version") unless File.exists?(Rails.root.join "tmp/video_version")     ffmpeg = ::FFMPEG::Movie.new(video.store_path)     if ffmpeg.height > Settings.video_version.height.to_h[version]       path = video.video_file.versions[version].path       tmp_path = "tmp/video_version/#{File.basename video.video_file_url(version)}"       ffmpeg.transvận chuyển tận nhàe(tmp_path, {resolution: resolution}, {preserve_aspect_ratio: :height})     end   end 
model/video.rb   def store_path version = nil     if version.nil?       (ENV["CDN_UPLOADER"] == "true") ? video_file_url : video_file.path     else       (ENV["CDN_UPLOADER"] == "true") ? video_file_url(version) : video_file.versions[version].path     end   end 

3. Replace file cũ bằng file video đã được xử lý

Phần này mình sẽ tách một hàm private để cho dễ nhìn. Đối với video được lưu ở local thì chúng ra dễ làm chỉ cần dùng hàm rename của class File, hàm này sẽ tự động move file ở thư mục tạm và replace vào địa chỉ mới mà ta truyền vào. Còn đối với video được lưu ở trên S3-Server thì ta phải tạo object của Aws-SDK, upload file với option acl: "public-read"(để video được public) rồi tiếp theo xoá file video ở thư mục tạm.

  class VideoProcess::CreateVersionService   ...   private   def update_version path, tmp_path     if ENV["CDN_UPLOADER"] == "true"       File.rename tmp_path, path     else       s3 = Aws::S3::Resource.new region: ENV["AWS_REGION"]       obj = s3.bucket(ENV["S3_BUCKET_NAME"]).object(path)       obj.upload_file Rails.root.join(tmp_path), acl: "public-read"       Rails.root.join(tmp_path).delete     end   end 

tiếp theo mình gọi hàm này trong hàm perform của service và gọi worker ở trong controller. Thế là ta đã hoàn thành việc xử lý video

class VideoProcess::CreateVersionService ... def perform     Dir.mkdir(Rails.root.join "tmp/video_version") unless File.exists?(Rails.root.join "tmp/video_version")     ffmpeg = ::FFMPEG::Movie.new(video.store_path)     if ffmpeg.height > Settings.video_version.height.to_h[version]       path = video.video_file.versions[version].path       tmp_path = "tmp/video_version/#{File.basename video.video_file_url(version)}"       ffmpeg.transvận chuyển tận nhàe(tmp_path, {resolution: resolution}, {preserve_aspect_ratio: :height})              update_version path, tmp_path     end   end   videos_controller.rb VideosController < ApplicationController   def create     ...     VideoProcess::CreateVersionWorker.perform_async video.id   end end 

Bài viết của mình đến đây là hết. Cảm ơn các bạn đã theo dõi.

Nguồn viblo.asia