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.
Files changed (34) hide show
  1. {plextool-3.0.1 → plextool-3.1.1}/PKG-INFO +48 -31
  2. {plextool-3.0.1 → plextool-3.1.1}/README.md +46 -29
  3. {plextool-3.0.1 → plextool-3.1.1}/plextool/__init__.py +1 -1
  4. {plextool-3.0.1 → plextool-3.1.1}/plextool/audit.py +11 -7
  5. {plextool-3.0.1 → plextool-3.1.1}/plextool/cli.py +16 -2
  6. {plextool-3.0.1 → plextool-3.1.1}/plextool/common.py +209 -2
  7. {plextool-3.0.1 → plextool-3.1.1}/plextool/dupes.py +3 -2
  8. {plextool-3.0.1 → plextool-3.1.1}/plextool/ondemand.py +2 -1
  9. {plextool-3.0.1 → plextool-3.1.1}/plextool/playlists.py +2 -1
  10. {plextool-3.0.1 → plextool-3.1.1}/plextool/server.py +1 -2
  11. {plextool-3.0.1 → plextool-3.1.1}/plextool/stale.py +97 -10
  12. {plextool-3.0.1 → plextool-3.1.1}/plextool/watched.py +4 -30
  13. {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/PKG-INFO +48 -31
  14. {plextool-3.0.1 → plextool-3.1.1}/pyproject.toml +2 -2
  15. {plextool-3.0.1 → plextool-3.1.1}/tests/test_tools.py +167 -3
  16. {plextool-3.0.1 → plextool-3.1.1}/LICENSE +0 -0
  17. {plextool-3.0.1 → plextool-3.1.1}/plextool/__main__.py +0 -0
  18. {plextool-3.0.1 → plextool-3.1.1}/plextool/auth.py +0 -0
  19. {plextool-3.0.1 → plextool-3.1.1}/plextool/recent.py +0 -0
  20. {plextool-3.0.1 → plextool-3.1.1}/plextool/resume.py +0 -0
  21. {plextool-3.0.1 → plextool-3.1.1}/plextool/scan.py +0 -0
  22. {plextool-3.0.1 → plextool-3.1.1}/plextool/status.py +0 -0
  23. {plextool-3.0.1 → plextool-3.1.1}/plextool/subs.py +0 -0
  24. {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/SOURCES.txt +0 -0
  25. {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/dependency_links.txt +0 -0
  26. {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/entry_points.txt +0 -0
  27. {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/requires.txt +0 -0
  28. {plextool-3.0.1 → plextool-3.1.1}/plextool.egg-info/top_level.txt +0 -0
  29. {plextool-3.0.1 → plextool-3.1.1}/setup.cfg +0 -0
  30. {plextool-3.0.1 → plextool-3.1.1}/tests/test_auth.py +0 -0
  31. {plextool-3.0.1 → plextool-3.1.1}/tests/test_ondemand.py +0 -0
  32. {plextool-3.0.1 → plextool-3.1.1}/tests/test_plextool.py +0 -0
  33. {plextool-3.0.1 → plextool-3.1.1}/tests/test_server.py +0 -0
  34. {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.0.1
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.9
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 you find one!
38
+ > Still in development/testing... please raise an issue if one is found!
39
39
 
40
- Command line tools for Plex library maintenance, built on
41
- [python-plexapi](https://python-plexapi.readthedocs.io/). They talk to the
42
- server through its API. Nothing here touches the Plex database file, which is
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 you rebuild the database, delete
53
- duplicate files and rescan, or reorganise files on disk, everything shows up as
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 you don't want.
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 you pass `--commit`. plextool uses Plex's API and
83
- never edits the Plex database file. It signs in through your browser with
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 your account, and wipe the saved login and keys. |
92
- | [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) | Find servers on your account or network and choose the one to use. Covers working offline or directly against the server. |
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 you pass
105
- `--commit`. Deleting files (`dupes --keep`) also asks you to type `DELETE`.
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 you don't want one
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 your Plex account and on your network
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 you pick the subtitle from the player's subtitle menu.
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 you run it on must be able to read the media files. If it sees
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 you are typing on, skip `server find` and `server use`:
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 is tested on Python 3.12. It declares Python 3.9 or later, so pip will
172
- install it on older versions. If something doesn't work on one, please
173
- [open an issue](https://github.com/milkmansson/plextool/issues) with your Python
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. You only need the ones for the file types
178
- you use.
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 you).
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
- If you would rather supply your own [Plex token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/),
217
- plextool looks for one in this order: `--token`, `--token-file FILE` (`-` for
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 your shell history.
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 your
232
- server when you pass `--commit`. Check the dry run first. Use at your own risk.
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 you find one!
4
+ > Still in development/testing... please raise an issue if one is found!
5
5
 
6
- Command line tools for Plex library maintenance, built on
7
- [python-plexapi](https://python-plexapi.readthedocs.io/). They talk to the
8
- server through its API. Nothing here touches the Plex database file, which is
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 you rebuild the database, delete
19
- duplicate files and rescan, or reorganise files on disk, everything shows up as
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 you don't want.
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 you pass `--commit`. plextool uses Plex's API and
49
- never edits the Plex database file. It signs in through your browser with
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 your account, and wipe the saved login and keys. |
58
- | [`plextool server`](https://plextool.readthedocs.io/en/latest/commands/server.html) | Find servers on your account or network and choose the one to use. Covers working offline or directly against the server. |
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 you pass
71
- `--commit`. Deleting files (`dupes --keep`) also asks you to type `DELETE`.
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 you don't want one
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 your Plex account and on your network
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 you pick the subtitle from the player's subtitle menu.
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 you run it on must be able to read the media files. If it sees
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 you are typing on, skip `server find` and `server use`:
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 is tested on Python 3.12. It declares Python 3.9 or later, so pip will
138
- install it on older versions. If something doesn't work on one, please
139
- [open an issue](https://github.com/milkmansson/plextool/issues) with your Python
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. You only need the ones for the file types
144
- you use.
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 you).
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
- If you would rather supply your own [Plex token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/),
183
- plextool looks for one in this order: `--token`, `--token-file FILE` (`-` for
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 your shell history.
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 your
198
- server when you pass `--commit`. Check the dry run first. Use at your own risk.
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.
@@ -1,3 +1,3 @@
1
1
  """plextool: maintenance helpers for a Plex Media Server, built on python-plexapi."""
2
2
 
3
- __version__ = "3.0.1"
3
+ __version__ = "3.1.1"
@@ -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
- for item in items:
86
- if needs_full:
87
- item.reload()
88
- reason = func(item, lang)
89
- if reason:
90
- found.append({"item": describe(item), "reason": reason})
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.search(libtype=level)
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
- return args.run(args)
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