handle shows without season folders #10

Merged
gpatti merged 2 commits from navigation into main 2026-04-06 02:18:59 +00:00
2 changed files with 31 additions and 2 deletions
Showing only changes of commit 87a90a88bc - Show all commits

View File

@@ -73,7 +73,15 @@ export default function TvView({ libraryId }: Props) {
setError(null) setError(null)
fetch(`/api/tv?libraryId=${encodeURIComponent(libraryId)}&seriesId=${encodeURIComponent(s.id)}`) fetch(`/api/tv?libraryId=${encodeURIComponent(libraryId)}&seriesId=${encodeURIComponent(s.id)}`)
.then((r) => r.json()) .then((r) => r.json())
.then((data) => { setSeasons(data); setLoading(false) }) .then((data: TvSeason[]) => {
setSeasons(data)
setLoading(false)
// Flat series: a single synthetic season (id='.') means episodes live
// directly in the series folder — skip the seasons screen automatically.
if (data.length === 1 && data[0].id === '.') {
openSeason(data[0])
}
})
.catch(() => { setError('Failed to load seasons'); setLoading(false) }) .catch(() => { setError('Failed to load seasons'); setLoading(false) })
} }

View File

@@ -83,10 +83,14 @@ export function scanTvLibrary(libraryRoot: string, libraryId: string): TvSeries[
const backdropFile = findFile(seriesPath, /^(backdrop|fanart|background)$/i) const backdropFile = findFile(seriesPath, /^(backdrop|fanart|background)$/i)
const seasonDirs = readDirs(seriesPath) const seasonDirs = readDirs(seriesPath)
const seasonCount = seasonDirs.filter((sd) => { const seasonDirCount = seasonDirs.filter((sd) => {
const sdPath = path.join(seriesPath, sd) const sdPath = path.join(seriesPath, sd)
return countVideosInDir(sdPath) > 0 return countVideosInDir(sdPath) > 0
}).length }).length
// If no season subdirectories contain videos, fall back to counting
// video files directly in the series root (flat/seasonless structure).
const seasonCount =
seasonDirCount > 0 ? seasonDirCount : countVideosInDir(seriesPath) > 0 ? 1 : 0
const id = encodeURIComponent(dirName) const id = encodeURIComponent(dirName)
@@ -150,6 +154,23 @@ export function scanTvSeasons(
}) })
} }
// Flat series fallback: no season subdirectories have videos, but the
// series root itself does. Return a single synthetic season with id '.'
// so that scanTvEpisodes can scan the series directory directly.
if (seasons.length === 0 && countVideosInDir(seriesPath) > 0) {
const posterFile = findFile(seriesPath, /^(poster|folder)$/i)
seasons.push({
id: '.',
seriesId,
title: 'Episodes',
seasonNumber: null,
posterUrl: posterFile
? thumbnailApiUrl(libraryId, path.join(seriesDirName, posterFile))
: null,
episodeCount: countVideosInDir(seriesPath),
})
}
return seasons.sort((a, b) => { return seasons.sort((a, b) => {
if (a.seasonNumber !== null && b.seasonNumber !== null) { if (a.seasonNumber !== null && b.seasonNumber !== null) {
return a.seasonNumber - b.seasonNumber return a.seasonNumber - b.seasonNumber