# -*- coding: utf-8 -*-
from __future__ import absolute_import
# Standard Library Imports
from time import strptime, strftime
import logging
import os
import re
# Fix attemp for
import _strptime
# Kodi imports
import xbmcplugin
import xbmcgui
# Package imports
from codequick.route import Route
from codequick.script import Script
from codequick.support import auto_sort, build_path, logger_id, dispatcher, CallbackRef
from codequick.utils import ensure_unicode, ensure_native_str, unicode_type, PY3, bold
from codequick import localized
if PY3:
# noinspection PyUnresolvedReferences, PyCompatibility
from collections.abc import MutableMapping, MutableSequence
else:
# noinspection PyUnresolvedReferences, PyCompatibility
from collections import MutableMapping, MutableSequence
__all__ = ["Listitem"]
# Logger specific to this module
logger = logging.getLogger("%s.listitem" % logger_id)
# Listitem thumbnail locations
local_image = ensure_native_str(os.path.join(Script.get_info("path"), u"resources", u"media", u"{}"))
global_image = ensure_native_str(os.path.join(Script.get_info("path_global"), u"resources", u"media", u"{}"))
# Prefetch fanart/icon for use later
_fanart = Script.get_info("fanart")
fanart = ensure_native_str(_fanart) if os.path.exists(_fanart) else None
icon = ensure_native_str(Script.get_info("icon"))
# Stream type map to ensure proper stream value types
stream_type_map = {"duration": int,
"channels": int,
"aspect": float,
"height": int,
"width": int}
# Listing sort methods & sort mappings.
# Skips infolables that have no sortmethod and type is string. As by default they will be string anyway
# noinspection PyUnresolvedReferences
infolable_map = {"artist": (None, xbmcplugin.SORT_METHOD_ARTIST_IGNORE_THE),
"studio": (ensure_native_str, xbmcplugin.SORT_METHOD_STUDIO_IGNORE_THE),
"title": (ensure_native_str, xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE),
"album": (ensure_native_str, xbmcplugin.SORT_METHOD_ALBUM_IGNORE_THE),
"code": (ensure_native_str, xbmcplugin.SORT_METHOD_PRODUCTIONCODE),
"count": (int, xbmcplugin.SORT_METHOD_PROGRAM_COUNT),
"rating": (float, xbmcplugin.SORT_METHOD_VIDEO_RATING),
"mpaa": (ensure_native_str, xbmcplugin.SORT_METHOD_MPAA_RATING),
"year": (int, xbmcplugin.SORT_METHOD_VIDEO_YEAR),
"listeners": (int, xbmcplugin.SORT_METHOD_LISTENERS),
"tracknumber": (int, xbmcplugin.SORT_METHOD_TRACKNUM),
"episode": (int, xbmcplugin.SORT_METHOD_EPISODE),
"country": (ensure_native_str, xbmcplugin.SORT_METHOD_COUNTRY),
"genre": (None, xbmcplugin.SORT_METHOD_GENRE),
"date": (ensure_native_str, xbmcplugin.SORT_METHOD_DATE),
"size": (int if PY3 else long, xbmcplugin.SORT_METHOD_SIZE),
"sortepisode": (int, None),
"sortseason": (int, None),
"userrating": (int, None),
"discnumber": (int, None),
"playcount": (int, None),
"overlay": (int, None),
"season": (int, None),
"top250": (int, None),
"setid": (int, None),
"dbid": (int, None)}
# Convenient function for adding to autosort set
auto_sort_add = auto_sort.add
# Map quality values to it's related video resolution, used by 'strea.hd'
quality_map = ((768, 576), (1280, 720), (1920, 1080), (3840, 2160)) # SD, 720p, 1080p, 4K
# Re.sub to remove formatting from label strings
strip_formatting = re.compile(r"\[[^\]]+?\]").sub
class Params(MutableMapping):
def __setstate__(self, state):
self.__dict__.update(state)
def __init__(self):
self.__dict__["raw_dict"] = {}
def __setattr__(self, name, value):
self[name] = value
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError("'{0}' object has no attribute '{1}'".format(self.__class__.__name__, name))
def __setitem__(self, key, value):
if isinstance(value, bytes):
self.raw_dict[key] = value.decode("utf8")
else:
self.raw_dict[key] = value
def __getitem__(self, key):
value = self.raw_dict[key]
return value.decode("utf8") if isinstance(value, bytes) else value
def __delitem__(self, key): # type: (str) -> None
del self.raw_dict[key]
def __delattr__(self, name):
try:
del self.raw_dict[name]
except KeyError:
raise AttributeError("'{0}' object has no attribute '{1}'".format(self.__class__.__name__, name))
def __len__(self):
return len(self.raw_dict)
def __iter__(self):
return iter(self.raw_dict)
def __str__(self):
return str(self.raw_dict)
def __repr__(self):
return "%s(%r)" % (self.__class__, self.raw_dict)
def clean(self):
"""Remove any and all None values from the dictionary."""
for key, val in list(self.raw_dict.items()):
if not val:
del self.raw_dict[key]
[docs]class Art(Params):
"""
Dictionary like object, that allows you to add various images. e.g. "thumb", "fanart".
if "thumb", "fanart" or "icon" is not set, then they will be set automaticly based on the add-on's
fanart and icon images if available.
.. note::
The automatic image values can be disabled by setting them to an empty string. e.g. item.art.thumb = "".
Expected art values are.
* thumb
* poster
* banner
* fanart
* clearart
* clearlogo
* landscape
* icon
:example:
>>> item = Listitem()
>>> item.art.icon = "http://www.example.ie/icon.png"
>>> item.art["fanart"] = "http://www.example.ie/fanart.jpg"
>>> item.art.local_thumb("thumbnail.png")
"""
def __setitem__(self, key, value): # type: (str, str) -> None
self.raw_dict[key] = ensure_native_str(value)
[docs] def local_thumb(self, image):
"""
Set the "thumbnail" image to a image file, located in the add-on "resources/media" directory.
:param str image: Filename of the image.
"""
# Here we can't be sure if 'image' only contains ascii characters, so ensure_native_str is needed
self.raw_dict["thumb"] = local_image.format(ensure_native_str(image))
[docs] def global_thumb(self, image):
"""
Set the "thumbnail" image to a image file, located in the codequick "resources/media" directory.
The available global thumbnail images are.
* next.png - Arrow pointing to the right.
* videos.png - Circle with a play button in the middle.
* search.png - An image of a magnifying glass.
* search_new.png - A magnifying glass with plus symbol in the middle.
* playlist.png - Image of three bulleted lines.
* recent.png - Image of a clock.
:param str image: Filename of the image.
"""
# Here we know that 'image' should only contain ascii characters
# So there is no neeed to use ensure_native_str
self.raw_dict["thumb"] = global_image.format(image)
def _close(self, listitem, isfolder): # type: (xbmcgui.ListItem, bool) -> None
if fanart and "fanart" not in self.raw_dict: # pragma: no branch
self.raw_dict["fanart"] = fanart
if "thumb" not in self.raw_dict: # pragma: no branch
self.raw_dict["thumb"] = icon
if "icon" not in self.raw_dict: # pragma: no branch
self.raw_dict["icon"] = "DefaultFolder.png" if isfolder else "DefaultVideo.png"
self.clean() # Remove all None values
listitem.setArt(self.raw_dict)
[docs]class Info(Params):
"""
Dictionary like object, that allow’s you to add listitem "infoLabels".
"InfoLabels" are like metadata for listitems. e.g. "duration", "genre", "size", "rating" and or "plot".
They are also used for sorting purpose's, sort methods will be automatically selected.
Some "infolabels" need to be of a given type e.g. "size" as "long", "rating" as "float".
For the most part, this conversion will be done automatically.
Example of what would happen is.
* "duration" would be converted to ``int`` and "xbmcplugin.SORT_METHOD_VIDEO_RUNTIME"
sort method will be selected.
* "size" would be converted to ``long`` and "xbmcplugin.SORT_METHOD_SIZE"
sort method will be selected.
.. seealso:: The full list of listitem "infoLabels" can be found at:
https://codedocs.xyz/xbmc/xbmc/group__python__xbmcgui__listitem.html#ga0b71166869bda87ad744942888fb5f14
.. note:: Duration infolabel value can be either in "seconds" or as a "hh:mm:ss" string.
:examples:
>>> item = Listitem()
>>> item.info.genre = "Science Fiction"
>>> item.info["size"] = 256816
"""
def __setitem__(self, key, value):
if value is None or value == "":
logger.debug("Ignoring empty infolable: '%s'", key)
return None
# Convert duration into an integer
elif key == "duration":
auto_sort_add(xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
self.raw_dict[key] = self._duration(value)
else:
# The sort method to set and the type that the infolabel should be
type_converter, sort_type = infolable_map.get(key, (None, None))
# Convert value to required type needed for this infolabel
if type_converter:
try:
value = type_converter(value)
except ValueError:
msg = "value of '%s' for infolabel '%s', is not of type '%s'"
raise TypeError(msg % (value, key, type_converter))
else:
self.raw_dict[key] = value
elif isinstance(value, str):
self.raw_dict[key] = value
elif isinstance(value, unicode_type):
# Only executes on python 2
self.raw_dict[key] = value.encode("utf8")
elif isinstance(value, bytes):
# Only executes on python 3
self.raw_dict[key] = value.decode("utf8")
else:
self.raw_dict[key] = value
if sort_type:
# Set the associated sort method for this infolabel
auto_sort_add(sort_type)
[docs] def date(self, date, date_format):
"""
Set the date infolabel.
:param str date: The date for the listitem.
:param str date_format: The format of the date as a strftime directive e.g. "june 27, 2017" => "%B %d, %Y"
.. seealso:: The full list of directives can be found at:
https://docs.python.org/3.6/library/time.html#time.strftime
:example:
>>> item = Listitem()
>>> item.info.date('june 27, 2017', '%B %d, %Y')
"""
converted_date = strptime(ensure_native_str(date), date_format)
self.raw_dict["date"] = strftime("%d.%m.%Y", converted_date) # 27.06.2017
self.raw_dict["aired"] = strftime("%Y-%m-%d", converted_date) # 2017-06-27
self.raw_dict["year"] = strftime("%Y", converted_date) # 2017
auto_sort_add(xbmcplugin.SORT_METHOD_VIDEO_YEAR)
auto_sort_add(xbmcplugin.SORT_METHOD_DATE)
@staticmethod
def _duration(duration):
"""Converts duration from a string of 'hh:mm:ss' into seconds."""
if isinstance(duration, (str, unicode_type)):
duration = duration.replace(";", ":").strip(":")
if ":" in duration:
# Split Time By Marker and Convert to Integer
time_parts = duration.split(":")
time_parts.reverse()
duration = 0
counter = 1
# Multiply Each 'Time Delta' Segment by it's Seconds Equivalent
for part in time_parts:
duration += int(part) * counter
counter *= 60
else:
# Convert to Interger
duration = int(duration)
return duration
def _close(self, listitem, content_type): # type: (xbmcgui.ListItem, str) -> None
raw_dict = self.raw_dict
# Add label as plot if no plot is found
if "plot" not in raw_dict: # pragma: no branch
raw_dict["plot"] = raw_dict["title"]
listitem.setInfo(content_type, raw_dict)
class Property(Params):
def __setitem__(self, key, value): # type: (str, str) -> None
if value:
self.raw_dict[key] = ensure_unicode(value)
else:
logger.debug("Ignoring empty property: '%s'", key)
def _close(self, listitem): # type: (xbmcgui.ListItem) -> None
for key, value in self.raw_dict.items():
listitem.setProperty(key, value)
[docs]class Stream(Params):
"""
Dictionary like object, that allows you to add "stream details". e.g. "video_codec", "audio_codec".
Expected stream values are.
* video_codec - str (h264)
* aspect - float (1.78)
* width - integer (1280)
* height - integer (720)
* channels - integer (2)
* audio_codec - str (AAC)
* audio_language - str (en)
* subtitle_language - str (en)
Type convertion will be done automatically, so manual convertion is not required.
:example:
>>> item = Listitem()
>>> item.stream.video_codec = "h264"
>>> item.stream.audio_codec = "aac"
"""
def __setitem__(self, key, value):
if not value:
logger.debug("Ignoring empty stream detail value for: '%s'", key)
return None
# Ensure that value is of required type
type_converter = stream_type_map.get(key, ensure_native_str)
try:
value = type_converter(value)
except ValueError:
msg = "Value of '%s' for stream info '%s', is not of type '%s'"
raise TypeError(msg % (value, key, type_converter))
else:
self.raw_dict[key] = value
[docs] def hd(self, quality, aspect=None):
"""
Convenient method to set required stream info to show "SD/HD/4K" logos.
The values witch are set are "width", "height" and "aspect".
If no aspect ratio is given, then a ratio of 1.78(16:9) is set when the quality is 720p or greater.
Quality options are.
* 0 = 480p
* 1 = 720p
* 2 = 1080p
* 3 = 4K.
:type quality: int or None
:param quality: Quality of the stream.
:param float aspect: [opt] The "aspect ratio" of the video.
:example:
>>> item = Listitem()
>>> item.stream.hd(2, aspect=1.78) # 1080p
"""
# Skip if value is None(Unknown), useful when passing a variable with unkown value
if quality is None:
return None
# Set video resolution
try:
self.raw_dict["width"], self.raw_dict["height"] = quality_map[quality]
except IndexError:
raise ValueError("quality id must be within range (0 to 3): '{}'".format(quality))
# Set the aspect ratio if one is given
if aspect:
self["aspect"] = aspect
# Or set the aspect ratio to 16:9 for HD content and above
elif self.raw_dict["height"] >= 720:
self.raw_dict["aspect"] = 1.78
def _close(self, listitem): # type: (xbmcgui.ListItem) -> None
video = {}
subtitle = {}
audio = {"channels": 2}
# Populate the above dictionary with the appropriate key/value pairs
for key, value in self.raw_dict.items():
rkey = key.split("_")[-1]
if key in {"video_codec", "aspect", "width", "height", "duration"}:
video[rkey] = value
elif key in {"audio_codec", "audio_language", "channels"}:
audio[rkey] = value
elif key == "subtitle_language":
subtitle[rkey] = value
else:
raise KeyError("unknown stream detail key: '{}'".format(key))
# Now we are ready to send the stream info to kodi
listitem.addStreamInfo("audio", audio)
if video:
listitem.addStreamInfo("video", video)
if subtitle:
listitem.addStreamInfo("subtitle", subtitle)
[docs]class Context(list):
"""
Adds item(s) to the context menu of the listitem.
This is a list containing "tuples" consisting of ("label", "command") pairs.
This class inherits all methods and attributes from the build-in data type :class:`list`.
.. seealso:: The full list of built-in functions can be found at:
http://kodi.wiki/view/List_of_Built_In_Functions
"""
[docs] def related(self, callback, *args, **kwargs):
"""
Convenient method to add a "Related Videos" context menu item.
All this really does is to call "context.container" and sets "label" for you.
:param Callback callback: The function that will be called when menu item is activated.
:param args: [opt] "Positional" arguments that will be passed to the callback.
:param kwargs: [opt] "Keyword" arguments that will be passed to the callback.
"""
# Add '_updatelisting_ = True' to callback params if called from the same callback as is given here
path = callback.path if isinstance(callback, CallbackRef) else callback.route.path
if path == dispatcher.get_route().path:
kwargs["_updatelisting_"] = True
related_videos_text = Script.localize(localized.RELATED_VIDEOS)
kwargs["_title_"] = related_videos_text
self.container(callback, related_videos_text, *args, **kwargs)
[docs] def container(self, callback, label, *args, **kwargs):
"""
Convenient method to add a context menu item that links to a "container".
:param Callback callback: The function that will be called when menu item is activated.
:param label: The label of the context menu item.
:type label: str
:param args: [opt] "Positional" arguments that will be passed to the callback.
:param kwargs: [opt] "Keyword" arguments that will be passed to the callback.
"""
command = "Container.Update(%s)" % build_path(callback, args, kwargs)
self.append((label, command))
[docs] def script(self, callback, label, *args, **kwargs):
"""
Convenient method to add a context menu item that links to a "script".
:param Callback callback: The function that will be called when menu item is activated.
:type label: str or unicode
:param label: The label of the context menu item.
:param args: [opt] "Positional" arguments that will be passed to the callback.
:param kwargs: [opt] "Keyword" arguments that will be passed to the callback.
"""
command = "RunPlugin(%s)" % build_path(callback, args, kwargs)
self.append((label, command))
def _close(self, listitem): # type: (xbmcgui.ListItem) -> None
if self:
listitem.addContextMenuItems(self)
[docs]class Listitem(object):
"""
The “listitem” control is used for the creating "folder" or "video" items within Kodi.
:param str content_type: [opt] Type of content been listed. e.g. "video", "music", "pictures".
"""
def __getstate__(self):
state = self.__dict__.copy()
state["label"] = self.label
del state["listitem"]
return state
def __setstate__(self, state):
label = state.pop("label")
self.__dict__.update(state)
self.listitem = xbmcgui.ListItem()
self.label = label
def __init__(self, content_type="video"):
self._content_type = content_type
self._is_playable = False
self._is_folder = False
self._args = None
self._path = ""
#: The underlining kodi listitem object, for advanced use.
self.listitem = xbmcgui.ListItem()
#: List of paths to subtitle files.
self.subtitles = []
self.info = Info()
"""
Dictionary like object for adding "infoLabels".
See :class:`listing.Info<codequick.listing.Info>` for more details.
"""
self.art = Art()
"""
Dictionary like object for adding "listitem art".
See :class:`listing.Art<codequick.listing.Art>` for more details.
"""
self.stream = Stream()
"""
Dictionary like object for adding "stream details".
See :class:`listing.Stream<codequick.listing.Stream>` for more details.
"""
self.context = Context()
"""
List object for "context menu" items.
See :class:`listing.Context<codequick.listing.Context>` for more details.
"""
self.params = Params()
"""
Dictionary like object for parameters that will be passed to the "callback" function.
:example:
>>> item = Listitem()
>>> item.params['videoid'] = 'kqmdIV_gBfo'
"""
self.property = Property()
"""
Dictionary like object that allows you to add "listitem properties". e.g. "StartOffset".
Some of these are processed internally by Kodi, such as the "StartOffset" property,
which is the offset in seconds at which to start playback of an item. Others may be used
in the skin to add extra information, such as "WatchedCount" for tvshow items.
:examples:
>>> item = Listitem()
>>> item.property['StartOffset'] = '256.4'
"""
@property
def label(self): # type: () -> str
"""
The listitem label property.
:example:
>>> item = Listitem()
>>> item.label = "Video Title"
"""
label = self.listitem.getLabel()
return label.decode("utf8") if isinstance(label, bytes) else label
@label.setter
def label(self, label): # type: (str) -> None
self.listitem.setLabel(label)
unformatted_label = strip_formatting("", label)
self.params["_title_"] = unformatted_label
self.info["title"] = unformatted_label
@property
def path(self):
return self._path
@path.setter
def path(self, value):
# For backwards compatibility
self._path = value
self._is_playable = True
[docs] def set_path(self, path, is_folder=False, is_playable=True):
"""
Set the listitem's path.
The path can be any of the following:
* Any kodi path, e.g. "plugin://" or "script://"
* Directly playable URL or filepath.
.. note::
When specifying a external 'plugin' or 'script' as the path, Kodi will treat it as a playable item.
To override this behavior, you can set the ``is_playable`` and ``is_folder`` parameters.
:param path: A playable URL or plugin/script path.
:param is_folder: Tells kodi if path is a folder (default -> ``False``).
:param is_playable: Tells kodi if path is a playable item (default -> ``True``).
"""
self._path = path
self._is_folder = is_folder
self._is_playable = False if path.startswith("script://") else is_playable
[docs] def set_callback(self, callback, *args, **kwargs):
"""
Set the "callback" function for this listitem.
The "callback" parameter can be any of the following:
* :class:`codequick.Script<codequick.script.Script>` callback.
* :class:`codequick.Route<codequick.route.Route>` callback.
* :class:`codequick.Resolver<codequick.resolver.Resolver>` callback.
* A callback reference object :func:`Script.ref<codequick.script.Script.ref>`.
:param callback: The "callback" function or reference object.
:param args: "Positional" arguments that will be passed to the callback.
:param kwargs: "Keyword" arguments that will be passed to the callback.
"""
if hasattr(callback, "route"):
callback = callback.route
elif not isinstance(callback, CallbackRef):
# We don't have a plugin / http path,
# So we should then have a callback path
if "://" not in callback:
msg = "passing callback path to 'set_callback' is deprecated, " \
"use callback reference 'Route.ref' instead"
logger.warning("DeprecationWarning: " + msg)
callback = dispatcher.get_route(callback)
else:
msg = "passing a playable / plugin path to 'set_callback' is deprecated, use 'set_path' instead"
logger.warning("DeprecationWarning: " + msg)
is_folder = kwargs.pop("is_folder", False)
is_playable = kwargs.pop("is_playable", not is_folder)
self.set_path(callback, is_folder, is_playable)
return
self.params.update(kwargs)
self._is_playable = callback.is_playable
self._is_folder = callback.is_folder
self._path = callback
self._args = args
# noinspection PyProtectedMember
def build(self):
listitem = self.listitem
isfolder = self._is_folder
listitem.setProperty("folder", str(isfolder).lower())
listitem.setProperty("isplayable", str(self._is_playable).lower())
if isinstance(self._path, CallbackRef):
path = build_path(self._path, self._args, self.params.raw_dict)
else:
path = self._path
if not isfolder:
# Add mediatype if not already set
if "mediatype" not in self.info.raw_dict and self._content_type in ("video", "music"): # pragma: no branch
self.info.raw_dict["mediatype"] = self._content_type
# Set the listitem subtitles
if self.subtitles:
self.listitem.setSubtitles(self.subtitles)
# Add Video Specific Context menu items
self.context.append(("$LOCALIZE[13347]", "Action(Queue)"))
self.context.append(("$LOCALIZE[13350]", "ActivateWindow(videoplaylist)"))
# Close video related datasets
self.stream._close(listitem)
# Set label to UNKNOWN if unset
if not self.label: # pragma: no branch
self.label = u"UNKNOWN"
# Close common datasets
listitem.setPath(path)
self.property._close(listitem)
self.context._close(listitem)
self.info._close(listitem, self._content_type)
self.art._close(listitem, isfolder)
# Return a tuple compatible with 'xbmcplugin.addDirectoryItems'
return path, listitem, isfolder
[docs] @classmethod
def from_dict(
cls,
callback,
label,
art=None,
info=None,
stream=None,
context=None,
properties=None,
params=None,
subtitles=None
):
"""
Constructor to create a "listitem".
This method will create and populate a listitem from a set of given values.
:param Callback callback: The "callback" function or playable URL.
:param str label: The listitem's label.
:param dict art: Dictionary of listitem art.
:param dict info: Dictionary of infoLabels.
:param dict stream: Dictionary of stream details.
:param list context: List of "context menu" item(s) containing "tuples" of ("label", "command") pairs.
:param dict properties: Dictionary of listitem properties.
:param dict params: Dictionary of parameters that will be passed to the "callback" function.
:param list subtitles: List of paths to subtitle files.
:return: A listitem object.
:rtype: Listitem
:example:
>>> params = {"url": "http://example.com"}
>>> item = {"label": "Video Title", "art": {"thumb": "http://example.com/image.jpg"}, "params": params}
>>> listitem = Listitem.from_dict(**item)
"""
item = cls()
item.label = label
if isinstance(callback, str) and "://" in callback:
item.set_path(callback)
else:
item.set_callback(callback)
if params: # pragma: no branch
item.params.update(params)
if info: # pragma: no branch
item.info.update(info)
if art: # pragma: no branch
item.art.update(art)
if stream: # pragma: no branch
item.stream.update(stream)
if properties: # pragma: no branch
item.property.update(properties)
if context: # pragma: no branch
item.context.extend(context)
if subtitles: # pragma: no branch
item.subtitles.extend(subtitles)
return item
[docs] @classmethod
def next_page(cls, *args, **kwargs):
"""
Constructor for adding link to "Next Page" of content.
By default the current running "callback" will be called with all of the parameters that are given here.
You can specify which "callback" will be called by setting a keyword only argument called 'callback'.
:param args: "Positional" arguments that will be passed to the callback.
:param kwargs: "Keyword" arguments that will be passed to the callback.
:example:
>>> item = Listitem()
>>> item.next_page(url="http://example.com/videos?page2")
"""
# Current running callback
callback = kwargs.pop("callback") if "callback" in kwargs else dispatcher.get_route().callback
# Add support params to callback params
kwargs["_updatelisting_"] = True if u"_nextpagecount_" in dispatcher.params else False
kwargs["_title_"] = dispatcher.params.get(u"_title_", u"")
kwargs["_nextpagecount_"] = dispatcher.params.get(u"_nextpagecount_", 1) + 1
# Create listitem instance
item = cls()
label = u"%s %i" % (Script.localize(localized.NEXT_PAGE), kwargs["_nextpagecount_"])
item.info["plot"] = Script.localize(localized.NEXT_PAGE_PLOT)
item.label = bold(label)
item.art.global_thumb("next.png")
item.set_callback(callback, *args, **kwargs)
return item
[docs] @classmethod
def recent(cls, callback, *args, **kwargs):
"""
Constructor for adding "Recent Videos" folder.
This is a convenience method that creates the listitem with "name", "thumbnail" and "plot", already preset.
:param Callback callback: The "callback" function.
:param args: "Positional" arguments that will be passed to the callback.
:param kwargs: "Keyword" arguments that will be passed to the callback.
"""
# Create listitem instance
item = cls()
item.label = bold(Script.localize(localized.RECENT_VIDEOS))
item.info["plot"] = Script.localize(localized.RECENT_VIDEOS_PLOT)
item.art.global_thumb("recent.png")
item.set_callback(callback, *args, **kwargs)
return item
[docs] @classmethod
def search(cls, callback, *args, **kwargs):
"""
Constructor to add "saved search" support to add-on.
This will first link to a "sub" folder that lists all saved "search terms". From here,
"search terms" can be created or removed. When a selection is made, the "callback" function
that was given will be executed with all parameters forwarded on. Except with one extra
parameter, ``search_query``, which is the "search term" that was selected.
:param Callback callback: Function that will be called when the "listitem" is activated.
:param args: "Positional" arguments that will be passed to the callback.
:param kwargs: "Keyword" arguments that will be passed to the callback.
"""
if hasattr(callback, "route"):
route = callback.route
elif isinstance(callback, CallbackRef):
route = callback
else:
route = dispatcher.get_route(callback)
kwargs["first_load"] = True
kwargs["_route"] = route.path
item = cls()
item.label = bold(Script.localize(localized.SEARCH))
item.art.global_thumb("search.png")
item.info["plot"] = Script.localize(localized.SEARCH_PLOT)
item.set_callback(Route.ref("/codequick/search:saved_searches"), *args, **kwargs)
return item
[docs] @classmethod
def youtube(cls, content_id, label=None, enable_playlists=True):
"""
Constructor to add a "YouTube channel" to add-on.
This listitem will list all videos from a "YouTube", channel or playlist. All videos will have a
"Related Videos" option via the context menu. If ``content_id`` is a channel ID and ``enable_playlists``
is ``True``, then a link to the "channel playlists" will also be added to the list of videos.
:param str content_id: Channel ID or playlist ID, of video content.
:param str label: [opt] Listitem Label. (default => "All Videos").
:param bool enable_playlists: [opt] Set to ``False`` to disable linking to channel playlists.
(default => ``True``)
:example:
>>> item = Listitem()
>>> item.youtube("UC4QZ_LsYcvcq7qOsOhpAX4A")
"""
# Youtube exists, Creating listitem link
item = cls()
item.label = label if label else bold(Script.localize(localized.ALLVIDEOS))
item.art.global_thumb("videos.png")
item.params["contentid"] = content_id
item.params["enable_playlists"] = False if content_id.startswith("PL") else enable_playlists
item.set_callback(Route.ref("/codequick/youtube:playlist"))
return item
def __repr__(self):
"""Returns representation of the object."""
return "{}('{}')".format(self.__class__.__name__, ensure_native_str(self.label))