Skip to content

Commit

Permalink
Add an adapter for Shingoncoder
Browse files Browse the repository at this point in the history
Shingoncoder is a backend for transcoding videos locally using ActiveJob and Ffmpeg
https://github.com/jcoyne/shingoncoder
  • Loading branch information
jcoyne committed Sep 28, 2015
1 parent b5c359f commit a10e442
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ Metrics/MethodLength:
Exclude:
- 'lib/active_encode/engine_adapters/matterhorn_adapter.rb'
- 'lib/active_encode/engine_adapters/zencoder_adapter.rb'
- 'lib/active_encode/engine_adapters/shingoncoder_adapter.rb'

Metrics/ClassLength:
Exclude:
- 'lib/active_encode/engine_adapters/matterhorn_adapter.rb'
- 'lib/active_encode/engine_adapters/zencoder_adapter.rb'
- 'lib/active_encode/engine_adapters/shingoncoder_adapter.rb'

Metrics/CyclomaticComplexity:
Exclude:
- 'lib/active_encode/engine_adapters/matterhorn_adapter.rb'
- 'lib/active_encode/engine_adapters/zencoder_adapter.rb'
- 'lib/active_encode/engine_adapters/shingoncoder_adapter.rb'

Style/FileName:
Exclude:
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ gem 'rubocop', require: false
gem 'rubocop-rspec', require: false
gem 'rubyhorn', git: "https://github.com/avalonmediasystem/rubyhorn.git"
gem 'zencoder'
gem 'shingoncoder'
3 changes: 1 addition & 2 deletions active-encode.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "activesupport"
spec.add_dependency "activemodel"


spec.add_development_dependency "bundler", "~> 1.7"
spec.add_development_dependency "coveralls"
spec.add_development_dependency "rake", "~> 10.0"
Expand Down
1 change: 1 addition & 0 deletions lib/active_encode/engine_adapters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module EngineAdapters
autoload :MatterhornAdapter
autoload :InlineAdapter
autoload :ZencoderAdapter
autoload :ShingoncoderAdapter
autoload :TestAdapter

ADAPTER = 'Adapter'.freeze
Expand Down
56 changes: 56 additions & 0 deletions lib/active_encode/engine_adapters/shingoncoder_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require 'active_support'
require 'active_support/core_ext'

module ActiveEncode
module EngineAdapters
class ShingoncoderAdapter < ZencoderAdapter
# @param [ActiveEncode::Base] encode
def create(encode)
response = Shingoncoder::Job.create(input: encode.input)
build_encode(job_details(response.body["id"]), encode.class)
end

# @param [Fixnum] id
# @param [Hash] opts
# @option opts :cast the class to cast the encoding job to.
def find(id, opts = {})
build_encode(job_details(id), opts[:cast])
end

# @param [ActiveEncode::Base] encode
def cancel(encode)
response = Shingoncoder::Job.cancel(encode.id)
build_encode(job_details(encode.id), encode.class) if response.success?
end

private

# @param [Fixnum] job_id the identifier for the job
# @return [Shingoncoder::Response] the response from Shingoncoder
def job_details(job_id)
Shingoncoder::Job.details(job_id)
end

# @return [Shingoncoder::Response] the response from Shingoncoder
def job_progress(job_id)
Shingoncoder::Job.progress(job_id)
end

# @param [Shingoncoder::Response] job_details
# @param [Class] cast the class of object to instantiate and return
def build_encode(job_details, cast)
return nil if job_details.nil?
encode = cast.new(convert_input(job_details), convert_options(job_details))
encode.id = job_details.body["job"]["id"].to_s
encode.state = convert_state(job_details)
progress = job_progress(encode.id)
encode.current_operations = convert_current_operations(progress)
encode.percent_complete = convert_percent_complete(progress, job_details)
encode.output = convert_output(job_details)
encode.errors = convert_errors(job_details)
encode.tech_metadata = convert_tech_metadata(job_details.body["job"]["input_media_file"])
encode
end
end
end
end
152 changes: 152 additions & 0 deletions spec/integration/shingoncoder_adapter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
require 'spec_helper'
require 'shingoncoder'
require 'json'

describe ActiveEncode::EngineAdapters::ShingoncoderAdapter do
before(:all) do
ActiveEncode::Base.engine_adapter = :shingoncoder
end
after(:all) do
ActiveEncode::Base.engine_adapter = :inline
end

let(:create_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_create.json'))) }

before do
allow(Shingoncoder::Job).to receive(:create).and_return(create_response)
end

let(:file) { "file://#{File.absolute_path('spec/fixtures/Bars_512kb.mp4')}" }

describe "#create" do
before do
allow(Shingoncoder::Job).to receive(:details).and_return(details_response)
allow(Shingoncoder::Job).to receive(:progress).and_return(progress_response)
end

let(:details_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_details_create.json'))) }
let(:progress_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_progress_create.json'))) }
let(:create_output) { [{ id: "511404522", url: "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20150610/c09b61e4d130ddf923f0653418a80b9c/399ae101c3f99b4f318635e78a4e587a.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=GY/9LMkQAiDOrMQwS5BkmOE200s%3D&Expires=1434033527", label: nil }] }

subject { ActiveEncode::Base.create(file) }
it { is_expected.to be_a ActiveEncode::Base }
its(:id) { is_expected.not_to be_empty }
it { is_expected.to be_running }
its(:output) { is_expected.to eq create_output }
its(:current_operations) { is_expected.to be_empty }
its(:percent_complete) { is_expected.to eq 0 }
its(:errors) { is_expected.to be_empty }
its(:tech_metadata) { is_expected.to be_empty }
end

describe "#find" do
before do
allow(Shingoncoder::Job).to receive(:details).and_return(details_response)
allow(Shingoncoder::Job).to receive(:progress).and_return(progress_response)
end

context "a running encode" do
let(:details_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_details_running.json'))) }
let(:progress_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_progress_running.json'))) }
let(:running_output) { [{ id: "510582971", url: "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20150609/48a6907086c012f68b9ca43461280515/1726d7ec3e24f2171bd07b2abb807b6c.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=vSvlxU94wlQLEbpG3Zs8ibp4MoY%3D&Expires=1433953106", label: nil }] }
let(:running_tech_metadata) { { audio_bitrate: "52", audio_codec: "aac", audio_channels: "2", duration: "57992", mime_type: "mpeg4", video_framerate: "29.97", height: "240", video_bitrate: "535", video_codec: "h264", width: "320" } }

subject { ActiveEncode::Base.find('166019107') }
it { is_expected.to be_a ActiveEncode::Base }
its(:id) { is_expected.to eq '166019107' }
it { is_expected.to be_running }
its(:output) { is_expected.to eq running_output }
its(:current_operations) { is_expected.to be_empty }
its(:percent_complete) { is_expected.to eq 30.0 }
its(:errors) { is_expected.to be_empty }
its(:tech_metadata) { is_expected.to eq running_tech_metadata }
end

context "a cancelled encode" do
let(:details_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_details_cancelled.json'))) }
let(:progress_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_progress_cancelled.json'))) }

subject { ActiveEncode::Base.find('165866551') }
it { is_expected.to be_a ActiveEncode::Base }
its(:id) { is_expected.to eq '165866551' }
it { is_expected.to be_cancelled }
its(:current_operations) { is_expected.to be_empty }
its(:percent_complete) { is_expected.to eq 0 }
its(:errors) { is_expected.to be_empty }
its(:tech_metadata) { is_expected.to be_empty }
end

context "a completed encode" do
let(:details_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_details_completed.json'))) }
let(:progress_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_progress_completed.json'))) }
let(:completed_output) { { id: "509856876", audio_bitrate: "53", audio_codec: "aac", audio_channels: "2", duration: "5000", mime_type: "mpeg4", video_framerate: "29.97", height: "240", video_bitrate: "549", video_codec: "h264", width: "320", url: "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20150608/ebbe865f8ef1b960d7c2bb0663b88a12/0f1948dcb2fd701fba30ff21908fe460.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=1LgIyl/el9E7zeyPxzd/%2BNwez6Y%3D&Expires=1433873646", label: nil } }
let(:completed_tech_metadata) { { audio_bitrate: "52", audio_codec: "aac", audio_channels: "2", duration: "57992", mime_type: "mpeg4", video_framerate: "29.97", height: "240", video_bitrate: "535", video_codec: "h264", width: "320" } }

subject { ActiveEncode::Base.find('165839139') }
it { is_expected.to be_a ActiveEncode::Base }
its(:id) { is_expected.to eq '165839139' }
it { is_expected.to be_completed }
its(:output) { is_expected.to include completed_output }
its(:current_operations) { is_expected.to be_empty }
its(:percent_complete) { is_expected.to eq 100 }
its(:errors) { is_expected.to be_empty }
its(:tech_metadata) { is_expected.to eq completed_tech_metadata }
end

context "a failed encode" do
let(:details_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_details_failed.json'))) }
let(:progress_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_progress_failed.json'))) }
let(:failed_tech_metadata) { { mime_type: "video/mp4", checksum: "7ae24368ccb7a6c6422a14ff73f33c9a", duration: "6314", audio_codec: "AAC", audio_channels: "2", audio_bitrate: "171030.0", video_codec: "AVC", video_bitrate: "74477.0", video_framerate: "23.719", width: "200", height: "110" } }
let(:failed_errors) { "The file is an XML file, and doesn't contain audio or video tracks." }

subject { ActiveEncode::Base.find('166079902') }
it { is_expected.to be_a ActiveEncode::Base }
its(:id) { is_expected.to eq '166079902' }
it { is_expected.to be_failed }
its(:current_operations) { is_expected.to be_empty }
its(:percent_complete) { is_expected.to eq 0 }
its(:errors) { is_expected.to include failed_errors }
its(:tech_metadata) { is_expected.to be_empty }
end
end

describe "#cancel!" do
before do
allow(Shingoncoder::Job).to receive(:cancel).and_return(cancel_response)
allow(Shingoncoder::Job).to receive(:details).and_return(details_response)
allow(Shingoncoder::Job).to receive(:progress).and_return(progress_response)
end

let(:cancel_response) { Shingoncoder::Response.new(code: 200) } # TODO: check that this is the correct response code for a successful cancel
let(:details_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_details_cancelled.json'))) }
let(:progress_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_progress_cancelled.json'))) }

let(:encode) { ActiveEncode::Base.create(file) }
subject { encode.cancel! }
it { is_expected.to be_a ActiveEncode::Base }
its(:id) { is_expected.to eq '165866551' }
it { is_expected.to be_cancelled }
end

describe "reload" do
before do
allow(Shingoncoder::Job).to receive(:details).and_return(details_response)
allow(Shingoncoder::Job).to receive(:progress).and_return(progress_response)
end

let(:details_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_details_running.json'))) }
let(:progress_response) { Shingoncoder::Response.new(body: JSON.parse(File.read('spec/fixtures/zencoder/job_progress_running.json'))) }
let(:reload_output) { [{ id: "510582971", url: "https://zencoder-temp-storage-us-east-1.s3.amazonaws.com/o/20150609/48a6907086c012f68b9ca43461280515/1726d7ec3e24f2171bd07b2abb807b6c.mp4?AWSAccessKeyId=AKIAI456JQ76GBU7FECA&Signature=vSvlxU94wlQLEbpG3Zs8ibp4MoY%3D&Expires=1433953106", label: nil }] }
let(:reload_tech_metadata) { { audio_bitrate: "52", audio_codec: "aac", audio_channels: "2", duration: "57992", mime_type: "mpeg4", video_framerate: "29.97", height: "240", video_bitrate: "535", video_codec: "h264", width: "320" } }

subject { ActiveEncode::Base.find('166019107').reload }
it { is_expected.to be_a ActiveEncode::Base }
its(:id) { is_expected.to eq '166019107' }
it { is_expected.to be_running }
its(:output) { is_expected.to eq reload_output }
its(:current_operations) { is_expected.to be_empty }
its(:percent_complete) { is_expected.to eq 30.0 }
its(:errors) { is_expected.to be_empty }
its(:tech_metadata) { is_expected.to eq reload_tech_metadata }
end
end

0 comments on commit a10e442

Please sign in to comment.