Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/backend/metadata/getmeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export async function getMetaFromId(
id: string,
seasonId?: string,
): Promise<DetailedMeta | null> {
if (type === MWMediaType.SERIES) {
const { getImdbOverride } = await import("./imdbMetadataProvider");
const override = await getImdbOverride(id, seasonId);
if (override) return override;
}

const details = await getMediaDetails(id, mediaTypeToTMDB(type));

if (!details) return null;
Expand Down
148 changes: 148 additions & 0 deletions src/backend/metadata/imdbMetadataProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { DetailedMeta, MWMediaType } from "./types/mw";
import { TMDBEpisodeShort } from "./types/tmdb";
import overrides from "./overrides.json";

const API_BASE = "https://api.balloonerismm.workers.dev";

const overrideMap: Record<string, string> = overrides;

export function hasImdbOverride(tmdbId: string): boolean {
return tmdbId in overrideMap;
}

function getImdbIdForTmdb(tmdbId: string): string | null {
return overrideMap[tmdbId] ?? null;
}

interface BalloonShowResponse {
id: string;
name: string;
original_name: string;
overview: string;
first_air_date: string;
poster_path: string | null;
number_of_seasons: number | null;
seasons: { season_number: number; label: string }[];
}

interface BalloonEpisode {
air_date: string;
episode_number: number;
id: string;
name: string;
overview: string;
runtime: number | null;
season_number: number;
still_path: string | null;
}

interface BalloonSeasonResponse {
episodes: BalloonEpisode[];
name: string;
season_number: number;
}

async function fetchShowInfo(imdbId: string): Promise<BalloonShowResponse> {
const res = await fetch(`${API_BASE}/tv/${imdbId}`);
if (!res.ok) throw new Error(`Show API returned ${res.status}`);
return res.json();
}

async function fetchSeasonData(
imdbId: string,
seasonNumber: number,
): Promise<BalloonSeasonResponse> {
const res = await fetch(
`${API_BASE}/tv/${imdbId}/season/${seasonNumber}`,
);
if (!res.ok) throw new Error(`Season API returned ${res.status}`);
return res.json();
}

export async function getImdbOverride(
tmdbId: string,
seasonId?: string,
): Promise<DetailedMeta | null> {
const imdbId = getImdbIdForTmdb(tmdbId);
if (!imdbId) return null;

try {
const show = await fetchShowInfo(imdbId);

const seasons = show.seasons
.filter((s) => s.season_number > 0)
.map((s) => ({
id: `${tmdbId}-s${s.season_number}`,
number: s.season_number,
title: `Season ${s.season_number}`,
}));

if (seasons.length === 0) return null;

let selectedSeasonNumber = seasons[0].number;
if (seasonId) {
const found = seasons.find((s) => s.id === seasonId);
if (found) selectedSeasonNumber = found.number;
}

const seasonData = await fetchSeasonData(imdbId, selectedSeasonNumber);
const selectedSeason = seasons.find(
(s) => s.number === selectedSeasonNumber,
)!;
if (seasonData.name) {
selectedSeason.title = seasonData.name;
}

return {
meta: {
type: MWMediaType.SERIES,
title: show.name,
originalTitle: show.original_name || undefined,
id: tmdbId,
year: show.first_air_date?.split("-")[0],
poster: show.poster_path || undefined,
overview: show.overview || undefined,
seasons,
seasonData: {
id: selectedSeason.id,
number: selectedSeason.number,
title: selectedSeason.title,
episodes: seasonData.episodes.map((ep) => ({
id: `${tmdbId}-s${selectedSeasonNumber}-e${ep.episode_number}`,
number: ep.episode_number,
title: ep.name,
air_date: ep.air_date || "",
still_path: ep.still_path,
overview: ep.overview || "",
})),
},
},
imdbId,
tmdbId,
};
} catch {
return null;
}
}

export async function getImdbEpisodes(
tmdbId: string,
seasonNumber: number,
): Promise<TMDBEpisodeShort[] | null> {
const imdbId = getImdbIdForTmdb(tmdbId);
if (!imdbId) return null;

try {
const seasonData = await fetchSeasonData(imdbId, seasonNumber);
return seasonData.episodes.map((ep) => ({
id: ep.episode_number,
episode_number: ep.episode_number,
title: ep.name,
air_date: ep.air_date || "",
still_path: ep.still_path,
overview: ep.overview || "",
}));
} catch {
return null;
}
}
3 changes: 3 additions & 0 deletions src/backend/metadata/overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"71446": "tt6468322"
}
4 changes: 4 additions & 0 deletions src/backend/metadata/tmdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MediaItem } from "@/utils/mediaTypes";
import { getProxyUrls } from "@/utils/proxyUrls";

import { MWMediaMeta, MWMediaType, MWSeasonMeta } from "./types/mw";
import { getImdbEpisodes } from "./imdbMetadataProvider";
import {
ExternalIdMovieSearchResult,
TMDBContentTypes,
Expand Down Expand Up @@ -527,6 +528,9 @@ export async function getEpisodes(
id: string,
season: number,
): Promise<TMDBEpisodeShort[]> {
const overrideEps = await getImdbEpisodes(id, season);
if (overrideEps) return overrideEps;

const data = await get<TMDBSeason>(`/tv/${id}/season/${season}`);
return data.episodes.map((e) => ({
id: e.id,
Expand Down
2 changes: 1 addition & 1 deletion src/components/player/atoms/Episodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ function EpisodeItem({
<div className="relative aspect-video max-h-[110px] w-1/3 flex-shrink-0 bg-video-context-hoverColor">
{episode.still_path ? (
<img
src={`https://image.tmdb.org/t/p/w300${episode.still_path}`}
src={episode.still_path.startsWith("http") ? episode.still_path : `https://image.tmdb.org/t/p/w300${episode.still_path}`}
alt={episode.title}
className="w-full h-full object-cover"
/>
Expand Down
Loading