simongregory/glued

View on GitHub
lib/glued/grabber.rb

Summary

Maintainability
A
25 mins
Test Coverage
# encoding: utf-8

class Unstuck < Exception
end

# Grabs all the fragments that make up the media file and joins
# them within a local file.
#
class Grabber
  attr_reader :urls

  SANE_FRAGMENT_MAX = 10_000
  SANE_FRAGMENT_MIN = 1

  def initialize(manifest, bootstrap, io = nil, do_download = true)
    # As we've hardcoded 1 into the @url below
    fail Unstuck, 'Only one segment can be handled' if bootstrap.segments != 1
    fail Unstuck, 'Not enough fragments' if bootstrap.fragments < SANE_FRAGMENT_MIN
    fail Unstuck, 'Too many fragments' if bootstrap.fragments > SANE_FRAGMENT_MAX

    @uri = "#{manifest.media_filename}.flv"
    @url = "#{manifest.base_ref}/#{manifest.media_filename}Seg1-Frag"
    @total_fragments = bootstrap.fragments
    @urls = []
    @downloaded_fragments = []
    @fragments_downloaded = 0
    @out = io

    create_download_file
    build
    download_all if do_download
  end

  private

  def create_download_file
    # TODO: Track how much has already been downloaded and append from that point
    fail Unstuck, "Aborting as the download target file '#{@uri}' already exists" if File.exist? @uri

    @out = File.new(@uri, 'ab') if @out.nil?

    # TODO: Test first fragment for audio and video write header accordingly
    @out.write(FLV.header)
  end

  def build
    # TODO, correctly set the fragment start
    fragment = 1
    while fragment <= @total_fragments
      @urls << "#{@url}#{fragment}"
      fragment += 1
    end
  end

  def download_all
    @urls.each { |url| download url }
  end

  def download(url)
    file_name = url.split('/').pop

    dl = fetch_and_report(url)

    fail "Invalid content type '#{dl.content_type}' for fragment #{file_name}." unless dl.content_type == 'video/f4f'

    reader = F4VIO.new(dl.body)
    f4f = F4F.new(reader)

    debug_stuff(dl.body)

    fail 'Fragment did not verify' unless f4f.ok?

    f4f.boxes.each do |box|
      if box.type == 'mdat'
        reader.pos = box.content_start
        @out.write(reader.read(box.content_size))
      end
    end

    @fragments_downloaded += 1
  end

  def fetch_and_report(url)
    start_time = Time.now

    dl = Curl::Easy.perform(url)

    report(dl.body.length, Time.now - start_time)

    dl
  end

  def report(number_of_bytes, in_seconds)
    bps = (number_of_bytes * 8) / in_seconds
    mbps = bps / 1_000_000

    print "\rDownloading #{@fragments_downloaded + 1}/#{@total_fragments} at #{mbps.round(2)} Mbps"
  end
end