CastagnaIT/plugin.video.netflix

View on GitHub
resources/lib/common/kodi_wrappers.py

Summary

Maintainability
A
0 mins
Test Coverage
# -*- coding: utf-8 -*-
"""
    Copyright (C) 2017 Sebastian Golasch (plugin.video.netflix)
    Copyright (C) 2021 Stefano Gottardo - @CastagnaIT (original implementation module)
    Wrappers of Kodi methods and objects

    SPDX-License-Identifier: MIT
    See LICENSES/MIT.md for more information.
"""
from typing import Dict, List, Tuple

import xbmc
import xbmcgui

from resources.lib.globals import G

# Convert the deprecated ListItem.setInfo keys to use method names of the new xbmc.InfoTagVideo object
INFO_CONVERT_KEY = {
    'Title': 'setTitle',
    'Year': 'setYear',
    'Plot': 'setPlot',
    'PlotOutline': 'setPlotOutline',
    'Season': 'setSeason',
    'Episode': 'setEpisode',
    'Rating': 'setRating',
    'UserRating': 'setUserRating',
    'Mpaa': 'setMpaa',
    'Duration': 'setDuration',
    'Trailer': 'setTrailer',
    'DateAdded': 'setDateAdded',
    'Director': 'setDirectors',
    'Writer': 'setWriters',
    'Genre': 'setGenres',
    'MediaType': 'setMediaType',
    'TVShowTitle': 'setTvShowTitle',
    'PlayCount': 'setPlaycount'
}

# xbmcgui.ListItem do not support any kind of object serialisation, then transferring directories of ListItem's
# from two python instances (add-on service instance to an add-on client instance) is usually impossible,
# then better simplify the code despite a slight overhead in directory loading.

# pylint: disable=redefined-builtin,invalid-name,no-member
class ListItemW(xbmcgui.ListItem):
    """
    Wrapper for xbmcgui.ListItem to add support for Pickle serialisation and add some helper functions
    ('offscreen' will be True by default)
    """

    def __init__(self, label='', label2='', path=''):
        super().__init__(label, label2, path, True)
        self.__dict__.update({
            'properties': {},
            'infolabels': {},
            'art': {},
            'stream_info': {}
        })

    def __getnewargs__(self):  # Pickle method
        """Passes arguments to __new__ method"""
        return self.getLabel(), self.getLabel2(), self.getPath(), True

    def __setstate__(self, state):  # Pickle method
        """Restore the state of the object data"""
        self.setContentLookup(False)
        if G.IS_OLD_KODI_MODULES:
            super().setInfo('video', state['infolabels'])
            for stream_type, quality_info in state['stream_info'].items():
                super().addStreamInfo(stream_type, quality_info)
        else:
            video_info = super().getVideoInfoTag()
            set_video_info_tag(state['infolabels'], video_info)
            if state['stream_info']:
                video_info.addVideoStream(xbmc.VideoStreamDetail(**state['stream_info']['video']))
                video_info.addAudioStream(xbmc.AudioStreamDetail(**state['stream_info']['audio']))
            # From Kodi 20 "ResumeTime" and "TotalTime" must be set with setResumePoint of InfoTagVideo object
            if 'ResumeTime' in state['properties']:
                resume_time = float(state['properties'].pop('ResumeTime', 0))
                total_time = float(state['properties'].pop('TotalTime', 0))
                video_info.setResumePoint(resume_time, total_time)
        super().setProperties(state['properties'])
        super().setArt(state['art'])
        super().addContextMenuItems(state.get('context_menus', []))
        super().select(state.get('is_selected', False))

    # To improve performances we override the xbmcgui.ListItem methods to store values locally (to self.__dict__)
    # without call the base method, then when the object will be unpickled with __setstate__,
    # the local values will be assigned to the base original ListItem methods.

    def setInfo(self, type: str, infoLabels: Dict[str, str]):
        # NOTE: 'type' argument is ignored because we use only 'video' type, but kept for future changes
        if G.IS_SERVICE:
            self.__dict__['infolabels'] = infoLabels
        else:
            super().setInfo(type, infoLabels)

    def getProperty(self, key: str):
        if G.IS_SERVICE:
            return self.__dict__['properties'].get(key)
        return super().getProperty(key)

    def setProperty(self, key: str, value: str):
        if G.IS_SERVICE:
            self.__dict__['properties'][key] = value
        else:
            super().setProperty(key, value)

    def setProperties(self, dictionary: Dict[str, str]):
        if G.IS_SERVICE:
            self.__dict__['properties'].update(dictionary)
        else:
            super().setProperties(dictionary)

    def getArt(self, key: str):
        if G.IS_SERVICE:
            return self.__dict__['art'].get(key)
        return super().getArt(key)

    def setArt(self, dictionary: Dict[str, str]):
        if G.IS_SERVICE:
            self.__dict__['art'].update(dictionary)
        else:
            super().setArt(dictionary)

    def addStreamInfo(self, cType: str, dictionary: Dict[str, str]):
        if G.IS_SERVICE:
            self.__dict__['stream_info'][cType] = dictionary
        else:
            super().addStreamInfo(cType, dictionary)

    def addContextMenuItems(self, items: List[Tuple[str, str]], replaceItems=False):
        if G.IS_SERVICE:
            self.__dict__['context_menus'] = items
        else:
            super().addContextMenuItems(items, replaceItems)

    def isSelected(self):
        if G.IS_SERVICE:
            return self.__dict__.get('is_selected', False)
        return super().isSelected()

    def select(self, selected: bool):
        if G.IS_SERVICE:
            self.__dict__['is_selected'] = selected
        else:
            super().select(selected)

    # Custom helper methods, for service instance only

    def addStreamInfoFromDict(self, dictionary):
        """
        Add or update all stream info from a dictionary
        [CAN BE USED ON SERVICE INSTANCE ONLY]
        """
        self.__dict__['stream_info'].update(dictionary)

    def updateInfo(self, dictionary):
        """
        Add or update data over the existing data previously added with 'setInfo'
        [CAN BE USED ON SERVICE INSTANCE ONLY]
        """
        self.__dict__['infolabels'].update(dictionary)


def set_video_info_tag(info: Dict[str, str], video_info_tag: xbmc.InfoTagVideo):
    """Convert old info data (for ListItem.setInfo) and use it to set the new methods of InfoTagVideo object"""
    # From Kodi v20 ListItem.setInfo is deprecated, we need to use the methods of InfoTagVideo object
    # "Cast" and "Tag" keys need to be converted
    cast_names = info.pop('Cast', [])
    video_info_tag.setCast([xbmc.Actor(name) for name in cast_names])
    tag_names = info.pop('Tag', [])
    video_info_tag.setTagLine(' / '.join(tag_names))
    for key, value in info.items():
        getattr(video_info_tag, INFO_CONVERT_KEY[key])(value)