update tmdb metadata handling

This commit is contained in:
tapframe 2025-10-30 11:44:53 +05:30
parent 4261891a35
commit 6fa53151fb
4 changed files with 79 additions and 36 deletions

View file

@ -459,7 +459,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio"; PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -490,8 +490,8 @@
"-lc++", "-lc++",
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app"; PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio"; PRODUCT_NAME = Nuvio;
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";

View file

@ -338,6 +338,8 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
const hydrateFromTmdb = async () => { const hydrateFromTmdb = async () => {
try { try {
if (!metadata?.id || !selectedSeason) return; if (!metadata?.id || !selectedSeason) return;
// Respect settings: skip TMDB enrichment when disabled
if (!settings?.enrichMetadataWithTMDB) return;
const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || []; const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || [];
if (currentSeasonEpisodes.length === 0) return; if (currentSeasonEpisodes.length === 0) return;
@ -375,7 +377,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
}; };
hydrateFromTmdb(); hydrateFromTmdb();
}, [metadata?.id, selectedSeason, groupedEpisodes]); }, [metadata?.id, selectedSeason, groupedEpisodes, settings?.enrichMetadataWithTMDB]);
// Enable item animations shortly after mount to avoid initial overlap/glitch // Enable item animations shortly after mount to avoid initial overlap/glitch
useEffect(() => { useEffect(() => {
@ -538,7 +540,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
// Get season poster URL (needed for both views) // Get season poster URL (needed for both views)
let seasonPoster = DEFAULT_PLACEHOLDER; let seasonPoster = DEFAULT_PLACEHOLDER;
if (seasonEpisodes[0]?.season_poster_path) { if (seasonEpisodes[0]?.season_poster_path) {
const tmdbUrl = tmdbService.getImageUrl(seasonEpisodes[0].season_poster_path, 'w500'); const tmdbUrl = tmdbService.getImageUrl(seasonEpisodes[0].season_poster_path, 'original');
if (tmdbUrl) seasonPoster = tmdbUrl; if (tmdbUrl) seasonPoster = tmdbUrl;
} else if (metadata?.poster) { } else if (metadata?.poster) {
seasonPoster = metadata.poster; seasonPoster = metadata.poster;
@ -653,18 +655,32 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
// Vertical layout episode card (traditional) // Vertical layout episode card (traditional)
const renderVerticalEpisodeCard = (episode: Episode) => { const renderVerticalEpisodeCard = (episode: Episode) => {
let episodeImage = EPISODE_PLACEHOLDER; // Resolve episode image with addon-first logic
if (episode.still_path) { const resolveEpisodeImage = (): string => {
// Check if still_path is already a full URL const candidates: Array<string | undefined | null> = [
if (episode.still_path.startsWith('http')) { // Add-on common fields
episodeImage = episode.still_path; (episode as any).thumbnail,
} else { (episode as any).image,
const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); (episode as any).thumb,
if (tmdbUrl) episodeImage = tmdbUrl; (episode as any)?.images?.still,
episode.still_path,
];
for (const cand of candidates) {
if (!cand) continue;
if (typeof cand === 'string' && (cand.startsWith('http://') || cand.startsWith('https://'))) {
return cand;
}
// TMDB relative paths only when enrichment is enabled
if (typeof cand === 'string' && cand.startsWith('/') && settings?.enrichMetadataWithTMDB) {
const tmdbUrl = tmdbService.getImageUrl(cand, 'original');
if (tmdbUrl) return tmdbUrl;
}
} }
} else if (metadata?.poster) { return metadata?.poster || EPISODE_PLACEHOLDER;
episodeImage = metadata.poster; };
}
let episodeImage = resolveEpisodeImage();
const episodeNumber = typeof episode.episode_number === 'number' ? episode.episode_number.toString() : ''; const episodeNumber = typeof episode.episode_number === 'number' ? episode.episode_number.toString() : '';
const seasonNumber = typeof episode.season_number === 'number' ? episode.season_number.toString() : ''; const seasonNumber = typeof episode.season_number === 'number' ? episode.season_number.toString() : '';
@ -695,7 +711,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
const effectiveVote = (tmdbOverride?.vote_average ?? episode.vote_average) || 0; const effectiveVote = (tmdbOverride?.vote_average ?? episode.vote_average) || 0;
const effectiveRuntime = tmdbOverride?.runtime ?? (episode as any).runtime; const effectiveRuntime = tmdbOverride?.runtime ?? (episode as any).runtime;
if (!episode.still_path && tmdbOverride?.still_path) { if (!episode.still_path && tmdbOverride?.still_path) {
const tmdbUrl = tmdbService.getImageUrl(tmdbOverride.still_path, 'w500'); const tmdbUrl = tmdbService.getImageUrl(tmdbOverride.still_path, 'original');
if (tmdbUrl) episodeImage = tmdbUrl; if (tmdbUrl) episodeImage = tmdbUrl;
} }
const progress = episodeProgress[episodeId]; const progress = episodeProgress[episodeId];
@ -877,7 +893,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 20 : 18 lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 20 : 18
} }
]} numberOfLines={isLargeScreen ? 4 : isTablet ? 3 : 2}> ]} numberOfLines={isLargeScreen ? 4 : isTablet ? 3 : 2}>
{episode.overview || 'No description available'} {(episode.overview || (episode as any).description || (episode as any).plot || (episode as any).synopsis || 'No description available')}
</Text> </Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
@ -886,18 +902,29 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
// Horizontal layout episode card (Netflix-style) // Horizontal layout episode card (Netflix-style)
const renderHorizontalEpisodeCard = (episode: Episode) => { const renderHorizontalEpisodeCard = (episode: Episode) => {
let episodeImage = EPISODE_PLACEHOLDER; const resolveEpisodeImage = (): string => {
if (episode.still_path) { const candidates: Array<string | undefined | null> = [
// Check if still_path is already a full URL (episode as any).thumbnail,
if (episode.still_path.startsWith('http')) { (episode as any).image,
episodeImage = episode.still_path; (episode as any).thumb,
} else { (episode as any)?.images?.still,
const tmdbUrl = tmdbService.getImageUrl(episode.still_path, 'w500'); episode.still_path,
if (tmdbUrl) episodeImage = tmdbUrl; ];
for (const cand of candidates) {
if (!cand) continue;
if (typeof cand === 'string' && (cand.startsWith('http://') || cand.startsWith('https://'))) {
return cand;
}
if (typeof cand === 'string' && cand.startsWith('/') && settings?.enrichMetadataWithTMDB) {
const tmdbUrl = tmdbService.getImageUrl(cand, 'original');
if (tmdbUrl) return tmdbUrl;
}
} }
} else if (metadata?.poster) { return metadata?.poster || EPISODE_PLACEHOLDER;
episodeImage = metadata.poster; };
}
let episodeImage = resolveEpisodeImage();
const episodeNumber = typeof episode.episode_number === 'number' ? episode.episode_number.toString() : ''; const episodeNumber = typeof episode.episode_number === 'number' ? episode.episode_number.toString() : '';
const seasonNumber = typeof episode.season_number === 'number' ? episode.season_number.toString() : ''; const seasonNumber = typeof episode.season_number === 'number' ? episode.season_number.toString() : '';
@ -1015,7 +1042,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
opacity: isTV ? 0.95 : isLargeTablet ? 0.9 : isTablet ? 0.9 : 0.9 opacity: isTV ? 0.95 : isLargeTablet ? 0.9 : isTablet ? 0.9 : 0.9
} }
]} numberOfLines={isLargeScreen ? 4 : 3}> ]} numberOfLines={isLargeScreen ? 4 : 3}>
{episode.overview || 'No description available'} {(episode.overview || (episode as any).description || (episode as any).plot || (episode as any).synopsis || 'No description available')}
</Text> </Text>
{/* Metadata Row */} {/* Metadata Row */}

View file

@ -1068,15 +1068,31 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
groupedAddonEpisodes[seasonNumber] = []; groupedAddonEpisodes[seasonNumber] = [];
} }
// Resolve image and description dynamically from arbitrary addons
const imageCandidate = (
video.thumbnail ||
video.image ||
video.thumb ||
(video.images && video.images.still) ||
null
);
const descriptionCandidate = (
video.overview ||
video.description ||
video.plot ||
video.synopsis ||
''
);
// Convert addon episode format to our Episode interface // Convert addon episode format to our Episode interface
const episode: Episode = { const episode: Episode = {
id: video.id, id: video.id,
name: video.name || video.title || `Episode ${episodeNumber}`, name: video.name || video.title || `Episode ${episodeNumber}`,
overview: video.overview || video.description || '', overview: descriptionCandidate,
season_number: seasonNumber, season_number: seasonNumber,
episode_number: episodeNumber, episode_number: episodeNumber,
air_date: video.released ? video.released.split('T')[0] : video.firstAired ? video.firstAired.split('T')[0] : '', air_date: video.released ? video.released.split('T')[0] : video.firstAired ? video.firstAired.split('T')[0] : '',
still_path: video.thumbnail, still_path: imageCandidate,
vote_average: parseFloat(video.rating) || 0, vote_average: parseFloat(video.rating) || 0,
runtime: undefined, runtime: undefined,
episodeString: `S${seasonNumber.toString().padStart(2, '0')}E${episodeNumber.toString().padStart(2, '0')}`, episodeString: `S${seasonNumber.toString().padStart(2, '0')}E${episodeNumber.toString().padStart(2, '0')}`,

View file

@ -842,7 +842,7 @@ const MetadataScreen: React.FC = () => {
return ( return (
<SafeAreaView <SafeAreaView
style={[styles.container, { backgroundColor: dynamicBackgroundColor }]} style={[styles.container, { backgroundColor: dynamicBackgroundColor }]}
edges={['bottom']} edges={[]}
> >
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" /> <StatusBar translucent backgroundColor="transparent" barStyle="light-content" />
<View style={styles.errorContainer}> <View style={styles.errorContainer}>
@ -913,7 +913,7 @@ const MetadataScreen: React.FC = () => {
<Animated.View style={[animatedBackgroundStyle, { flex: 1 }]}> <Animated.View style={[animatedBackgroundStyle, { flex: 1 }]}>
<SafeAreaView <SafeAreaView
style={[containerStyle, styles.container]} style={[containerStyle, styles.container]}
edges={['bottom']} edges={[]}
> >
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" animated /> <StatusBar translucent backgroundColor="transparent" barStyle="light-content" animated />