feat(media): the audiobook relayout, filed by the book Plex matched #101
No files matched your search
+17
-11
@@ -35,11 +35,11 @@ by what the rows actually hold:
|
|||||||
rest. One rule that always resolves beats a shelf that is half author and half
|
rest. One rule that always resolves beats a shelf that is half author and half
|
||||||
narrator; the author stays visible in the book folder's own name, which is the
|
narrator; the author stays visible in the book folder's own name, which is the
|
||||||
title Plex shows.
|
title Plex shows.
|
||||||
* Nothing is guessed at. A chapter row with no book or no narrator parks — and so
|
* Nothing is guessed at. A chapter row with no book or no narrator parks — a
|
||||||
does one whose names are a tag-scan placeholder (`[Unknown Album]`), which
|
tag-scan placeholder (`[Unknown Album]`) names neither, since unrelated books
|
||||||
unrelated books share: filing on it would merge them onto one shelf. An audio
|
share it and filing on one merges them, and so does a chapter number the ripper
|
||||||
file Plex never matched parks, a video container parks, and a loose file in a
|
mistagged as the artist (`0.4`). An audio file Plex never matched parks, a
|
||||||
folder holding two books parks: nothing says which book owns it.
|
video container parks, and a loose file in a folder holding two books parks.
|
||||||
|
|
||||||
Nothing is deleted and nothing is overwritten. The output is a list of rename(2)
|
Nothing is deleted and nothing is overwritten. The output is a list of rename(2)
|
||||||
intents for `scripts/media-apply.py`, whose preflight refuses any target that is
|
intents for `scripts/media-apply.py`, whose preflight refuses any target that is
|
||||||
@@ -75,15 +75,21 @@ PLACEHOLDER_NARRATOR = {"[unknown artist]", "unknown artist", "unknown author",
|
|||||||
"<unknown>", "[unknown]", "unknown"}
|
"<unknown>", "[unknown]", "unknown"}
|
||||||
|
|
||||||
|
|
||||||
def identity(value, placeholders):
|
def identity(value, placeholders, needs_letter=False):
|
||||||
"""The tag value Plex matched, when it names something; '' when it does not.
|
"""The tag value Plex matched, when it names something; '' when it does not.
|
||||||
|
|
||||||
A trailing parenthesized group is stripped for the test only, because rippers
|
A trailing parenthesized group is stripped for the test only, because rippers
|
||||||
glue timestamps to the placeholder (`Unknown album (03/03/2015 21:39:55)`).
|
glue timestamps to the placeholder (`Unknown album (03/03/2015 21:39:55)`).
|
||||||
|
|
||||||
|
`needs_letter` is for a NARRATOR alone: a shelf is a person's name, and 272
|
||||||
|
chapters here carry a chapter number in the artist tag (`0.4`, `31`). The same
|
||||||
|
test must never judge a BOOK title, which may honestly be `1984`.
|
||||||
"""
|
"""
|
||||||
v = (value or "").strip()
|
v = (value or "").strip()
|
||||||
bare = re.sub(r"\s*\([^()]*\)\s*$", "", v).strip().casefold()
|
bare = re.sub(r"\s*\([^()]*\)\s*$", "", v).strip().casefold()
|
||||||
return "" if bare in placeholders else v
|
if bare in placeholders or (needs_letter and not any(c.isalpha() for c in v)):
|
||||||
|
return ""
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
# The characters Plex documents as breaking playback. They are dropped, not
|
# The characters Plex documents as breaking playback. They are dropped, not
|
||||||
@@ -197,7 +203,7 @@ def group_books():
|
|||||||
d = os.path.dirname(path)
|
d = os.path.dirname(path)
|
||||||
AUDIO_BY_DIR[d].add(path)
|
AUDIO_BY_DIR[d].add(path)
|
||||||
CHILD_OF[os.path.dirname(d)].add(d) # so a cover above Part 1/Part 2 sees both
|
CHILD_OF[os.path.dirname(d)].add(d) # so a cover above Part 1/Part 2 sees both
|
||||||
narrator = identity(r["narrator"], PLACEHOLDER_NARRATOR)
|
narrator = identity(r["narrator"], PLACEHOLDER_NARRATOR, needs_letter=True)
|
||||||
book = identity(r["book"], PLACEHOLDER_BOOK)
|
book = identity(r["book"], PLACEHOLDER_BOOK)
|
||||||
if not book:
|
if not book:
|
||||||
park(path, "a chapter row that names no book — Plex's album title is a "
|
park(path, "a chapter row that names no book — Plex's album title is a "
|
||||||
@@ -205,9 +211,9 @@ def group_books():
|
|||||||
"merge unrelated books onto one shelf")
|
"merge unrelated books onto one shelf")
|
||||||
continue
|
continue
|
||||||
if not narrator:
|
if not narrator:
|
||||||
park(path, "a chapter row that names no narrator — the album's artist "
|
park(path, "a chapter row that names no narrator — the artist row is a "
|
||||||
"row is a placeholder like [Unknown Artist], so no shelf "
|
"placeholder like [Unknown Artist] or a chapter number a "
|
||||||
"owns it")
|
"ripper mistagged as the artist, so no shelf owns it")
|
||||||
continue
|
continue
|
||||||
key = (narrator.casefold(), book.casefold(), (r["year"] or "").strip())
|
key = (narrator.casefold(), book.casefold(), (r["year"] or "").strip())
|
||||||
g = BOOKS.setdefault(key, {"narrators": [], "books": [], "year": key[2],
|
g = BOOKS.setdefault(key, {"narrators": [], "books": [], "year": key[2],
|
||||||
|
|||||||
@@ -352,9 +352,9 @@ Two laws are forced by what the rows actually hold:
|
|||||||
What it refuses, the music arm's list with one addition: a video container, audio
|
What it refuses, the music arm's list with one addition: a video container, audio
|
||||||
no chapter row names, and a loose file in a folder holding two books all park under
|
no chapter row names, and a loose file in a folder holding two books all park under
|
||||||
`_unsorted` with the path they have today — and so does a chapter whose row names
|
`_unsorted` with the path they have today — and so does a chapter whose row names
|
||||||
**no book or no narrator**, placeholder included (`[Unknown Album]`, `no title`,
|
**no book or no narrator**: a placeholder (`[Unknown Album]`, `[Unknown Artist]`)
|
||||||
`[Unknown Artist]`): unrelated books share those strings, so filing on one merges
|
that unrelated books share, or a chapter number mistagged as the artist (`0.4`).
|
||||||
them. `Various Artists` is not a placeholder — that is a multi-cast read.
|
`Various Artists` is not a placeholder — that is a multi-cast read.
|
||||||
|
|
||||||
## Laws
|
## Laws
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,10 @@ setup() {
|
|||||||
row "$H/Rip/tape.mp3" "Ann Reader" "Unknown album (03/03/2015 21:39:55)" "" 1 "One"
|
row "$H/Rip/tape.mp3" "Ann Reader" "Unknown album (03/03/2015 21:39:55)" "" 1 "One"
|
||||||
row "$H/NoWho/Real Book.mp3" "[Unknown Artist]" "A Named Book" 2001 1 "One"
|
row "$H/NoWho/Real Book.mp3" "[Unknown Artist]" "A Named Book" 2001 1 "One"
|
||||||
row "$H/Multicast/Genesis.mp3" "Various Artists" "The Bible" "" 1 "Genesis"
|
row "$H/Multicast/Genesis.mp3" "Various Artists" "The Bible" "" 1 "Genesis"
|
||||||
|
at "$H/Numbered/01 - Grimoire.mp3" 9000
|
||||||
|
at "$H/Digits/1984.mp3" 9000
|
||||||
|
row "$H/Numbered/01 - Grimoire.mp3" "0.4" "Grimoire of the Lamb" "" 1 "One"
|
||||||
|
row "$H/Digits/1984.mp3" "Ann Reader" "1984" 1949 1 "One"
|
||||||
|
|
||||||
# The enum TSV is what scripts/media-census.py enumerate writes: bytes, ext,
|
# The enum TSV is what scripts/media-census.py enumerate writes: bytes, ext,
|
||||||
# path, no header.
|
# path, no header.
|
||||||
@@ -358,6 +362,16 @@ PY
|
|||||||
"$AB/Various Artists/The Bible/Genesis.mp3" ]
|
"$AB/Various Artists/The Bible/Genesis.mp3" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "a chapter number mistagged as the narrator parks" {
|
||||||
|
plan
|
||||||
|
[ "$(kind "$H/Numbered/01 - Grimoire.mp3")" = "park" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a book whose title is digits still files, because only a name must have a letter" {
|
||||||
|
plan
|
||||||
|
[ "$(dst "$H/Digits/1984.mp3")" = "$AB/Ann Reader/1984 (1949)/1984.mp3" ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "the row, not the extension, decides what a chapter is" {
|
@test "the row, not the extension, decides what a chapter is" {
|
||||||
plan
|
plan
|
||||||
[ "$(kind "$G/NoExt/part01")" = "chapter" ]
|
[ "$(kind "$G/NoExt/part01")" = "chapter" ]
|
||||||
|
|||||||
Reference in new issue
Block a user