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

View file

@ -338,6 +338,8 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
const hydrateFromTmdb = async () => {
try {
if (!metadata?.id || !selectedSeason) return;
// Respect settings: skip TMDB enrichment when disabled
if (!settings?.enrichMetadataWithTMDB) return;
const currentSeasonEpisodes = groupedEpisodes[selectedSeason] || [];
if (currentSeasonEpisodes.length === 0) return;
@ -375,7 +377,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
};
hydrateFromTmdb();
}, [metadata?.id, selectedSeason, groupedEpisodes]);
}, [metadata?.id, selectedSeason, groupedEpisodes, settings?.enrichMetadataWithTMDB]);
// Enable item animations shortly after mount to avoid initial overlap/glitch
useEffect(() => {
@ -538,7 +540,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
// Get season poster URL (needed for both views)
let seasonPoster = DEFAULT_PLACEHOLDER;
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;
} else if (metadata?.poster) {
seasonPoster = metadata.poster;
@ -653,18 +655,32 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
// Vertical layout episode card (traditional)
const renderVerticalEpisodeCard = (episode: Episode) => {
let episodeImage = EPISODE_PLACEHOLDER;
if (episode.still_path) {
// 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;
// Resolve episode image with addon-first logic
const resolveEpisodeImage = (): string => {
const candidates: Array<string | undefined | null> = [
// Add-on common fields
(episode as any).thumbnail,
(episode as any).image,
(episode as any).thumb,
(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) {
episodeImage = metadata.poster;
}
return metadata?.poster || EPISODE_PLACEHOLDER;
};
let episodeImage = resolveEpisodeImage();
const episodeNumber = typeof episode.episode_number === 'number' ? episode.episode_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 effectiveRuntime = tmdbOverride?.runtime ?? (episode as any).runtime;
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;
}
const progress = episodeProgress[episodeId];
@ -869,7 +885,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
)}
</View>
</View>
<Text style={[
<Text style={[
styles.episodeOverview,
{
color: currentTheme.colors.mediumEmphasis,
@ -877,7 +893,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
lineHeight: isTV ? 22 : isLargeTablet ? 20 : isTablet ? 20 : 18
}
]} 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>
</View>
</TouchableOpacity>
@ -886,18 +902,29 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
// Horizontal layout episode card (Netflix-style)
const renderHorizontalEpisodeCard = (episode: Episode) => {
let episodeImage = EPISODE_PLACEHOLDER;
if (episode.still_path) {
// 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;
const resolveEpisodeImage = (): string => {
const candidates: Array<string | undefined | null> = [
(episode as any).thumbnail,
(episode as any).image,
(episode as any).thumb,
(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;
}
if (typeof cand === 'string' && cand.startsWith('/') && settings?.enrichMetadataWithTMDB) {
const tmdbUrl = tmdbService.getImageUrl(cand, 'original');
if (tmdbUrl) return tmdbUrl;
}
}
} else if (metadata?.poster) {
episodeImage = metadata.poster;
}
return metadata?.poster || EPISODE_PLACEHOLDER;
};
let episodeImage = resolveEpisodeImage();
const episodeNumber = typeof episode.episode_number === 'number' ? episode.episode_number.toString() : '';
const seasonNumber = typeof episode.season_number === 'number' ? episode.season_number.toString() : '';
@ -1006,7 +1033,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
</Text>
{/* Episode Description */}
<Text style={[
<Text style={[
styles.episodeDescriptionHorizontal,
{
fontSize: isTV ? 16 : isLargeTablet ? 15 : isTablet ? 14 : 12,
@ -1015,7 +1042,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
opacity: isTV ? 0.95 : isLargeTablet ? 0.9 : isTablet ? 0.9 : 0.9
}
]} 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>
{/* Metadata Row */}

View file

@ -1068,15 +1068,31 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
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
const episode: Episode = {
id: video.id,
name: video.name || video.title || `Episode ${episodeNumber}`,
overview: video.overview || video.description || '',
overview: descriptionCandidate,
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,
still_path: imageCandidate,
vote_average: parseFloat(video.rating) || 0,
runtime: undefined,
episodeString: `S${seasonNumber.toString().padStart(2, '0')}E${episodeNumber.toString().padStart(2, '0')}`,

View file

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