Spaces:
Paused
Paused
- lib/ytdl.js +56 -33
lib/ytdl.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
| 1 |
const { spawn } = require("child_process");
|
| 2 |
const path = require("path");
|
|
|
|
| 3 |
|
| 4 |
-
const
|
| 5 |
-
const
|
|
|
|
| 6 |
|
| 7 |
function runYtDlp(args) {
|
| 8 |
return new Promise((resolve, reject) => {
|
| 9 |
const process = spawn(ytdlp, args, { stdio: ["ignore", "pipe", "pipe"] });
|
| 10 |
let stdout = "";
|
| 11 |
let stderr = "";
|
| 12 |
-
|
| 13 |
process.stdout.on("data", (data) => (stdout += data.toString()));
|
| 14 |
process.stderr.on("data", (data) => (stderr += data.toString()));
|
| 15 |
-
|
| 16 |
process.on("close", (code) => {
|
| 17 |
if (code === 0) resolve(stdout.trim());
|
| 18 |
else reject(new Error(stderr.trim() || `yt-dlp exited with code ${code}`));
|
|
@@ -20,42 +22,63 @@ function runYtDlp(args) {
|
|
| 20 |
});
|
| 21 |
}
|
| 22 |
|
| 23 |
-
|
| 24 |
-
* Mengambil metadata video Twitter menggunakan yt-dlp.
|
| 25 |
-
* @param {string} url - URL tweet yang berisi video.
|
| 26 |
-
* @returns {Promise<Object>} - Metadata video dalam format JSON.
|
| 27 |
-
*/
|
| 28 |
-
async function getTwitterVideoInfo(url) {
|
| 29 |
try {
|
| 30 |
const result = await runYtDlp(["-j", "--cookies", cookiesPath, url]);
|
| 31 |
const info = JSON.parse(result);
|
| 32 |
-
|
| 33 |
-
// Pastikan `formats` ada, jika tidak buat array kosong
|
| 34 |
-
const videoFormats = (info.formats || []).filter(
|
| 35 |
-
(f) => (f.video_ext !== "none" && f.audio_ext !== "none") || f.url.includes(".mp4")
|
| 36 |
-
);
|
| 37 |
-
|
| 38 |
-
const audioFormats = (info.formats || []).filter((f) => f.video_ext === "none" && f.audio_ext !== "none");
|
| 39 |
-
|
| 40 |
return {
|
| 41 |
-
|
| 42 |
-
title: info.title
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
};
|
| 56 |
} catch (err) {
|
| 57 |
throw new Error(`Failed to get video info: ${err.message}`);
|
| 58 |
}
|
| 59 |
}
|
| 60 |
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
const { spawn } = require("child_process");
|
| 2 |
const path = require("path");
|
| 3 |
+
const fs = require("fs");
|
| 4 |
|
| 5 |
+
const tmp = path.join(__dirname, "../tmp"); // path to your tmp folder
|
| 6 |
+
const ytdlp = "yt-dlp"; // Assume yt-dlp is installed globally inside the Docker container
|
| 7 |
+
const cookiesPath = path.join(__dirname, "./cookies/yt.txt");
|
| 8 |
|
| 9 |
function runYtDlp(args) {
|
| 10 |
return new Promise((resolve, reject) => {
|
| 11 |
const process = spawn(ytdlp, args, { stdio: ["ignore", "pipe", "pipe"] });
|
| 12 |
let stdout = "";
|
| 13 |
let stderr = "";
|
| 14 |
+
|
| 15 |
process.stdout.on("data", (data) => (stdout += data.toString()));
|
| 16 |
process.stderr.on("data", (data) => (stderr += data.toString()));
|
| 17 |
+
|
| 18 |
process.on("close", (code) => {
|
| 19 |
if (code === 0) resolve(stdout.trim());
|
| 20 |
else reject(new Error(stderr.trim() || `yt-dlp exited with code ${code}`));
|
|
|
|
| 22 |
});
|
| 23 |
}
|
| 24 |
|
| 25 |
+
async function getInfo(url) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
try {
|
| 27 |
const result = await runYtDlp(["-j", "--cookies", cookiesPath, url]);
|
| 28 |
const info = JSON.parse(result);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
return {
|
| 30 |
+
id: info.id,
|
| 31 |
+
title: info.title,
|
| 32 |
+
thumbnail: `https://i.ytimg.com/vi/${info.id}/maxresdefault.jpg`,
|
| 33 |
+
description: info.description,
|
| 34 |
+
duration: info.duration,
|
| 35 |
+
views: info.view_count,
|
| 36 |
+
likes: info.like_count,
|
| 37 |
+
comments: info.comment_count,
|
| 38 |
+
uploaded: info.timestamp,
|
| 39 |
+
channel: {
|
| 40 |
+
id: info.channel_id,
|
| 41 |
+
handle: info.uploader_id,
|
| 42 |
+
name: info.uploader,
|
| 43 |
+
picture: info.channel_thumbnail || "",
|
| 44 |
+
subscribers: info.channel_follower_count,
|
| 45 |
+
verified: !!info.channel_is_verified,
|
| 46 |
+
},
|
| 47 |
+
videos: [...new Set(info.formats.filter(v => v.video_ext === "mp4").map(v => v.height + "p"))],
|
| 48 |
};
|
| 49 |
} catch (err) {
|
| 50 |
throw new Error(`Failed to get video info: ${err.message}`);
|
| 51 |
}
|
| 52 |
}
|
| 53 |
|
| 54 |
+
async function getVideo(url, quality = "480p") {
|
| 55 |
+
const ts = Date.now();
|
| 56 |
+
const outputFile = `${tmp}/${ts}.mp4`;
|
| 57 |
+
try {
|
| 58 |
+
await runYtDlp(["-f", `ba[ext=m4a]+bv[height=${quality.slice(0, -1)}]`, "--merge-output-format", "mp4", "-o", outputFile, url]);
|
| 59 |
+
const result = fs.readFileSync(outputFile);
|
| 60 |
+
fs.unlinkSync(outputFile);
|
| 61 |
+
return result;
|
| 62 |
+
} catch (err) {
|
| 63 |
+
throw new Error(`Failed to download video: ${err.message}`);
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
async function getAudio(url) {
|
| 68 |
+
const ts = Date.now();
|
| 69 |
+
const outputFile = `${tmp}/${ts}.mp3`;
|
| 70 |
+
try {
|
| 71 |
+
await runYtDlp(["-f", "ba", "-o", outputFile, url]);
|
| 72 |
+
const result = fs.readFileSync(outputFile);
|
| 73 |
+
fs.unlinkSync(outputFile);
|
| 74 |
+
return result;
|
| 75 |
+
} catch (err) {
|
| 76 |
+
throw new Error(`Failed to download audio: ${err.message}`);
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
module.exports = {
|
| 81 |
+
getInfo,
|
| 82 |
+
getAudio,
|
| 83 |
+
getVideo,
|
| 84 |
+
};
|