v17 How to implement search function for my video add-on
#1
Hi folks,

I'm new to Kodi add-on development. I'm working on a simple project that gets videos from a particular website. I want two list items on the first screen of my add-on: "Recently added" and "Search".

I've been following the video add-on tutorial by Roman VM (thanks!) very closely, and was able to implement the first one successfully. But I'm lost as to how I would implement a search function. I tried searching the forums and wasn't able to find any helpful answer that I understood. I've seen and used the search buttons in many other add-ons so I know it's possible.

This what I need to implement. When the user presses "Search", a modal keyboard appears, and the user input is then stored in a variable. I think the router function would have to pause (?) while this process takes place. I know how to take the user input, generate the query and then return the results to the add-on. I just don't know how to get user input in the middle and use that data when loading the next page.

I would really appreciate it if someone could help me with this. Thanks.
Reply
#2
Here is an code example how you can do it.
The Function doModal blocks until the users confirm or aborts.

Code:
keyboard = xbmc.Keyboard()
keyboard.doModal()
if (keyboard.isConfirmed()):
    input = keyboard.getText()
    [...]
Reply
#3
Thanks Rechi! I got my add-on to work just the way I want it.

In case anyone else in the future would like some sample code to follow for a video add-on with a search function and web scraping, I'm attaching a skeleton version of my code. It's a modified version of Roman VM's tutorial code.

Note: Most of my changes are in the get_videos() function. I also created two new helper functions: get_categories(), create_video_list(url).

Hope it helps!

Code:
import sys
from urllib import urlencode
from urllib import quote
from urlparse import parse_qsl
import xbmcgui
import xbmcplugin
import xbmc

# Get the plugin url in plugin:// notation.
_url = sys.argv[0]
# Get the plugin handle as an integer number.
_handle = int(sys.argv[1])

CATEGORIES = ["Recently Added", "Search"]

def get_user_input():  
    kb = xbmc.Keyboard('', 'Please enter the video title')
    kb.doModal() # Onscreen keyboard appears
    if not kb.isConfirmed():
        return
    query = kb.getText() # User input
    return query

def get_url(**kwargs):
    """
    Create a URL for calling the plugin recursively from the given set of keyword arguments.

    :param kwargs: "argument=value" pairs
    :type kwargs: dict
    :return: plugin call URL
    :rtype: str
    """
    return '{0}?{1}'.format(_url, urlencode(kwargs))


def get_categories():
    """
    Get the list of video categories.

    Here you can insert some parsing code that retrieves
    the list of video categories (e.g. 'Movies', 'TV-shows', 'Documentaries' etc.)
    from some site or server.

    .. note:: Consider using `generator functions <https://wiki.python.org/moin/Generators>`_
        instead of returning lists.

    :return: The list of video categories
    :rtype: list
    """
    return CATEGORIES

def create_video_list(url):
        """
        Scrape web page for videofiles/streams and parse information for each video file.
        
        Example of returned list:
        
        videos = [{
                    'name' : 'Foo',
                    'thumb' : 'http://somewebsite.com/video/123/thumb.jpg',
                    'video' : 'http://somewebsite.com/video/123/video.mp4',
                    'genre' : 'Comedy',
                    'Plot' : 'Foo does Bar.',                    
        }]
        
        :param url: Web page url
        :type url: str
        :return: the list of videos found on the page; each video is in the form of a dictionary (see above)
        :rtype: list
        """
        videos = []
        # Write your code here
        return videos
    

def get_videos(category):
    """
    Get the list of videofiles/streams.

    Here you can insert some parsing code that retrieves
    the list of video streams in the given category from some site or server.

    .. note:: Consider using `generators functions <https://wiki.python.org/moin/Generators>`_
        instead of returning lists.

    :param category: Category name
    :type category: str
    :return: the list of videos in the category
    :rtype: list
    """
    if category == "Recently Added":
        url = "http://somewebsite.com/recent/" # Change this to a valid url that you want to scrape
        videos = create_video_list(url)
        return videos
    elif category == "Search":
        query = get_user_input() # User input via onscreen keyboard
        if not query:
            return [] # Return empty list if query is blank
        url = "http://somewebsite.com/results/?query={}".format(quote(query)) # Change this to a valid url for search results that you want to scrape
        videos = create_video_list(url)
        return videos

def list_categories():
    """
    Create the list of video categories in the Kodi interface.
    """
    # Get video categories
    categories = get_categories()
    # Iterate through categories
    for category in categories:
        # Create a list item with a text label and a thumbnail image.
        list_item = xbmcgui.ListItem(label=category)
        # Set graphics (thumbnail, fanart, banner, poster, landscape etc.) for the list item.
        # Here we use the same image for all items for simplicity's sake.
        # In a real-life plugin you need to set each image accordingly.
        
        #list_item.setArt({'thumb': video_thumb, 'icon': video_thumb, 'fanart': video_thumb}) # Uncomment this line and change video_thumb to path of image file you want. This line is not required.
        
        # Set additional info for the list item.
        # Here we use a category name for both properties for for simplicity's sake.
        # setInfo allows to set various information for an item.
        # For available properties see the following link:
        # http://mirrors.xbmc.org/docs/python-docs/15.x-isengard/xbmcgui.html#ListItem-setInfo
        list_item.setInfo('video', {'title': category, 'genre': category})
        # Create a URL for a plugin recursive call.
        # Example: plugin://plugin.video.example/?action=listing&category=Animals
        url = get_url(action='listing', category=category)
        # is_folder = True means that this item opens a sub-list of lower level items.
        is_folder = True
        # Add our item to the Kodi virtual folder listing.
        xbmcplugin.addDirectoryItem(_handle, url, list_item, is_folder)
    # Add a sort method for the virtual folder items (alphabetically, ignore articles)
    xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE)
    # Finish creating a virtual folder.
    xbmcplugin.endOfDirectory(_handle)


def list_videos(category):
    """
    Create the list of playable videos in the Kodi interface.

    :param category: Category name
    :type category: str
    """
    # Get the list of videos in the category.
    videos = get_videos(category)
    # Iterate through videos.
    for video in videos:
        # Create a list item with a text label and a thumbnail image.
        list_item = xbmcgui.ListItem(label=video['name'])
        # Set additional info for the list item.
        list_item.setInfo('video', {'title': video['name'], 'genre': video['genre'], 'plot': video['plot']})
        # Set graphics (thumbnail, fanart, banner, poster, landscape etc.) for the list item.
        # Here we use the same image for all items for simplicity's sake.
        # In a real-life plugin you need to set each image accordingly.
        list_item.setArt({'thumb': video['thumb'], 'icon': video['thumb'], 'fanart': video['thumb']})
        # 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=http://www.vidsplay.com/vids/crab.mp4
        url = get_url(action='play', video=video['video'])
        # 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 a sort method for the virtual folder items (alphabetically, ignore articles)
    xbmcplugin.addSortMethod(_handle, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE)
    # 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.
    play_item = xbmcgui.ListItem(path=path)
    # 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 params:
        if params['action'] == 'listing':
            # Display the list of videos in a provided category.
            list_videos(params['category'])
        elif params['action'] == 'play':
            # Play a video from a provided URL.
            play_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('Invalid paramstring: {0}!'.format(paramstring))
    else:
        # If the plugin is called from Kodi UI without any parameters,
        # display the list of video categories
        list_categories()


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:])
Reply
#4
(2017-04-16, 21:18)jkdev Wrote: Hi folks,

I'm new to Kodi add-on development. I'm working on a simple project that gets videos from a particular website. I want two list items on the first screen of my add-on: "Recently added" and "Search".

I've been following the video add-on tutorial by Roman VM (thanks!) very closely, and was able to implement the first one successfully. But I'm lost as to how I would implement a search function. I tried searching the forums and wasn't able to find any helpful answer that I understood. I've seen and used the search buttons in many other add-ons so I know it's possible.

This what I need to implement. When the user presses "Search", a modal keyboard appears, and the user input is then stored in a variable. I think the router function would have to pause (?) while this process takes place. I know how to take the user input, generate the query and then return the results to the add-on. I just don't know how to get user input in the middle and use that data when loading the next page.

I would really appreciate it if someone could help me with this. Thanks.

Jkdev i am currently in a similar situation , I only need the "Search" option....i would like the the modal keyboard to appear within my addon so that it could search a specific name within the addon and display the results if found.....could you please help me?...Where can i store the needed data to create the search button and in what format? xml etc.
.thank you
Reply
#5
(2017-04-22, 00:42)gamezone69 Wrote: Jkdev i am currently in a similar situation , I only need the "Search" option....i would like the the modal keyboard to appear within my addon so that it could search a specific name within the addon and display the results if found.....could you please help me?...Where can i store the needed data to create the search button and in what format? xml etc.
.thank you

Hi gamezone69,

The modal keyboard does appear within the add-on (as an "overlay") when keyboard.doModal() is called. So that part is already taken care of.

Afterwards, what you do with the query is entirely up to you. In my example, I took the query, inserted it into an url and then passed it to the create_video_list(url) function, which scraped the site and returned a list of results. Instead, we could modify the function to create_video_list(query), where it searches a local file for the user's query. This would depend on how you're storing the data that is to be searched. I can't really recommend a format without knowing what the data consists of, how often it will need to be updated if ever, how large it is, and if you already have something implemented. XML, JSON, and even a plain text file are all possible options.

As a basic example, suppose you have a text file called "master.txt". Each line contains the title and file path of a video, separated by a comma.

Code:
Soccer practice,media/soccer_practice.mp4
Family vacation 2013,media/vacation_2013.mp4

In this case, if you wanted to search by title, create_video_list(query) would be implemented something like this (along with a helper function, search(query, title)):

Code:
MASTER_FILE = 'master.txt'

def create_video_list(query):
        """
        Search local text file, MASTER_FILE, for title of video file.
        
        Example of returned list:
        
        videos = [{
                    'name' : 'Soccer practice',
                    'video' : 'media/soccer_practice.mp4',                
        }]
        
        :param query: Whole or part title of a video
        :type query: str
        :return: the list of videos found; each video is in the form of a dictionary (see above)
        :rtype: list
        """
        videos = []
        
        # Read text file and search each line
        with open(MASTER_FILE, 'r') as f:
            lines = f.readlines()
            for line in lines:
                line = line.rstrip('\n') # Remove newline character at end of each line
                title = line.split(',')[0] # Split line at comma and take the first part as title
                path = line.split(',')[1]
                if search(query, title):
                    # If search returns True
                    entry = {'name' : title, 'video': path}
                    videos.append(entry)
        return videos

def search(query, title):
    """
    This search function can be as complex as desired. We will keep it simple.
    If the query is contained within the title (i.e. substring), return True. Otherwise, return False.
    
    :param query: Whole or part title of a video
    :type query: str
    :param title: Actual title of a video (from MASTER_FILE)
    :type title: str
    :return: True or False
    :rtype: boolean
    """
    
    # If search should be case sensitive, then comment out the following 2 lines.
    query = query.lower()
    title = title.lower()
    
    if query in title:
        return True
    else:
        return False

Let me know if you have any more questions. It would be easier to help if I know more specifics about what you're trying to do.
Reply
#6
Thank you Jkdev for responding. Is there any way that i can contact you via email...so that i can send you more specifics about my project...it is complete...just need the search function , if you can send me a email , i tried sending a pm but for some reason that option is off. My [email protected] you my friend for any help in resolving this issue....I have searched just about everywhere on how to do this but to no avail....
Reply

Logout Mark Read Team Forum Stats Members Help
How to implement search function for my video add-on0