Last active
December 25, 2015 02:09
-
-
Save inferiorhumanorgans/6900411 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'json' | |
require 'net/http' | |
require 'optparse' | |
require 'ostruct' | |
CHANNEL_URL = 'http://web.mobilerider.com/clients/hsb/api/media/?filter={"channel":{},"query":""}' | |
STREAMS = {} | |
$options = OpenStruct.new | |
$options.debug = false | |
$options.format = '.mp4' | |
$options.ffmpeg = ENV['ffmpeg'] || './ffmpeg' | |
def parse_cmdline | |
opts = OptionParser.new do |opts| | |
opts.on('-f', '--format EXTENSION', 'Specify an ffmpeg supported container') do |ext| | |
$options.format = ext | |
end | |
opts.on('-d', '--debug', 'Enable debugging nonsense') do | |
$options.debug = true | |
end | |
opts.on('-r', '--resolution RESOLUTION', 'Desired resolution') do |resolution| | |
$options.resolution = resolution | |
end | |
opts.on('-x', '--executable EXECUTABLE', 'Path to ffmpeg executable') do |exe| | |
$opts.ffmpeg = exe | |
end | |
opts.on('-i', '--ids STREAM_IDS', 'Comma separated list of stream ids') do |ids| | |
$options.ids = ids.split(',') | |
end | |
opts.on('-l' , '--list', 'List the available streams and exit') do | |
$options.list_only = true | |
end | |
end | |
opts.parse(ARGV) | |
end | |
def get_url(url) | |
STDERR.puts "Fetching: #{url}" if $options.debug | |
escaped = URI.escape(url) | |
return Net::HTTP.get(URI(escaped)) | |
end | |
def get_json (url) | |
STDERR.puts "JSON decoding: #{url}" if $options.debug | |
return JSON.parse(get_url(url)) | |
end | |
def get_streams | |
streams = get_json(CHANNEL_URL) | |
unless streams.include? 'objects' | |
STDERR.puts "Malformed channel info" | |
exit(-1) | |
end | |
STREAMS['62665'] = {title: '2012-10-05', vendor_id: 2409} | |
STREAMS['62672'] = {title: '2012-10-06', vendor_id: 2409} | |
STREAMS['62680'] = {title: '2012-10-07', vendor_id: 2409} | |
streams['objects'].sort{|a,b| a['title'] <=> b['title']}.each do |stream| | |
# Presumably the <STAGE NAME> LIVE streams are just the stage for the whole day | |
next if stream['title'] =~ /Live/ unless defined? INCLUDE_FULL_DAY_STREAMS_TOO | |
STREAMS[stream['id']] = {title: stream['title'], vendor_id: 3050} | |
end | |
end | |
def show_streams | |
puts "%5s\t%s" % ['ID', 'Artist'] | |
STREAMS.each do |stream_id, stream| | |
puts "%5d\t%s" % [stream_id, stream[:title]] | |
end | |
end | |
def choose_stream | |
show_streams | |
STDERR.printf "Pick a stream ID: " | |
STDERR.flush | |
input = STDIN.gets.strip | |
unless STREAMS.include? input | |
STDERR.puts "Couldn't find that stream, sorry." | |
exit(-1) | |
end | |
input | |
end | |
def get_resolutions(stream_id) | |
stream = get_json("http://web.mobilerider.com/api2/#{STREAMS[stream_id][:vendor_id]}/media/#{stream_id}.json") | |
# So I guess we could get different playlists here, but for now let's just take the first playlist we find. | |
m3u = get_url(stream['files'].values.first).split("\n") | |
STREAMS[stream_id][:resolutions] = {} | |
m3u.each_with_index do |line, idx| | |
next unless line =~ /#EXT-X-STREAM-INF/ | |
# Gross, but good enough for HSB | |
resolution = line.sub(/.*RESOLUTION=/, '').sub(/,.*/, '') | |
STREAMS[stream_id][:resolutions][resolution] = m3u[idx+1] | |
end | |
if STREAMS[stream_id][:resolutions].empty? | |
STREAMS[stream_id][:master_playlist] = stream['files'].values.first | |
end | |
end | |
def choose_resolution(stream_id) | |
return nil if STREAMS[stream_id][:resolutions].empty? | |
STREAMS[stream_id][:resolutions].keys.each_with_index do |resolution, idx| | |
STDERR.puts '%2d.) %s' % [idx, resolution] | |
end | |
STDERR.printf 'Pick a resolution: ' | |
STDERR.flush | |
resolution = STDIN.gets.strip | |
resolution = STREAMS[stream_id][:resolutions].keys[resolution.to_i] unless resolution =~ /x/ | |
unless STREAMS[stream_id][:resolutions].include? resolution | |
STDERR.puts "Couldn't find that resolution, sorry" | |
exit(-1) | |
end | |
resolution | |
end | |
def get_mux_cmd(stream_id=nil, resolution=nil) | |
get_streams if STREAMS.empty? | |
stream_id = choose_stream if stream_id.nil? | |
get_resolutions(stream_id) unless STREAMS[stream_id].include? :resolutions | |
resolution = choose_resolution(stream_id) if resolution.nil? or resolution.empty? | |
if resolution | |
"#{$options.ffmpeg} -i '#{STREAMS[stream_id][:resolutions][resolution]}' -c copy -bsf:a aac_adtstoasc '#{STREAMS[stream_id][:title]}#{$options.format}'" | |
else | |
"#{$options.ffmpeg} -i '#{STREAMS[stream_id][:master_playlist]}' -c copy -bsf:a aac_adtstoasc '#{STREAMS[stream_id][:title]}#{$options.format}'" | |
end | |
end | |
parse_cmdline | |
if $options.list_only == true | |
get_streams if STREAMS.empty? | |
show_streams | |
exit 0 | |
end | |
if $options.ids | |
$options.ids.each do |id| | |
system(get_mux_cmd(id, $options.resolution)) | |
end | |
else | |
system(get_mux_cmd(nil, $options.resolution)) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment