Maintain a Plex friendly directory of artists and albums with symlinks
There's no one-size-fits-all way to organize music. But, Plex expects your music only one way.
With this script you can leave your music files where they are while simultaneously maintaining a directory of symlinks that Plex reads from instead.
A small json file tracks every configured path's mtime. On each run, the script checks the modified timestamp of each configured path and all the subdirs for "Category" paths. When you add or remove an "Artist" or "VA Album" from those paths, the script notices the parent directory's mtime change and queues it for processing.
Unless --force is set, only directories where mtime differs are going to get their contents processed into symlinks.
All broken symlinks found are automatically removed beforehand.
When no changes are detected, the script exits without attempting to process anything further, making it safe to run at a high frequency.
Requires Python 3.11+
# make your config.
cp m4p.config{.example,}.toml
# update your config.
vim m4p.config.tomlusage: musiclinker4plex.py [-h] [-c DIR] [-f] [-j] [-l CSV] [-v] [--allow-root]
Maintain a Plex friendly directory of artists and albums with symlinks
options:
-h, --help show this help message and exit
-c DIR, --confs DIR path to configs
-f, --force clear existing symlinks and recreate all links
-j, --junctions [Windows] use NTFS junctions instead of symlinks
-l CSV, --libs CSV CSV list of libraries to exclusively scan (in order)
-v, --verbose print every action being taken and the results
--allow-root allow running as root/admin
alias m4p="/bin/python3 /usr/local/bin/musiclinker4plex.py"
# a regular scan of all configured libraries
m4p
# on windows, only process two libraries in D:\Configs\m4p.config.toml
# use NTFS Junctions instead of symlinks
m4p --confs "D:\Configs" --libs "library3,library1" --junctions
# as root, read config file at /root/m4p.config.toml
# process all configured libraries and directories
# and overwrite any existing symlinks in the process
m4p -c "/root" -f --allow-root
# example cron to run run every minute.
* * * * * /bin/python3 /usr/local/bin/musiclinker4plex.pym4p.config.toml - What folders to scan and create links from
m4p.cache.libraryname.json - The modified timestamps of all scanned folders per library
- Each
[library]section defines one output directory. - Each library name is case-sensitive.
- Only use absolute paths.
| Setting | Description |
|---|---|
plex_root |
The directory where the symlinks will be created |
ignore * |
List of dir names to skip when scanning |
artist_dirs |
Array of dirs containing an Artist / Album layout |
artist_cat_dirs |
Array of dirs containing a Category / Artist / Album layout |
special_artists * |
Matching dirs within artist_dirs ** become individual artist dirs |
va_album_dirs |
Array of dirs containing Various Artists Album |
va_album_cat_dirs |
Array of dirs containing a Category / Various Artists Album layout |
- * wildcards supported
- ** applies to both artist_dirs and artist_cat_dirs
Each library's plex_root is assumed to never share the same tree of any other library's and is not recommended.
- The script will process the libraries and directories in the configured order (or the order they're specified in when called with
--libs). - If the
plex_rootof multiple libraries share the same tree, the first library will get the symlinks- - -AND, if you specify the force flag in that setup the last library scanned gets the symlinks. fun.
/home/you/MUSIC/
|__ Artists/
| |__ Aaron Funk/
| | |__ Last Step/
| | |__ Venetian Snares/
| |__ Wesley Willis/
|__ Comedy/
| |__ Matt Besser/
| |__ misc/
|__ Games/
| |__ FEZ/
| |__ The Tomorrow Children/
|__ Genres/
| |__ Dabke/
| |__ Omar Souleyman/
|__ Labels/
| |__ Childisc/
| | |__ Childisc Vol. 1/
| |__ misc/
| |__ Planet Mu/
| |__ µ Allstars Criminal/
|__ Movies/
|__ A Clockwork Orange/
|__ Return To Silent Hill/
[mylibrary]
plex_root = "/home/you/MUSIC_Plex"
ignore = ["misc"]
artist_dirs = [
"/home/you/MUSIC/Artists",
"/home/you/MUSIC/Comedy",
]
artist_cat_dirs = ["/home/you/MUSIC/Genres"]
special_artists = ["Aaron Funk"]
va_album_dirs = [
"/home/you/MUSIC/Games",
"/home/you/MUSIC/Movies",
]
va_album_cat_dirs = ["/home/you/MUSIC/Labels"]/home/you/MUSIC_Plex/
|__ Last Step -> ../MUSIC/Artists/Aaron Funk/Last Step
|__ Matt Besser -> ../MUSIC/Comedy/Matt Besser
|__ Omar Souleyman -> ../MUSIC/Genres/Dabke/Omar Souleyman
|__ Venetian Snares -> ../MUSIC/Artists/Aaron Funk/Venetian Snares
|__ Wesley Willis -> ../MUSIC/Artists/Wesley Willis
|__ Various Artists/
|__ A Clockwork Orange -> ../../MUSIC/Movies/A Clockwork Orange
|__ Childisc Vol. 1 -> ../../MUSIC/Labels/Childisc/Childisc Vol. 1
|__ FEZ -> ../../MUSIC/Games/FEZ
|__ Return To Silent Hill -> ../../MUSIC/Movies/Return To Silent Hill
|__ The Tomorrow Children -> ../../MUSIC/Games/The Tomorrow Children
|__ µ Allstars Criminal -> ../../MUSIC/Labels/Planet Mu/µ Allstars Criminal
- Junctions & symlinks are only possible on NTFS within Windows.
- symlinks are the default and are supported in Windows 10+ with Developer Mode enabled.
- You can specify the
--junctionsflag to force the use of NTFS Junctions, which does not require additional permissions. - If you run this script within a WSL environment, the symlinks created are only usable if-
- Plex Media Server runs natively on Windows and has Dev Mode enabled, or
- Plex Media Server also runs from WSL.
- If a soundtrack only has one artist, that's fine. Plex's ability to correctly match the album seems unaffected.
YMMV - The script needs to be run from the same system as Plex.
- It might be worth adding optional remote path links.
- The script assumes all "Artist" and "VA Album" dirs are unique across an entire library.
- If there are duplicates, only the first one scanned will get the symlink created for it (unless
--forceis set). - It might be worth trying to create suffixed (Artist$N) symlinks. Plex's matcher might be cool with it?
- If there are duplicates, only the first one scanned will get the symlink created for it (unless
- Plex integration to queue up a library scan upon changes being made would definitely be nice.
- A clean, dependency free way to do that is a requirement.
- This is made to be multi-platform but has not had much testing. Should definitely add
pytest- Haven't had a chance to test it much on Windows, in particular.