From 9ba45ed44334d5259ff384945f6aa569d0977a50 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 31 Jul 2026 16:59:41 +0200 Subject: [PATCH] fix(downloads): preserve snapshot source semantics --- .../stalker-vod-download.spec.ts | 35 +++------------ .../stalker-vod-download.ts | 24 +++++----- .../stalker-series-view.component.spec.ts | 24 +++++----- .../stalker-series-view.component.ts | 33 +++++++------- .../vod-details-downloads.service.ts | 39 ++++++++-------- .../vod-details-route-playback.spec.ts | 45 ++++++------------- 6 files changed, 82 insertions(+), 118 deletions(-) diff --git a/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.spec.ts b/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.spec.ts index cf134e036..0fc8e061d 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.spec.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.spec.ts @@ -38,21 +38,10 @@ describe('startStalkerVodDownload', () => { genre: 'Drama, Thriller', rating_imdb: '7.7', tmdb_id: 541, - tmdb_cast: [ - { - name: 'Stella Star', - character: 'Mira', - profileUrl: null, - tmdbPersonId: 77, - }, - ], - tmdb_directors: [ - { - name: 'Dorian Vale', - profileUrl: null, - tmdbPersonId: 78, - }, - ], + actors: 'Stella Star, Mira Moon', + director: 'Dorian Vale', + tmdb_cast: [], + tmdb_directors: [], }, }, } as unknown as VodDetailsItem; @@ -80,19 +69,8 @@ describe('startStalkerVodDownload', () => { providerCategoryId: '7', genres: ['Drama', 'Thriller'], tmdbId: 541, - cast: [ - expect.objectContaining({ - name: 'Stella Star', - role: 'Mira', - tmdbPersonId: 77, - }), - ], - creators: [ - expect.objectContaining({ - name: 'Dorian Vale', - tmdbPersonId: 78, - }), - ], + cast: [{ name: 'Stella Star' }, { name: 'Mira Moon' }], + creators: [{ name: 'Dorian Vale' }], }), }) ); @@ -131,6 +109,7 @@ describe('startStalkerVodDownload', () => { expect(startDownload).toHaveBeenCalledWith( expect.objectContaining({ + title: ' ', metadataSnapshot: expect.objectContaining({ mediaKind: 'movie', title: 'Sparse title', diff --git a/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.ts b/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.ts index fac20a50f..1d571bba0 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-catalog-detail/stalker-vod-download.ts @@ -95,14 +95,14 @@ function people( enriched: TmdbEnrichedCastMember[] | undefined, fallback: string | undefined ) { - return ( - enriched?.map((person) => ({ - name: person.name, - role: person.character, - profileUrl: person.profileUrl ?? undefined, - tmdbPersonId: person.tmdbPersonId, - })) ?? textList(fallback)?.map((name) => ({ name })) - ); + return enriched?.length + ? enriched.map((person) => ({ + name: person.name, + role: person.character, + profileUrl: person.profileUrl ?? undefined, + tmdbPersonId: person.tmdbPersonId, + })) + : textList(fallback)?.map((name) => ({ name })); } export async function startStalkerVodDownload( @@ -119,7 +119,9 @@ export async function startStalkerVodDownload( } const itemData = item.data as DownloadVodData; - const title = firstText(itemData?.info?.name, itemData?.title) ?? 'Unknown'; + const downloadTitle = itemData?.info?.name || itemData?.title || 'Unknown'; + const snapshotTitle = + firstText(itemData?.info?.name, itemData?.title) ?? 'Unknown'; const cmdToUse = await resolveDownloadCmd(item, itemData, deps); const url = await deps.fetchLinkToPlay( @@ -135,12 +137,12 @@ export async function startStalkerVodDownload( playlistId: playlist.id, xtreamId: normalizeStalkerEntityIdAsNumber(itemData?.id) ?? 0, contentType: 'vod', - title, + title: downloadTitle, url, posterUrl: itemData?.info?.movie_image, metadataSnapshot: createMovieDownloadSnapshot({ language: deps.language?.trim() || 'en', - title, + title: snapshotTitle, originalTitle: itemData.info?.o_name, plot: itemData.info?.description, releaseDate: itemData.info?.releasedate, diff --git a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.spec.ts b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.spec.ts index e8e036802..98b777590 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.spec.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.spec.ts @@ -357,6 +357,10 @@ describe('StalkerSeriesViewComponent', () => { description: 'Parent series plot', movie_image: 'https://images.example.test/posters/signal-house.jpg', + actors: 'Sienna Wave', + director: 'Cora Bell', + tmdb_cast: [], + tmdb_directors: [], }, }); @@ -364,20 +368,14 @@ describe('StalkerSeriesViewComponent', () => { await fixture.whenStable(); await fixture.componentInstance.downloadEpisode({ - id: 'episode-tracking-id', - episode_num: 2, + episode_num: -3, title: 'The Call', - container_extension: 'mpg', info: { plot: 'Episode-specific plot', movie_image: 'https://images.example.test/stills/the-call.jpg', - duration_secs: 3120, - rating: 8.6, }, custom_sid: 'vod-series', - added: '', - season: 1, - direct_source: '', + season: '0', originalId: '502', } as never); @@ -386,10 +384,10 @@ describe('StalkerSeriesViewComponent', () => { playlistId: 'stalker-1', playlistType: 'stalker', seriesXtreamId: 50001, - seasonNumber: 1, - episodeNumber: 2, + seasonNumber: 0, + episodeNumber: -3, + title: 'Signal House - S00E-3 - The Call', metadataSnapshot: expect.objectContaining({ - version: 1, language: 'en', mediaKind: 'series', title: 'Signal House', @@ -397,9 +395,11 @@ describe('StalkerSeriesViewComponent', () => { posterUrl: 'https://images.example.test/posters/signal-house.jpg', providerCategoryId: '18', + cast: [{ name: 'Sienna Wave' }], + creators: [{ name: 'Cora Bell' }], episode: { seasonNumber: 1, - episodeNumber: 2, + episodeNumber: 1, title: 'The Call', plot: 'Episode-specific plot', stillUrl: diff --git a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts index ace0a9c58..0c703376b 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts @@ -119,14 +119,14 @@ function snapshotPeople( enriched: TmdbEnrichedCastMember[] | undefined, fallback: string | undefined ) { - return ( - enriched?.map((person) => ({ - name: person.name, - role: person.character, - profileUrl: person.profileUrl ?? undefined, - tmdbPersonId: person.tmdbPersonId, - })) ?? snapshotTextList(fallback)?.map((name) => ({ name })) - ); + return enriched?.length + ? enriched.map((person) => ({ + name: person.name, + role: person.character, + profileUrl: person.profileUrl ?? undefined, + tmdbPersonId: person.tmdbPersonId, + })) + : snapshotTextList(fallback)?.map((name) => ({ name })); } function positiveCoordinate(value: number | string | undefined): number { @@ -1105,12 +1105,13 @@ export class StalkerSeriesViewComponent implements OnDestroy { const episodeInfo = this.getEpisodeInfo(episode); const posterUrl = episodeInfo?.movie_image; - const seasonNum = positiveCoordinate(episode.season); - const episodeNum = positiveCoordinate(episode.episode_num); + const seasonNum = Number(episode.season || 1); + const episodeNum = episode.episode_num || 1; const seriesTitle = - item.info?.name?.trim() || - this.displayItem()?.info?.name?.trim() || - 'Series'; + item.info?.name || this.displayItem()?.info?.name || 'Series'; + const snapshotSeriesTitle = seriesTitle.trim() || 'Series'; + const snapshotSeasonNum = positiveCoordinate(episode.season); + const snapshotEpisodeNum = positiveCoordinate(episode.episode_num); const episodeTitle = `${seriesTitle} - S${String(seasonNum).padStart( 2, '0' @@ -1128,7 +1129,7 @@ export class StalkerSeriesViewComponent implements OnDestroy { this.translateService.currentLang || this.translateService.defaultLang || 'en', - title: seriesTitle, + title: snapshotSeriesTitle, originalTitle: item.info?.o_name, plot: item.info?.description, releaseDate: item.info?.releasedate, @@ -1146,8 +1147,8 @@ export class StalkerSeriesViewComponent implements OnDestroy { item.info?.director ), episode: { - seasonNumber: seasonNum, - episodeNumber: episodeNum, + seasonNumber: snapshotSeasonNum, + episodeNumber: snapshotEpisodeNum, title: episode.title, plot: episodeInfo?.plot, stillUrl: episodeInfo?.movie_image, diff --git a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-downloads.service.ts b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-downloads.service.ts index ba3491597..b7fc6a8a1 100644 --- a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-downloads.service.ts +++ b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-downloads.service.ts @@ -148,34 +148,35 @@ export class VodDetailsDownloadsService { 'en', title: presentation.title, originalTitle: info?.o_name, - plot: info?.plot || info?.description, + plot: info?.description || info?.plot, releaseDate: info?.releasedate, year: year(info?.releasedate), durationMinutes: durationMinutes(info), genres: textList(info?.genre), - rating: number(info?.rating), + rating: number(info?.rating_imdb), status: info?.status, posterUrl: presentation.posterUrl, backdropUrl: info?.backdrop_path?.[0], tmdbId: number(info?.tmdb_id), providerCategoryId: vodItem.movie_data?.category_id, - cast: - info?.tmdb_cast?.map((person) => ({ - name: person.name, - role: person.character, - profileUrl: person.profileUrl ?? undefined, - tmdbPersonId: person.tmdbPersonId, - })) ?? - textList(info?.actors || info?.cast)?.map((name) => ({ - name, - })), - creators: - info?.tmdb_directors?.map((person) => ({ - name: person.name, - role: person.character, - profileUrl: person.profileUrl ?? undefined, - tmdbPersonId: person.tmdbPersonId, - })) ?? textList(info?.director)?.map((name) => ({ name })), + cast: info?.tmdb_cast?.length + ? info.tmdb_cast.map((person) => ({ + name: person.name, + role: person.character, + profileUrl: person.profileUrl ?? undefined, + tmdbPersonId: person.tmdbPersonId, + })) + : textList(info?.actors || info?.cast)?.map((name) => ({ + name, + })), + creators: info?.tmdb_directors?.length + ? info.tmdb_directors.map((person) => ({ + name: person.name, + role: person.character, + profileUrl: person.profileUrl ?? undefined, + tmdbPersonId: person.tmdbPersonId, + })) + : textList(info?.director)?.map((name) => ({ name })), }), headers: { userAgent: playlist.userAgent, diff --git a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route-playback.spec.ts b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route-playback.spec.ts index d04c545eb..fbc5bfe85 100644 --- a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route-playback.spec.ts +++ b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route-playback.spec.ts @@ -421,14 +421,15 @@ describe('VodDetailsRouteComponent — playback actions', () => { ); }); - it('downloads the movie the route currently shows', async () => { + it('downloads the route movie with the metadata precedence rendered on screen', async () => { currentPlaylist.set({ id: 'playlist-1' }); fixture.detectChanges(); await fixture.componentInstance.downloadVod({ info: { name: 'Metadata Movie', - description: 'Provider or TMDB description', + description: 'Rendered description wins', + plot: 'Lower-priority plot', movie_image: 'https://images.example.test/posters/metadata-movie.jpg', backdrop_path: [ @@ -437,24 +438,14 @@ describe('VodDetailsRouteComponent — playback actions', () => { releasedate: '2025-03-14', duration_secs: 7200, genre: 'Drama, Mystery', - rating: '8.4', + rating: '9.9', + rating_imdb: '7.3', status: 'Released', tmdb_id: 12345, - tmdb_cast: [ - { - name: 'Ada Actor', - character: 'The Lead', - profileUrl: 'https://image.tmdb.org/t/p/w185/ada.jpg', - tmdbPersonId: 91, - }, - ], - tmdb_directors: [ - { - name: 'Dana Director', - profileUrl: null, - tmdbPersonId: 92, - }, - ], + actors: 'Ada Actor, Bea Actor', + director: 'Dana Director', + tmdb_cast: [], + tmdb_directors: [], }, // A DIFFERENT id in the payload: the route's id must win. movie_data: { @@ -478,7 +469,7 @@ describe('VodDetailsRouteComponent — playback actions', () => { language: 'en', mediaKind: 'movie', title: 'Metadata Movie', - plot: 'Provider or TMDB description', + plot: 'Rendered description wins', posterUrl: 'https://images.example.test/posters/metadata-movie.jpg', backdropUrl: @@ -486,19 +477,9 @@ describe('VodDetailsRouteComponent — playback actions', () => { providerCategoryId: '235', tmdbId: 12345, genres: ['Drama', 'Mystery'], - cast: [ - expect.objectContaining({ - name: 'Ada Actor', - role: 'The Lead', - tmdbPersonId: 91, - }), - ], - creators: [ - expect.objectContaining({ - name: 'Dana Director', - tmdbPersonId: 92, - }), - ], + rating: 7.3, + cast: [{ name: 'Ada Actor' }, { name: 'Bea Actor' }], + creators: [{ name: 'Dana Director' }], }), }) );