PageRenderTime 49ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/resources/lib/tvrage/tvrage/api.py

https://github.com/analogue/mythbox
Python | 233 lines | 170 code | 18 blank | 45 comment | 22 complexity | 0c90e884988a82ad2dadf776f399a523 MD5 | raw file
  1. # Copyright (c) 2009, Christian Kreutzer
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are met:
  6. #
  7. # * Redistributions of source code must retain the above copyright notice,
  8. # this list of conditions, and the following disclaimer.
  9. # * Redistributions in binary form must reproduce the above copyright notice,
  10. # this list of conditions, and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. # * Neither the name of the author of this software nor the name of
  13. # contributors to this software may be used to endorse or promote products
  14. # derived from this software without specific prior written consent.
  15. #
  16. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. # POSSIBILITY OF SUCH DAMAGE.
  27. import feeds
  28. from datetime import date
  29. from time import mktime, strptime
  30. from exceptions import (ShowHasEnded, FinaleMayNotBeAnnouncedYet,
  31. ShowNotFound, NoNewEpisodesAnnounced)
  32. from util import _fetch, parse_synopsis
  33. class Episode(object):
  34. """represents an tv episode description from tvrage.com"""
  35. def __init__(self, show, season, airdate, title, link, number, prodnumber):
  36. self.show = show
  37. self.season = season
  38. try:
  39. self.airdate = date.fromtimestamp(mktime(
  40. strptime(airdate, '%Y-%m-%d')))
  41. except ValueError:
  42. self.airdate = None
  43. self.title = title
  44. self.link = link
  45. self.number = number
  46. self.prodnumber = prodnumber
  47. self.recap_url = link + '/recap'
  48. self.id = link.split('/')[-1]
  49. def __unicode__(self):
  50. return u'%s %sx%02d %s' % (self.show,
  51. self.season, self.number, self.title)
  52. __str__ = __repr__ = __unicode__
  53. @property
  54. def summary(self):
  55. """parses the episode's summary from the episode's tvrage page"""
  56. try:
  57. page = _fetch(self.link).read()
  58. if not 'Click here to add a summary' in page:
  59. summary = parse_synopsis(page, cleanup='var addthis_config')
  60. return summary
  61. except Exception, e:
  62. print('Episode.summary: %s, %s' % (self, e))
  63. return 'No summary available'
  64. @property
  65. def recap(self):
  66. """parses the episode's recap text from the episode's tvrage recap
  67. page"""
  68. try:
  69. page = _fetch(self.recap_url).read()
  70. if not 'Click here to add a recap for' in page:
  71. recap = parse_synopsis(page,
  72. cleanup='Share this article with your'
  73. ' friends')
  74. return recap
  75. except Exception, e:
  76. print('Episode.recap:urlopen: %s, %s' % (self, e))
  77. return 'No recap available'
  78. class Season(dict):
  79. """represents a season container object"""
  80. is_current = False
  81. def episode(self, n):
  82. """returns the nth episode"""
  83. return self[n]
  84. @property
  85. def premiere(self):
  86. """returns the season premiere episode"""
  87. return self[1] # analog to the real world, season is 1-based
  88. @property
  89. def finale(self):
  90. """returns the season finale episode"""
  91. if not self.is_current:
  92. return self[len(self.keys())]
  93. else:
  94. raise FinaleMayNotBeAnnouncedYet('this is the current season...')
  95. class Show(object):
  96. """represents a TV show description from tvrage.com
  97. this class is kind of a wrapper around the following of tvrage's xml feeds:
  98. * http://www.tvrage.com/feeds/search.php?show=SHOWNAME
  99. * http://www.tvrage.com/feeds/episode_list.php?sid=SHOWID
  100. """
  101. def __init__(self, name):
  102. self.shortname = name
  103. self.episodes = {}
  104. # the following properties will be populated dynamically
  105. self.genres = []
  106. self.showid = ''
  107. self.name = ''
  108. self.link = ''
  109. self.country = ''
  110. self.status = ''
  111. self.classification = ''
  112. self.started = 0
  113. self.ended = 0
  114. self.seasons = 0
  115. show = feeds.search(self.shortname, node='show')
  116. if not show:
  117. raise ShowNotFound(name)
  118. # dynamically mapping the xml tags to properties:
  119. for elem in show:
  120. if not elem.tag == 'seasons': # we'll set this later
  121. # these properties should be ints
  122. if elem.tag in ('started', 'ended'):
  123. self.__dict__[elem.tag] = int(elem.text)
  124. # these are fine as strings
  125. else:
  126. self.__dict__[elem.tag] = elem.text
  127. self.genres = [g.text for g in show.find('genres')]
  128. # and now grabbing the episodes
  129. eplist = feeds.episode_list(self.showid, node='Episodelist')
  130. # populating the episode list
  131. for season in eplist:
  132. try:
  133. snum = int(season.attrib['no'])
  134. except KeyError:
  135. pass # TODO: adding handeling for specials and movies
  136. # bsp: http://www.tvrage.com/feeds/episode_list.php?sid=3519
  137. else:
  138. self.episodes[snum] = Season()
  139. for episode in season:
  140. epnum = int(episode.find('seasonnum').text)
  141. self.episodes[snum][epnum] = Episode(
  142. self.name,
  143. snum,
  144. episode.find('airdate').text,
  145. episode.find('title').text,
  146. episode.find('link').text,
  147. epnum,
  148. episode.find('prodnum').text,
  149. )
  150. if snum > 0:
  151. self.seasons = max(snum, self.seasons)
  152. self.episodes[self.seasons].is_current = True
  153. @property
  154. def pilot(self):
  155. """returns the pilot/1st episode"""
  156. return self.episodes[1][1]
  157. @property
  158. def current_season(self):
  159. """returns the season currently running on tv"""
  160. if not self.ended: # still running
  161. return self.episodes[self.seasons]
  162. else:
  163. raise ShowHasEnded(self.name)
  164. @property
  165. def next_episode(self):
  166. """returns the next upcoming episode"""
  167. try:
  168. return self.upcoming_episodes.next()
  169. except StopIteration:
  170. raise NoNewEpisodesAnnounced(self.name)
  171. @property
  172. def upcoming_episodes(self):
  173. """returns all upcoming episodes that have been annouced yet"""
  174. today = date.today()
  175. for e in self.current_season.values():
  176. if (e.airdate is not None) and (e.airdate >= today):
  177. yield e
  178. @property
  179. def latest_episode(self):
  180. """returns the latest episode that has aired already"""
  181. today = date.today()
  182. eps = self.season(self.seasons).values()
  183. eps.reverse()
  184. for e in eps:
  185. if (e.airdate is not None) and (e.airdate < today):
  186. return e
  187. @property
  188. def synopsis(self):
  189. """scraps the synopsis from the show's tvrage page using a regular
  190. expression. This method might break when the page changes. unfortunatly
  191. the episode summary isnt available via one of the xml feeds"""
  192. try:
  193. page = _fetch(self.link).read()
  194. synopsis = parse_synopsis(page)
  195. return synopsis
  196. except Exception, e:
  197. print('Show.synopsis:urlopen: %s, %s' % (self, e))
  198. return 'No Synopsis available'
  199. def season(self, n):
  200. """returns the nth season as dict of episodes"""
  201. return self.episodes[n]