Plex iNotifier: Auto-update a remote Plex Media Server when new content is added on NAS

EDIT: I’ve moved this over to a GitHub repository, which is much easier for tracking changes. I also renamed it from “Plex Notifier” to “Plex iNotifier” to help avoid confusion with the “Plex Notify” application. You’re still welcome to report issues here, or submit an issue ticket on GitHub.

_The GitHub repository is here: https://github.com/talisto/plex-inotifier_

Plex iNotifier

Plex is an amazing piece of software. One issue with Plex Media Server, though, is that it doesn’t auto-update the library when there are changes on connected network shares. This can be annoying if you run your PMS on a different machine (e.g. Nvidia Shield, Mac Mini, etc) than where your media library is stored (e.g. NAS), as you either have to trigger an update on PMS manually when adding/moving/renaming content or wait for the library update interval to kick in.

So, I wrote a script to automate that (borrowed heavily from here).

The script works by tying into inotify on the NAS, which is a kernel subsystem that notices changes to the filesystem and reports those changes to applications. The script uses inotify to monitor the media directories on the NAS for changes, then connects to the remote Plex Server’s web API to find the appropriate media section to refresh. If it finds a matching section, it uses the web API to send an update command to that section.

Installation

  1. Clone the GitHub repository or download the latest zipfile.
  2. Make sure you have “Python3” installed.
  3. You’ll need to install the “pynotify” Python module. The easiest way is to install the Python EasyInstall utility; Shell into your server, and run: wget https://bootstrap.pypa.io/ez_setup.py -O - | python3 then run: easy_install pyinotify
  4. Save the plex-inotify.py script somewhere on your NAS/fileserver, e.g. /usr/local/bin
  5. Edit the plex_server_host variable near the top of your script to match the IP address of your Plex Server. If you have local DNS resolution, you can use a hostname instead.
  6. Edit the path_maps variable to map the local paths of the media shares on your fileserver to their corresponding library names in your Plex Media Server.
  7. You should change the daemonize variable to False for testing purposes until you’re sure that everything is working properly.
  8. Try running the script with python3 plex-inotify.py, and if all goes well, it will load up without errors :slight_smile:

If you set daemonize to True, then the script will fork itself into a background task when you run it. It will stay running even if you log out of the shell.

IMPORTANT: With the security changes in PMS version 1.1.0, if your Plex Media Server is signed in to a plex.tv account, you’ll need to set the plex_account_token variable (see “troubleshooting” below) or you can add the IP address of your fileserver to the following advanced preference: Settings > Server > Network > List of IP addresses and networks that are allowed without auth.

Troubleshooting

  • If you see a bunch of errors that say something like Errno=No space left on device (ENOSPC), then your inotify watcher limit is too low. Run sysctl -n -w fs.inotify.max_user_watches=16384 and then try again. Keep raising the number until the errors go away.

  • If you see an error that says Errno=No such file or directory (ENOENT), then you didn’t configure your paths_maps properly. Make sure each entry in the list is a local path to your media on the NAS and then the corresponding library/section name on your PMS.

  • If you’re getting an error that says urllib.error.HTTPError: HTTP Error 401: Unauthorized, then you need to set the plex_account_token variable. Follow this link for instructions on how to get your account token. Make sure that when you’re setting the variable, you wrap the token in quotes, like this: plex_account_token = 'A2ekcFXjzPqmefBpv8da'

Let me know if you find this useful!

@trumpy81 said:
Are you sure you have ‘Update my library automatically’ turned on and ‘Run a partial scan when changes are detected’ turned off in Plex settings?

Auto updating should work correctly and is not dependent on any connections. If it is not working then you should investigate that further.

PMS doesn’t auto-update when the media is being read from network shares (NFS, SMB, AFP). That’s the point of this script. This issue has been discussed in this forum and elsewhere many, many times, and is even documented in this support article: https://support.plex.tv/hc/en-us/articles/200289306-Update-or-Refresh-a-Library

Which says regarding the “Update my library automatically” option: “Note: In most cases, this should work for content on local filesystems. It will generally not work for network shares mounted via SMB, NFS, AFP, or similar.

You can use http://[PMS_IP_ADDRESS]:32400/library/sections/29/refresh to update a library.
And use this to show the library sections: http://[PMS_IP_Address]:32400/library/sections

That’s exactly what my script is doing. Did you even read my post?

Perfect to automatically refresh my PMS on Nvidia Shield ! Thanks ! :smiley:

Hallo talisto,

ich habe seit Kurzem auch eine Shield und wollte Dein Script ausprobieren… leider bekomme ich nach dem Start dann doch ein paar Fehlermeldungen.

So Script gestartet:

root@DiskStation:~# /usr/local/etc/rc.d/plex_notify.py

Das kommt nach Start:

/usr/local/etc/rc.d/plex_notify.py: line 10: plex_server_host: command not found
/usr/local/etc/rc.d/plex_notify.py: line 13: plex_server_port: command not found
/usr/local/etc/rc.d/plex_notify.py: line 16: path_maps: command not found
/usr/local/etc/rc.d/plex_notify.py: line 17: /volume1/video: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 18: /volume1/photo: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 19: /volume1/music: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 20: /volume1/Aufnahmen: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 21: /volume1/Blu-ray: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 22: /volume1/Serien: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 23: /volume1/Transfer: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 24: /volume1/Zeugs: Is a directory
/usr/local/etc/rc.d/plex_notify.py: line 25: syntax error near unexpected token }' /usr/local/etc/rc.d/plex_notify.py: line 25: }’

Einträge im Script:

Plex Server IP or hostname

plex_server_host = 192.168.2.4

Plex Server port

plex_server_port = 32400

Map the mount points on your Plex Media Server to the fileserver’s local paths

path_maps = {
/volume1/video
/volume1/photo
/volume1/music
/volume1/Aufnahmen
/volume1/Blu-ray
/volume1/Serien
/volume1/Transfer
/volume1/Zeugs
}

Müssen noch anderswo Änderungen vorgenommen werden?

Hi RSchally,

Unfortunately I don’t speak German, but the problem is that you need to use Python3 to run the script, like this:

root@DiskStation:~# python3 /usr/local/etc/rc.d/plex_notify.py

Hey Talisto,

thanx for your reply! Will try to start the script with your mentioned command…
Are my shares on the diskstation set corret the way I did?

Thanx

Hello Talisto

Thanks for this script
It work fine on dsm 6
When i launch in ssh command Line, all is perfect, but It no work on sheduled task.

I created a scheduled task from the menu in the Control Panel nas . I chose execution at boot , and set the command line " python3 /volume/scripts/plex_notify.py"

I 'd like the script to start at boot (after an update for instance)

When I try Lanch task here error :

Traceback (most recent call last): File "/volume1/scripts/plex_notify.py", line 168, in <module> wdd = wm.add_watch(list(sections.keys()), watch_events, rec=True, auto_add=True) File "/volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages/pyinotify-0.9.6-py3.5.egg/pyinotify.py", line 1916, in add_watch File "/volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages/pyinotify-0.9.6-py3.5.egg/pyinotify.py", line 1833, in __add_watch File "/volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages/pyinotify-0.9.6-py3.5.egg/pyinotify.py", line 153, in inotify_add_watch File "/volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages/pyinotify-0.9.6-py3.5.egg/pyinotify.py", line 246, in _inotify_add_watch UnicodeEncodeError: 'ascii' codec can't encode characters in position 23-24: ordinal not in range(128)

Hey talisto,

tried again and when starting the script I get this message… any idea what’s going wrong?

Wanted to try with one folder first:

path_maps = {
‘/Volumes/Aufnahmen’: ‘/volume1/Aufnahmen’
}

root@DiskStation:~# python3 /usr/local/etc/rc.d/plex_notify.py
Traceback (most recent call last):
File “/usr/local/etc/rc.d/plex_notify.py”, line 160, in
sections = get_plex_sections(plex_server_host, plex_server_port, path_maps)
File “/usr/local/etc/rc.d/plex_notify.py”, line 137, in get_plex_sections
response = urllib.request.urlopen(req)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 162, in urlopen
return opener.open(url, data, timeout)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 465, in open
response = self._open(req, data)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 483, in _open
‘_open’, req)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 443, in _call_chain
result = func(*args)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 1268, in http_open
return self.do_open(http.client.HTTPConnection, req)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 1243, in do_open
r = h.getresponse()
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/http/client.py”, line 1174, in getresponse
response.begin()
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/http/client.py”, line 282, in begin
version, status, reason = self._read_status()
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/http/client.py”, line 251, in _read_status
raise RemoteDisconnected(“Remote end closed connection without”
http.client.RemoteDisconnected: Remote end closed connection without response

Does this work with the latest 1.X series Plex Media Server?

Tried to install on latest ubuntu, did: sudo apt-get install python3-pyinotify and it appeared to install with no issues.

Made the IP and mapping changes to the file and ran it using sudo python3 plex_notify.py

I receive the following error:

Traceback (most recent call last):
File “plex_notify.py”, line 161, in
sections = get_plex_sections(plex_server_host, plex_server_port, path_maps)
File “plex_notify.py”, line 138, in get_plex_sections
response = urllib.request.urlopen(req)
File “/usr/lib/python3.5/urllib/request.py”, line 162, in urlopen
return opener.open(url, data, timeout)
File “/usr/lib/python3.5/urllib/request.py”, line 471, in open
response = meth(req, response)
File “/usr/lib/python3.5/urllib/request.py”, line 581, in http_response
‘http’, request, response, code, msg, hdrs)
File “/usr/lib/python3.5/urllib/request.py”, line 509, in error
return self._call_chain(*args)
File “/usr/lib/python3.5/urllib/request.py”, line 443, in _call_chain
result = func(*args)
File “/usr/lib/python3.5/urllib/request.py”, line 589, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 401: Unauthorized

I love this idea and would love to get it to work. Without it, ISCSI is pretty much the only alternative I can think of. Thanks in advance for any help.

Hi Kaizo33,

@kaizo33 said:
Hello Talisto
It work fine on dsm 6
When i launch in ssh command Line, all is perfect, but It no work on sheduled task.

Python needs the language environment variable set to utf8, which is set when you login via SSH, but not when you run the script as a scheduled task. Just change your scheduled task to look like this instead:

export LANG=en_US.utf8
python3 /volume/scripts/plex_notify.py

that will set the appropriate environment variable before the script runs, which should fix the problem.

Also, make sure you set the daemonize variable in the script to True so that it will run in the background, otherwise the task scheduler will keep waiting for the script to finish, and will suck up 100% of your CPU.

Hi RSchally,

@RSchally said:
Hey talisto,
tried again and when starting the script I get this message… any idea what’s going wrong?

I think that is because you have your Plex Media Server set to require secure connections. At the moment my script can’t connect using a secure connection, so you’ll need to change your settings. Go into the PMS “Settings”, then the “Network” section. Change “Secure connections” to “Preferred” instead of “Required”.

EDIT: I just updated the script to support secure connections. PLEASE NOTE that the path mapping structure has changed as well.

Hi devz3ro,

@devz3ro said:
Does this work with the latest 1.X series Plex Media Server?

EDIT: I’ve figured this out; you either have a PIN enabled on your account, if you have multiple users enabled in your PMS. In that case you’re going to need to update the script to v1.3 and set your Plex account token. Please refer to the first post in this thread (which I’ve edited) for instructions on how to do that.

I’ve updated the script to v1.1 to make the path mapping easier. Now the local paths on the fileserver are mapped to the PMS library/section name, which should be less confusing.

I’ve updated the script to v1.2, which will now use secure/HTTPS connections by default.

thanks it work fine.

Still having problems… copied the file to root and started with

root@DiskStation:~# python3 /root/plex_notify.py

This is the log:

Traceback (most recent call last):
File “/root/plex_notify.py”, line 180, in
response = url_open("%s://%s:%d/library/sections" % (protocol, plex_server_h ost, plex_server_port))
File “/root/plex_notify.py”, line 156, in url_open
return urllib.request.urlopen(req, context=ctx)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 162, in urlopen
return opener.open(url, data, timeout)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 471, in open
response = meth(req, response)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 581, in http_response
‘http’, request, response, code, msg, hdrs)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 509, in error
return self._call_chain(*args)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 443, in _call_chain
result = func(*args)
File “/volume1/@appstore/py3k/usr/local/lib/python3.5/urllib/request.py”, line 589, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 401: Unauthorized

Any idea what I’m doing wrong?

@RSchally said:
urllib.error.HTTPError: HTTP Error 401: Unauthorized
Any idea what I’m doing wrong?

Ok, I’ve figured this out :slight_smile: You either have a PIN set on your Plex account, or you have multiple users set up in your PMS. So the script needs an account token to authenticate with PMS. I’ve updated the script with a new plex_account_token option. You’ll need to update your script, and then follow these instructions to get your account token:
https://support.plex.tv/hc/en-us/articles/204059436-Finding-your-account-token-X-Plex-Token

PERFECT!

Now it’s working as mentioned and I wonna say thank you for the script.

Hello again Talisto,

your script’s working fine so far, thanx again!

Is it possible to update the script avoiding this error:

If you see a bunch of errors that say something like Errno=No space left on device (ENOSPC), then your inotify watcher limit is too low. Run sysctl -n -w fs.inotify.max_user_watches=16384 and then try again. Keep raising the number until the errors go away.

Just put your script on my DS and it’s autostarted after a reboot. Unfortunately after rebooting the script’s not working correctly because of this error.

Hi RSchally,

@RSchally said:
Just put your script on my DS and it’s autostarted after a reboot. Unfortunately after rebooting the script’s not working correctly because of this error.

You can edit /etc/sysctl.conf and add this line:

fs.inotify.max_user_watches=16384

…then it should activate it when your NAS reboots. If you’re still getting the error, raise the 16384 number to something larger; try doubling it to 32768. Note that raising the number will use more RAM.