plextool 3.0.1__tar.gz → 3.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plextool-3.0.1 → plextool-3.1.1}/PKG-INFO +48 -31
- {plextool-3.0.1 → plextool-3.1.1}/README.md +46 -29
- {plextool-3.0.1 → plextool-3.1.1}/plextool/__init__.py +1 -1
- {plextool-3.0.1 → plextool-3.1.1}/plextool/audit.py +11 -7
- {plextool-3.0.1 → plextool-3.1.1}/plextool/cli.py +16 -2
- {plextool-3.0.1 → plextool-3.1.1}/plextool/common.py +209 -2
- {plextool-3.0.1 → plextool-3.1.1}/plextool/dupes.py +3 -2
- {plextool-3.0.1 → plextool-3.1.1}/plextool/ondemand.py +2 -1
- {plextool-3.0.1 → plextool-3.1.1}/plextool/playlists.py +2 -1
- {plextool-3.0.1 → plextool-3.1.1}/plextool/server.py +1 -2
- {plextool-3.0.1 → plextool-3.1.1}/plextool/stale.py +97 -10
- {plextool-3.0.1 → plextool-3.1.1}/plextool/watched.py +4 -30
- {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/PKG-INFO +48 -31
- {plextool-3.0.1 → plextool-3.1.1}/pyproject.toml +2 -2
- {plextool-3.0.1 → plextool-3.1.1}/tests/test_tools.py +167 -3
- {plextool-3.0.1 → plextool-3.1.1}/LICENSE +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool/__main__.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool/auth.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool/recent.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool/resume.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool/scan.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool/status.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool/subs.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/SOURCES.txt +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/dependency_links.txt +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/entry_points.txt +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/requires.txt +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/top_level.txt +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/setup.cfg +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/tests/test_auth.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/tests/test_ondemand.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/tests/test_plextool.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/tests/test_server.py +0 -0
- {plextool-3.0.1 → plextool-3.1.1}/tests/test_subs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plextool
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Summary: Maintenance helpers for a Plex Media Server
|
|
5
5
|
Author: Milkmansson
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Topic :: Multimedia :: Video
|
|
21
21
|
Classifier: Topic :: System :: Systems Administration
|
|
22
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: plexapi>=4.18
|
|
@@ -35,11 +35,11 @@ Dynamic: license-file
|
|
|
35
35
|
# plextool
|
|
36
36
|
|
|
37
37
|
> [!WARNING]
|
|
38
|
-
> Still in development/testing... please raise an issue if
|
|
38
|
+
> Still in development/testing... please raise an issue if one is found!
|
|
39
39
|
|
|
40
|
-
Command line
|
|
41
|
-
[python-plexapi](https://python-plexapi.readthedocs.io/).
|
|
42
|
-
server through its API.
|
|
40
|
+
Command line tool for Plex library maintenance, built on
|
|
41
|
+
[python-plexapi](https://python-plexapi.readthedocs.io/). It talks to the
|
|
42
|
+
server through its API. No access is made to the Plex database file, which is
|
|
43
43
|
too risky to edit by hand.
|
|
44
44
|
|
|
45
45
|
## What it is useful for
|
|
@@ -49,8 +49,8 @@ things Plex's web interface can't, and finding problems across a large library.
|
|
|
49
49
|
Typical jobs:
|
|
50
50
|
|
|
51
51
|
- **Fix the Recently Added order.** This is what it was first built for. Plex has no way to
|
|
52
|
-
edit the Recently Added lists, so after
|
|
53
|
-
|
|
52
|
+
edit the Recently Added lists, so after the database is rebuilt, duplicate
|
|
53
|
+
files are deleted and Plex rescans, or files are reorganised on disk, everything shows up as
|
|
54
54
|
"new" and the real newest content is buried. [`plextool recent`](https://plextool.readthedocs.io/en/latest/commands/recent.html)
|
|
55
55
|
changes the "added at" date of movies, shows, seasons and episodes: set each
|
|
56
56
|
item back to its own release or air date, shift dates by an offset, or set one date for a
|
|
@@ -60,7 +60,7 @@ Typical jobs:
|
|
|
60
60
|
$ plextool recent --title "Some Show" --shift=-30d --commit
|
|
61
61
|
```
|
|
62
62
|
- **Find and remove duplicate movies and episodes.** [`plextool dupes`](https://plextool.readthedocs.io/en/latest/commands/dupes.html)
|
|
63
|
-
lists every item Plex holds more than one file for, and can delete the extras
|
|
63
|
+
lists every item Plex holds more than one file for, and can delete the extras that are not wanted.
|
|
64
64
|
- **Free up disk space.** [`plextool stale`](https://plextool.readthedocs.io/en/latest/commands/stale.html) finds large, never-watched or
|
|
65
65
|
long-idle movies and shows.
|
|
66
66
|
- **Audit a library.** [`plextool audit`](https://plextool.readthedocs.io/en/latest/commands/audit.html) reports unmatched items, missing files,
|
|
@@ -79,8 +79,8 @@ Typical jobs:
|
|
|
79
79
|
- **Script Plex.** Reports can print JSON, and exports can go through a pipe, so the output
|
|
80
80
|
feeds other tools.
|
|
81
81
|
|
|
82
|
-
Everything that changes the server is a preview until
|
|
83
|
-
never edits the Plex database file. It signs in through
|
|
82
|
+
Everything that changes the server is a preview until `--commit` is passed. plextool uses Plex's API and
|
|
83
|
+
never edits the Plex database file. It signs in through a browser with
|
|
84
84
|
[`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html), and works against a server on the same machine, across the
|
|
85
85
|
internet, or directly with no internet access ([`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html)).
|
|
86
86
|
|
|
@@ -88,8 +88,8 @@ internet, or directly with no internet access ([`plextool server`](https://plext
|
|
|
88
88
|
|
|
89
89
|
| Command | What it does |
|
|
90
90
|
| --- | --- |
|
|
91
|
-
| [`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html) | Sign in to Plex with a link or QR code, list and remove old sign-ins from
|
|
92
|
-
| [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) | Find servers on
|
|
91
|
+
| [`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html) | Sign in to Plex with a link or QR code, list and remove old sign-ins from the account, and wipe the saved login and keys. |
|
|
92
|
+
| [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) | Find servers on the account or network and choose the one to use. Covers working offline or directly against the server. |
|
|
93
93
|
| [`plextool status`](https://plextool.readthedocs.io/en/latest/commands/status.html) | Server version, library sizes, who is watching what. |
|
|
94
94
|
| [`plextool scan`](https://plextool.readthedocs.io/en/latest/commands/scan.html) | Scan libraries or a single folder, refresh metadata, empty trash, optimize. |
|
|
95
95
|
| [`plextool recent`](https://plextool.readthedocs.io/en/latest/commands/recent.html) | Change "added at" dates to reorder Recently Added. Works on movies, shows, seasons and episodes. |
|
|
@@ -101,8 +101,8 @@ internet, or directly with no internet access ([`plextool server`](https://plext
|
|
|
101
101
|
| [`plextool playlists`](https://plextool.readthedocs.io/en/latest/commands/playlists.html) | Back up and restore video playlists. |
|
|
102
102
|
| [`plextool subs`](https://plextool.readthedocs.io/en/latest/commands/subs.html) | Extract subtitles from MKV and MP4 files, search Plex's subtitle providers for new ones and pick which to download, and copy subtitles Plex downloaded (kept in its database, not beside the video) out as files next to the video. Files are named for Plex, forced and SDH included. |
|
|
103
103
|
|
|
104
|
-
The `plextool` commands that change anything do a dry run unless
|
|
105
|
-
|
|
104
|
+
The `plextool` commands that change anything do a dry run unless `--commit` is
|
|
105
|
+
passed. Deleting files (`dupes --keep`) also asks for `DELETE` to be typed.
|
|
106
106
|
|
|
107
107
|
## Quick start
|
|
108
108
|
|
|
@@ -110,9 +110,9 @@ Install it from PyPI. This needs `python` installed as a prerequisite:
|
|
|
110
110
|
|
|
111
111
|
```console
|
|
112
112
|
$ python3 -m venv .venv && source .venv/bin/activate # optional: keeps plextool's packages separate
|
|
113
|
-
$ pip install 'plextool[qr]' # '[qr]' adds a QR code to the sign-in; leave it off if
|
|
113
|
+
$ pip install 'plextool[qr]' # '[qr]' adds a QR code to the sign-in; leave it off if no QR code is wanted
|
|
114
114
|
$ plextool auth login # open the link it prints (or scan the QR code) and approve
|
|
115
|
-
$ plextool server find # servers on
|
|
115
|
+
$ plextool server find # servers on the Plex account and on the network
|
|
116
116
|
$ plextool server use "My Server" # one of the names from the list
|
|
117
117
|
$ plextool status # libraries, and who is watching what right now
|
|
118
118
|
```
|
|
@@ -135,21 +135,21 @@ $ plextool subs fetch -t "Some Movie" --lang sv --choose --export --commit # s
|
|
|
135
135
|
**Where the subtitle ends up.** When Plex downloads a subtitle from a provider, it keeps
|
|
136
136
|
the copy in its own database. It does not put a file next to the video, so only Plex can
|
|
137
137
|
show it: another player, or another Plex server, never sees it. The first two commands stop
|
|
138
|
-
there, and
|
|
138
|
+
there, and the subtitle is picked from the player's subtitle menu.
|
|
139
139
|
|
|
140
140
|
**`--export` gets around that.** Once Plex has downloaded the subtitle, plextool copies
|
|
141
141
|
Plex's stored copy out of the server and saves it as an ordinary file beside the video
|
|
142
142
|
(`<video name>.sv.srt`), which any player can use. It doesn't read the video file, so the
|
|
143
143
|
MKV or MP4 is never opened, and it doesn't contact the subtitle provider. (Subtitle
|
|
144
144
|
tracks that are already inside an MKV or MP4 are a different job, for `plextool subs extract`.)
|
|
145
|
-
For `--export`, the machine
|
|
145
|
+
For `--export`, the machine it runs on must be able to read the media files. If it sees
|
|
146
146
|
them under a different path than Plex does, use `--map`. The
|
|
147
147
|
[`subs` page](https://plextool.readthedocs.io/en/latest/commands/subs.html#where-each-subtitle-comes-from) sets out where each kind of subtitle comes from.
|
|
148
148
|
|
|
149
149
|
For a TV show, `-t "Some Show"` covers every episode, which is a search for each one,
|
|
150
150
|
so start with `--list`.
|
|
151
151
|
|
|
152
|
-
If the server is on the machine
|
|
152
|
+
If the server is on the same machine as the terminal, skip `server find` and `server use`:
|
|
153
153
|
plextool tries `http://localhost:32400` by default. Without internet access, see
|
|
154
154
|
[working offline](https://plextool.readthedocs.io/en/latest/commands/server.html#working-offline-or-directly-against-the-server).
|
|
155
155
|
|
|
@@ -163,19 +163,36 @@ $ plextool recent --help
|
|
|
163
163
|
$ plextool watched copy --help
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
To see what plextool is doing, add `-v` (`--verbose`) to any command. It prints each request sent
|
|
167
|
+
to Plex, with the time, on standard error, followed by a line for the reply: the status, how many
|
|
168
|
+
records were in it (and where that page sits in the whole list), its size and how long it took.
|
|
169
|
+
It also prints when each slow step starts and finishes. The token
|
|
170
|
+
is hidden in that output.
|
|
171
|
+
|
|
172
|
+
```console
|
|
173
|
+
$ plextool stale --all-users -v
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
When a command takes more than 5 seconds, plextool prints `Finished in 2m 14s` on standard error at the end
|
|
177
|
+
(with `-v`, always).
|
|
178
|
+
|
|
179
|
+
Reading a big library can take minutes. After about 10 seconds of silence, plextool prints
|
|
180
|
+
`still reading <library>...` to standard error, and again every 20 seconds, so it is clear it has not
|
|
181
|
+
stopped. `--json` output is not affected.
|
|
182
|
+
|
|
166
183
|
A fuller page for each command is on [Read the Docs](https://plextool.readthedocs.io/)
|
|
167
184
|
(linked in the table above); its source is in `docs/source/commands/`.
|
|
168
185
|
|
|
169
186
|
## Install
|
|
170
187
|
|
|
171
|
-
plextool
|
|
172
|
-
|
|
173
|
-
[open an issue](https://github.com/milkmansson/plextool/issues) with
|
|
188
|
+
plextool needs Python 3.10 or later, because plexapi 4.18 does. It is tested on
|
|
189
|
+
Python 3.12. If something doesn't work on another version, please
|
|
190
|
+
[open an issue](https://github.com/milkmansson/plextool/issues) with the Python
|
|
174
191
|
version.
|
|
175
192
|
|
|
176
193
|
`plextool subs` also needs MKVToolNix (`mkvmerge`, `mkvextract`) for MKV files and
|
|
177
|
-
FFmpeg (`ffmpeg`, `ffprobe`) for MP4 files.
|
|
178
|
-
|
|
194
|
+
FFmpeg (`ffmpeg`, `ffprobe`) for MP4 files. Only the ones for the file types
|
|
195
|
+
in use are needed.
|
|
179
196
|
|
|
180
197
|
```console
|
|
181
198
|
$ pip install plextool # installs the `plextool` command
|
|
@@ -208,16 +225,16 @@ Without a saved choice, plextool uses `http://localhost:32400`, or `$PLEX_URL`
|
|
|
208
225
|
or `--base-url` if given. [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) explains the
|
|
209
226
|
options, including working with no internet or directly on the server's machine.
|
|
210
227
|
|
|
211
|
-
That saves a Plex token in `~/.config/plextool/` (readable only by
|
|
228
|
+
That saves a Plex token in `~/.config/plextool/` (readable only by the current user).
|
|
212
229
|
[`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html) explains exactly what is kept, how to wipe it
|
|
213
230
|
with `plextool auth wipe`, and `--type jwt`, a short-lived token that plex.tv
|
|
214
231
|
accepts but Plex Media Server doesn't yet.
|
|
215
232
|
|
|
216
|
-
|
|
217
|
-
|
|
233
|
+
plextool looks for a [Plex token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)
|
|
234
|
+
in this order: `--token`, `--token-file FILE` (`-` for
|
|
218
235
|
standard input), `$PLEX_TOKEN`, the output of `$PLEX_TOKEN_CMD`, then the saved
|
|
219
236
|
login. Prefer `--token-file` or the environment to `--token`, which leaves the
|
|
220
|
-
token in
|
|
237
|
+
token in the shell history.
|
|
221
238
|
|
|
222
239
|
## Development
|
|
223
240
|
|
|
@@ -228,5 +245,5 @@ $ pytest
|
|
|
228
245
|
|
|
229
246
|
## Disclaimer
|
|
230
247
|
|
|
231
|
-
These tools only use the documented plexapi calls, but they do write to
|
|
232
|
-
server when
|
|
248
|
+
These tools only use the documented plexapi calls, but they do write to the
|
|
249
|
+
server when `--commit` is passed. Check the dry run first. Use is at the user's own risk.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# plextool
|
|
2
2
|
|
|
3
3
|
> [!WARNING]
|
|
4
|
-
> Still in development/testing... please raise an issue if
|
|
4
|
+
> Still in development/testing... please raise an issue if one is found!
|
|
5
5
|
|
|
6
|
-
Command line
|
|
7
|
-
[python-plexapi](https://python-plexapi.readthedocs.io/).
|
|
8
|
-
server through its API.
|
|
6
|
+
Command line tool for Plex library maintenance, built on
|
|
7
|
+
[python-plexapi](https://python-plexapi.readthedocs.io/). It talks to the
|
|
8
|
+
server through its API. No access is made to the Plex database file, which is
|
|
9
9
|
too risky to edit by hand.
|
|
10
10
|
|
|
11
11
|
## What it is useful for
|
|
@@ -15,8 +15,8 @@ things Plex's web interface can't, and finding problems across a large library.
|
|
|
15
15
|
Typical jobs:
|
|
16
16
|
|
|
17
17
|
- **Fix the Recently Added order.** This is what it was first built for. Plex has no way to
|
|
18
|
-
edit the Recently Added lists, so after
|
|
19
|
-
|
|
18
|
+
edit the Recently Added lists, so after the database is rebuilt, duplicate
|
|
19
|
+
files are deleted and Plex rescans, or files are reorganised on disk, everything shows up as
|
|
20
20
|
"new" and the real newest content is buried. [`plextool recent`](https://plextool.readthedocs.io/en/latest/commands/recent.html)
|
|
21
21
|
changes the "added at" date of movies, shows, seasons and episodes: set each
|
|
22
22
|
item back to its own release or air date, shift dates by an offset, or set one date for a
|
|
@@ -26,7 +26,7 @@ Typical jobs:
|
|
|
26
26
|
$ plextool recent --title "Some Show" --shift=-30d --commit
|
|
27
27
|
```
|
|
28
28
|
- **Find and remove duplicate movies and episodes.** [`plextool dupes`](https://plextool.readthedocs.io/en/latest/commands/dupes.html)
|
|
29
|
-
lists every item Plex holds more than one file for, and can delete the extras
|
|
29
|
+
lists every item Plex holds more than one file for, and can delete the extras that are not wanted.
|
|
30
30
|
- **Free up disk space.** [`plextool stale`](https://plextool.readthedocs.io/en/latest/commands/stale.html) finds large, never-watched or
|
|
31
31
|
long-idle movies and shows.
|
|
32
32
|
- **Audit a library.** [`plextool audit`](https://plextool.readthedocs.io/en/latest/commands/audit.html) reports unmatched items, missing files,
|
|
@@ -45,8 +45,8 @@ Typical jobs:
|
|
|
45
45
|
- **Script Plex.** Reports can print JSON, and exports can go through a pipe, so the output
|
|
46
46
|
feeds other tools.
|
|
47
47
|
|
|
48
|
-
Everything that changes the server is a preview until
|
|
49
|
-
never edits the Plex database file. It signs in through
|
|
48
|
+
Everything that changes the server is a preview until `--commit` is passed. plextool uses Plex's API and
|
|
49
|
+
never edits the Plex database file. It signs in through a browser with
|
|
50
50
|
[`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html), and works against a server on the same machine, across the
|
|
51
51
|
internet, or directly with no internet access ([`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html)).
|
|
52
52
|
|
|
@@ -54,8 +54,8 @@ internet, or directly with no internet access ([`plextool server`](https://plext
|
|
|
54
54
|
|
|
55
55
|
| Command | What it does |
|
|
56
56
|
| --- | --- |
|
|
57
|
-
| [`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html) | Sign in to Plex with a link or QR code, list and remove old sign-ins from
|
|
58
|
-
| [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) | Find servers on
|
|
57
|
+
| [`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html) | Sign in to Plex with a link or QR code, list and remove old sign-ins from the account, and wipe the saved login and keys. |
|
|
58
|
+
| [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) | Find servers on the account or network and choose the one to use. Covers working offline or directly against the server. |
|
|
59
59
|
| [`plextool status`](https://plextool.readthedocs.io/en/latest/commands/status.html) | Server version, library sizes, who is watching what. |
|
|
60
60
|
| [`plextool scan`](https://plextool.readthedocs.io/en/latest/commands/scan.html) | Scan libraries or a single folder, refresh metadata, empty trash, optimize. |
|
|
61
61
|
| [`plextool recent`](https://plextool.readthedocs.io/en/latest/commands/recent.html) | Change "added at" dates to reorder Recently Added. Works on movies, shows, seasons and episodes. |
|
|
@@ -67,8 +67,8 @@ internet, or directly with no internet access ([`plextool server`](https://plext
|
|
|
67
67
|
| [`plextool playlists`](https://plextool.readthedocs.io/en/latest/commands/playlists.html) | Back up and restore video playlists. |
|
|
68
68
|
| [`plextool subs`](https://plextool.readthedocs.io/en/latest/commands/subs.html) | Extract subtitles from MKV and MP4 files, search Plex's subtitle providers for new ones and pick which to download, and copy subtitles Plex downloaded (kept in its database, not beside the video) out as files next to the video. Files are named for Plex, forced and SDH included. |
|
|
69
69
|
|
|
70
|
-
The `plextool` commands that change anything do a dry run unless
|
|
71
|
-
|
|
70
|
+
The `plextool` commands that change anything do a dry run unless `--commit` is
|
|
71
|
+
passed. Deleting files (`dupes --keep`) also asks for `DELETE` to be typed.
|
|
72
72
|
|
|
73
73
|
## Quick start
|
|
74
74
|
|
|
@@ -76,9 +76,9 @@ Install it from PyPI. This needs `python` installed as a prerequisite:
|
|
|
76
76
|
|
|
77
77
|
```console
|
|
78
78
|
$ python3 -m venv .venv && source .venv/bin/activate # optional: keeps plextool's packages separate
|
|
79
|
-
$ pip install 'plextool[qr]' # '[qr]' adds a QR code to the sign-in; leave it off if
|
|
79
|
+
$ pip install 'plextool[qr]' # '[qr]' adds a QR code to the sign-in; leave it off if no QR code is wanted
|
|
80
80
|
$ plextool auth login # open the link it prints (or scan the QR code) and approve
|
|
81
|
-
$ plextool server find # servers on
|
|
81
|
+
$ plextool server find # servers on the Plex account and on the network
|
|
82
82
|
$ plextool server use "My Server" # one of the names from the list
|
|
83
83
|
$ plextool status # libraries, and who is watching what right now
|
|
84
84
|
```
|
|
@@ -101,21 +101,21 @@ $ plextool subs fetch -t "Some Movie" --lang sv --choose --export --commit # s
|
|
|
101
101
|
**Where the subtitle ends up.** When Plex downloads a subtitle from a provider, it keeps
|
|
102
102
|
the copy in its own database. It does not put a file next to the video, so only Plex can
|
|
103
103
|
show it: another player, or another Plex server, never sees it. The first two commands stop
|
|
104
|
-
there, and
|
|
104
|
+
there, and the subtitle is picked from the player's subtitle menu.
|
|
105
105
|
|
|
106
106
|
**`--export` gets around that.** Once Plex has downloaded the subtitle, plextool copies
|
|
107
107
|
Plex's stored copy out of the server and saves it as an ordinary file beside the video
|
|
108
108
|
(`<video name>.sv.srt`), which any player can use. It doesn't read the video file, so the
|
|
109
109
|
MKV or MP4 is never opened, and it doesn't contact the subtitle provider. (Subtitle
|
|
110
110
|
tracks that are already inside an MKV or MP4 are a different job, for `plextool subs extract`.)
|
|
111
|
-
For `--export`, the machine
|
|
111
|
+
For `--export`, the machine it runs on must be able to read the media files. If it sees
|
|
112
112
|
them under a different path than Plex does, use `--map`. The
|
|
113
113
|
[`subs` page](https://plextool.readthedocs.io/en/latest/commands/subs.html#where-each-subtitle-comes-from) sets out where each kind of subtitle comes from.
|
|
114
114
|
|
|
115
115
|
For a TV show, `-t "Some Show"` covers every episode, which is a search for each one,
|
|
116
116
|
so start with `--list`.
|
|
117
117
|
|
|
118
|
-
If the server is on the machine
|
|
118
|
+
If the server is on the same machine as the terminal, skip `server find` and `server use`:
|
|
119
119
|
plextool tries `http://localhost:32400` by default. Without internet access, see
|
|
120
120
|
[working offline](https://plextool.readthedocs.io/en/latest/commands/server.html#working-offline-or-directly-against-the-server).
|
|
121
121
|
|
|
@@ -129,19 +129,36 @@ $ plextool recent --help
|
|
|
129
129
|
$ plextool watched copy --help
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
To see what plextool is doing, add `-v` (`--verbose`) to any command. It prints each request sent
|
|
133
|
+
to Plex, with the time, on standard error, followed by a line for the reply: the status, how many
|
|
134
|
+
records were in it (and where that page sits in the whole list), its size and how long it took.
|
|
135
|
+
It also prints when each slow step starts and finishes. The token
|
|
136
|
+
is hidden in that output.
|
|
137
|
+
|
|
138
|
+
```console
|
|
139
|
+
$ plextool stale --all-users -v
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
When a command takes more than 5 seconds, plextool prints `Finished in 2m 14s` on standard error at the end
|
|
143
|
+
(with `-v`, always).
|
|
144
|
+
|
|
145
|
+
Reading a big library can take minutes. After about 10 seconds of silence, plextool prints
|
|
146
|
+
`still reading <library>...` to standard error, and again every 20 seconds, so it is clear it has not
|
|
147
|
+
stopped. `--json` output is not affected.
|
|
148
|
+
|
|
132
149
|
A fuller page for each command is on [Read the Docs](https://plextool.readthedocs.io/)
|
|
133
150
|
(linked in the table above); its source is in `docs/source/commands/`.
|
|
134
151
|
|
|
135
152
|
## Install
|
|
136
153
|
|
|
137
|
-
plextool
|
|
138
|
-
|
|
139
|
-
[open an issue](https://github.com/milkmansson/plextool/issues) with
|
|
154
|
+
plextool needs Python 3.10 or later, because plexapi 4.18 does. It is tested on
|
|
155
|
+
Python 3.12. If something doesn't work on another version, please
|
|
156
|
+
[open an issue](https://github.com/milkmansson/plextool/issues) with the Python
|
|
140
157
|
version.
|
|
141
158
|
|
|
142
159
|
`plextool subs` also needs MKVToolNix (`mkvmerge`, `mkvextract`) for MKV files and
|
|
143
|
-
FFmpeg (`ffmpeg`, `ffprobe`) for MP4 files.
|
|
144
|
-
|
|
160
|
+
FFmpeg (`ffmpeg`, `ffprobe`) for MP4 files. Only the ones for the file types
|
|
161
|
+
in use are needed.
|
|
145
162
|
|
|
146
163
|
```console
|
|
147
164
|
$ pip install plextool # installs the `plextool` command
|
|
@@ -174,16 +191,16 @@ Without a saved choice, plextool uses `http://localhost:32400`, or `$PLEX_URL`
|
|
|
174
191
|
or `--base-url` if given. [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) explains the
|
|
175
192
|
options, including working with no internet or directly on the server's machine.
|
|
176
193
|
|
|
177
|
-
That saves a Plex token in `~/.config/plextool/` (readable only by
|
|
194
|
+
That saves a Plex token in `~/.config/plextool/` (readable only by the current user).
|
|
178
195
|
[`plextool auth`](https://plextool.readthedocs.io/en/latest/commands/auth.html) explains exactly what is kept, how to wipe it
|
|
179
196
|
with `plextool auth wipe`, and `--type jwt`, a short-lived token that plex.tv
|
|
180
197
|
accepts but Plex Media Server doesn't yet.
|
|
181
198
|
|
|
182
|
-
|
|
183
|
-
|
|
199
|
+
plextool looks for a [Plex token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)
|
|
200
|
+
in this order: `--token`, `--token-file FILE` (`-` for
|
|
184
201
|
standard input), `$PLEX_TOKEN`, the output of `$PLEX_TOKEN_CMD`, then the saved
|
|
185
202
|
login. Prefer `--token-file` or the environment to `--token`, which leaves the
|
|
186
|
-
token in
|
|
203
|
+
token in the shell history.
|
|
187
204
|
|
|
188
205
|
## Development
|
|
189
206
|
|
|
@@ -194,5 +211,5 @@ $ pytest
|
|
|
194
211
|
|
|
195
212
|
## Disclaimer
|
|
196
213
|
|
|
197
|
-
These tools only use the documented plexapi calls, but they do write to
|
|
198
|
-
server when
|
|
214
|
+
These tools only use the documented plexapi calls, but they do write to the
|
|
215
|
+
server when `--commit` is passed. Check the dry run first. Use is at the user's own risk.
|
|
@@ -16,6 +16,8 @@ from .common import (
|
|
|
16
16
|
paint,
|
|
17
17
|
pick_sections,
|
|
18
18
|
plural,
|
|
19
|
+
scan,
|
|
20
|
+
working,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
LOW_RES = {"sd", "480", "576", "360", "240"}
|
|
@@ -82,12 +84,14 @@ def run_check(name: str, items: list, lang: str | None) -> list[dict]:
|
|
|
82
84
|
"""Apply one check to items; returns [{'item': description, 'reason': ...}]."""
|
|
83
85
|
func, _, _, needs_full = CHECKS[name]
|
|
84
86
|
found = []
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
with working(f"checking {name}") as progress:
|
|
88
|
+
for number, item in enumerate(items, start=1):
|
|
89
|
+
progress.detail = f"{number} of {len(items)} items"
|
|
90
|
+
if needs_full:
|
|
91
|
+
item.reload()
|
|
92
|
+
reason = func(item, lang)
|
|
93
|
+
if reason:
|
|
94
|
+
found.append({"item": describe(item), "reason": reason})
|
|
91
95
|
return found
|
|
92
96
|
|
|
93
97
|
|
|
@@ -124,7 +128,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
124
128
|
for name in names:
|
|
125
129
|
level = FILE_LEVEL[section.TYPE] if CHECKS[name][1] else section.TYPE
|
|
126
130
|
if level not in cache:
|
|
127
|
-
cache[level] = section
|
|
131
|
+
cache[level] = scan(section, libtype=level)
|
|
128
132
|
report.setdefault(name, {})[section.title] = run_check(name, cache[level], args.lang)
|
|
129
133
|
|
|
130
134
|
if args.json:
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import sys
|
|
7
|
+
import time
|
|
7
8
|
|
|
8
9
|
from . import (
|
|
9
10
|
__version__,
|
|
@@ -20,7 +21,10 @@ from . import (
|
|
|
20
21
|
subs,
|
|
21
22
|
watched,
|
|
22
23
|
)
|
|
23
|
-
from .common import PlexToolError
|
|
24
|
+
from .common import PlexToolError, enable_verbose, format_duration
|
|
25
|
+
|
|
26
|
+
# Seconds a command must take before its total time is printed (always printed with --verbose).
|
|
27
|
+
SHOW_TIME_AFTER = 5
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -31,6 +35,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
31
35
|
"Anything that changes your server is a dry run unless you pass --commit.",
|
|
32
36
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
33
37
|
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
38
|
+
parser.add_argument("-v", "--verbose", dest="verbose_first", action="store_true",
|
|
39
|
+
help="print each request sent to Plex, with its reply, on stderr (also accepted after the command)")
|
|
34
40
|
subparsers = parser.add_subparsers(dest="command", metavar="command")
|
|
35
41
|
for module in (auth, server, status, scan, recent, dupes, stale, audit, watched, resume, playlists, subs):
|
|
36
42
|
module.add_parser(subparsers)
|
|
@@ -43,8 +49,16 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
43
49
|
if not args.command:
|
|
44
50
|
parser.print_help()
|
|
45
51
|
return 0
|
|
52
|
+
verbose = args.verbose_first or getattr(args, "verbose", False)
|
|
53
|
+
if verbose:
|
|
54
|
+
enable_verbose()
|
|
55
|
+
started = time.monotonic()
|
|
46
56
|
try:
|
|
47
|
-
|
|
57
|
+
status = args.run(args)
|
|
58
|
+
elapsed = time.monotonic() - started
|
|
59
|
+
if verbose or elapsed >= SHOW_TIME_AFTER:
|
|
60
|
+
print(f"Finished in {format_duration(elapsed)}", file=sys.stderr)
|
|
61
|
+
return status
|
|
48
62
|
except PlexToolError as e:
|
|
49
63
|
print(f"error: {e}", file=sys.stderr)
|
|
50
64
|
return 2
|