diff --git a/.cache/emby-user-context.json b/.cache/emby-user-context.json index 37db410..59a013f 100644 --- a/.cache/emby-user-context.json +++ b/.cache/emby-user-context.json @@ -1386,7 +1386,7 @@ "isPlayed": true }, { - "id": "1330174", + "id": "1345802", "name": "Project Nazi: The Blueprints of Evil", "type": "Series", "seriesName": null, @@ -1410,15 +1410,7 @@ "isPlayed": true }, { - "id": "1329610", - "name": "Prodigal Son", - "type": "Series", - "seriesName": null, - "datePlayed": null, - "isPlayed": true - }, - { - "id": "1330186", + "id": "1345819", "name": "We Are Who We Are", "type": "Series", "seriesName": null, @@ -1426,7 +1418,7 @@ "isPlayed": true }, { - "id": "1328552", + "id": "1345818", "name": "WandaVision", "type": "Series", "seriesName": null, @@ -1434,7 +1426,7 @@ "isPlayed": true }, { - "id": "1328551", + "id": "1345812", "name": "The Falcon and The Winter Soldier", "type": "Series", "seriesName": null, @@ -1442,7 +1434,7 @@ "isPlayed": true }, { - "id": "1328548", + "id": "1345806", "name": "Snowpiercer", "type": "Series", "seriesName": null, @@ -1450,15 +1442,15 @@ "isPlayed": true }, { - "id": "1330183", - "name": "The Outsider", + "id": "1345815", + "name": "The Outsider (2020)", "type": "Series", "seriesName": null, "datePlayed": null, "isPlayed": true }, { - "id": "1328549", + "id": "1345808", "name": "Station Eleven", "type": "Series", "seriesName": null, @@ -1466,7 +1458,7 @@ "isPlayed": true }, { - "id": "1330988", + "id": "1348189", "name": "Moon Knight", "type": "Series", "seriesName": null, @@ -1474,7 +1466,7 @@ "isPlayed": true }, { - "id": "1330175", + "id": "1345803", "name": "Rise of the Nazis", "type": "Series", "seriesName": null, @@ -1482,23 +1474,15 @@ "isPlayed": true }, { - "id": "1329606", - "name": "Coyote", + "id": "1345813", + "name": "The Head (2020)", "type": "Series", "seriesName": null, "datePlayed": null, "isPlayed": true }, { - "id": "1330182", - "name": "The Head", - "type": "Series", - "seriesName": null, - "datePlayed": null, - "isPlayed": true - }, - { - "id": "1312988", + "id": "1344688", "name": "Domina", "type": "Series", "seriesName": null, @@ -1506,7 +1490,7 @@ "isPlayed": true }, { - "id": "1330172", + "id": "1345800", "name": "Obi-Wan Kenobi", "type": "Series", "seriesName": null, @@ -1514,7 +1498,7 @@ "isPlayed": true }, { - "id": "1328550", + "id": "1345810", "name": "The Book of Boba Fett", "type": "Series", "seriesName": null, @@ -1522,7 +1506,7 @@ "isPlayed": true }, { - "id": "1330178", + "id": "1345807", "name": "Stanley Tucci: Searching for Italy", "type": "Series", "seriesName": null, @@ -1546,7 +1530,7 @@ "isPlayed": true }, { - "id": "1330989", + "id": "1348190", "name": "The Man Who Fell to Earth", "type": "Series", "seriesName": null, @@ -1619,7 +1603,7 @@ } ], "excludedFolderLookup": {}, - "lastSyncedAt": "2026-04-26T22:31:15.087Z" + "lastSyncedAt": "2026-04-28T01:58:15.752Z" }, "ff5a825760c24f9ab6f63b04513909a4": { "views": [ diff --git a/src/lib/GenreCleanupPage.svelte b/src/lib/GenreCleanupPage.svelte new file mode 100644 index 0000000..1b04364 --- /dev/null +++ b/src/lib/GenreCleanupPage.svelte @@ -0,0 +1,662 @@ + + +
+ + +
+
+
+
+ + +
+
+ event.key === 'Enter' && searchLibrary()} + placeholder={mediaType === 'Movie' ? 'Search movies...' : 'Search shows...'} + /> + +
+

The suggested genre uses TMDB order as a hint, but you can change the choice before writing it back to Emby.

+
+ + {#if searchError} +
{searchError}
+ {/if} + {#if actionError} +
{actionError}
+ {:else if actionMessage} +
{actionMessage}
+ {/if} + +
+
+
+ +

Matches

+
+ Inspect one title at a time or batch the current search results. +
+ + {#if !searchBusy && !searchResults.length} +
Search for a movie or show to start cleaning up its genres.
+ {:else} +
+ {#each searchResults as item} + {@const inspection = inspections[item.id]} +
+
+
+
{item.name}
+
+ {item.type}{item.year ? ` · ${item.year}` : ''} + {#if inspection?.updated} · updated{/if} +
+
+ +
+ +
+ Emby + {(inspection?.currentGenres || item.genres || []).join(', ') || 'None'} +
+ + {#if inspection?.error} +
{inspection.error}
+ {:else if inspection?.item} +
+
+ TMDB + {inspection.tmdb?.genres?.join(', ') || 'No genres returned'} +
+
+ Match + + {#if inspection.tmdb?.tmdbId} + {inspection.tmdb.source === 'providerId' ? 'Provider ID match' : 'Title search match'} · TMDB {inspection.tmdb.tmdbId} + {:else} + No TMDB match + {/if} + +
+ + {#if inspection.tmdb?.genres?.length} +
+ +
+ +
+
+ {/if} +
+ {/if} +
+ {/each} +
+ {/if} +
+
+ + +
+
+ + diff --git a/src/lib/genre-cleanup.js b/src/lib/genre-cleanup.js new file mode 100644 index 0000000..19cb0b7 --- /dev/null +++ b/src/lib/genre-cleanup.js @@ -0,0 +1,54 @@ +function normalizeGenreKey(value) { + return String(value || '') + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, ''); +} + +export function normalizeGenreNames(values) { + const seen = new Set(); + const names = []; + + for (const value of values || []) { + const trimmed = String(value || '').trim(); + if (!trimmed) continue; + const key = normalizeGenreKey(trimmed); + if (!key || seen.has(key)) continue; + seen.add(key); + names.push(trimmed); + } + + return names; +} + +export function pickSuggestedGenre(tmdbGenres, currentGenres = []) { + const normalizedTmdbGenres = normalizeGenreNames(tmdbGenres); + if (!normalizedTmdbGenres.length) return ''; + + const currentKeys = new Set(normalizeGenreNames(currentGenres).map(normalizeGenreKey)); + const matched = normalizedTmdbGenres.find((genre) => currentKeys.has(normalizeGenreKey(genre))); + + return matched || normalizedTmdbGenres[0]; +} + +export function buildSingleGenreUpdate(item, genreName) { + const selectedGenre = String(genreName || '').trim(); + if (!selectedGenre) { + throw new Error('A genre is required'); + } + + const nextItem = JSON.parse(JSON.stringify(item || {})); + const existingGenreItems = Array.isArray(item?.GenreItems) ? item.GenreItems : []; + const matchedGenreItem = existingGenreItems.find( + (entry) => normalizeGenreKey(entry?.Name) === normalizeGenreKey(selectedGenre) + ); + + nextItem.Genres = [selectedGenre]; + nextItem.GenreItems = [ + matchedGenreItem + ? { ...matchedGenreItem, Name: selectedGenre } + : { Name: selectedGenre } + ]; + + return nextItem; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a5bf34b..2b7fe8b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,6 +1,7 @@