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) || [];