diff --git a/Gemfile b/Gemfile index 8c36b3e87..3ce5dafd9 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,6 @@ gem 'bcrypt', :require => "bcrypt" gem 'capistrano', '~> 3.10' gem 'capistrano-rails' gem 'capistrano-rbenv' -gem 'streamio-ffmpeg' gem 'rubyzip', :require => "zip" gem 'stripe' gem 'aws-sdk-sqs', '~> 1' diff --git a/Gemfile.lock b/Gemfile.lock index 07dd95424..8be564ad3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -458,8 +458,6 @@ GEM net-scp (>= 1.1.2) net-ssh (>= 2.8.0) stackprof (0.2.17) - streamio-ffmpeg (3.0.2) - multi_json (~> 1.8) stripe (5.38.0) stripe-ruby-mock (3.0.1) dante (>= 0.2.0) @@ -580,7 +578,6 @@ DEPENDENCIES simplecov solargraph stackprof - streamio-ffmpeg stripe stripe-ruby-mock terminal-table diff --git a/app/logical/ffmpeg.rb b/app/logical/ffmpeg.rb index 24f30045c..fa8a1fa0a 100644 --- a/app/logical/ffmpeg.rb +++ b/app/logical/ffmpeg.rb @@ -1,4 +1,11 @@ +require "shellwords" + +# A wrapper for the ffmpeg command. class FFmpeg + extend Memoist + + class Error < StandardError; end + attr_reader :file # Operate on a file with FFmpeg. @@ -16,10 +23,52 @@ class FFmpeg # https://ffmpeg.org/ffmpeg.html#Main-options # https://ffmpeg.org/ffmpeg-filters.html#thumbnail - ffmpeg_out, status = Open3.capture2e("ffmpeg -i #{file.path} -vf thumbnail=300 -frames:v 1 -y #{vp.path}") - raise "ffmpeg failed: #{ffmpeg_out}" if !status.success? - Rails.logger.debug(ffmpeg_out) + output = shell!("ffmpeg -i #{file.path.shellescape} -vf thumbnail=300 -frames:v 1 -y #{vp.path.shellescape}") + Rails.logger.debug(output) MediaFile.open(vp) end + + # Get file metadata using ffprobe. + # @see https://ffmpeg.org/ffprobe.html + # @see https://gist.github.com/nrk/2286511 + # @return [Hash] a hash of the file's metadata + def metadata + output = shell!("ffprobe -v quiet -print_format json -show_format -show_streams #{file.path.shellescape}") + json = JSON.parse(output) + json.with_indifferent_access + end + + def width + video_channels.first[:width] + end + + def height + video_channels.first[:height] + end + + def duration + metadata.dig(:format, :duration).to_f + end + + def video_channels + metadata[:streams].select { |stream| stream[:codec_type] == "video" } + end + + def audio_channels + metadata[:streams].select { |stream| stream[:codec_type] == "audio" } + end + + def has_audio? + audio_channels.present? + end + + def shell!(command) + program = command.shellsplit.first + output, status = Open3.capture2e(command) + raise Error, "#{program} failed: #{output}" if !status.success? + output + end + + memoize :metadata end diff --git a/app/logical/media_file/video.rb b/app/logical/media_file/video.rb index 13afbd7fb..b981c432e 100644 --- a/app/logical/media_file/video.rb +++ b/app/logical/media_file/video.rb @@ -3,14 +3,12 @@ # # @see https://github.com/streamio/streamio-ffmpeg class MediaFile::Video < MediaFile + delegate :duration, :has_audio?, to: :video + def dimensions [video.width, video.height] end - def duration - video.duration - end - def preview(max_width, max_height) preview_frame.preview(max_width, max_height) end @@ -19,20 +17,14 @@ class MediaFile::Video < MediaFile preview_frame.crop(max_width, max_height) end - def has_audio? - video.audio_channels.present? - end - private def video - raise NotImplementedError, "can't process videos: ffmpeg or mkvmerge not installed" unless self.class.videos_enabled? - - FFMPEG::Movie.new(file.path) + FFmpeg.new(file) end def preview_frame - FFmpeg.new(file).smart_video_preview + video.smart_video_preview end memoize :video, :preview_frame, :dimensions, :duration, :has_audio? diff --git a/config/initializers/ffmpeg.rb b/config/initializers/ffmpeg.rb deleted file mode 100644 index 387508f8c..000000000 --- a/config/initializers/ffmpeg.rb +++ /dev/null @@ -1,3 +0,0 @@ -unless Rails.env.development? - FFMPEG.logger.level = Logger::ERROR -end diff --git a/test/files/test-audio.mp4 b/test/files/test-audio.mp4 new file mode 100644 index 000000000..dbfae20bb Binary files /dev/null and b/test/files/test-audio.mp4 differ diff --git a/test/unit/media_file_test.rb b/test/unit/media_file_test.rb index b45d055e9..ff0d6345d 100644 --- a/test/unit/media_file_test.rb +++ b/test/unit/media_file_test.rb @@ -170,4 +170,11 @@ class MediaFileTest < ActiveSupport::TestCase assert_equal([60, 60], webm.dimensions) end end + + context "for a video" do + should "detect videos with audio" do + assert_equal(true, MediaFile.open("test/files/test-audio.mp4").has_audio?) + assert_equal(false, MediaFile.open("test/files/test-300x300.mp4").has_audio?) + end + end end