PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/sickbeard/metadata/tivo.py

https://bitbucket.org/fyelles/sick-beard
Python | 320 lines | 191 code | 50 blank | 79 comment | 17 complexity | a32e5e04b87144432f32587ac3936297 MD5 | raw file
  1. # Author: Nic Wolfe <nic@wolfeden.ca>
  2. # Author: Gordon Turner <gordonturner@gordonturner.ca>
  3. # URL: http://code.google.com/p/sickbeard/
  4. #
  5. # This file is part of Sick Beard.
  6. #
  7. # Sick Beard is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Sick Beard is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Sick Beard. If not, see <http://www.gnu.org/licenses/>.
  19. import datetime
  20. import os
  21. import sickbeard
  22. #from sickbeard.common import *
  23. from sickbeard import logger, exceptions, helpers
  24. from sickbeard.metadata import generic
  25. from sickbeard import encodingKludge as ek
  26. from sickbeard import config
  27. from lib.tvdb_api import tvdb_api, tvdb_exceptions
  28. class TIVOMetadata(generic.GenericMetadata):
  29. """
  30. Metadata generation class for TIVO
  31. The following file structure is used:
  32. show_root/Season 01/show - 1x01 - episode.avi.txt (* existing episode)
  33. show_root/Season 01/.meta/show - 1x01 - episode.avi.txt (episode metadata)
  34. This class only generates episode specific metadata files, it does NOT generated a default.txt file.
  35. """
  36. def __init__(self,
  37. show_metadata=False,
  38. episode_metadata=False,
  39. poster=False,
  40. fanart=False,
  41. episode_thumbnails=False,
  42. season_thumbnails=False):
  43. generic.GenericMetadata.__init__(self,
  44. show_metadata,
  45. episode_metadata,
  46. poster,
  47. fanart,
  48. episode_thumbnails,
  49. season_thumbnails)
  50. self._ep_nfo_extension = "txt"
  51. self.generate_ep_metadata = True
  52. self.name = 'TIVO'
  53. self.eg_show_metadata = "<i>not supported</i>"
  54. self.eg_episode_metadata = "Season##\\.meta\\<i>filename</i>.txt"
  55. self.eg_fanart = "<i>not supported</i>"
  56. self.eg_poster = "<i>not supported</i>"
  57. self.eg_episode_thumbnails = "<i>not supported</i>"
  58. self.eg_season_thumbnails = "<i>not supported</i>"
  59. # Override with empty methods for unsupported features.
  60. def create_show_metadata(self, show_obj):
  61. pass
  62. def create_fanart(self, show_obj):
  63. pass
  64. def get_episode_thumb_path(self, ep_obj):
  65. pass
  66. def get_season_thumb_path(self, show_obj, season):
  67. pass
  68. def retrieveShowMetadata(self, dir):
  69. return (None, None)
  70. # Override and implement features for Tivo.
  71. def get_episode_file_path(self, ep_obj):
  72. """
  73. Returns a full show dir/.meta/episode.txt path for Tivo
  74. episode metadata files.
  75. Note, that pyTivo requires the metadata filename to include the original extention.
  76. ie If the episode name is foo.avi, the metadata name is foo.avi.txt
  77. ep_obj: a TVEpisode object to get the path for
  78. """
  79. if ek.ek(os.path.isfile, ep_obj.location):
  80. metadata_file_name = ek.ek(os.path.basename, ep_obj.location) + "." + self._ep_nfo_extension
  81. metadata_dir_name = ek.ek(os.path.join, ek.ek(os.path.dirname, ep_obj.location), '.meta')
  82. metadata_file_path = ek.ek(os.path.join, metadata_dir_name, metadata_file_name)
  83. else:
  84. logger.log(u"Episode location doesn't exist: "+str(ep_obj.location), logger.DEBUG)
  85. return ''
  86. return metadata_file_path
  87. def _ep_data(self, ep_obj):
  88. """
  89. Creates a key value structure for a Tivo episode metadata file and
  90. returns the resulting data object.
  91. ep_obj: a TVEpisode instance to create the metadata file for.
  92. Lookup the show in http://thetvdb.com/ using the python library:
  93. https://github.com/dbr/tvdb_api/
  94. The results are saved in the object myShow.
  95. The key values for the tivo metadata file are from:
  96. http://pytivo.sourceforge.net/wiki/index.php/Metadata
  97. """
  98. data = "";
  99. eps_to_write = [ep_obj] + ep_obj.relatedEps
  100. tvdb_lang = ep_obj.show.lang
  101. try:
  102. # There's gotta be a better way of doing this but we don't wanna
  103. # change the language value elsewhere
  104. ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy()
  105. if tvdb_lang and not tvdb_lang == 'en':
  106. ltvdb_api_parms['language'] = tvdb_lang
  107. t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms)
  108. myShow = t[ep_obj.show.tvdbid]
  109. except tvdb_exceptions.tvdb_shownotfound, e:
  110. raise exceptions.ShowNotFoundException(str(e))
  111. except tvdb_exceptions.tvdb_error, e:
  112. logger.log("Unable to connect to TVDB while creating meta files - skipping - "+str(e), logger.ERROR)
  113. return False
  114. for curEpToWrite in eps_to_write:
  115. try:
  116. myEp = myShow[curEpToWrite.season][curEpToWrite.episode]
  117. except (tvdb_exceptions.tvdb_episodenotfound, tvdb_exceptions.tvdb_seasonnotfound):
  118. logger.log("Unable to find episode " + str(curEpToWrite.season) + "x" + str(curEpToWrite.episode) + " on tvdb... has it been removed? Should I delete from db?")
  119. return None
  120. if myEp["firstaired"] == None and ep_obj.season == 0:
  121. myEp["firstaired"] = str(datetime.date.fromordinal(1))
  122. if myEp["episodename"] == None or myEp["firstaired"] == None:
  123. return None
  124. if myShow["seriesname"] != None:
  125. # Title of the series (The Simpsons, Seinfeld, etc.) or title of the movie (The Mummy, Spiderman, etc).
  126. data += ("title : " + myShow["seriesname"] + "\n")
  127. # Name of series (The Simpsons, Seinfeld, etc.). This should be included if the show is episodic.
  128. # For movies, you may repeat the name of the movie (The Mummy, Spiderman, etc), leave blank, or omit.
  129. data += ("seriesTitle : " + myShow["seriesname"] + "\n")
  130. # Title of the episode (Pilot, Homer's Night Out, Episode 02, etc.) Should be included for episodic shows.
  131. # Leave blank or omit for movies.
  132. #
  133. # Added season episode to title, so that the shows will sort correctly, as often the date information is wrong.
  134. data += ("episodeTitle : " + config.naming_ep_type[sickbeard.NAMING_EP_TYPE] % {'seasonnumber': curEpToWrite.season, 'episodenumber': curEpToWrite.episode} + " " + curEpToWrite.name + "\n")
  135. # This should be entered for episodic shows and omitted for movies. The standard tivo format is to enter
  136. # the season number followed by the episode number for that season. For example, enter 201 for season 2
  137. # episode 01.
  138. # This only shows up if you go into the Details from the Program screen.
  139. # This seems to disappear once the video is transferred to TiVo.
  140. # NOTE: May not be correct format, missing season, but based on description from wiki leaving as is.
  141. data += ("episodeNumber : " + str(curEpToWrite.episode) + "\n")
  142. # Must be entered as true or false. If true, the year from originalAirDate will be shown in parentheses
  143. # after the episode's title and before the description on the Program screen.
  144. # FIXME: Hardcode isEpisode to true for now, not sure how to handle movies
  145. data += ("isEpisode : true\n")
  146. # Write the synopsis of the video here.
  147. # Micrsoft Word's smartquotes can die in a fire.
  148. sanitizedDescription = curEpToWrite.description
  149. # Replace double curly quotes
  150. sanitizedDescription = sanitizedDescription.replace(u"\u201c", "\"").replace(u"\u201d", "\"")
  151. # Replace single curly quotes
  152. sanitizedDescription = sanitizedDescription.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u02BC", "'")
  153. data += ("description : " + sanitizedDescription + "\n")
  154. # Usually starts with "SH" and followed by 6-8 digits.
  155. # Tivo uses zap2it for thier data, so the series id is the zap2it_id.
  156. if myShow["zap2it_id"] != None:
  157. data += ("seriesId : " + myShow["zap2it_id"] + "\n")
  158. # This is the call sign of the channel the episode was recorded from.
  159. if myShow["network"] != None:
  160. data += ("callsign : " + myShow["network"] + "\n")
  161. # This must be entered as yyyy-mm-ddThh:mm:ssZ (the t is capitalized and never changes, the Z is also
  162. # capitalized and never changes). This is the original air date of the episode.
  163. # NOTE: Hard coded the time to T00:00:00Z as we really don't know when during the day the first run happened.
  164. if curEpToWrite.airdate != datetime.date.fromordinal(1):
  165. data += ("originalAirDate : " + str(curEpToWrite.airdate) + "T00:00:00Z\n")
  166. # This shows up at the beginning of the description on the Program screen and on the Details screen.
  167. if myShow["actors"]:
  168. for actor in myShow["actors"].split('|'):
  169. if actor:
  170. data += ("vActor : " + actor + "\n")
  171. # This is shown on both the Program screen and the Details screen. It uses a single digit to determine the
  172. # number of stars: 1 for 1 star, 7 for 4 stars
  173. if myShow["rating"] != None:
  174. try:
  175. rating = float(myShow['rating'])
  176. except ValueError:
  177. rating = 0.0
  178. rating = rating / 10 * 4
  179. data += ("starRating : " + str(rating) + "\n")
  180. # This is shown on both the Program screen and the Details screen.
  181. # It uses the standard TV rating system of: TV-Y7, TV-Y, TV-G, TV-PG, TV-14, TV-MA and TV-NR.
  182. if myShow["contentrating"]:
  183. data += ("tvRating : " + str(myShow["contentrating"]) + "\n")
  184. # This field can be repeated as many times as necessary or omitted completely.
  185. if ep_obj.show.genre:
  186. for genre in ep_obj.show.genre.split('|'):
  187. if genre:
  188. data += ("vProgramGenre : " + str(genre) + "\n")
  189. # NOTE: The following are metadata keywords are not used
  190. # displayMajorNumber
  191. # showingBits
  192. # displayMinorNumber
  193. # colorCode
  194. # vSeriesGenre
  195. # vGuestStar, vDirector, vExecProducer, vProducer, vWriter, vHost, vChoreographer
  196. # partCount
  197. # partIndex
  198. return data
  199. def write_ep_file(self, ep_obj):
  200. """
  201. Generates and writes ep_obj's metadata under the given path with the
  202. given filename root. Uses the episode's name with the extension in
  203. _ep_nfo_extension.
  204. ep_obj: TVEpisode object for which to create the metadata
  205. file_name_path: The file name to use for this metadata. Note that the extension
  206. will be automatically added based on _ep_nfo_extension. This should
  207. include an absolute path.
  208. """
  209. data = self._ep_data(ep_obj)
  210. if not data:
  211. return False
  212. nfo_file_path = self.get_episode_file_path(ep_obj)
  213. nfo_file_dir = ek.ek(os.path.dirname, nfo_file_path)
  214. try:
  215. if not ek.ek(os.path.isdir, nfo_file_dir):
  216. logger.log("Metadata dir didn't exist, creating it at "+nfo_file_dir, logger.DEBUG)
  217. ek.ek(os.makedirs, nfo_file_dir)
  218. helpers.chmodAsParent(nfo_file_dir)
  219. logger.log(u"Writing episode nfo file to "+nfo_file_path)
  220. nfo_file = ek.ek(open, nfo_file_path, 'w')
  221. # Calling encode directly, b/c often descriptions have wonky characters.
  222. nfo_file.write( data.encode( "utf-8" ) )
  223. nfo_file.close()
  224. helpers.chmodAsParent(nfo_file_path)
  225. except IOError, e:
  226. logger.log(u"Unable to write file to "+nfo_file_path+" - are you sure the folder is writable? "+str(e).decode('utf-8'), logger.ERROR)
  227. return False
  228. return True
  229. # present a standard "interface"
  230. metadata_class = TIVOMetadata