import { useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { motion } from 'framer-motion' import { Film, Tv, AlertCircle } from '../lib/icons' import { useLibraryItems } from '../hooks/use-jellyfin' import PosterCard from '../components/ui/PosterCard' export default function DuplicatesPage() { const navigate = useNavigate() const { data, isLoading } = useLibraryItems(undefined, { includeItemTypes: ['Movie', 'Series'], sortBy: ['SortName'], sortOrder: ['Ascending'], limit: 5000, }) const items = data?.Items || [] const duplicates = useMemo(() => { // Group by TMDB ID first (most reliable) const byTmdb = new Map() const byNameYear = new Map() for (const item of items) { const tmdb = item.ProviderIds?.Tmdb if (tmdb) { const list = byTmdb.get(String(tmdb)) || [] list.push(item) byTmdb.set(String(tmdb), list) continue } // Fallback: normalized name + year + type const key = `${(item.Name || '').toLowerCase().trim()}|${item.ProductionYear || 'none'}|${item.Type}` const list = byNameYear.get(key) || [] list.push(item) byNameYear.set(key, list) } const groups: { key: string; label: string; items: typeof items }[] = [] for (const [tmdb, list] of byTmdb) { if (list.length >= 2) { const first = list[0] groups.push({ key: `tmdb-${tmdb}`, label: `${first.Name} (${first.ProductionYear || '?'})`, items: list, }) } } for (const [, list] of byNameYear) { if (list.length >= 2) { const first = list[0] groups.push({ key: `name-${first.Id}`, label: `${first.Name} (${first.ProductionYear || '?'}) - no TMDB match`, items: list, }) } } return groups.sort((a, b) => a.label.localeCompare(b.label)) }, [items]) return (
Tools

Duplicate finder

Scans your library for items that appear more than once - either by shared TMDB ID or by matching name + year.

{isLoading && items.length === 0 && (

Scanning your library...

)} {duplicates.length === 0 && !isLoading && (

No duplicates found

Every movie and series in your library has a unique identity. Nice and tidy.

)}
{duplicates.map((group, gi) => (
{group.items[0]?.Type === 'Series' ? ( ) : ( )}

{group.label}

{group.items.length} copies
{group.items.map((item, i) => ( item.Id && navigate(`/item/${item.Id}`)} /> ))}
))}
) }