Tuesday, 24 March 2015

Developing Plex Media Server Plugins

Plex Media Server has a nicely extensible API for writing plugins which are classified into channels, agents and services. However in trying to tweak a plugin I wanted to get working, I’ve discovered that the API that forms the latest Plex Media Server doesn’t match up with the API Reference provided. My next post I’ll give dumps of the runtime API and the differences in the object model – which aren’t too much of a big deal unless your doing audio only plugins (I’m getting a streaming radio station channel working).

There also didn’t seem to be a SDK or toolset for developing the plugins, which upon looking at the plugin bootstrap procedure I can understand why – a lot of the classes are dynamically generated and together with some other imports, are set as global variables on the plugin’s executed __init__.py

I spent a bit of time trying to get PyDev to compile using some hackery but the way I was trying to do it I hit too many hurdles. I was trying to add the globals that are exposed to the plugin at runtime to the same modules so that the framework PyDev uses to pick them up. I went down some dead-ends on that and eventually discovered that PyDev doesn’t seem to use the python-c process it kicks off in order to look up the global variables.

I did however get some launch scripts working, both from the command prompt and within PyDev. It was was part reverse engineering, part trail and error. The plugin can be located anywhere. To make the paths shorter, I moved the local application data using the Web settings –> General (Advanced Settings) from “C:\Users\[user]\AppData\Local\Plex Media Server” to “C:\PlexData\Plex Media Server” .  My install directory is C:\PlexInstall.

Running from the command line

Here is the contents of my C:\Development\MyPlugin.bundle\run-plugin.cmd It allows you to test that it compiles using the bootstrap but none of the http wiring and plugin data needs to be configured so it won’t serve requests.

@echo off
set PLEXHOME=C:\PlexInstall
set PYTHONPATH=%PLEXHOME%\python27.zip;%PLEXHOME%\Exts

"%PLEXHOME%\PlexScriptHost.exe" "%PLEXLOCALAPPDATA%\Plex Media Server\Plug-ins\Framework.bundle\Contents\Resources\Versions\2\Python/bootstrap.py" "--log-file=%CURDIR%\logs\plugin.log" "%CURDIR%" %*

Setting up PyDev Environment

During the process of writing this up I discovered that when I had thought that I had it working using PlexScriptHost in PyDev, it was using regular Python. I wasted a fair amount of time discovering that I didn’t have it working (as far as I can tell). There’s a bug in PyDev where if you have a launch configuration that uses a different interpreter to what the project is configured with, it will revert back to the project’s interpreter when you change certain settings but stay the same other times. So take it from me, don’t waste your time trying to use PlexScriptHost.

I had configured as my interpreter Python 2.7 for win32, with the following PYTHONPATH (configured from windows->Preferences->Interpreters->Python):

  1. C:\PlexData\Plex Media Server\Plug-ins\Framework.bundle\Contents\Resources\Platforms\Shared\Libraries
  2. C:\PlexData\Plex Media Server\Plug-ins\Framework.bundle\Contents\Resources\Versions\2\Python
  3. C:\PlexInstall\DLLs
  4. C:\PlexInstall\Exts
  5. C:\PlexInstall\python27.zip
  6. C:\PlexInstall
  7. C:\PlexData\Plex Media Server\Plug-ins\Framework.bundle\Contents\Resources\Platforms\Windows\i386\Libraries

To make the launch config easier to manage it best to link in the location of bootstrap.py, so right click on your plugin project and select

  1. New —> Folder
  2. Call the folder “Framework-2-Python”
  3. Click Advanced, Link to alternate location
  4. Type in C:\PlexData\Plex Media Server\Plug-ins\Framework.bundle\Contents\Resources\Versions\2\Python
  5. Click finish

For the launch config:

  1. Right click the project, select Run As –> Run configurations…
  2. Select Python Run then click new
  3. Next to main module browse to the bootstrap.properties in the previously created folder. i.e. ${workspace_loc:MyPlugin.bundle/Framework-2-Python/bootstrap.py}
  4. Under arguments enter something similar to this (changing the name of your project): --log-file=${workspace_loc:/MyPlugin.bundle/logs}\plugin.log ${workspace_loc:/MyPlugin.bundle}
  5. Set working directory to C:\PlexInstall
  6. Go to environment add: PYTHONPATH=C:\PlexInstall\python27.zip;C:\PlexInstall\Exts PLEXLOCALAPPDATA=C:\PlexData

The PLEXLOCALAPPDATA is only required if you moved the application data from c:\Users\[user]\AppData\Roaming. That should do it.

I’ve got a sitecustomize.py I’ve been working on to get rid of the compile errors in PyDev. Its a hack at the moment as they’re just stub classes, a better way would to be to generate python source code from the global variables / classes of the runtime plugin environment.

This was all done with the following versions: Plex Media Server v0. Framework.bundle 2.5.0, “Mon Jul 28 12:19:14 UTC 2014