/** * transcribe audio files/url to text * base: https://freescribe.app/ * Creator: ShanMolvyr * Jangan Hapus Kreator hargai rakyat kecil * * Sumber: https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q */ const axios = require("axios"); const { randomBytes } = require("crypto"); const fs = require("fs"); const path = require("path"); const FormData = require("form-data"); const BASE_URL = "https://freescribe.app"; function generateVisitorId() { return randomBytes(16).toString("hex"); } const HEADERS = { "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", Origin: "https://freescribe.app", Referer: "https://freescribe.app/", }; async function getVideoInfo(url, language = "auto") { const res = await axios.post(`${BASE_URL}/api/video-info`, { url, language }, { headers: HEADERS }); return res.data; } async function getTranscript(url, language = "auto", visitorId) { const res = await axios.post(`${BASE_URL}/api/transcript`, { url, language, visitorId }, { headers: HEADERS }); return { data: res.data, rateLimit: { limit: res.headers["x-ratelimit-limit"], remaining: res.headers["x-ratelimit-remaining"], reset: res.headers["x-ratelimit-reset"], }, }; } async function transcribeUrl(videoUrl, language = "auto") { const visitorId = generateVisitorId(); const info = await getVideoInfo(videoUrl, language); const { data, rateLimit } = await getTranscript(videoUrl, language, visitorId); return { visitorId, platform: info.platform, url: data.url, language, id: data.id, segments: data.segments, fullText: data.segments.map((s) => s.text).join(" "), rateLimit, }; } async function transcribeFile(filePathOrUrl, language = "id") { const NEXT_ACTION_ID = "7f23a5c300d194d27bdf76b07a09cd419158a19017"; const form = new FormData(); let fileBuffer, fileName, mimeType; const isRemote = filePathOrUrl.startsWith("http://") || filePathOrUrl.startsWith("https://"); if (isRemote) { const dlRes = await axios.get(filePathOrUrl, { responseType: "arraybuffer" }); fileBuffer = Buffer.from(dlRes.data); fileName = path.basename(new URL(filePathOrUrl).pathname) || "audio.mp3"; mimeType = dlRes.headers["content-type"] || "audio/mpeg"; } else { fileBuffer = fs.readFileSync(filePathOrUrl); fileName = path.basename(filePathOrUrl); mimeType = "audio/mpeg"; } form.append("1_file", fileBuffer, { filename: fileName, contentType: mimeType }); form.append("1_language", language); form.append("0", JSON.stringify([{ action: "$K1", id: "1" }])); const res = await axios.post(`${BASE_URL}/`, form, { headers: { ...form.getHeaders(), "Next-Action": NEXT_ACTION_ID, "Next-Router-State-Tree": encodeURIComponent( JSON.stringify(["", { children: [["locale", "en", "d"], { children: ["(marketing)", { children: ["(home)", { children: ["__PAGE__", {}, "/", "refresh"] }] }, null, null] }, null, null] }, null, null, true]) ), "User-Agent": HEADERS["User-Agent"], Origin: BASE_URL, Referer: `${BASE_URL}/`, }, }); const rawText = typeof res.data === "string" ? res.data : JSON.stringify(res.data); let uploadId = null; for (const line of rawText.split("\n")) { try { const parsed = JSON.parse(line.replace(/^\d+:/, "")); if (parsed?.data?.success && parsed?.data?.id) { uploadId = parsed.data.id; break; } } catch (_) {} } if (!uploadId) { throw new Error("Upload failed: " + rawText); } return { mode: "file", uploadId, fileName, language }; } async function main() { const [, , mode, target, language] = process.argv; if (!mode || !target) { process.exit(1); } try { let result; if (mode === "url") { result = await transcribeUrl(target, language || "auto"); } else if (mode === "file") { result = await transcribeFile(target, language || "id"); } else { process.exit(1); } console.log(JSON.stringify(result, null, 2)); } catch (err) { console.log(JSON.stringify({ error: true, message: err.message, status: err.response?.status || null, data: err.response?.data || null, }, null, 2)); process.exit(1); } } main();