mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
Added option to enable/disable tmdb enriched metadata for addons
This commit is contained in:
parent
b0b309b0f1
commit
2a89695b0b
3 changed files with 104 additions and 29 deletions
|
|
@ -12,6 +12,7 @@ import { usePersistentSeasons } from './usePersistentSeasons';
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { Stream } from '../types/metadata';
|
||||
import { storageService } from '../services/storageService';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
// Constants for timeouts and retries
|
||||
const API_TIMEOUT = 10000; // 10 seconds
|
||||
|
|
@ -109,6 +110,7 @@ interface UseMetadataReturn {
|
|||
}
|
||||
|
||||
export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadataReturn => {
|
||||
const { settings } = useSettings();
|
||||
const [metadata, setMetadata] = useState<StreamingContent | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
|
@ -300,6 +302,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (__DEV__) logger.log('[loadCast] Starting cast fetch for:', id);
|
||||
setLoadingCast(true);
|
||||
try {
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
if (__DEV__) logger.log('[loadCast] TMDB enrichment disabled by settings');
|
||||
setLoadingCast(false);
|
||||
return;
|
||||
}
|
||||
// Check cache first
|
||||
const cachedCast = cacheService.getCast(id, type);
|
||||
if (cachedCast) {
|
||||
|
|
@ -394,6 +401,22 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Handle TMDB-specific IDs
|
||||
let actualId = id;
|
||||
if (id.startsWith('tmdb:')) {
|
||||
// If enrichment disabled, resolve to an addon-friendly ID (IMDb) before calling addons
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
const tmdbRaw = id.split(':')[1];
|
||||
try {
|
||||
if (__DEV__) logger.log('[loadMetadata] enrichment=OFF; resolving TMDB→Stremio ID', { type, tmdbId: tmdbRaw });
|
||||
const stremioId = await catalogService.getStremioId(type === 'series' ? 'tv' : 'movie', tmdbRaw);
|
||||
if (stremioId) {
|
||||
actualId = stremioId;
|
||||
if (__DEV__) logger.log('[loadMetadata] resolved TMDB→Stremio ID', { actualId });
|
||||
} else {
|
||||
if (__DEV__) logger.warn('[loadMetadata] failed to resolve TMDB→Stremio ID; addon fetch may fail', { type, tmdbId: tmdbRaw });
|
||||
}
|
||||
} catch (e) {
|
||||
if (__DEV__) logger.error('[loadMetadata] error resolving TMDB→Stremio ID', e);
|
||||
}
|
||||
} else {
|
||||
const tmdbId = id.split(':')[1];
|
||||
// For TMDB IDs, we need to handle metadata differently
|
||||
if (type === 'movie') {
|
||||
|
|
@ -539,9 +562,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
logger.error('Failed to fetch TV show details from TMDB:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all data in parallel
|
||||
if (__DEV__) logger.log('[loadMetadata] fetching addon metadata', { type, actualId, addonId });
|
||||
const [content, castData] = await Promise.allSettled([
|
||||
// Load content with timeout and retry
|
||||
withRetry(async () => {
|
||||
|
|
@ -553,6 +578,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (actualId.startsWith('tt')) {
|
||||
setImdbId(actualId);
|
||||
}
|
||||
if (__DEV__) logger.log('[loadMetadata] addon metadata fetched', { hasResult: Boolean(result) });
|
||||
return result;
|
||||
}),
|
||||
// Start loading cast immediately in parallel
|
||||
|
|
@ -560,6 +586,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
]);
|
||||
|
||||
if (content.status === 'fulfilled' && content.value) {
|
||||
if (__DEV__) logger.log('[loadMetadata] addon metadata:success', { id: content.value?.id, type: content.value?.type, name: content.value?.name });
|
||||
setMetadata(content.value);
|
||||
// Check if item is in library
|
||||
const isInLib = catalogService.getLibraryItems().some(item => item.id === id);
|
||||
|
|
@ -571,6 +598,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Update cache
|
||||
cacheService.setMetadata(id, type, content.value);
|
||||
} else {
|
||||
if (__DEV__) logger.warn('[loadMetadata] addon metadata:not found or failed', { status: content.status, reason: (content as any)?.reason?.message });
|
||||
throw new Error('Content not found');
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -636,29 +664,33 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
if (__DEV__) 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,
|
||||
}));
|
||||
}
|
||||
});
|
||||
if (__DEV__) logger.log('🖼️ Successfully fetched and attached TMDB season posters to addon episodes.');
|
||||
// Fetch season posters from TMDB only if enrichment is enabled; otherwise skip quietly
|
||||
if (settings.enrichMetadataWithTMDB) {
|
||||
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,
|
||||
}));
|
||||
}
|
||||
});
|
||||
if (__DEV__) 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);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch TMDB season posters for addon episodes:', error);
|
||||
} else {
|
||||
if (__DEV__) logger.log('[loadSeriesData] TMDB enrichment disabled; skipping season poster fetch');
|
||||
}
|
||||
|
||||
setGroupedEpisodes(groupedAddonEpisodes);
|
||||
|
|
@ -680,6 +712,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}
|
||||
|
||||
// Try to get TMDB ID for additional metadata (cast, etc.) but don't override episodes
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
if (__DEV__) logger.log('[loadSeriesData] TMDB enrichment disabled; skipping TMDB episode fallback (preserving current episodes)');
|
||||
return;
|
||||
}
|
||||
const tmdbIdResult = await tmdbService.findTMDBIdByIMDB(id);
|
||||
if (tmdbIdResult) {
|
||||
setTmdbId(tmdbIdResult);
|
||||
|
|
@ -1078,11 +1114,11 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (metadata?.imdb_id) {
|
||||
// Replace the series ID in episodeId with the IMDb ID
|
||||
const [, season, episode] = episodeId.split(':');
|
||||
stremioEpisodeId = `series:${metadata.imdb_id}:${season}:${episode}`;
|
||||
stremioEpisodeId = `${metadata.imdb_id}:${season}:${episode}`;
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using IMDb ID from metadata for Stremio episode:', stremioEpisodeId);
|
||||
} else if (imdbId) {
|
||||
const [, season, episode] = episodeId.split(':');
|
||||
stremioEpisodeId = `series:${imdbId}:${season}:${episode}`;
|
||||
stremioEpisodeId = `${imdbId}:${season}:${episode}`;
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Using stored IMDb ID for Stremio episode:', stremioEpisodeId);
|
||||
} else {
|
||||
// Convert TMDB ID to IMDb ID for Stremio addons
|
||||
|
|
@ -1091,7 +1127,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
|
||||
if (externalIds?.imdb_id) {
|
||||
const [, season, episode] = episodeId.split(':');
|
||||
stremioEpisodeId = `series:${externalIds.imdb_id}:${season}:${episode}`;
|
||||
stremioEpisodeId = `${externalIds.imdb_id}:${season}:${episode}`;
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted TMDB to IMDb ID for Stremio episode:', stremioEpisodeId);
|
||||
} else {
|
||||
if (__DEV__) console.log('⚠️ [loadEpisodeStreams] No IMDb ID found for TMDB ID, using original episode ID:', stremioEpisodeId);
|
||||
|
|
@ -1105,6 +1141,12 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
if (__DEV__) console.log('📝 [loadEpisodeStreams] Converting IMDB ID to TMDB ID...');
|
||||
tmdbId = await withTimeout(tmdbService.findTMDBIdByIMDB(id), API_TIMEOUT);
|
||||
if (__DEV__) console.log('✅ [loadEpisodeStreams] Converted to TMDB ID:', tmdbId);
|
||||
// Normalize episode id to 'tt:season:episode' format for addons that expect tt prefix
|
||||
const parts = episodeId.split(':');
|
||||
if (parts.length === 3 && parts[0] === 'series') {
|
||||
stremioEpisodeId = `${id}:${parts[1]}:${parts[2]}`;
|
||||
if (__DEV__) console.log('🔧 [loadEpisodeStreams] Normalized episode ID for addons:', stremioEpisodeId);
|
||||
}
|
||||
} else {
|
||||
tmdbId = id;
|
||||
if (__DEV__) console.log('ℹ️ [loadEpisodeStreams] Using ID as both TMDB and Stremio ID:', tmdbId);
|
||||
|
|
@ -1212,6 +1254,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}, [metadata?.videos, type]);
|
||||
|
||||
const loadRecommendations = useCallback(async () => {
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
if (__DEV__) console.log('[useMetadata] enrichment disabled; skip recommendations');
|
||||
return;
|
||||
}
|
||||
if (!tmdbId) return;
|
||||
|
||||
setLoadingRecommendations(true);
|
||||
|
|
@ -1240,6 +1286,10 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
// Fetch TMDB ID if needed and then recommendations
|
||||
useEffect(() => {
|
||||
const fetchTmdbIdAndRecommendations = async () => {
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
if (__DEV__) console.log('[useMetadata] enrichment disabled; skip TMDB id extraction and certification (extract path)');
|
||||
return;
|
||||
}
|
||||
if (metadata && !tmdbId) {
|
||||
try {
|
||||
const tmdbService = TMDBService.getInstance();
|
||||
|
|
@ -1268,23 +1318,29 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
};
|
||||
|
||||
fetchTmdbIdAndRecommendations();
|
||||
}, [metadata, id]);
|
||||
}, [metadata, id, settings.enrichMetadataWithTMDB]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tmdbId) {
|
||||
if (__DEV__) console.log('[useMetadata] tmdbId available; loading recommendations and enabling certification checks', { tmdbId });
|
||||
loadRecommendations();
|
||||
if (settings.enrichMetadataWithTMDB) {
|
||||
if (__DEV__) console.log('[useMetadata] tmdbId available; loading recommendations and enabling certification checks', { tmdbId });
|
||||
loadRecommendations();
|
||||
}
|
||||
// Reset recommendations when tmdbId changes
|
||||
return () => {
|
||||
setRecommendations([]);
|
||||
setLoadingRecommendations(true);
|
||||
};
|
||||
}
|
||||
}, [tmdbId, loadRecommendations]);
|
||||
}, [tmdbId, loadRecommendations, settings.enrichMetadataWithTMDB]);
|
||||
|
||||
// Ensure certification is attached whenever a TMDB id is known and metadata lacks it
|
||||
useEffect(() => {
|
||||
const maybeAttachCertification = async () => {
|
||||
if (!settings.enrichMetadataWithTMDB) {
|
||||
if (__DEV__) console.log('[useMetadata] enrichment disabled; skip certification (attach path)');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!metadata) {
|
||||
if (__DEV__) console.warn('[useMetadata] skip certification attach: metadata not ready');
|
||||
|
|
@ -1311,7 +1367,7 @@ export const useMetadata = ({ id, type, addonId }: UseMetadataProps): UseMetadat
|
|||
}
|
||||
};
|
||||
maybeAttachCertification();
|
||||
}, [tmdbId, metadata, type]);
|
||||
}, [tmdbId, metadata, type, settings.enrichMetadataWithTMDB]);
|
||||
|
||||
// Reset tmdbId when id changes
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ export interface AppSettings {
|
|||
trailerMuted: boolean; // Default to muted for better user experience
|
||||
// AI
|
||||
aiChatEnabled: boolean; // Enable/disable Ask AI and AI features
|
||||
// Metadata enrichment
|
||||
enrichMetadataWithTMDB: boolean; // Use TMDB to enrich metadata (cast, certification, posters, fallbacks)
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: AppSettings = {
|
||||
|
|
@ -124,6 +126,8 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||
trailerMuted: true, // Default to muted for better user experience
|
||||
// AI
|
||||
aiChatEnabled: false,
|
||||
// Metadata enrichment
|
||||
enrichMetadataWithTMDB: true,
|
||||
};
|
||||
|
||||
const SETTINGS_STORAGE_KEY = 'app_settings';
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { logger } from '../utils/logger';
|
|||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
// (duplicate import removed)
|
||||
|
||||
const TMDB_API_KEY_STORAGE_KEY = 'tmdb_api_key';
|
||||
const USE_CUSTOM_TMDB_API_KEY = 'use_custom_tmdb_api_key';
|
||||
|
|
@ -48,6 +49,7 @@ const TMDBSettingsScreen = () => {
|
|||
const apiKeyInputRef = useRef<TextInput>(null);
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { settings, updateSetting } = useSettings();
|
||||
|
||||
const openAlert = (
|
||||
title: string,
|
||||
|
|
@ -288,6 +290,19 @@ const TMDBSettingsScreen = () => {
|
|||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={[styles.switchCard, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
||||
<View style={styles.switchTextContainer}>
|
||||
<Text style={[styles.switchTitle, { color: currentTheme.colors.text }]}>Enrich Metadata with TMDb</Text>
|
||||
<Text style={[styles.switchDescription, { color: currentTheme.colors.mediumEmphasis }]}>When enabled, the app augments addon metadata with TMDb for cast, certification, logos/posters, and episode fallback. Disable to strictly use addon metadata only.</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.enrichMetadataWithTMDB}
|
||||
onValueChange={(v) => updateSetting('enrichMetadataWithTMDB', v)}
|
||||
trackColor={{ false: 'rgba(255,255,255,0.1)', true: currentTheme.colors.primary }}
|
||||
thumbColor={Platform.OS === 'android' ? (settings.enrichMetadataWithTMDB ? currentTheme.colors.white : currentTheme.colors.white) : ''}
|
||||
ios_backgroundColor={'rgba(255,255,255,0.1)'}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.switchCard, { backgroundColor: currentTheme.colors.elevation2 }]}>
|
||||
<View style={styles.switchTextContainer}>
|
||||
<Text style={[styles.switchTitle, { color: currentTheme.colors.text }]}>Use Custom TMDb API Key</Text>
|
||||
|
|
|
|||
Loading…
Reference in a new issue