Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: ["main", "copilot/**"]
pull_request:
branches: ["main"]

jobs:
lint-and-test:
name: Lint & Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
python-version: ["3.11", "3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Lint with ruff
run: ruff check .

- name: Run tests
run: pytest -v
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2024-01-01

### Added

- Initial release of `jellyfin_cleanup` CLI tool.
- Async scraping of the full Jellyfin library with configurable page size and concurrency.
- SQLite cache so subsequent runs can skip re-scraping.
- Bulk deletion via `DELETE /Items?ids=…` with per-item fallback on 404.
- Exponential backoff with jitter for retries on transient errors.
- `--dry-run` mode to preview matches without deleting.
- `--force-rescrape` and `--no-rescrape` flags to control cache behaviour non-interactively.
- `--yes` flag to skip the delete confirmation prompt.
- Support for multiple target path prefixes (positional or `--target-path`).
- `jellyfin-cleanup` console script entry point.
- GitHub Actions CI workflow (lint with ruff + pytest on Python 3.11 and 3.12).
109 changes: 108 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,109 @@
# jellyfinCleaner
A py file to delete bad jellyfin library entries

A command-line tool to find and delete Jellyfin library items by path prefix — useful when you have moved, renamed, or removed drives and need to clean up stale entries that Jellyfin still tracks.

## Features

- **Async scraping** — fetches your entire Jellyfin library in parallel pages and caches results in a local SQLite database.
- **SQLite cache** — avoids re-scraping on every run; prompts you to re-use cached data or refresh it.
- **Bulk deletion** — sends batched `DELETE /Items?ids=…` requests with configurable concurrency and batch size; falls back to per-item deletion on 404 responses.
- **Retry with backoff** — exponential backoff with jitter on transient errors (429 / 5xx / timeouts).
- **Dry-run mode** — preview what would be deleted without touching anything.
- **Resumable** — items that failed to delete are marked `failed` in the DB and will be retried automatically on the next run.

## Requirements

- Python ≥ 3.11
- A Jellyfin server with API access

## Installation

```bash
# From source (recommended)
pip install .

# Or in editable mode for development
pip install -e ".[dev]"
```

This installs the `jellyfin-cleanup` command.

## Quick Start

```bash
# Set your API key once (or pass --api-key on every run)
export JELLYFIN_API_KEY="your_api_key_here"

# Preview items under a path (dry-run, no changes made)
jellyfin-cleanup --dry-run /mnt/old-drive/movies

# Delete items (will prompt for confirmation)
jellyfin-cleanup /mnt/old-drive/movies

# Delete items from multiple paths without confirmation prompts
jellyfin-cleanup --yes /mnt/old-drive/movies /mnt/old-drive/shows

# Run against a remote Jellyfin instance
jellyfin-cleanup --url http://jellyfin.home:8096 --api-key abc123 /mnt/old-drive
```

## Usage

```
usage: jellyfin_cleanup [-h] [--target-path PATH] [--url URL] [--api-key KEY]
[--db FILE] [--page-size N] [--fetch-concurrency N]
[--delete-concurrency N] [--delete-batch-size N]
[--max-retries N] [--retry-backoff-base SECS]
[--retry-backoff-max SECS] [--timeout-connect SECS]
[--timeout-read SECS] [--timeout-write SECS]
[--timeout-pool SECS] [--force-rescrape] [--no-rescrape]
[--yes] [--dry-run] [--verbose]
[PATH ...]
```

### Key options

| Option | Default | Description |
|---|---|---|
| `PATH …` (positional) | — | One or more path prefixes to target |
| `-t`, `--target-path PATH` | — | Path prefix (repeatable, merged with positional) |
| `-u`, `--url URL` | `http://127.0.0.1:8096` | Jellyfin base URL |
| `-k`, `--api-key KEY` | `JELLYFIN_API_KEY` env | Jellyfin API key |
| `--db FILE` | `jellyfin_cleanup.db` | SQLite cache file |
| `--page-size N` | `500` | Items per fetch page |
| `--fetch-concurrency N` | `3` | Parallel page-fetch requests |
| `--delete-concurrency N` | `5` | Parallel bulk-delete requests |
| `--delete-batch-size N` | `50` | Items per bulk-delete API call |
| `--max-retries N` | `5` | Max retries per request |
| `--force-rescrape` | `False` | Re-scrape even if cache exists |
| `--no-rescrape` | `False` | Always use cached data |
| `--yes`, `-y` | `False` | Skip delete confirmation prompt |
| `--dry-run` | `False` | Preview without deleting |
| `--verbose`, `-v` | `False` | Enable DEBUG logging |

## Development

```bash
# Install with dev extras
pip install -e ".[dev]"

# Run tests
pytest -v

# Lint
ruff check .
```

## How It Works

1. **Connectivity check** — verifies the server is reachable and the API key is valid.
2. **Scrape** — pages through `GET /Items?Recursive=true&Fields=Path` and stores every item in a local SQLite database with its `delete_status = 'pending'`.
3. **Target matching** — queries the DB for items whose `path` starts with any of the specified prefixes and whose `delete_status` is `pending` or `failed`.
4. **Preview** — prints a grouped summary of matching items.
5. **Delete** — sends concurrent batched `DELETE /Items?ids=…` requests; records each outcome (`deleted`, `not_found`, or `failed`) back to the DB.
6. **Summary** — prints final DB statistics and warns if any items remain `failed`.

## License

GPL-3.0 — see [LICENSE](LICENSE).

Loading
Loading