/resources/lib/tvrage/tvrage/api.py
Python | 233 lines | 170 code | 18 blank | 45 comment | 22 complexity | 0c90e884988a82ad2dadf776f399a523 MD5 | raw file
- # Copyright (c) 2009, Christian Kreutzer
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are met:
- #
- # * Redistributions of source code must retain the above copyright notice,
- # this list of conditions, and the following disclaimer.
- # * Redistributions in binary form must reproduce the above copyright notice,
- # this list of conditions, and the following disclaimer in the
- # documentation and/or other materials provided with the distribution.
- # * Neither the name of the author of this software nor the name of
- # contributors to this software may be used to endorse or promote products
- # derived from this software without specific prior written consent.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- # POSSIBILITY OF SUCH DAMAGE.
- import feeds
- from datetime import date
- from time import mktime, strptime
- from exceptions import (ShowHasEnded, FinaleMayNotBeAnnouncedYet,
- ShowNotFound, NoNewEpisodesAnnounced)
- from util import _fetch, parse_synopsis
- class Episode(object):
- """represents an tv episode description from tvrage.com"""
- def __init__(self, show, season, airdate, title, link, number, prodnumber):
- self.show = show
- self.season = season
- try:
- self.airdate = date.fromtimestamp(mktime(
- strptime(airdate, '%Y-%m-%d')))
- except ValueError:
- self.airdate = None
- self.title = title
- self.link = link
- self.number = number
- self.prodnumber = prodnumber
- self.recap_url = link + '/recap'
- self.id = link.split('/')[-1]
- def __unicode__(self):
- return u'%s %sx%02d %s' % (self.show,
- self.season, self.number, self.title)
- __str__ = __repr__ = __unicode__
- @property
- def summary(self):
- """parses the episode's summary from the episode's tvrage page"""
- try:
- page = _fetch(self.link).read()
- if not 'Click here to add a summary' in page:
- summary = parse_synopsis(page, cleanup='var addthis_config')
- return summary
- except Exception, e:
- print('Episode.summary: %s, %s' % (self, e))
- return 'No summary available'
- @property
- def recap(self):
- """parses the episode's recap text from the episode's tvrage recap
- page"""
- try:
- page = _fetch(self.recap_url).read()
- if not 'Click here to add a recap for' in page:
- recap = parse_synopsis(page,
- cleanup='Share this article with your'
- ' friends')
- return recap
- except Exception, e:
- print('Episode.recap:urlopen: %s, %s' % (self, e))
- return 'No recap available'
- class Season(dict):
- """represents a season container object"""
- is_current = False
- def episode(self, n):
- """returns the nth episode"""
- return self[n]
- @property
- def premiere(self):
- """returns the season premiere episode"""
- return self[1] # analog to the real world, season is 1-based
- @property
- def finale(self):
- """returns the season finale episode"""
- if not self.is_current:
- return self[len(self.keys())]
- else:
- raise FinaleMayNotBeAnnouncedYet('this is the current season...')
- class Show(object):
- """represents a TV show description from tvrage.com
- this class is kind of a wrapper around the following of tvrage's xml feeds:
- * http://www.tvrage.com/feeds/search.php?show=SHOWNAME
- * http://www.tvrage.com/feeds/episode_list.php?sid=SHOWID
- """
- def __init__(self, name):
- self.shortname = name
- self.episodes = {}
- # the following properties will be populated dynamically
- self.genres = []
- self.showid = ''
- self.name = ''
- self.link = ''
- self.country = ''
- self.status = ''
- self.classification = ''
- self.started = 0
- self.ended = 0
- self.seasons = 0
- show = feeds.search(self.shortname, node='show')
- if not show:
- raise ShowNotFound(name)
- # dynamically mapping the xml tags to properties:
- for elem in show:
- if not elem.tag == 'seasons': # we'll set this later
- # these properties should be ints
- if elem.tag in ('started', 'ended'):
- self.__dict__[elem.tag] = int(elem.text)
- # these are fine as strings
- else:
- self.__dict__[elem.tag] = elem.text
- self.genres = [g.text for g in show.find('genres')]
- # and now grabbing the episodes
- eplist = feeds.episode_list(self.showid, node='Episodelist')
- # populating the episode list
- for season in eplist:
- try:
- snum = int(season.attrib['no'])
- except KeyError:
- pass # TODO: adding handeling for specials and movies
- # bsp: http://www.tvrage.com/feeds/episode_list.php?sid=3519
- else:
- self.episodes[snum] = Season()
- for episode in season:
- epnum = int(episode.find('seasonnum').text)
- self.episodes[snum][epnum] = Episode(
- self.name,
- snum,
- episode.find('airdate').text,
- episode.find('title').text,
- episode.find('link').text,
- epnum,
- episode.find('prodnum').text,
- )
- if snum > 0:
- self.seasons = max(snum, self.seasons)
- self.episodes[self.seasons].is_current = True
- @property
- def pilot(self):
- """returns the pilot/1st episode"""
- return self.episodes[1][1]
- @property
- def current_season(self):
- """returns the season currently running on tv"""
- if not self.ended: # still running
- return self.episodes[self.seasons]
- else:
- raise ShowHasEnded(self.name)
- @property
- def next_episode(self):
- """returns the next upcoming episode"""
- try:
- return self.upcoming_episodes.next()
- except StopIteration:
- raise NoNewEpisodesAnnounced(self.name)
- @property
- def upcoming_episodes(self):
- """returns all upcoming episodes that have been annouced yet"""
- today = date.today()
- for e in self.current_season.values():
- if (e.airdate is not None) and (e.airdate >= today):
- yield e
- @property
- def latest_episode(self):
- """returns the latest episode that has aired already"""
- today = date.today()
- eps = self.season(self.seasons).values()
- eps.reverse()
- for e in eps:
- if (e.airdate is not None) and (e.airdate < today):
- return e
- @property
- def synopsis(self):
- """scraps the synopsis from the show's tvrage page using a regular
- expression. This method might break when the page changes. unfortunatly
- the episode summary isnt available via one of the xml feeds"""
- try:
- page = _fetch(self.link).read()
- synopsis = parse_synopsis(page)
- return synopsis
- except Exception, e:
- print('Show.synopsis:urlopen: %s, %s' % (self, e))
- return 'No Synopsis available'
- def season(self, n):
- """returns the nth season as dict of episodes"""
- return self.episodes[n]