class HLSDownload::HLS
Attributes
iframe_m3u8[RW]
logger[RW]
main_url[RW]
media_files[RW]
prefix[RW]
raw_url[RW]
sub_m3u8[RW]
Public Class Methods
new(url, logger = nil, prefix = nil)
click to toggle source
# File lib/hls.rb, line 45 def initialize(url, logger = nil, prefix = nil) @raw_url = url @logger = logger || new_logger @main_url = URI.parse(url) @media_files = [] @sub_m3u8 = [] @prefix = prefix @iframe_m3u8 = [] parse @main_url end
Public Instance Methods
download!(opts = {})
click to toggle source
# File lib/hls.rb, line 56 def download!(opts = {}) manifest_file = main_url.path.split('/').last base_url = main_url.to_s.gsub("/#{manifest_file}", '') @output_dir = opts[:output_dir] || 'out' FileUtils.mkdir_p @output_dir main_manifest_output = File.join(@output_dir, 'manifest.m3u8') main_manifest_contents = http_get(main_url) sub_m3u8.each do | m3u8 | output = nil if m3u8.prefix rel_path = File.join(m3u8.prefix, 'index.m3u8') output = File.join(@output_dir, rel_path) # rewrite contents main_manifest_contents.gsub!(m3u8.raw_url, rel_path) else output = m3u8.main_url.to_s.gsub(base_url, @output_dir) end manifest_contents = http_get(m3u8.main_url) m3u8.media_files.each_with_index do | media_file, i | media_output = nil if m3u8.prefix ext = File.extname(media_file.raw.split('?')[0].split('/').last) basename = "#{i+1}#{ext}" media_output = File.join(@output_dir, m3u8.prefix, basename) # rewrite contents manifest_contents.gsub!(media_file.raw, basename) else media_output = media_file.url.gsub(base_url, @output_dir) end download_file(URI.parse(media_file.url), media_output) end FileUtils.mkdir_p File.dirname(output) write(output, manifest_contents) end iframe_m3u8.each do | iframe | if iframe.raw.include?('://') unless iframe.raw.include?(base_url) raise HLSException.new('download I-FRAME-STREAM-INF with absolute url is not supported') end end iframe_output = iframe.url.to_s.gsub(base_url, @output_dir) iframe_manifest_contents = http_get(iframe.url) lines = iframe_manifest_contents.split("\n") files = lines.reject { |l| l.start_with? '#' } files = files.map { |l| l.gsub(' ', '') } files = files.reject { |l| l.empty? } files.each do |f| if f.include?('://') raise HLSException.new('download I-FRAME-STREAM-INF with absolute path media files is not supported') end end FileUtils.mkdir_p File.dirname(iframe_output) download_file(iframe.url, iframe_output) end write(main_manifest_output, main_manifest_contents) # TODO download recursively. i.e. allow downloading only 1 rendition end
Private Instance Methods
download_file(url, output)
click to toggle source
# File lib/hls.rb, line 213 def download_file(url, output) unless output == '/dev/null' logger.debug "downloading #{output}" dir = Pathname.new(output).dirname.to_s unless File.directory?(dir) logger.debug "creating dir: #{dir}" FileUtils.mkdir_p(dir) end end resp = nil Net::HTTP.start(url.host) do |http| resp = http.get("#{url.path}?#{url.query}") if ['4', '5'].include? resp.code[0] raise HLSException.new("#{url} response status code: #{resp.code}") end write(output, resp.body) end resp.body end
http_get(url)
click to toggle source
# File lib/hls.rb, line 208 def http_get(url) logger.debug "http get #{url.to_s}" download_file(url, '/dev/null') end
is_media_playlist?(man)
click to toggle source
# File lib/hls.rb, line 233 def is_media_playlist?(man) MEDIA_FORMAT_EXTENSIONS.each do |ext| return true if man.include? ext end false end
new_logger()
click to toggle source
# File lib/hls.rb, line 240 def new_logger l = Logger.new STDOUT l.level = Logger::ERROR l end
parse(url)
click to toggle source
# File lib/hls.rb, line 124 def parse(url) man = http_get(url) logger.debug "parsing manifest" lines = man.split("\n") unless man.include? '.m3u8' # this is a variant playlist raise HLSException.new('unsupported media playlist') unless is_media_playlist? man logger.debug 'getting media url(s)' files = lines.reject { |l| l.start_with? '#' } files = files.map { |l| l.gsub(' ', '') } files = files.reject { |l| l.empty? } files.each_with_index do |f, i| raw = f unless f.include? '://' # relative path f_url = url.dup split_path = f_url.dup.path.split('/') split_path[-1] = f path = File.join(split_path) f_url.path = path.start_with?('/') ? path : "/#{path}" f = f_url.to_s end @media_files << HLSMediaFile.new(f, i, raw) end return end # this is a master playlist uris = [] iframe_m3u8_uris = [] lines.select { |l| l.include? '.m3u8' }.each do |l| is_iframe = false if l.start_with? '#' is_iframe = l.include?('I-FRAME-STREAM-INF') uri_res = nil l.split(',').each do | attribute | next unless attribute.start_with? 'URI' uri_res = attribute.gsub('URI=', '').gsub('"', '') end if is_iframe iframe_m3u8_uris << uri_res else uris << uri_res end else uris << l end end uris.each_with_index do |u, i| prefix = nil if u.include? '://' prefix = "presentation_#{i}" else # relative path s_url = url.dup split_path = s_url.dup.path.split('/') split_path[-1] = u path = File.join(split_path) s_url.path = path.start_with?('/') ? path : "/#{path}" u = s_url.to_s end logger.debug "sub playlist found #{u}" sub_m3u8 << HLS.new(u, logger, prefix) end iframe_m3u8_uris.each_with_index do |u, i| raw = u if u.include? '://' else # relative path s_url = url.dup split_path = s_url.dup.path.split('/') split_path[-1] = u path = File.join(split_path) s_url.path = path.start_with?('/') ? path : "/#{path}" u = s_url.to_s end @iframe_m3u8 << IFrameM3U8.new(u, i, raw) end end
write(output, contents)
click to toggle source
# File lib/hls.rb, line 246 def write(output, contents) open(output, "wb") do |file| file.write(contents) end end