feat: get image from tmdb

This commit is contained in:
max_richter 2023-07-31 17:21:17 +02:00
parent 79975905d1
commit 3a5c5b4404
12 changed files with 148 additions and 29 deletions

View File

@ -9,6 +9,7 @@ export function Card(
style={{
backgroundImage: `url(${image})`,
backgroundSize: "cover",
//background: "#2B2930",
}}
class="text-white rounded-3xl shadow-md p-4 relative overflow-hidden
lg:w-56 lg:h-56
@ -23,7 +24,10 @@ export function Card(
{title}
</div>
</div>
<div class="absolute inset-x-0 bottom-0 h-3/4 bg-gradient-to-t from-black to-transparent" />
<div
class="absolute inset-x-0 bottom-0 h-3/4"
style={{ background: "linear-gradient(transparent, #000e)" }}
/>
</a>
);
}

View File

@ -3,8 +3,9 @@ import { Recipe } from "@lib/recipes.ts";
import IconExternalLink from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/external-link.tsx";
import { Star } from "@components/Stars.tsx";
export function RecipeHero(
{ data, backlink }: {
{ data, subline, backlink }: {
backlink: string;
subline?: string[];
data: { meta?: { image?: string; link?: string }; name: string };
},
) {
@ -53,8 +54,9 @@ export function RecipeHero(
</div>
)}
<div class="absolute inset-x-0 bottom-0 py-4 px-8 py-8 ">
<div
class={`absolute inset-x-0 bottom-0 py-4 px-8 py-8 ${
class={`${
imageUrl ? "noisy-gradient" : ""
} flex gap-4 items-center pt-12`}
>
@ -67,6 +69,18 @@ export function RecipeHero(
{data.meta?.rating && <Star rating={data.meta.rating} />}
</div>
{subline?.length &&
(
<div
class="relative z-50 flex gap-5 font-sm text-light mt-3"
style={{ color: imageUrl ? "#1F1F1F" : "white" }}
>
{subline.map((s) => {
return <span>{s}</span>;
})}
</div>
)}
</div>
</div>
);
}

View File

@ -25,9 +25,12 @@ export const MainLayout = ({ children, url }: Props) => {
];
return (
<div class="md:grid" style={{ gridTemplateColumns: "200px 1fr" }}>
<div
class="md:grid mx-auto"
style={{ gridTemplateColumns: "200px 1fr", maxWidth: "1024px" }}
>
<aside class="p-4 hidden md:block">
<nav class="min-h-fit rounded-3xl p-3 grid gap-3">
<nav class="min-h-fit rounded-3xl p-3 grid gap-3 fixed t-0">
{menu.map((m) => {
return (
<a

View File

@ -21,7 +21,7 @@ export const menus: Record<string, Menu> = {
state.activeState.value = "loading";
const movie = context as Movie;
let query = movie.name;
const query = movie.name;
// if (movie.meta.author) {
// query += movie.meta.author;
@ -31,8 +31,6 @@ export const menus: Record<string, Menu> = {
`/api/tmdb/query?q=${encodeURIComponent(query)}`,
);
console.log(response);
const json = await response.json() as TMDBMovie[];
const menuID = `result/${movie.name}`;
@ -43,7 +41,10 @@ export const menus: Record<string, Menu> = {
title: `${m.title} released ${m.release_date}`,
cb: async () => {
state.activeState.value = "loading";
const res = await fetch(`/api/tmdb/${m.id}`);
const res = await fetch(`/api/movies/${movie.name}/`, {
method: "POST",
body: JSON.stringify({ tmdbId: m.id }),
});
const j = await res.json();
console.log("Selected", { movie, m, j });
state.visible.value = false;

View File

@ -38,6 +38,26 @@ export async function getDocuments(): Promise<Document[]> {
return documents;
}
export async function createDocument(
name: string,
content: string | ArrayBuffer,
mediaType?: string,
) {
const headers = new Headers();
if (mediaType) {
headers.append("Content-Type", mediaType);
}
const response = await fetch(SILVERBULLET_SERVER + "/" + name, {
body: content,
method: "PUT",
headers,
});
return response;
}
export async function getDocument(name: string): Promise<string> {
const cachedDocument = await cache.getDocument(name);
if (cachedDocument) return cachedDocument;

View File

@ -10,7 +10,7 @@ export type Movie = {
description: string;
hashtags: string[];
meta: {
published: Date;
date: Date;
image: string;
author: string;
rating: number;

View File

@ -1,3 +1,4 @@
import * as cache from "@lib/cache/cache.ts";
import { MovieDb } from "https://esm.sh/moviedb-promise@3.4.1";
const moviedb = new MovieDb(Deno.env.get("TMDB_API_KEY") || "");
@ -12,3 +13,16 @@ export function getMovie(id: number) {
export function getMovieCredits(id: number) {
return moviedb.movieCredits(id);
}
export async function getMoviePoster(id: string): Promise<ArrayBuffer> {
const cachedPoster = await cache.get("posters/" + id);
if (cachedPoster) return cachedPoster as ArrayBuffer;
const posterUrl = `https://image.tmdb.org/t/p/original/${id}`;
const response = await fetch(posterUrl);
const poster = await response.arrayBuffer();
cache.set(`posters/${id}`, new Uint8Array());
return poster;
}

View File

@ -1,6 +1,21 @@
import { HandlerContext } from "$fresh/server.ts";
import { getDocument } from "@lib/documents.ts";
import { createDocument, getDocument } from "@lib/documents.ts";
import { fileExtension } from "https://deno.land/x/file_extension/mod.ts";
import { parseMovie } from "@lib/movies.ts";
import * as tmdb from "@lib/tmdb.ts";
function safeFileName(inputString: string): string {
// Convert the string to lowercase
let fileName = inputString.toLowerCase();
// Replace spaces with underscores
fileName = fileName.replace(/ /g, "_");
// Remove characters that are not safe for file names
fileName = fileName.replace(/[^\w.-]/g, "");
return fileName;
}
export async function getMovie(name: string) {
const document = await getDocument(`Media/movies/${name}.md`);
@ -14,10 +29,46 @@ export const handler = async (
_req: Request,
_ctx: HandlerContext,
): Promise<Response> => {
const movie = await getMovie(_ctx.params.name);
const headers = new Headers();
headers.append("Content-Type", "application/json");
if (_req.method === "GET") {
const movie = await getMovie(_ctx.params.name);
return new Response(JSON.stringify(movie));
}
if (_req.method === "POST") {
const body = await _req.json();
const name = _ctx.params.name;
const { tmdbId } = body;
if (!name || !tmdbId) {
return new Response("Bad Request", {
status: 400,
});
}
const movieDetails = await tmdb.getMovie(tmdbId);
const movieCredits = await tmdb.getMovieCredits(tmdbId);
const releaseDate = movieDetails.release_date;
const posterPath = movieDetails.poster_path;
const director = movieCredits?.crew?.filter?.((person) =>
person.job === "Director"
);
if (posterPath) {
const poster = await tmdb.getMoviePoster(posterPath);
const extension = fileExtension(posterPath);
const finalPath = `Media/movies/images/${
safeFileName(name)
}_cover.${extension}`;
await createDocument(finalPath, poster);
}
console.log({ releaseDate, director, posterPath });
return new Response(JSON.stringify(movieCredits), {
headers,
});
}
return new Response();
};

View File

@ -15,9 +15,15 @@ export const handler: Handlers<Movie | null> = {
export default function Greet(props: PageProps<Movie>) {
const movie = props.data;
const { author = "", date = "" } = movie.meta;
return (
<MainLayout url={props.url}>
<RecipeHero data={movie} backlink="/movies" />
<RecipeHero
data={movie}
subline={[author, date.toString()]}
backlink="/movies"
/>
<KMenu type="main" context={movie} />
<div class="px-8 text-white mt-10">
<pre

View File

@ -0,0 +1,3 @@
[ZoneTransfer]
ZoneId=3
HostUrl=about:internet

View File

@ -1,5 +1,5 @@
body {
background: #1F1F1F;
background: #141217;
padding: 0px 20px;
}
pre {

View File

@ -1,4 +1,7 @@
<svg width="1185" height="1185" viewBox="0 0 1185 1185" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 -4.3099e-05H1185V1184.99H0V-4.3099e-05Z" fill="#1F1F1F"/>
<path d="M305.227 296.249H879.769C884.726 296.249 888.747 300.266 888.747 305.223V879.766C888.747 884.723 884.726 888.744 879.769 888.744H305.227C300.269 888.744 296.249 884.723 296.249 879.766V305.223C296.249 300.266 300.269 296.249 305.227 296.249ZM870.791 314.201H314.204V870.789H870.791V314.201ZM641.944 435.489C656.788 435.489 670.234 441.509 679.963 451.238C680.133 451.408 680.295 451.586 680.449 451.767C689.889 461.457 695.712 474.689 695.712 489.257C695.712 504.081 689.692 517.511 679.963 527.244L679.943 527.259L679.963 527.275C670.234 537.004 656.788 543.028 641.944 543.028C627.1 543.028 613.659 537.004 603.926 527.275C594.197 517.546 588.177 504.101 588.177 489.257C588.177 474.417 594.197 460.971 603.926 451.242C613.659 441.509 627.1 435.489 641.944 435.489ZM667.268 463.929C660.794 457.455 651.831 453.446 641.944 453.446C632.054 453.446 623.099 457.455 616.617 463.933C610.143 470.411 606.134 479.37 606.134 489.257C606.134 499.147 610.143 508.106 616.617 514.58C623.099 521.062 632.054 525.071 641.944 525.071C651.831 525.071 660.794 521.062 667.268 514.584L667.284 514.6C673.754 508.122 677.755 499.163 677.755 489.257C677.755 479.556 673.916 470.763 667.683 464.316L667.268 463.929ZM592.499 655.839L655.236 590.163C658.645 586.58 664.317 586.442 667.9 589.847L668.239 590.191L737.084 662.254L805.952 734.349C809.361 737.932 809.222 743.604 805.64 747.009C802.053 750.418 796.385 750.279 792.976 746.697L724.104 674.602L661.726 609.305L604.882 668.807L667.493 734.349C670.902 737.932 670.76 743.604 667.177 747.009C663.594 750.418 657.922 750.279 654.513 746.697L585.645 674.602L523.267 609.305L460.885 674.602L392.019 746.697C388.612 750.279 382.94 750.418 379.357 747.009C375.774 743.604 375.634 737.932 379.041 734.349L447.909 662.254L516.777 590.163C520.186 586.58 525.858 586.442 529.441 589.847L529.781 590.191L592.499 655.839Z" fill="white" fill-opacity="0.1"/>
<svg width="387" height="387" viewBox="0 0 387 387" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="387" height="387" fill="#2B2930"/>
<circle cx="212.471" cy="154.8" r="15.5559" stroke="#39363F" stroke-width="3.03529"/>
<path d="M110.978 249.463L155.261 205.18C161.188 199.253 170.797 199.253 176.724 205.18L221.007 249.463" stroke="#39363F" stroke-width="3.03529"/>
<path d="M192.362 220.628L207.81 205.18C213.737 199.253 223.346 199.253 229.273 205.18L273.556 249.463" stroke="#39363F" stroke-width="3.03529"/>
<rect x="86.1265" y="86.1265" width="214.747" height="214.747" rx="13.6588" stroke="#39363F" stroke-width="3.03529"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 652 B