Noosfero/noosfero

View on GitHub
plugins/html5_video/script/video_processor_foreground.rb

Summary

Maintainability
A
55 mins
Test Coverage
# Creates the namespace, so we can use the lib classes
module VideoProcessor; end

require_relative "../lib/video_processor/ffmpeg"
require_relative "../lib/video_processor/pool_manager"
require_relative "../lib/video_processor/recorder"
require_relative "../lib/video_processor/converter"
require_relative "../lib/video_processor/logger"

include VideoProcessor::Recorder

RAILS_ENV = ENV["RAILS_ENV"] || "development"
NOOSFERO_ROOT = File.join(__dir__, "../../../")
POOL = VideoProcessor::PoolManager.new(NOOSFERO_ROOT)

ERRORS = {}
MAX_RETRIES = 3

# Creates the pool dir if it does not exist
POOL.init_pools

LOGGER = VideoProcessor::Logger.new(File.join(NOOSFERO_ROOT, "log"))

# Redirects stdout and stderr to log files
unless RAILS_ENV == "test"
  $stdout.reopen(LOGGER.info_log_path, "a")
  $stderr.reopen(LOGGER.info_log_path, "a")
end

# Watches pool dir using inotifywait
def watch_pool
  IO.popen("inotifywait -r -m -e create #{POOL.waiting_pool}") do |output|
    output.each do |line|
      path, events, filename = line.split
      if events.include? "ISDIR"
        # A subfolder for the environment was created.
        env_id = filename
      else
        # A new file was added
        env_id = path.split("/").last
      end

      # inotify may lose some events, so run for all files every time
      sleep(0.5) # FIXME wait until the file was written
      process_all_files(env_id)
    end
  end
end

# Processes every file inside an environment subfolder
def process_all_files(env_id, pool = :waiting)
  POOL.all_files(env_id, pool).each do |file|
    # Moves the file to the ONGOING pool, so we can try again if the process
    # dies during the conversion. It removes the file from the pool if it was
    # processed, or adds it back to the WAITING pool if something went wrong.
    video_id = file.split("/").last
    video_path = POOL.assign(env_id, video_id, pool)
    next if video_path.nil?

    begin
      process_video(env_id, video_path, video_id)
      POOL.pop(env_id, video_id)
    rescue => e
      LOGGER.error "Error while processing [Video #{video_id}]: #{e}"
      POOL.pop(env_id, video_id)

      # Move to failed if MAX_RETRIES were exceeded
      pool_dir = (ERRORS[video_id] >= MAX_RETRIES) ? :failed : :waiting
      POOL.push(env_id, video_id, video_path, pool_dir)
    end
  end
end

# Processes a single video file. It will:
# - Generate preview images
# - Convert video to web formats
def process_video(env_id, video_path, video_id)
  ERRORS[video_id] ||= 0
  return if ERRORS[video_id] > MAX_RETRIES

  LOGGER.info "Processing [Video #{video_id}] (#{video_path})"
  ffmpeg = VideoProcessor::Ffmpeg.new

  begin
    converter = VideoProcessor::Converter.new(ffmpeg, video_path, video_id)
    converter.logger = LOGGER

    register_conversion_start(env_id, converter.info, video_id)

    previews = converter.create_preview_imgs
    LOGGER.info "Registering previews for [Video #{video_id}]"
    register_previews(env_id, previews, video_id)

    videos = converter.create_web_videos
    LOGGER.info "Registering results for [Video #{video_id}]"
    register_results(env_id, videos, video_id)

    LOGGER.info "Finished processing [Video #{video_id}]"
  rescue => e
    ERRORS[video_id] += 1
    register_errors(env_id, video_id, e.to_s)
    raise e
  end
end

unless RAILS_ENV == "test"
  # Change the working dir to inside the app, so we can call `rails`
  Dir.chdir(__dir__)

  Process.fork do
    # Process pending files
    %w[ongoing waiting].each do |pool|
      files = Dir[File.join(POOL.path, pool, "*")]
      env_ids = files.map { |f| f.split("/").last }
      env_ids.each { |env_id| process_all_files(env_id, pool.to_sym) }
    end
  end

  trap "SIGTERM" do
    # Gracefully finish all child processes
    Process.kill("TERM", 0)
    Process.exit
  end

  # Start watching the pool dir
  watch_pool
end