plexscripts is a collection of Plex library administration tools consolidated around the plexadm CLI. The tool reads Plex connection details from ~/.plexconfig.ini, connects with plexapi, and performs listing, tagging, collection maintenance, studio updates, writer updates, and batch processing for one Plex library.
Most commands mutate Plex immediately. There is no global dry-run mode yet.
Create ~/.plexconfig.ini with a [default] section:
[default]
plexHost = 192.168.1.10
plexPort = 32400
plexToken = your-token
plexSectionName = Your Library Name
plexSection = optional-section-idYou can use another config file with --config PATH on Plex-backed subcommands.
Install local development dependencies into .venv/:
make installRun the CLI from the repository:
./bin/plexadm --helpInstall the command, shell helpers, and reference data under /usr/local:
sudo make install-systemThat installs:
/usr/local/bin/plexadm/usr/local/bin/plexadm-scripts/*.sh/usr/local/share/plexadm/reference
Build and run the default mass process:
docker compose up --buildThe compose file mounts:
~/.plexconfig.ini:/root/.plexconfig.ini:ro./reference:/app/reference:rw
Override the default command with a shell:
docker compose run --rm plexadm bashRun one command directly:
docker compose run --rm plexadm plexadm list collections
docker compose run --rm plexadm bash scripts/set_tags_based_on_title.shrestart is set to "no".
Normal validation flow:
make lintfix && make lint && make testTargets:
make install: create.venv/and installrequirements-dev.txtmake lintfix: runruff formatandruff check --fixmake lint: run ruff, mypy, shell syntax checks, shellcheck if installed, and hadolint. If hadolint is not installed, it runs via Docker.make test: run pytest with coverage and writecoverage.xmlfor Codecovmake install-system: install command and scripts to/usr/local
The GitHub Actions Docker workflow builds on pull requests and pushes to GHCR on main, version tags, and manual dispatch.
Top-level command groups:
plexadm list ...
plexadm collection ...
plexadm studio ...
plexadm writers ...
plexadm smart-collections ...
plexadm tools ...
plexadm top ...All Plex-backed subcommands support:
--config PATHList all videos:
plexadm list videosFilter videos by title text, prefix, or regex:
plexadm list videos --title "pattern"
plexadm list videos --startswith "Alice"
plexadm list videos --regex "Scene #[0-9]+"Use Plex search by title:
plexadm list videos --search-title "pattern"List videos by collection, studio, writer, or missing studio:
plexadm list videos --collection "00C: Unrated"
plexadm list videos --studio "Studio Name"
plexadm list videos --writer "Writer Name"
plexadm list videos --no-studioFind titles that do not contain the expected - separator:
plexadm list videos --no-title-spacesForce reload while listing all videos:
plexadm list videos --reloadList collections with item counts:
plexadm list collectionsFilter collection titles:
plexadm list collections "01: Category:"
plexadm list collections "03: Star:"List studios with counts:
plexadm list studiosFilter studios:
plexadm list studios "Tushy"List writers with counts across the library:
plexadm list writersList writers appearing in a collection:
plexadm list writers --collection "01: Category: Solo"List writers appearing under a studio:
plexadm list studio-writers "Studio Name"Special list kinds:
uncategorizeduncollectedmultipartmergedpotential-indiemulti-f-without-categoryno-compositionno-hairno-moneyshot
Examples:
plexadm list special uncategorized
plexadm list special no-hair
plexadm list special uncollected
plexadm list special multipartThese commands add or remove collection membership immediately.
Scan every title locally and add matching videos to a collection:
plexadm collection add-title "01: Category: Anal" "Anal"Match title prefix instead of substring:
plexadm collection add-title "02: Independent Content" "Alice" --startswithSkip scene-style titles:
plexadm collection add-title "02: Independent Content" "Alice" --skip-scenesUse Plex search filters to add title matches not already in the collection:
plexadm collection add-search "01: Category: Anal" "Anal"This is what scripts/set_tags_based_on_title.sh uses.
Scan titles for a writer pattern, confirm the Plex writer exactly matches, then add to a collection:
plexadm collection add-writer "01: Category: Solo" "Writer Name"Add all videos matching any writer in a file:
plexadm collection add-writers "01: Category: Solo" reference/writers_solo.txtWriter files are newline-delimited.
Copy membership from one collection to another:
plexadm collection copy "01: Category: Deepthroat" "01: Category: Blowjob"Only videos not already in the target collection are added.
Add all videos from a studio to a collection:
plexadm collection copy-studio "Tushy" "01: Category: Anal"Remove matching videos from a collection:
plexadm collection remove-title "01: Category: Example" "pattern"Add videos shorter than 90 seconds by default:
plexadm collection add-short "01: Category: Short Videos"Set a different duration threshold in milliseconds:
plexadm collection add-short "01: Category: Short Videos" --max-duration-ms 120000Add videos where media height is greater than width:
plexadm collection add-vertical "01: Category: Vertical"Add unrated videos and remove rated videos:
plexadm collection sync-unrated "00C: Unrated"The default collection is 00C: Unrated:
plexadm collection sync-unratedAdd videos with empty studio and remove videos that now have a studio:
plexadm collection sync-no-studio "00A: NO STUDIO2"The default collection is 00A: NO STUDIO2:
plexadm collection sync-no-studioThese commands update Plex studio fields immediately.
Set a studio on title matches that do not already have a studio:
plexadm studio set-title "Studio Name" "pattern"Require the pattern to match an exact Plex writer too:
plexadm studio set-title "Independent Content" "Writer Name" --require-writerSkip scene-style titles:
plexadm studio set-title "Independent Content" "Writer Name" --skip-scenesShortcut for independent content. It requires exact writer match and skips scenes:
plexadm studio set-independent "Writer Name"Use a newline-delimited writer file:
plexadm studio bulk-independent reference/writers_indie.txtRename studio values across matching videos:
plexadm studio rename "Old Studio" "New Studio"Parse writer names from the title prefix before the first - and add missing Plex writers:
plexadm writers set-from-titlesExample title format:
Alice, Bob - Example Title
Set missing writers from titles, then create missing smart collections for studios and writers:
plexadm writers set-and-syncThis is the first step in scripts/mass_process.sh.
Create missing smart collections for all discovered studios and writers:
plexadm smart-collections syncStudio smart collections are named:
02: Studio: Studio Name
Writer smart collections are named:
03: Star: Writer Name
Independent Content is named:
02: Independent Content
Rename collections using a Python regular expression replacement:
plexadm smart-collections rename "^Old Prefix: " "New Prefix: "Show top category collections:
plexadm top categories
plexadm top categories --limit 25Show top studios:
plexadm top studiosShow top writers or scenes missing studios:
plexadm top writers-without-studios
plexadm top scenes-without-studiosShow unrated writer or scene counts:
plexadm top unrated-writers
plexadm top unrated-scenesOverride the collection used by top reports where applicable:
plexadm top unrated-writers --collection "00C: Unrated"Find which Plex item references a file path:
plexadm tools find-missing-file "/path/to/file.mp4"Generate a download scene name:
plexadm tools fix-dl-scene-name "original.mp4"
plexadm tools fix-dl-scene-name "original.mp4" --prefix "Alice"Generate an UltraFilms-style filename:
plexadm tools fix-ultrafilms-name "some_file_name.mp4"Print OFDL name mappings from JSON:
plexadm tools ofdl-gen-names --map-file reference/indie_usernames_to_map.jsonRun rsync:
plexadm tools ofdl-rsync SOURCE DESTINATIONRemove _23fps, _24fps, _25fps, _30fps, _50fps, or _60fps style title suffixes:
plexadm tools remove-fps-title "Video_60fps.mp4"Upload local *.mp4 files to a remote host:
plexadm tools upload-vids
plexadm tools upload-vids --remote-host truenas --upload-path "/mnt/myzmirror/plexdata/NSFW Scenes"The scripts/ directory contains batch wrappers around plexadm.
Important scripts:
scripts/mass_process.sh: full batch processscripts/set_tags_based_on_title.sh: title-search category taggingscripts/set_tags_based_on_writers.sh: writer-file tagging and independent content updatesscripts/copy_collections.sh: collection and studio propagation rulesscripts/set_unrated.sh: unrated collection sync with log outputscripts/top_*.sh: convenience reports
Run the full batch:
bash scripts/mass_process.shreference/ is for local runtime data, writer lists, logs, and notes. Files such as reference/*.txt, reference/*.log, reference/*.md, and reference/*.json are ignored by git.
reference/legacy-python/ contains the archived pre-refactor Python scripts for comparison.