mirror of
https://github.com/SickGear/SickGear.git
synced 2024-11-16 01:45:08 +00:00
7a6936823e
Add `spoken_languages` to tmdb API and TVInfoShow object. Add `trailers`, `homepage` to trakt API and TVInfoShow object. Add trakt episode data if returned from api. Add trakt API methods. - get_most_played - get_most_watched - get_most_collected - get_anticipated - get_recommended - get_trending - get_popular - get_recommended_for_account - get_new_shows - get_new_seasons - get_watchlisted_for_account - get_similar - hide_recommended_for_account (to hide/remove recommended shows for account) - unhide_recommended_for_account - list_hidden_recommended_for_account Fix caching tmdb language list over different runtime instances. Add episode_count and fix ti_show in tmdb_api person object. Change set additional properties in get_person trakt_api. Add tmdb API methods and tvinfo_base. - get_recommended_for_show - get_similar --- fix supported language caching improve print output (source name) of tvinfo_api_tests fix tvinfo_api_tests data creation --- Add code so that it runs with all_test use mock today() and now() dates add option to only get new urls mock data try also to make object creation only when needed fix person parser in tmdb_api add search_person test in tvinfo_api_tests restore mocked methods at the end of the tvinfo_api_tests to prevent other tests to fail when called via all_tests switch gzip with better lzma compression for mock files (default lib in py3) move mock files in test unit sub folder --- Fix trakt method `get_recommended`. Fix browse trakt tests in tvinfo_api_tests. Change set episode id in trakt api. --- Add test_browse_endpoints to tvinfo_api_tests. --- Add enforce_type to sg_helpers. Change use enforce str for overviews. Change remove `if PY2` code sections Add support for datetime.time in _make_airtime in tv.py Refactor tvmaze_api show data setter. Change test to not allow None for seriesname. Add additional missing showdata with caller load_data(). Add load_data() to TVInfoShow. Add guestcast, guestcrew to episodes in pytvmaze lib. --- Change make seriesid of TVInfoShow a alias property of id. Add tvinfo tests. Add search tests. Add show, person tests. Change add trakt tests. Change add tmdb search tests. tvmaze_api exclude rating from mapping. Allow None for seriesname. Fix origin_countries in trakt_api search. Fix show_type in tvmaze_api. Fix airtime for episodes in tvmaze_api. --- Change switch to property instead of legacy dict-like use for trakt search results. Change optimize speed of get() function. Fix make BaseTVinfoSeasonnotfound and BaseTVinfoAttributenotfound also a subclass of AttributeError and KeyError. Change mock get() to work with and without default args just like dict get(). Change add language to tmdb_api search results. Change improve person search by remote id, by getting the complete persons data when there is only 1 result. Change trakt API search results to tvinfoshow. Change search results to TVInfoShow objs in tvmaze_api. Change simplify poster URL generation for search results. Change search results to TVInfoShow objs. Change add tvdb genre links to displayShow. Change workaround for missing data in person data (series set to None). Fix add show to characters of person if there is no name on IMDb (set to 'unknown name'). Change add config and icons for linkedin, reddit, wikidata, youtube. Add TVInfoIDs, TVInfoSocialIDs to Trakt. Add TVInfoIDs to tmdb_api. Add TVInfoIDs to tvmaze_api. add TVInfoIDs to imdb_api. Change make character name '' if None. Fix for 'unknown name' persons and characters. Add contentrating. Change fill in new fields to get_person results. ---- Change set new in/active dates to network. Change add active_date, inactive_date to TVInfoNetwork class. Change add default kwargs to tmdb discover method if no kwargs are set. Change default: English language shows with first air date greater then today. Change add slug field to returned data from discover. Change add 'score' mapped to rating to discover returned results. Fix valid_data for discover method. Change add result_count to discover. Change add _sanitise_image_uri to discover method. Fix convert_person. Change add missing _sanitise_image_uri for images in some places. Fix crew. Change return type of tvinfo base: discover to list tvinfoshow. Fix people remote id search. Change add tmdb person id search. Change fix people endpoint fieldname changes. Change add biography to person object. Change move 401 expired token handling into TvdbAuth class. Change get new token if old token is expired. Change add raise error if episodes fallback fails to load data. Change add break if no valid_data to absolute and alternative numberings. Change add filter only networks. Change add new required parameter meta=translations to get translated (includes the original language) show overviews. Change add check if show is set for person compare. Fix person update properties with no show set. Change add person image. Change add alternative episode orders. Change add alt_ep_numbering to TVINFO_Show. Change add old interface for dvd order. Change add trakt slug tvinfo search test cases. Change add mock for old tvdb get new token. Change old lib to newer tvinfo data. Fix person id (not available on old api). Change more places to new TVInfoAPI interface.
594 lines
25 KiB
Python
594 lines
25 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
|
|
import io
|
|
import os
|
|
|
|
from . import generic
|
|
from .. import logger
|
|
import sg_helpers
|
|
from ..indexers.indexer_config import TVINFO_IMDB, TVINFO_TVDB
|
|
from lib.tvinfo_base.exceptions import *
|
|
import sickgear
|
|
import exceptions_helper
|
|
from exceptions_helper import ex
|
|
from lxml_etree import etree
|
|
|
|
from _23 import decode_str
|
|
from six import string_types
|
|
|
|
# noinspection PyUnreachableCode
|
|
if False:
|
|
from typing import AnyStr, Dict, Optional, Union
|
|
|
|
|
|
class KODIMetadata(generic.GenericMetadata):
|
|
"""
|
|
Metadata generation class for Kodi.
|
|
|
|
The following file structure is used:
|
|
|
|
show_root/tvshow.nfo (show metadata)
|
|
show_root/fanart.jpg (fanart)
|
|
show_root/poster.jpg (poster)
|
|
show_root/banner.jpg (banner)
|
|
show_root/Season ##/filename.ext (*)
|
|
show_root/Season ##/filename.nfo (episode metadata)
|
|
show_root/Season ##/filename-thumb.jpg (episode thumb)
|
|
show_root/season##-poster.jpg (season posters)
|
|
show_root/season##-banner.jpg (season banners)
|
|
show_root/season-all-poster.jpg (season all poster)
|
|
show_root/season-all-banner.jpg (season all banner)
|
|
"""
|
|
|
|
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
|
|
):
|
|
|
|
generic.GenericMetadata.__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 = 'Kodi' # type: AnyStr
|
|
|
|
self.poster_name = 'poster.jpg' # type: AnyStr
|
|
self.season_all_poster_name = 'season-all-poster.jpg' # type: AnyStr
|
|
|
|
# web-ui metadata template
|
|
self.eg_show_metadata = 'tvshow.nfo' # type: AnyStr
|
|
self.eg_episode_metadata = 'Season##\\<i>filename</i>.nfo' # type: AnyStr
|
|
self.eg_fanart = 'fanart.jpg' # type: AnyStr
|
|
self.eg_poster = 'poster.jpg' # type: AnyStr
|
|
self.eg_banner = 'banner.jpg' # type: AnyStr
|
|
self.eg_episode_thumbnails = 'Season##\\<i>filename</i>-thumb.jpg' # type: AnyStr
|
|
self.eg_season_posters = 'season##-poster.jpg' # type: AnyStr
|
|
self.eg_season_banners = 'season##-banner.jpg' # type: AnyStr
|
|
self.eg_season_all_poster = 'season-all-poster.jpg' # type: AnyStr
|
|
self.eg_season_all_banner = 'season-all-banner.jpg' # type: AnyStr
|
|
|
|
def _show_data(self, show_obj):
|
|
# type: (sickgear.tv.TVShow) -> Optional[Union[bool, AnyStr]]
|
|
"""
|
|
Creates an elementTree XML structure for a Kodi-style tvshow.nfo and
|
|
returns the resulting data object.
|
|
|
|
show_obj: a TVShow instance to create the NFO for
|
|
"""
|
|
|
|
show_id = show_obj.prodid
|
|
|
|
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)
|
|
|
|
tv_node = etree.Element('tvshow')
|
|
|
|
try:
|
|
show_info = t.get_show(show_id, language=show_obj.lang)
|
|
except BaseTVinfoShownotfound as e:
|
|
logger.error(f'Unable to find show with id {show_id} on {sickgear.TVInfoAPI(show_obj.tvid).name},'
|
|
f' skipping it')
|
|
raise e
|
|
except BaseTVinfoError as e:
|
|
logger.error(f'{sickgear.TVInfoAPI(show_obj.tvid).name} is down, can\'t use its data to add this show')
|
|
raise e
|
|
|
|
if not self._valid_show(show_info, show_obj):
|
|
return
|
|
|
|
# check for title and id
|
|
if None is getattr(show_info, 'seriesname', None) or None is getattr(show_info, 'id', None):
|
|
logger.error(f'Incomplete info for show with id {show_id} on {sickgear.TVInfoAPI(show_obj.tvid).name},'
|
|
f' skipping it')
|
|
return False
|
|
|
|
title = etree.SubElement(tv_node, 'title')
|
|
if None is not getattr(show_info, 'seriesname', None):
|
|
title.text = '%s' % show_info['seriesname']
|
|
|
|
# year = etree.SubElement(tv_node, 'year')
|
|
premiered = etree.SubElement(tv_node, 'premiered')
|
|
premiered_text = self.get_show_year(show_obj, show_info, year_only=False)
|
|
if premiered_text:
|
|
premiered.text = '%s' % premiered_text
|
|
|
|
has_id = False
|
|
tvdb_id = None
|
|
for tvid, slug in map(
|
|
lambda _tvid: (_tvid, sickgear.TVInfoAPI(_tvid).config.get('kodi_slug')),
|
|
list(sickgear.TVInfoAPI().all_sources)):
|
|
mid = slug and show_obj.ids[tvid].get('id')
|
|
if mid:
|
|
has_id = True
|
|
kwargs = dict(type=slug)
|
|
if tvid == show_obj.tvid:
|
|
kwargs.update(dict(default='true'))
|
|
if TVINFO_TVDB == tvid == show_obj.tvid:
|
|
tvdb_id = str(mid)
|
|
uniqueid = etree.SubElement(tv_node, 'uniqueid', **kwargs)
|
|
uniqueid.text = '%s%s' % (('', 'tt')[TVINFO_IMDB == tvid], mid)
|
|
if not has_id:
|
|
logger.error(f'Incomplete info for show with id {show_id} on {sickgear.TVInfoAPI(show_obj.tvid).name},'
|
|
f' skipping it')
|
|
return False
|
|
|
|
ratings = etree.SubElement(tv_node, 'ratings')
|
|
if None is not getattr(show_info, 'rating', None):
|
|
# todo: name dynamic depending on source
|
|
rating = etree.SubElement(ratings, 'rating', name='thetvdb', max='10')
|
|
rating_value = etree.SubElement(rating, 'value')
|
|
rating_value.text = '%s' % show_info['rating']
|
|
if None is not getattr(show_info, 'siteratingcount', None):
|
|
ratings_votes = etree.SubElement(rating, 'votes')
|
|
ratings_votes.text = '%s' % show_info['siteratingcount']
|
|
|
|
plot = etree.SubElement(tv_node, 'plot')
|
|
if None is not getattr(show_info, 'overview', None):
|
|
plot.text = '%s' % show_info['overview']
|
|
|
|
if tvdb_id:
|
|
episodeguide = etree.SubElement(tv_node, 'episodeguide')
|
|
episodeguideurl = etree.SubElement(episodeguide, 'url', post='yes', cache='auth.json')
|
|
episodeguideurl.text = sickgear.TVInfoAPI(TVINFO_TVDB).config['epg_url'].replace('{MID}', tvdb_id)
|
|
|
|
mpaa = etree.SubElement(tv_node, 'mpaa')
|
|
if None is not getattr(show_info, 'contentrating', None):
|
|
mpaa.text = '%s' % show_info['contentrating']
|
|
|
|
genre = etree.SubElement(tv_node, 'genre')
|
|
if None is not getattr(show_info, 'genre', None):
|
|
if isinstance(show_info['genre'], string_types):
|
|
genre.text = ' / '.join([x.strip() for x in show_info['genre'].split('|') if x.strip()])
|
|
|
|
studio = etree.SubElement(tv_node, 'studio')
|
|
if None is not getattr(show_info, 'network', None):
|
|
studio.text = '%s' % show_info['network']
|
|
|
|
self.add_actor_element(show_info, etree, tv_node)
|
|
|
|
# Make it purdy
|
|
sg_helpers.indent_xml(tv_node)
|
|
|
|
# output valid xml
|
|
# data = etree.ElementTree(tv_node)
|
|
# output non valid xml that Kodi accepts
|
|
data = decode_str(etree.tostring(tv_node))
|
|
parts = data.split('episodeguide')
|
|
if 3 == len(parts):
|
|
data = 'episodeguide'.join([parts[0], parts[1].replace('&quot;', '"'), parts[2]])
|
|
|
|
return data
|
|
|
|
def write_show_file(self, show_obj):
|
|
# type: (sickgear.tv.TVShow) -> bool
|
|
"""
|
|
This method overrides and handles _show_data as a string
|
|
instead of an ElementTree.
|
|
"""
|
|
data = self._show_data(show_obj)
|
|
|
|
if not data:
|
|
return False
|
|
|
|
nfo_file_path = self.get_show_file_path(show_obj)
|
|
|
|
logger.debug(f'Writing Kodi metadata file: {nfo_file_path}')
|
|
|
|
data = '<?xml version="1.0" encoding="UTF-8"?>\n%s' % data
|
|
return sg_helpers.write_file(nfo_file_path, data, utf8=True)
|
|
|
|
def write_ep_file(self, ep_obj):
|
|
# type: (sickgear.tv.TVEpisode) -> bool
|
|
"""
|
|
Generates and writes ep_obj's metadata under the given path with the
|
|
given filename root. Uses the episode's name with the extension in
|
|
_ep_nfo_extension.
|
|
|
|
ep_obj: TVEpisode object for which to create the metadata
|
|
|
|
file_name_path: The file name to use for this metadata. Note that the extension
|
|
will be automatically added based on _ep_nfo_extension. This should
|
|
include an absolute path.
|
|
"""
|
|
|
|
data = self._ep_data(ep_obj)
|
|
|
|
if not data:
|
|
return False
|
|
|
|
nfo_file_path = self.get_episode_file_path(ep_obj)
|
|
|
|
logger.debug(f'Writing episode metadata file: {nfo_file_path}')
|
|
|
|
return sg_helpers.write_file(nfo_file_path, data, xmltree=True, xml_header=True, utf8=True)
|
|
|
|
def _ep_data(self, ep_obj):
|
|
# type: (sickgear.tv.TVEpisode) -> Optional[etree.Element]
|
|
"""
|
|
Creates an elementTree XML structure for a Kodi-style episode.nfo and
|
|
returns the resulting data object.
|
|
show_obj: a TVEpisode 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
|
|
|
|
tvinfo_config = sickgear.TVInfoAPI(ep_obj.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 != ep_obj.show_obj.dvdorder:
|
|
tvinfo_config['dvdorder'] = True
|
|
|
|
try:
|
|
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
|
|
|
|
if not self._valid_show(show_info, ep_obj.show_obj):
|
|
return
|
|
|
|
if 1 < len(ep_obj_list_to_write):
|
|
root_node = etree.Element('xbmcmultiepisode')
|
|
else:
|
|
root_node = etree.Element('episodedetails')
|
|
|
|
# write an NFO 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('Unable to find episode %sx%s on %s.. has it been removed? Should I delete from db?' %
|
|
(cur_ep_obj.season, cur_ep_obj.episode, sickgear.TVInfoAPI(ep_obj.show_obj.tvid).name))
|
|
return None
|
|
|
|
if None is getattr(ep_info, 'firstaired', None):
|
|
ep_info['firstaired'] = str(datetime.date.fromordinal(1))
|
|
|
|
if None is getattr(ep_info, 'episodename', None):
|
|
logger.debug('Not generating nfo because the episode has no title')
|
|
return None
|
|
|
|
logger.debug('Creating metadata for episode %sx%s' % (ep_obj.season, ep_obj.episode))
|
|
|
|
if 1 < len(ep_obj_list_to_write):
|
|
ep_node = etree.SubElement(root_node, 'episodedetails')
|
|
else:
|
|
ep_node = root_node
|
|
|
|
title = etree.SubElement(ep_node, 'title')
|
|
if None is not cur_ep_obj.name:
|
|
title.text = '%s' % cur_ep_obj.name
|
|
|
|
showtitle = etree.SubElement(ep_node, 'showtitle')
|
|
if None is not cur_ep_obj.show_obj.name:
|
|
showtitle.text = '%s' % cur_ep_obj.show_obj.name
|
|
|
|
season = etree.SubElement(ep_node, 'season')
|
|
season.text = str(cur_ep_obj.season)
|
|
|
|
episodenum = etree.SubElement(ep_node, 'episode')
|
|
episodenum.text = str(cur_ep_obj.episode)
|
|
|
|
slug = sickgear.TVInfoAPI(cur_ep_obj.indexer).config.get('kodi_slug')
|
|
if slug:
|
|
uniqueid = etree.SubElement(ep_node, 'uniqueid', type=slug, default='true')
|
|
uniqueid.text = str(cur_ep_obj.epid)
|
|
|
|
aired = etree.SubElement(ep_node, 'aired')
|
|
if cur_ep_obj.airdate != datetime.date.fromordinal(1):
|
|
aired.text = str(cur_ep_obj.airdate)
|
|
else:
|
|
aired.text = ''
|
|
|
|
plot = etree.SubElement(ep_node, 'plot')
|
|
if None is not cur_ep_obj.description:
|
|
plot.text = '%s' % cur_ep_obj.description
|
|
|
|
runtime = etree.SubElement(ep_node, 'runtime')
|
|
if 0 != cur_ep_obj.season:
|
|
if None is not getattr(show_info, 'runtime', None):
|
|
runtime.text = '%s' % show_info['runtime']
|
|
|
|
displayseason = etree.SubElement(ep_node, 'displayseason')
|
|
if None is not getattr(ep_info, 'airsbefore_season', None):
|
|
displayseason_text = ep_info['airsbefore_season']
|
|
if None is not displayseason_text:
|
|
displayseason.text = '%s' % displayseason_text
|
|
|
|
displayepisode = etree.SubElement(ep_node, 'displayepisode')
|
|
if None is not getattr(ep_info, 'airsbefore_episode', None):
|
|
displayepisode_text = ep_info['airsbefore_episode']
|
|
if None is not displayepisode_text:
|
|
displayepisode.text = '%s' % displayepisode_text
|
|
|
|
thumb = etree.SubElement(ep_node, 'thumb')
|
|
thumb_text = getattr(ep_info, 'filename', None)
|
|
if None is not thumb_text:
|
|
thumb.text = '%s' % thumb_text
|
|
|
|
watched = etree.SubElement(ep_node, 'watched')
|
|
watched.text = 'false'
|
|
|
|
credits = etree.SubElement(ep_node, 'credits')
|
|
credits_text = getattr(ep_info, 'writer', None)
|
|
if None is not credits_text:
|
|
credits.text = '%s' % credits_text
|
|
|
|
director = etree.SubElement(ep_node, 'director')
|
|
director_text = getattr(ep_info, 'director', None)
|
|
if None is not director_text:
|
|
director.text = '%s' % director_text
|
|
|
|
ratings = etree.SubElement(ep_node, 'ratings')
|
|
if None is not getattr(ep_info, 'rating', None):
|
|
# todo: name dynamic depending on source
|
|
rating = etree.SubElement(ratings, 'rating', name='thetvdb', max='10')
|
|
rating_value = etree.SubElement(rating, 'value')
|
|
rating_value.text = '%s' % ep_info['rating']
|
|
if None is not getattr(show_info, 'siteratingcount', None):
|
|
ratings_votes = etree.SubElement(rating, 'votes')
|
|
ratings_votes.text = '%s' % show_info['siteratingcount']
|
|
|
|
gueststar_text = getattr(ep_info, 'gueststars', None)
|
|
if isinstance(gueststar_text, string_types):
|
|
for actor in [x.strip() for x in gueststar_text.split('|') if x.strip()]:
|
|
cur_actor = etree.SubElement(ep_node, 'actor')
|
|
cur_actor_name = etree.SubElement(cur_actor, 'name')
|
|
cur_actor_name.text = '%s' % actor
|
|
|
|
self.add_actor_element(show_info, etree, ep_node)
|
|
|
|
# Make it purdy
|
|
sg_helpers.indent_xml(root_node)
|
|
|
|
data = etree.ElementTree(root_node)
|
|
|
|
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 = et.SubElement(node, 'actor')
|
|
|
|
cur_actor_name = et.SubElement(cur_actor, 'name')
|
|
cur_actor_name_text = actor['person']['name']
|
|
if cur_actor_name_text:
|
|
cur_actor_name.text = '%s' % cur_actor_name_text
|
|
|
|
cur_actor_role = et.SubElement(cur_actor, 'role')
|
|
cur_actor_role_text = actor['character']['name']
|
|
if cur_actor_role_text:
|
|
cur_actor_role.text = '%s' % cur_actor_role_text
|
|
|
|
cur_actor_thumb = et.SubElement(cur_actor, 'thumb')
|
|
cur_actor_thumb_text = actor['character']['image']
|
|
if None is not cur_actor_thumb_text:
|
|
cur_actor_thumb.text = '%s' % cur_actor_thumb_text
|
|
|
|
|
|
def set_nfo_uid_updated(*args, **kwargs):
|
|
from .. import db
|
|
if not db.DBConnection().has_flag('kodi_nfo_uid'):
|
|
db.DBConnection().set_flag('kodi_nfo_uid')
|
|
sickgear.show_queue_scheduler.action.remove_event(sickgear.show_queue.DAILY_SHOW_UPDATE_FINISHED_EVENT,
|
|
set_nfo_uid_updated)
|
|
|
|
|
|
def remove_default_attr(*args, **kwargs):
|
|
try:
|
|
from .. import db
|
|
msg = 'Updating Kodi Nfo'
|
|
sickgear.classes.loading_msg.set_msg_progress(msg, '0%')
|
|
|
|
kodi = metadata_class()
|
|
num_shows = len(sickgear.showList)
|
|
for n, cur_show_obj in enumerate(sickgear.showList):
|
|
try:
|
|
changed = False
|
|
with cur_show_obj.lock:
|
|
# call for progress with value
|
|
sickgear.classes.loading_msg.set_msg_progress(msg, '{:6.2f}%'.format(float(n)/num_shows * 100))
|
|
|
|
try:
|
|
nfo_path = kodi.get_show_file_path(cur_show_obj)
|
|
except(BaseException, Exception):
|
|
nfo_path = None
|
|
if nfo_path:
|
|
# show
|
|
try:
|
|
if os.path.isfile(nfo_path):
|
|
with io.open(nfo_path, 'r', encoding='utf8') as xml_file_obj:
|
|
xmltree = etree.ElementTree(file=xml_file_obj)
|
|
|
|
# remove default="" attributes
|
|
default = False
|
|
ratings = xmltree.find('ratings')
|
|
r = None is not ratings and ratings.findall('rating') or []
|
|
for element in r:
|
|
if not element.attrib.get('default'):
|
|
changed |= None is not element.attrib.pop('default', None)
|
|
else:
|
|
default = True
|
|
if len(r) and not default:
|
|
ratings.find('rating').attrib['default'] = 'true'
|
|
changed = True
|
|
|
|
# remove default="" attributes
|
|
default = False
|
|
uniques = xmltree.findall('uniqueid')
|
|
for element in uniques:
|
|
if not element.attrib.get('default'):
|
|
changed |= None is not element.attrib.pop('default', None)
|
|
else:
|
|
default = True
|
|
if len(uniques) and not default:
|
|
xmltree.find('uniqueid').attrib['default'] = 'true'
|
|
changed = True
|
|
|
|
# remove redundant duplicate tags
|
|
root = xmltree.getroot()
|
|
for element in xmltree.findall('premiered')[1:]:
|
|
root.remove(element)
|
|
changed = True
|
|
|
|
if changed:
|
|
sg_helpers.indent_xml(root)
|
|
sg_helpers.write_file(nfo_path, xmltree, xmltree=True, xml_header=True, utf8=True)
|
|
except(BaseException, Exception):
|
|
pass
|
|
|
|
# episodes
|
|
episodes = cur_show_obj.get_all_episodes(has_location=True)
|
|
for cur_ep_obj in episodes:
|
|
try:
|
|
changed = False
|
|
nfo_path = kodi.get_episode_file_path(cur_ep_obj)
|
|
if nfo_path and os.path.isfile(nfo_path):
|
|
with io.open(nfo_path, 'r', encoding='utf8') as xml_file_obj:
|
|
xmltree = etree.ElementTree(file=xml_file_obj)
|
|
|
|
# remove default="" attributes
|
|
default = False
|
|
ratings = xmltree.find('ratings')
|
|
r = None is not ratings and ratings.findall('rating') or []
|
|
for element in r:
|
|
if not element.attrib.get('default'):
|
|
changed |= None is not element.attrib.pop('default', None)
|
|
else:
|
|
default = True
|
|
if len(r) and not default:
|
|
ratings.find('rating').attrib['default'] = 'true'
|
|
changed = True
|
|
|
|
if changed:
|
|
sg_helpers.indent_xml(xmltree.getroot())
|
|
sg_helpers.write_file(nfo_path, xmltree, xmltree=True, xml_header=True, utf8=True)
|
|
|
|
except(BaseException, Exception):
|
|
pass
|
|
except(BaseException, Exception):
|
|
pass
|
|
|
|
db.DBConnection().set_flag('kodi_nfo_default_removed')
|
|
sickgear.classes.loading_msg.set_msg_progress(msg, '100%')
|
|
|
|
except(BaseException, Exception):
|
|
pass
|
|
|
|
|
|
def rebuild_nfo(*args, **kwargs):
|
|
"""
|
|
General meta .nfo rebuilder no matter the source of the .nfo
|
|
|
|
case 1, rebuild missing uniqueids and set default="true" to correct uid type
|
|
"""
|
|
try:
|
|
from .. import db
|
|
msg = 'Rebuilding Kodi Nfo'
|
|
sickgear.classes.loading_msg.set_msg_progress(msg, '0%')
|
|
|
|
kodi = metadata_class()
|
|
num_shows = len(sickgear.showList)
|
|
for n, cur_show_obj in enumerate(sickgear.showList):
|
|
try:
|
|
with cur_show_obj.lock:
|
|
# call for progress with value
|
|
sickgear.classes.loading_msg.set_msg_progress(msg, '{:6.2f}%'.format(float(n)/num_shows * 100))
|
|
|
|
try:
|
|
nfo_path = kodi.get_show_file_path(cur_show_obj)
|
|
if nfo_path and os.path.isfile(nfo_path):
|
|
with io.open(nfo_path, 'r', encoding='utf8') as xml_file_obj:
|
|
xmltree = etree.ElementTree(file=xml_file_obj)
|
|
|
|
# check xml keys exist to validate file as type Kodi episode or tvshow .nfo
|
|
if ((('show' in xmltree.getroot().tag) and bool(xmltree.findall('title')))
|
|
or ((bool(xmltree.findall('showtitle')) or bool(xmltree.findall('title')))
|
|
and bool(xmltree.findall('season')))) \
|
|
and sg_helpers.remove_file_perm(nfo_path):
|
|
kodi.write_show_file(cur_show_obj)
|
|
except(BaseException, Exception):
|
|
pass
|
|
|
|
except(BaseException, Exception):
|
|
pass
|
|
|
|
db.DBConnection().set_flag('kodi_nfo_rebuild_uniqueid')
|
|
sickgear.classes.loading_msg.set_msg_progress(msg, '100%')
|
|
|
|
except(BaseException, Exception):
|
|
pass
|
|
|
|
|
|
# present a standard "interface" from the module
|
|
metadata_class = KODIMetadata
|