diff --git a/src/app/api/nfo-refresh/route.ts b/src/app/api/nfo-refresh/route.ts index 576f4a1..dba57f3 100644 --- a/src/app/api/nfo-refresh/route.ts +++ b/src/app/api/nfo-refresh/route.ts @@ -120,7 +120,46 @@ export async function POST(request: NextRequest) { status: nfo.status ?? null, }), }) - return NextResponse.json({ updated: true, title: nfo.title, year: nfo.year }) + + // Optionally also refresh every episode NFO in this series + let episodesUpdated = 0 + const includeEpisodes = searchParams.get('includeEpisodes') === 'true' + if (includeEpisodes) { + type EpRow = { item_key: string; file_path: string | null; metadata: string | null } + const episodeRows = db + .prepare(`SELECT item_key, file_path, metadata FROM media_items WHERE item_type = 'tv_episode' AND item_key LIKE ?`) + .all(`${libraryId}:tv_episode:${encodedDirName}:%`) as EpRow[] + + const updateEp = db.prepare(` + UPDATE media_items SET title = @title, plot = @plot, metadata = @metadata WHERE item_key = @item_key + `) + + db.transaction(() => { + for (const ep of episodeRows) { + if (!ep.file_path) continue + const epDir = path.join(libraryRoot, path.dirname(ep.file_path)) + const baseName = path.basename(ep.file_path, path.extname(ep.file_path)) + const epNfo = parseEpisodeNfo(path.join(epDir, `${baseName}.nfo`)) + if (!epNfo) continue + const epMeta = ep.metadata ? JSON.parse(ep.metadata) : {} + updateEp.run({ + item_key: ep.item_key, + title: epNfo.title ?? null, + plot: epNfo.plot ?? null, + metadata: JSON.stringify({ + ...epMeta, + episodeNumber: epNfo.episode ?? epMeta.episodeNumber ?? null, + seasonNumber: epNfo.season ?? epMeta.seasonNumber ?? null, + aired: epNfo.aired ?? null, + rating: epNfo.rating ?? null, + }), + }) + episodesUpdated++ + } + })() + } + + return NextResponse.json({ updated: true, title: nfo.title, year: nfo.year, episodesUpdated }) } if (itemType === 'tv_episode') { diff --git a/src/components/tv/TvView.tsx b/src/components/tv/TvView.tsx index 36ccbfe..72e3f11 100644 --- a/src/components/tv/TvView.tsx +++ b/src/components/tv/TvView.tsx @@ -184,11 +184,18 @@ export default function TvView({ libraryId }: Props) { setRefreshingMeta(true) setWarnRefresh(false) const itemKey = `${libraryId}:tv_series:${selectedSeries.id}` + const currentId = selectedSeries.id fetch( - `/api/nfo-refresh?libraryId=${encodeURIComponent(libraryId)}&itemType=tv_series&itemKey=${encodeURIComponent(itemKey)}`, + `/api/nfo-refresh?libraryId=${encodeURIComponent(libraryId)}&itemType=tv_series&itemKey=${encodeURIComponent(itemKey)}&includeEpisodes=true`, { method: 'POST' } ) - .then(() => fetchSeries()) + .then(() => fetch(`/api/tv?libraryId=${encodeURIComponent(libraryId)}`)) + .then((r) => r.json()) + .then((data: TvSeries[]) => { + setSeries(data) + const updated = data.find((s) => s.id === currentId) + if (updated) setSelectedSeries(updated) + }) .finally(() => setRefreshingMeta(false)) } diff --git a/src/lib/tv.ts b/src/lib/tv.ts index 00cb0f1..bb67b65 100644 --- a/src/lib/tv.ts +++ b/src/lib/tv.ts @@ -3,6 +3,7 @@ import path from 'path' import type { TvSeries, TvSeason, TvEpisode } from '@/types' import { getDb } from './db' import { HIDDEN_FILES, VIDEO_EXTENSIONS, fileApiUrl, thumbnailApiUrl, findFile } from './media-utils' +import { parseTvShowNfo } from './nfo' function isVideoFile(name: string): boolean { return VIDEO_EXTENSIONS.has(path.extname(name).toLowerCase()) @@ -52,6 +53,7 @@ export function scanTvLibrary(libraryRoot: string, libraryId: string): TvSeries[ const posterFile = findFile(seriesPath, /^(poster|folder)$/i) const backdropFile = findFile(seriesPath, /^(backdrop|fanart|background)$/i) + const nfo = parseTvShowNfo(path.join(seriesPath, 'tvshow.nfo')) const seasonDirs = readDirs(seriesPath) const seasonDirCount = seasonDirs.filter((sd) => { @@ -67,11 +69,11 @@ export function scanTvLibrary(libraryRoot: string, libraryId: string): TvSeries[ series.push({ id, - title: dirName, - year: null, - plot: null, - genres: [], - status: null, + title: nfo?.title ?? dirName, + year: nfo?.year ?? null, + plot: nfo?.plot ?? null, + genres: nfo?.genres ?? [], + status: nfo?.status ?? null, posterUrl: posterFile ? thumbnailApiUrl(libraryId, path.join(dirName, posterFile)) : null,