diff --git a/public/thumbnail-placeholder.png b/public/thumbnail-placeholder.png
new file mode 100644
index 00000000..59aa341b
Binary files /dev/null and b/public/thumbnail-placeholder.png differ
diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json
index fd1d56f0..16aebae3 100644
--- a/src/assets/locales/en.json
+++ b/src/assets/locales/en.json
@@ -423,6 +423,7 @@
"airs": "Airs",
"endsAt": "Ends at {{time}}",
"trailer": "Trailer",
+ "trailers": "Trailers",
"similar": "Similar",
"collection": {
"movies": "Movies",
diff --git a/src/backend/metadata/types/tmdb.ts b/src/backend/metadata/types/tmdb.ts
index 60bedc7a..ace86197 100644
--- a/src/backend/metadata/types/tmdb.ts
+++ b/src/backend/metadata/types/tmdb.ts
@@ -391,6 +391,7 @@ export interface TMDBVideo {
type: string;
official: boolean;
published_at: string;
+ thumbnail?: string;
}
export interface TMDBVideosResponse {
diff --git a/src/components/overlays/detailsModal/components/carousels/TrailerCarousel.tsx b/src/components/overlays/detailsModal/components/carousels/TrailerCarousel.tsx
index 1c7c9e1e..c58cb181 100644
--- a/src/components/overlays/detailsModal/components/carousels/TrailerCarousel.tsx
+++ b/src/components/overlays/detailsModal/components/carousels/TrailerCarousel.tsx
@@ -7,12 +7,14 @@ import { TMDBContentTypes, TMDBVideo } from "@/backend/metadata/types/tmdb";
interface TrailerCarouselProps {
mediaId: string;
mediaType: TMDBContentTypes;
- onTrailerClick: (videoKey: string) => void;
+ imdbData?: any;
+ onTrailerClick: (videoKey: string, isImdbTrailer?: boolean) => void;
}
export function TrailerCarousel({
mediaId,
mediaType,
+ imdbData,
onTrailerClick,
}: TrailerCarouselProps) {
const { t } = useTranslation();
@@ -36,38 +38,73 @@ export function TrailerCarousel({
loadVideos();
}, [mediaId, mediaType]);
- if (videos.length === 0) return null;
+ // Combine TMDB videos and IMDb trailer
+ const allTrailers = [
+ ...videos,
+ ...(imdbData?.trailer_url
+ ? [
+ {
+ id: "imdb-trailer",
+ key: imdbData.trailer_url,
+ name: "IMDb Trailer",
+ site: "IMDb",
+ size: 1080,
+ type: "Trailer",
+ official: true,
+ published_at: new Date().toISOString(),
+ thumbnail: imdbData.trailer_thumbnail,
+ },
+ ]
+ : []),
+ ];
+
+ if (allTrailers.length === 0) return null;
return (
- {t("details.trailers", "Trailers")}
+ {t("details.trailers")}
- {videos.map((video) => (
-
);
diff --git a/src/components/overlays/detailsModal/components/layout/DetailsContent.tsx b/src/components/overlays/detailsModal/components/layout/DetailsContent.tsx
index 2079c6bf..709655af 100644
--- a/src/components/overlays/detailsModal/components/layout/DetailsContent.tsx
+++ b/src/components/overlays/detailsModal/components/layout/DetailsContent.tsx
@@ -268,7 +268,6 @@ export function DetailsContent({ data, minimal = false }: DetailsContentProps) {
setShowTrailer(true)}
onShareClick={handleShareClick}
showProgress={showProgress}
voteAverage={data.voteAverage}
@@ -379,8 +378,16 @@ export function DetailsContent({ data, minimal = false }: DetailsContentProps) {
? TMDBContentTypes.MOVIE
: TMDBContentTypes.TV
}
- onTrailerClick={(videoKey) => {
- const trailerUrl = `https://www.youtube.com/embed/${videoKey}?autoplay=1&rel=0`;
+ imdbData={imdbData}
+ onTrailerClick={(videoKey, isImdbTrailer) => {
+ let trailerUrl: string;
+ if (isImdbTrailer) {
+ // IMDb trailer is already a full URL
+ trailerUrl = videoKey;
+ } else {
+ // TMDB trailer needs to be converted to YouTube embed URL
+ trailerUrl = `https://www.youtube.com/embed/${videoKey}?autoplay=1&rel=0`;
+ }
setShowTrailer(true);
setImdbData((prev: any) => ({
...prev,
diff --git a/src/components/overlays/detailsModal/components/sections/DetailsBody.tsx b/src/components/overlays/detailsModal/components/sections/DetailsBody.tsx
index e229e60f..54f6ad88 100644
--- a/src/components/overlays/detailsModal/components/sections/DetailsBody.tsx
+++ b/src/components/overlays/detailsModal/components/sections/DetailsBody.tsx
@@ -16,7 +16,6 @@ import { DetailsBodyProps } from "../../types";
export function DetailsBody({
data,
onPlayClick,
- onTrailerClick,
onShareClick,
showProgress,
voteAverage,
@@ -232,19 +231,6 @@ export function DetailsBody({
- {imdbData?.trailer_url && (
-
-
-
- )}
void;
- onTrailerClick: () => void;
onShareClick: () => void;
showProgress: ShowProgressResult | null;
voteAverage?: number;
diff --git a/src/utils/imdbScraper.ts b/src/utils/imdbScraper.ts
index 7f28d69f..ca5d1db9 100644
--- a/src/utils/imdbScraper.ts
+++ b/src/utils/imdbScraper.ts
@@ -55,6 +55,7 @@ interface IMDbMetadata {
plot?: string;
poster_url?: string;
trailer_url?: string;
+ trailer_thumbnail?: string;
url?: string;
genre?: string[];
cast?: string[];
@@ -251,9 +252,9 @@ export async function scrapeIMDb(
metadata.imdb_rating = aboveTheFold.ratingsSummary?.aggregateRating || null;
metadata.votes = aboveTheFold.ratingsSummary?.voteCount || null;
metadata.poster_url = aboveTheFold.primaryImage?.url || "";
- metadata.trailer_url =
- aboveTheFold.primaryVideos?.edges?.[0]?.node?.playbackURLs?.[0]?.url ||
- "";
+ const trailerNode = aboveTheFold.primaryVideos?.edges?.[0]?.node;
+ metadata.trailer_url = trailerNode?.playbackURLs?.[0]?.url || "";
+ metadata.trailer_thumbnail = trailerNode?.thumbnail?.url || "";
// Extract arrays
metadata.genre = aboveTheFold.genres?.genres?.map((g: any) => g.text) || [];