plex-renamer 1.0.0__py3-none-any.whl
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.
- plex_renamer-1.0.0.dist-info/METADATA +291 -0
- plex_renamer-1.0.0.dist-info/RECORD +22 -0
- plex_renamer-1.0.0.dist-info/WHEEL +4 -0
- plex_renamer-1.0.0.dist-info/entry_points.txt +2 -0
- plex_renamer-1.0.0.dist-info/licenses/LICENSE +21 -0
- plexrenamer/__init__.py +17 -0
- plexrenamer/__main__.py +5 -0
- plexrenamer/classify.py +449 -0
- plexrenamer/cli.py +498 -0
- plexrenamer/db.py +236 -0
- plexrenamer/decisions.py +91 -0
- plexrenamer/executor.py +456 -0
- plexrenamer/junk.py +194 -0
- plexrenamer/lint.py +115 -0
- plexrenamer/naming.py +141 -0
- plexrenamer/ops.py +183 -0
- plexrenamer/planner.py +553 -0
- plexrenamer/progress.py +62 -0
- plexrenamer/scan.py +127 -0
- plexrenamer/sidecars.py +136 -0
- plexrenamer/undo.py +376 -0
- plexrenamer/util.py +494 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: plex-renamer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Renames a local media volume to the naming standard Plex publishes, with a SQLite journal that can reverse any run.
|
|
5
|
+
Project-URL: Source, https://github.com/agusgonzaleznic/plex-renamer
|
|
6
|
+
Project-URL: Issues, https://github.com/agusgonzaleznic/plex-renamer/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/agusgonzaleznic/plex-renamer/releases
|
|
8
|
+
Author: Agustin Gonzalez Nicolini
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: media,naming,plex,rename,undo
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Multimedia :: Video
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# plex-renamer
|
|
21
|
+
|
|
22
|
+
Renames a local media volume to the naming standard Plex publishes, with a full
|
|
23
|
+
SQLite journal so any run can be replayed in reverse.
|
|
24
|
+
|
|
25
|
+
The rules are taken from Plex's own support articles. The distilled ruleset is
|
|
26
|
+
kept in `reports/plex-spec.json` as a reference, with the source article for each
|
|
27
|
+
section; the code does not read it. Of its 260 rules, 155 are marked
|
|
28
|
+
`verified: true` and 105 `verified: false`.
|
|
29
|
+
|
|
30
|
+
## Guarantees
|
|
31
|
+
|
|
32
|
+
- **Two kinds of file are deleted, and undo writes them back.** These are
|
|
33
|
+
torrent-site ads (a text, picture, link, page, info or program file whose
|
|
34
|
+
name starts with the name of a torrent site, such as
|
|
35
|
+
`YTS.MX - Official site.jpg`) and `.DS_Store` files, known by name and by the
|
|
36
|
+
first bytes of that format. A poster or an info file named after a release
|
|
37
|
+
only carries a site's tag, so a name with a year, a resolution, a codec or a
|
|
38
|
+
source in it is never an ad. Each is a plain
|
|
39
|
+
file of at most 1 MiB, and its bytes, modification time and mode go into the
|
|
40
|
+
journal before `os.unlink` removes it, so `undo` writes it back. A file that
|
|
41
|
+
has changed since it was listed is not deleted. Folders whose names start
|
|
42
|
+
with `@`, `#` or `.@`, which a NAS keeps for itself, are never looked into.
|
|
43
|
+
- **Nothing else in the library is deleted.** Apart from those two kinds, the
|
|
44
|
+
library is changed with `os.rename`, `os.mkdir`, `os.makedirs` and `os.rmdir`
|
|
45
|
+
only, and `os.rmdir` only runs on a folder that a listing shows to be empty.
|
|
46
|
+
A bare `rmdir` is no
|
|
47
|
+
guard: macOS removes a folder that holds only `._` files, and the files with
|
|
48
|
+
it. On a volume that keeps a folder's attributes in a `._` file beside the
|
|
49
|
+
folder, such as FAT, macOS removes that file with the folder, so a folder with
|
|
50
|
+
such a file beside it stays. The only things removed are empty directories:
|
|
51
|
+
ones the plan clears away, which `undo` recreates, and ones a run created,
|
|
52
|
+
once they are empty again. The plan clears away a folder the run empties, and
|
|
53
|
+
an empty folder of a title that already has its proper folder. A folder that
|
|
54
|
+
is already at a planned path when the run gets to it is not the run's, and
|
|
55
|
+
`undo` leaves it. When a folder has to go, its folder metadata (`.DS_Store`,
|
|
56
|
+
`Thumbs.db`) moves up to the parent first (as `.DS_Store-1` and so on when
|
|
57
|
+
the name is taken). A `.DS_Store` is then deleted with the ads, and a
|
|
58
|
+
`Thumbs.db` stays. A server can keep files of its own hidden
|
|
59
|
+
from the share, such as thumbnail caches. The tool never sees those, and what
|
|
60
|
+
becomes of them when their folder is removed is up to the server.
|
|
61
|
+
- **Besides those, the one thing the tool deletes is its own.** To estimate the running time,
|
|
62
|
+
`apply` makes a new folder at the volume root (`.plexrenamer-benchmark-` and a
|
|
63
|
+
random suffix), writes 60 scratch files into it, times renaming them and
|
|
64
|
+
removes those files and that folder again, also on `--dry-run`. It removes
|
|
65
|
+
nothing it did not make there, and a folder that holds anything else stays.
|
|
66
|
+
`--rate` skips it.
|
|
67
|
+
- **A lookup that fails is not an answer.** On a share, a lookup can fail for a
|
|
68
|
+
moment. Right before an operation and in `undo`, the tool never takes that to
|
|
69
|
+
mean that a path is free or that a file is gone: the operation in hand fails
|
|
70
|
+
and says why. While the plan is made, such a lookup can make the plan refuse
|
|
71
|
+
to start or leave something out, and the checks right before each operation
|
|
72
|
+
catch the rest. When `undo` or `verify` cannot read the library to look a run
|
|
73
|
+
over, it stops, changes nothing and says so.
|
|
74
|
+
- **Every run can be undone, also one that was cut off, and also after a remount.**
|
|
75
|
+
The whole plan is in the SQLite journal before the first operation, each rename
|
|
76
|
+
with the size and inode of its source, each deleted file with its bytes before
|
|
77
|
+
it goes, and each operation is marked as started
|
|
78
|
+
before it runs and as done after. `undo` goes through the journal backwards and
|
|
79
|
+
settles every row by where its file is now, found by name, kind and size. That
|
|
80
|
+
way `undo` also reverses an operation that was in flight when the run stopped
|
|
81
|
+
(Ctrl-C, a kill, a crash), one whose mark never reached the journal, and a
|
|
82
|
+
rename that went through although the share reported an error. The inode is
|
|
83
|
+
extra proof while the journal's numbers still tell files apart. A network
|
|
84
|
+
share can number its files anew on every mount, or work each number out from
|
|
85
|
+
the path, so that every rename changes it. The files the run moved show
|
|
86
|
+
whether it has, and then the inode is not asked for. On such a share a rename
|
|
87
|
+
whose reply was lost needs a size to go by: for a folder or an empty file it
|
|
88
|
+
is reported and stays as it is. When `apply` or `prune` moves folder
|
|
89
|
+
metadata, that move is in the journal too.
|
|
90
|
+
- **Undo does not guess and does not give up.** It never moves a file onto an
|
|
91
|
+
original path that has been taken, moves back only what it finds by name, kind
|
|
92
|
+
and size where the run was to put it, and keeps a folder a run created while
|
|
93
|
+
anything else is in it. A rename that `apply` refused before trying it,
|
|
94
|
+
because its source was gone or its destination taken, never touched the disk,
|
|
95
|
+
and `undo` leaves it alone. It stops when the library is not mounted. A row it
|
|
96
|
+
could not undo keeps its state and is tried again by the next `undo`, which is
|
|
97
|
+
also how an `undo` that was itself cut off is finished. A run is marked undone
|
|
98
|
+
only when every row is reversed or shown not to have happened, and `undo` then
|
|
99
|
+
leaves it alone. Runs are undone newest first: plain `undo` picks the newest
|
|
100
|
+
run that still stands, and `undo --run N` refuses while a later one does,
|
|
101
|
+
unless `--force` is given.
|
|
102
|
+
- **What undo cannot do.** It cannot bring back what is no longer there: a file
|
|
103
|
+
that was deleted, moved elsewhere or replaced since the run stays as a failed
|
|
104
|
+
row, and the run stays partly undone. So does a folder the run removed whose
|
|
105
|
+
name something else has taken since: it is not made again. A deleted file is
|
|
106
|
+
written back only where its path is free: a file there now, such as a
|
|
107
|
+
`.DS_Store` Finder has written again, stays, and the row waits with the bytes
|
|
108
|
+
in the journal. Until a deleted file is back, the rows that would move or
|
|
109
|
+
remove its folder wait too, and a file whose folder is gone is not written
|
|
110
|
+
back: the folder is not made again. An `undo` that is killed while it writes
|
|
111
|
+
a file back can leave a `.plexrenamer-restore-` file beside it, which keeps
|
|
112
|
+
that folder in place until it is removed by hand. An older run can
|
|
113
|
+
then still be undone by naming it with `--run` and adding `--force`; its rows
|
|
114
|
+
on paths the later run still stands on wait until that run is undone. A
|
|
115
|
+
folder's own attributes, such as Finder tags or a comment, go with it when it
|
|
116
|
+
is removed: `undo` recreates the folder without them. A folder and an empty
|
|
117
|
+
file are known by name and kind alone. Where the inode is not asked, two
|
|
118
|
+
things of one kind and size cannot be told apart either:
|
|
119
|
+
- a file of the same name, kind and size cannot be told from the one the run
|
|
120
|
+
moved;
|
|
121
|
+
- a move of a folder or an empty file that went through although the journal
|
|
122
|
+
has it as failed is reported, not reversed;
|
|
123
|
+
- where a run was to move such look-alikes along one path (a swap, or a to b
|
|
124
|
+
and b to c), the journal decides: a row behind the last row that carries a
|
|
125
|
+
mark never ran, and a rename refused before it was tried did not happen. A
|
|
126
|
+
leg that failed but may have gone through all the same, as after a lost
|
|
127
|
+
reply, is reported and stays as it is. In a journal written before refused
|
|
128
|
+
renames were marked as such, so is every failed leg. Each mark is committed
|
|
129
|
+
on its own, so marks go missing only when the journal loses writes it had
|
|
130
|
+
committed: in a power cut, or when it is copied without its `-wal` file. A
|
|
131
|
+
swap or a cycle of look-alikes whose marks went missing can look as if it
|
|
132
|
+
never got that far. Undo then leaves some or all of it where the run put
|
|
133
|
+
it, and marks the run undone all the same.
|
|
134
|
+
- **The plan is validated before anything moves.** Two sources mapping to one
|
|
135
|
+
destination, a vanished source, or an already-occupied destination all stop
|
|
136
|
+
the run before it starts, unless `--force` is given. Even then nothing is
|
|
137
|
+
overwritten: right before each rename its destination is looked at again, and
|
|
138
|
+
a taken one fails that row. Each file is also looked at again right before its
|
|
139
|
+
own move: one that has changed kind or size since the plan, or is no longer
|
|
140
|
+
the planned file by its inode where that tells, stays where it is. A file
|
|
141
|
+
planned empty is checked by kind alone.
|
|
142
|
+
- **One writer at a time.** The checks look at a path and then rename it, and
|
|
143
|
+
nothing locks the library. Run the tool while nothing else writes to it.
|
|
144
|
+
- **Pruning cannot escape the library.** Empty-directory removal is bounded to the
|
|
145
|
+
interior of the category directories, so it can never reach the volume root.
|
|
146
|
+
- **Renames are ordered.** A move whose destination is still occupied by another
|
|
147
|
+
pending move is deferred; a genuine swap is staged through a temporary name.
|
|
148
|
+
|
|
149
|
+
## Install
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pipx install plex-renamer # or: pip install plex-renamer
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
It needs Python 3.12 or newer and nothing outside the standard library. The
|
|
156
|
+
installed command is `plex-renamer`, with the same sub-commands and flags as
|
|
157
|
+
`python3 -m plexrenamer` below. The journal (`state/`), the decisions and the
|
|
158
|
+
reports live in the folder it runs from, so run it from the same folder every
|
|
159
|
+
time, or point `--db` at the journal.
|
|
160
|
+
|
|
161
|
+
Each GitHub release carries the wheel and the sdist with a `SHA256SUMS` file;
|
|
162
|
+
`sha256sum -c SHA256SUMS` in the download folder checks them.
|
|
163
|
+
|
|
164
|
+
## Usage
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
python3 -m plexrenamer plan # build and validate, write reports/
|
|
168
|
+
python3 -m plexrenamer apply --dry-run # op count, measured rate, ETA only
|
|
169
|
+
python3 -m plexrenamer apply # run it, with a progress bar
|
|
170
|
+
python3 -m plexrenamer verify # re-check the last run by name and size
|
|
171
|
+
python3 -m plexrenamer undo # reverse the newest run that stands
|
|
172
|
+
python3 -m plexrenamer undo --run 3 # reverse a specific run
|
|
173
|
+
python3 -m plexrenamer status # list runs and their op counts
|
|
174
|
+
python3 -m plexrenamer prune --dry-run # find shells emptied by a run
|
|
175
|
+
python3 -m plexrenamer clean --dry-run # list the ads and .DS_Store files
|
|
176
|
+
python3 -m plexrenamer clean # delete them, journalled
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`apply` deletes the ads and `.DS_Store` files itself once every move has gone
|
|
180
|
+
through, in the same run, so one `undo` reverses both. `clean` does only that.
|
|
181
|
+
|
|
182
|
+
Useful flags: `--root` (default `/Volumes/Multimedia`), `--categories` to limit the
|
|
183
|
+
run, `--episode-case lower` to emit `s01e01` instead of `S01E01`, `--decisions` to
|
|
184
|
+
point at a different override directory.
|
|
185
|
+
|
|
186
|
+
## How it works
|
|
187
|
+
|
|
188
|
+
1. **scan** walks the category directories read-only into a tree.
|
|
189
|
+
2. **classify** decides whether each folder is a movie, a show, or a grouping
|
|
190
|
+
folder holding several titles, and flags anything it is unsure about.
|
|
191
|
+
3. **decisions** overlays per-title corrections from `decisions/*.json`: canonical
|
|
192
|
+
title, year, database ids, movie/show, editions, or skip.
|
|
193
|
+
4. **planner** turns each title into concrete file moves.
|
|
194
|
+
5. **executor** validates, expands into `mkdir` / `rename` / `rmdir` operations,
|
|
195
|
+
journals them, runs them with a progress bar, deletes the ads and
|
|
196
|
+
`.DS_Store` files with their bytes in the journal, then checks that every
|
|
197
|
+
file is where the journal says, by name, kind and size, and that every
|
|
198
|
+
deleted file is gone.
|
|
199
|
+
6. **undo** settles every journal row against the disk, newest first.
|
|
200
|
+
|
|
201
|
+
## Naming applied
|
|
202
|
+
|
|
203
|
+
| Case | Result |
|
|
204
|
+
|---|---|
|
|
205
|
+
| Movie | `Category/Title (Year) {imdb-tt…}/Title (Year) {imdb-tt…}.ext` |
|
|
206
|
+
| Movie edition | `Title (Year) {edition-Final Cut}/Title (Year) {edition-Final Cut}.ext` |
|
|
207
|
+
| Split movie | `Title (Year) - part1.ext` … `part8` |
|
|
208
|
+
| Show | `Category/Show (Year) {imdb-tt…}/Season 01/Show (Year) - S01E01 - Title.ext` |
|
|
209
|
+
| Multi-episode file | `Show (Year) - S02E01-E03.ext` |
|
|
210
|
+
| Specials | `Specials/Show (Year) - S00E01 - Title.ext` |
|
|
211
|
+
| Subtitle | `<video stem>.en.srt`, `.en.forced.srt`, `.en.sdh.srt` |
|
|
212
|
+
| Extras | `Behind The Scenes`, `Deleted Scenes`, `Featurettes`, `Interviews`, `Scenes`, `Shorts`, `Trailers`, `Other` |
|
|
213
|
+
| Artwork | `poster.jpg`, `background.jpg` |
|
|
214
|
+
| DVD rip | `Title (Year)/VIDEO_TS/…` kept intact |
|
|
215
|
+
| Junk | dot-prefixed so the scanner skips it; `.hidden-` in front where a dot alone gives a name the system owns (`.DS_Store`, `._…`) or the name starts with `hidden-`; ads and `.DS_Store` files are then deleted |
|
|
216
|
+
|
|
217
|
+
## Deliberate deviations from the published examples
|
|
218
|
+
|
|
219
|
+
- **`S01E01`, not `s01e01`.** Plex's examples are lowercase and the token is
|
|
220
|
+
matched case-insensitively. Uppercase matches what every mainstream tool
|
|
221
|
+
already uses. `--episode-case lower` switches it.
|
|
222
|
+
- **A regional label before the language code** (`Movie (2019).Brazilian.pt.srt`).
|
|
223
|
+
Plex documents only `name.lang.ext`, which would collide when a release ships
|
|
224
|
+
several variants of one language. The label form is widely supported and is the
|
|
225
|
+
only way to keep the distinction. It is only used from the third file of a
|
|
226
|
+
language on: the first two take the two code widths Plex documents, `.pt.srt`
|
|
227
|
+
and `.por.srt`, so `.eng.srt` beside `.en.srt` is intended.
|
|
228
|
+
- **A three-letter tag the tool does not know is kept as the language code.**
|
|
229
|
+
Release tools name subtitle files by ISO-639-2 code, so `xyz.srt` becomes
|
|
230
|
+
`Movie (2019).xyz.srt`, and lint lists every such code. A name with no
|
|
231
|
+
language in it, or with `und` (undetermined), is filed as English.
|
|
232
|
+
- **One id tag per title.** Plex documents `{imdb-…}`, `{tmdb-…}` and `{tvdb-…}`
|
|
233
|
+
as alternatives, so exactly one is emitted, preferring IMDb.
|
|
234
|
+
|
|
235
|
+
## Tests
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
python3 -m unittest discover -s tests -v
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Covers the naming patterns, plan validation, cycle breaking, the apply/undo
|
|
242
|
+
round trip byte for byte, and an explicit assertion that no file content ever
|
|
243
|
+
disappears from the volume. The recovery tests cut `apply` and `undo` off at
|
|
244
|
+
every single step, before and after the step touches the disk, and check that
|
|
245
|
+
the tree comes back exactly, folder metadata included, also when the tree has
|
|
246
|
+
been swapped for a copy with new inode numbers in between, which is what a
|
|
247
|
+
remount does. The safety tests put somebody else's files in the way, take the
|
|
248
|
+
library away, lose a reply from the share and undo runs out of order, and check
|
|
249
|
+
that no foreign file is touched and that every row that could not be undone is
|
|
250
|
+
still there for the next try. The share tests put the library on the inode
|
|
251
|
+
numbers an SMB share was seen to hand out on macOS: numbers above 2^63 - 1,
|
|
252
|
+
which are more than an SQLite integer holds, and numbers worked out from the
|
|
253
|
+
path, which change with every rename. On the second kind the round trip,
|
|
254
|
+
recovery and safety tests run a second time, apart from two safety tests whose
|
|
255
|
+
promise rests on an inode that holds: there each is replaced by a test of what
|
|
256
|
+
is left of it. The look-alike tests do the same with swaps and chains of files
|
|
257
|
+
of one size, folders and empty files, cut off at every step, and undo an older
|
|
258
|
+
run with `--force` while a later run on its paths still stands. The lookup tests
|
|
259
|
+
make single lookups fail where a decision rests on them, and remove folders the
|
|
260
|
+
way macOS does, also on a FAT volume. The delete tests check that only ads and
|
|
261
|
+
real `.DS_Store` files go and nothing that changed since the listing, and that
|
|
262
|
+
`undo` writes each one back byte for byte with its time and mode: after a lost
|
|
263
|
+
reply, after a cut on either side of the delete, after a remount, and never
|
|
264
|
+
over a file that has taken its place.
|
|
265
|
+
|
|
266
|
+
CI runs them on macOS, on Python 3.12 to 3.15, for every pull request and every
|
|
267
|
+
push to main.
|
|
268
|
+
|
|
269
|
+
## Pruning shells left behind by a rename
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
python3 -m plexrenamer prune --dry-run # list what would go
|
|
273
|
+
python3 -m plexrenamer prune # remove them, journalled
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`prune` removes directories that hold no media anywhere below them, but only
|
|
277
|
+
those this tool actually emptied, established from the journal rather than
|
|
278
|
+
guessed. A folder that was already empty before the tool ever ran is reported
|
|
279
|
+
and left alone. Any folder metadata inside a pruned directory is moved to the
|
|
280
|
+
parent rather than deleted, and `undo` recreates the directory and puts that
|
|
281
|
+
file back. A directory that still holds anything else, `._` files included, is
|
|
282
|
+
kept and reported.
|
|
283
|
+
|
|
284
|
+
## Releases
|
|
285
|
+
|
|
286
|
+
A release is a tag `vX.Y.Z` on main that matches `__version__` in
|
|
287
|
+
`plexrenamer/__init__.py`. Pushing it runs `release.yml`: the tests and the
|
|
288
|
+
package build first, then the GitHub release with the wheel, the sdist and
|
|
289
|
+
their `SHA256SUMS`, then the upload of the same files to PyPI through Trusted
|
|
290
|
+
Publishing. The release refuses a tag that is not on main, or that moved after
|
|
291
|
+
the run started.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
plexrenamer/__init__.py,sha256=Tavnl7aqZ3SA0WyoyN8pJ7fs70jviEJ_-X2jTSVHMug,627
|
|
2
|
+
plexrenamer/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
|
|
3
|
+
plexrenamer/classify.py,sha256=R2_-Z3YzEVqjphTznQDZER5UIre-9_fOlovfECgPOss,16703
|
|
4
|
+
plexrenamer/cli.py,sha256=W7EjmsJIEuNpf_ViiBokD5YiADBVUj639KNJsCFm-wc,21283
|
|
5
|
+
plexrenamer/db.py,sha256=ipC_5ronni0KEShpaI8eGbGRLJGxdkO3HksQsDjU_9E,8285
|
|
6
|
+
plexrenamer/decisions.py,sha256=9RNujKZ_t_GhG_2HHCZDZTCiNAPT1CMX_KeScq0TSOo,3741
|
|
7
|
+
plexrenamer/executor.py,sha256=CFS0YwKCikWCcycceTLFglVyG9P41keb3LYAQSJtxYk,18689
|
|
8
|
+
plexrenamer/junk.py,sha256=Z8_av5v_3z_fbi71yCZZjKV1v0jFgDISrj2nbT2E-gk,7761
|
|
9
|
+
plexrenamer/lint.py,sha256=jYFy0fjY7z8kdmjWJnmf2bHLPtYdD22ru7K4S-C0ExI,5295
|
|
10
|
+
plexrenamer/naming.py,sha256=4rtQdrzcvlDR57BstViXpgeJrvmRDnNrrbf8KvA3iVk,4754
|
|
11
|
+
plexrenamer/ops.py,sha256=gKbD9EwaNORCXhw46gt0sZry9RqAALWDAzPnBMBjNkU,6254
|
|
12
|
+
plexrenamer/planner.py,sha256=b9EfS7qTVyNfox7vDHCUbwzonxTeVaruLd-Z6cUEpLg,26372
|
|
13
|
+
plexrenamer/progress.py,sha256=D2Eg1PgdWsG2pOC2DARdoyXowq4J_dCf32i1zHmjXCg,1921
|
|
14
|
+
plexrenamer/scan.py,sha256=8JgcCNzlXFaCnmNqovtHFFpScN05kBzfLLglnI6CYAc,3577
|
|
15
|
+
plexrenamer/sidecars.py,sha256=6fK0DjDwqqwb-RKdsr53Xe1IaDG0WSCDq81pr5N3a0Y,5411
|
|
16
|
+
plexrenamer/undo.py,sha256=sTd3cl5RAqPoCoFrF6fj68y90t0CGQzU8YriZzfixdM,17563
|
|
17
|
+
plexrenamer/util.py,sha256=TdffpcCa2w0iAdxDswf52gU6YVbyDdgIXg_7EalHkus,19548
|
|
18
|
+
plex_renamer-1.0.0.dist-info/METADATA,sha256=7uW1-YMyEcHAf1HEj5f_EYzp23uCAHwMSfnelQgPmkc,17685
|
|
19
|
+
plex_renamer-1.0.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
20
|
+
plex_renamer-1.0.0.dist-info/entry_points.txt,sha256=SHt7FdYjxVqkrKa53fWl8WfBvQLi6PiCRnMc7GrWilc,53
|
|
21
|
+
plex_renamer-1.0.0.dist-info/licenses/LICENSE,sha256=RSYs2DrUUAf2WxDQD9zD2zI_kuo-uSuhwr8Wo4tZPgs,1082
|
|
22
|
+
plex_renamer-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Agustin Gonzalez Nicolini
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
plexrenamer/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Plex-standard renamer for a local media volume.
|
|
2
|
+
|
|
3
|
+
Every mutation goes through a SQLite journal so that any run can be replayed
|
|
4
|
+
in reverse. Nothing in the library is ever deleted. The only things removed
|
|
5
|
+
there are empty directories: those a plan clears away, which undo recreates,
|
|
6
|
+
and those a run created, once they are empty again. The speed test removes
|
|
7
|
+
only the scratch files it wrote into a folder of its own.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "1.0.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def console():
|
|
14
|
+
"""The installed plex-renamer command: the commands it prints start with its own name."""
|
|
15
|
+
from . import cli
|
|
16
|
+
cli.PROG = "plex-renamer"
|
|
17
|
+
return cli.main()
|