#!/usr/bin/env python # -*- coding: utf-8 -*- # # tv_grab_ar.py # # Copyright 2009-2012, Mauro A. Meloni # http://bitnegro.blogspot.com/ # # 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 # the Free Software Foundation; either version 2 of the License, or # (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 . # # # Version history: # # 2012.12.04-1 Fix sutil por cambio en el disenio del sitio web # 2012.05.02-1 Tratamiento de excepcion ante errores de HTTP y DNS # 2012.04.04-1 Tratamiento de excepcion ante fichaId invalida # 2012.03.24-1 Solo se guarda el cache al fin del proceso (faster!) # 2011.12.25-1 Los mensajes de informacion van ahora a stderr # 2011.12.22-1 (Bug)fixes en la seleccion de zona y canales # 2011.09.16-1 Nueva recuperacion de canales por redisenio de la web # 2011.09.15-1 Salida de fecha y hora utilizando huso horario UTC # 2011.09.14-1 Implementados switches capabilities y description # 2011.06.06-1 Backup del archivo de fichas en caso de corrupcion # 2011.05.30-1 Fix error de codificacion al mostrar la lista de zonas # 2011.04.27-1 Fix p/despliegue de nombres de canal (gracias a Donato) # 2011.03.11-1 Adicion de cambio de zona (gracias a Mariano Cosentino) # 2010.11.19-1 Modificacion para que lea el genero de los programas # 2010.10.16-1 Posibilidad de obtener grilla de la semana posterior # 2010.09.13-1 Crea directorios de configuracion si no existen # 2010.09.11-1 Adicion de posibilidad de cambio de zona # 2010.09.06-1 Fix para fichaId vacia # 2010.08.24-1 Nuevas rutinas debido al redisenio del sitio web # 2010.08.19-1 Pretty-Print XML # 2010.08.18-1 Adicion de esperas para evitar saturar al servidor # 2010.08.17-1 Conversion desde beautifulsoup a lxml (faster!) # 2010.07.19-1 Fix para manejar los valores de canales en el menu # 2010.05.19-1 Crear cookie de zona en vez de obtenerla del sitio # 2010.04.27-1 Cambio de url para retrieve_descriptions # 2010.04.25-1 Cambio de url para retrieve_programs # 2010.04.06-1 Manejo de excepcion al obtener las descripciones # 2010.03.31-2 Manejo de excepcion al obtener la programacion semanal # 2010.03.31-1 Multicanal es ahora Cablevision # 2009.09.21-1 Fix para multicanal, que tiene mal el nro de TCM # 2009.09.17-3 Despliegue de genero como subtitulo # 2 Fix para programas emitidos en dias sucesivos # 2009.09.17-1 Correccion de stationlist.xml # 2009.09.16-6 Xmltv Writer ad hoc # 5 Descarga de canales ordenados por id # 4 Cache de fichas # 3 Fixes a temas de encoding # 2 Recuperacion de descripciones # 2009.09.16-1 Version inicial # # Para determinar el codigo de zona, ver # http://www.buscadorcablevision.com.ar/popUpSeleccionZona.php?paisSeleccionado=cd # import codecs import cookielib from time import sleep from datetime import datetime, date, time, timedelta, tzinfo from lxml import etree, html import optparse from os import mkdir, rename from os.path import dirname, expanduser, join, isdir, isfile, splitext import cPickle import re from shutil import copy from StringIO import StringIO import sys import urllib2 VERSION = '2012.12.04-1' LANG = u'es' DATETIME_FMT = '%Y%m%d%H%M %Z' INVALID_CHARS = r'[^A-Za-z0-9.,;\-\s]' TVTIME_CONFIG_DIR = expanduser('~/.tvtime') XMLTV_CONFIG_DIR = expanduser('~/.xmltv') SLEEP_TIME = 1.3 def parse_style (styledef): style = {} items = styledef.split(';') map(''.strip, items) for item in items: try: (tag, value) = item.split(':') except ValueError: continue style[tag.strip()] = value.strip() return style def remove_letters (data): return re.sub(r'[^-0-9]', '', data) def sec_to_hour (seconds): return '%02d:%02d' % (seconds / 3600, (seconds / 60) % 60) class GMT (tzinfo): def __init__ (self, offset): self.__offset = timedelta(hours=offset) if offset < 0: sign = '-' else: sign = '+' self.__name = sign + '%02d' % abs(offset) + '00' def utcoffset (self, dt): return self.__offset def dst (self, dt): return timedelta(0) def tzname (self, dt): return self.__name class Writer: def __init__ (self, encoding, source_info_url, source_info_name, generator_info_name, generator_info_url): self.encoding = encoding self.source_info_url = source_info_url self.source_info_name = source_info_name self.generator_info_name = generator_info_name self.generator_info_url = generator_info_url self.channels = [] self.programs = [] def addChannel (self, d): self.channels.append(d) def addProgramme (self, d): self.programs.append(d) def xml_start (self): template = ''' ''' return template % ( self.encoding, self.generator_info_name, self.generator_info_url, self.source_info_name, self.source_info_url, ) def channel_to_xml (self, d): elem = etree.Element('channel', { 'id': unicode(d['id']) } ) for (text, lang) in d['display-name']: etree.SubElement(elem, 'display-name', { 'lang': lang } ).text = unicode(text) if d.has_key('icon'): for item in d['icon']: etree.SubElement(etree, 'icon', { 'src': item } ) return elem def program_to_xml (self, d): attrs = { 'channel': unicode(d['channel']), 'start': d['start'], 'stop': d['stop'], } elem = etree.Element('programme', attrs) for (text, lang) in d['title']: etree.SubElement(elem, 'title', { 'lang': lang } ).text = text if d.has_key('desc'): for (text, lang) in d['desc']: etree.SubElement(elem, 'desc', { 'lang': lang } ).text = text if d.has_key('category'): # hack for tvtime itemlang = u'' itemtext = u'' for (text, lang) in d['category']: # output += u' %s\n' % (lang, text) itemlang = lang itemtext += text + ', ' if itemtext: itemtext = 'Genero: %s' % itemtext[:-2] etree.SubElement(elem, 'sub-title', { 'lang': itemlang } ).text = itemtext return elem def indent (self, elem, level=0): # By Filip Salomonsso # http://infix.se/2007/02/06/gentlemen-indent-your-xml i = "\n" + level * "\t" if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + "\t" for e in elem: self.indent(e, level+1) if not e.tail or not e.tail.strip(): e.tail = i + "\t" if not e.tail or not e.tail.strip(): e.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i def build_tree (self): tree = etree.parse(StringIO(self.xml_start())) root = tree.getroot() for d in self.channels: root.append(self.channel_to_xml(d)) for d in self.programs: root.append(self.program_to_xml(d)) self.indent(tree.getroot()) return tree def writetofile (self, filename): tree = self.build_tree() tree.write( filename, encoding='utf-8', xml_declaration=True, ) def tostring (self): tree = self.build_tree() data = etree.tostring( tree, encoding='utf-8', xml_declaration=True, ) return data class XmltvChannel: def __init__ (self, id, number, name): self.id = int(id) self.number = int(number) self.name = name self.icon = None self.url = None self.enabled = True def get_dict (self): d = {} d['id'] = int(self.id) d['display-name'] = [ (self.name, LANG), (self.number, LANG), ] if self.icon: d['icon'] = [self.icon] if self.url: d['url'] = self.url return d def __str__ (self): return '%02d - %s (id %d)' % (self.number, self.name.encode('utf-8'), self.id) class XmltvProgram: def __init__ (self): self.channel = None self.start = None self.stop = None self.title = '' self.description = '' self.ficha = None self.duration = None self.starthour = None self.data = None def get_dict (self): d = {} d['channel'] = int(self.channel) d['title'] = [(self.title, LANG)] if self.description: d['desc'] = [(self.description, LANG)] d['start'] = self.start.strftime(DATETIME_FMT) d['stop'] = self.stop.strftime(DATETIME_FMT) if self.data: for key in ('G\xc3\xa9nero', u'G\xe9nero'): if not self.data.has_key(key): continue cats = [] for cat in self.data[key].split(','): cats.append((cat.strip(), LANG)) d['category'] = cats for key in ('País', u'Pa\xeds'): if not self.data.has_key(key): continue d['country'] = [(self.data[key], LANG)] for key in ('Año', u'A\xf1o'): if not self.data.has_key(key): continue d['date'] = [self.data[key]] #key = 'T\xedtulo original' #if self.data.has_key(key): # d['original-title'] = [(self.data[key], LANG)] #for key in ('Protagonista', 'Protagonistas'): # if not self.data.has_key(key): continue # d['cast'] = [(self.data[key], LANG)] #for key in ('Director', 'Directores'): # if not self.data.has_key(key): continue # d['director'] = [(self.data[key], LANG)] #key = 'Clasificaci\xf3n' #if self.data.has_key(key): # d['rating'] = [(self.data[key], LANG)] #key = 'Duraci\xf3n' #if self.data.has_key(key): # d['length'] = [(self.data[key], LANG)] #key = 'Conductor' #if self.data.has_key(key): # d['host'] = [(self.data[key], LANG)] #audio #credits #episode-num #language #last-chance #length #new #orig-language #premiere #previously-shown #rating #star-rating #sub-title #subtitles #video return d def __str__ (self): retval = '' if self.channel is not None: retval = 'Channel: \t%s\n' % self.channel retval += 'Title: \t%s\n' % self.title.encode('utf-8') retval += 'Description: \t%s\n' % self.description if self.start is not None: retval += 'Start: \t%s\n' % self.start if self.stop is not None: retval += 'Stop: \t%s\n' % self.stop if self.duration is not None: retval += 'Duration: \t%s\n' % sec_to_hour(self.duration) if self.starthour is not None: retval += 'Start Hour: \t%s\n' % sec_to_hour(self.starthour) retval += 'Data:\n' if self.data: for item in self.data: retval += '\t%s: %s\n' % (item, self.data[item]) return retval class TvGrabAr: START_CONST = 3600 / 67 DURATION_CONST = 3600 / 67 def __init__ (self, provider = 'CABLEVISION'): self.fichasdb = join(TVTIME_CONFIG_DIR, 'fichas.db') self.options = None self.base_url = None self.base_domain = None if provider == 'CABLEVISION': self.base_domain = 'www.buscadorcablevision.com.ar' self.base_url = 'http://' + self.base_domain self.opener = urllib2.build_opener(urllib2.ProxyHandler()) self.input_timezone = GMT(-3) # zona horaria de la grilla self.output_timezone = GMT(0) # salida en UTC por defecto self.fichas = {} self.codigo_pais = 'ca' self.codigo_zona = None self.nombre_zona = None def description (self): print 'Argentina (%s)' % self.base_domain def capabilities (self): print 'baseline' print 'manualconfig' print 'cache' def get_config_zona (self): #if not isdir(dirname(self.options.config_file)): # mkdir(dirname(self.options.config_file)) # si fue establecida por parametro if self.options.codigo_zona: self.codigo_zona = self.options.codigo_zona self.nombre_zona = 'USER SELECTED LOCATION' return # si esta en el archivo de configuracion if isfile(self.options.config_file): for line in open(self.options.config_file): datum, id, name = line.strip().split(' ', 2) if datum == 'location': self.codigo_zona = int(id) self.nombre_zona = name return # en cualquier otro caso self.codigo_zona = 3 self.nombre_zona = 'Capital Federal y GBA' def set_cookie (self, codigo_pais=None, codigo_zona=None): cj = cookielib.CookieJar() if codigo_pais is None: codigo_pais = self.codigo_pais if codigo_zona is None: codigo_zona = self.codigo_zona # bake the cookies ck1 = cookielib.Cookie( name='paisSeleccionado', value=codigo_pais, domain=self.base_domain, domain_specified=False, domain_initial_dot=False, version=0, port=None, port_specified=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False ) ck2 = cookielib.Cookie( name='zonaSeleccionada', value=str(codigo_zona), domain=self.base_domain, domain_specified=False, domain_initial_dot=False, version=0, port=None, port_specified=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False ) cj.set_cookie(ck1) cj.set_cookie(ck2) self.opener = urllib2.build_opener(urllib2.ProxyHandler(), urllib2.HTTPCookieProcessor(cj)) def retrieve_locations (self): locations = {} url = self.base_url + '/popUpSeleccionZona.php?paisSeleccionado=' + self.codigo_pais if self.options.verbose: print 'Retrieving %s ... ' % url body = html.parse(self.opener.open(url)) ul = body.find(".//ul") if ul is None: print 'No locations found online.' print 'Maybe the website is offline or it has been recently redesigned.' return locations for elem in ul.iterchildren('li'): aelem = elem.find('a') code = int(aelem.get('href').split('=')[-1]) name = aelem.text locations[code] = name if self.options.verbose: print 'Found %d locations available.' % len(locations) return locations def retrieve_channels (self): if self.codigo_zona is None: self.get_config_zona() channels = {} url = self.base_url + '/popUpGuiaCanales.php?companiaSeleccionada=%d' % self.codigo_zona if self.options.verbose: print >> sys.stderr, 'Retrieving %s ... ' % url body = html.parse(self.opener.open(url)) grilla = body.find(".//div[@class='items']") if grilla is None: print >> sys.stderr, 'No channels found online.' print >> sys.stderr, 'Maybe the website is offline or it has been recently redesigned.' return channels for elem in grilla.iterdescendants('li'): aelem = elem.find('a') (channid, num) = re.findall('canal=(\d+)&sintonia=(\d+)', aelem.get('onclick'))[0] channel = XmltvChannel(channid, num, aelem.get('title').strip()) channels[channel.id] = channel if self.options.verbose: print >> sys.stderr, 'Found %d channels online.' % len(channels) return channels def retrieve_week (self, channel, day, weekadvance, programs): startDay = datetime.combine(day, time.min) startDay -= timedelta(startDay.weekday()) startDay += timedelta(weekadvance * 7) url = '/dinamicas/grilla_semanal/index.php?canal=%d&sintonia=%d&cantidadPasa=%d' % (channel.id, channel.number, weekadvance) if self.options.verbose: print >> sys.stderr, 'Retrieving %s ...' % url try: body = html.parse(self.opener.open(self.base_url + url)) except urllib2.HTTPError, e: # ante algun error al obtener la grilla, omitir la programacion de la misma print >> sys.stderr, 'HTTP error: %s ' % str(e) return [] except urllib2.URLError, e: # ante algun error al obtener la grilla, omitir la programacion de la misma print >> sys.stderr, 'URL error: %s ' % str(e) return [] sleep(SLEEP_TIME) grilla = body.find(".//div[@id='ContGrillaSemanal']") if grilla is None: print >> sys.stderr, 'No weekly grid found online.' print >> sys.stderr, 'Maybe the website is offline or it has been recently redesigned.' return [] for col in grilla[0].iterchildren('div'): if not col.get('class').startswith('diaSemana'): continue for celda in col.iterchildren('a'): programs.append(self.parse_program(channel, startDay, celda)) startDay += timedelta(1) def retrieve_grid (self, channel, day): if self.options.verbose: print >> sys.stderr, 'Channel %s' % channel programs = [] self.retrieve_week(channel, day, 0, programs) if self.options.two_weeks: self.retrieve_week(channel, day, 1, programs) if self.options.verbose: print >> sys.stderr, 'Found %d programs.' % len(programs) return programs def parse_program (self, channel, startDay, celda): prog = XmltvProgram() prog.channel = channel.id prog.title = celda.get('title').strip() try: href = celda.get('href') if href == '#': href = celda.get('rev') fichaid = href.split('=')[1] except AttributeError: pass except IndexError: pass else: if fichaid.isdigit(): prog.ficha = int(fichaid) style = parse_style(celda.get('style')) top = max(0, int(remove_letters(style['top']))) height = max(0, int(remove_letters(style['height']))) prog.starthour = (top + 1) * TvGrabAr.START_CONST prog.duration = height * TvGrabAr.DURATION_CONST programStartHour = datetime.strptime(sec_to_hour(prog.starthour), '%H:%M') programDuration = datetime.strptime(sec_to_hour(prog.duration), '%H:%M') prog.start = datetime.combine(startDay.date(), programStartHour.time()) # conversion de zona horaria prog.start = prog.start.replace(tzinfo=self.input_timezone) prog.start = prog.start.astimezone(self.output_timezone) # fin conversion de zona horaria prog.stop = prog.start + timedelta(hours=programDuration.hour, minutes=programDuration.minute) if not self.options.skip_descriptions: self.retrieve_descriptions(prog) return prog def retrieve_descriptions (self, prog): if not prog.ficha: return url = '/ficha.php?verFicha=%d' % prog.ficha if self.fichas.has_key(prog.ficha): if self.options.verbose: print >> sys.stderr, 'Skipping %s ...' % url (prog.description, prog.data) = self.fichas[prog.ficha] else: if self.options.verbose: print >> sys.stderr, 'Retrieving %s ...' % url try: fh = self.opener.open(self.base_url + url) buffr = fh.read() fh.close() except urllib2.HTTPError, e: # ante algun error al obtener la descripcion, omitirla print >> sys.stderr, 'HTTP error: %s ' % str(e) return except urllib2.URLError, e: # ante algun error al obtener la descripcion, omitirla print >> sys.stderr, 'URL error: %s ' % str(e) return body = html.fromstring(buffr) sleep(SLEEP_TIME) if body is None: return divFicha = body.find(".//div[@id='ficha']") if divFicha is None: return None p = divFicha.find(".//div[@class='sinopsis']").find('p') if p is not None and p.text: prog.description = p.text ulFichaTecnica = divFicha.find(".//ul[@id='fichaTecnica']") data = {} key = '' val = '' for li in ulFichaTecnica.iterchildren('li'): clase = li.get('class') if clase.startswith('itemf'): key = li.text.strip() elif clase.startswith('descf'): val = li.text.strip() if key and val: data[key] = val prog.data = data self.fichas[prog.ficha] = (prog.description, prog.data) def select_channels (self): channels = self.retrieve_channels() chanlist = channels.values() chanlist = sorted(chanlist, cmp=lambda x,y: x.id - y.id) add_all = False skip_all = False for channel in chanlist: prompt = 'add channel %s [yes, no, all, none] ? ' % str(channel) if not add_all and not skip_all: reply = None while reply not in ['y', 'yes', 'n', 'no', 'all', 'none', '']: reply = raw_input(prompt).strip().lower() if reply == '' or reply == 'y' or reply == 'yes': channel.enabled = True elif reply == 'n' or reply == 'no': channel.enabled = False elif reply == 'all': channel.enabled = True add_all = True elif reply == 'none': channel.enabled = False skip_all = True elif add_all: channel.enabled = True print prompt + 'yes' elif skip_all: channel.enabled = False print prompt + 'no' return chanlist def select_location (self): locations = self.retrieve_locations() ordenadas = sorted(locations.items(), key=lambda x: x[1]) zone = None while not locations.has_key(zone): c = r = 0 for code, name in ordenadas: c += 1 r += 1 print "%4d. %-30s " % (code, name[:30]), if c == 2: print c = 0 if r == 46: raw_input('more below -- press Enter key to continue ') r = 0 print try: zone = int(raw_input('enter your location code: ')) except ValueError: pass print 'location selected: ', locations[zone] self.codigo_zona = zone self.nombre_zona = locations[zone] def configure (self): self.set_cookie(self.codigo_pais, 3) # requerido para evitar el loop if not isdir(dirname(self.options.config_file)): mkdir(dirname(self.options.config_file)) conf = codecs.open(self.options.config_file, 'w', 'UTF-8') self.select_location() conf.write(u'location %d %s\n' % (self.codigo_zona, self.nombre_zona)) self.set_cookie() chanlist = self.select_channels() for channel in chanlist: if not channel.enabled: conf.write('#') conf.write(u'channel %d %s\n' % (channel.id, channel.name)) conf.close() print 'Finished configuration.' def set_enabled_channels (self, channels): for id in channels: channels[id].enabled = False enabled = 0 if not isdir(dirname(self.options.config_file)): mkdir(dirname(self.options.config_file)) if not isfile(self.options.config_file): return False for line in open(self.options.config_file): (chan, id, name) = line.split(' ', 2) if chan != 'channel': continue enabled += 1 if int(id) in channels: channels[int(id)].enabled = True if self.options.verbose: print >> sys.stderr, 'Found %d channels enabled.' % enabled return True def fix_stationlist (self, channels): if self.options.verbose: print >> sys.stderr, 'Fixing %s names ... ' % self.options.station_file, tree = etree.parse(self.options.station_file) stations = tree.findall('.//{http://tvtime.sourceforge.net/DTD/}station') if not stations: if self.options.verbose: print >> sys.stderr, 'not a stationlist.xml file.' return for station in stations: if not station.get('channel').isdigit(): continue number = int(station.get('channel')) item = [chan for chan in channels if chan.number == number] if item: station.set('name', item[0].name) try: tree.write( self.options.station_file, encoding='utf-8', xml_declaration=True, pretty_print=True ) if self.options.verbose: print >> sys.stderr, 'done.' except IOError: if self.options.verbose: print >> sys.stder, 'could not write %s.' % self.options.station_file def sort_programs (self, programs): return sorted(programs, cmp=lambda x,y: (x.start - y.start).days * 24 * 3600 + (x.start - y.start).seconds) def load_fichas (self): if not isdir(dirname(self.fichasdb)): mkdir(dirname(self.fichasdb)) for ntry in range(3): if self.options.verbose: print >> sys.stderr, 'Reading program card cache ... ', try: fh = open(self.fichasdb, 'rb') self.fichas = cPickle.load(fh) fh.close() except (EOFError, cPickle.UnpicklingError): # verificar si es posible restaurar un backup base, ext = splitext(self.fichasdb) fichasbak = base + '.bak' if isfile(fichasbak): if self.options.verbose: print >> sys.stderr, 'corrupt file, using backup' copy(fichasbak, self.fichasdb) else: if self.options.verbose: print >> sys.stderr, 'corrupt file, discarded' rename(self.fichasdb, base + '.old') except IOError: if self.options.verbose: print >> sys.stderr, 'could not load %s' % self.fichasdb break else: if self.options.verbose: print >> sys.stderr, '%d programs known.' % len(self.fichas) break def save_fichas (self): if not isdir(dirname(self.fichasdb)): mkdir(dirname(self.fichasdb)) if self.options.verbose: print >> sys.stderr, 'Saving program card cache ... ', try: fh = open(self.fichasdb, 'wb') cPickle.dump(self.fichas, fh) fh.close() if self.options.verbose: print >> sys.stderr, 'done.' # backup del archivo de fichas base, ext = splitext(self.fichasdb) copy(self.fichasdb, base + '.bak') except IOError: if self.options.verbose: print >> sys.stderr, 'could not write %s' % self.fichasdb def grab (self): if self.codigo_zona is None: self.get_config_zona() self.set_cookie() if self.options.verbose: print >> sys.stderr, 'Configured location: %d. %s' % (self.codigo_zona, self.nombre_zona) if self.options.verbose: print >> sys.stderr, 'Getting list of channels' channels = self.retrieve_channels() if not self.set_enabled_channels(channels): print >> sys.stderr, "Please run './tv_grab_ar.py --configure' first." print >> sys.stderr, '' return channels = channels.values() channels = sorted(channels, cmp=lambda x,y: x.id - y.id) if self.options.fix_stationlist: self.fix_stationlist(channels) self.load_fichas() if not self.options.list_channels: programs = [] for channel in channels: if not channel.enabled: continue programs += self.retrieve_grid(channel, date.today()) self.save_fichas() programs = self.sort_programs(programs) xml = Writer( \ encoding='UTF-8', source_info_url=self.base_url, source_info_name=self.base_url, generator_info_name='tv_grab_ar.py ' + VERSION, generator_info_url='http://bitnegro.blogspot.com/' ) for channel in channels: if channel.enabled: xml.addChannel(channel.get_dict()) if not self.options.list_channels: for program in programs: xml.addProgramme(program.get_dict()) if options.output: xml.writetofile(options.output) else: print xml.tostring() if __name__ == '__main__': reload(sys) sys.setdefaultencoding('utf-8') parser = optparse.OptionParser( version='%prog ' + VERSION, description='Get Argentinian television listings in XMLTV format' ) parser.add_option('--days', type='int', dest='days', default=3, metavar='N', help='Grab N days. The default is 3. [not implemented]') parser.add_option('--offset', type='int', dest='offset', default=0, metavar='N', help='Start N days in the future. The default is to start from today. [not implemented]') parser.add_option('--skip-descriptions', action='store_true', dest='skip_descriptions', default=False, help='Do not download program descriptions.') parser.add_option('--output', dest='output', metavar='FILE', help='Write to FILE rather than standard output.') parser.add_option('--fix-stationlist', action='store_true', dest='fix_stationlist', help='Fill the channel names into the station list file.') parser.add_option('--station-file', dest='station_file', metavar='FILE', default=join(TVTIME_CONFIG_DIR, 'stationlist.xml'), help='Set the name of the station list file, the default is <' + join(TVTIME_CONFIG_DIR, 'stationlist.xml') + '>') parser.add_option('--configure', action='store_true', dest='configure', help='Prompt for which channels and write the configuration file.') parser.add_option('--config-file', dest='config_file', metavar='FILE', default=join(XMLTV_CONFIG_DIR, 'tv_grab_ar.conf'), help='Set the name of the configuration file, the default is <' + join(XMLTV_CONFIG_DIR, 'tv_grab_ar.conf') + '>. This is the file written by --configure and read when grabbing.') parser.add_option('--quiet', action='store_true', dest='quiet', default=False, help='Suppress the progress messages normally written to standard error.') parser.add_option('--verbose', action='store_true', dest='verbose', default=False, help='Display additional information.') parser.add_option('--list-channels', action='store_true', dest='list_channels', help='Display only the channel listing.') parser.add_option('--zone', type='int', dest='codigo_zona', metavar='N', help='Override user location for retrieval of channels.') parser.add_option('--two-weeks', action='store_true', dest='two_weeks', default=False, help='Try to obtain both the current and the next week\' programming grid.') parser.add_option('--capabilities', action='store_true', dest='capabilities', help='Show which capabilities the grabber supports. For more information, see ') parser.add_option('--describe', action='store_true', dest='description', help='Show a brief description of the grabber.') parser.add_option('--description', action='store_true', dest='description', help='Show a brief description of the grabber.') parser.add_option('--cache', dest='cache_file', metavar='FILE', help='Unused (only for compatibility). [not implemented]') (options, args) = parser.parse_args() if options.days < 0: parser.error('number of days must not be negative') if options.verbose: print >> sys.stderr, 'tv_grab_ar.py %s\n' % VERSION app = TvGrabAr() app.options = options if options.capabilities: app.capabilities() elif options.description: app.description() elif options.configure: app.configure() else: app.grab()