Metadata-Version: 2.4
Name: kurmann-plex-downloader
Version: 2.4.0
Summary: CLI Tool zum Downloaden von Original-Dateien aus Plex
Project-URL: Homepage, https://github.com/kurmann/plex-downloader
Project-URL: Repository, https://github.com/kurmann/plex-downloader
Project-URL: Changelog, https://github.com/kurmann/plex-downloader/blob/main/CHANGELOG.md
License: MIT License
        
        Copyright (c) 2026 Patrick Kurmann
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: plexapi>=4.15
Requires-Dist: requests>=2.31
Requires-Dist: rich>=13.0
Requires-Dist: tomli-w>=1.0
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest-mock>=3.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# Plex Downloader CLI

Ein modernes Kommandozeilen-Tool (CLI), um Filme und TV Shows von einem Plex-Server in **Originalqualität** (Direct Stream) herunterzuladen.

Entwickelt von Patrick Kurmann mit Python, [Typer](https://typer.tiangolo.com/) und [Rich](https://rich.readthedocs.io/).

## Features

* **Neueste Inhalte:** Zeige die 12 neuesten Filme oder TV-Serien an, die deiner Plex-Bibliothek hinzugefügt wurden.
* **Interaktive Suche:** Suche blitzschnell nach Filmen und TV Shows in deinen Plex-Bibliotheken.
* **Unattended Filmsuche:** Suche per Titel nach Filmen und erhalte strukturierte Treffer mit Rating-Key, Titel und Jahr – ohne interaktive Rückfragen. Mit `--json` als vollständige maschinenlesbare JSON-Ausgabe inklusive Edition, Dateigrösse, Auflösung und externen IDs.
* **Externe IDs optional anzeigen:** Verfügbare TMDB-, IMDB- und TVDB-IDs lassen sich bei Bedarf mit `--show-ids` in Such- und Auswahlansichten einblenden.
* **Deterministischer Film-Download:** Lade einen Film direkt per eindeutigem Plex-Rating-Key herunter – deterministisch und skriptbar. Kein Suche-Schritt, kein Zufall.
* **Unattended Film-Download:** Lade einen Film direkt per Titel herunter, ohne interaktive Rückfragen – skriptbar und automatisierbar. Unterstützt auch geplante Nacht-Downloads via `--night`.
* **Optionale externe ID im Dateinamen:** Film-Downloads können per `--append-id tmdb|imdb|tvdb` eine vorhandene externe ID als Suffix wie ` [tmdb-27205]` an den Dateinamen anhängen.
* **Flexibles Verzeichnis-Layout:** Standardmäßig werden Filme direkt im Download-Verzeichnis abgelegt (flaches Layout). Mit `--movie-subdir` oder der Config-Einstellung `create_movie_subdir` lässt sich das klassische Emby-Unterverzeichnis (`{Titel} ({Jahr})/Datei`) aktivieren.
* **TV Show Support:** Lade ganze Serien, einzelne Episoden oder alle Episoden ab einer bestimmten Episode bis zum Ende der Staffel herunter.
* **Geplanter Download:** Plane Downloads für 2 Uhr morgens direkt im interaktiven Ablauf.
* **Medienserver-Integration:** Automatisches Verschieben von Downloads zum Medienserver (lokal oder per rclone zu NAS/Cloud).
* **Originalqualität:** Lädt die rohe Videodatei (z. B. MKV, MP4) herunter, ohne Transcodierung oder Qualitätsverlust.
* **Optional ohne eingebettetes Artwork:** Entfernt auf Wunsch eingebettete Vorschaubilder/Cover-Art aus Filmdateien (z. B. MKV, MP4, M4V) nach dem Download.
* **Schicke UI:** Fortschrittsbalken, farbige Ausgaben und formatierte Tabellen.
* **Sicherer Login:** Verbindet sich mit deinen Plex-Zugangsdaten und nutzt Tokens zur Authentifizierung.
* **Konfigurierbar:** Speichert deine Einstellungen (Server, Token, Pfad) lokal ab.

## Installation

### Option 1: Installation via PyPI (Empfohlen)

```bash
pipx install kurmann-plex-downloader
```

Oder mit pip in einer virtuellen Umgebung:

```bash
pip install kurmann-plex-downloader
```

### Option 2: Installation via pipx aus GitHub

```bash
pipx install git+https://github.com/kurmann/plex-downloader.git
```

### Option 3: Lokale Entwicklung

Wenn du den Code verändern oder erweitern möchtest:

1. Repository klonen:
```bash
git clone https://github.com/kurmann/plex-downloader.git
cd plex-downloader
```

2. Abhängigkeiten installieren (empfohlen: [uv](https://docs.astral.sh/uv/)):
```bash
uv sync
```

Oder mit pip:
```bash
pip install -e ".[dev]"
```

## Benutzung

Sobald das Tool installiert ist, steht dir der Befehl `plex-dl` systemweit zur Verfügung.

### 1. Ersteinrichtung (Konfiguration)

Bevor du starten kannst, musst du dich einmalig einloggen und den gewünschten Server auswählen.

```bash
plex-dl init
```

*Folge den Anweisungen im Terminal, um dich bei plex.tv einzuloggen und deinen Server zu wählen.*

> **Hinweis:** `init` ist für die **Ersteinrichtung** gedacht. Um einzelne Einstellungen (Server, Download-Pfad) anzupassen, nutze `config set`:

```bash
plex-dl config set server_name "Neuer Server"
plex-dl config set download_path ~/Downloads
plex-dl config set remove_embedded_artwork_movies true
```

### 2. Interaktiven Auswahl- und Download-Flow starten

Starte den interaktiven Flow explizit mit dem `select`-Unterbefehl:

```bash
plex-dl select
```

Optional mit externen IDs in der Anzeige (neu in 2.4.0):

```bash
plex-dl select --show-ids
```

Im Hauptmenü kannst du:
- **Option 1**: Nach Filmen oder TV-Serien suchen
- **Option 2**: Die 12 neuesten Filme anzeigen
- **Option 3**: Die 12 neuesten TV-Serien anzeigen

Nach der Anzeige der Liste kannst du einen Film oder eine Serie auswählen, in die Warteschlange legen und herunterladen.

> **Hinweis:** `plex-dl` ohne Unterbefehl zeigt nur die Hilfe an. Die interaktive Sitzung muss explizit mit `plex-dl select` gestartet werden.

### 3. Unattended Film-Download (neu in 2.1.0)

Der Befehl `movie download-first` lädt das erste Suchresultat für einen Filmtitel direkt herunter – ohne interaktive Rückfragen. Er eignet sich ideal für Skripte und automatisierte Abläufe.

```bash
plex-dl movie download-first "Inception"
```

**Verhalten:**
1. Verbindet sich mit dem konfigurierten Plex-Server.
2. Sucht in den Film-Bibliotheken nach dem angegebenen Titel.
3. Wählt das **erste** Suchresultat aus.
4. Lädt den Film herunter.
5. Existierende Dateien werden stillschweigend übersprungen (kein Prompt).

**Ausgabe:**
- Ergebnisdaten auf `stdout`, z. B.: `downloaded: Inception (2010)`
- Fehler- und Diagnosemeldungen auf `stderr`

**Exit-Codes:**
| Code | Bedeutung |
|------|-----------|
| `0`  | Download erfolgreich |
| `1`  | Kein Treffer oder Download fehlgeschlagen |
| `2`  | Fehlende oder ungültige Konfiguration |

**Optionen:**
```bash
# Anderen Download-Pfad angeben
plex-dl movie download-first "The Matrix" --download-path ~/Downloads

# Download auf 2 Uhr morgens verschieben (unattended, kein Prompt)
plex-dl movie download-first "War Machine" --night

# Eingebettete Vorschaubilder/Cover-Art nach dem Download entfernen
plex-dl movie download-first "Skyfall" --remove-embedded-artwork

# Vorhandene TMDB-ID als Suffix an den Dateinamen anhängen
plex-dl movie download-first "Inception" --append-id tmdb

# Unterverzeichnis nach Emby-Konvention erstellen ({Titel} ({Jahr})/Datei)
plex-dl movie download-first "Inception" --movie-subdir

# Hilfe anzeigen
plex-dl movie download-first --help
```

**Nachtmodus (`--night`):**

Mit dem Flag `--night` wird der Download bis 2 Uhr morgens verzögert – vollständig unattended und ohne interaktive Rückfragen. Die Statusmeldungen erscheinen auf `stderr`:

```
Geplanter Nacht-Download um 2:00 Uhr
Aktuelle Zeit: 22:15:00
Zielzeit: 14.03.2026 02:00:00
Wartezeit: 3 Stunden, 44 Minuten
Warte bis 2 Uhr morgens … (Ctrl+C zum Abbrechen)
```

> **Hinweis:** `download-first` ist eine Convenience-Funktion und wählt bewusst das erste Suchresultat. Für deterministischen Download per eindeutiger ID steht `movie download --rating-key <id>` zur Verfügung (neu in 2.3.0).
>
> **Hinweis:** Für `--remove-embedded-artwork` werden lokal verfügbare `ffprobe`/`ffmpeg`-Tools verwendet. Ohne diese Tools bleibt die heruntergeladene Datei unverändert.

### 4. Unattended Filmsuche (neu in 2.2.0)

Der Befehl `movie search` sucht nach Filmen in den Plex-Bibliotheken und zeigt die Treffer als Tabelle an – ohne Download. Er eignet sich ideal als Grundlage für spätere Skripte, die gezielt per Rating-Key herunterladen wollen.

```bash
plex-dl movie search "Dune"
```

**Erwartete Ausgabe:**

```
                 Suchergebnisse für 'Dune'
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓
┃ Rating-Key ┃ Titel              ┃ Jahr ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━┩
│ 123456     │ Dune               │ 2021 │
│ 123789     │ Dune: Part Two     │ 2024 │
│ 124000     │ Dune               │ 1984 │
└────────────┴────────────────────┴──────┘
```

**Verhalten:**
1. Verbindet sich mit dem konfigurierten Plex-Server.
2. Sucht in den Film-Bibliotheken nach dem angegebenen Titel.
3. Zeigt alle Treffer als Tabelle an – **ohne Download**.
4. Der Rating-Key kann für deterministische Downloads per `movie download --rating-key` verwendet werden (neu in 2.3.0).

**Ausgabe:**
- Ergebnistabelle auf `stdout`
- Fehler- und Diagnosemeldungen auf `stderr`

**Exit-Codes:**
| Code | Bedeutung |
|------|-----------|
| `0`  | Suche erfolgreich (auch bei 0 Treffern) |
| `1`  | Allgemeiner Fehler (z. B. Verbindungsfehler) |
| `2`  | Fehlende oder ungültige Konfiguration |

**Optionen:**
```bash
# Anzahl Treffer begrenzen
plex-dl movie search "Dune" --limit 5

# Maschinenlesbare JSON-Ausgabe (neu in 2.3.0)
plex-dl movie search "Rocky" --json

# Verfügbare externe IDs in der Trefferliste anzeigen (neu in 2.4.0)
plex-dl movie search "Inception" --show-ids

# Hilfe anzeigen
plex-dl movie search --help
```

#### JSON-Ausgabe mit `--json` (neu in 2.3.0)

Mit dem Flag `--json` liefert `movie search` eine vollständige, maschinenlesbare JSON-Ausgabe auf `stdout`. Ideal für Shell-Skripte und Automatisierungen.

```bash
plex-dl movie search "Rocky" --json
```

**Beispiel-Ausgabe:**

```json
{
  "query": "Rocky",
  "success": true,
  "message": "2 Treffer für 'Rocky'.",
  "total": 2,
  "items": [
    {
      "rating_key": "35502",
      "title": "Rocky",
      "year": 1976,
      "media_type": "movie",
      "edition": null,
      "file_size_bytes": 54858234880,
      "resolution": "1080",
      "container": "mkv",
      "external_ids": {
        "tmdb": "1366",
        "imdb": "tt0075148"
      }
    },
    {
      "rating_key": "35501",
      "title": "Rocky II",
      "year": 1979,
      "media_type": "movie",
      "edition": "Director's Cut",
      "file_size_bytes": 54858234880,
      "resolution": "1080",
      "container": "mkv",
      "external_ids": {}
    }
  ]
}
```

Fehlen Felder wie Edition oder Dateigrösse in Plex, werden sie konsistent als `null` zurückgegeben.

### 5. Deterministischer Film-Download per Rating-Key (neu in 2.3.0)

Der Befehl `movie download --rating-key` lädt einen Film anhand seines eindeutigen Plex-Rating-Keys herunter. Im Gegensatz zu `download-first` wird kein Suche-Schritt durchgeführt – der Download ist deterministisch und wiederholbar.

Der Rating-Key ist in den Suchergebnissen von `movie search` sichtbar.

```bash
plex-dl movie download --rating-key 35502
```

Mit `--night` wird der Download bis 2 Uhr morgens verzögert – ohne interaktive Bestätigung:

```bash
plex-dl movie download --rating-key 35502 --night
```

Mit `--append-id tmdb|imdb|tvdb` wird – falls vorhanden – genau eine externe ID als Suffix im Format ` [tmdb-27205]` an den finalen Dateinamen angehängt:

```bash
plex-dl movie download --rating-key 35502 --append-id tmdb
```

**Typischer Workflow:**

```bash
# 1. Film suchen und Rating-Key ermitteln
plex-dl movie search "Rocky"

# Ausgabe:
#  Rating-Key │ Titel   │ Jahr
#  35502      │ Rocky   │ 1976

# 2. Film gezielt herunterladen
plex-dl movie download --rating-key 35502

# 3. Oder als Nacht-Download planen
plex-dl movie download --rating-key 35502 --night
```

**Verhalten:**
1. Verbindet sich mit dem konfigurierten Plex-Server.
2. Löst das Filmobjekt anhand des Rating-Keys auf.
3. Validiert, dass es sich um einen Film handelt (kein TV-Show, Episode o. ä.).
4. Wenn `--night` gesetzt: wartet unattended bis 2 Uhr morgens, dann Download.
5. Lädt den Film deterministisch herunter.
6. Existierende Dateien werden stillschweigend übersprungen (kein Prompt).

**Ausgabe:**
- Ergebnisdaten auf `stdout`, z. B.: `downloaded: Rocky (1976)`
- Fehler- und Diagnosemeldungen auf `stderr`

**Exit-Codes:**
| Code | Bedeutung |
|------|-----------|
| `0`  | Download erfolgreich |
| `1`  | Rating-Key nicht gefunden, falscher Medientyp oder Download fehlgeschlagen |
| `2`  | Fehlende oder ungültige Konfiguration |

**Optionen:**
```bash
# Anderen Download-Pfad angeben
plex-dl movie download --rating-key 35502 --download-path ~/Downloads

# Nacht-Download planen
plex-dl movie download --rating-key 35502 --night

# Vorhandene IMDB-ID an den Dateinamen anhängen
plex-dl movie download --rating-key 35502 --append-id imdb

# Unterverzeichnis nach Emby-Konvention erstellen ({Titel} ({Jahr})/Datei)
plex-dl movie download --rating-key 35502 --movie-subdir

# Hilfe anzeigen
plex-dl movie download --help
```

### 6. Suchen & Herunterladen (interaktiv)

Starte `plex-dl select` und wähle im Hauptmenü **Option 1**, dann gib einen Film- oder Serientitel ein. Das Tool zeigt dir alle Treffer an und lässt dich auswählen, welchen du laden möchtest.

Für TV Shows wirst du gefragt, ob du die ganze Serie, nur eine bestimmte Episode oder alle Episoden ab einer bestimmten Episode bis zum Ende der Staffel herunterladen möchtest.

#### Geplanter Download (Nachtmodus)

Nach der Auswahl des gewünschten Inhalts wirst du interaktiv gefragt, ob der Download sofort oder um 2 Uhr morgens starten soll:

```
Wann möchtest du den Download starten?
1. Jetzt sofort
2. Um 2:00 Uhr morgens
```

Im Nachtmodus (Option 2):
- Wartet die Anwendung aktiv bis 2 Uhr morgens
- Zeigt die verbleibende Wartezeit alle 60 Sekunden an
- Führt den Download automatisch um 2 Uhr aus

**Beispiel:**
```bash
# Interaktiven Flow starten und im Menü suchen
plex-dl select

# Nach der Auswahl des Inhalts wirst du gefragt:
# "Wann möchtest du den Download starten?"
# 1. Jetzt sofort
# 2. Um 2:00 Uhr morgens
```

### 4. Medienserver-Integration

Bei der Erstkonfiguration (`plex-dl init`) kannst du ein oder mehrere Download-Ziele konfigurieren. Nach jedem erfolgreichen Download werden die Dateien automatisch in das gewählte Ziel verschoben.

**Beispiele:**
- Lokaler Pfad: `/mnt/media` oder `~/Media`
- rclone Remote: `mynas:media/plex` (für NAS oder Cloud-Speicher)

**Vorteile:**
- Automatische Organisation der Medienbibliothek
- Bei Serien wird jede Episode sofort nach Download verschoben → Platz wird für die nächste Episode frei
- Unterstützt sowohl lokale als auch Remote-Ziele via rclone

**Hinweis:** Falls rclone nicht installiert ist, erfolgt bei lokalen Pfaden ein automatischer Fallback auf Python's Standardmethoden.

### Konfiguration

Die Konfigurationsdatei wird standardmäßig hier gespeichert:
`~/.config/plex-downloader/config.toml`

Konfigurationswerte werden über dedizierte Unterbefehle gelesen und geschrieben:

```bash
# Wert setzen
plex-dl config set token <dein-plex-token>
plex-dl config set server_name "Mein Plex Server"
plex-dl config set download_path ~/Downloads/plex

# Einzelnen Wert abfragen
plex-dl config get server_name

# Alle Werte anzeigen
plex-dl config list
```

**Unterstützte Konfigurationsschlüssel:**

| Schlüssel                       | Beschreibung                                                                    |
|---------------------------------|---------------------------------------------------------------------------------|
| `token`                         | Plex Authentifizierungs-Token                                                   |
| `server_name`                   | Name deines Plex-Servers                                                        |
| `download_path`                 | Temporäres Verzeichnis für Downloads                                            |
| `remove_embedded_artwork_movies`| Eingebettete Vorschaubilder bei Filmen nach dem Download entfernen (true/false) |
| `create_movie_subdir`           | Unterverzeichnis pro Film erstellen (Emby-Konvention, Standard: false)          |
| `targets`                       | Liste von Zielverzeichnissen (via `plex-dl init`)                               |

> **Hinweis:** `targets` (Zielverzeichnisse) werden über `plex-dl init` oder die interaktive Erstkonfiguration gepflegt, da es sich um eine Liste von Objekten handelt.

## Projektstruktur

Dieses Projekt nutzt das moderne `src`-Layout für Python-Pakete:

```text
plex-downloader/
├── pyproject.toml       # Abhängigkeiten & Entry Point
├── src/
│   └── plex_downloader/
│       ├── __init__.py
│       ├── main.py      # Kompatibilitäts-Layer (Re-Exporte)
│       ├── core/
│       │   ├── config.py         # Konfigurationsmanagement (reine I/O-Funktionen)
│       │   └── scheduler.py      # Zeitplanungslogik (Nacht-Download-Berechnung)
│       ├── api/
│       │   ├── models.py         # Request/Result-Datenmodelle
│       │   └── facade.py         # Öffentliche API-Fassade
│       ├── services/
│       │   └── plex_service.py   # Plex-Server-Verbindung
│       ├── cli/
│       │   ├── app.py            # Typer-CLI (Subcommands: init, config, select, movie)
│       │   ├── movie.py          # Unterbefehlsgruppe 'movie' (download-first, download, search, …)
│       │   └── interactive.py    # Interaktive Sitzungslogik
│       └── modules/
│           ├── downloader.py     # Download-Logik
│           ├── rclone_mover.py   # Medienserver-Integration
│           └── cleanup.py        # Temporäre Dateien bereinigen
```

## Änderungsverlauf

### 2.4.0 – 2026-03-19
- Neue normalisierte externe IDs (`tmdb`, `imdb`, `tvdb`) im `MediaMatch`-Modell und in `movie search --json`
- Neuer `--show-ids`-Flag für `plex-dl select` und `plex-dl movie search` zur optionalen Anzeige externer IDs in Listen- und Auswahlansichten

### 2.3.0 – 2026-03-13
- Neuer `--json`-Flag für `plex-dl movie search`: maschinenlesbare JSON-Ausgabe inkl. Edition/Version, Dateigrösse, Auflösung und Container-Format
- Erweitertes `MediaMatch`-Modell mit `edition`, `file_size_bytes`, `resolution` und `container`
- Neuer unattended CLI-Befehl `plex-dl movie download --rating-key <id>` für den deterministischen Film-Download per eindeutiger Plex-ID
- Neue öffentliche API-Methode `download_movie_by_rating_key()` als stabiler Einstiegspunkt
- Validierung, dass der Rating-Key auf einen Film verweist (Fehler bei falschem Medientyp)
- `DownloadRequest.download_path` ist jetzt optional (analog zu `download-first`)

### 2.2.0 – 2026-03-13
- Neuer unattended CLI-Befehl `plex-dl movie search "<titel>"` für die Filmsuche ohne Download
- Suchergebnisse enthalten Rating-Key, Titel und Jahr – als Grundlage für deterministische Downloads
- `--limit`-Option für die Suche (Standard: 10 Treffer)
- Öffentliche API-Methode `search_movies()` als stabiler Einstiegspunkt dokumentiert und genutzt

### 2.1.0 – 2026-03-13
- Neuer unattended CLI-Befehl `plex-dl movie download-first "<titel>"` für den direkten Film-Download per Titel ohne interaktive Rückfragen
- Neuer `--night`-Flag: verschiebt den Download unattended bis 2 Uhr morgens (vollständig skriptbar)
- Neue `movie`-Unterbefehlsgruppe als Grundlage für spätere Befehle (`movie search`, `movie download --rating-key`)
- Neue öffentliche API-Methoden `search_movies()` und `download_first_movie_by_title()` in der API-Fassade
- Neues Modell `MediaMatch` für typisierte Suchresultate ohne rohe Plex-Objekte in der öffentlichen API
- Neues Modul `core/scheduler.py` mit wiederverwendbarer Zeitplanungslogik für Nacht-Downloads
- `download_video()` unterstützt jetzt `skip_existing=True` für den unattended Pfad ohne interaktiven Prompt

Vollständige Historie: [CHANGELOG.md](CHANGELOG.md)

## Lizenz

Dieses Projekt ist unter der MIT Lizenz veröffentlicht. Siehe [LICENSE](LICENSE) für Details.

## Haftungsausschluss

Dieses Tool ist nur für den persönlichen Gebrauch gedacht. Bitte respektiere das Urheberrecht und lade nur Inhalte herunter, an denen du die Rechte besitzt oder auf die du legitimen Zugriff hast.
