From 8cbda5c902b4ffff808c40774a5b111dceb0e665 Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 30 Jun 2025 15:55:41 +0530 Subject: [PATCH] Enhance SeriesContent and useMetadata hooks for improved image handling and cast fetching This update modifies the SeriesContent component to check if the episode still_path is a full URL before attempting to fetch it from TMDB, improving image loading efficiency. Additionally, the useMetadata hook has been enhanced to include caching for cast data, better error handling, and improved logging for debugging purposes. The logic for handling TMDB and IMDb IDs has been streamlined, ensuring a more robust data fetching process. --- src/components/metadata/SeriesContent.tsx | 18 ++- src/hooks/useMetadata.ts | 138 +++++++++++++--------- 2 files changed, 94 insertions(+), 62 deletions(-) diff --git a/src/components/metadata/SeriesContent.tsx b/src/components/metadata/SeriesContent.tsx index 6a9e731..76d0f71 100644 --- a/src/components/metadata/SeriesContent.tsx +++ b/src/components/metadata/SeriesContent.tsx @@ -250,8 +250,13 @@ export const SeriesContent: React.FC = ({ const renderVerticalEpisodeCard = (episode: Episode) => { let episodeImage = EPISODE_PLACEHOLDER; if (episode.still_path) { - const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); - if (tmdbUrl) episodeImage = tmdbUrl; + // Check if still_path is already a full URL + if (episode.still_path.startsWith('http')) { + episodeImage = episode.still_path; + } else { + const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); + if (tmdbUrl) episodeImage = tmdbUrl; + } } else if (metadata?.poster) { episodeImage = metadata.poster; } @@ -369,8 +374,13 @@ export const SeriesContent: React.FC = ({ const renderHorizontalEpisodeCard = (episode: Episode) => { let episodeImage = EPISODE_PLACEHOLDER; if (episode.still_path) { - const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); - if (tmdbUrl) episodeImage = tmdbUrl; + // Check if still_path is already a full URL + if (episode.still_path.startsWith('http')) { + episodeImage = episode.still_path; + } else { + const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); + if (tmdbUrl) episodeImage = tmdbUrl; + } } else if (metadata?.poster) { episodeImage = metadata.poster; } diff --git a/src/hooks/useMetadata.ts b/src/hooks/useMetadata.ts index 722ff17..26e6f4d 100644 --- a/src/hooks/useMetadata.ts +++ b/src/hooks/useMetadata.ts @@ -184,20 +184,23 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat }; const loadCast = async () => { - if (!metadata || !metadata.id) return; - + logger.log('[loadCast] Starting cast fetch for:', id); setLoadingCast(true); try { + // Check cache first + const cachedCast = cacheService.getCast(id, type); + if (cachedCast) { + logger.log('[loadCast] Using cached cast data'); + setCast(cachedCast); + setLoadingCast(false); + return; + } + // Handle TMDB IDs - let metadataId = id; - let metadataType = type; - if (id.startsWith('tmdb:')) { - const extractedTmdbId = id.split(':')[1]; - logger.log('[loadCast] Using extracted TMDB ID:', extractedTmdbId); - - // For TMDB IDs, we'll use the TMDB API directly - const castData = await tmdbService.getCredits(parseInt(extractedTmdbId), type); + const tmdbId = id.split(':')[1]; + logger.log('[loadCast] Using TMDB ID directly:', tmdbId); + const castData = await tmdbService.getCredits(parseInt(tmdbId), type); if (castData && castData.cast) { const formattedCast = castData.cast.map((actor: any) => ({ id: actor.id, @@ -205,49 +208,41 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat character: actor.character, profile_path: actor.profile_path })); + logger.log(`[loadCast] Found ${formattedCast.length} cast members from TMDB`); setCast(formattedCast); + cacheService.setCast(id, type, formattedCast); setLoadingCast(false); - return formattedCast; + return; } - setLoadingCast(false); - return []; - } - - // Continue with the existing logic for non-TMDB IDs - const cachedCast = cacheService.getCast(id, type); - if (cachedCast) { - setCast(cachedCast); - setLoadingCast(false); - return; } - // Load cast in parallel with a fallback to empty array - const castLoadingPromise = loadWithFallback(async () => { - const tmdbId = await withTimeout( - tmdbService.findTMDBIdByIMDB(id), - API_TIMEOUT - ); - - if (tmdbId) { - const castData = await withTimeout( - tmdbService.getCredits(tmdbId, type), - API_TIMEOUT, - { cast: [], crew: [] } - ); - - if (castData.cast && castData.cast.length > 0) { - setCast(castData.cast); - cacheService.setCast(id, type, castData.cast); - return castData.cast; - } - } - return []; - }, []); + // Handle IMDb IDs or convert to TMDB ID + let tmdbId; + if (id.startsWith('tt')) { + logger.log('[loadCast] Converting IMDb ID to TMDB ID'); + tmdbId = await tmdbService.findTMDBIdByIMDB(id); + } - await castLoadingPromise; + if (tmdbId) { + logger.log('[loadCast] Fetching cast using TMDB ID:', tmdbId); + const castData = await tmdbService.getCredits(tmdbId, type); + if (castData && castData.cast) { + const formattedCast = castData.cast.map((actor: any) => ({ + id: actor.id, + name: actor.name, + character: actor.character, + profile_path: actor.profile_path + })); + logger.log(`[loadCast] Found ${formattedCast.length} cast members`); + setCast(formattedCast); + cacheService.setCast(id, type, formattedCast); + } + } else { + logger.warn('[loadCast] Could not find TMDB ID for cast fetch'); + } } catch (error) { - console.error('Failed to load cast:', error); - setCast([]); + logger.error('[loadCast] Failed to load cast:', error); + // Don't clear existing cast data on error } finally { setLoadingCast(false); } @@ -462,13 +457,6 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat setMetadata(content.value); // Update cache cacheService.setMetadata(id, type, content.value); - - if (type === 'series') { - // Load series data after the enhanced metadata is processed - setTimeout(() => { - loadSeriesData().catch(console.error); - }, 100); - } } else { throw new Error('Content not found'); } @@ -499,7 +487,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat const groupedAddonEpisodes: GroupedEpisodes = {}; addonVideos.forEach((video: any) => { - const seasonNumber = video.season || 1; + const seasonNumber = video.season; + if (!seasonNumber || seasonNumber < 1) { + return; // Skip season 0, which often contains extras + } const episodeNumber = video.episode || video.number || 1; if (!groupedAddonEpisodes[seasonNumber]) { @@ -514,7 +505,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat season_number: seasonNumber, episode_number: episodeNumber, air_date: video.released ? video.released.split('T')[0] : video.firstAired ? video.firstAired.split('T')[0] : '', - still_path: video.thumbnail ? video.thumbnail.replace('https://image.tmdb.org/t/p/w500', '') : null, + still_path: video.thumbnail, vote_average: parseFloat(video.rating) || 0, runtime: undefined, episodeString: `S${seasonNumber.toString().padStart(2, '0')}E${episodeNumber.toString().padStart(2, '0')}`, @@ -531,6 +522,32 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat }); logger.log(`📺 Processed addon episodes into ${Object.keys(groupedAddonEpisodes).length} seasons`); + + // Fetch season posters from TMDB + try { + const tmdbIdToUse = tmdbId || (id.startsWith('tt') ? await tmdbService.findTMDBIdByIMDB(id) : null); + if (tmdbIdToUse) { + if (!tmdbId) setTmdbId(tmdbIdToUse); + const showDetails = await tmdbService.getTVShowDetails(tmdbIdToUse); + if (showDetails?.seasons) { + Object.keys(groupedAddonEpisodes).forEach(seasonStr => { + const seasonNum = parseInt(seasonStr, 10); + const seasonInfo = showDetails.seasons.find(s => s.season_number === seasonNum); + const seasonPosterPath = seasonInfo?.poster_path; + if (seasonPosterPath) { + groupedAddonEpisodes[seasonNum] = groupedAddonEpisodes[seasonNum].map(ep => ({ + ...ep, + season_poster_path: seasonPosterPath, + })); + } + }); + logger.log('🖼️ Successfully fetched and attached TMDB season posters to addon episodes.'); + } + } + } catch (error) { + logger.error('Failed to fetch TMDB season posters for addon episodes:', error); + } + setGroupedEpisodes(groupedAddonEpisodes); // Set the first available season @@ -561,11 +578,16 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat ]); const transformedEpisodes: GroupedEpisodes = {}; - Object.entries(allEpisodes).forEach(([season, episodes]) => { - const seasonInfo = showDetails?.seasons?.find(s => s.season_number === parseInt(season)); + Object.entries(allEpisodes).forEach(([seasonStr, episodes]) => { + const seasonNum = parseInt(seasonStr, 10); + if (seasonNum < 1) { + return; // Skip season 0, which often contains extras + } + + const seasonInfo = showDetails?.seasons?.find(s => s.season_number === seasonNum); const seasonPosterPath = seasonInfo?.poster_path; - transformedEpisodes[parseInt(season)] = episodes.map(episode => ({ + transformedEpisodes[seasonNum] = episodes.map(episode => ({ ...episode, episodeString: `S${episode.season_number.toString().padStart(2, '0')}E${episode.episode_number.toString().padStart(2, '0')}`, season_poster_path: seasonPosterPath || null