add video to opengraph
deploy / build-and-deploy (push) Successful in 42s

This commit is contained in:
bytedream committed 2026-10-31 01:49:53 +01:00
1 parent 73d4f6af42
commit 30c76a10ab
4 files changed
+35 -13

No files matched your search

+5 -3
View File
@@ -26,12 +26,14 @@ const { title, description, keywords, openGraph, viewTransition } = Astro.props;
<title>{title}</title> <title>{title}</title>
{openGraph && ( {openGraph && (
<> <>
<meta name="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:url" content={Astro.url.pathname} /> {description && <meta property="og:description" content={description} />}
<meta property="og:url" content={Astro.url.href} />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:image" content={logo512.src} /> <meta property="og:image" content={new URL(logo512.src, Astro.url).href} />
</> </>
)} )}
<slot name="head" />
<meta name="description" content={description} /> <meta name="description" content={description} />
{keywords && <meta name="keywords" content={keywords.join(', ')} />} {keywords && <meta name="keywords" content={keywords.join(', ')} />}
<base href=`${BASE_PATH}/` /> <base href=`${BASE_PATH}/` />
+1
View File
@@ -14,6 +14,7 @@ const { title, description, footer = true } = Astro.props;
--- ---
<BaseLayout title={title} description={description} openGraph={true} viewTransition> <BaseLayout title={title} description={description} openGraph={true} viewTransition>
{Astro.slots.has('head') && <slot name="head" slot="head" />}
<main class="min-h-[calc(100vh-3.5rem)] h-full w-full relative overflow-y-hidden"> <main class="min-h-[calc(100vh-3.5rem)] h-full w-full relative overflow-y-hidden">
<slot /> <slot />
</main> </main>
+15 -4
View File
@@ -11,19 +11,30 @@ if (!video) return new Response(null, { status: 404 });
const metadata = await getVideoMetadata(video); const metadata = await getVideoMetadata(video);
if (!metadata) return new Response(null, { status: 404 }); if (!metadata) return new Response(null, { status: 404 });
const { title, description } = metadata; const { title, description, width, height, duration } = metadata;
const basePath = import.meta.env.BASE_URL.replace(/\/$/, ''); const basePath = import.meta.env.BASE_URL.replace(/\/$/, '');
const streamUrl = `${basePath}/video/${encodeURIComponent(video.name)}/stream`; const streamPath = `${basePath}/video/${encodeURIComponent(video.name)}/stream`;
const streamUrl = new URL(streamPath, Astro.url);
--- ---
<WebsiteLayout title={`${title} – Video`}> <WebsiteLayout title={`${title} – Video`} description={description ?? undefined}>
<Fragment slot="head">
<meta property="og:type" content="video" />
<meta property="og:video" content={streamUrl.href} />
{streamUrl.href.startsWith('https://') && <meta property="og:video:secure_url" content={streamUrl.href} />}
<meta property="og:video:type" content={video.contentType} />
{width && <meta property="og:video:width" content={width.toString()} />}
{height && <meta property="og:video:height" content={height.toString()} />}
{duration && <meta property="og:video:duration" content={duration.toString()} />}
</Fragment>
<section class="mx-auto flex w-full max-w-6xl flex-col gap-5 px-4 py-8 lg:py-12"> <section class="mx-auto flex w-full max-w-6xl flex-col gap-5 px-4 py-8 lg:py-12">
<div> <div>
<p class="text-sm tracking-widest text-base-content/60 uppercase">Video</p> <p class="text-sm tracking-widest text-base-content/60 uppercase">Video</p>
<h1 class="mt-1 text-2xl font-semibold break-words lg:text-4xl">{title}</h1> <h1 class="mt-1 text-2xl font-semibold break-words lg:text-4xl">{title}</h1>
</div> </div>
<VideoPlayer client:load src={streamUrl} type={video.contentType} title={title} /> <VideoPlayer client:load src={streamPath} type={video.contentType} title={title} />
{description && ( {description && (
<p class="max-w-3xl text-base leading-relaxed whitespace-pre-line text-base-content/70">{description}</p> <p class="max-w-3xl text-base leading-relaxed whitespace-pre-line text-base-content/70">{description}</p>
+14 -6
View File
@@ -26,13 +26,15 @@ export interface VideoRange {
export interface VideoMetadata { export interface VideoMetadata {
title: string; title: string;
description: string | null; description: string | null;
width: number | null;
height: number | null;
duration: number | null;
} }
interface VideoMetadataCacheEntry { interface VideoMetadataCacheEntry {
modifiedAtMs: number; modifiedAtMs: number;
size: number; size: number;
title: string; metadata: VideoMetadata;
description: string | null;
} }
const videoMetadataCache = new Map<string, VideoMetadataCacheEntry>(); const videoMetadataCache = new Map<string, VideoMetadataCacheEntry>();
@@ -83,22 +85,28 @@ export async function getVideoMetadata(video: ResolvedVideo): Promise<VideoMetad
const cached = videoMetadataCache.get(video.path); const cached = videoMetadataCache.get(video.path);
if (cached && cached.modifiedAtMs === modifiedAtMs && cached.size === video.size) { if (cached && cached.modifiedAtMs === modifiedAtMs && cached.size === video.size) {
return cached; return cached.metadata;
} }
let metadata; let metadata: VideoMetadata;
try { try {
const parsed = await parseFile(video.path, { const parsed = await parseFile(video.path, {
duration: false, duration: false,
skipCovers: true skipCovers: true
}); });
const videoTrack = parsed.format.trackInfo?.find((track) => track.video);
const duration = parsed.format.containerDuration ?? parsed.format.duration;
metadata = { metadata = {
title: parsed.common.title?.trim() || video.name, title: parsed.common.title?.trim() || video.name,
description: description:
parsed.common.longDescription?.trim() || parsed.common.longDescription?.trim() ||
parsed.common.description?.map((value) => value.trim()).find(Boolean) || parsed.common.description?.map((value) => value.trim()).find(Boolean) ||
null null,
width: videoTrack?.video?.displayWidth ?? videoTrack?.video?.pixelWidth ?? null,
height: videoTrack?.video?.displayHeight ?? videoTrack?.video?.pixelHeight ?? null,
duration: duration ? Math.round(duration) : null
}; };
} catch { } catch {
// couldn't read metadata // couldn't read metadata
@@ -108,7 +116,7 @@ export async function getVideoMetadata(video: ResolvedVideo): Promise<VideoMetad
videoMetadataCache.set(video.path, { videoMetadataCache.set(video.path, {
modifiedAtMs, modifiedAtMs,
size: video.size, size: video.size,
...metadata metadata
}); });
return metadata; return metadata;