#!/usr/bin/python
# coding=UTF-8
#
# tvkaista-cli - a command line interface to tvkaista.fi PVR service
#
# http://code.google.com/p/tvkaista-cli/
#
# Copyright (c) 2009-2010 Matti Pöllä
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

__author__ = "Matti Pöllä"
__copyright__ = "Copyright 2009-2010, Matti Pöllä"
__license__ = "GPL"
__email__ = "mpo@iki.fi"
__version__ = "0.65"

import urllib
import urllib2
import re
import os
import htmlentitydefs
import sys
import getopt
import shutil
import time
import math
import webbrowser
import pytz
import datetime
# Tvkaista-cli currently depends on httplib2 but easy_install fails
# to install httplib2 (resulting in HTTP error 404).
try:
    import httplib2
except:
    print "httplib2 is required to run this script. On Debian-based" 
    print "systems run 'sudo apt-get install python-httplib2'."
    exit(1)

from math import floor
from xml.dom import minidom
from subprocess import call

# File suffix for incomplete downloads
TMP_SUFFIX = '.part'
# File suffix for subtitle files
SUB_SUFFIX = '.srt'
# File suffix for thumbnail files
# use '.tbn' for Xbox Media Center (XBMC)
THUMB_SUFFIX = '.jpg'
# Video player to stream with
VPLAYER = 'mplayer'
# Progress bar width
BAR_WIDTH = 30
# Progress bar delay bitmask (sensible values=7,15,31,63)
PROGRESS_DELAY_BITMASK = 15
# Default download limit in bytes/sec, 0=no limit
DOWNLOAD_LIMIT = 0
# File system character set
FILECHARSET = 'utf-8'
# Terminal character set
TERMCHARSET = 'utf-8'

ASITE = 'www.tvkaista.fi'
TIMEZONE = pytz.timezone ("Europe/Helsinki")
TIMEFORMAT = "%a, %d %b %Y %H:%M:%S +0000"

# HTTP headers
headers = {'Content-type': 'application/x-www-form-urlencoded'}
http = httplib2.Http()

settings = {}
att = ['username', 'password', 'format', 'downloaddir']
formats = ['iTunes', 'flv', 'h264', 'ts']
verbose = False
debug = False
prompt = False
show = False
getthumb = True
reverse = False
nmax = False

target = {}
target['list'] = False
target['seasons'] = False
target['search'] = False
target['storage'] = False

# Print list of programs by default
mode = {}
mode['show'] = False
mode['add'] = False
mode['del'] = False
mode['seasonadd'] = False
mode['seasondel'] = False
mode['get'] = False
mode['stream'] = False
mode['play'] = False
mode['summary'] = False

class Timer:
    """
    The Timer class is used as a stopwatch to estimate the
    download speed and remaining time.
    """
    def __init__(self):
        self.t = time.time()
    def gettime(self):
        return time.time() - self.t
    def reset(self):
        self.t = time.time()

class Program:
    """
    The Programm class encapsulates a downloadable item.
    """
    def __init__(self, pid, ptit, plen, pdt):
        self.idn = pid
        self.length = int(plen)
        self.title = ptit
        self.date = time.strftime("%Y-%m-%d", time.localtime(pdt))
        year = time.strftime("%Y", time.localtime(pdt))
        month = time.strftime("%m", time.localtime(pdt))
        self.time = "klo " + time.strftime("%H:%M", time.localtime(pdt))
        self.dir = settings['downloaddir'] + "/" + year + "/" + month
        urlbase = "@" + ASITE + "/recordings/download/"
        self.vurl = "http://" + settings['username'] + ":" + \
                    settings['password'] + urlbase+pid + "." + \
                    formatstring(settings['format'])
        self.surl = "http://" + settings['username'] + ":" + \
                    settings['password'] + urlbase+pid + "" + \
                    SUB_SUFFIX
        self.turl = "http://" + settings['username'] + ":" + \
                    settings['password'] + "@" + ASITE + "/feed/thumbnails/" + pid + ".jpg"
        self.vfile = self.dir + "/" + ptit.replace(" ","_") + "_" + \
                     self.date + "_" + pid + "." + filesuffix(settings['format'])
        self.vfile = self.vfile.encode(FILECHARSET)
        self.sfile = self.dir + "/" + ptit.replace(" ","_") + "_" + \
                     self.date + "_" + pid + "" + SUB_SUFFIX
        self.sfile = self.sfile.encode(FILECHARSET)
        self.tfile = self.dir + "/" + ptit.replace(" ","_") + "_" + \
                     self.date + "_" + pid + "" + THUMB_SUFFIX
        self.tfile = self.tfile.encode(FILECHARSET)

    def is_available(self):
        """ 
        Check file availability using the HTTP response code
        """
        try:
            return (urllib.urlopen(self.vurl).getcode() == 200)
        except AttributeError:
            print "Warning: could not check file availability"
            return True

    def download(self):
        """
        The download method executes the actual download process of
        a video/subtitle file.
        """
        if not os.path.exists(self.dir):
            try:
                os.makedirs(self.dir)
            except OSError:
                print "Could not write to %s" % self.dir
                exit(1)
        print self.label().encode(TERMCHARSET)
        if (os.path.exists(self.vfile)):
            sys.stdout.write("\r[" + BAR_WIDTH * '=' + '] (done)\n')
        else:
            # Check that the file exists - encoding to various video formats
            # can take many hours.
            if not self.is_available():
                print "File not available (try \"-f ts\" for raw MPEG stream)."
                return
            try:
                cursor_hide()
                timer.reset()
                urllib.urlretrieve(self.vurl, self.vfile + TMP_SUFFIX, \
                reporthook=progressbar)
                cursor_unhide()
                if (os.path.exists(self.vfile + TMP_SUFFIX)):
                    shutil.move(self.vfile+TMP_SUFFIX, self.vfile)
                print "\n"
            except KeyboardInterrupt:
                cursor_unhide()
                print "\nDownload interrupted"
                exit(0)
        # Download subtitle file. Remove if empty.
        if (not os.path.exists(self.sfile) and urllib.urlopen(self.surl).getcode() == 200):
            try:
                urllib.urlretrieve(self.surl, self.sfile + TMP_SUFFIX)
                if (os.path.exists(self.sfile + TMP_SUFFIX)):
                    shutil.move(self.sfile + TMP_SUFFIX, self.sfile)
            except KeyboardInterrupt:
                print "\nDownload interrupted"
                exit(0)
            if (os.stat(self.sfile).st_size == 0):
                os.remove(self.sfile)
        # Download thumbnail file
        if (not os.path.exists(self.tfile) and getthumb and urllib.urlopen(self.turl).getcode() == 200):
            try:
                urllib.urlretrieve(self.turl, self.tfile + TMP_SUFFIX)
                if (os.path.exists(self.tfile + TMP_SUFFIX)):
                    shutil.move(self.tfile + TMP_SUFFIX, self.tfile)
            except KeyboardInterrupt:
                print "\nDownload interrupted"
                exit(0)


    def downloaded(self):
        """
        Check whether the recording has already been downloaded.
        """
        return True if os.path.exists(self.vfile) else False

    def label(self, showid=False):
        """
        A short description about the recording.
        """
        return self.date + " " +  self.time + " " + self.title
        
    def longlabel(self, showid=False):
        """
        A longer description about the recording.
        """
        return self.idn + " " + self.date + " " +  self.time + " " + self.title

    def geturl(self):
        return self.vurl
        
    def getid(self):
        return self.idn

    def getsize(self):
        return self.length

def cursor_hide():
    """
    Hide the terminal cursor to reduce UI clutter.
    """
    sys.stdout.write('\033[?25l')

def cursor_unhide():
    """
    Make the cursor visible again after being hidden during the download phase.
    """
    sys.stdout.write('\033[?25h')

# Usage message
def usage():
    """
    Print instruction on how to use this program.
    """
    print "Usage: tvkaista [options] command target"
    print ""
    print "Commands:"
    print " show              show programs (default)"
    print " get               download programs"
    print " stream            stream using an external video player"
    print " play              open in a browser window"
    print ""
    print "Targets:" 
    print " list              playlist"
    print " seasons           seasons of favourites"
    print " search [term]     search for programs (default)"
    print " storage           storage"
    print ""
    print "Options:" 
    print "  -h --help        show this message"
    print "  --version        show version"
    print "  -v               print more information"
    print "  --prompt         ask separately for each program"
    print "  -u               username"
    print "  -p               password"
    print "  -f               video format (iTunes/flv/h264/ts/help)"
    print "  -d               download directory"
    print "  --limit          download limit in kbytes/sec"
    print "  -n               maximum number of results to show"
    print "  -r --reverse     list in reverse order (new to old)"
    print ""

# List available video formats.
def show_format_info():
    print "Available video formats:"
    print " iTunes  300 kbps MPEG-4"
    print " flv       1 Mbps flash video"
    print " h264      2 Mbps MPEG-4"
    print " ts        8 Mbps MPEG-2 transport stream"

# On the first run, prompt the user for settings
def prompt_settings():
    settings = {}
    settings['username'] = raw_input("TVkaista username: ")
    settings['password'] = raw_input("TVkaista password: ")
    settings['format'] = "unknown"
    while (settings['format'] not in formats):
        if (settings['format']) == "help":
            show_format_info()
        settings['format'] = raw_input("File format (iTunes/flv/h264/ts): ")
    settings['downloaddir'] = raw_input("Download directory: ")
    for k in settings.keys():
        print "   " + k + ": " + settings[k]
    while (raw_input("Are these values ok (Y/n)? ") in ['n']):
        prompt_settings()
    return settings

# Write configuration file.
# Format: option = value
def write_rcfile(settings):
    """
    Write settings into a configuration file."
    """
    omask = os.umask(077)
    f = open(getconffile(), 'w')
    f.write("# Configuration file for tvkaista-cli script\n")
    f.write("# http://code.google.com/p/tvkaista-cli/\n")
    for k in settings.keys():
        f.write(k+" = " + settings[k] + "\n")
    f.close()
    os.umask(omask)

# Read configuration file.
# Format: option = value
def read_rcfile(filename):
    """
    Read settings from a configuration file.
    """
    config = {}
    f = open(filename)
    for line in f:
        if "#" in line:
            line, comment = line.split("#", 1)
        if "=" in line:
            # split on option char:
            key, value = line.split("=", 1)
            key = key.strip()
            value = value.strip()
            config[key] = value
    f.close()
    return config

# Transform month name abbreviations into numbers 1-12.
def rfc822month(mon):
    """
    Map month names into zero-padded numbers.
    """
    return {
      'Jan': '01',
      'Feb': '02',
      'Mar': '03',
      'Apr': '04',
      'May': '05',
      'Jun': '06',
      'Jul': '07',
      'Aug': '08',
      'Sep': '09',
      'Oct': '10',
      'Nov': '11',
      'Dec': '12',
    }[mon]

# Filename suffix for a given video format
def filesuffix(format):
    """
    Map format names into file suffices.
    """
    return {
      'iTunes': 'mp4',
      'flv' :   'flv',
      'h264':   'mp4',
      'ts':     'ts'
    }[format]

# Map from format types into file suffices
def formatstring(format):
    """
    Map format names into tvkaista.fi's internal format
    """
    return {
      'iTunes': 'mp4',
      'flv' :   'flv',
      'h264':   'h264',
      'ts':     'ts'
    }[format]

def unescape(text):
    """
    Remove HTML encoding of strings
    Original code from tvkaistaforxbmc plugin
    """
    def fixup(m):
        text = m.group(0)
        if text[:2] == "&#":
            try:
                if text[:3] == "&#x":
                    return unichr(int(text[3:-1], 16))
                else:
                    return unichr(int(text[2:-1]))
            except ValueError:
                pass
        else:
            try:
                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
            except KeyError:
                pass
        return text
    return re.sub("&#?\w+;", fixup, text)

# Helper function for promt; give a name for operation modes
def command_name():
    if mode['get']:
        return "Download"
    if mode['stream']:
        return "Stream"
    if mode['play']:
        return "Play"

# Parse a list of download targets from a RSS file into a list of
# downloadable objects
def feedreader(feed):
    """
    Build a list of recordings from a RSS feed.
    """
    passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
    passman.add_password(None, feed, settings['username'], \
			 settings['password'])
    opener = urllib2.build_opener(urllib2.HTTPBasicAuthHandler(passman))
    urllib2.install_opener(opener)
    try:
        content = urllib2.urlopen(feed).read()
        #print content
    except(urllib2.HTTPError):
        print "HTTP 404 Error: " + feed
        exit(1)

    dom = minidom.parseString(content)
    items = dom.getElementsByTagName('item')
    # Flip order if using "-r"
    if reverse:
        items.reverse()
    # Limit number of result
    if 0 < nmax < len(items):
        items = items[-nmax:]
    ret = []
    for i in items:
        ptit = i.getElementsByTagName('title')[0].childNodes[0]\
	       .nodeValue.replace(" ","_")
        try:
            plen = i.getElementsByTagName('enclosure')[0].\
		   attributes['length'].value
        except:
            plen = 0
        plin = i.getElementsByTagName('link')[0].childNodes[0].nodeValue
        # Tvkaista attribute 'pubDate is in UTF; convert it to EET using pytz
        timestr = i.getElementsByTagName('pubDate')[0].childNodes[0].nodeValue
        tvkaistatime = datetime.datetime.strptime(timestr, TIMEFORMAT)
        tvkaistatime = tvkaistatime.replace(tzinfo=pytz.utc)
        pdt = time.mktime(tvkaistatime.astimezone(TIMEZONE).timetuple())

        pid = re.compile('http://www.tvkaista.fi/search/[?]findid=(\d+)')\
	      .findall(plin)[0]
        program = Program(pid, ptit, plen, pdt)
        if (prompt and not program.downloaded()):
            print program.label().encode(TERMCHARSET)
            try:
                if (prompt and raw_input('Download (Y/n) ') == 'n'):
                    continue
            except KeyboardInterrupt:
                exit(0)
        ret.append(program)
    dom.unlink()
    return ret

# Login to alpha.tvkaista.fi 
def login():
    loginurl = 'http://' + ASITE + '/login/'
    body = {'action': 'login', 'username': settings['username'], \
            'password': settings['password'], 'rememberme':'on'}
    global headers
    headers = {'Content-type': 'application/x-www-form-urlencoded'}
    response, content = http.request(loginurl, 'POST', headers=headers, \
                        body=urllib.urlencode(body))
    return response['set-cookie']

# Add/delete items in the playlist for alpha site.
def editlist(action, idn):
    """
    Edit (add/remove) playlist.
    """
    sessionid = login()
    sessionid = sessionid[11:43] # FIXME: Parse session id properly
    if action == 'add':
        fname = 'addToPlaylist'
    if action == 'del':
        fname = 'removeFromPlaylist'
    url = 'http://%s/dwr/call/plaincall/TVKaistaCentreServer.%s.dwr;jsessionid=%s?=' % (ASITE, fname, sessionid)
    postdata = {    
        'callCount':'1',
        'page':'/recordings/',
        'httpSessionId':sessionid,
        'scriptSessionId':sessionid,
        'c0-scriptName':'TVKaistaCentreServer',
        'c0-methodName':fname,
        'c0-id':'0',
        'c0-param0':'boolean:false',
        'c0-param1':'number:' + str(idn),
        'batchId':'1'
    }
    try:
        content = urllib2.urlopen(url, urllib.urlencode(postdata)).read()
    except Error:
        print "Could not update favourites."
        exit(1)

# Format raw byte count into reasonable units.
def dataformat(b):
    """
    Show data amounts in a reasonable way.
    """
    if (b < 1024):
        return "%d bytes" % b
    b = b / 1024
    if (b < 1024):
        return "%.2f KiB" % b
    b = b / 1024
    if (b < 1024):
        return "%.2f MiB" % b
    b = float(float(b) / 1024.0)
    return "%.2f GiB" % b

def timeformat(s):
    if s < 0:
        return "00:00:00"
    h = floor(s / 3600)
    s = s - h * 3600
    m = floor(s / 60)
    s = s - m * 60
    return "%02d:%02d:%02d" % (h, m, s)

# Progress bar display for urllib.urlretrieve function.
def progressbar(count, blockSize, totalSize):
    """
    Progress bar function for monitoring downloads.
    """
    if (count & PROGRESS_DELAY_BITMASK != 0):
        return
    if (totalSize > 0):
        mcount = math.floor(totalSize / blockSize)
        percent = int(math.ceil(100 * count / mcount))
        blocks = int(math.ceil(BAR_WIDTH * count / mcount))
    else:
        percent = 0
        blocks = 0
    sys.stdout.write("\r[" + blocks * "=" + (BAR_WIDTH - blocks) * ' ' + \
		     "] " + "%02d%% " % percent)
    sys.stdout.write('of %s ' % dataformat(totalSize))
    elapsed = timer.gettime()
    bps_speed = count * blockSize / elapsed
    if DOWNLOAD_LIMIT > 0 and bps_speed > DOWNLOAD_LIMIT:
        sleeptime = (min( 5.0, float((count*blockSize)/DOWNLOAD_LIMIT - elapsed)))
        if sleeptime > 0:
            time.sleep(sleeptime)
    sys.stdout.write('at %s/s ' % dataformat(bps_speed))
    if bps_speed > 0:
        eta = (totalSize - count * blockSize) / (bps_speed)
    else:
        eta = 0
    sys.stdout.write('ETA: %s' % timeformat(eta))
    sys.stdout.write(4 * ' ')
    sys.stdout.flush()

# Return a suitable file name for configuration options.
def getconffile():
    # TODO: OS specific selection of configuration file name.
    return os.getenv("HOME") + "/.tvkaista-cli"

def valid_settings(s, critical):
    """
    Confirm that all required settings have been defined.
    """
    for k in att:
        try:
            if (s[k] == None):
                if (critical):
                    print k + " not specified. See \"tvkaista --help\""
                    sys.exit(1)
                else:
                    return False
        except KeyError:
            if (critical):
                print k + " not specified. See \"tvkaista --help\""
                sys.exit(1)
            else:
                return False
    return True
    
# Add a format-specific suffix to play urls in alpha.tvkaista.fi
def play_url_suffix(format):
    return {
      'iTunes': '/3/300000',
      'flv' :   '/4/1000000',
      'h264':   '/3/2000000',
      'ts':     '/0/8000000'
    }[format]

def print_summary():
    plurl = 'http://%s/feed/playlist/%s.rss' % \
        (ASITE, formatstring(settings['format']))
    dl_togo = 0
    dl_done = 0
    plcount = 0
    for i in feedreader(plurl):
        plcount += 1
        if i.downloaded():
            dl_done += i.getsize()
        else:
            dl_togo += i.getsize()
    if dl_togo == 0:
        print "Download complete."
    else:
        perc = (100 * float(dl_done)/float(dl_done+dl_togo))
        print "Playlist: %s to be downloaded (%02d %% of %s complete) " % \
        (dataformat(dl_togo), perc, dataformat(dl_togo + dl_done))

# Main program
def main(argv):

    global conffile
    global verbose
    global debug
    global prompt
    global settings
    global timer
    global target
    global DOWNLOAD_LIMIT
    global nmax
    global reverse

    timer = Timer()
    exp_settings = {}

    # Processing of command line options using getopt.
    try:
        opts, args = getopt.getopt(argv, "hvprf:u:p:d:n:", ["help", "verbose", \
                     "debug", "prompt", "reverse", "format=", "username=", \
                     "password=", "downloaddir=", "limit="])
    except getopt.GetoptError:
        usage()
        sys.exit(2)
    for opt, arg in opts:
        if opt in ("-h", "--help"):
            usage()
            sys.exit()
        if opt in ("-f", "--format"):
            exp_settings['format'] = arg
        if opt in ("-u", "--username"):
            exp_settings['username'] = arg
        if opt in ("-p", "--password"):
            exp_settings['password'] = arg
        if opt in ("-d", "--downloaddir"):
            exp_settings['downloaddir'] = arg
        if opt in ("-v", "--verbose"):
            verbose = True
        if opt == "--debug":
            debug = True
        if (opt == "--prompt"):
            prompt = True
        if (opt == "--limit"):
            DOWNLOAD_LIMIT = 1024*int(arg)
        if opt in ("-n"):
            try:
                nmax = int(arg)
            except ValueError:
                print "Invalid argument for -n"
                exit(1)
        if opt in ("-r", "--reverse"):
            reverse = True

    if len(args) == 0:
        usage()
        exit(0)

    # Parse action
    if args[0] in mode.keys():    
        mode[args[0]] = True
        if debug:
            print "MODE: " + args[0]
        args.pop(0)
    # Default to show
    else:
        mode['show'] = True
        if debug:
            print "MODE: show"

    # Look for program ID numbers as arguments
    target_ids = re.findall('([0-9]{6,7})', " ".join(args))

    # No target given
    if len(args) == 0 and not mode['summary']:
        usage()
        exit(0)

    # Parse target
    if not mode['summary'] and args[0] in target.keys():
        target[args[0]] = True
        args.pop(0)
        if debug:
            print "TARGET: " + args[0]
    # Specific IDs
    elif len(target_ids) > 0:
        target['byid'] = True
        if debug:
            print "TARGET: IDs " + ",".join(target_ids)
    # Default to search
    else:
        if debug:
            print "TARGET: search (default)"
        target['search'] = True
        if not mode['summary'] and args[0] == 'search':
            args.pop(0)
    search_terms = " ".join(args)
    if debug:
        print "TARGET: search \"" + search_terms + "\""

    # First see whether a configuration file is available.
    conffile = getconffile()

    if os.path.exists(conffile):
        if (verbose):
            print "Reading configuration from " + conffile
        settings = read_rcfile(conffile)
    else:
        if (not valid_settings(exp_settings, False)):
            settings = prompt_settings()
            write_rcfile(settings)

    # Override configuration file if necessary.
    settings.update(exp_settings)

    # Make sure that all required settings have been defined.
    valid_settings(settings, True)

    # Debug: print all settings
    if debug:
        for k in att:
            print k + ": " + settings[k]

    if sum(mode.values()) > 1:
        print "Only one of show/get/stream/play/add/del is allowed."
        sys.exit(0)

    if mode['summary']:
        print_summary()
        exit(0)

    # If no mode if specified, select 'show'
    elif sum(mode.values()) == 0:
        mode['show'] = True

    if sum(target.values()) > 1:
        print "Only one of target is allowed."
        sys.exit(0)

    # Add/del using program id numbers
    if mode['add'] and len(target_ids) > 0:
        if debug:
            print "ADD: " + " ".join(target_ids)
        [editlist("add", t) for t in target_ids]
        exit(0)
    if mode['del']:
        if debug:
            print "DEL: " + " ".join(target_ids)
        [editlist("del", t) for t in target_ids]
        exit(0)
        
    # Add to playlist using search terms
    if mode['add'] and len(target_ids) == 0:
        plurl = 'http://%s/feed/playlist/%s.rss' % \
                (ASITE, formatstring(settings['format']))
        plids = []
        [plids.append(i.getid()) for i in feedreader(plurl)]
        if debug:
            print "ADD: (search terms) ".join(search_terms)
        feed = "http://" + ASITE + "/feed/search/title/" + \
               urllib.urlencode({'':search_terms})[1:] + "/" + \
               formatstring(settings['format'])+".rss"
        targets = feedreader(feed)    
        for t in targets:
            if t.getid() not in plids:
                print t.longlabel().encode(TERMCHARSET) 
                if raw_input('Add to list (Y/n) ') in ['n', 'e']:
                    continue
                editlist("add", t.getid())
        exit(0)

    # Seasonadd/seasondel mode
    if mode['seasonadd'] or mode['seasondel']:
        print "Not implemented yet."
        exit(0)

    # Playlist
    plurl = 'http://%s/feed/playlist/%s.rss' % \
    (ASITE, formatstring(settings['format']))
    if target['list']:
        feed = plurl
        
    # Storage
    sturl = 'http://%s/feed/storage/%s.rss' % \
    (ASITE, formatstring(settings['format']))
        
    if target['storage']:
        feed = sturl
        
    # Seasons
    elif target['seasons']:
        feed = 'http://%s/feed/seasonpasses/*/%s.rss' % \
        (ASITE, formatstring(settings['format']))
    # Search
    elif target['search']:
        feed = "http://" + ASITE + "/feed/search/title/" + \
        urllib.urlencode({'':search_terms})[1:] + "/" + \
        formatstring(settings['format'])+".rss"

    # Exception: program id numbers have been specified without
    # an action to perform.  
    if len(target_ids) > 0 and mode['show']:
        print "Please specify what to do with program id numbers (add/del)."
        exit(0)

    targets = feedreader(feed)

    if len(targets) == 0:
        print "No targets found."
        exit(0)

    # Stream mode
    if mode['stream']:
        if debug:
            print "STREAM " + targets[-1].getid()
        if targets[-1].is_available():
            try:
                call([VPLAYER, targets[-1].geturl()])
            except:
                print "Could not launch streaming. Try 'play' mode."
        else:
            print "File not available (try \"-f ts\" for raw MPEG stream)."
            
            
    # Play mode
    if mode['play']:
        if debug:
            print "PLAY " + targets[-1].getid()
        purl = 'http://%s/recordings/play/%s%s' % \
               (ASITE, targets[-1].getid(), play_url_suffix(settings['format']))
        webbrowser.open(purl)

    # Show mode
    elif mode['show']: # None of show[''] variables are set
        if target['list']:
            pltargets = targets
        else:
            pltargets = feedreader(plurl)
        pl_ids = []
        # Get list of program IDs of the playlist
        [pl_ids.append(t.getid()) for t in pltargets]
        if debug:
            print "SHOW " + targets[-1].getid()
        for t in targets:
            box = ''
            if t.getid() in pl_ids:
                box += '[L'
            else:
                box += '[ '
            if t.downloaded():
                box += '*] '
            else:
                box += ' ] '
            print box + t.longlabel().encode(TERMCHARSET)
    # Get mode
    elif mode['get'] and len(targets) > 0:
        if debug:
            print "GET " + ", ".join([t.getid() for t in targets])
        [t.download() for t in targets]
    sys.exit(0)

if __name__ == "__main__":
    main(sys.argv[1:])

