Add video metadata to video for sending

This commit is contained in:
Sudo Space 2025-04-19 21:37:53 +03:30
parent c9c8df96c6
commit e1224d9dee
9 changed files with 111 additions and 1 deletions

View file

@ -28,7 +28,7 @@ Just send your media link to bot and get your content 😃. Of course, the admin
## Setup and deploy
1. Install [Bun](https://bun.sh), [Ytdlp](https://github.com/yt-dlp/yt-dlp/wiki/Installation#installing-the-release-binary) and [aria2](https://github.com/aria2/aria2).
1. Install [Bun](https://bun.sh), [Ytdlp](https://github.com/yt-dlp/yt-dlp/wiki/Installation#installing-the-release-binary), [ffmpeg](https://ffmpeg.org/) and [aria2](https://github.com/aria2/aria2).
2. Install [Docker](https://docs.docker.com/engine/install/) and run [aiogram/telegram-bot-api](https://hub.docker.com/r/aiogram/telegram-bot-api) image as a container.
3. Clone it this repo `git clone https://github.com/sudospaes/rigel.git`.
4. Move to cloned directory.

View file

@ -6,10 +6,12 @@
"dependencies": {
"@grammyjs/storage-prisma": "^2.5.1",
"@prisma/client": "^6.6.0",
"fluent-ffmpeg": "^2.1.3",
"grammy": "^1.35.1",
},
"devDependencies": {
"@types/bun": "latest",
"@types/fluent-ffmpeg": "^2.1.27",
"prisma": "^6.6.0",
},
"peerDependencies": {
@ -88,12 +90,16 @@
"@types/bun": ["@types/bun@1.2.9", "", { "dependencies": { "bun-types": "1.2.9" } }, "sha512-epShhLGQYc4Bv/aceHbmBhOz1XgUnuTZgcxjxk+WXwNyDXavv5QHD1QEFV0FwbTSQtNq6g4ZcV6y0vZakTjswg=="],
"@types/fluent-ffmpeg": ["@types/fluent-ffmpeg@2.1.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A=="],
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"async": ["async@0.2.10", "", {}, "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="],
"bun-types": ["bun-types@1.2.9", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
@ -104,10 +110,14 @@
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
"fluent-ffmpeg": ["fluent-ffmpeg@2.1.3", "", { "dependencies": { "async": "^0.2.9", "which": "^1.1.1" } }, "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"grammy": ["grammy@1.35.1", "", { "dependencies": { "@grammyjs/types": "3.19.0", "abort-controller": "^3.0.0", "debug": "^4.3.4", "node-fetch": "^2.7.0" } }, "sha512-6kNMODaXVj0R+dqYcHqMyjOURsCUFGvVkd7z/dDO1H1cqrGW/kyAFc4oETUKkcyUN/X80N0DchNVuc2JmTPxPQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
@ -123,5 +133,7 @@
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
}
}

View file

@ -5,6 +5,7 @@
"license": "BSD-3-Clause",
"devDependencies": {
"@types/bun": "latest",
"@types/fluent-ffmpeg": "^2.1.27",
"prisma": "^6.6.0"
},
"peerDependencies": {
@ -13,6 +14,7 @@
"dependencies": {
"@grammyjs/storage-prisma": "^2.5.1",
"@prisma/client": "^6.6.0",
"fluent-ffmpeg": "^2.1.3",
"grammy": "^1.35.1"
},
"scripts": {

41
src/helpers/ffmpeg.ts Normal file
View file

@ -0,0 +1,41 @@
import { join } from "path";
import ffmpeg from "fluent-ffmpeg";
import { rootPath } from "helpers/utils";
import type { VideoMetadata } from "types/interface";
export async function getMetadata(filePath: string): Promise<VideoMetadata> {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(filePath, (err, data) => {
if (err) return reject(err);
if (!data.streams[0]) return reject(new Error("No stream found"));
resolve({
height: data.streams[0].height!,
width: data.streams[0].width!,
duration: data.format.duration!,
});
});
});
}
export async function getThumbnail(filePath: string): Promise<string> {
const uuid = Bun.randomUUIDv7();
const thumbPath = join(rootPath(), "thumbnails", `${uuid}.jpg`);
return new Promise((resolve, reject) => {
ffmpeg(filePath)
.thumbnail({
count: 1,
filename: `${uuid}.jpg`,
size: "320x320",
folder: join(rootPath(), "thumbnails"),
})
.on("end", () => {
return resolve(thumbPath);
})
.on("error", (err) => {
return reject(err);
});
});
}

View file

@ -15,3 +15,10 @@ export function runDetached(fn: () => Promise<any>) {
}
})();
}
export async function removeFile(path: string) {
const file = Bun.file(path);
if (await file.exists()) {
await file.delete();
}
}

View file

@ -5,6 +5,8 @@ import type { UserContext } from "types/type";
import Instagram from "models/instagram";
import { waitList, waitForDownload, waitForArchiving } from "helpers/ytdlp";
import { sendFromArchive, addToArchive } from "helpers/archive";
import { getMetadata, getThumbnail } from "helpers/ffmpeg";
import { removeFile } from "helpers/utils";
const caption = Bun.env.CAPTION as string;
@ -46,11 +48,21 @@ async function handleInstagram(ctx: UserContext, url: string) {
"⬆️ Uploading to Telegram..."
);
const metadata = await getMetadata(ytdlp.filePath);
const thumbnail = await getThumbnail(ytdlp.filePath);
const file = await ctx.replyWithVideo(new InputFile(ytdlp.filePath), {
reply_parameters: { message_id: ctx.msgId! },
duration: metadata.duration,
supports_streaming: true,
height: metadata.height,
width: metadata.width,
thumbnail: new InputFile(thumbnail),
caption,
});
await removeFile(thumbnail);
ctx.api.deleteMessage(msg.chat.id, msg.message_id);
await addToArchive(url, {

View file

@ -5,6 +5,8 @@ import type { UserContext } from "types/type";
import Pinterest from "models/pinterest";
import { waitList, waitForDownload, waitForArchiving } from "helpers/ytdlp";
import { sendFromArchive, addToArchive } from "helpers/archive";
import { getMetadata, getThumbnail } from "helpers/ffmpeg";
import { removeFile } from "helpers/utils";
const caption = Bun.env.CAPTION as string;
@ -46,11 +48,21 @@ async function handlePinterest(ctx: UserContext, url: string) {
"⬆️ Uploading to Telegram..."
);
const metadata = await getMetadata(ytdlp.filePath);
const thumbnail = await getThumbnail(ytdlp.filePath);
const file = await ctx.replyWithVideo(new InputFile(ytdlp.filePath), {
reply_parameters: { message_id: ctx.msgId! },
duration: metadata.duration,
supports_streaming: true,
height: metadata.height,
width: metadata.width,
thumbnail: new InputFile(thumbnail),
caption,
});
await removeFile(thumbnail);
ctx.api.deleteMessage(msg.chat.id, msg.message_id);
await addToArchive(url, {

View file

@ -5,6 +5,8 @@ import type { UserContext } from "types/type";
import Tiktok from "models/tiktok";
import { waitList, waitForDownload, waitForArchiving } from "helpers/ytdlp";
import { sendFromArchive, addToArchive } from "helpers/archive";
import { getMetadata, getThumbnail } from "helpers/ffmpeg";
import { removeFile } from "helpers/utils";
const caption = Bun.env.CAPTION as string;
@ -46,11 +48,21 @@ async function handleTiktok(ctx: UserContext, url: string) {
"⬆️ Uploading to Telegram..."
);
const metadata = await getMetadata(ytdlp.filePath);
const thumbnail = await getThumbnail(ytdlp.filePath);
const file = await ctx.replyWithVideo(new InputFile(ytdlp.filePath), {
reply_parameters: { message_id: ctx.msgId! },
duration: metadata.duration,
supports_streaming: true,
height: metadata.height,
width: metadata.width,
thumbnail: new InputFile(thumbnail),
caption,
});
await removeFile(thumbnail);
ctx.api.deleteMessage(msg.chat.id, msg.message_id);
await addToArchive(url, {

View file

@ -5,6 +5,8 @@ import type { UserContext } from "types/type";
import Youtube from "models/youtube";
import { waitList, waitForDownload, waitForArchiving } from "helpers/ytdlp";
import { sendFromArchive, addToArchive } from "helpers/archive";
import { getMetadata, getThumbnail } from "helpers/ffmpeg";
import { removeFile } from "helpers/utils";
const caption = Bun.env.CAPTION as string;
@ -110,11 +112,21 @@ async function handleYoutube(
"⬆️ Uploading to Telegram..."
);
const metadata = await getMetadata(ytdlp.filePath);
const thumbnail = await getThumbnail(ytdlp.filePath);
const file = await ctx.replyWithVideo(new InputFile(ytdlp.filePath), {
reply_parameters: { message_id: +msgId },
duration: metadata.duration,
height: metadata.height,
width: metadata.width,
supports_streaming: true,
thumbnail: new InputFile(thumbnail),
caption,
});
await removeFile(thumbnail);
await addToArchive(url, {
chatId: file.chat.id.toString(),
msgId: file.message_id,