kapoorlakshya/screen-recorder

View on GitHub
lib/screen-recorder/options.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
# @since 1.0.0-beta11
#
# @api private
module ScreenRecorder
  # @since 1.0.0-beta11
  class Options
    attr_reader :all

    DEFAULT_LOG_FILE = 'ffmpeg.log'.freeze
    DEFAULT_FPS = 15.0
    DEFAULT_MAC_INPUT_PIX_FMT = 'uyvy422'.freeze # For avfoundation
    DEFAULT_PIX_FMT = 'yuv420p'.freeze
    YUV420P_SCALING = '"scale=trunc(iw/2)*2:trunc(ih/2)*2"'.freeze

    def initialize(options)
      # @todo Consider using OpenStruct
      @all = verify_options options
      advanced[:input] = default_advanced_input.merge(advanced_input)
      advanced[:output] = default_advanced_output.merge(advanced_output)
      advanced[:log] ||= DEFAULT_LOG_FILE

      # Fix for using yuv420p pixel format for output
      # @see https://www.reck.dk/ffmpeg-libx264-height-not-divisible-by-2/
      advanced_output[:vf] = YUV420P_SCALING if advanced_output[:pix_fmt] == 'yuv420p'
    end

    #
    # Returns given input file or input
    #
    def input
      @all[:input]
    end

    #
    # Returns capture device in use
    #
    def capture_device
      determine_capture_device
    end

    #
    # Returns given output filepath
    #
    def output
      @all[:output]
    end

    #
    # Returns given values that are optional
    #
    def advanced
      @all[:advanced] ||= {}
    end

    #
    # Returns given framerate
    #
    def framerate
      ScreenRecorder.logger.warn '#framerate will not be available in the next release. Use #advanced instead.'
      advanced[:output][:framerate]
    end

    #
    # Returns given log filename
    #
    def log
      advanced[:log]
    end

    #
    # Returns a String with all options parsed and
    # ready for the ffmpeg process to use
    #
    def parsed
      vals = "-f #{capture_device} "
      vals << parse_advanced(advanced_input)
      vals << "-i #{input} " unless advanced_input[:i] # Input provided by user
      vals << parse_advanced(advanced)
      vals << parse_advanced(advanced_output)
      vals << output
    end

    private

    #
    # Verifies the required options are provided and returns
    # the given options Hash. Raises ArgumentError if all required
    # options are not present in the given Hash.
    #
    def verify_options(options)
      TypeChecker.check options, Hash
      TypeChecker.check options[:advanced], Hash if options[:advanced]
      missing_options = required_options.select { |req| options[req].nil? }
      err = "Required options are missing: #{missing_options}"
      raise(ArgumentError, err) unless missing_options.empty?

      options
    end

    def advanced_input
      advanced[:input] ||= {}
    end

    def advanced_output
      advanced[:output] ||= {}
    end

    def default_advanced_input
      {
        pix_fmt: OS.mac? ? DEFAULT_MAC_INPUT_PIX_FMT : nil
      }
    end

    def default_advanced_output
      {
        pix_fmt:   DEFAULT_PIX_FMT,
        framerate: advanced[:framerate] || DEFAULT_FPS
      }
    end

    #
    # Returns Array of required options as Symbols
    #
    def required_options
      %i[input output]
    end

    #
    # Returns given Hash parsed and ready for ffmpeg to receive.
    #
    def parse_advanced(opts)
      # @todo Replace arr with opts.each_with_object([])
      arr = []
      rejects = %i[input output log]
      # Do not parse input/output and log as they're placed separately in #parsed
      opts.reject { |k, _| rejects.include? k }
        .each do |k, v|
        arr.push "-#{k} #{v}" unless v.nil? # Ignore blank params
      end
      "#{arr.join(' ')} "
    end

    #
    # Returns input capture device based on user given value or the current OS.
    #
    def determine_capture_device
      # User given capture device or format from advanced configs Hash
      # @see https://www.ffmpeg.org/ffmpeg.html#Main-options
      return advanced_input[:f] if advanced_input[:f]

      return advanced_input[:fmt] if advanced_input[:fmt]

      default_capture_device
    end

    #
    # Returns input capture device for current OS.
    #
    def default_capture_device
      return 'gdigrab' if OS.windows?

      return 'x11grab' if OS.linux?

      return 'avfoundation' if OS.mac?

      raise 'Your OS is not supported. Feel free to create an Issue on GitHub.'
    end
  end
end