fix: resolve 4 media embed bugs from testing

1. Video aspect ratio: remove container border/overflow-hidden, use
   preload="metadata" so browser knows dimensions before play
2. YouTube Error 153: remove sandbox attr (too restrictive), add full
   allow permissions (encrypted-media, accelerometer, gyroscope, etc.)
3. Google Images not displaying: detect image Content-Type from HTTP
   response in metadataFetcher, override classifier to create image
   embed for URLs that serve image/* content
4. Audio seeking broken: add HTTP Range request support in uploads
   route (Accept-Ranges, Content-Range, 206 Partial Content)
This commit is contained in:
Jannis Braun
2026-03-21 00:19:04 +01:00
parent ff2decded5
commit c0133397e3
5 changed files with 58 additions and 11 deletions
+22
View File
@@ -134,6 +134,28 @@ export async function uploadRoutes(app: FastifyInstance): Promise<void> {
reply.header('Content-Disposition', `attachment; filename="${encodeURIComponent(originalName)}"`);
}
// Support Range requests for audio/video seeking
const stat = fs.statSync(filepath);
const fileSize = stat.size;
const rangeHeader = request.headers.range;
if (rangeHeader) {
const parts = rangeHeader.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0] ?? '0', 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunkSize = end - start + 1;
reply.header('Content-Range', `bytes ${start}-${end}/${fileSize}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunkSize);
reply.code(206);
const stream = fs.createReadStream(filepath, { start, end });
return reply.send(stream);
}
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', fileSize);
const stream = fs.createReadStream(filepath);
return reply.send(stream);
});
+23 -7
View File
@@ -75,20 +75,36 @@ export async function resolveEmbeds(
let image: string | null = null;
let siteName: string | null = null;
// Track the effective embed type (may be overridden by Content-Type detection)
let effectiveEmbedType = classification.embedType;
if (classification.embedType === 'image') {
// Direct image URL — no fetch needed, use the URL as the image source
image = url;
} else if (classification.needsMetadataFetch) {
const metadata = await fetchUrlMetadata(url);
if (metadata) {
title = metadata.title;
description = metadata.description;
image = metadata.image;
siteName = metadata.siteName;
// If the URL itself is a direct media resource (detected via Content-Type),
// override the classification instead of trying to use og: metadata
if (metadata.contentType) {
if (metadata.contentType.startsWith('image/')) {
effectiveEmbedType = 'image';
image = url;
} else if (metadata.contentType.startsWith('video/')) {
effectiveEmbedType = 'video';
} else if (metadata.contentType.startsWith('audio/')) {
effectiveEmbedType = 'audio';
}
} else {
title = metadata.title;
description = metadata.description;
image = metadata.image;
siteName = metadata.siteName;
}
}
// For generic embeds, skip if we couldn't extract a title
if (classification.embedType === 'generic' && !title) {
// For generic embeds, skip if we couldn't extract a title and it's not a media URL
if (effectiveEmbedType === 'generic' && !title) {
continue;
}
}
@@ -99,7 +115,7 @@ export async function resolveEmbeds(
messageId: isDm ? null : messageId,
dmMessageId: isDm ? messageId : null,
url,
embedType: classification.embedType,
embedType: effectiveEmbedType,
provider: classification.provider,
title,
description,
@@ -22,6 +22,8 @@ export interface UrlMetadata {
image: string | null;
siteName: string | null;
url: string;
/** Set when the URL itself is a direct media resource (image/video/audio) */
contentType?: string;
}
export async function fetchUrlMetadata(url: string): Promise<UrlMetadata | null> {
@@ -66,6 +68,13 @@ export async function fetchUrlMetadata(url: string): Promise<UrlMetadata | null>
return null;
}
// If the response is a direct media file (image/video/audio), return early
// with the content type — don't try to parse it as HTML
const responseContentType = response.headers.get('content-type') ?? '';
if (responseContentType.startsWith('image/') || responseContentType.startsWith('video/') || responseContentType.startsWith('audio/')) {
return { title: null, description: null, image: null, siteName: null, url, contentType: responseContentType };
}
// Early exit if Content-Length > 512KB
const contentLength = parseInt(response.headers.get('content-length') ?? '0', 10);
if (contentLength > 512_000) {