plugin.video.piped/main.py
2024-01-16 05:08:13 -05:00

179 lines
6.8 KiB
Python

# Copyright (C) 2023, Roman V. M.
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
"""
Example video plugin that is compatible with Kodi 20.x "Nexus" and above
"""
import os
import sys
from urllib.parse import urlencode, parse_qsl
from lib.piped_api import PipedClient
from datetime import datetime
import xbmcgui
import xbmcplugin
from xbmcaddon import Addon
from xbmcvfs import translatePath
# Get the plugin url in plugin:// notation.
URL = sys.argv[0]
# Get a plugin handle as an integer number.
HANDLE = int(sys.argv[1])
# Get addon base path
ADDON_PATH = translatePath(Addon().getAddonInfo('path'))
ICONS_DIR = os.path.join(ADDON_PATH, 'resources', 'images', 'icons')
FANART_DIR = os.path.join(ADDON_PATH, 'resources', 'images', 'fanart')
# Public domain movies are from https://publicdomainmovie.net
# Here we use a hardcoded list of movies simply for demonstrating purposes
# In a "real life" plugin you will need to get info and links to video files/streams
# from some website or online service.
PIPED = PipedClient(base_api_url=xbmcplugin.getSetting(int(sys.argv[1]), "base_api_url"))
def get_url(**kwargs):
"""
Create a URL for calling the plugin recursively from the given set of keyword arguments.
:param kwargs: "argument=value" pairs
:return: plugin call URL
:rtype: str
"""
return '{}?{}'.format(URL, urlencode(kwargs))
def get_videos():
"""
Get the list of videofiles/streams.
Here you can insert some code that retrieves
the list of video streams in the given section from some site or API.
:return: the list of videos from the search results
:rtype: list
"""
query = xbmcgui.Dialog().input("Search Videos")
return PIPED.get_search_results(query, "videos")['items']
def list_videos():
"""
Create the list of playable videos in the Kodi interface.
"""
# Set plugin category. It is displayed in some skins as the name
# of the current section.
xbmcplugin.setPluginCategory(HANDLE, "Search Results")
# Set plugin content. It allows Kodi to select appropriate views
# for this type of content.
xbmcplugin.setContent(HANDLE, 'movies')
# Get the list of videos in the category.
videos = get_videos()
# Iterate through videos.
for video in videos:
# Create a list item with a text label
list_item = xbmcgui.ListItem(label=video['title'])
# Set graphics (thumbnail, fanart, banner, poster, landscape etc.) for the list item.
# Here we use only poster for simplicity's sake.
# In a real-life plugin you may need to set multiple image types.
list_item.setArt({'poster': video['thumbnail']})
# Set additional info for the list item via InfoTag.
# 'mediatype' is needed for skin to display info for this ListItem correctly.
info_tag = list_item.getVideoInfoTag()
info_tag.setMediaType('movie')
info_tag.setTitle(video['title'])
info_tag.setPlot(video['shortDescription'])
# info_tag.setYear(video['uploadedDate'])
# Set 'IsPlayable' property to 'true'.
# This is mandatory for playable items!
list_item.setProperty('IsPlayable', 'true')
# Create a URL for a plugin recursive call.
# Example: plugin://plugin.video.example/?action=play&video=https%3A%2F%2Fia600702.us.archive.org%2F3%2Fitems%2Firon_mask%2Firon_mask_512kb.mp4
# /watch?v= is 9 characters long, strip it
vidid = video['url'][9:]
url = get_url(action='play', video=vidid)
# Add the list item to a virtual Kodi folder.
# is_folder = False means that this item won't open any sub-list.
is_folder = False
# Add our item to the Kodi virtual folder listing.
xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder)
# Add sort methods for the virtual folder items
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE)
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
# Finish creating a virtual folder.
xbmcplugin.endOfDirectory(HANDLE)
def play_video(path):
"""
Play a video by the provided path.
:param path: Fully-qualified video URL
:type path: str
"""
# Create a playable item with a path to play.
# offscreen=True means that the list item is not meant for displaying,
# only to pass info to the Kodi player
play_item = xbmcgui.ListItem(offscreen=True)
play_item.setPath(path)
# Pass the item to the Kodi player.
xbmcplugin.setResolvedUrl(HANDLE, True, listitem=play_item)
def play_yt_video(vidid):
"""
Play a HLS video stream by the provided video ID.
:param path: Fully-qualified video URL
:type path: str
"""
# Create a playable item with a path to play.
# offscreen=True means that the list item is not meant for displaying,
# only to pass info to the Kodi player
play_item = xbmcgui.ListItem(offscreen=True)
play_item.setPath(PIPED.get_video(vidid).hls)
play_item.setProperty('inputstream', 'inputstream.adaptive')
play_item.setProperty('inputstream.adaptive.manifest_type', 'hls')
# Pass the item to the Kodi player.
xbmcplugin.setResolvedUrl(HANDLE, True, listitem=play_item)
def router(paramstring):
"""
Router function that calls other functions
depending on the provided paramstring
:param paramstring: URL encoded plugin paramstring
:type paramstring: str
"""
# Parse a URL-encoded paramstring to the dictionary of
# {<parameter>: <value>} elements
params = dict(parse_qsl(paramstring))
# Check the parameters passed to the plugin
if not params:
# If the plugin is called from Kodi UI without any parameters,
# display the list of video categories
list_videos()
elif params['action'] == 'play':
# Play a video from a provided URL.
play_yt_video(params['video'])
else:
# If the provided paramstring does not contain a supported action
# we raise an exception. This helps to catch coding errors,
# e.g. typos in action names.
raise ValueError(f'Invalid paramstring: {paramstring}!')
if __name__ == '__main__':
# Call the router function and pass the plugin call parameters to it.
# We use string slicing to trim the leading '?' from the plugin call paramstring
router(sys.argv[2][1:])