A zero-dependency Python script that reorganises a flat folder of ebook files into the directory structure expected by Audiobookshelf.
Before After
────────────────────────────────── ──────────────────────────────────────────────────────
Adams, Douglas - Hitchhiker 01 - Douglas Adams/
The Hitchhiker's Guide to the Hitchhiker/
Galaxy.mobi The Hitchhiker's Guide to the Galaxy/
Adams, Douglas - Hitchhiker 02 - The Hitchhiker's Guide to the Galaxy.mobi
The Restaurant at the End of Douglas Adams/
the Universe.mobi Hitchhiker/
Atwood, Margaret - The Restaurant at the End of the Universe/
The Handmaid's Tale.mobi The Restaurant at the End of the Universe.mobi
A Game of Thrones - Book 1.epub Margaret Atwood/
A Clash of Kings - Book 2.epub The Handmaid's Tale/
... The Handmaid's Tale.mobi
George R.R. Martin/
A Song of Ice and Fire/
A Game of Thrones/
A Game of Thrones.epub
A Clash of Kings/
A Clash of Kings.epub
- Seven filename parsers handle the most common naming conventions found in the wild (see Supported formats below).
- EPUB metadata fallback — files with unrecognisable names (e.g. hash-only
filenames like
23d489d6b8b8f6fe54f886ff15f21136) are identified by reading the embeddeddc:creator/dc:titlefrom the OPF metadata inside the EPUB. - Series detection — series names are extracted from the filename and become
an intermediate folder level (
Author/Series/Title/file). - Conflict resolution — if two books would land in the same destination path
the second one is renamed
Title_2,Title_3, etc. - Dry-run mode — preview every planned move without touching any files.
- Zero dependencies — standard library only; requires Python 3.10+.
- Existing author sub-folders in the source are handled correctly.
- Python 3.10 or later (uses
str | Noneunion syntax) - No third-party packages required
git clone https://github.com/<you>/book-organizer.git
cd book-organizer
No virtual environment or pip install needed.
Copy .env.example to .env and fill in your paths, then source it before
running:
cp .env.example .env
# edit .env, then:
source .env
python3 organize_books.py
| Variable | Description |
|---|---|
BOOKS_SOURCE |
Required. Path to the existing flat ebook directory. |
BOOKS_DEST |
Output directory. Defaults to <source>_organized if not set. |
python3 organize_books.py --source DIR [--dest DIR] [--in-place] [--dry-run]
| Flag | Description |
|---|---|
--source DIR |
Flat ebook source directory. Overrides $BOOKS_SOURCE. |
--dest DIR |
Output directory. Overrides $BOOKS_DEST. Ignored with --in-place. |
--in-place |
Reorganise directly inside the source folder. |
--dry-run |
Print what would happen without moving any files. |
python3 organize_books.py --source /media/Books --dry-run
This prints every planned move and writes a full log to organize_books.log —
no files are touched.
python3 organize_books.py --source /media/Books --dest /media/Books_organized
The original files stay in place until you delete them manually, giving you a chance to verify the organised output first.
python3 organize_books.py --source /media/Books --in-place
Files are moved inside the source folder and empty leftover directories are removed afterwards.
| Pattern | Example input | Resolved as |
|---|---|---|
Lastname, First - Series ## - Title |
Adams, Douglas - Hitchhiker 01 - The Hitchhiker's Guide to the Galaxy.mobi |
Douglas Adams / Hitchhiker / The Hitchhiker's Guide… |
Lastname, First - Title |
Atwood, Margaret - The Handmaid's Tale.mobi |
Margaret Atwood / The Handmaid's Tale |
Name - Series ## - Title |
C.S. Lewis - The Chronicles of Narnia 01 - The Lion, the Witch and the Wardrobe.mobi |
C.S. Lewis / The Chronicles of Narnia / The Lion… |
Name - Title |
Aesop - Aesop's Fables.mobi |
Aesop / Aesop's Fables |
##. TITLE by Author |
04. WHERE THE CRAWDADS SING by Delia Owens.epub |
Delia Owens / Where the Crawdads Sing |
##_Title_words (year) |
03_Blood_of_Elves (1994).epub |
Andrzej Sapkowski / The Witcher / Blood of Elves ¹ |
Title - Book N |
A Game of Thrones - Book 1.epub |
George R.R. Martin / A Song of Ice and Fire / A Game of Thrones ¹ |
[Author_Name]_Title_words |
[Shanna_Katz]_Some_Title.epub |
Shanna Katz / Some Title |
| Hash / no-metadata filename | 23d489d6b8b8f6fe54f886ff15f21136 (EPUB) |
Reads embedded OPF metadata |
| Existing author sub-folder | Charles Dickens/A Christmas Carol.pdf |
Charles Dickens / A Christmas Carol |
¹ Requires the title to appear in the KNOWN_BOOKS dictionary (see below).
Open organize_books.py and add entries to the KNOWN_BOOKS dictionary near
the top:
KNOWN_BOOKS: dict[str, tuple[str, str | None]] = {
# "Exact Title as it appears in filename": ("Author Name", "Series Name or None"),
"The Way of Kings": ("Brandon Sanderson", "The Stormlight Archive"),
"Words of Radiance": ("Brandon Sanderson", "The Stormlight Archive"),
}
The lookup is case-insensitive and strips trailing year annotations like (1994).
<dest>/
├── Author Name/
│ ├── Book Title/
│ │ └── Book Title.epub
│ └── Series Name/
│ ├── Book Title/
│ │ └── Book Title.mobi
│ └── Another Title/
│ └── Another Title.mobi
└── Unknown Author/
└── Unrecognised Title/
└── Unrecognised Title.pdf
Books whose author cannot be determined from the filename or EPUB metadata are
placed under Unknown Author/ so they are still accessible in Audiobookshelf.
Every run appends to organize_books.log in the current working directory.
Lines are prefixed with INFO, WARNING (skipped files), or ERROR (failed
moves).
.epub .mobi .azw .azw3 .pdf .prc .fb2 .djvu .cbz .cbr .lit
Extension matching is case-insensitive (.MOBI is handled the same as .mobi).
Extension-less EPUB files (detected by ZIP magic bytes) are also recognised.
MIT