Jellyfin Media Organizer (JMO)
A plan-first Python CLI for organizing media into Jellyfin-friendly layouts. The current implementation is deliberately focused on TV shows while the planning and safety model is built out.
Try JMO in five minutes (without your media)
The safest way to evaluate JMO is with its disposable synthetic demo. It never reads your library, never calls a provider, and never changes files outside the demo directory.
py -m pip install jellyfin-media-organizer
jmo demo
jmo doctor <demo-shows> --destination-root <demo-destination> --output-dir <demo-state> --cache-dir <demo-cache>
jmo inspect <demo-state>
PyPI publication is being staged for the project. Until it is enabled, install
the verified wheel attached to the latest GitHub Release, then run the same
jmo demo workflow. See package publishing for the
maintainer setup and trust boundary.
If you are running from a source checkout, use .venv\Scripts\python.exe and
.venv\Scripts\jmo.exe instead. The demo prints the exact workspace paths and
creates a ready, one-video synthetic plan. A typical summary looks like this:
records=1
matched=1
extra=0
duplicate=0
held=0
suspicious=0
unresolved=0
preflight_ready=true
apply_safe=true
The complete fabricated walkthrough, including duplicate, held, extra, sidecar, and ambiguous examples, is in the synthetic case study. For safe beta testing and feedback, see Community beta.
JMO is not a universal media manager yet. It is a TV-show-focused, plan-first tool for people who want to inspect and review a proposed Jellyfin layout before anything can move.
JMO is intentionally conservative: planning and review remain non-mutating, while jmo apply is an explicitly gated executor for one exact reviewed plan. Apply permits only same-filesystem, atomic, no-overwrite renames for matched and extra operation groups. It never copies across filesystems, overwrites, deletes, quarantines, or moves duplicate/held/ignored records.
Current capabilities
- deterministic parsing of common season/episode, absolute, special/OVA-OAD, and date-based filename patterns;
- read-only inventory scanning for
.mkv,.mp4, and.avifiles; - explicit handling of samples, unreadable entries, blocked links, extras, and ambiguous evidence;
- deterministic inventory reconciliation;
- versioned organizer plan models and immutable JSON schema contracts;
- data-driven aliases and numbering policies;
- persistent TVMaze cache primitives for deterministic/offline replay;
- canonical TVMaze show resolution with fail-closed ambiguity handling;
- a namespaced metadata-provider boundary while TVMaze remains the configured provider;
- end-to-end, show-grouped plan generation with cached provider metadata;
- companion subtitle planning and duplicate-safe operation groups;
- immutable JSON/CSV/text audit bundles with provenance and stable hashes;
- whole-plan preflight that blocks unresolved, colliding, or unsafe plans;
- session-bound, resumable non-mutating review for duplicate groups and held sources;
- exact-hash, revision, and root-bound apply confirmation;
- append-only durable apply journals with group rollback, resume, and verification;
- synthetic regression fixtures for ambiguous and adversarial cases.
jmo plan is operational and remains strictly non-mutating. It inventories one explicit Shows root, resolves each show through the persistent provider cache, constructs destinations, classifies duplicates and companions, runs preflight, and writes an immutable audit bundle. It never moves, copies, renames, overwrites, or deletes media.
For first-time setup, run jmo doctor before planning and jmo inspect after a
run to get a concise status summary. jmo config example and jmo overrides example generate starter files for reusable local configuration. jmo init
creates that state layout in one step, and jmo demo creates a disposable
synthetic workspace for learning the workflow without real media.
jmo review consumes a fresh plan-schema-v3 manifest and records explicit review decisions into a resumable session plus a new reviewed override contract. Review can resolve duplicate decisions and held sources as provider-confirmed episodes/specials or explicit extras, but it is also strictly non-mutating: quarantine choices are markers only, and review never moves, deletes, or quarantines media. See the non-mutating review workflow before using it.
jmo apply consumes the already-reviewed plan.json, preflight.json, and run-provenance.json. It does not rerun parsing or make review decisions. Start with --check-only; an actual run additionally requires an external journal and the exact confirmation token bound to the plan hash, review-session hash, clean source revision, source root, and destination root. See the apply safety contract.
Requirements
- Python 3.12+
- CI is configured for Linux on Python 3.12, 3.13, and 3.14, plus Windows and macOS on Python 3.12. Installed wheel/sdist workflows run on all three operating systems; consult the CI results for the revision you install.
- Project metadata advertises Python 3.12, 3.13, and 3.14 support.
The runtime package currently has no third-party dependencies.
Repository identity
The standalone project is maintained in the jellyfin-media-organizer repository, matching the installable project name. This repository name is the intentional long-term identity for JMO rather than a temporary fork-era name.
Maintained project metadata points to this repository and its issue tracker. Packaging or documentation changes should not reintroduce obsolete upstream package/repository URLs as JMO's own project metadata; historical upstream credit remains in the acknowledgments and license sections below.
Install
From a source checkout:
git clone https://github.com/akjroller/jellyfin-media-organizer.git
cd jellyfin-media-organizer
python -m venv .venv
./.venv/bin/python -m pip install .
./.venv/bin/jmo --version
./.venv/bin/jmo plan --help
Windows PowerShell does not require virtual-environment activation or an execution-policy change:
git clone https://github.com/akjroller/jellyfin-media-organizer.git
cd jellyfin-media-organizer
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install .
.\.venv\Scripts\jmo.exe --version
.\.venv\Scripts\jmo.exe plan --help
A minimal planning run uses separate existing source and destination roots, plus generated-state locations outside both media roots:
./.venv/bin/jmo plan ExampleMedia/Shows \
--destination-root ExampleMedia/OrganizedShows \
--output-dir LocalState/audit-001 \
--cache-dir LocalState/cache
In PowerShell, use .\.venv\Scripts\jmo.exe instead of ./.venv/bin/jmo and put the arguments on one line (Bash backslashes are not PowerShell continuations).
For a complete first-time sequence, see First run. Other documentation uses jmo as shorthand for the executable in your environment; activation is optional.
Normal wheels and source distributions carry a file-verified build revision. They do not need a Git checkout at runtime. Builds made from dirty checkouts remain ineligible for apply, and altered or unstamped installations fail closed. Reinstall after pulling changes: pip install . installs a snapshot, not a live view of the checkout. Build metadata verifies integrity, not publisher authenticity; install artifacts only from a trusted source.
Review plan.json, plan.sha256, decision.sha256, run-provenance.json, preflight.json, preflight.txt, and the CSV reports in the output directory. A ready plan exits 0; configuration, provider, unresolved, and preflight failures use distinct nonzero exit codes.
Use --offline for a hard zero-provider-call replay from a warmed cache and --refresh for a deliberate refresh. Local override files are passed explicitly with --overrides. --json emits the versioned machine-readable summary; --verbose opts into additional local diagnostic detail.
The historical organizer command remains available as a compatibility alias:
organizer --version
organizer plan --help
You can also run the package directly:
python -m jellyfin_show_organizer --version
python -m jellyfin_show_organizer plan --help
Embedding applications can import the deliberately small read-only API:
from jellyfin_show_organizer.api import inspect_audit, plan_library
The API exposes planning and audit inspection only. It does not bypass review, preflight, approval, journaling, or rollback requirements.
Safety boundary
The current implementation is Shows-only. Do not point it at a Movies directory, a mixed media root, or a parent directory containing unrelated media.
Planning, review, preflight, and jmo apply --check-only are read-only with respect to media. A successful plan or completed review is not authorization to mutate files. Mutation requires the explicit apply subcommand, three exact approval values, a root-bound confirmation token, a journal outside the media roots, and successful live revalidation.
Apply eligibility is status-based. Only matched and extra video records plus their associated companions may move. A duplicate loser can retain the collided destination as audit evidence, so implementations and operators must never interpret a non-null destination as movement eligibility.
Repository examples and tests use synthetic paths and fixtures. Real library inventories, provider caches, manifests, reports, media files, deployment-specific overrides, machine-specific paths, and other environment-specific data should remain local and untracked.
Documentation
- Getting started
- Operational runbook
- Non-mutating review workflow
- Apply safety contract and runbook
- Troubleshooting safely
- Contributor workflow
- Architecture
- Plan-only release-candidate validation
- Local overrides
- Provider cache and offline policy
- Numbering policies
- Metadata-provider boundary
- Release and versioning policy
- Security policy
- Changelog
- Synthetic case study
- Community beta guide
- Package publishing
- Upstream acknowledgments
Development
The quickest complete local validation is:
python tools/run_checks.py
It runs the same constraints, lint, format, type, test, and repository-safety gates used by CI. Use the individual commands below when iterating on one check.
python -m pip install -e ".[dev]"
python tools/check_ci_constraints.py
python -m ruff check jellyfin_show_organizer tests tools
python -m ruff format --check jellyfin_show_organizer tests tools
python -m mypy jellyfin_show_organizer tests
python -m pytest
python tools/check_repository_safety.py
Public regression tests use fabricated data and offline provider fixtures. See the contributor workflow before adding parser, matcher, numbering, destination, sidecar, override, or provider behavior.
Project layout
jellyfin_show_organizer/ core application package
data/ versioned schemas and generic packaged defaults
tests/
fixtures/ synthetic deterministic fixtures
local/ offline test suite
docs/ architecture and operating guidance
Releases
JMO uses Semantic Versioning. Pull-request CI builds and verifies both wheel and source-distribution installs in isolated environments. Verified artifacts can be built by the deliberate release-artifact workflow or a matching version tag; the repository does not automatically publish packages to a package registry.
The v0.1.0 version tag was the first verified release. The v0.1.1 patch release added the reviewed-duplicate winner fix, v0.2.0 added onboarding, review-status, reporting, and contributor-workflow foundations, v0.3.0 added guided review workflows and quarantine hardening, and v0.3.1 adds the protected PyPI publication path. A GitHub Release is published separately from the tag and does not itself approve or authorize any media run. The presence of jmo apply in source remains subject to the explicit apply safety contract.
See the release policy for the version source of truth, supported runtime matrix, tag rules, artifact verification process, and privacy boundary.
Project history and credit
JMO began as a fork of jkwill87/mnamer, created and maintained by Jessy Williams. That MIT-licensed project provided the original media-organizing groundwork from which this project started.
JMO has since diverged into its own Jellyfin-focused, plan-first design. The upstream project and its maintainers are not responsible for JMO's current behavior or support. See ACKNOWLEDGMENTS.md for the retained attribution.
License
MIT. The original upstream copyright and permission notice is retained in LICENSE.txt.
Release files for jellyfin-media-organizer 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jellyfin_media_organizer-0.3.1.tar.gz | 218.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jellyfin_media_organizer-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 473.7 kB
Release files / jellyfin_media_organizer-0.3.1.tar.gz
| Download URL | jellyfin_media_organizer-0.3.1.tar.gz |
|---|---|
| Size | 218.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4e98bff2fc0f5ca297282c284fc73ebb1d8ae69759fdb1e1eb68de6b1bc9120e
|
|
BLAKE2b-256 checksum How to use checksums |
0d11f12ff273267c53e37967e8e0aa81a6a01cc10ff3c0b2b51a5888c12eb010
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / jellyfin_media_organizer-0.3.1-py3-none-any.whl
| Download URL | jellyfin_media_organizer-0.3.1-py3-none-any.whl |
|---|---|
| Size | 255.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e2b122407f4f7d341cad342aa42636c23db3af6c276894f0fde441e2680f6fc2
|
|
BLAKE2b-256 checksum How to use checksums |
6dc3b79441d6e7aaa936f1e42d731670aedc0d4266edfc4b8fa116edcf19a3f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log