Welcome to our forums! Please take a few moments to read through our Community Guidelines (also conveniently linked in the header at the top of each page). There, you'll find guidelines on conduct, tips on getting the help you may be searching for, and more!

Plex Plugin Development Walkthrough

IanDBirdIanDBird Plex EmployeeMembers, Plex Employee, Plex Pass, Plex Ninja Posts: 2,292 Plex Employee
edited January 2014 in Channel Plugin Development

I've spent the last few months attempting to write a variety of new plugins, specifically focused on UK based sources. These include LoveFilm-Player, Stuff.tv, Blinkbox and SkyPlayer. The latter has recently stopped working due to Sky killing the website and replacing it with a new SkyGo service. I'm therefore in the process of re-writing it and thought I would take the opportunity to write up the process of writing plugin from scratch to help out any would-be developers out there. All of this information is available via the Plex Development Guide but I thought a Walkthrough might be a good alternative and hopefully demystify the process a little to encourage other people to give it a go. I'm going to keep updating this post with more information as I progress. There will no doubt be a few mistakes so yell if you see something. Also, if you think certain sections could benefit from further explanations, just post.

Anyway, let's begin...

The Basics...
The first thing to consider is what technologies are utilised in order to develop your own plugin. Luckily, you don't need to be an expert in any of these (I'm certainly not) but you find yourself using similar code/approaches over and over again. These technologies include the following:

  • Python
  • XPath
  • JavaScript
  • JSON
  • Regular Expressions (or regex)

I've included a brief section in each one of these but the Internet is your best friend. Simply Googling (or Binging) the terms will give you a huge supply of guides and useful information. Don't be put off by number of technologies, you'll pick up what you need to know fairly quickly and once in a while need to find out something new, hence the need for a Plugin Developers forum smile.gif

Python
This is the main language which plugins are developed in. It's a very high level language so the syntax is very descriptive meaning that by simply looking at existing examples, you should be able understand what they doing and construct something that meets your needs. I've compiled a list of useful online resources but also got a few examples of common constructs that you'll likely need. The most important thing to remember is that "spacing is important!!!"

Variables
These are names for things that you wish to store temporarily and use later. You don't need to explicitly type them as this will be determined during execution. You don't even need to say that it's a variable, just give it a name and make it equal something!
 

string = "this is a string"
number = 10
...

Equality
You'll often need to test whether two variables or returned values are equal. The following section of code gives you some general examples...



number = 10

# True
number == 10
number > 5
number >= 10
number < 11
number <= 10
# False
number != 10
number > 10
number < 10

Iteration (or the "for" loop)
This is a very useful mechanism to repeat a section of code multiple times based upon some condition. This condition could be a known hardcoded number of times, or something determined during runtime. The following section of code gives some examples...



# Loop 10 times...
for index in range(10):
...

# Loop over a list
list = ["a", "b", "c"]
for character in list:
...

This posting would be far too large to go into a more detail explanation of Python. However, the following resources are a good place to start:


XPath
When writing a plugin, you may find yourself with a page of HTML and needing to located certain sections of it and even obtain text within the available nodes. XPath is basically a a mechanism of describing the required elements of an XML document and since HTML is effectively XML, it's ideal. The best tool I've found to play around with this stuff is the XPath navigator plugin available for Firefox. Note that is works specifically with HTML, not all XML. This means that you can try out various XPath queries on an actual web page, and see what is returned. The following section contains some very basic XML and XPath just to give you a flavour. One key thing to remember is that XPath always returns a list! Therefore, if you're only expecting a single element, you must postfix it with [0] to get the first element, or maybe you want to iterate over all the elements in the list...

HTML Source

<?xml version="1.0" encoding="utf-8" ?>
<books>
<book>
<title>A beginners guide to XPath</title>
<author>Gary Francis</author>
<description>A book that explains XPath for beginners</description>
<data type="Price">12.00</data>
<data type="ISBN">1234567890</data>
</book>
<book>
<title>Advanced C# Programming</title>
<author>A. Uther</author>
<description>Advanced applied C# techniques.</description>
<data type="Price">47.00</data>
</book>
<book>
<title>Understanding C# for beginners</title>
<author>Any body</author>
<description>How to get started with C# and .NET</description>
<data type="Price">12.00</data>
<data type="Comment">This was a great book... It helped Loads.</data>
<data type="Comment">Excellent material if you new to C#.</data>
</book>
</books>

XPath Example


# Select all the nodes which have a data element with the type called "Price"
priceList = htmlSource.xpath("//data[@type='Price']")

#12.00
priceList[0]

#47.00
priceList[1]

#12.00
priceList[2]

The following online resources are also useful:


JavaScript
I shouldn't need to go into too much details about what this is, the Internet is basically written using it. However, you might get away with not touching any JavaScript at all!

JSON (JavaScript Object Notation)
This is effectively a string format which describes the properties/attributes of an object. This format is used by Plex for the preferences file and user strings. It's also possible that the plugin that you're writing will make use of web services which reply to requests in a JSON format. Luckily there are helper methods for handling these, they can be found here.

Also, when creating your own JSON files, it's important that they are syntactically correct. There are validators available as web pages which can be useful when attempting to find an invalid element. One that I use can be found here.

Regular Expressions (or regex)
Most sane people will hate these, I especially do!!! This is a mechanism of defining a pattern which can be used to match against a string. There are LOADS of online sources which are much better at describing these than me, these include the following:



We'll tackle these a little more when we need to use them...

What does a plugin contain?
A plugin is essentially a folder (named *.bundle) which contains a number of other folders and files. These include the source files of the plugin, along with resources and meta files. The full documentation of what a .bundle folder should contained can be found here. However, an example folder contents is as follows:
img-bundle.png

Configuration Files
There are two different configuration files associated with a Plugin. These are as follows:

  • Info.plist
  • DefaultPrefs.json

Info.plist
The Info.plist file contains information in the Apple property list format about the bundle’s contents. This information is used by the framework when loading the plug-in. A detailed description of what values this file can contain, and what they mean can be found here. However, I thought that it might actually be useful to also provide some examples of existing plugins so that you can see what they're like. Here's a few examples.:


DefaultPrefs.json
This is a JSON-formatted file containing a set of preferences used by the plug-in, and the default values to use if the user hasn’t specified their own. An example of a preferences file may be the following:

[
{
"id": "username",
"type": "text",
"label": "Username",
"default": ""
},
{
"id": "password",
"type": "text",
"label": "Password",
"default": "",
"option": "hidden"
},
{
"id": "quality",
"type": "enum",
"label": "Quality",
"values": ["Auto", "Low", "Medium", "High"],
"default": "Auto"
}
]

Try using the JSON and playing the the JSON validator that I mentioned above. I would recommend making deliberate errors and seeing what the validator reports...

Directories
As mentioned previously, the .bundle file should also contain a number of sub-folders. These contain both the code, dependencies and various resources that are required by the plugin. They are all very much self descriptive and full details can be found on the official developer guide here, but for completeness, I will include a short description of them...

Code
This subfolder contains the main source files which you've written for the plugin. The main file should always be called "__init__.py" but additional files can also be placed here and imported when needed. We'll come on the format of the source a little later, for now its just important to know where it lives. It's also worth noting that the filename has a total of 4 underscores "_" in it. Two before "init" and two after.

Resources
This are separate files (e.g. images) which are required by the plugin. For example, every plugin will require an icon and default background artwork. As long as the default images are placed in this folder and properly named (i.e. "icon-default.png" and "art-default.jpg"), the plugin framework will use them automatically. Other images and resources can be contained within this folder and referenced (via their filename) and the R() function within the source files. E.g.

ART2 = R("alternative_art.jpg")
ICON2 = R("alternative_icon.png")

There are specific guidelines over what a suitable plugin icon should conform too and similar for the main artwork. These are documented, along with a useful template, here.

Strings\*.json (where * is the name of the language, e.g. en for English)
This is another JSON formatted file which contains the user visible strings which may be localised into other languages. This makes it easier for the plugins to be translated without actually touching any of the source files. It's generally seen as good practice to have these extracted into one of these files instead of hard coded in the source. They can be access from the python using the following syntax...

ObjectContainer.title1 = L("Title") # Find the string called "Title" from the localised JSON file...

Services
This is a slightly more advanced feature of a plugin. For now, I think we'll just acknowledge that it exists but come back to it in the future...

The current Plex plugin framework is geared towards the use of URL Services. If one already exists which will support the website for your channel, then writing the channel navigation structure is all that is necessary. If not, then you will need to write an URL Service for your channel. See The Power of the URL Service article on the Knowledge Base for more info about URL Services. 

One Last Thing Before We Begin...
Right, before we dive straight into writing a plugin, we need to make sure we know what happens when we make a mistake! There are various different bugs/mistakes one can make and these can have very different affects on what you will see from the client app. For example, completely **** up one of the configuration file could result in the plugin not being visible, etc. When this happens (and it will happen!), you need to know where the appropriate log files are stored and which one to look at...

Note the log locations listed are specific to Mac OSX. For log locations on other platforms, check here.

Plex Media Server.log (~/Library/Application Support/Plex Media Server/)
This is the main server log file. It contains information associated with attempting to load plugins, transcoding sessions, high level errors, etc. If you make a mistake in the format of your plugin, e.g. corrupt file or invalid spacing in the python, it's likely to appear in here. The file can easily get big so you'll need to search for key words, e.g. "name of plugin", "exception", etc. If you have a quick scan over this file, you'll get a feel for what information may come in handy...

com.plexapp.plugins.*.log (~/Library/Application Support/Plex Media Server/Plug-ins/ where * is the name of the plugin)
This log file is specific to the execution of your plugin. As the user navigates around the various sections, details of the HTTP requests, along with the python functions being called will be contained within here. Since you'll be editing python using a pretty basic text editor, you'll no-doubt make a few mistakes, for example make a spelling mistake in the name of a function. These type of errors will cause "Exceptions" which are automatically reported in this file. These also include what are known as "Stack Traces" which give you a reasonably good description of how the code execution reached the point of failure.

Another useful thing is that within your plugin code, you can explicitly log information which will appear in this file. This is fully documented here but the following code just gives a very brief example:

# Log a hard coded string
Log("Write this string to the file")

# Log the source code of a web page returned from a HTTP reqest...
page = HTTP.Request("www.someurl.com").content
Log(page)

com.plexapp.system.log (~/Library/Application Support/Plex Media Server/Plug-ins/)
This is the log file associated with the plugin framework/system. If you're struggling to find details of a specific issue, it's always best to check in here to see if there is any more information that can be of some use. Errors generated by URL Services often end up in here.

Now, The Coding Begins...

The Basics...
I'm going to focus on how to write a Video plugin, but there really is very little difference between these and Music and Photo plugins. Where appropriate, I will try and make reference to the alternative content types. The first thing to look at is what is the bare minimum amount of code we can write in order to make a valid (and pretty useless) plugin. Lets just jump straight into the code, and then break down each section.



####################################################################################################

PREFIX = "/video/example"

NAME = "Example Plugin"

####################################################################################################

# This function is initially called by the PMS framework to initialize the plugin. This includes
# setting up the Plugin static instance along with the displayed artwork.
def Start():

# Setup the default breadcrumb title for the plugin
ObjectContainer.title1 = NAME

# This main function will setup the displayed items.
# Initialize the plugin
handler(PREFIX, NAME)
def MainMenu():
oc = ObjectContainer()

return oc

As the above code suggests, every Plugin must define a "Start" method. This is responsible for initializing the plugin and telling the framework information about itself, including assigning default values such as the title1 attribute for ObjectContainers. It is possible and even reasonable to leave the Start() method empty as long as it is still included.

Let's break this down a little further:
 

# Initialize the plugin
handler(PREFIX, NAME)
def MainMenu():

You'll right above the definition of the MainMenu() method a line starting with "@handler". That decorator achieves a couple important tasks. First, by attaching it to the MainMenu() method, we notify the plugin framework that MainMenu() is the root or first menu to display to the user. Second, passing the PREFIX and NAME variables initialize the plugin with the values of those variables. The "Prefix" is basically a unique identifier for the plugin which not only defines it's identifier, but also it's type. It's important to note that this is of the format "/video/*" meaning that it is a Video plugin. For Music plugins, this would be "/music/*" and for Photo plugins, this would be "/photos/*". The general convention would be to just use the name of the actual plugin, i.e. skygo or spotify, etc. The NAME is what wil be displayed to the user when the channel is displayed in the list of all available Video Plugins. The @handler decorator has a (@route) counterpart for other non-root methods which we'll discuss more later. By default, the plugin is initialized with "icon-default.png" and "art-default.jpg" as the values for "thumb" and "art", respectively. However, it is also possible to assign them explicitly. Like so:

# Initialize the plugin
handler(PREFIX, NAME, thumb="icon-default.png", art="art-default.jpg")

This section is configuring the default values for both the ObjectContainer objects:
 

# Setup the default breadcrumb title for the plugin
ObjectContainer.title1 = NAME

A ObjectContainer is a container object (obvious enough?) which can contain multiple other objects. These contained objects may themselves represent another navigable level (e.g. the DirectoryObject) or they might represent actual video types, which we'll come onto in a bit. The attributes that are being set (e.g. title1, etc) can all be individually set when a ObjectContainer is constructed, but doing this here removes the need to duplicate the logic. As discussed above, the thumb and art attributes are set automatically but they can be assigned manually as well. The following example demonstrates the two equivalent mechanisms:
 

# Setup the artwork associated with the plugin
ObjectContainer.art = R(ART)
ObjectContainer.title1 = NAME

# Is the same as...

oc = ObjectContainer(art = R(ART), title1 = NAME)

Here is a brief explanation of the different ObjectContainer attributes:

  • art - The background artwork to be displayed. This is coming from a Resource file, and therefore is prefixed with R
  • title1 - The text to be displayed in the breadcrumb list at the top of the client app.

If you think of the ObjectContainer being the object representing the background of the Plugin, a DirectoryObject can be thought of as a user select-able item. The "thumb" attribute is basically defining the default icon to be used if none is specified when the plugin constructs new instances. We'll talk more about DirectoryObjects in the next section...

Navigating (Sub-menus)
Well, i'm pretty sure if you released the plugin described above, maybe a few people would download and they would all complain it's useless! All plugins tend to have some type of navigable content. This varies from hard coded options and those dynamically determined based upon the content available from an online resource. This section will discuss both of these different types and hopefully give you the basics on how these plugins are structured.

Hard Coded Items
So, you're writing your plugin and you need to have an select-able option called "Live" which will return a number of other select-able options. Lets start by jumping into some code again…
 

@handler(PREFIX, NAME)
def MainMenu():
    oc = ObjectContainer()
    oc.add(DirectoryObject(key=Callback(LiveMenu), title="Live")))
    return oc
 
@route(PREFIX + '/livemenu')
def LiveMenu():
    oc = ObjectContainer()

    oc.add(DirectoryObject(key=Callback(ChannelMenu, channel = 1), title="Channel 1"))
    oc.add(DirectoryObject(key=Callback(ChannelMenu, channel = 2), title="Channel 2"))
    oc.add(DirectoryObject(key=Callback(ChannelMenu, channel = 3), title="Channel 3"))
    ...

    return oc
 
@route(PREFIX + '/channelmenu')
def ChannelMenu(channel = None):
# Return video content for specific live channel

We've populated the MainMenu method to return a ObjectContainer which contains a single DirectoryObject. We know that unless we assigned something else, the default thumb would be what's included as "icon-default.png". This would appear as a single option to the user with the title "Live". However, this is the first time we've come across the Callback method. This is providing a relationship between the string to presented to the user, and the actual method to subsequently call when the user selects this. Is this example, the plugin framework will call the LiveMenu method. The Callback function is assigned to the "key" attribute of the enclosing DirectoryObject. It is the "key" attribute which is used behind the scenes for navigating between menus and media playback.

The next interesting thing is how the LiveMenu's DirectoryObjects are passing information through for the ChannelMenu. The ChannelMenu contains the following definition:
 

@route(PREFIX + '/channelmenu')
def ChannelMenu(channel = None):
…

Note that the channel parameter is assigned a default value of None. That is to say, an empty value is acceptable or, interpreted another way, the channel parameter is optional. If the parameter was mandatory, it would simply be:
 

@route(PREFIX + '/channelmenu')
def ChannelMenu(channel):
…

However, since it is defined with an equals character, this tells Python that it is actually optional. If the caller does not define it, it will have the value None. With the above example, this will either be 1, 2 or 3, depending on which option the user actually selected. This means that we can implement a single method "ChannelMenu" which can actually handle all Live Channels, but just gets the information that it requires passed in as a parameter.

 

Also notice the @route decorator attached to the definition of the ChannelMenu() method. The @route decorator is used to create a RESTful API for the channel's methods. Among other benefits, it can result in shorter, more human-readable URLs for the behind-the-scenes communications. The convention for the @route is to use the plugin prefix combined with a reasonable representation of the method being called. In this case, ChannelMenu(). So the long-form of the route being defined in our example would be:

/video/example/channelmenu

The @route decorator also allows the capability to ensure that variables are treated as the proper data type as they are passed from one method to another. In our example, we may wish to ensure that the "channel" parameter is treated as an int, rather than possibly being inadvertently converted to a string.

@route(PREFIX + '/channelmenu', channel = int)

As you can see, the way to define the datatype of the parameter is by assigning it with the "=" sign. Any valid datatype can be used. Other examples are string, list, and dict. This tends to become more valuable as your channel gets more complicated.

 

Dynamic Items
It's unlikely that you can get away with purely using hard coded items. It's more likely that you will have a number of URLs (either to http pages or web services) which will return information about the types of content that is currently available. We'll create a simple example which looks at a simple extract of HTML. The page will contain a lot more information but if we can identify the list that is of interest, we can use XPath to extract the required information. We can also use this extra information to improve the artwork, description, subtitle, etc that is displayed to the user.

Lets imagine we have the following html contained within a page. This is taken from this page:
 

<div class="resultsBlock">
<div class="promoItem ">
<a class="teaserImageLnk " href="http://go.sky.com/vod/content/SKYMOVIES/content/videoId/58f9e6293e928210VgnVCM1000002c04170a________/content/default/videoDetailsPage.do" onclick="show_waiting();">
<img src="http://media.entertainment.sky.com/image/unscaled/2010/06/07/SVOD-S-Avatar.jpg" width="180" height="100" alt="Avatar">
</a>
<div class="synopsisText">
<h2>
<a href="http://go.sky.com/vod/content/SKYMOVIES/content/videoId/58f9e6293e928210VgnVCM1000002c04170a________/content/default/videoDetailsPage.do" class="arrow " onclick="show_waiting();">Avatar</a>
</h2>
<p>James Cameron is back with the sci-fi spectacular that has changed the face of cinema. And it rocks. It’s the future and the resource-starved human race has found rich pickings on the lush moon Pandora... which is making the native Na’vi - blue, ten</p>
<p><b>Director</b> James Cameron</p>
<p><b>Starring</b> Michelle Rodriguez, Zoe Saldana, Giovanni Ribisi, Sam Worthington, Sigourney Weaver</p>
<ul class="info">
<li><strong>duration</strong><span>155 mins</span></li>
<li><strong>rating</strong><span>12A</span></li>
</ul>
<div class="availability">Sky Movies</div>
</div>
</div>
<div class="promoItem ">…</div>
<div class="promoItem ">…</div>
<div class="promoItem ">…</div>
<div class="promoItem ">…</div>
</div>

This is actually the results of a search query for the title "Avatar". I identified that the html page contained a div section called "resultsBlock" which contains the actual found items (as shown by the div promoItems). We first must ask the PMS framework to obtain the web page for us. This can be done using the HTML methods as documented here
 

# Returns the web page as an Element
     search_page = HTML.ElementFromURL("http://...")

Now we've got the page, we need to find the resultsBlock and iterate over the individual results:
 

# The XPath query will return a list of all items which match the query.
    items = search_page.xpath("//div[@class='resultsBlock']/div[@class='promoItem ']")
    for item in items:
...

The individual items that are iterated over as still Elements but they have a root node of <div class="promoItem ">. This means that we will call the next section of code 5 times to correspond to the 5 promoItems located in the HTML source.

Now, lets look at the source again and see what useful information we can extract to make the DirectoryObject a little nicer. Well, we obviously have a title - "Avatar" but we also have a few other things, e.g. artwork, description, director, actors, duration, rating and associated channel. This means we can do something much better than simply displaying the option "Avatar" to the user. We'll need to extract all the necessary information and assign it to the corresponding attributes of the ObjectContainer. Once we've done all this, we should have a method similar to the following:
 

@route(PREFIX + '/search')
def Search(query, url = None):
    oc = ObjectContainer(title2 = query)
    
    url = SEARCH_URL % String.Quote(query)
    search_page = HTML.ElementFromURL(url)

    items = search_page.xpath("//div[@class='resultsBlock']/div[@class='promoItem ']")
    for item in items:
    
        title = item.xpath("./div[@class='synopsisText']/h2/a/text()")[0]
    
        # If the specified image URL is relative, then translate it
        image = item.xpath("./a/img")[0].get('src')
        if image.startswith("http") == False:
            image = BASE_URL + image
    
        # If the specified URL is relative, then translate it
        url = item.xpath("./a")[0].get('href')
        if url.startswith("http") == False:
            url = BASE_URL + url
    
        # [Optional] - The Summary
        summary = None
        try: summary = item.xpath(".//div[@class='synopsisText']/p/text()")[0]
        except: pass
    
        # Add the found item to the collection
        oc.add(VideoClipObject(
            url,
            title = title,
            summary = summary,
            thumb = image))

    # If there are no titles, we should warn the user.
    if len(oc) == 0:
        return ObjectContainer(header=NAME, L('ErrorNoTitles'))
            
    return oc

I've been a little lazy and only extracted a few of the items, but you should be able to get the idea. This means that this method now returns an ObjectContainer which contains five sub-items, which all contain titles, images and descriptions! That's looking a bit better now.

Input Items
Those of you who have used a number of different Plugins will be aware that there is a mechanism to prompt the user for some type of input. This is most common when performing a Search but can actually be used for any input that you need. The following image is taken from the OSX client app and shows a simple Input prompt:

InputDirectoryItem.png

This type of control is known as an InputDirectoryObject. It's essentially exactly the same as a DirectoryObject but this control is displayed to the user between them selecting the item, and Plex calling the associated Callback. The following code show's how to create one of these and how to implement the supporting method.
 

@handler(PREFIX, NAME)
def MainMenu():
    oc = ObjectContainer()
    oc.add(InputDirectoryObject(key=Callback(Search, prompt="Search for..."), title="Search Title", summary="The description of what you are searching for...", thumb = R("search-icon.png"))))
    return oc
 
@route(PREFIX + '/search')
def Search(query, url = None):
…

Now that the Search method has the query, it can do whatever it needs too to actually perform the search and process the results. In the above example, we assign a "prompt" to the Search. That is what will be presented to the user in the Search dialog box. It is not necessary to provide a value for the prompt but, it may provide clarity to the user.

It is also worth noting that the plugin Framework offers a SearchObjectDirectory method which is very much like the InputDirectoryObject except that rather than calling a method defined in the channel code, it calls a Search method which is populated via a Search Service. Search Services are covered in the Channel Development documentation here.

It's worth noting that some clients may treat SearchDirectoryObjects differently from InputDirectoryObjects. It's up to the channel developer to choose which one works better for his/her needs. For example, the Roku app uses different input options for SearchDirectoryObject versus InputDirectoryObject. However, assigning a string beginning with the keyword "Search" to the above-mentioned "prompt" parameter for an InputDirectoryObject will trigger the Roku app to use the more restrictive "Search" keyboard interface which lacks the non-alphanumeric characters.

Preference Items
It's often the case that a plugin will allow the user to specify a number of preferences. These can be anything, for example a username and password required to authenticate to the web service or web page. This is actually very easy. A preferences directory can be called by simply using a "PrefsObject" as shown in the following code:

@handler(PREFIX, NAME)
def MainMenu():
    oc = ObjectContainer()
    
    # Preferences
    oc.add(PrefsObject(title=L('Preferences')))
    
    return oc

You'll notice that the PrefsObject doesn't use a Callback and doesn't actually specify any method to call. This is because Plex will automatically interpret the DefaultPrefs.json file contained within your .bundle and display the appropriate options. There are various types of preferences which can be used, for example free form text, hidden text (ideal for obscured passwords), and lists (like quality settings: low, medium and high). A more detailed description of the available here.

Handling preference values within the plugin code is also quite simple since the plugin Framework makes them all available via a dictionary object. So, the value of any preference setting can be retrieved by its 'id', as shown below:

username = Prefs['username']

Actually playing different view types…
Coming Soon... smile.gif

«1

Comments

  • CapybaraCapybara Members Posts: 88 ✭✭
    Nice post, can't wait for part 2. thanks
  • macbartmacbart Members Posts: 83
    I am very curious about how to write plugins. Thanks for this useful information.
  • wmbellwmbell Members, Plex Pass Posts: 451 Plex Pass
    Great Post!!
    Mods should sticky this!!
  • IanDBirdIanDBird Plex Employee Members, Plex Employee, Plex Pass, Plex Ninja Posts: 2,292 Plex Employee
    Thanks, i'm gonna keep adding sections as I get more time. It's likely to take quite a while as i'm doing this as well as working on plugins. I've just updated the initial post to now contain some code and will hopefully get more done in the next day or two.

    If there is anything which is a little confusing, please shout! I can then go back and hopefully explain it a little better.
  • BrettBBrettB Members Posts: 24
    Wow, awesome thread, thank you!
  • mpdaughertympdaugherty Members Posts: 1
    Thanks a lot for the great intro! I'm writing my first plugin today and wanted to point out something that slowed me down for a few hours. The log files are not in ~/Library/Application Support/Plex Media Server. They are actually in ~/Library/Logs (at least on my machine - Snow Leopard and Plex 0.9.3.4).
  • zone51zone51 Members Posts: 2
    Hi,
    As a new member I have found out that it is very difficult to get started with writing a plug-in.
    Your post did a very good job! thanks.
    The next post should contain description of how to install & debug
  • mikedm139mikedm139 Plugin Developer Members, Plex Pass, Plex Ninja Posts: 3,022 Plex Ninja
    zone51 wrote:

    Hi,
    As a new member I have found out that it is very difficult to get started with writing a plug-in.
    Your post did a very good job! thanks.
    The next post should contain description of how to install & debug


    Your primary debugging tool for plugins, whether it's something you wrote yourself or a broken plugin that you're trying to fix, is the plugin log. When some part of a plugin is failing, the log will usually contain an error message which points to the reason for the error and the line in the plugin code that caused the error. Interpreting the error and figuring out how to fix it takes a combination of practice, trial and error, and some detective work. There are countless things that can go wrong with plugins (and usually do). In my experience, the most. common errors when writing a new plugin are typos and syntax errors, as well as incorrect xpath expressions. When existing plugins break, the source of the error is almost always due to a change in the code of the source website. The plugin log will again point you to the line in the code that threw the error. Then it's time to start investigating the source website to see how to gather the same information as the previous plugin code, only using the existing webpage.

    I'm pretty sure one of Ian''s blog posts at dev.plexapp.com covers some suggestions on how best to install a working copy of a plugin so that it won't get overwritten by PMS. There's more good reading there and I think that future tutorials from Ian will show up as blog posts there rather than forum posts.

    If you are having trouble with one or more channels, check here first.
    If you are asked to provide logs, which you will be if you are having plugin problems, the help center has details on where to find them.
    ***************************************************************************************************************************************
    Check out unofficial channels in the
    UnSupported AppStore
    Follow my efforts on Github and Twitter
    Donate

  • maler01maler01 Members Posts: 18
    Hello, thanks for guide.

    One question - did you tried any conversion of XBMC plugin to Plex plugin? So far i didn't found any info about future support of XBMC plugins in next versions of Plex, so maybe such a guide can be useful too.
  • sander1sander1 Channel Developer/Admin Members, Plex Pass, Plex Ninja Posts: 3,633 Plex Ninja
    MaLer wrote:

    Hello, thanks for guide.

    One question - did you tried any conversion of XBMC plugin to Plex plugin? So far i didn't found any info about future support of XBMC plugins in next versions of Plex, so maybe such a guide can be useful too.

    Hi!
    XBMC plugins and Plex channels work different and there's not 1 method to "convert" one into another. They need to be rewritten, probably almost completely.

  • lahattelahatte Members, Plex Pass Posts: 53 Plex Pass
    edited March 2013

    Hi. Thank you very much for this. It helps, but I for one am having a bit of difficulty making the connection to the 'Search' function you have coded here. I would like to actually be able to put the go.sky.com site in as the location of interest and get the plugin going. 

    I guess what I am asking for is a complete code example that will actually work, including the call to the Search function, etc. That, along with the descriptions of the code sections would be very helpful.

    Thanks again.

    Clay

  • MaldivesMaldives Members Posts: 2

    Hi Ian

    I have a plex media server setup to play cartoons for children. Is it possible to make a channel from my media library like playing random or list of media. I want them to feel it like a cable channel. Is it possible? if so please guide me to docs..

    Thank you

  • pradeepkgpradeepkg Members Posts: 15

    I am using Eclipse PyDev plugin for writing the channel code and it makes life much easier. I just wanted to know where can I find the Plex core API objects to add to the path so that I can check for errors in my IDE.

  • greenguygreenguy Members Posts: 25

    this is great!  bookmarked

  • eskwireeskwire Members Posts: 132 ✭✭
    I want to channels do you recommend me to try make them or just comment witch channels they are?

    all-fanart.jpg

  • fpintafpinta Members Posts: 1

    Hi all, there is a way to add file extension to the existing video files? I want to add .xyz file type as movie file type.

    I found the code Common.py on /var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/Scanners.bundle/Contents/Resources/Common but this dir is refreshed periodically.

    Could you help me?

    Regards.

    Francesco

  • dane22dane22 Members, Plex Pass, Plex Ninja Posts: 10,083 Plex Ninja

    Wow......This is actually brilliant work.....Huge Kudo to you 4 that, but one small Q:

    IanB! wrote on July 11 2011, 4:46 PM: »

    Actually playing different view types…
    Coming Soon... smile.gif

    When :P

    Best regards

    Tommy

    I hate bugs - Tommy Lee Jones, MIB
    Join me in developing: epg-dk, str2utf-8, remidx, ExportTools, WebTools
    Support the Samsung Client:Donate
    Guides I use: Media Naming Guide, Local subtitles, Log-Files, QNAP FAQ, The Plex Dance

    NO Support via PM, unless called by me

  • HuXHuX Members, Plex Pass Posts: 176 Plex Pass
    pradeepkg wrote on April 22 2013, 9:01 PM: »

    I am using Eclipse PyDev plugin for writing the channel code and it makes life much easier. I just wanted to know where can I find the Plex core API objects to add to the path so that I can check for errors in my IDE.

    I've got the same setup, and it is possible, but a waste of time, adding the Plex Framework to the work path IMO.

    You get all the system functions included, that I've found crashes the channel/service.

    Also tried to compile the docs into Dash, but without any luck (I'm not a programmer, so it might be possible, just not with my skills ;) )
    Some good plugins to Eclipse for Channel development (again IMO) is a JSON editor and an XML editor.
    Unfortunately there's no plist editor, that i can find, but if anyone finds one, give a shout :D

    /HuX

    /HuX

    Adm. Plex DK Facebook gruppe
    Plex Plug-in udvikler (DRNU, YouSee, Radio24syv, DFI Metadata agent)
  • ace996ace996 Members Posts: 1

    great job. I've been looking for this for awhile.

    Thanks for sharing

  • cncbcncb Members, Plex Pass Posts: 134 Plex Pass

    Was there ever a "part 2"?

«1
Sign In or Register to comment.