diff --git a/client/src/routes/TvShows.svelte b/client/src/routes/TvShows.svelte index e7f836c..aaeca69 100644 --- a/client/src/routes/TvShows.svelte +++ b/client/src/routes/TvShows.svelte @@ -13,6 +13,9 @@ let refreshing = false; let rescanning = false; let error = null; + let mounted = false; + let lastLoadedCount = null; + let unsubscribeCount = null; let selectedShow = null; let selectedSeason = null; @@ -189,10 +192,12 @@ let filteredShows = []; const list = await resp.json(); shows = Array.isArray(list) ? list.map(normalizeShow) : []; tvShowCount.set(shows.length); + lastLoadedCount = shows.length; } catch (err) { error = err?.message || "TV dizileri alınamadı."; shows = []; tvShowCount.set(0); + lastLoadedCount = 0; } finally { loading = false; } @@ -251,6 +256,21 @@ let filteredShows = []; } } + onMount(() => { + mounted = true; + loadShows(); + unsubscribeCount = tvShowCount.subscribe((val) => { + if (!mounted) return; + if (loading || refreshing || rescanning) return; + if (val === lastLoadedCount) return; + loadShows(); + }); + return () => { + mounted = false; + unsubscribeCount && unsubscribeCount(); + }; + }); + function openShow(show) { if (!show) return; selectedShow = show; diff --git a/server/modules/tvDataStore.js b/server/modules/tvDataStore.js index b05314c..c0d1729 100644 --- a/server/modules/tvDataStore.js +++ b/server/modules/tvDataStore.js @@ -1,12 +1,19 @@ import { connectMongo } from "./db.js"; const COLLECTION = "tv_data"; +const TVDB_KEY_PREFIX = "tvdb-"; + +function canonicalTvdbKey(tvdbId) { + if (tvdbId === null || tvdbId === undefined) return null; + return `${TVDB_KEY_PREFIX}${tvdbId}`; +} async function getCollection() { const { db } = await connectMongo(); const col = db.collection(COLLECTION); await col.createIndex({ rootFolder: 1 }); - await col.createIndex({ tvdbId: 1 }); + await col.createIndex({ rootFolders: 1 }); + await col.createIndex({ tvdbId: 1 }, { sparse: true }); await col.createIndex({ updatedAt: -1 }); return col; } @@ -17,6 +24,7 @@ function buildDocument(key, rootFolder, seriesData) { _id: key, key, rootFolder, + rootFolders: rootFolder ? [rootFolder] : [], tvdbId, name: seriesData?.name || null, data: seriesData || {}, @@ -26,8 +34,40 @@ function buildDocument(key, rootFolder, seriesData) { export async function upsertTvSeries(key, rootFolder, seriesData) { const col = await getCollection(); - const doc = buildDocument(key, rootFolder, seriesData); - await col.updateOne({ _id: key }, { $set: doc }, { upsert: true }); + const tvdbId = seriesData?.id ?? seriesData?.tvdbId ?? null; + const canonicalKey = tvdbId !== null ? canonicalTvdbKey(tvdbId) : null; + const targetKey = canonicalKey || key; + + const existingByTvdb = + tvdbId !== null ? await col.findOne({ tvdbId }) : null; + const existing = + existingByTvdb || + (await col.findOne({ _id: targetKey })) || + (await col.findOne({ _id: key })); + + const desiredKey = canonicalKey || existingByTvdb?._id || targetKey; + const doc = buildDocument( + desiredKey, + rootFolder || existing?.rootFolder, + seriesData + ); + + const rootSet = new Set(existing?.rootFolders || []); + if (existing?.rootFolder) rootSet.add(existing.rootFolder); + if (rootFolder) rootSet.add(rootFolder); + doc.rootFolders = Array.from(rootSet); + doc.rootFolder = doc.rootFolder || doc.rootFolders[0] || null; + doc.tvdbId = tvdbId; + doc.key = canonicalKey || doc._id; + doc._id = doc.key; + + await col.updateOne({ _id: doc._id }, { $set: doc }, { upsert: true }); + + // Eğer eski bir anahtar farklıysa temizle + if (existing && existing._id !== doc._id) { + await col.deleteOne({ _id: existing._id }).catch(() => {}); + } + return doc.data; } @@ -39,10 +79,15 @@ export async function getTvSeriesByKey(key) { export async function getTvSeriesByRoot(rootFolder) { const col = await getCollection(); - const docs = await col.find({ rootFolder }).toArray(); + const docs = await col + .find({ + $or: [{ rootFolder }, { rootFolders: rootFolder }] + }) + .toArray(); return docs.map((doc) => ({ key: doc.key, rootFolder: doc.rootFolder, + rootFolders: doc.rootFolders || [], data: doc.data })); } @@ -53,13 +98,19 @@ export async function listAllTvSeries() { return docs.map((doc) => ({ key: doc.key, rootFolder: doc.rootFolder, + rootFolders: doc.rootFolders || [], data: doc.data })); } export async function listTvSeriesKeysForRoot(rootFolder) { const col = await getCollection(); - const docs = await col.find({ rootFolder }).project({ key: 1 }).toArray(); + const docs = await col + .find({ + $or: [{ rootFolder }, { rootFolders: rootFolder }] + }) + .project({ key: 1 }) + .toArray(); return docs.map((d) => d.key).filter(Boolean); } @@ -70,5 +121,34 @@ export async function removeTvSeriesByKey(key) { export async function removeTvSeriesByRoot(rootFolder) { const col = await getCollection(); - await col.deleteMany({ rootFolder }); + const cursor = col.find({ + $or: [{ rootFolder }, { rootFolders: rootFolder }] + }); + + // Silmek yerine root'u listeden çıkar; boş kalırsa kaydı kaldır + // Not: cursor.forEach async callback desteklemez, manual loop + while (await cursor.hasNext()) { + const doc = await cursor.next(); + const roots = new Set(doc.rootFolders || []); + if (doc.rootFolder) roots.add(doc.rootFolder); + roots.delete(rootFolder); + + if (roots.size === 0) { + await col.deleteOne({ _id: doc._id }); + } else { + const nextRootFolder = Array.from(roots)[0]; + await col.updateOne( + { _id: doc._id }, + { + $set: { + rootFolder: nextRootFolder, + rootFolders: Array.from(roots), + updatedAt: Date.now() + } + } + ); + } + } } + +export { canonicalTvdbKey }; diff --git a/server/server.js b/server/server.js index 2fba987..99adbe2 100644 --- a/server/server.js +++ b/server/server.js @@ -15,8 +15,10 @@ import { restoreTorrentsFromDisk } from "./modules/state.js"; import { createWebsocketServer, broadcastJson } from "./modules/websocket.js"; import { connectMongo, getDb } from "./modules/db.js"; import { + canonicalTvdbKey, getTvSeriesByKey as loadTvSeriesByKey, listAllTvSeries as listAllTvSeriesFromDb, + listTvSeriesKeysForRoot as listTvSeriesKeysForRootDb, removeTvSeriesByKey, removeTvSeriesByRoot, upsertTvSeries @@ -2815,22 +2817,63 @@ async function ensureSeriesData( seriesData.tvdbId = seriesId; } - let targetPaths = - existingPaths && existingPaths.key?.includes("__") ? existingPaths : null; - - if (!targetPaths) { - targetPaths = buildTvSeriesPaths( - normalizedRoot, - seriesId, - seriesInfo.title - ); - if (!targetPaths && existingPaths) { - targetPaths = existingPaths; + // Eğer aynı tvdbId için kanonik kayıt varsa onu yükle (bölümler birleşsin) + if (!existingPaths && seriesId) { + const canonicalKey = canonicalTvdbKey(seriesId); + try { + const existingCanonical = await loadTvSeriesByKey(canonicalKey); + if (existingCanonical) { + seriesData = existingCanonical; + existingPaths = tvSeriesPathsByKey(canonicalKey); + } + } catch (err) { + console.warn( + `⚠️ TV metadata kanonik kayıt okunamadı (${canonicalKey}): ${err.message}` + ); } } + // Her zaman kanonik anahtarı hedefle; legacy path varsa taşı + const canonicalPaths = buildTvSeriesPaths( + normalizedRoot, + seriesId, + seriesInfo.title + ); + let targetPaths = canonicalPaths || existingPaths || null; + if (!targetPaths && existingPaths) { + targetPaths = existingPaths; + } + if (!targetPaths) return null; + // Legacy klasör -> kanonik klasöre taşı + if ( + existingPaths && + canonicalPaths && + existingPaths.key !== canonicalPaths.key && + fs.existsSync(existingPaths.dir) + ) { + try { + if (!fs.existsSync(targetPaths.dir)) { + fs.mkdirSync(targetPaths.dir, { recursive: true }); + } + const entries = fs.readdirSync(existingPaths.dir, { withFileTypes: true }); + for (const entry of entries) { + const src = path.join(existingPaths.dir, entry.name); + const dest = path.join(targetPaths.dir, entry.name); + if (!fs.existsSync(dest)) { + fs.renameSync(src, dest); + } + } + fs.rmSync(existingPaths.dir, { recursive: true, force: true }); + console.log(`🔀 TV metadata klasörü taşındı: ${existingPaths.key} -> ${targetPaths.key}`); + } catch (err) { + console.warn( + `⚠️ TV metadata klasörü taşınamadı (${existingPaths.key} -> ${targetPaths.key}): ${err.message}` + ); + } + } + const showDir = targetPaths.dir; const seriesMetaPath = targetPaths.metadata; @@ -3169,6 +3212,7 @@ async function ensureSeriesData( still: fs.existsSync(stillPath) ? encodeTvDataPath(targetPaths.key, path.relative(showDir, stillPath)) : null, + folder: normalizedRoot, file: normalizedFile, mediaInfo: mediaInfo || null, tvdbEpisodeId: episodeTvdbId, @@ -3181,6 +3225,47 @@ async function ensureSeriesData( seasonContainer.episodeCount = Object.keys(seasonContainer.episodes).length; seasonContainer.updatedAt = Date.now(); + // Eski kayıt varsa sezon/bölüm verilerini koruyarak birleştir + try { + const existingDoc = await loadTvSeriesByKey(targetPaths.key); + const existingSeasons = + existingDoc && typeof existingDoc.seasons === "object" + ? existingDoc.seasons + : {}; + const incomingSeasons = + seriesData && typeof seriesData.seasons === "object" + ? seriesData.seasons + : {}; + + const mergedSeasons = { ...existingSeasons }; + + // Önce mevcut sezonları kopyala, sonra gelen verileri ekle/override et + for (const [seasonKey, incomingSeason] of Object.entries(incomingSeasons)) { + const prevSeason = mergedSeasons[seasonKey] || {}; + const prevEpisodes = + (prevSeason && typeof prevSeason.episodes === "object" + ? prevSeason.episodes + : {}) || {}; + const nextEpisodes = { ...prevEpisodes }; + + if (incomingSeason.episodes && typeof incomingSeason.episodes === "object") { + for (const [epKey, epVal] of Object.entries(incomingSeason.episodes)) { + nextEpisodes[epKey] = epVal; + } + } + + mergedSeasons[seasonKey] = { + ...prevSeason, + ...incomingSeason, + episodes: nextEpisodes + }; + } + + seriesData.seasons = mergedSeasons; + } catch (err) { + console.warn(`⚠️ TV metadata merge başarısız (db - ${targetPaths.key}): ${err.message}`); + } + ensureDirForFile(seriesMetaPath); try { await upsertTvSeries(targetPaths.key, normalizedRoot, seriesData); @@ -3234,12 +3319,16 @@ async function ensureSeriesData( } function tvSeriesKey(rootFolder, seriesId = null, fallbackTitle = null) { + const tvdbId = normalizeTvdbId(seriesId); + if (tvdbId !== null) { + return canonicalTvdbKey(tvdbId); + } + const normalizedRoot = rootFolder ? sanitizeRelative(rootFolder) : null; if (!normalizedRoot) return null; + let suffix = null; - if (seriesId) { - suffix = String(seriesId).toLowerCase(); - } else if (fallbackTitle) { + if (fallbackTitle) { const slug = String(fallbackTitle) .trim() .toLowerCase() @@ -3256,11 +3345,14 @@ function tvSeriesKey(rootFolder, seriesId = null, fallbackTitle = null) { function parseTvSeriesKey(key) { const normalized = sanitizeRelative(String(key || "")); + if (normalized.startsWith("tvdb-")) { + return { rootFolder: null, seriesId: normalized.slice(5), key: normalized, canonical: true }; + } if (!normalized.includes("__")) { - return { rootFolder: normalized, seriesId: null, key: normalized }; + return { rootFolder: normalized, seriesId: null, key: normalized, canonical: false }; } const [rootFolder, suffix] = normalized.split("__", 2); - return { rootFolder, seriesId: suffix || null, key: normalized }; + return { rootFolder, seriesId: suffix || null, key: normalized, canonical: false }; } function tvSeriesPathsByKey(key) { @@ -3279,10 +3371,11 @@ function tvSeriesPathsByKey(key) { } function tvSeriesPaths(rootFolderOrKey, seriesId = null, fallbackTitle = null) { + const rawKey = String(rootFolderOrKey || ""); if ( seriesId === null && fallbackTitle === null && - String(rootFolderOrKey || "").includes("__") + (rawKey.includes("__") || rawKey.startsWith("tvdb-")) ) { return tvSeriesPathsByKey(rootFolderOrKey); } @@ -3317,9 +3410,19 @@ function seasonAssetPaths(paths, seasonNumber) { }; } -function listTvSeriesKeysForRoot(rootFolder) { +async function listTvSeriesKeysForRoot(rootFolder) { const normalizedRoot = rootFolder ? sanitizeRelative(rootFolder) : null; if (!normalizedRoot) return []; + + // Önce DB'den oku (rootFolders desteğiyle) + try { + const dbKeys = await listTvSeriesKeysForRootDb(normalizedRoot); + if (dbKeys?.length) return dbKeys; + } catch (err) { + console.warn(`⚠️ TV metadata anahtarları DB'den alınamadı: ${err.message}`); + } + + // Legacy cache dizinlerini tara (geri uyumluluk) if (!fs.existsSync(TV_DATA_ROOT)) return []; const keys = []; try { @@ -3342,16 +3445,34 @@ function listTvSeriesKeysForRoot(rootFolder) { return keys; } -function removeSeriesData(rootFolder, seriesId = null) { +async function removeSeriesData(rootFolder, seriesId = null) { const keys = seriesId ? [tvSeriesKey(rootFolder, seriesId)].filter(Boolean) - : listTvSeriesKeysForRoot(rootFolder); + : await listTvSeriesKeysForRoot(rootFolder); + + if (!keys.length) return; + + if (seriesId === null) { + await removeTvSeriesByRoot(rootFolder); + } + for (const key of keys) { - removeTvSeriesByKey(key).catch((err) => - console.warn(`⚠️ TV metadata silinemedi (db - ${key}): ${err.message}`) - ); + if (seriesId !== null) { + await removeTvSeriesByKey(key).catch((err) => + console.warn(`⚠️ TV metadata silinemedi (db - ${key}): ${err.message}`) + ); + } const dir = tvSeriesDir(key); - if (dir && fs.existsSync(dir)) { + // Dizini ancak DB kaydı kalmadıysa sil + let keepDir = false; + try { + const stillExists = await loadTvSeriesByKey(key); + keepDir = Boolean(stillExists); + } catch { + /* no-op */ + } + + if (!keepDir && dir && fs.existsSync(dir)) { try { fs.rmSync(dir, { recursive: true, force: true }); console.log(`🧹 TV metadata silindi: ${dir}`); @@ -3365,10 +3486,10 @@ function removeSeriesData(rootFolder, seriesId = null) { function removeSeriesEpisode(rootFolder, relativeFilePath) { if (!rootFolder || !relativeFilePath) return; - const keys = listTvSeriesKeysForRoot(rootFolder); - if (!keys.length) return; - (async () => { + const keys = await listTvSeriesKeysForRoot(rootFolder); + if (!keys.length) return; + for (const key of keys) { const paths = tvSeriesPathsByKey(key); @@ -3408,7 +3529,7 @@ function removeSeriesEpisode(rootFolder, relativeFilePath) { if (!removed) continue; if (!Object.keys(seasons).length) { - removeSeriesData(seriesData._dupe?.folder || rootFolder, seriesData.id); + await removeSeriesData(seriesData._dupe?.folder || rootFolder, seriesData.id); continue; } @@ -3416,7 +3537,7 @@ function removeSeriesEpisode(rootFolder, relativeFilePath) { seriesData.updatedAt = Date.now(); try { - await upsertTvSeries(key, paths.rootFolder, seriesData); + await upsertTvSeries(key, rootFolder, seriesData); } catch (err) { console.warn( `⚠️ TV metadata güncellenemedi (db - ${key}): ${err.message}` @@ -3457,7 +3578,9 @@ async function importLegacySeriesMetadata() { try { const data = JSON.parse(fs.readFileSync(paths.metadata, "utf-8")) || {}; - await upsertTvSeries(key, paths.rootFolder, data); + const parsed = parseTvSeriesKey(key); + const rootForDoc = data?._dupe?.folder || parsed.rootFolder || null; + await upsertTvSeries(key, rootForDoc, data); imported += 1; try { fs.rmSync(paths.metadata, { force: true }); @@ -3493,7 +3616,9 @@ function purgeRootFolder(rootFolder) { removeAllThumbnailsForRoot(safe); removeMovieData(safe); - removeSeriesData(safe); + removeSeriesData(safe).catch((err) => + console.warn(`⚠️ TV metadata silinemedi (purge - ${safe}): ${err?.message || err}`) + ); const infoPath = path.join(DOWNLOAD_DIR, safe, INFO_FILENAME); if (fs.existsSync(infoPath)) { @@ -3814,10 +3939,10 @@ function renameSeriesDataPaths(rootFolder, oldRel, newRel) { const newPrefix = normalizeTrashPath(newRel); if (!oldPrefix || oldPrefix === newPrefix) return; - const keys = listTvSeriesKeysForRoot(rootFolder); - if (!keys.length) return; - (async () => { + const keys = await listTvSeriesKeysForRoot(rootFolder); + if (!keys.length) return; + for (const key of keys) { const paths = tvSeriesPathsByKey(key); let seriesData; @@ -3869,7 +3994,7 @@ function renameSeriesDataPaths(rootFolder, oldRel, newRel) { if (changed) { try { - await upsertTvSeries(key, paths.rootFolder, seriesData); + await upsertTvSeries(key, rootFolder, seriesData); } catch (err) { console.warn( `⚠️ TV metadata güncellenemedi (db - ${key}): ${err.message}` @@ -5686,7 +5811,7 @@ async function rebuildMovieMetadata({ clearCache = false, resetSeriesData = fals if (!videoEntries.length) { removeMovieData(folder); if (resetSeriesData) { - removeSeriesData(folder); + await removeSeriesData(folder); } const update = { primaryVideoPath: null, @@ -5713,7 +5838,7 @@ async function rebuildMovieMetadata({ clearCache = false, resetSeriesData = fals removeMovieData(folder); if (resetSeriesData) { - removeSeriesData(folder); + await removeSeriesData(folder); } const matches = []; @@ -5922,37 +6047,55 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { if (!key) continue; const paths = tvSeriesPathsByKey(key); const parsed = parseTvSeriesKey(key); - const rootFolder = parsed.rootFolder || doc.rootFolder; - if (!rootFolder) continue; - const infoForFolder = readInfoForRoot(rootFolder) || {}; - const infoFiles = infoForFolder.files || {}; - const infoEpisodes = infoForFolder.seriesEpisodes || {}; + const docFolders = Array.isArray(doc.rootFolders) + ? doc.rootFolders.filter(Boolean) + : []; + const primaryFolder = + doc.rootFolder || + parsed.rootFolder || + docFolders[0] || + null; + if (!primaryFolder) continue; + + const rootFolder = primaryFolder; + + const folderSet = new Set([rootFolder, ...docFolders]); const infoEpisodeIndex = new Map(); + const infoFilesByFolder = new Map(); - for (const [relPath, meta] of Object.entries(infoEpisodes)) { - if (!meta) continue; - const seasonNumber = toFiniteNumber( - meta.season ?? meta.seasonNumber ?? meta.seasonNum - ); - const episodeNumber = toFiniteNumber( - meta.episode ?? meta.episodeNumber ?? meta.episodeNum - ); - if (!Number.isFinite(seasonNumber) || !Number.isFinite(episodeNumber)) continue; + for (const folderName of folderSet) { + const infoForFolder = readInfoForRoot(folderName) || {}; + const infoFiles = infoForFolder.files || {}; + const infoEpisodes = infoForFolder.seriesEpisodes || {}; + infoFilesByFolder.set(folderName, infoFiles); - const normalizedRel = normalizeTrashPath(relPath); - const ext = path.extname(normalizedRel).toLowerCase(); - if (!VIDEO_EXTS.includes(ext)) continue; + for (const [relPath, meta] of Object.entries(infoEpisodes)) { + if (!meta) continue; + const seasonNumber = toFiniteNumber( + meta.season ?? meta.seasonNumber ?? meta.seasonNum + ); + const episodeNumber = toFiniteNumber( + meta.episode ?? meta.episodeNumber ?? meta.episodeNum + ); + if (!Number.isFinite(seasonNumber) || !Number.isFinite(episodeNumber)) + continue; - const absVideo = normalizedRel - ? path.join(DOWNLOAD_DIR, rootFolder, normalizedRel) - : null; - if (!absVideo || !fs.existsSync(absVideo)) continue; + const normalizedRel = normalizeTrashPath(relPath); + const ext = path.extname(normalizedRel).toLowerCase(); + if (!VIDEO_EXTS.includes(ext)) continue; - infoEpisodeIndex.set(`${seasonNumber}-${episodeNumber}`, { - relPath: normalizedRel, - meta - }); + const absVideo = normalizedRel + ? path.join(DOWNLOAD_DIR, folderName, normalizedRel) + : null; + if (!absVideo || !fs.existsSync(absVideo)) continue; + + infoEpisodeIndex.set(`${folderName}:${seasonNumber}-${episodeNumber}`, { + relPath: normalizedRel, + meta, + folder: folderName + }); + } } let seriesData = doc.data; @@ -5963,7 +6106,7 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { const seasonsObj = seriesData?.seasons || {}; if (!Object.keys(seasonsObj).length) { - removeSeriesData(rootFolder, seriesData.id ?? seriesData.tvdbId ?? null); + await removeSeriesData(rootFolder, seriesData.id ?? seriesData.tvdbId ?? null); continue; } @@ -5998,13 +6141,14 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { ), seasons: new Map(), primaryFolder: rootFolder, - folders: new Set([rootFolder]) + folders: new Set([rootFolder, ...docFolders]) }; aggregated.set(showKey, base); return base; })(); record.folders.add(rootFolder); + for (const extra of docFolders) record.folders.add(extra); if ( seriesData.overview && seriesData.overview.length > (record.overview?.length || 0) @@ -6043,11 +6187,12 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { const rawEpisodes = rawSeason.episodes || {}; for (const [episodeKey, rawEpisode] of Object.entries(rawEpisodes)) { if (!rawEpisode || typeof rawEpisode !== "object") continue; + const episodeRootForCleanup = rawEpisode.folder || rootFolder; const relativeFile = (rawEpisode.file || "").replace(/\\/g, "/"); if (relativeFile) { const absEpisodePath = path.join( DOWNLOAD_DIR, - rootFolder, + episodeRootForCleanup, relativeFile ); if (!fs.existsSync(absEpisodePath)) { @@ -6116,13 +6261,16 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { "0" )}E${String(episodeNumber).padStart(2, "0")}`; } - const infoEpisode = infoEpisodeIndex.get(`${seasonNumber}-${episodeNumber}`); + const episodeRoot = rawEpisode.folder || rootFolder; + const infoEpisode = infoEpisodeIndex.get(`${episodeRoot}:${seasonNumber}-${episodeNumber}`); if (infoEpisode?.relPath) { const normalizedRel = infoEpisode.relPath.replace(/^\/+/, ""); - const withRoot = `${rootFolder}/${normalizedRel}`.replace(/^\/+/, ""); + const withRoot = `${episodeRoot}/${normalizedRel}`.replace(/^\/+/, ""); normalizedEpisode.file = normalizedRel; normalizedEpisode.videoPath = withRoot; - const fileMeta = infoFiles[normalizedRel]; + const fileMeta = (infoFilesByFolder.get(episodeRoot) || {})[ + normalizedRel + ]; if (fileMeta?.mediaInfo && !normalizedEpisode.mediaInfo) { normalizedEpisode.mediaInfo = fileMeta.mediaInfo; } @@ -6138,10 +6286,10 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { const isExternal = /^https?:\/\//i.test(videoPath); const needsFolderPrefix = !isExternal && - !videoPath.startsWith(`${rootFolder}/`) && - !videoPath.startsWith(`/${rootFolder}/`); + !videoPath.startsWith(`${episodeRoot}/`) && + !videoPath.startsWith(`/${episodeRoot}/`); if (needsFolderPrefix) { - videoPath = `${rootFolder}/${videoPath}`.replace(/\\/g, "/"); + videoPath = `${episodeRoot}/${videoPath}`.replace(/\\/g, "/"); } const finalPath = videoPath.replace(/^\/+/, ""); if (finalPath !== rawVideoPath) { @@ -6149,7 +6297,7 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { } normalizedEpisode.videoPath = finalPath; } else if (relativeFile) { - normalizedEpisode.videoPath = `${rootFolder}/${relativeFile}` + normalizedEpisode.videoPath = `${episodeRoot}/${relativeFile}` .replace(/\\/g, "/") .replace(/^\/+/, ""); if (normalizedEpisode.videoPath !== rawVideoPath) { @@ -6178,7 +6326,7 @@ app.get("/api/tvshows", requireAuth, async (req, res) => { } } } - normalizedEpisode.folder = rootFolder; + normalizedEpisode.folder = episodeRoot; const existingEpisode = seasonRecord.episodes.get(episodeNumber); seasonRecord.episodes.set( @@ -6981,7 +7129,7 @@ app.post("/api/match/manual", requireAuth, async (req, res) => { // Mevcut movie_data ve TV verilerini temizle removeMovieData(rootFolder, relativeVideoPath); - removeSeriesData(rootFolder); + await removeSeriesData(rootFolder); // TMDB'den detaylı bilgi al const movieDetails = await tmdbFetch(`/movie/${movieId}`, { @@ -7063,7 +7211,7 @@ app.post("/api/match/manual", requireAuth, async (req, res) => { // Mevcut movie_data ve TV verilerini temizle removeMovieData(rootFolder); - removeSeriesData(rootFolder); + await removeSeriesData(rootFolder); // TVDB'den dizi bilgilerini al const extended = await fetchTvdbSeriesExtended(seriesId);