This commit is contained in:
1 parent
73d4f6af42
commit
30c76a10ab
4 files changed
+35
-13
No files matched your search
@@ -26,12 +26,14 @@ const { title, description, keywords, openGraph, viewTransition } = Astro.props;
|
||||
<title>{title}</title>
|
||||
{openGraph && (
|
||||
<>
|
||||
<meta name="og:title" content={title} />
|
||||
<meta property="og:url" content={Astro.url.pathname} />
|
||||
<meta property="og:title" content={title} />
|
||||
{description && <meta property="og:description" content={description} />}
|
||||
<meta property="og:url" content={Astro.url.href} />
|
||||
<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} />
|
||||
{keywords && <meta name="keywords" content={keywords.join(', ')} />}
|
||||
<base href=`${BASE_PATH}/` />
|
||||
|
||||
@@ -14,6 +14,7 @@ const { title, description, footer = true } = Astro.props;
|
||||
---
|
||||
|
||||
<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">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
@@ -11,19 +11,30 @@ if (!video) return new Response(null, { status: 404 });
|
||||
const metadata = await getVideoMetadata(video);
|
||||
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 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">
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<VideoPlayer client:load src={streamUrl} type={video.contentType} title={title} />
|
||||
<VideoPlayer client:load src={streamPath} type={video.contentType} title={title} />
|
||||
|
||||
{description && (
|
||||
<p class="max-w-3xl text-base leading-relaxed whitespace-pre-line text-base-content/70">{description}</p>
|
||||
|
||||
+14
-6
@@ -26,13 +26,15 @@ export interface VideoRange {
|
||||
export interface VideoMetadata {
|
||||
title: string;
|
||||
description: string | null;
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
duration: number | null;
|
||||
}
|
||||
|
||||
interface VideoMetadataCacheEntry {
|
||||
modifiedAtMs: number;
|
||||
size: number;
|
||||
title: string;
|
||||
description: string | null;
|
||||
metadata: VideoMetadata;
|
||||
}
|
||||
|
||||
const videoMetadataCache = new Map<string, VideoMetadataCacheEntry>();
|
||||
@@ -83,22 +85,28 @@ export async function getVideoMetadata(video: ResolvedVideo): Promise<VideoMetad
|
||||
const cached = videoMetadataCache.get(video.path);
|
||||
|
||||
if (cached && cached.modifiedAtMs === modifiedAtMs && cached.size === video.size) {
|
||||
return cached;
|
||||
return cached.metadata;
|
||||
}
|
||||
|
||||
let metadata;
|
||||
let metadata: VideoMetadata;
|
||||
try {
|
||||
const parsed = await parseFile(video.path, {
|
||||
duration: false,
|
||||
skipCovers: true
|
||||
});
|
||||
|
||||
const videoTrack = parsed.format.trackInfo?.find((track) => track.video);
|
||||
const duration = parsed.format.containerDuration ?? parsed.format.duration;
|
||||
|
||||
metadata = {
|
||||
title: parsed.common.title?.trim() || video.name,
|
||||
description:
|
||||
parsed.common.longDescription?.trim() ||
|
||||
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 {
|
||||
// couldn't read metadata
|
||||
@@ -108,7 +116,7 @@ export async function getVideoMetadata(video: ResolvedVideo): Promise<VideoMetad
|
||||
videoMetadataCache.set(video.path, {
|
||||
modifiedAtMs,
|
||||
size: video.size,
|
||||
...metadata
|
||||
metadata
|
||||
});
|
||||
|
||||
return metadata;
|
||||
|
||||
Reference in new issue
Block a user