diff --git a/pkgs/api/src/index.ts b/pkgs/api/src/index.ts index b872e00..2acbd9e 100644 --- a/pkgs/api/src/index.ts +++ b/pkgs/api/src/index.ts @@ -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); } ); diff --git a/pkgs/api/src/utils.ts b/pkgs/api/src/utils.ts index aea4e29..6678248 100644 --- a/pkgs/api/src/utils.ts +++ b/pkgs/api/src/utils.ts @@ -8,11 +8,13 @@ import path from "node:path"; ffmpeg.setFfmpegPath(ffmpegPath as string); -export function tsToMpeg4(buffer: Buffer | Uint8Array): Promise { +export function tsToMpeg4(buffers: Uint8Array[]): Promise { 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`); diff --git a/pkgs/app/src/components/Post.tsx b/pkgs/app/src/components/Post.tsx index 0ee1f36..3fab498 100644 --- a/pkgs/app/src/components/Post.tsx +++ b/pkgs/app/src/components/Post.tsx @@ -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()} /> + + ); }; @@ -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 ( - + } {videoMetadata && ( - ); }; diff --git a/pkgs/app/src/lib/processVideoEmbed.ts b/pkgs/app/src/lib/processVideoEmbed.ts index 284147d..3ad4f72 100644 --- a/pkgs/app/src/lib/processVideoEmbed.ts +++ b/pkgs/app/src/lib/processVideoEmbed.ts @@ -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, diff --git a/pkgs/app/src/lib/utils.ts b/pkgs/app/src/lib/utils.ts new file mode 100644 index 0000000..257be6d --- /dev/null +++ b/pkgs/app/src/lib/utils.ts @@ -0,0 +1,12 @@ +export const concatQueryParams = (params: Record) => + 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; diff --git a/pkgs/app/src/routes/getOEmbed.ts b/pkgs/app/src/routes/getOEmbed.ts index a828585..0f7b545 100644 --- a/pkgs/app/src/routes/getOEmbed.ts +++ b/pkgs/app/src/routes/getOEmbed.ts @@ -3,12 +3,15 @@ import { Handler } from "hono"; export enum OEmbedTypes { Post = 1, Profile, + Video, } export const getOEmbed: Handler = 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 = 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); }; diff --git a/pkgs/app/src/routes/getPost.tsx b/pkgs/app/src/routes/getPost.tsx index ff51822..b94a331 100644 --- a/pkgs/app/src/routes/getPost.tsx +++ b/pkgs/app/src/routes/getPost.tsx @@ -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!", diff --git a/pkgs/app/wrangler.toml b/pkgs/app/wrangler.toml index bd0e04d..d15be37 100644 --- a/pkgs/app/wrangler.toml +++ b/pkgs/app/wrangler.toml @@ -13,3 +13,4 @@ VIXBLUESKY_API_URL="https://api.bskyx.app/" [[kv_namespaces]] binding = "bskyx" id = "ee913536e1fb47dcb9fa6275725f5a6f" +preview_id = "5e180765d24346c9b238be57d9c7001f"