fixes for video and embed descriptions (#9)

This commit is contained in:
Lexie
2024-09-18 01:41:04 +02:00
committed by GitHub
parent 88dc790567
commit dc03bde85c
8 changed files with 106 additions and 22 deletions

View File

@@ -46,20 +46,37 @@ app.get<{ Params: { "*": string } }>(
},
},
async (req, res) => {
let url = req.params["*"];
// Idk anymore
let urls = Buffer.from(req.params["*"], 'base64').toString().split(";");
if (url.endsWith(".mp4")) {
url = url.slice(0, -4);
// Remove .mp4 extension if it exists
if (urls.at(-1)?.endsWith(".mp4")) {
urls = urls.slice(0, -1);
urls.push(urls.at(-1)?.slice(0, -4) as string);
}
url = decodeURIComponent(url);
urls = urls.map((url) => decodeURIComponent(url));
const result = await fetch(url).then((res) => res.arrayBuffer());
const result = await Promise.allSettled(
urls.map((url) => fetch(url).then((res) => res.arrayBuffer()))
);
const video = await tsToMpeg4(Buffer.from(result));
if (result.some((res) => res.status === "rejected")) {
res.status(400).send({ error: "Failed to fetch video" });
return;
}
const buffers = result.map((res) =>
res.status === "fulfilled" ? new Uint8Array(res.value) : new Uint8Array(0)
);
const video = await tsToMpeg4(buffers);
const fileName = `video-${new Date().toUTCString()}.mp4`;
res.header("Content-Type", "video/mp4");
res.header("Cache-Control", "public, max-age=604800");
res.header("Content-Disposition", `attachment; filename=${fileName}`);
res.send(video);
}
);

View File

@@ -8,11 +8,13 @@ import path from "node:path";
ffmpeg.setFfmpegPath(ffmpegPath as string);
export function tsToMpeg4(buffer: Buffer | Uint8Array): Promise<Buffer> {
export function tsToMpeg4(buffers: Uint8Array[]): Promise<Buffer> {
return new Promise((res, rej) => {
const input = new PassThrough();
input.end(buffer);
buffers.forEach((b) => input.write(b));
input.end();
const tempFilePath = path.join(tmpdir(), `output-${Date.now()}.mp4`);

View File

@@ -5,6 +5,7 @@ import { OEmbedTypes } from "../routes/getOEmbed";
import { parseEmbedImages } from "../lib/parseEmbedImages";
import { parseEmbedDescription } from "../lib/parseEmbedDescription";
import { StreamInfo } from "../lib/processVideoEmbed";
import { join } from "../lib/utils";
interface PostProps {
post: AppBskyFeedDefs.PostView;
@@ -23,11 +24,20 @@ const Meta = ({ post }: { post: AppBskyFeedDefs.PostView }) => (
const Video = ({
streamInfo,
apiUrl,
appDomain,
post,
description,
}: {
streamInfo: StreamInfo;
apiUrl: string;
appDomain: string;
post: AppBskyFeedDefs.PostView;
description: string;
}) => {
const url = `${apiUrl}generate/${encodeURIComponent(streamInfo.uri)}.mp4`;
// Discord can't handle query params in the URL, so i have to do this 🔥beautiful mess🔥
const url = `${apiUrl}generate/${btoa(join(streamInfo.uri, ";"))}.mp4`;
console.log(url);
return (
<>
@@ -54,6 +64,18 @@ const Video = ({
property="twitter:player:height"
content={streamInfo.resolution.height.toString()}
/>
<link
rel="alternate"
type="application/json+oembed"
href={`https:/${appDomain}/oembed?type=${OEmbedTypes.Video}&replies=${
post.replyCount
}&reposts=${post.repostCount}&likes=${
post.likeCount
}&avatar=${encodeURIComponent(
post.author.avatar ?? ""
)}&description=${encodeURIComponent(description)}`}
/>
</>
);
};
@@ -89,11 +111,14 @@ export const Post = ({
}: PostProps) => {
const images = parseEmbedImages(post);
const isAuthor = images === post.author.avatar;
const description = parseEmbedDescription(post);
console.log(post);
return (
<Layout url={url}>
<meta name="twitter:creator" content={`@${post.author.handle}`} />
<meta property="og:description" content={parseEmbedDescription(post)} />
<meta property="og:description" content={description} />
<meta
property="og:title"
content={`${post.author.displayName} (@${post.author.handle})`}
@@ -111,18 +136,28 @@ export const Post = ({
{images.length !== 0 && !videoMetadata && <Images images={images} />}
{videoMetadata && (
<Video apiUrl={apiUrl} streamInfo={videoMetadata.at(-1)!} />
<Video
apiUrl={apiUrl}
streamInfo={videoMetadata.at(-1)!}
appDomain={appDomain}
description={description}
post={post}
/>
)}
<link
rel="alternate"
type="application/json+oembed"
href={`https:/${appDomain}/oembed?type=${OEmbedTypes.Post}&replies=${
post.replyCount
}&reposts=${post.repostCount}&likes=${
post.likeCount
}&avatar=${encodeURIComponent(post.author.avatar ?? "")}`}
/>
{!videoMetadata && (
<link
rel="alternate"
type="application/json+oembed"
href={`https:/${appDomain}/oembed?type=${OEmbedTypes.Post}&replies=${
post.replyCount
}&reposts=${post.repostCount}&likes=${
post.likeCount
}&avatar=${encodeURIComponent(
post.author.avatar ?? ""
)}&description=${encodeURIComponent(description)}`}
/>
)}
</Layout>
);
};

View File

@@ -7,7 +7,7 @@ export interface StreamInfo {
height: number;
};
codecs: string;
uri: string;
uri: string | string[];
}
export interface M3U8Data {
@@ -81,7 +81,7 @@ async function parseM3U8(
const parsed = await parseM3U8(removeLastPathSegment(resolvedUrl), cont);
streams.at(-1)!.uri = parsed.streams[0].uri;
streams.at(-1)!.uri = parsed.streams.map((s) => s.uri as string);
} else if (line.includes(".ts")) {
streams.push({
bandwidth: 0,

12
pkgs/app/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,12 @@
export const concatQueryParams = (params: Record<string, string | string[]>) =>
Object.entries(params)
.map(([key, value]) => {
if (Array.isArray(value)) {
return value.map((v) => `${key}=${v}`).join("&");
}
return `${key}=${value}`;
})
.join("&");
export const join = (t: string | string[], s: string) =>
Array.isArray(t) ? t.join(s) : t;

View File

@@ -3,12 +3,15 @@ import { Handler } from "hono";
export enum OEmbedTypes {
Post = 1,
Profile,
Video,
}
export const getOEmbed: Handler<Env, "/oembed"> = async (c) => {
const type = +(c.req.query("type") ?? 0);
const avatar = c.req.query("avatar");
console.log(type);
const defaults = {
provider_name: "VixBluesky",
provider_url: "https://bskyx.app/",
@@ -36,5 +39,17 @@ export const getOEmbed: Handler<Env, "/oembed"> = async (c) => {
...defaults,
});
}
if (type === OEmbedTypes.Video) {
const { replies, reposts, likes, description } = c.req.query();
return c.json({
...defaults,
provider_name: `VixBluesky\n\n🗨 ${replies} ♻️ ${reposts} 💙 ${likes}`,
description,
title: description,
author_name: description,
});
}
return c.json(defaults, 400);
};

View File

@@ -11,6 +11,8 @@ export const getPost: Handler<
const { user, post } = c.req.param();
const agent = c.get("Agent");
const { data, success } = await fetchPost(agent, { user, post });
console.log(data);
if (!success) {
throw new HTTPException(500, {
message: "Failed to fetch the post!",

View File

@@ -13,3 +13,4 @@ VIXBLUESKY_API_URL="https://api.bskyx.app/"
[[kv_namespaces]]
binding = "bskyx"
id = "ee913536e1fb47dcb9fa6275725f5a6f"
preview_id = "5e180765d24346c9b238be57d9c7001f"