diff --git a/.gitignore b/.gitignore index 9b73c89..b823f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ mkmf.log coverage .ruby-version +.byebug_history diff --git a/.rubocop.yml b/.rubocop.yml index 9cd8c7f..05b03ce 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -19,25 +19,18 @@ Metrics/AbcSize: 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' + - 'lib/active_encode/engine_adapters/*' 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' + - 'lib/active_encode/engine_adapters/*' 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' + - 'lib/active_encode/engine_adapters/*' Style/FileName: Exclude: - - 'lib/active-encode.rb' # Remove this when https://github.com/projecthydra-labs/active-encode/issues/1 is fixed Style/IndentationConsistency: EnforcedStyle: rails diff --git a/Gemfile b/Gemfile index 2dab2c5..4d5244c 100644 --- a/Gemfile +++ b/Gemfile @@ -9,3 +9,4 @@ gem 'rubocop-rspec', require: false gem 'rubyhorn', git: "https://github.com/avalonmediasystem/rubyhorn.git" gem 'zencoder' gem 'shingoncoder' +gem 'aws-sdk' diff --git a/README.md b/README.md index 23b802a..0584f77 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,14 @@ ActiveEncode::Base.create(File.open('spec/fixtures/Bars_512kb.mp4')) Create returns an encoding job that has been submitted to the encoding engine for processing. At this point it will have an id, a state, the input, and any additional information the encoding engine returns. ```ruby -#"avalon", :stream_base=>"file:///home/cjcolvar/Code/avalon/avalon/red5/webapps/avalon/streams"}, @id="12154", @state=:running, @current_operations=[], @percent_complete=0.0, @output=[], @errors=[], @tech_metadata={}> +#"avalon", :stream_base=>"file:///home/cjcolvar/Code/avalon/avalon/red5/webapps/avalon/streams"}, @id="12154", @state=:running, @current_operations=[], @percent_complete=0.0, @output=[], @errors=[], @tech_metadata={}> ``` ```ruby encode.id # "12103" encode.state # :running ``` -This encode can be looked back up later using #find. Alternatively, use #reload to refresh an instance with the latest information from the +This encode can be looked back up later using #find. Alternatively, use #reload to refresh an instance with the latest information from the ```ruby encode = ActiveEncode::Base.find("12103") @@ -85,17 +85,15 @@ end Engine adapters are shims between ActiveEncode and the back end encoding service. Each service has its own API and idiosyncracies so consult the table below to see what features are supported by each adapter. Add an additional engines by creating an engine adapter class that implements :create, :find, :list, :cancel, :purge, and :remove_output. -| Feature | Matterhorn Adapter | Zencoder Adapter (prototype) | Shingoncoder | Inline Adapter (In progress) | Test Adapter | -| --- | --- | --- | --- | --- | -| Create | X | X | X | X | X | -| Find | X | X | X | X | X | -| List | | | | | | -| Cancel | X | X | | | X | -| Purge | X | | | | X | -| Remove output | X | | | | | -| Preset | X | | | | | -| Multiple outputs | X (via preset) | | | | | - +| Adapter/Feature | Create | Find | List | Cancel | Purge | Remove Output | Preset | Multiple Outputs | +|--------------------------|--------|------|------|--------|-------|---------------|--------|------------------| +| Matterhorn | X | X | | X | X | X | X | X | +| Zencoder | X | X | | X | | | | | +| Shingoncoder (prototype) | X | X | | | | | | | +| Shingoncoder | X | X | | | | | | | +| AWS Elastic Transcoder | X | X | | X | | | | | +| Inline | X | X | | | | | | | +| Test | X | X | | X | | X | | | ## Contributing diff --git a/lib/active_encode/engine_adapters.rb b/lib/active_encode/engine_adapters.rb index 76b8222..ac44dd5 100644 --- a/lib/active_encode/engine_adapters.rb +++ b/lib/active_encode/engine_adapters.rb @@ -13,6 +13,7 @@ module EngineAdapters autoload :InlineAdapter autoload :ZencoderAdapter autoload :ShingoncoderAdapter + autoload :ElasticTranscoderAdapter autoload :TestAdapter ADAPTER = 'Adapter'.freeze diff --git a/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb b/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb new file mode 100644 index 0000000..d1ad2ba --- /dev/null +++ b/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb @@ -0,0 +1,152 @@ +module ActiveEncode + module EngineAdapters + class ElasticTranscoderAdapter + # TODO: add a stub for an input helper (supplied by an initializer) that transforms encode.input into a zencoder accepted url + def create(encode) + job = client.create_job( + input: { key: encode.input }, + pipeline_id: encode.options[:pipeline_id], + output_key_prefix: encode.options[:output_key_prefix], + outputs: encode.options[:outputs], + user_metadata: encode.options[:user_metadata] + ).job + + build_encode(get_job_details(job.id), encode.class) + end + + def find(id, opts = {}) + build_encode(get_job_details(id), opts[:cast]) + end + + # TODO: implement list_jobs_by_pipeline and list_jobs_by_status + def list(*_filters) + raise NotImplementedError + end + + # Can only cancel jobs with status = "Submitted" + def cancel(encode) + response = client.cancel_job(id: encode.id) + build_encode(get_job_details(encode.id), encode.class) if response.successful? + end + + def purge(_encode) + raise NotImplementedError + end + + def remove_output(_encode, _output_id) + raise NotImplementedError + end + + private + + # Needs region and credentials setup per http://docs.aws.amazon.com/sdkforruby/api/Aws/ElasticTranscoder/Client.html + def client + @client ||= Aws::ElasticTranscoder::Client.new + end + + def get_job_details(job_id) + client.read_job(id: job_id).job + end + + def build_encode(job, cast) + return nil if job.nil? + encode = cast.new(convert_input(job), convert_options(job)) + encode.id = job.id + encode.state = convert_state(job) + encode.current_operations = convert_current_operations(job) + encode.percent_complete = convert_percent_complete(job) + encode.created_at = convert_time(job.timing["submit_time_millis"]) + encode.updated_at = convert_time(job.timing["start_time_millis"]) + encode.finished_at = convert_time(job.timing["finish_time_millis"]) + encode.output = convert_output(job) + encode.errors = convert_errors(job) + encode.tech_metadata = convert_tech_metadata(job.input.detected_properties) + encode + end + + def convert_time(time_millis) + return nil if time_millis.nil? + Time.at(time_millis / 1000).iso8601 + end + + def convert_state(job) + case job.status + when "Submitted", "Progressing" # Should there be a queued state? + :running + when "Canceled" + :cancelled + when "Error" + :failed + when "Complete" + :completed + end + end + + def convert_current_operations(_job) + current_ops = [] + current_ops + end + + def convert_percent_complete(job) + case job.status + when "Submitted" + 10 + when "Progressing" + 50 + when "Complete" + 100 + else + 0 + end + end + + def convert_input(job) + job.input + end + + def convert_options(_job_details) + {} + end + + def convert_output(job) + output = [] + job.outputs.each do |o| + # It is assumed that the first part of the output key can be used to label the output + # e.g. "quality-medium/somepath/filename.flv" + label = o.key.split("/", 2).first + url = job.output_key_prefix + o.key + extras = { id: o.id, url: url, label: label } + extras[:hls_url] = url + ".m3u8" if url.include?("/hls/") # TODO: find a better way to signal hls + output << convert_tech_metadata(o).merge(extras) + end + output + end + + def convert_errors(job) + job.outputs.select { |o| o.status == "Error" }.collect(&:status_detail).compact + end + + def convert_tech_metadata(props) + return {} if props.nil? || props.empty? + metadata_fields = { + file_size: { key: :file_size, method: :itself }, + duration_millis: { key: :duration, method: :to_s }, + frame_rate: { key: :video_framerate, method: :itself }, + segment_duration: { key: :segment_duration, method: :itself }, + width: { key: :width, method: :itself }, + height: { key: :height, method: :itself } + } + + metadata = {} + props.each_pair do |key, value| + next if value.nil? + conversion = metadata_fields[key.to_sym] + next if conversion.nil? + metadata[conversion[:key]] = value.send(conversion[:method]) + end + + metadata + end + end + end +end diff --git a/lib/active_encode/version.rb b/lib/active_encode/version.rb index 76caaa3..0d1de2d 100644 --- a/lib/active_encode/version.rb +++ b/lib/active_encode/version.rb @@ -1,3 +1,3 @@ module ActiveEncode - VERSION = '0.0.3'.freeze + VERSION = '0.1.0'.freeze end diff --git a/spec/fixtures/elastic_transcoder/input_completed.json b/spec/fixtures/elastic_transcoder/input_completed.json new file mode 100644 index 0000000..934467f --- /dev/null +++ b/spec/fixtures/elastic_transcoder/input_completed.json @@ -0,0 +1 @@ +{"key":"somefile.mp4","detected_properties":{"width":1280,"height":720,"frame_rate":"25","file_size":21069678,"duration_millis":117312}} diff --git a/spec/fixtures/elastic_transcoder/input_generic.json b/spec/fixtures/elastic_transcoder/input_generic.json new file mode 100644 index 0000000..1e1d246 --- /dev/null +++ b/spec/fixtures/elastic_transcoder/input_generic.json @@ -0,0 +1 @@ +{"key":"somefile.mp4"} diff --git a/spec/fixtures/elastic_transcoder/input_progressing.json b/spec/fixtures/elastic_transcoder/input_progressing.json new file mode 100644 index 0000000..934467f --- /dev/null +++ b/spec/fixtures/elastic_transcoder/input_progressing.json @@ -0,0 +1 @@ +{"key":"somefile.mp4","detected_properties":{"width":1280,"height":720,"frame_rate":"25","file_size":21069678,"duration_millis":117312}} diff --git a/spec/fixtures/elastic_transcoder/job_canceled.json b/spec/fixtures/elastic_transcoder/job_canceled.json new file mode 100644 index 0000000..93c9db4 --- /dev/null +++ b/spec/fixtures/elastic_transcoder/job_canceled.json @@ -0,0 +1 @@ +{"id":"1471963629141-kmcocm","arn":"arn:aws:elastictranscoder:us-west-2:039358184980:job/1471963629141-kmcocm","pipeline_id":"1470797373927-1ukxrn","output_key_prefix":"elastic-transcoder-samples/output/hls/","playlists":[],"status":"Canceled","timing":{"submit_time_millis":1471963629189,"finish_time_millis":1471975185271}} diff --git a/spec/fixtures/elastic_transcoder/job_completed.json b/spec/fixtures/elastic_transcoder/job_completed.json new file mode 100644 index 0000000..d07c8bd --- /dev/null +++ b/spec/fixtures/elastic_transcoder/job_completed.json @@ -0,0 +1 @@ +{"id":"1471963629141-kmcocm","arn":"arn:aws:elastictranscoder:us-west-2:039358184980:job/1471963629141-kmcocm","pipeline_id":"1470797373927-1ukxrn","output_key_prefix":"elastic-transcoder-samples/output/hls/","playlists":[],"status":"Complete","timing":{"submit_time_millis":1471963629189,"start_time_millis":1471975169865,"finish_time_millis":1471975185271}} diff --git a/spec/fixtures/elastic_transcoder/job_created.json b/spec/fixtures/elastic_transcoder/job_created.json new file mode 100644 index 0000000..12b2a6f --- /dev/null +++ b/spec/fixtures/elastic_transcoder/job_created.json @@ -0,0 +1 @@ +{"id":"1471963629141-kmcocm","arn":"arn:aws:elastictranscoder:us-west-2:039358184980:job/1471963629141-kmcocm","pipeline_id":"1470797373927-1ukxrn","input":{"key":"somefile.mp4"},"output":{"id":"1","key":"hlsAudio/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a","preset_id":"1351620000001-200071","segment_duration":"2.0","status":"Submitted","watermarks":[]},"output_key_prefix":"elastic-transcoder-samples/output/hls/","playlists":[],"status":"Submitted","timing":{"submit_time_millis":1471963629189}} diff --git a/spec/fixtures/elastic_transcoder/job_failed.json b/spec/fixtures/elastic_transcoder/job_failed.json new file mode 100644 index 0000000..edaee11 --- /dev/null +++ b/spec/fixtures/elastic_transcoder/job_failed.json @@ -0,0 +1 @@ +{"id":"1471963629141-kmcocm","arn":"arn:aws:elastictranscoder:us-west-2:039358184980:job/1471963629141-kmcocm","pipeline_id":"1470797373927-1ukxrn","output_key_prefix":"elastic-transcoder-samples/output/hls/","playlists":[],"status":"Error","timing":{"submit_time_millis":1471963629189,"start_time_millis":1471975169865,"finish_time_millis":1471975185271}} diff --git a/spec/fixtures/elastic_transcoder/job_progressing.json b/spec/fixtures/elastic_transcoder/job_progressing.json new file mode 100644 index 0000000..7fb8948 --- /dev/null +++ b/spec/fixtures/elastic_transcoder/job_progressing.json @@ -0,0 +1 @@ +{"id":"1471963629141-kmcocm","arn":"arn:aws:elastictranscoder:us-west-2:039358184980:job/1471963629141-kmcocm","pipeline_id":"1470797373927-1ukxrn","output_key_prefix":"elastic-transcoder-samples/output/hls/","playlists":[],"status":"Progressing","timing":{"submit_time_millis":1471963629189,"start_time_millis":1471975169865}} diff --git a/spec/fixtures/elastic_transcoder/output_canceled.json b/spec/fixtures/elastic_transcoder/output_canceled.json new file mode 100644 index 0000000..9f5376d --- /dev/null +++ b/spec/fixtures/elastic_transcoder/output_canceled.json @@ -0,0 +1 @@ +{"id":"2","key":"hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a","preset_id":"1351620000001-200050","segment_duration":"2.0","status":"Canceled","watermarks":[]} diff --git a/spec/fixtures/elastic_transcoder/output_completed.json b/spec/fixtures/elastic_transcoder/output_completed.json new file mode 100644 index 0000000..98ded1c --- /dev/null +++ b/spec/fixtures/elastic_transcoder/output_completed.json @@ -0,0 +1 @@ +{"id":"2","key":"hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a","preset_id":"1351620000001-200050","segment_duration":"2.0","status":"Complete","status_detail":"The input file for this job contains 6 audio channels and the preset is configured for 2 audio channels. Amazon Elastic Transcoder audio-channel mapping may not result in the desired audio. Some individual segment files for this output have a higher bit rate than the average bit rate of the transcoded media. Playlists including this output will record a higher bit rate than the rate specified by the preset.","duration":118,"width":400,"height":224,"frame_rate":"25","file_size":6901104,"duration_millis":117353,"watermarks":[]} diff --git a/spec/fixtures/elastic_transcoder/output_failed.json b/spec/fixtures/elastic_transcoder/output_failed.json new file mode 100644 index 0000000..d911ce1 --- /dev/null +++ b/spec/fixtures/elastic_transcoder/output_failed.json @@ -0,0 +1 @@ +{"id":"2","key":"hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a","preset_id":"1351620000001-200050","segment_duration":"2.0","status":"Error","status_detail":"3002 0419623b-342f-4bde-9e3a-439477a95f2c: The specified object could not be saved in the specified bucket because an object by that name already exists: bucket=elasticbeanstalk-us-west-2-039358184980, key=elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a00000.ts.","watermarks":[]} diff --git a/spec/fixtures/elastic_transcoder/output_progressing.json b/spec/fixtures/elastic_transcoder/output_progressing.json new file mode 100644 index 0000000..fba2cf7 --- /dev/null +++ b/spec/fixtures/elastic_transcoder/output_progressing.json @@ -0,0 +1 @@ +{"id":"2","key":"hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a","preset_id":"1351620000001-200050","segment_duration":"2.0","status":"Progressing","watermarks":[]} diff --git a/spec/fixtures/elastic_transcoder/output_submitted.json b/spec/fixtures/elastic_transcoder/output_submitted.json new file mode 100644 index 0000000..a603a00 --- /dev/null +++ b/spec/fixtures/elastic_transcoder/output_submitted.json @@ -0,0 +1 @@ +{"id":"2","key":"hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a","preset_id":"1351620000001-200050","segment_duration":"2.0","status":"Submitted","watermarks":[]} diff --git a/spec/integration/elastic_transcoder_adapter_spec.rb b/spec/integration/elastic_transcoder_adapter_spec.rb new file mode 100644 index 0000000..b67906e --- /dev/null +++ b/spec/integration/elastic_transcoder_adapter_spec.rb @@ -0,0 +1,207 @@ +require 'spec_helper' +require 'aws-sdk' +require 'json' + +describe ActiveEncode::EngineAdapters::ElasticTranscoderAdapter do + before(:all) do + ActiveEncode::Base.engine_adapter = :elastic_transcoder + end + after(:all) do + ActiveEncode::Base.engine_adapter = :inline + end + + let(:client) { double(Aws::ElasticTranscoder::Client) } + + before do + allow_any_instance_of(ActiveEncode::EngineAdapters::ElasticTranscoderAdapter).to receive(:client).and_return(client) + allow(client).to receive(:read_job).and_return(Aws::ElasticTranscoder::Types::ReadJobResponse.new(job: job)) + allow(client).to receive(:create_job).and_return(Aws::ElasticTranscoder::Types::CreateJobResponse.new(job: job_created)) + end + + let(:job_created) do + j = Aws::ElasticTranscoder::Types::Job.new JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_created.json')) + j.input = Aws::ElasticTranscoder::Types::JobInput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/input_generic.json'))) + j.outputs = [ Aws::ElasticTranscoder::Types::JobOutput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/output_submitted.json')))] + j + end + + describe "#create" do + let(:job) { job_created } + let(:create_output) { [{id: "2", url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", hls_url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a.m3u8", label: "hls0400k", segment_duration: "2.0"}] } + + subject { ActiveEncode::Base.create( + "somefile.mp4", + pipeline_id: "1471963629141-kmcocm", + output_key_prefix: "elastic-transcoder-samples/output/hls/", + outputs: [{ + key: 'hls0400k/' + "e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", + preset_id: "1351620000001-200050", + segment_duration: "2" + }]) + } + + 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 10 } + its(:errors) { is_expected.to be_empty } + its(:created_at) { is_expected.to be_the_same_time_as '2016-08-23T10:47:09-04:00' } + its(:updated_at) { is_expected.to be_nil } + its(:finished_at) { is_expected.to be_nil } + its(:tech_metadata) { is_expected.to be_empty } + end + + describe "#find" do + context "a running encode" do + let(:job) do + j = Aws::ElasticTranscoder::Types::Job.new JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_progressing.json')) + j.input = Aws::ElasticTranscoder::Types::JobInput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/input_progressing.json'))) + j.outputs = [ Aws::ElasticTranscoder::Types::JobOutput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/output_progressing.json')))] + j + end + + let(:running_output) { [{id: "2", url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", hls_url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a.m3u8", label: "hls0400k", :segment_duration=>"2.0"}] } + let(:running_tech_metadata) { {:width=>1280, :height=>720, :video_framerate=>"25", :file_size=>21069678, :duration=>"117312"} } + + subject { ActiveEncode::Base.find('1471963629141-kmcocm') } + it { is_expected.to be_a ActiveEncode::Base } + its(:id) { is_expected.to eq '1471963629141-kmcocm' } + 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 50 } + its(:errors) { is_expected.to be_empty } + its(:created_at) { is_expected.to be_the_same_time_as '2016-08-23T10:47:09-04:00' } + its(:updated_at) { is_expected.to be_the_same_time_as '2016-08-23T13:59:29-04:00' } + its(:finished_at) { is_expected.to be_nil } + its(:tech_metadata) { is_expected.to eq running_tech_metadata } + end + + context "a canceled encode" do + let(:job) do + j = Aws::ElasticTranscoder::Types::Job.new JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_canceled.json')) + j.input = Aws::ElasticTranscoder::Types::JobInput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/input_generic.json'))) + j.outputs = [ Aws::ElasticTranscoder::Types::JobOutput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/output_canceled.json')))] + j + end + + subject { ActiveEncode::Base.find('1471963629141-kmcocm') } + it { is_expected.to be_a ActiveEncode::Base } + its(:id) { is_expected.to eq '1471963629141-kmcocm' } + 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(:created_at) { is_expected.to be_the_same_time_as '2016-08-23T10:47:09-04:00' } + its(:updated_at) { is_expected.to be_nil } + its(:finished_at) { is_expected.to be_the_same_time_as '2016-08-23T13:59:45-04:00' } + its(:tech_metadata) { is_expected.to be_empty } + end + + context "a completed encode" do + let(:job) do + j = Aws::ElasticTranscoder::Types::Job.new JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_completed.json')) + j.input = Aws::ElasticTranscoder::Types::JobInput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/input_completed.json'))) + j.outputs = [ Aws::ElasticTranscoder::Types::JobOutput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/output_completed.json')))] + j + end + let(:completed_output) { [{id: "2", url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", hls_url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a.m3u8", label: "hls0400k", :width=>400, :height=>224, :video_framerate=>"25", :file_size=>6901104, :duration=>"117353", :segment_duration=> "2.0"}] } + let(:completed_tech_metadata) { {:width=>1280, :height=>720, :video_framerate=>"25", :file_size=>21069678, :duration=>"117312"} } + + subject { ActiveEncode::Base.find('1471963629141-kmcocm') } + it { is_expected.to be_a ActiveEncode::Base } + its(:id) { is_expected.to eq '1471963629141-kmcocm' } + it { is_expected.to be_completed } + its(:output) { is_expected.to eq 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(:created_at) { is_expected.to be_the_same_time_as '2016-08-23T10:47:09-04:00' } + its(:updated_at) { is_expected.to be_the_same_time_as '2016-08-23T13:59:29-04:00' } + its(:finished_at) { is_expected.to be_the_same_time_as '2016-08-23T13:59:45-04:00' } + its(:tech_metadata) { is_expected.to eq completed_tech_metadata } + end + + context "a failed encode" do + let(:job) do + j = Aws::ElasticTranscoder::Types::Job.new JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_failed.json')) + j.input = Aws::ElasticTranscoder::Types::JobInput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/input_generic.json'))) + j.outputs = [ Aws::ElasticTranscoder::Types::JobOutput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/output_failed.json')))] + j + end + let(:failed_tech_metadata) { {} } + + subject { ActiveEncode::Base.find('1471963629141-kmcocm') } + it { is_expected.to be_a ActiveEncode::Base } + its(:id) { is_expected.to eq '1471963629141-kmcocm' } + 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.not_to be_empty } + its(:created_at) { is_expected.to be_the_same_time_as '2016-08-23T10:47:09-04:00' } + its(:updated_at) { is_expected.to be_the_same_time_as '2016-08-23T13:59:29-04:00' } + its(:finished_at) { is_expected.to be_the_same_time_as '2016-08-23T13:59:45-04:00' } + its(:tech_metadata) { is_expected.to be_empty } + end + end + + describe "#cancel!" do + before do + allow(client).to receive(:cancel_job).and_return(cancel_response) + end + + let(:cancel_response) do + res = double(Aws::ElasticTranscoder::Types::CancelJobResponse) + allow(res).to receive(:successful?).and_return(true) + res + end + + let(:job) do + j = Aws::ElasticTranscoder::Types::Job.new JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_canceled.json')) + j.input = Aws::ElasticTranscoder::Types::JobInput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/input_generic.json'))) + j.outputs = [ Aws::ElasticTranscoder::Types::JobOutput.new(JSON.parse(File.read('spec/fixtures/elastic_transcoder/output_canceled.json')))] + j + end + + let(:encode) { ActiveEncode::Base.create( + "somefile.mp4", + pipeline_id: "1471963629141-kmcocm", + output_key_prefix: "elastic-transcoder-samples/output/hls/", + outputs: [{ + key: 'hls0400k/' + "e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", + preset_id: "1351620000001-200050", + segment_duration: "2" + }]) } + subject { encode.cancel! } + it { is_expected.to be_a ActiveEncode::Base } + its(:id) { is_expected.to eq '1471963629141-kmcocm' } + it { is_expected.to be_cancelled } + end + + # describe "reload" do + # before do + # allow(ElasticTranscoder::Job).to receive(:details).and_return(details_response) + # allow(ElasticTranscoder::Job).to receive(:progress).and_return(progress_response) + # end + # + # let(:details_response) { ElasticTranscoder::Response.new(body: JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_details_running.json'))) } + # let(:progress_response) { ElasticTranscoder::Response.new(body: JSON.parse(File.read('spec/fixtures/elastic_transcoder/job_progress_running.json'))) } + # let(:reload_output) { [{ id: "510582971", url: "https://elastic_transcoder-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(:created_at) { is_expected.to eq '2015-06-09T16:18:26Z' } + # its(:updated_at) { is_expected.to eq '2015-06-09T16:18:28Z' } + # its(:finished_at) { is_expected.to be_nil } + # its(:tech_metadata) { is_expected.to eq reload_tech_metadata } + # end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c3bd42b..1a0a3fc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,3 +10,9 @@ RSpec.configure do |_config| end + +RSpec::Matchers.define :be_the_same_time_as do |expected| + match do |actual| + expect(Time.parse(expected)).to eq(Time.parse(actual)) + end +end