Syncing Plex Watch State to Jellyfin
I have been using Plex to manage and serve the media on my homelab for the last few years. After recent worrying trends have started to emerge on Plex I have started exploring a migration to Jellyfin instead.
In this post I'll detail how I am syncing my watch state between both Plex and Jellyfin as I decide which one to stick with.
Plex Services
I currently use Plex for management of my media, which includes:
- Movies
- TV
- Music - specifically Plexamp
- Audiobooks - utilising audNexus as my metadata provider
- YouTube
In addition to Plex, I setup Tautulli for tracking server usage and Overseerr for managing media requests.
Configuration for all of this is managed by NixOS - as seen here
Jellyfin Configuration
I run a much more simple setup when it comes to Jellyfin, with it managing:
- Movies
- TV
- Music - I will look to switch to a different solution if this proves too basic
- YouTube
I manage audiobooks via Audiobookshelf as a dedicated service, as it does a much better job than Plex at managing audiobook librarires.
My Jellyfin configuration is also managed by NixOS and is available here
Note: the Jellyfin setup is a bit more involved when it comes to hardware transcoding. I have included a few useful notes in my linked config with helpful resources.
Syncing Watch Status
After doing some initial research I came across a project for this purpose aptly named watchstate.
Watchstatus Configuration
This was pretty easy to setup via docker-compose
. I followed the instructions on GitHub and first created a
docker-compose.yml
:
services:
watchstate:
image: ghcr.io/arabcoders/watchstate:latest
network_mode: "host" # enable connection to host vpn
user: "1000:1000"
container_name: watchstate
restart: unless-stopped
environment:
- WS_TZ=Australia/Melbourne
ports:
- "8001:8080" # avoid clashing with other service on 8080
volumes:
- ./data:/config:rw # mount current directory to container /config directory.
Following this I created a data
folder (passed as a volume to container) in the same
directory with: mkdir -p data
Finally I started the container by running:
# install docker-compose for the current shell in NixOS
nix shell nixpkgs#docker-compose
# start the container in the background
sudo docker-compose up -d
Adding Backend
With the container running I was then able to add a new backend. This refers to setting up the connection to a media service such as Plex or Jellyfin.
This was done by running the following commands and completing the prompts:
docker exec -ti watchstate console config:add
You will need to extract a Plex token and retrieve you Jellyfin API as part of this setup: Go to Dashboard > Advanced > API keys > then create new api keys
The only issue I ran into was due to me being unfamiliar with the terminology.
I wanted to sync my watch status from Plex to Jellyfin. According to Watchstate
terminology this means I want to import
my Plex watch state to the database
and export
the database state into Jellyfin.
Once I understood this I was able to setup backends for both Plex and Jellyfin.
Syncing Backends
With both backends configured I first exported my Plex state:
# I named my plex backend plex_media
sudo docker exec -ti watchstate console state:import -v -s plex_media
Then finally I export the database state to Jellyfin, following initial instructions to forcibly sync the initial export:
# I named my jellyfin backend jellyfin_media
sudo docker exec -ti watchstate console state:import -vvifs jellyfin_media
Following this I checked Jellyfin and sure enough, everything was in sync!
Scheduling Regular Sync
The project mentioned the option of setting up cron jobs for regular syncing ongoing. I chose not to implement this, as I am happy to manage this manually myself. I'll evaluate Jellyfin as a replacement to Plex and stick with one or the other.
Should I decide to run this services long-term then I'd create a NixOS module for it.
If you decide to schedule syncing its just a matter of setting the following
environment variable in docker-compose.yml
: WS_CRON_IMPORT=1
and WS_CRON_EXPORT=1