module Video2gif::Options

Public Class Methods

parse(args) click to toggle source
# File lib/video2gif/options.rb, line 9
    def self.parse(args)
      options = {}

      parser = OptionParser.new do |parser|
        parser.banner = <<~BANNER
          video2gif #{Video2gif::VERSION}

          Usage: video2gif <video> [<output GIF filename>] [options]
        BANNER

        parser.separator ''
        parser.separator 'General GIF options:'

        parser.on('-s SEEK',
                  '--seek SEEK',
                  'Set time to seek to in the input video (use a count of',
                  'seconds or HH:MM:SS.SS format)') do |s|
          options[:seek] = s
        end

        parser.on('-t TIME',
                  '--time TIME',
                  'Set duration to use from the input video (use a count of',
                  'seconds)') do |t|
          options[:time] = t
        end

        parser.on('-f FRAMES',
                  '--fps FRAMES',
                  'Set frames per second for the resulting GIF (default 10)') do |f|
          options[:fps] = f
        end

        parser.on('-w WIDTH',
                  '--width WIDTH',
                  'Scale the width of the resulting GIF in pixels (aspect',
                  'ratio is preserved, default is 400 pixels)') do |w|
          options[:width] = w
        end

        # parser.on('-hHEIGHT', '--height=HEIGHT', 'Scale the height of the resulting GIF') do |h|
        #   options[:height] = h
        # end

        parser.on('-p PALETTE',
                  '--palette PALETTE',
                  'Set the palette size of the resulting GIF (maximum of 255',
                  'colors)') do |p|
          options[:palette] = p
        end

        parser.on('--palette-mode MODE',
                  '--palettemode MODE',
                  'Configure a custom palette statistics mode, using either',
                  '"full" (single full-frame palette for the whole GIF),',
                  '"diff" (single palette emphasizing movement against a',
                  'static background across frames), or',
                  '"single" (individual palette per frame, adds to file size)',
                  '(default "diff")') do |p|
          options[:palettemode] = p
        end

        parser.on('-d [ALGORITHM]',
                  '--[no-]dither [ALGORITHM]',
                  'Set the dithering algorithm for the palette generation',
                  '(default enabled with "floyd_steinberg")') do |d|
          if d.nil?
            options[:dither] = 'floyd_steinberg'
          else
            options[:dither] = d || 'none'
          end
        end

        parser.on('--crop-width SIZE',
                  '--crop-size-w SIZE',
                  'Pixel size of width to select from source video, before scaling') do |s|
          options[:wregion] = s
        end

        parser.on('--crop-height SIZE',
                  '--crop-size-h SIZE',
                  'Pixel size of height to select from source video, before scaling') do |s|
          options[:hregion] = s
        end

        parser.on('--crop-x OFFSET',
                  '--crop-offset-x OFFSET',
                  'Pixel offset from left to select from source video, before scaling') do |o|
          options[:xoffset] = o
        end

        parser.on('--crop-y OFFSET',
                  '--crop-offset-y OFFSET',
                  'Pixel offset from top to select from source video, before scaling') do |o|
          options[:yoffset] = o
        end

        parser.on('-a [THRESHOLD]',
                  '--autocrop [THRESHOLD]',
                  'Attempt automatic cropping based on black region, scaled',
                  'from 0 (nothing) to 255 (everything), default threshold 24') do |c|
          options[:autocrop] = c || 24
        end

        parser.on('--contrast CONTRAST',
                  'Apply contrast adjustment, scaled from -2.0 to 2.0 (default 1)') do |c|
          options[:contrast] = c
          options[:eq] = true
        end

        parser.on('--brightness BRIGHTNESS',
                  'Apply brightness adjustment, scaled from -1.0 to 1.0 (default 0)') do |b|
          options[:brightness] = b
          options[:eq] = true
        end

        parser.on('--saturation SATURATION',
                  'Apply saturation adjustment, scaled from 0.0 to 3.0 (default 1)') do |s|
          options[:saturation] = s
          options[:eq] = true
        end

        parser.on('--gamma GAMMA',
                  'Apply gamma adjustment, scaled from 0.1 to 10.0 (default 1)') do |g|
          options[:gamma] = g
          options[:eq] = true
        end

        parser.on('--red-gamma GAMMA',
                  'Apply red channel gamma adjustment, scaled from 0.1 to 10.0 (default 1)') do |g|
          options[:gamma_r] = g
          options[:eq] = true
        end

        parser.on('--green-gamma GAMMA',
                  'Apply green channel gamma adjustment, scaled from 0.1 to 10.0 (default 1)') do |g|
          options[:gamma_g] = g
          options[:eq] = true
        end

        parser.on('--blue-gamma GAMMA',
                  'Apply blue channel gamma adjustment, scaled from 0.1 to 10.0 (default 1)') do |g|
          options[:gamma_b] = g
          options[:eq] = true
        end

        parser.on('--tonemap [ALGORITHM]',
                  'Attempt to force tonemapping from HDR (BT.2020) to SDR',
                  '(BT.709) using algorithm (experimental, requires ffmpeg with',
                  'libzimg) (default "hable", "mobius" is a good alternative)') do |t|
          options[:tonemap] = t || 'hable'
        end

        parser.on('--subtitles [INDEX]',
                  '(Experimental, requires ffprobe) Attempt to use the',
                  'subtitles built into the video to overlay text on the',
                  'resulting GIF. May be extremely slow for text subtitles.',
                  'Takes an optional integer value to choose the subtitle',
                  'stream (defaults to the first subtitle stream, index 0)') do |s|
          options[:subtitles] = s || true
          options[:subtitle_index] =  if options[:subtitles].is_a?(TrueClass)  # default to first stream
                                        0
                                      elsif options[:subtitles].match?(/\A\d+\z/)  # select stream by index
                                        options[:subtitles].to_i
                                      elsif options[:subtitles].is_a?(String)  # open subtitles file
                                        puts 'ERROR: Selecting subtitles by filename is not yet supported!'
                                        exit 1
                                      end
        end

        parser.on('-r MULTIPLIER',
                  '--rate MULTIPLIER',
                  '--speed MULTIPLIER',
                  '(Experimental, SLOW) Multiplier for the speed of the',
                  'GIF, where less than 1 indicates a lower speed and',
                  'greater than 1 indicates a faster speed (default 1)') do |r|
          options[:rate] = r
        end

        parser.separator ''
        parser.separator 'Text overlay options (only used if text is defined):'

        parser.on('-T TEXT',
                  '--text TEXT',
                  'Set text to overlay on the GIF (use "\n" for line breaks)') do |p|
          options[:text] = p
        end

        parser.on('-C TEXTCOLOR',
                  '--text-color TEXTCOLOR',
                  'Set the color for text overlay (default white)') do |p|
          options[:textcolor] = p
        end

        parser.on('-S TEXTSIZE',
                  '--text-size TEXTSIZE',
                  'Set the point size for text overlay (default 30)') do |p|
          options[:textsize] = p
        end

        parser.on('-B TEXTBORDER',
                  '--text-border TEXTBORDER',
                  'Set the width of the border for text overlay (default 1)') do |p|
          options[:textborder] = p
        end

        parser.on('-F TEXTFONT',
                  '--text-font TEXTFONT',
                  'Set the font name for text overlay (default "Arial")') do |p|
          options[:textfont] = p
        end

        parser.on('-V TEXTSTYLE',
                  '--text-variant TEXTVARIANT',
                  'Set the font variant for text overlay (default "Bold")') do |p|
          options[:textvariant] = p
        end

        parser.on('-X TEXTXPOS',
                  '--text-x-position TEXTXPOS',
                  'Set the X position for the text, starting from left (default is center)') do |p|
          options[:xpos] = p
        end

        parser.on('-Y TEXTXPOS',
                  '--text-y-position TEXTYPOS',
                  'Set the Y position for the text, starting from top (default is near bottom)') do |p|
          options[:ypos] = p
        end

        parser.separator ''
        parser.separator 'Other options:'

        parser.on_tail('-v', '--verbose', 'Show ffmpeg command executed and output') do |p|
          options[:verbose] = p
        end

        parser.on_tail('-q', '--quiet', 'Suppress all log output (overrides verbose)') do |p|
          options[:quiet] = p
        end

        parser.on_tail('-h', '--help', 'Show this message') do
          puts parser
          exit
        end

        parser.parse!(args)
      end

      parser.parse!

      if args.size < 1 || args.size > 2
        puts 'ERROR: Specify one video to convert at a time!'
        puts ''
        puts parser.help
        exit 1
      end

      unless File.exists?(args[0])
        puts "ERROR: Specified video file does not exist: #{args[0]}!"
        puts ''
        puts parser.help
        exit
      end

      options[:input_filename]  = args[0]
      options[:output_filename] = if args[1]
                                    args[1].end_with?('.gif') ? args[1] : args[1] + '.gif'
                                  else
                                    File.join(File.dirname(args[0]),
                                              File.basename(args[0], '.*') + '.gif')
                                  end

      options
    end