diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 78edcf26..393533a7 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -142,6 +142,7 @@ METADATA_MEDIABROWSER = None METADATA_PS3 = None METADATA_WDTV = None METADATA_TIVO = None +METADATA_MEDE8ER = None QUALITY_DEFAULT = None STATUS_DEFAULT = None @@ -525,7 +526,7 @@ def initialize(consoleLogging=True): USE_LISTVIEW, METADATA_XBMC, METADATA_XBMC_12PLUS, METADATA_MEDIABROWSER, METADATA_PS3, metadata_provider_dict, \ NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, \ GUI_NAME, HOME_LAYOUT, HISTORY_LAYOUT, DISPLAY_SHOW_SPECIALS, COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, COMING_EPS_MISSED_RANGE, DATE_PRESET, TIME_PRESET, TIME_PRESET_W_SECONDS, \ - METADATA_WDTV, METADATA_TIVO, IGNORE_WORDS, CALENDAR_UNPROTECTED, CREATE_MISSING_SHOW_DIRS, \ + METADATA_WDTV, METADATA_TIVO, METADATA_MEDE8ER, IGNORE_WORDS, CALENDAR_UNPROTECTED, CREATE_MISSING_SHOW_DIRS, \ ADD_SHOWS_WO_DIR, USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, subtitlesFinderScheduler, \ USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, TMDB_API_KEY, DEBUG, PROXY_SETTING, NUM_OF_THREADS @@ -987,6 +988,7 @@ def initialize(consoleLogging=True): METADATA_PS3 = check_setting_str(CFG, 'General', 'metadata_ps3', '0|0|0|0|0|0|0|0|0|0') METADATA_WDTV = check_setting_str(CFG, 'General', 'metadata_wdtv', '0|0|0|0|0|0|0|0|0|0') METADATA_TIVO = check_setting_str(CFG, 'General', 'metadata_tivo', '0|0|0|0|0|0|0|0|0|0') + METADATA_MEDE8ER = check_setting_str(CFG, 'General', 'metadata_mede8er', '0|0|0|0|0|0|0|0|0|0') GUI_NAME = check_setting_str(CFG, 'GUI', 'gui_name', 'slick') @@ -1038,6 +1040,7 @@ def initialize(consoleLogging=True): (METADATA_PS3, metadata.ps3), (METADATA_WDTV, metadata.wdtv), (METADATA_TIVO, metadata.tivo), + (METADATA_MEDE8ER, metadata.mede8er), ]: (cur_metadata_config, cur_metadata_class) = cur_metadata_tuple tmp_provider = cur_metadata_class.metadata_class() @@ -1394,6 +1397,7 @@ def save_config(): new_config['General']['metadata_ps3'] = METADATA_PS3 new_config['General']['metadata_wdtv'] = METADATA_WDTV new_config['General']['metadata_tivo'] = METADATA_TIVO + new_config['General']['metadata_mede8er'] = METADATA_MEDE8ER new_config['General']['cache_dir'] = ACTUAL_CACHE_DIR if ACTUAL_CACHE_DIR else 'cache' new_config['General']['root_dirs'] = ROOT_DIRS if ROOT_DIRS else '' diff --git a/sickbeard/config.py b/sickbeard/config.py index c4b5a6e9..4602cf27 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -606,6 +606,7 @@ class ConfigMigrator(): metadata_ps3 = check_setting_str(self.config_obj, 'General', 'metadata_ps3', '0|0|0|0|0|0') metadata_wdtv = check_setting_str(self.config_obj, 'General', 'metadata_wdtv', '0|0|0|0|0|0') metadata_tivo = check_setting_str(self.config_obj, 'General', 'metadata_tivo', '0|0|0|0|0|0') + metadata_mede8er = check_setting_str(self.config_obj, 'General', 'metadata_mede8er', '0|0|0|0|0|0') use_banner = bool(check_setting_int(self.config_obj, 'General', 'use_banner', 0)) @@ -627,6 +628,11 @@ class ConfigMigrator(): metadata = '|'.join(cur_metadata) logger.log(u"Upgrading " + metadata_name + " metadata, new value: " + metadata) + elif len(cur_metadata) == 10: + + metadata = '|'.join(cur_metadata) + logger.log(u"Keeping " + metadata_name + " metadata, value: " + metadata) + else: logger.log(u"Skipping " + metadata_name + " metadata: '" + metadata + "', incorrect format", logger.ERROR) @@ -641,3 +647,4 @@ class ConfigMigrator(): sickbeard.METADATA_PS3 = _migrate_metadata(metadata_ps3, 'PS3', use_banner) sickbeard.METADATA_WDTV = _migrate_metadata(metadata_wdtv, 'WDTV', use_banner) sickbeard.METADATA_TIVO = _migrate_metadata(metadata_tivo, 'TIVO', use_banner) + sickbeard.METADATA_MEDE8ER = _migrate_metadata(metadata_mede8er, 'Mede8er', use_banner) diff --git a/sickbeard/metadata/__init__.py b/sickbeard/metadata/__init__.py index 2a6d405c..26585643 100644 --- a/sickbeard/metadata/__init__.py +++ b/sickbeard/metadata/__init__.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . -__all__ = ['generic', 'helpers', 'xbmc', 'xbmc_12plus', 'mediabrowser', 'ps3', 'wdtv', 'tivo'] +__all__ = ['generic', 'helpers', 'xbmc', 'xbmc_12plus', 'mediabrowser', 'ps3', 'wdtv', 'tivo', 'mede8er'] import sys -import xbmc, xbmc_12plus, mediabrowser, ps3, wdtv, tivo +import xbmc, xbmc_12plus, mediabrowser, ps3, wdtv, tivo, mede8er def available_generators(): diff --git a/sickbeard/metadata/mede8er.py b/sickbeard/metadata/mede8er.py new file mode 100644 index 00000000..d775fdd4 --- /dev/null +++ b/sickbeard/metadata/mede8er.py @@ -0,0 +1,356 @@ +# Author: Nic Wolfe +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard 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 3 of the License, or +# (at your option) any later version. +# +# Sick Beard 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 Sick Beard. If not, see . + +import datetime + +import sickbeard + +import mediabrowser + +from sickbeard import logger, exceptions, helpers +from lib.tvdb_api import tvdb_api, tvdb_exceptions +from sickbeard.exceptions import ex + +try: + import xml.etree.cElementTree as etree +except ImportError: + import elementtree.ElementTree as etree + + +class Mede8erMetadata(mediabrowser.MediaBrowserMetadata): + """ + Metadata generation class for Mede8er based on the MediaBrowser. + + The following file structure is used: + + show_root/series.xml (show metadata) + show_root/folder.jpg (poster) + show_root/fanart.jpg (fanart) + show_root/Season ##/folder.jpg (season thumb) + show_root/Season ##/filename.ext (*) + show_root/Season ##/filename.xml (episode metadata) + show_root/Season ##/filename.jpg (episode thumb) + """ + + def __init__(self, + show_metadata=False, + episode_metadata=False, + fanart=False, + poster=False, + banner=False, + episode_thumbnails=False, + season_posters=False, + season_banners=False, + season_all_poster=False, + season_all_banner=False): + + mediabrowser.MediaBrowserMetadata.__init__(self, + show_metadata, + episode_metadata, + fanart, + poster, + banner, + episode_thumbnails, + season_posters, + season_banners, + season_all_poster, + season_all_banner) + + self.name = "Mede8er" + + self.fanart_name = "fanart.jpg" + + # web-ui metadata template + # self.eg_show_metadata = "series.xml" + self.eg_episode_metadata = "Season##\\filename.xml" + self.eg_fanart = "fanart.jpg" + # self.eg_poster = "folder.jpg" + # self.eg_banner = "banner.jpg" + self.eg_episode_thumbnails = "Season##\\filename.jpg" + # self.eg_season_posters = "Season##\\folder.jpg" + # self.eg_season_banners = "Season##\\banner.jpg" + # self.eg_season_all_poster = "not supported" + # self.eg_season_all_banner = "not supported" + + def get_episode_file_path(self, ep_obj): + return helpers.replaceExtension(ep_obj.location, self._ep_nfo_extension) + + def get_episode_thumb_path(self, ep_obj): + return helpers.replaceExtension(ep_obj.location, 'jpg') + + def _show_data(self, show_obj): + """ + Creates an elementTree XML structure for a MediaBrowser-style series.xml + returns the resulting data object. + + show_obj: a TVShow instance to create the NFO for + """ + + tvdb_lang = show_obj.lang + # There's gotta be a better way of doing this but we don't wanna + # change the language value elsewhere + ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy() + + if tvdb_lang and not tvdb_lang == 'en': + ltvdb_api_parms['language'] = tvdb_lang + + t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms) + + rootNode = etree.Element("details") + tv_node = etree.SubElement(rootNode, "movie") + tv_node.attrib["isExtra"] = "false" + tv_node.attrib["isSet"] = "false" + tv_node.attrib["isTV"] = "true" + + try: + myShow = t[int(show_obj.indexerid)] + except tvdb_exceptions.tvdb_shownotfound: + logger.log(u"Unable to find show with id " + str(show_obj.indexerid) + " on tvdb, skipping it", logger.ERROR) + raise + + except tvdb_exceptions.tvdb_error: + logger.log(u"TVDB is down, can't use its data to make the NFO", logger.ERROR) + raise + + # check for title and id + try: + if myShow['seriesname'] == None or myShow['seriesname'] == "" or myShow['id'] == None or myShow['id'] == "": + logger.log(u"Incomplete info for show with id " + str(show_obj.indexerid) + " on tvdb, skipping it", logger.ERROR) + return False + except tvdb_exceptions.tvdb_attributenotfound: + logger.log(u"Incomplete info for show with id " + str(show_obj.indexerid) + " on tvdb, skipping it", logger.ERROR) + return False + + SeriesName = etree.SubElement(tv_node, "title") + if myShow['seriesname'] != None: + SeriesName.text = myShow['seriesname'] + else: + SeriesName.text = "" + + Genres = etree.SubElement(tv_node, "genres") + if myShow["genre"] != None: + for genre in myShow['genre'].split('|'): + if genre and genre.strip(): + cur_genre = etree.SubElement(Genres, "Genre") + cur_genre.text = genre.strip() + + FirstAired = etree.SubElement(tv_node, "premiered") + if myShow['firstaired'] != None: + FirstAired.text = myShow['firstaired'] + + year = etree.SubElement(tv_node, "year") + if myShow["firstaired"] != None: + try: + year_text = str(datetime.datetime.strptime(myShow["firstaired"], '%Y-%m-%d').year) + if year_text: + year.text = year_text + except: + pass + + if myShow['rating'] != None: + try: + rating = int((float(myShow['rating']) * 10)) + except ValueError: + rating = 0 + Rating = etree.SubElement(tv_node, "rating") + rating_text = str(rating) + if rating_text != None: + Rating.text = rating_text + + Status = etree.SubElement(tv_node, "status") + if myShow['status'] != None: + Status.text = myShow['status'] + + mpaa = etree.SubElement(tv_node, "mpaa") + if myShow["contentrating"] != None: + mpaa.text = myShow["contentrating"] + + IMDB_ID = etree.SubElement(tv_node, "id") + if myShow['imdb_id'] != None: + IMDB_ID.attrib["moviedb"] = "imdb" + IMDB_ID.text = myShow['imdb_id'] + + tvdbid = etree.SubElement(tv_node, "indexerid") + if myShow['id'] != None: + tvdbid.text = myShow['id'] + + Runtime = etree.SubElement(tv_node, "runtime") + if myShow['runtime'] != None: + Runtime.text = myShow['runtime'] + + cast = etree.SubElement(tv_node, "cast") + + if myShow["_actors"] != None: + for actor in myShow['_actors']: + cur_actor_name_text = actor['name'] + + if cur_actor_name_text != None and cur_actor_name_text.strip(): + cur_actor = etree.SubElement(cast, "actor") + cur_actor.text = cur_actor_name_text.strip() + + helpers.indentXML(rootNode) + + data = etree.ElementTree(rootNode) + + return data + + def _ep_data(self, ep_obj): + """ + Creates an elementTree XML structure for a MediaBrowser style episode.xml + and returns the resulting data object. + + show_obj: a TVShow instance to create the NFO for + """ + + eps_to_write = [ep_obj] + ep_obj.relatedEps + + tvdb_lang = ep_obj.show.lang + + try: + # There's gotta be a better way of doing this but we don't wanna + # change the language value elsewhere + ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy() + + if tvdb_lang and not tvdb_lang == 'en': + ltvdb_api_parms['language'] = tvdb_lang + + t = tvdb_api.Tvdb(actors=True, **ltvdb_api_parms) + myShow = t[ep_obj.show.indexerid] + except tvdb_exceptions.tvdb_shownotfound, e: + raise exceptions.ShowNotFoundException(e.message) + except tvdb_exceptions.tvdb_error, e: + logger.log(u"Unable to connect to TVDB while creating meta files - skipping - " + ex(e), logger.ERROR) + return False + + rootNode = etree.Element("details") + movie = etree.SubElement(rootNode, "movie") + + movie.attrib["isExtra"] = "false" + movie.attrib["isSet"] = "false" + movie.attrib["isTV"] = "true" + + # write an MediaBrowser XML containing info for all matching episodes + for curEpToWrite in eps_to_write: + + try: + myEp = myShow[curEpToWrite.season][curEpToWrite.episode] + except (tvdb_exceptions.tvdb_episodenotfound, tvdb_exceptions.tvdb_seasonnotfound): + logger.log(u"Unable to find episode " + str(curEpToWrite.season) + "x" + str(curEpToWrite.episode) + " on tvdb... has it been removed? Should I delete from db?") + return None + + if curEpToWrite == ep_obj: + # root (or single) episode + + # default to today's date for specials if firstaired is not set + if myEp['firstaired'] == None and ep_obj.season == 0: + myEp['firstaired'] = str(datetime.date.fromordinal(1)) + + if myEp['episodename'] == None or myEp['firstaired'] == None: + return None + + episode = movie + + EpisodeName = etree.SubElement(episode, "title") + if curEpToWrite.name != None: + EpisodeName.text = curEpToWrite.name + else: + EpisodeName.text = "" + + SeasonNumber = etree.SubElement(episode, "season") + SeasonNumber.text = str(curEpToWrite.season) + + EpisodeNumber = etree.SubElement(episode, "episode") + EpisodeNumber.text = str(ep_obj.episode) + + year = etree.SubElement(episode, "year") + if myShow["firstaired"] != None: + try: + year_text = str(datetime.datetime.strptime(myShow["firstaired"], '%Y-%m-%d').year) + if year_text: + year.text = year_text + except: + pass + + plot = etree.SubElement(episode, "plot") + if myShow["overview"] != None: + plot.text = myShow["overview"] + + Overview = etree.SubElement(episode, "episodeplot") + if curEpToWrite.description != None: + Overview.text = curEpToWrite.description + else: + Overview.text = "" + + mpaa = etree.SubElement(episode, "mpaa") + if myShow["contentrating"] != None: + mpaa.text = myShow["contentrating"] + + if not ep_obj.relatedEps: + if myEp["rating"] != None: + try: + rating = int((float(myEp['rating']) * 10)) + except ValueError: + rating = 0 + Rating = etree.SubElement(episode, "rating") + rating_text = str(rating) + if rating_text != None: + Rating.text = rating_text + + director = etree.SubElement(episode, "director") + director_text = myEp['director'] + if director_text != None: + director.text = director_text + + credits = etree.SubElement(episode, "credits") + credits_text = myEp['writer'] + if credits_text != None: + credits.text = credits_text + + cast = etree.SubElement(episode, "cast") + + if myShow["_actors"] != None: + for actor in myShow['_actors']: + cur_actor_name_text = actor['name'] + + if cur_actor_name_text != None and cur_actor_name_text.strip(): + cur_actor = etree.SubElement(cast, "actor") + cur_actor.text = cur_actor_name_text.strip() + + else: + # append data from (if any) related episodes + + if curEpToWrite.name: + if not EpisodeName.text: + EpisodeName.text = curEpToWrite.name + else: + EpisodeName.text = EpisodeName.text + ", " + curEpToWrite.name + + if curEpToWrite.description: + if not Overview.text: + Overview.text = curEpToWrite.description + else: + Overview.text = Overview.text + "\r" + curEpToWrite.description + + helpers.indentXML(rootNode) + data = etree.ElementTree(rootNode) + + return data + + +# present a standard "interface" from the module +metadata_class = Mede8erMetadata diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 09b536c5..3762a059 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1137,7 +1137,7 @@ class ConfigPostProcessing: @cherrypy.expose def savePostProcessing(self, naming_pattern=None, naming_multi_ep=None, xbmc_data=None, xbmc_12plus_data=None, mediabrowser_data=None, sony_ps3_data=None, - wdtv_data=None, tivo_data=None, + wdtv_data=None, tivo_data=None, mede8er_data=None, keep_processed_dir=None, process_method=None, process_automatically=None, rename_episodes=None, unpack=None, move_associated_files=None, tv_download_dir=None, naming_custom_abd=None, @@ -1183,6 +1183,7 @@ class ConfigPostProcessing: sickbeard.METADATA_PS3 = sony_ps3_data sickbeard.METADATA_WDTV = wdtv_data sickbeard.METADATA_TIVO = tivo_data + sickbeard.METADATA_MEDE8ER = mede8er_data sickbeard.metadata_provider_dict['XBMC'].set_config(sickbeard.METADATA_XBMC) sickbeard.metadata_provider_dict['XBMC 12+'].set_config(sickbeard.METADATA_XBMC_12PLUS) @@ -1190,6 +1191,7 @@ class ConfigPostProcessing: sickbeard.metadata_provider_dict['Sony PS3'].set_config(sickbeard.METADATA_PS3) sickbeard.metadata_provider_dict['WDTV'].set_config(sickbeard.METADATA_WDTV) sickbeard.metadata_provider_dict['TIVO'].set_config(sickbeard.METADATA_TIVO) + sickbeard.metadata_provider_dict['Mede8er'].set_config(sickbeard.METADATA_MEDE8ER) if self.isNamingValid(naming_pattern, naming_multi_ep) != "invalid": sickbeard.NAMING_PATTERN = naming_pattern