mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
fix(epg): repair SD poster display and backfill missing artwork
Build absolute poster proxy URLs via build_absolute_uri_with_port so program detail images resolve correctly in dev and behind reverse proxies. Backfill sd_icon for programs when poster fetch is enabled but a delta refresh skips program metadata. Add a dev-mode frontend fallback for relative image paths and correct the poster caching description in the SD settings UI.
This commit is contained in:
parent
3835954024
commit
c4d73eae39
4 changed files with 39 additions and 10 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from core.utils import validate_flexible_url
|
||||
from core.utils import validate_flexible_url, build_absolute_uri_with_port
|
||||
from rest_framework import serializers
|
||||
from .models import EPGSource, EPGData, ProgramData
|
||||
from apps.channels.models import Channel, Stream
|
||||
|
|
@ -169,9 +169,14 @@ class ProgramDetailSerializer(ProgramDataSerializer):
|
|||
data['icon'] = cp.get('icon')
|
||||
data['images'] = cp.get('images') or []
|
||||
|
||||
# SD poster: expose as proxy URL so frontend never needs SD auth
|
||||
# SD poster: expose as absolute proxy URL so frontend/img tags never need SD auth
|
||||
if cp.get('sd_icon'):
|
||||
data['poster_url'] = f"/api/epg/programs/{obj.id}/poster/"
|
||||
poster_path = f"/api/epg/programs/{obj.id}/poster/"
|
||||
request = self.context.get('request')
|
||||
if request:
|
||||
data['poster_url'] = build_absolute_uri_with_port(request, poster_path)
|
||||
else:
|
||||
data['poster_url'] = poster_path
|
||||
else:
|
||||
data['poster_url'] = None
|
||||
|
||||
|
|
|
|||
|
|
@ -2995,7 +2995,23 @@ def fetch_schedules_direct(source, stations_only=False, force=False):
|
|||
# Step 8: Fetch program artwork (posters) if enabled
|
||||
# -------------------------------------------------------------------------
|
||||
fetch_posters = (source.custom_properties or {}).get('fetch_posters', False)
|
||||
if fetch_posters and program_metadata:
|
||||
poster_program_ids = set(program_metadata.keys()) if program_metadata else set()
|
||||
if fetch_posters and not poster_program_ids:
|
||||
# Backfill when posters were enabled after initial fetch, or a delta
|
||||
# refresh skipped program metadata but programs still lack sd_icon.
|
||||
poster_program_ids = set(
|
||||
ProgramData.objects.filter(
|
||||
epg_id__in=mapped_epg_ids,
|
||||
program_id__isnull=False,
|
||||
).exclude(custom_properties__has_key='sd_icon')
|
||||
.values_list('program_id', flat=True)
|
||||
)
|
||||
if poster_program_ids:
|
||||
logger.info(
|
||||
f"Poster backfill: {len(poster_program_ids)} programs missing sd_icon."
|
||||
)
|
||||
|
||||
if fetch_posters and poster_program_ids:
|
||||
logger.info("Poster fetch enabled — retrieving program artwork from Schedules Direct.")
|
||||
_sd_send_ws_sync(source.id, "parsing_programs", 98,
|
||||
message="Fetching program artwork...")
|
||||
|
|
@ -3007,7 +3023,7 @@ def fetch_schedules_direct(source, stations_only=False, force=False):
|
|||
artwork_lookup_ids = set()
|
||||
pid_to_artwork_key = {} # maps original programID -> the key we looked up
|
||||
|
||||
for pid in program_metadata:
|
||||
for pid in poster_program_ids:
|
||||
if pid.startswith('EP'):
|
||||
sh_root = 'SH' + pid[2:10] + '0000'
|
||||
artwork_lookup_ids.add(sh_root)
|
||||
|
|
@ -3116,7 +3132,7 @@ def fetch_schedules_direct(source, stations_only=False, force=False):
|
|||
logger.warning(f"Poster artwork fetch failed (non-fatal): {art_error}", exc_info=True)
|
||||
|
||||
elif fetch_posters:
|
||||
logger.info("Poster fetch enabled but no new program metadata downloaded — skipping artwork.")
|
||||
logger.info("Poster fetch enabled but all mapped programs already have artwork.")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Step 9: Apply SD station logos to matched channels if enabled
|
||||
|
|
|
|||
|
|
@ -40,11 +40,19 @@ function formatDurationMinutes(startTime, endTime) {
|
|||
return `${minutes}m`;
|
||||
}
|
||||
|
||||
function resolveApiUrl(url) {
|
||||
if (!url || url.startsWith('http')) return url;
|
||||
const apiHost = import.meta.env.DEV
|
||||
? `http://${window.location.hostname}:5656`
|
||||
: '';
|
||||
return `${apiHost}${url}`;
|
||||
}
|
||||
|
||||
function resolveImageUrl(detail) {
|
||||
if (detail?.tmdb_poster_url) return detail.tmdb_poster_url;
|
||||
if (detail?.poster_url) return detail.poster_url;
|
||||
if (detail?.images?.length > 0) return detail.images[0].url;
|
||||
if (detail?.icon) return detail.icon;
|
||||
if (detail?.poster_url) return resolveApiUrl(detail.poster_url);
|
||||
if (detail?.images?.length > 0) return resolveApiUrl(detail.images[0].url);
|
||||
if (detail?.icon) return resolveApiUrl(detail.icon);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ const SDSettings = ({ sourceId, customProperties }) => {
|
|||
|
||||
<Switch
|
||||
label="Fetch Program Posters"
|
||||
description="WARNING: USES ADDITIONAL API REQUESTS. Poster images are fetched on-demand and cached locally for 30 days. Initial viewing of new programs will consume API requests against your Schedules Direct rate limit."
|
||||
description="WARNING: USES ADDITIONAL API REQUESTS. Poster artwork is fetched during EPG refresh; image bytes are cached by nginx on first view (24h). Initial fetch and viewing new programs consume API requests against your Schedules Direct rate limit."
|
||||
checked={fetchPosters}
|
||||
onChange={(e) => handlePosterToggle(e.currentTarget.checked)}
|
||||
disabled={saving}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue