Check unmatched media at season and episode level
Checking only shows and movies was hiding the larger problem. Battlestar Galactica has a real plex:// GUID, a poster, and passes a show-level audit, while all 74 of its episodes and all 5 of its seasons carry local:// GUIDs and display as "Episode 1", "Episode 2" with no titles or artwork. Surveying every level found 97 unmatched episodes of 11531 and 7 unmatched seasons of 954, across four shows — none of it visible before. Also adds known-issues.conf, an accepted-findings list. The unmatched movie in the Movies library is the first entry: the 2003 BSG miniseries is catalogued as television, so the movie agent has no record to match it against and the item cannot be fixed in place. Listed entries are still printed, as [info] rather than [warn], so they stop counting as unresolved faults without ever being silently suppressed. The episode list is a ~30MB response, so it is written to a temp file before parsing rather than piped into jq. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3df1cca91c
commit
829bf009df
+63
-9
@@ -117,26 +117,80 @@ check_libraries() {
|
||||
done < <(jq -r '.MediaContainer.Directory[]|"\(.key)\t\(.title)\t\(.type)\t\(.refreshing)"' <<<"$sections")
|
||||
}
|
||||
|
||||
# Is this ratingKey listed in known-issues.conf? Echoes the reason if so.
|
||||
known_issue_reason() {
|
||||
local rk="${1:?}"
|
||||
local conf="$COLLECTION_DIR/known-issues.conf"
|
||||
[[ -f "$conf" ]] || return 1
|
||||
awk -v rk="$rk" '
|
||||
/^[[:space:]]*(#|$)/ { next }
|
||||
$1 == rk { $1=""; sub(/^[[:space:]]+/, ""); print; found=1; exit }
|
||||
END { exit !found }
|
||||
' "$conf"
|
||||
}
|
||||
|
||||
# An unmatched item keeps a local:// guid instead of a real agent guid, so it
|
||||
# will never receive metadata or artwork. Checked at every level: a show can be
|
||||
# matched while all of its episodes are not.
|
||||
check_unmatched() {
|
||||
section "Unmatched media"
|
||||
local sections
|
||||
sections="$(plex_get /library/sections)"
|
||||
local key title type code items unmatched
|
||||
[[ -n "$WORKDIR" ]] || WORKDIR="$(mktemp -d)"
|
||||
|
||||
local unmatched_filter='.MediaContainer.Metadata[]?
|
||||
|select((.guid//"")|test("^(local://|com\\.plexapp\\.agents\\.none)"))'
|
||||
|
||||
local key title type code
|
||||
while IFS=$'\t' read -r key title type; do
|
||||
case "$type" in
|
||||
movie) code=1 ;;
|
||||
show) code=2 ;;
|
||||
*) continue ;;
|
||||
*) info "${title}: skipped (${type} libraries are not match-checked)"; continue ;;
|
||||
esac
|
||||
items="$(plex_get "/library/sections/${key}/all" --get --data-urlencode "type=${code}")" || continue
|
||||
# An unmatched item keeps a local:// guid instead of a real agent guid.
|
||||
unmatched="$(jq -r '[.MediaContainer.Metadata[]?
|
||||
|select((.guid//"")|test("^(local://|com\\.plexapp\\.agents\\.none)"))]|length' <<<"$items")"
|
||||
if [[ "$unmatched" == "0" ]]; then
|
||||
ok "${title}: all items matched to an agent"
|
||||
|
||||
# ── top level: movies or shows, reported per item ────────────────────────
|
||||
plex_get "/library/sections/${key}/all" --get --data-urlencode "type=${code}" \
|
||||
-o "$WORKDIR/top.json" || { warn "${title}: could not list items"; continue; }
|
||||
jq -r "${unmatched_filter}|\"\(.ratingKey)\t\(.title)\"" "$WORKDIR/top.json" > "$WORKDIR/top_unmatched.tsv"
|
||||
|
||||
local n_top rk t reason accepted=0 flagged=0
|
||||
n_top="$(wc -l < "$WORKDIR/top_unmatched.tsv" | tr -d ' ')"
|
||||
if [[ "$n_top" == "0" ]]; then
|
||||
ok "${title}: all $(jq -r '.MediaContainer.Metadata|length' "$WORKDIR/top.json") items matched"
|
||||
else
|
||||
warn "${title}: ${unmatched} unmatched item(s) — they will never get metadata or artwork"
|
||||
while IFS=$'\t' read -r rk t; do
|
||||
if reason="$(known_issue_reason "$rk")"; then
|
||||
info "${title}: '${t}' (${rk}) unmatched — accepted: ${reason:0:96}"
|
||||
accepted=$((accepted + 1))
|
||||
else
|
||||
warn "${title}: '${t}' (${rk}) is unmatched — it will never get metadata or artwork"
|
||||
flagged=$((flagged + 1))
|
||||
fi
|
||||
done < "$WORKDIR/top_unmatched.tsv"
|
||||
(( flagged == 0 )) && ok "${title}: ${accepted} unmatched item(s), all accepted in known-issues.conf"
|
||||
fi
|
||||
|
||||
# ── seasons and episodes, reported per parent show ───────────────────────
|
||||
[[ "$code" == "2" ]] || continue
|
||||
local level lcode total bad
|
||||
for level in season:3 episode:4; do
|
||||
lcode="${level##*:}"
|
||||
# Large response (the episode list runs to tens of MB) — write to a file
|
||||
# rather than piping it into jq.
|
||||
plex_get "/library/sections/${key}/all" --get --data-urlencode "type=${lcode}" \
|
||||
-o "$WORKDIR/lvl.json" || { warn "${title}: could not list ${level%%:*}s"; continue; }
|
||||
total="$(jq -r '.MediaContainer.Metadata|length' "$WORKDIR/lvl.json")"
|
||||
bad="$(jq -r "[${unmatched_filter}]|length" "$WORKDIR/lvl.json")"
|
||||
if [[ "$bad" == "0" ]]; then
|
||||
ok "${title}: all ${total} ${level%%:*}s matched"
|
||||
else
|
||||
warn "${title}: ${bad}/${total} ${level%%:*}s unmatched, by show:"
|
||||
jq -r "${unmatched_filter}|.grandparentTitle // .parentTitle // .title" "$WORKDIR/lvl.json" \
|
||||
| sort | uniq -c | sort -rn \
|
||||
| awk '{c=$1; $1=""; sub(/^[[:space:]]+/,""); printf " %-40s %s\n", $0, c}'
|
||||
fi
|
||||
done
|
||||
done < <(jq -r '.MediaContainer.Directory[]|"\(.key)\t\(.title)\t\(.type)"' <<<"$sections")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user