mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-26 19:12:54 +00:00
update tmdb metadata handling
This commit is contained in:
parent
4261891a35
commit
6fa53151fb
4 changed files with 79 additions and 36 deletions
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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 */}
|
||||||
|
|
|
||||||
|
|
@ -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')}`,
|
||||||
|
|
|
||||||
|
|
@ -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 />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue