import { Link } from 'react-router-dom' import { Play } from 'lucide-react' import { imageURL } from '../api/client' import { ExternalPlayerButton } from '../components/ExternalPlayerButton' import type { Media } from '../types' import { seriesTitleFromPath } from '../utils/groupSeries' import { formatSize } from './libraryPageModel' type SeasonGroup = { season: number episodes: Media[] } type LibrarySeriesEpisodesProps = { loading: boolean selectedEpisodes: SeasonGroup[] selectedSeason: number | null visibleEpisodes: Media[] playbackFrom: string onSeasonChange: (season: number) => void } export function LibrarySeriesEpisodes({ loading, selectedEpisodes, selectedSeason, visibleEpisodes, playbackFrom, onSeasonChange, }: LibrarySeriesEpisodesProps) { if (loading) { return (
正在加载剧集…
) } const displaySeason = selectedSeason ?? selectedEpisodes[0]?.season ?? 1 return ( <>
{selectedEpisodes.map(({ season, episodes }) => ( ))}

{displaySeason === 0 ? '特别篇' : `第 ${displaySeason} 季`}

{visibleEpisodes.map((ep) => { const displayTitle = episodeDisplayTitle(ep, visibleEpisodes) return (
{ep.backdrop_url || ep.poster_url ? ( ) : ( {ep.episode_num || '—'} )}
{ep.episode_num > 0 && ( {ep.episode_num} )}

{displayTitle}

{ep.duration_sec > 0 ? `${Math.floor(ep.duration_sec / 60)} 分钟` : formatSize(ep.size_bytes)}

) })}
) } function episodeDisplayTitle(ep: Media, siblings: Media[]): string { let mainTitle = '' const epTitle = ep.episode_title?.trim() if (epTitle && !looksLikeSeriesTitle(ep, epTitle, siblings)) { mainTitle = epTitle } else { const mediaTitle = ep.title?.trim() if (mediaTitle && !looksLikeSeriesTitle(ep, mediaTitle, siblings)) { mainTitle = mediaTitle } } if (ep.episode_num > 0) { if (!mainTitle) { return `第 ${ep.episode_num} 集` } const prefixRegex = new RegExp(`^(第\\s*0*${ep.episode_num}\\s*集|ep?\\.?\\s*0*${ep.episode_num}\\b)`, 'i') if (prefixRegex.test(mainTitle)) { return mainTitle } return `第 ${ep.episode_num} 集 · ${mainTitle}` } return mainTitle || '未命名' } function looksLikeSeriesTitle(ep: Media, title: string, siblings: Media[]): boolean { const normalized = normalizeEpisodeTitle(title) if (!normalized) return true if (ep.original_name && normalizeEpisodeTitle(ep.original_name) === normalized) return true const pathTitle = seriesTitleFromPath(ep.path) if (pathTitle && normalizeEpisodeTitle(pathTitle) === normalized) return true const siblingTitles = new Set( siblings .map((item) => normalizeEpisodeTitle(item.title)) .filter(Boolean), ) return siblingTitles.size === 1 && siblingTitles.has(normalized) && siblings.length > 1 } function normalizeEpisodeTitle(value?: string): string { return (value ?? '') .toLowerCase() .replace(/\s*\((?:19|20)\d{2}\)\s*/g, ' ') .replace(/\s*\{(?:tmdb|tmdbid|douban|bangumi|bgm|thetvdb|tvdb)[\s:=#-]*[a-z0-9_-]+\}\s*/g, ' ') .replace(/[\s._-]+/g, ' ') .trim() }