Here we will document the creation of an "add-on". In this instance, “plugin.video.metalvideo”. This will be a simplified version of the full add-on that can be found over at: https://github.com/willforde/plugin.video.metalvideo
First of all, import the required “Codequick” components.
- :class:`Route<codequick.route.Route>` will be used to list folder items.
- :class:`Resolver<codequick.resolver.Resolver>` will be used to resolve video URLs.
- :class:`Listitem<codequick.listing.Listitem>` is used to create "items" within Kodi.
- :mod:`utils<codequick.utils>` is a module, containing some useful functions.
- :func:`run<codequick.run>` is the function that controls the execution of the add-on.
from codequick import Route, Resolver, Listitem, utils, run
Next we will import "urlquick", which is a "light-weight" HTTP client with a "requests" like interface, featuring caching support.
import urlquick
Now, we will use :func:`utils.urljoin_partial<codequick.utils.urljoin_partial>` to create a URL constructor with the "base" URL of the site. This is use to convert relative URLs to absolute URLs. Normally HTML is full of relative URLs and this makes it easier to work with them, guaranteeing that you will always have an absolute URL to use.
# Base url constructor
url_constructor = utils.urljoin_partial("https://metalvideo.com")
Next we will create the "Root" function which will be the starting point for the add-on. It is very important that the "Root" function is called "root". This function will first have to be registered as a "callback" function. Since this is a function that will return "listitems", this will be registered as a :class:`Route<codequick.route.Route>` callback. It is expected that a :class:`Route<codequick.route.Route>` callback should return a "generator" or "list", of :class:`codequick.Listitem<codequick.listing.Listitem>` objects. The first argument that will be passed to a :class:`Route<codequick.route.Route>` callback, will always be the :class:`Route<codequick.route.Route>` instance.
This "callback" will parse the list of “Music Video Categories” available on: http://metalvideo.com, This will return a "generator" of "listitems" linking to a sub-directory of videos within that category. Parsing of the HTML source will be done using "HTMLement" which is integrated into the "urlquick" request response.
.. seealso:: URLQuick: http://urlquick.readthedocs.io/en/stable/ HTMLement: http://python-htmlement.readthedocs.io/en/stable/
@Route.register
def root(plugin):
# Request the online resource
url = url_constructor("/browse.html")
resp = urlquick.get(url)
# Filter source down to required section by giving the name and
# attributes of the element containing the required data.
# It's a lot faster to limit the parser to required section.
root_elem = resp.parse("div", attrs={"id": "primary"})
# Parse each category
for elem in root_elem.iterfind("ul/li"):
item = Listitem()
# The image tag contains both the image url and title
img = elem.find(".//img")
# Set the thumbnail image
item.art["thumb"] = img.get("src")
# Set the title
item.label = img.get("alt")
# Fetch the url
url = elem.find("div/a").get("href")
# This will set the callback that will be called when listitem is activated.
# 'video_list' is the route callback function that we will create later.
# The 'url' argument is the url of the category that will be passed
# to the 'video_list' callback.
item.set_callback(video_list, url=url)
# Return the listitem as a generator.
yield item
Now, we can create the "video parser" callback that will return "playable" listitems. Since this is another function that will return listitems, it will be registered as a :class:`Route<codequick.route.Route>` callback.
@Route.register
def video_list(plugin, url):
# Request the online resource.
url = url_constructor(url)
resp = urlquick.get(url)
# Parse the html source
root_elem = resp.parse("div", attrs={"class": "primary-content"})
# Parse each video
for elem in root_elem.find("ul").iterfind("./li/div"):
item = Listitem()
# Set the thumbnail image of the video.
item.art["thumb"] = elem.find(".//img").get("src")
# Set the duration of the video
item.info["duration"] = elem.find("span/span/span").text.strip()
# Set thel plot info
item.info["plot"] = elem.find("p").text.strip()
# Set view count
views = elem.find("./div/span[@class='pm-video-attr-numbers']/small").text
item.info["count"] = views.split(" ", 1)[0].strip()
# Set the date that the video was published
date = elem.find(".//time[@datetime]").get("datetime")
date = date.split("T", 1)[0]
item.info.date(date, "%Y-%m-%d") # 2018-10-19
# Url to video & name
a_tag = elem.find("h3/a")
url = a_tag.get("href")
item.label = a_tag.text
# Extract the artist name from the title
item.info["artist"] = [a_tag.text.split("-", 1)[0].strip()]
# 'play_video' is the resolver callback function that we will create later.
# The 'url' argument is the url of the video that will be passed
# to the 'play_video' resolver callback.
item.set_callback(play_video, url=url)
# Return the listitem as a generator.
yield item
# Extract the next page url if one exists.
next_tag = root_elem.find("./div[@class='pagination pagination-centered']/ul")
if next_tag is not None:
# Find all page links
next_tag = next_tag.findall("li/a")
# Reverse list of links so the next page link should be the first item
next_tag.reverse()
# Attempt to find the next page link with the text of '>>'
for node in next_tag:
if node.text == u"\xbb":
yield Listitem.next_page(url=node.get("href"), callback=video_list)
# We found the link so we can now break from the loop
break
Finally we need to create the :class:`Resolver<codequick.resolver.Resolver>` "callback", and register it as so. This callback is expected to return a playable video URL. The first argument that will be passed to a :class:`Resolver<codequick.resolver.Resolver>` callback, will always be a :class:`Resolver<codequick.resolver.Resolver>` instance.
@Resolver.register
def play_video(plugin, url):
# Sence https://metalvideo.com uses enbeaded youtube videos,
# we can use 'plugin.extract_source' to extract the video url.
url = url_constructor(url)
return plugin.extract_source(url)
:func:`plugin.extract_source<codequick.resolver.Resolver.extract_source>` uses "YouTube.DL" to extract the video URL. Since it uses YouTube.DL, it will work with way-more than just youtube.
.. seealso:: https://rg3.github.io/youtube-dl/supportedsites.html
So to finish, we need to initiate the "codequick" startup process. This will call the "callback functions" automatically for you.
if __name__ == "__main__":
run()