Learning to develop a plugin, help needed

So I am trying to create a Plugin that would show unicast streams (iptv) on my local network. Plex can play the streams, I've put then in a *.str files and loaded to Plex as home videos and they work just fine so want to develop further and use a plugin for that, checked the documentation, reviewed some already working plugins and want to start from the basics, so I have the required folder structure, files Info.plist and __init__.py and for the start I want the menu to work, so in __init__.py I created ObjectContainer and added some MediaObject to test:

PREFIX = '/video/example'
TITLE = 'Example Plugin'

def Start():
ObjectContainer.title1 = TITLE

@handler(PREFIX, TITLE)
def MainMenu():
oc = ObjectContainer()
oc.add(MediaObject(title = ‘LAN stream’, url = ‘http://192.168.1.1:4000/udp/233.136.41.12:1234’))
oc.add(MediaObject(title = ‘WAN stream’, url = ‘http://84.240.5.215:4000/udp/233.136.41.12:1234’))
return oc

and when opening channel in Plex Server I get error "The media server responded with an error" however there are no errors in com.plexapp.plugins.example.log file, please help.

I think you need some more code in your Start() method.  This is what I have in mine:

VIDEO_PREFIX = "/video/hockey"

NAME = “Hockey”

ART = ‘art-default.png’
ICON = ‘icon-default.png’

def Start():
# Initialize the plugin
Plugin.AddPrefixHandler(VIDEO_PREFIX, MainMenu, NAME, ICON, ART)
Plugin.AddViewGroup(“LIST”, viewMode = “List”, mediaType = “items”)

ObjectContainer.title1 = NAME

MainMenu is the call back that indicates where your plugin should start; yours is named the same.

I'm not sure if the AddViewGroup call is necessary or just there for visuals.

You do not need additional code in your Start Menu, you have the Prefix handler already. And the StartMenu in general is optional and not required. See the __init__.py template here for a little more explanation of setting in your StartMenu. http://forums.plexapp.com/index.php/topic/62610-channel-development-templates/?p=404012

Probably the reason you are not seeing any errors in the channel log is because you are trying to call a Media Object directly from that Main Menu, so the errors are related to the MediaObject and would show up in the system log (com.plexapp.system.log). Most likely the error is in the "oc.add" section of your code in the MainMenu.

As to why this is a specific issue causing errors, I usually use a URL service, so I usually do not call a MediaObject directly in my channel code, so one of the more experienced developers could probably be more helpful with specifics on the syntax for this.

In looking at your code a little more, I would think your first error will be because title and url are not listed as attributes for a MediaObject in the Framework.

I always understood it that a channel returns a metadata object and if you are not using a URL service to process the url in that metadata object, then that metadata object may contain one or more media objects. So you would need to specify that this is for example a VideoClipObject first and then the MediaObject would be listed as an item within that VideoClipObject that gives the url as the partObject key and include specifics of what type of media it was. 

So I would think that each oc.add in your MainMenu function should at least contain the code below based on the specifics of your media that you want to include like container, audio codec, etc .

Some one with more experience with adding media directly to the channel code may be able to give you more exact instructions on doing this. There may may also be some additional code you would need to add to prevent errors that could occur from automatically asking Plex to access media as soon as you pull up the MainMenu function.

    oc.add(VideoClipObject(
        title = title, 
        rating_key = url, 
        items = [
            MediaObject(
                parts = [
                    PartObject(key=url)
                ],
            )
        ]
    )

Thank You shopgirl284, you are right, Plex client doesn't want to play directly, and after some browsing I Found an example and now my code looks like this:

import re

PREFIX = ‘/video/example’
TITLE = ‘Demo plugin’

def Start():
ObjectContainer.title1 = TITLE

@handler(PREFIX, TITLE)
def MainMenu():
oc = ObjectContainer()
url = ‘http://192.168.1.1:4000/udp/233.136.41.12:1234
title = ‘Demo stream’
oc.add(VideoClipObject(
key = Callback(Lookup, url = url, title = title),
rating_key = url,
title = title,
items = [
MediaObject(
parts = [PartObject(key = url)]
)
]
))
return oc

@route(PREFIX + ‘/lookup’)
def Lookup(url, title):
oc = ObjectContainer()
oc.add(VideoClipObject(
key = Callback(Lookup, url = url, title = title),
rating_key = url,
title = title,
items = [
MediaObject(
parts = [PartObject(key = url)]
)
]
))
return oc

It works, but there are are some things I do not understand:

  1. whats is "import re"? it's "regular expression";
  2. why is there some kind of a recursion and double code, functions mainmenu and lookup are almost the same, but whatever I do I can not get rid of one of them, why do I need both of them?
  3. how to add multiple streams without doing more copies of same code?

Any help is appreciated.

I think I can answer 3 question myself, I need to put all the streams urls in a file, and then parse it somehow line by line into the plugin in a for loop or something similar, so a simple question, how? I can find documentation for XML, HTML, JSON, YAML and RSS, but what about plain text file manipulations, to be more precise M3U playlist, anyone knows of any good examples?

Looks like i can put the m3u file into resources folder and access it via Resource.Load(itemname, binary=True) command and save it into a string variable, then I somehow need to proccess it, for that I guess I should learn some python, an one more question, which version does Plex uses, because the syntax is different in v. 2 anr 3.

Regex is a not so fun method of predetermining the data you want to find usually when you do a search of data like the contents of a web page. You use Regex to tell the search what to look for so you can find specific pieces of data. For for example '/(\d{5,6})\.html' would find a 5 or 6 digit number that that is followed by '.html'  Here is a good page that explains the basics of it http://www.regular-expressions.info/reference.html.

Regex is not fun or easy. It is something I am just now getting comfortable with the basics of, but it is necessary in Plex channel programming.

As far as which version of Python, it used to vary based on the Plex OS used. I was just told that the latest version of PMS bundles it's own copy of python 2.7. Prior to that it was anywhere from 2.5 (OSX) up to 2.7 (linux).

The code above is not really double coding,  the MainMenu() function just pulls up the sections as a directory to show the title and info related to the videos and uses a callback to the second menu and the second menu actually processes the videos and allows them to play it. As I had said in an earlier post, it is probably not best to bring up media directly in your main menu.  You would be asking Plex to do a lot of things all at once as soon as you open up the channel plugin, if you did not add the extra step of sending the video request to the Lookup() funtion since it would have to process any code necessary for starting the channel like the plist and process the videos all as soon as you open the channel otherwise.  Especially because you are not using any type of redirect or indirect method that would tell the channel to wait until the user clicks play to process everything related to the video request.

To process data that may be in an html, xml, json, or other formatted page, you would use the Plex parsing API and most likely use that parsing API along with xpath. There are some good basic tutorials on this pinned at the beginning of this forum. The parsing API options are listed in the Framework Documentation available here http://dev.plexapp.com/

It all depends on your particular project, if you only have a couple hard coded direct links to video files that you are accessing that will only be access by you in this personal channel you may not need a separate URL service. 

But I tend to think that with most projects, it is best to use a separate URL service. Anytime you are able to specify the pattern of the URL being used by the channel to access media files, so it can be directed properly to a separate URL service, I think that separating that code with a separate URL service helps simplify the project. And most examples of channels you find use a separate URL service to process the media files and it also allows the service to provide info for programs like plexit and additional data needed for certain clients. 

With a separate URL service, you can focus the code of your channel to just display the video info like the metadata and a URL for accessing the actual videos. And then that channel would send that URL on to the URL service which can focus on locating and processing the media files based on the formats, qualities, etc. that may be available for you to use. Also, trying to pull data from a site or URL within the same function that processes your media requires extra care to ensure it does not cause delays that will cause Plex to timeout before it returns the video. Even a very basic URL service that just defines the MediaObjects and has a very basic side function to determine the address of the video files can greatly simplify the code needed in your channel.

If you are trying to access video playlist, if those files contain metadata like title, description, etc that would be processed in your channel code. But if that video playlist contains data to help you locate of the actual video files associated with a channel and the different formats available, then that could be processed in a separate URL service.

One big advantage of a separate URL service is that since a URL service allows you to separate the code needed for the metadata and the actual media files, it also allows you to fully test and debug those portions of code independently, so you can know that access the the media is possible and working, prior to building the channel with all the supporting data you want to pull when accessing that media.

If you do not to create a separate URL service, here is an example of a channel that includes a function to process media. https://github.com/shopgirl284/RSSFeed.bundle This example channel handles RSS feeds that either have an existing Plex URL service or include a direct link to the media files within the XML page submitted to it. The only reason I did not create a separate URL service for this example channel is because it would be impossible to determine the pattern of the URL that would be sent to that URL service, since the user can input a wide variety of URL addresses. And that lack of a URL pattern would make Plex unable to determine which URLs to send to a separate URL service for this channel.

Also, if you are planning on actually building a data file to contain specific info, the above example shows code for accessing a JSON file that is transferred to a local dictionary file.

So long story short, how do I divide may code into separate service?

Another moment, as i mentioned before, I coudn't find API for m3u parser, so I kind of wrote it myself, keep in mind that is like 3rd day of me using Python:

PREFIX = '/video/example4'
TITLE = 'IPTV'
itemslist = []

def Start():
ObjectContainer.title1 = TITLE

@handler(PREFIX, TITLE)
def MainMenu():
#itemslist = []
groupslist = []
playlist = Resource.Load(‘playlist.m3u’, binary = True)
if playlist <> None:
lines = playlist.splitlines()
for i in range(len(lines) - 1):
line = lines*.strip()
if line.startswith(’#EXTINF’):
url = lines[i + 1].strip()
title = line[line.rfind(’,’) + 1:len(line)].strip()
thumb = GetParm(line, ‘tvg-logo’)
group_title = GetParm(line, ‘group-title’)
itemslist.append([url, title, thumb, group_title])
if group_title <> ‘’ and not group_title in groupslist:
groupslist.append(group_title)
itemslist.sort(key = lambda tulp: tulp[1].lower())
groupslist.sort(key = lambda str: str.lower())
groupslist.insert(0, ‘All’)
groupslist.append(‘Uncategorized’)

if len(itemslist) < 1:
    return ObjectContainer(header="No items found", message="Make sure file playlist.m3u is present and not empty in plugins Resources folder")

oc = ObjectContainer()

for group in groupslist:
    oc.add(DirectoryObject(
        key = Callback(ItemsMenu, group_title = group),#, itemslist = itemslist),
        title = group
    ))

return oc

@route(PREFIX + ‘/itemsmenu’)
def ItemsMenu(group_title):#, itemslist):
oc = ObjectContainer(title1 = group_title)
for item in itemslist:
if item[3] == group_title or group_title == ‘All’ or (item[3] == ‘’ and group_title == ‘Uncategorized’):
url = item[0]
title = item[1]
thumb = item[2]
oc.add(VideoClipObject(
key = Callback(Lookup, url = url, title = title, thumb = thumb),
rating_key = url,
title = title,
thumb = thumb,
items = [
MediaObject(
parts = [PartObject(key = url)]
)
]
))
return oc

@route(PREFIX + ‘/lookup’)
def Lookup(url, title, thumb):
oc = ObjectContainer()
oc.add(VideoClipObject(
key = Callback(Lookup, url = url, title = title, thumb = thumb),
rating_key = url,
title = title,
thumb = thumb,
items = [
MediaObject(
parts = [PartObject(key = url)]
)
]
))
return oc

def GetParm(text, parm):
x = text.find(parm)
if x > -1:
y = text.find(’"’, x + len(parm)) + 1
z = text.find(’"’, y)
return text[y:z].strip()
else:
return ‘’

And I have some problems:

  1. I don't like itemslist defined globaly, but somewhy I can't pass it as an argument for other function, any ideas why?
  2. I don't like using tulpes for item information, would like to use a structure/class, but don't know how to sort a list of structured items, any ideas?

A URL service may not be necessary for this particular project, since you are just trying to directly accessing a few m3u playlist.

Here are a couple post of others who were looking to do something similar to your project that may be helpful for you.

http://forums.plexapp.com/index.php/topic/7691-skeleton-plugin-for-m3u/

http://forums.plexapp.com/index.php/topic/68553-create-a-playlist-on-the-fly-or-play-all-videos-in-container/

I think one said that since it is basically a text file, so you are best using regex to parse data from it.

The MSNBC plugin probably has the best examples of global lists and lists being passed to different functions (https://github.com/plexinc-plugins/msnbc.bundle/blob/master/Contents/Code/__init__.py#L253)

Thank you shopgirl284

So it looks I am done for the moment, if anyone is interested there is a link to github: https://github.com/Cigaras/IPTV.bundle

I realize that this is an old thread, but you piqued my interest with *.str files. I seem to recall having some familiarity with them in the past, but for the life of me can't remember and my friend google isn't helping me. Could you please let me know how/with what you create *.str files?

Thanks!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.