#!/usr/bin/ruby
=begin
Purpose:
Automate channel selection for premium membershi from
Digitally Imported ( http://DI.fm/ ) radio
Author: Wejn <wejn at box dot cz>
License: GPLv2 (without the "latter" option)
Requires: Ruby >= 1.8, di.fm premium membership, mplayer,
basic unix skills to operate the script ;)
TS: 20061217000000
Installation:
1) Copy this file to directory in your $PATH
2) Create ~/.difmrc containing your di.fm access credentials
in format: username:password
3) launch it without params to see list of channels
4) give channel name (regexp) as param to launch the stream
via mplayer
Beware: Channel list gets cached (for 20 mins) as ~/.difm.cache.pls
to avoid hitting them everytime. If you absolutely need
to refresh the list (after messing w/ your favs), remove
the cache file.
=end
require 'net/http'
require 'open-uri'
require 'timeout'
require 'pp'
# Class that loads channel list from DI.fm premium membership favorites page
class DIFM # {{{1
BASEURL = 'http://www.di.fm/listencustom/192k.pls'
CACHE = File.join((ENV['HOME'] || '.'), '.difm.cache.pls')
CACHE_VALIDITY = 20*60 # 20 minutes
attr_reader :channels
def initialize(username, password) # {{{2
content = fetch_pls(username, password)
@channels = parse_pls(content)
end
def search_channel(chan)
out = []
@channels.each do |c|
out << c if Regexp.new(chan, Regexp::IGNORECASE) =~ c.title
end
out
end # }}}2
private
def fetch_pls(username, password) # {{{2
url = BASEURL + '?user=' + username + '&pass=' + password
content = nil
timeout(20) do
if ! FileTest.exists?(CACHE) ||
File::stat(CACHE).mtime < Time.now - CACHE_VALIDITY
content = open(url).read
unless content.index("[playlist]")
raise ArgumentError, "Wrong credentials!"
end
begin
File.open(CACHE, 'w') do |f|
f.write(content)
end
end
else
content = File.open(CACHE, 'r').read
end
end
content
end # }}}2
Channel = Struct.new(:title, :file)
class Channel # {{{2
def url
file
end
def name
title
end
end # }}}2
def parse_pls(content) # {{{2
channels = []
entries = nil
content = content.split(/\n+/)
content.shift if content.first.index('[playlist]')
content.each do |ln|
ar = ln.split(/=/, 2)
case ar.first
when /^NumberOfEntries/
entries = ar[1].to_i
when /^(Title|File)(\d+)$/
#puts "_OK_: " + ar.inspect
(channels[$2.to_i - 1] ||= Channel.new).
send($1.downcase + '=', ar[1])
when /^(Length\d+|Version)$/
#puts "Skip: " + ar.inspect
else
raise "Unexpected pls line: " + ar.inspect
end
end
unless (entries || channels.size) == channels.size
raise "Number of channels doesn't match pls actual size!"
end
channels
end # }}}2
end # }}}1
# Display DI.fm channels in a nice way
def channels_out(chans) # {{{1
# to have the list unsorted remove ".sort" from next line:
names = chans.map { |x| x.name }.sort
maxn = names.map { |x| x.length }.max
tsize = 80 # FIXME: lazy to determine this ...
count = tsize / (maxn + 2)
out = []
names.each_with_index do |n, i|
out << "" if (i % count).zero?
out.last << " " + n.ljust(maxn)
end
out
end # }}}1
# Here we go ...
puts "DI.fm simple launcher by Wejn <wejn at box dot cz>, version: 1"
puts "=------------------------------------------------------------="
puts
creds = nil
# Fetch credentials from rcfile ...
begin # {{{1
rc = File.open(File.join(ENV['HOME'] || '.', '.difmrc')).gets.chomp
creds = rc.split(/:/, 2)
raise "Wrong credentials" unless creds.size == 2
rescue
puts "Unable to load rc file (for whatever reason)!"
puts "Make sure you have your credentials in ~/.difmrc in following"
puts " format: username:password"
exit 1
end # }}}1
# Obtain channel list ...
begin # {{{1
difm = DIFM.new(*creds)
rescue
puts "Unable to download channel list from DI.fm:"
puts $!.to_s.gsub(/^/, ' ')
exit 1
end # }}}1
# Perform requested action ...
if ARGV.empty? # {{{1
puts "Available channels:"
puts channels_out(difm.channels)
puts
puts "Give channel name (regex) on cmdline to launch it."
else
res = difm.search_channel(ARGV.join(' '))
case res.size
when 0
puts "No such channel '#{ARGV.join(' ')}' ... :("
when 1
puts "Channel selected: " + res.first.name
puts
exec "mplayer", res.first.url
else
puts "Multiple results:"
puts channels_out(res)
end
end # }}}1
# Yes, this will annoy the hell out of vim newbies:
# vim: set ft=ruby fdm=marker ts=4 :