fix(downloads): preserve snapshot source semantics

This commit is contained in:
4gray committed 2026-08-01 16:29:51 +02:00
1 parent 513348e011
commit 9ba45ed443
6 files changed
+82 -118

No files matched your search

@@ -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',
@@ -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,
@@ -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:
@@ -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,
@@ -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,
@@ -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' }],
}),
})
);