fixes for video and embed descriptions (#9)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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`);
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
12
pkgs/app/src/lib/utils.ts
Normal 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;
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -13,3 +13,4 @@ VIXBLUESKY_API_URL="https://api.bskyx.app/"
|
||||
[[kv_namespaces]]
|
||||
binding = "bskyx"
|
||||
id = "ee913536e1fb47dcb9fa6275725f5a6f"
|
||||
preview_id = "5e180765d24346c9b238be57d9c7001f"
|
||||
|
||||
Reference in New Issue
Block a user