SickGear/sickgear/metadata/mede8er.py
2023-04-27 12:53:49 +01:00

367 lines
14 KiB
Python

#
# This file is part of SickGear.
#
# SickGear 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.
#
# SickGear 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 SickGear. If not, see <http://www.gnu.org/licenses/>.
import datetime
from . import mediabrowser
from .. import logger
import sg_helpers
from lib.tvinfo_base.exceptions import *
import sickgear
import exceptions_helper
from exceptions_helper import ex
from lxml_etree import etree
# noinspection PyUnreachableCode
if False:
from typing import AnyStr, Dict, Optional, Union
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, # type: bool
episode_metadata=False, # type: bool
use_fanart=False, # type: bool
use_poster=False, # type: bool
use_banner=False, # type: bool
episode_thumbnails=False, # type: bool
season_posters=False, # type: bool
season_banners=False, # type: bool
season_all_poster=False, # type: bool
season_all_banner=False # type: bool
):
mediabrowser.MediaBrowserMetadata.__init__(
self,
show_metadata,
episode_metadata,
use_fanart,
use_poster,
use_banner,
episode_thumbnails,
season_posters,
season_banners,
season_all_poster,
season_all_banner)
self.name = 'Mede8er' # type: AnyStr
self.fanart_name = 'fanart.jpg' # type: AnyStr
# web-ui metadata template
# self.eg_show_metadata = 'series.xml'
self.eg_episode_metadata = 'Season##\\<i>filename</i>.xml' # type: AnyStr
self.eg_fanart = 'fanart.jpg' # type: AnyStr
# self.eg_poster = 'folder.jpg'
# self.eg_banner = 'banner.jpg'
self.eg_episode_thumbnails = 'Season##\\<i>filename</i>.jpg' # type: AnyStr
# self.eg_season_posters = 'Season##\\folder.jpg'
# self.eg_season_banners = 'Season##\\banner.jpg'
# self.eg_season_all_poster = '<i>not supported</i>'
# self.eg_season_all_banner = '<i>not supported</i>'
def get_episode_file_path(self, ep_obj):
# type: (sickgear.tv.TVEpisode) -> AnyStr
return sg_helpers.replace_extension(ep_obj.location, self._ep_nfo_extension)
def get_episode_thumb_path(self, ep_obj):
# type: (sickgear.tv.TVEpisode) -> AnyStr
return sg_helpers.replace_extension(ep_obj.location, 'jpg')
def _show_data(self, show_obj):
# type: (sickgear.tv.TVShow) -> Optional[Union[bool, etree.Element]]
"""
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
"""
show_lang = show_obj.lang
tvinfo_config = sickgear.TVInfoAPI(show_obj.tvid).api_params.copy()
tvinfo_config['actors'] = True
if show_lang and not 'en' == show_lang:
tvinfo_config['language'] = show_lang
if 0 != show_obj.dvdorder:
tvinfo_config['dvdorder'] = True
t = sickgear.TVInfoAPI(show_obj.tvid).setup(**tvinfo_config)
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:
show_info = t.get_show(show_obj.prodid, language=show_obj.lang)
except BaseTVinfoShownotfound as e:
logger.error(f'Unable to find show with id {show_obj.prodid} on tvdb, skipping it')
raise e
except BaseTVinfoError as e:
logger.error(f'TVDB is down, can\'t use its data to make the NFO')
raise e
if not self._valid_show(show_info, show_obj):
return
# check for title and id
try:
if None is show_info['seriesname'] \
or '' == show_info['seriesname'] \
or None is show_info['id'] \
or '' == show_info['id']:
logger.error(f'Incomplete info for show with id {show_obj.prodid}'
f' on {sickgear.TVInfoAPI(show_obj.tvid).name}, skipping it')
return False
except BaseTVinfoAttributenotfound:
logger.error(f'Incomplete info for show with id {show_obj.prodid}'
f' on {sickgear.TVInfoAPI(show_obj.tvid).name}, skipping it')
return False
SeriesName = etree.SubElement(tv_node, 'title')
if None is not show_info['seriesname']:
SeriesName.text = '%s' % show_info['seriesname']
else:
SeriesName.text = ''
Genres = etree.SubElement(tv_node, 'genres')
if None is not show_info['genre']:
for genre in show_info['genre'].split('|'):
if genre and genre.strip():
cur_genre = etree.SubElement(Genres, 'Genre')
cur_genre.text = '%s' % genre.strip()
FirstAired = etree.SubElement(tv_node, 'premiered')
if None is not show_info['firstaired']:
FirstAired.text = '%s' % show_info['firstaired']
year = etree.SubElement(tv_node, 'year')
year_text = self.get_show_year(show_obj, show_info)
if year_text:
year.text = '%s' % year_text
if None is not show_info['rating']:
try:
rating = int((float(show_info['rating']) * 10))
except ValueError:
rating = 0
Rating = etree.SubElement(tv_node, 'rating')
rating_text = str(rating)
if None is not rating_text:
Rating.text = '%s' % rating_text
Status = etree.SubElement(tv_node, 'status')
if None is not show_info['status']:
Status.text = '%s' % show_info['status']
mpaa = etree.SubElement(tv_node, 'mpaa')
if None is not show_info['contentrating']:
mpaa.text = '%s' % show_info['contentrating']
IMDB_ID = etree.SubElement(tv_node, 'id')
if None is not show_info['imdb_id']:
IMDB_ID.attrib['moviedb'] = 'imdb'
IMDB_ID.text = '%s' % show_info['imdb_id']
prodid = etree.SubElement(tv_node, 'indexerid')
if None is not show_info['id']:
prodid.text = '%s' % show_info['id']
Runtime = etree.SubElement(tv_node, 'runtime')
if None is not show_info['runtime']:
Runtime.text = '%s' % show_info['runtime']
cast = etree.SubElement(tv_node, 'cast')
self.add_actor_element(show_info, etree, cast)
sg_helpers.indent_xml(rootNode)
data = etree.ElementTree(rootNode)
return data
def _ep_data(self, ep_obj):
# type: (sickgear.tv.TVEpisode) -> Optional[Union[bool, etree.Element]]
"""
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
"""
ep_obj_list_to_write = [ep_obj] + ep_obj.related_ep_obj
show_lang = ep_obj.show_obj.lang
try:
# There's gotta be a better way of doing this but we don't wanna
# change the language value elsewhere
tvinfo_config = sickgear.TVInfoAPI(ep_obj.show_obj.tvid).api_params.copy()
if show_lang and not 'en' == show_lang:
tvinfo_config['language'] = show_lang
if 0 != ep_obj.show_obj.dvdorder:
tvinfo_config['dvdorder'] = True
t = sickgear.TVInfoAPI(ep_obj.show_obj.tvid).setup(**tvinfo_config)
show_info = t.get_show(ep_obj.show_obj.prodid, language=ep_obj.show_obj.lang)
except BaseTVinfoShownotfound as e:
raise exceptions_helper.ShowNotFoundException(ex(e))
except BaseTVinfoError as e:
logger.error(f'Unable to connect to {sickgear.TVInfoAPI(ep_obj.show_obj.tvid).name}'
f' while creating meta files - skipping - {ex(e)}')
return False
if not self._valid_show(show_info, ep_obj.show_obj):
return
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 cur_ep_obj in ep_obj_list_to_write:
try:
ep_info = show_info[cur_ep_obj.season][cur_ep_obj.episode]
except (BaseException, Exception):
logger.log(f'Unable to find episode {cur_ep_obj.season}x{cur_ep_obj.episode} on tvdb...'
f' has it been removed? Should it be deleted from the db?')
return None
if cur_ep_obj == ep_obj:
# root (or single) episode
# default to today's date for specials if firstaired is not set
if None is ep_info['firstaired'] and 0 == ep_obj.season:
ep_info['firstaired'] = str(datetime.date.fromordinal(1))
if None is ep_info['episodename'] or None is ep_info['firstaired']:
return None
episode = movie
EpisodeName = etree.SubElement(episode, 'title')
if None is not cur_ep_obj.name:
EpisodeName.text = '%s' % cur_ep_obj.name
else:
EpisodeName.text = ''
SeasonNumber = etree.SubElement(episode, 'season')
SeasonNumber.text = str(cur_ep_obj.season)
EpisodeNumber = etree.SubElement(episode, 'episode')
EpisodeNumber.text = str(ep_obj.episode)
year = etree.SubElement(episode, 'year')
year_text = self.get_show_year(ep_obj.show_obj, show_info)
if year_text:
year.text = '%s' % year_text
plot = etree.SubElement(episode, 'plot')
if None is not show_info['overview']:
plot.text = '%s' % show_info['overview']
Overview = etree.SubElement(episode, 'episodeplot')
if None is not cur_ep_obj.description:
Overview.text = '%s' % cur_ep_obj.description
else:
Overview.text = ''
mpaa = etree.SubElement(episode, 'mpaa')
if None is not show_info['contentrating']:
mpaa.text = '%s' % show_info['contentrating']
if not ep_obj.related_ep_obj:
if None is not ep_info['rating']:
try:
rating = int((float(ep_info['rating']) * 10))
except ValueError:
rating = 0
Rating = etree.SubElement(episode, 'rating')
rating_text = str(rating)
if None is not rating_text:
Rating.text = '%s' % rating_text
director = etree.SubElement(episode, 'director')
director_text = ep_info['director']
if None is not director_text:
director.text = '%s' % director_text
credits = etree.SubElement(episode, 'credits')
credits_text = ep_info['writer']
if None is not credits_text:
credits.text = '%s' % credits_text
cast = etree.SubElement(episode, 'cast')
self.add_actor_element(show_info, etree, cast)
else:
# append data from (if any) related episodes
if cur_ep_obj.name:
if not EpisodeName.text:
EpisodeName.text = '%s' % cur_ep_obj.name
else:
EpisodeName.text = '%s, %s' % (EpisodeName.text, cur_ep_obj.name)
if cur_ep_obj.description:
if not Overview.text:
Overview.text = '%s' % cur_ep_obj.description
else:
Overview.text = '%s\r%s' % (Overview.text, cur_ep_obj.description)
sg_helpers.indent_xml(rootNode)
data = etree.ElementTree(rootNode)
return data
@staticmethod
def add_actor_element(show_info, et, node):
# type: (Dict, etree, etree.Element) -> None
for actor in getattr(show_info, 'actors', []):
cur_actor_name_text = actor['character']['name'] and actor['person']['name'] \
and actor['character']['name'] != actor['person']['name'] \
and '%s (%s)' % (actor['character']['name'], actor['person']['name']) \
or actor['person']['name'] or actor['character']['name']
if cur_actor_name_text:
cur_actor = et.SubElement(node, 'actor')
cur_actor.text = '%s' % cur_actor_name_text
# present a standard "interface" from the module
metadata_class = Mede8erMetadata