#!/usr/bin/env ruby -W
#
# query-handbrake-log
#
# Copyright (c) 2013-2020 Don Melton
#

$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')

require 'abbrev'
require 'video_transcoding/cli'

module VideoTranscoding
  class Command
    include CLI

    def about
      <<HERE
query-handbrake-log #{VERSION}
#{COPYRIGHT}
HERE
    end

    def usage
      <<HERE
Report information from HandBrake-generated `.log` files.

Usage: #{$PROGRAM_NAME} INFO [OPTION]... [FILE|DIRECTORY]...

Information types:
t,  time            time spent during transcoding
                      (sorted from short to long)
s,  speed           speed of transcoding in frames per second
                      (sorted from fast to slow)
b,  bitrate         video bitrate of transcoded output
                      (sorted from low to high)
r,  ratefactor      average P-frame quantizer for transcoding
                      (sorted from low to high)

Options:
    --reverse       reverse direction of sort
    --tabular       use tab character as field delimiter and suppress labels

-v, --verbose       increase diagnostic information
-q, --quiet         decrease     "           "

-h, --help          display this help and exit
    --version       output version information and exit
HERE
    end

    def initialize
      super
      @reverse  = false
      @tabular  = false
      @info     = nil
      @logs     = []
      @paths    = []
    end

    def define_options(opts)
      opts.on('--reverse') { @reverse = true }
      opts.on('--tabular') { @tabular = true }
    end

    def configure
      fail UsageError, 'missing argument' if ARGV.empty?
      arg = $ARGV.shift

      @info = case arg
      when 'time', 't'
        :time
      when 'speed', 's'
        @reverse = !@reverse
        :speed
      when 'bitrate', 'b'
        :bitrate
      when 'ratefactor', 'r'
        :ratefactor
      else
        fail UsageError, "invalid information type: #{arg}"
      end
    end

    def process_input(arg)
      Console.info "Processing: #{arg}..."
      input = File.absolute_path(arg)

      if File.directory? input
        logs = Dir[input + File::SEPARATOR + '*.log']
        fail "does not contain `.log` files: #{input}" if logs.empty?
        @logs += logs
        @paths << input
      else
        fail "not a `.log` file: #{input}" unless File.extname(input) == '.log'
        @logs << File.absolute_path(input)
        @paths << File.dirname(input)
      end
    end

    def complete
      @logs.uniq!
      @paths.uniq!

      if @paths.size > 1
        prefix = File.dirname(@paths.abbrev.keys.min_by { |key| key.size }) + File::SEPARATOR
      else
        prefix = ''
      end

      if @tabular
        delimiter = "\t"
        fps_label = ''
        kbps_label = ''
      else
        delimiter = ' '
        fps_label = ' fps'
        kbps_label = ' kbps'
      end

      report = []

      @logs.each do |log|
        Console.info "Reading: #{log}..."
        video = File.basename(log, '.log')
        video += " (#{File.dirname(log).sub(prefix, '')})" unless prefix.empty?
        found = false
        count = 0
        duration_line = nil
        rate_line = nil
        fps_line = nil
        fps_line_2 = nil
        ratefactor_line = nil
        bitrate_line = nil

        begin
          File.foreach(log) do |line|
            found = true if not found and line =~ /^HandBrake/
            count += 1
            fail "not a HandBrake-generated `.log` file: #{log}" if count > 10 and not found
            duration_line = line if duration_line.nil? and line =~ /\+ duration: /
            rate_line = line if line =~ /\+ frame rate: /

            if line =~ /average encoding speed/
              if fps_line.nil?
                fps_line = line
              elsif fps_line_2.nil?
                if fps_line =~ / ([0.]+) fps\r?$/
                  fps_line = line
                else
                  fps_line_2 = line
                end
              end
            end

            ratefactor_line = line if line =~ /x26[45] \[info\]: frame P:/
            bitrate_line = line if bitrate_line.nil? and line =~ /mux: track 0/
          end
        rescue SystemCallError => e
          raise "reading failed: #{e}"
        end

        fail "not a HandBrake-generated `.log` file: #{log}" unless found

        case @info
        when :time, :speed
          if fps_line.nil?
            if @info == :time
              report << "00:00:00#{delimiter}#{video}"
            else
              report << "00.000000#{fps_label}#{delimiter}#{video}"
            end
          else
            Console.debug fps_line

            if fps_line =~ / ([0-9.]+) fps\r?$/
              fps = $1
            else
              fail "fps not found: #{log}"
            end

            unless fps_line_2.nil?
              Console.debug fps_line_2

              if fps_line_2 =~ / ([0-9.]+) fps\r?$/
                fps_2 = $1
                fps = (1 / ((1 / fps.to_f) + (1 / fps_2.to_f))).round(6).to_s
              else
                fail "fps not found: #{log}"
              end
            end

            if @info == :time
              fail "duration not found: #{log}" if duration_line.nil?
              Console.debug duration_line

              if duration_line =~ /([0-9]{2}):([0-9]{2}):([0-9]{2})/
                duration = ($1.to_i * 60 * 60) + ($2.to_i * 60) + $3.to_i
                fail "frame rate not found: #{log}" if rate_line.nil?
                Console.debug rate_line
                rate = rate_line.sub(/^.*: [^0-9]*/, '').sub(/ fps.*$/, '').to_f
                seconds = ((duration * rate) / fps.to_f).to_i
                hours   = seconds / (60 * 60)
                minutes = (seconds / 60) % 60
                seconds = seconds % 60
                report << format("%02d:%02d:%02d%s%s", hours, minutes, seconds, delimiter, video)
              else
                fail "duration not found: #{log}"
              end
            else
              report << "#{fps}#{fps_label}#{delimiter}#{video}"
            end
          end
        when :bitrate
          if bitrate_line.nil?
            report << "0000.00#{kbps_label}#{delimiter}#{video}"
          else
            Console.debug bitrate_line

            if bitrate_line =~ /([0-9.]+) kbps/
              report << "#{$1}#{kbps_label}#{delimiter}#{video}"
            else
              fail "bitrate not found: #{log}"
            end
          end
        when :ratefactor
          if ratefactor_line.nil?
            report << "00.00#{delimiter}#{video}"
          else
            Console.debug ratefactor_line

            if ratefactor_line =~ /Avg QP: ?([0-9.]+)/
              report << "#{$1}#{delimiter}#{video}"
            else
              fail "ratefactor not found: #{log}"
            end
          end
        end
      end

      if @info == :time
        report.sort!
      else
        report.sort! do |a, b|
          number_a = a.sub(/ .*$/, '').to_f
          number_b = b.sub(/ .*$/, '').to_f

          if number_a < number_b
            -1
          elsif number_a > number_b
            1
          else
            a <=> b
          end
        end
      end

      report.reverse! if @reverse
      puts report
    end
  end
end

VideoTranscoding::Command.new.run
