'General' Channel Development Questions From A New Developer

plugin-dev

#1

Hi All!

As per @dane22 's suggestion, I'm taking a few questions I had for him in PM and posting them here, as I'm new to Channel development, but fairly versed in other projects.

Without further ado...

So, the plugin in question(s) are here:

https://github.com/d8ahazard/FlexTV.bundle/commit/8d3ed3c00a85a0dc6aeaeb66118e604b9bae59ed

  1. I'd like it to use the PyChromeCast Library, which has it's own set of dependencies, etc. In my latest commit, I have taken and created the directory /Contents/Libraries/Shared under my project, placed in all the files pulled down by doing a pip install as directed in the PCC readme:

https://github.com/balloob/pychromecast

Yet when I try to access the plugin via URL, I see an error in the log about being unable to find pychromecast.

What am I doing wrong?

  1. Is there a specific trick to forcing Plex to reload my code changes, or is it automagic every time I reload the endpoint?

  2. One of the commands I'll need to do is to the /Devices endpoint. I see I can basically return a MediaContainer from the call, which is cool, because I'm already scraping Plex.tv for device listings.

Question being - is there some documentation on the containers I can return? Is it possible for me to return (more or less) the same structure as I'd find at Plex.tv/devices - so I can just add data from that URL to my existing parser for Plex.tv/devices?

  1. Is there a particular/recommended editor for doing Python work? I don't know the syntax well yet, so having a smarter IDE would probably help a lot, and Pycharm seems to really hate Plex plugin format. Ideally, Pycharm would be my tool of choice, but I don't see anything posted anywhere about how to provide the libraries it needs to understand the native Plex calls.

#2

First of all, a Plex Channel lives in a Plex sandbox, and as such, do not like stuff starting with __

Secondly, a zip with the plugin log do help :wink:

When you change your code, PMS should auto-detect, and reload…
Check logs will help, or if running on anything else than Windows, start PMS from a terminal, and in the start function of the Channel, do like print "Starting"

And I personally use Visual Studio Code for my work

/T


#3

@dane22 said:
First of all, a Plex Channel lives in a Plex sandbox, and as such, do not like stuff starting with __

Secondly, a zip with the plugin log do help :wink:

https://pastebin.com/XMK2fAgi


#4

@dane22 said:
First of all, a Plex Channel lives in a Plex sandbox, and as such, do not like stuff starting with __

Forgot to ask what you meant by this. Looking at some of your other projects, I see you importing stuff in a similar fashion. I’ve double-checked the directory structure, don’t see anything obviously wrong with the library itself.

Am I correct in understanding that’s what the error in the log is from? Or is the .dll issue on the windows side?


#5

You are missing a comma here:
https://github.com/d8ahazard/FlexTV.bundle/blob/tommy-work/Contents/Code/init.py#L85

However, after adding that, it failed on a missing google plugin, and sadly haven’t got time to dig into this, since already have my plate full of challenges :smile:


#6

@dane22 said:
You are missing a comma here:
https://github.com/d8ahazard/FlexTV.bundle/blob/tommy-work/Contents/Code/init.py#L85

However, after adding that, it failed on a missing google plugin, and sadly haven’t got time to dig into this, since already have my plate full of challenges :smile:

Fair enough.

That typo was fixed on my side, but I’m still seeing the same exact error about not finding pychromecast - not a google plugin.

Is that log message about google in the FlexTV.bundle log, or another file?

I know you’re busy. Not asking you to do it all for me, just help me get my bearings and an understanding of the basics, then I should be able to figure out the rest…


#7

Log said:

2018-01-15 22:42:53,596 (7f986e684700) : CRITICAL (core:615) - Exception starting plug-in (most recent call last): File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/core.py", line 608, in start self.sandbox.execute(self.init_code) File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 256, in execute exec(code) in self.environment File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Library/Plex Media Server/Plug-ins/FlexTV.bundle/Contents/Code/__init__.py", line 14, in <module> import google.protobuf File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 345, in __import__ raise e ImportError: No module named google.protobuf


#8

@dane22 said:
Log said:

2018-01-15 22:42:53,596 (7f986e684700) : CRITICAL (core:615) - Exception starting plug-in (most recent call last): File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/core.py", line 608, in start self.sandbox.execute(self.init_code) File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 256, in execute exec(code) in self.environment File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Library/Plex Media Server/Plug-ins/FlexTV.bundle/Contents/Code/__init__.py", line 14, in <module> import google.protobuf File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 345, in __import__ raise e ImportError: No module named google.protobuf

So, I’ve got google\protobuf in the Libraries\Shared directory.

I see in several files under the pychromecast project they are importing “from google.protobuf”. Is the Plex engine able to understand that this notation means to look in shared libraries for the google\protobuf directory? Or do I need to notate it like …google.protobuf - or in some other fashion?

Also, inside google\protobuf - is there any need to change the references to protobuf in there?


#9

@dane22 said:
Log said:

2018-01-15 22:42:53,596 (7f986e684700) : CRITICAL (core:615) - Exception starting plug-in (most recent call last): File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/core.py", line 608, in start self.sandbox.execute(self.init_code) File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 256, in execute exec(code) in self.environment File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Library/Plex Media Server/Plug-ins/FlexTV.bundle/Contents/Code/__init__.py", line 14, in <module> import google.protobuf File "/share/CACHEDEV1_DATA/.qpkg/PlexMediaServer/Resources/Plug-ins-fc63598ba/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 345, in __import__ raise e ImportError: No module named google.protobuf

Okay, sorted that out. The google directory needs a blank init.py file in order for pyChromeCast/Plex to recognize it as a module.

NOW, I’m getting:

2018-01-16 14:50:04,163 (-8e59900) : DEBUG (core:566) - Finished loading plug-in code 2018-01-16 14:50:05,124 (-c54a4c0) : DEBUG (core:538) - Machine identifier is 511722ffd7807f5b8d4187e12ffbb8adb5641bf2 2018-01-16 14:50:05,124 (-c54a4c0) : DEBUG (core:539) - Server version is 1.10.1.4602-f54242b6b 2018-01-16 14:50:07,946 (-beee4c0) : DEBUG (services:362) - Loaded services 2018-01-16 14:50:08,484 (-c25c4c0) : DEBUG (services:438) - No shared code to load 2018-01-16 14:50:08,491 (-8e59900) : CRITICAL (core:615) - Exception starting plug-in (most recent call last): File "/volume1/@appstore/Plex Media Server/Resources/Plug-ins-f54242b6b/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/core.py", line 608, in start self.sandbox.execute(self.init_code) File "/volume1/@appstore/Plex Media Server/Resources/Plug-ins-f54242b6b/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 256, in execute exec(code) in self.environment File "/volume1/Plex/Library/Application Support/Plex Media Server/Plug-ins/FlexTV.bundle/Contents/Code/__init__.py", line 14, in <module> import pychromecast File "/volume1/@appstore/Plex Media Server/Resources/Plug-ins-f54242b6b/Framework.bundle/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 345, in __import__ raise e ImportError: No module named netifaces

Doing my homework, I see that netifaces is special, because it wants to access the NICS. So, am I screwed?

It looks like all it’s doing with netifaces is listing the IP addresses available to the system into an array, then calling that with normalize_interface_choice…

So, is there a method from the Plex core that I can use to just spoof this since Plex should know it’s own IP Address? I think if I can feed that data into zeroconf.py (@L1585), I can move onto the next stumbling block. XD


#10

Network.Address

Returns the server’s IP address. Unless the server is connected directly to the internet, this will usually be a local IP address. Return type str

Above would most likely needing to be called from the channels init.py file, since seems to recall, that framework calls doesn’t work from /shared dir

And when said, since this is a channel, you could also simply use 127.0.0.1


#11

And now I’ve got it listing devices!!!

Next random question - how can I populate my ObjectContainer with elements based on the cast items??


#12

@dane22 said:
Network.Address

Returns the server’s IP address. Unless the server is connected directly to the internet, this will usually be a local IP address. Return type str

Above would most likely needing to be called from the channels init.py file, since seems to recall, that framework calls doesn’t work from /shared dir

And when said, since this is a channel, you could also simply use 127.0.0.1

Exactly what I wound up doing - I saw that the zeroconf module had an option to return 0.0.0.0 for binding the socket - this works, I’m now scraping and listing Cast devices.

Only thing I can’t figure out (and it’s not a deal-breaker) is if there is some kind of DeviceObject container that’s undocumented. I’m returning Cast info using a DirectoryContainer and just sticking the needed info into fields, but I feel like this is hacky.

Is there any way to define a custom container, where I can call the fields what they actually are?


#13

@digitalhigh said:
Only thing I can’t figure out (and it’s not a deal-breaker) is if there is some kind of DeviceObject container that’s undocumented. I’m returning Cast info using a DirectoryContainer and just sticking the needed info into fields, but I feel like this is hacky.

Is there any way to define a custom container, where I can call the fields what they actually are?

You are draining me here :wink:

Besides that, I have no idea what you mean?

Context and coding sample please


#14

@dane22 said:

@digitalhigh said:
Only thing I can’t figure out (and it’s not a deal-breaker) is if there is some kind of DeviceObject container that’s undocumented. I’m returning Cast info using a DirectoryContainer and just sticking the needed info into fields, but I feel like this is hacky.

Is there any way to define a custom container, where I can call the fields what they actually are?

You are draining me here :wink:

Besides that, I have no idea what you mean?

Context and coding sample please

Haha - we’re almost there though!! :dizzy:

So, my output currently looks like this for the \Devices enpoint:

<MediaContainer title1="Cast Devices" noHistory="1" title2="Devices found" noCache="1" size="8" identifier="com.plexapp.plugins.FlexTV" sourceTitle="FlexTV" mediaTagPrefix="/system/bundle/media/flags/"> <Directory thumb="192.168.1.217:8009" duration="1" title="Kitchen Home" summary="audio"/> <Directory thumb="192.168.1.217:42581" duration="1" title="Upstairs" summary="group"/> <Directory thumb="192.168.1.150:8009" duration="1" title="Bathroom" summary="audio"/> <Directory thumb="192.168.1.131:8009" duration="1" title="Basement speaker" summary="audio"/> <Directory thumb="192.168.1.104:8009" duration="1" tagline="E8C28D3C" title="Gavin's Room" summary="cast"/> <Directory thumb="192.168.1.129:8009" duration="1" title="Bedroom TV" summary="cast"/> <Directory thumb="192.168.1.197:8009" duration="1" title="SHIELD" summary="cast"/> <Directory thumb="192.168.1.118:8009" duration="1" title="SHIELD" summary="cast"/> </MediaContainer>

I want to do that, but make it look like the output of plex.tv\api\resources. My scrapers already know how to parse that output. It’s minor to write a new method that scrapes what I’m outputting now, but would be nice to have those fields, just for consistency.


#15

Hah…Finally nailed you here :smile:

We are now moving into what the Framework actually was designed to do, and never ever touched that!

Maybe @shopgirl284 has a clue?


#16

@dane22 said:
Hah…Finally nailed you here :smile:

We are now moving into what the Framework actually was designed to do, and never ever touched that!

Maybe @shopgirl284 has a clue?

I was poking around in the framework, and found a declaration for a “ProxyObject” that appeared to take a multidimensional array and convert it to an object, but I couldn’t access it as I have things declared now.


#17

Coding against the framework is indeed up hill…

And you did check out this?


#18

@dane22 said:
Coding against the framework is indeed up hill…

And you did check out this?
https://forums.plex.tv/discussion/comment/970602/#Comment_970602

I did, that’s how I figured out the structure for the DirectoryObject I’m returning. Also where I got the idea to grep around in the PMS directory and found the ProxyObject. I think I found an example declaration I can copy to use it, but I’ve gotta play around with it.

From “Plex Media Server\Resources\Plug-ins-fc63598ba\Framework.bundle\Contents\Resources\Versions\2\Python\Framework\api\modelkit.py”
`
import Framework
import os

from base import BaseKit

def ProxyObjectGenerator(proxy_name):
def ProxyFunction(data, sort_order=None, ext=None, index=None, **kwargs):
return Framework.modelling.attributes.ProxyObject(proxy_name, proxy_name.lower(), data, sort_order, ext, index, **kwargs)
return ProxyFunction

class ProxyKit(object):
def init(self):
self.Preview = ProxyObjectGenerator(‘Preview’)
self.Media = ProxyObjectGenerator(‘Media’)
self.LocalFile = ProxyObjectGenerator(‘LocalFile’)
self.Remote = ProxyObjectGenerator(‘Remote’)


`


#19

@dane22

Okay, so, after another day of plugging away, a few more questions…if you’re feeling magnanimous:

  1. I see there’s a function called UpdateCache() that appears to be useable to do a thing at a given interval. Ideally, I’d like to use this to call a re-scan of cast devices every N minutes, and save them with Data.Set().

I also see that you’ve set HTTP.CacheTime to 0.

So, is there a reason for CacheTime to be at zero, where changing it will break something? If not, is there anything else I need to do to get UpdateCache() to work properly? Does it work properly?

  1. Some of my helper modules call log.debug() versus Log.Debug(). Is there a way to pipe this output to Log.Debug(), or does this get written somewhere else?

#20
  1. No idea, since never used it myself

And reason why I set HTTP.CacheTime to zerro was/is, that I don’t code regular channels, but more into the tools buisness

  1. I suggest you check out github for the Sub-Zero plugin…
  2. The dev of that tweaked the framework in ways I simply can’t yet grasp, and it works perfectly for him