img2pdf
convert image bisa single dan multiple select image dan menjadi pdf
3 Juni 2026492 file
README.mdmarkdown · 34 baris
Cara Pakai
node img2pdf.js <file|url> [file|url...] [-o output.pdf] [--page-size A4] [--orientation portrait] [--margin 0]
Example
# URL langsung
node img2pdf.js https://example.com/a.jpg https://example.com/b.webp -o hasil.pdf
# File lokal mix format
node img2pdf.js a.jpg b.png c.webp -o hasil.pdf
# Dengan opsi halaman
node img2pdf.js *.jpg -o hasil.pdf --page-size A4 --orientation portrait --margin 10
Output
{"ok":true,"output":"hasilnya.pdf","size":7669941}
📝 Output
<p align="center"> <img src="https://lokerku.pages.dev/i/6zho7txn" width="900"> </p>img2pdf.jsjavascript · 136 baris
/** * convert gambar ke pdf * base: https://tools.pdf24.org/id/gambar-ke-pdf * Creator: ShanMolvyr * Jangan Hapus Kreator hargai rakyat kecil * Note: support dari direct link image dan image lokal * Sumber: https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q */const fs = require("fs");const path = require("path");const axios = require("axios");const FormData = require("form-data");const API = "https://filetools7.pdf24.org/client.php";const H = { Origin: "https://tools.pdf24.org", Referer: "https://tools.pdf24.org/", "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36",};const jar = {};function saveCookies(headers) { const raw = headers["set-cookie"]; if (!raw) return; for (const c of (Array.isArray(raw) ? raw : [raw])) { const [pair] = c.split(";"); const idx = pair.indexOf("="); if (idx < 0) continue; jar[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim(); }}function ch() { const s = Object.entries(jar).map(([k, v]) => `${k}=${v}`).join("; "); return s ? { Cookie: s } : {};}const isUrl = (s) => /^https?:\/\//i.test(s);const isWebp = (s) => /\.webp$/i.test(isUrl(s) ? new URL(s).pathname : s);const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const out = (data) => process.stdout.write(JSON.stringify(data) + "\n");async function fetchStream(url) { const res = await axios.get(url, { responseType: "stream", timeout: 30000 }); let filename = path.basename(new URL(url).pathname) || "image"; const cd = res.headers["content-disposition"]; if (cd) { const m = cd.match(/filename[^;=\n]*=(["']?)(.+)\1/i); if (m) filename = m[2].trim(); } if (!path.extname(filename)) filename += `.${(res.headers["content-type"] || "").split("/")[1]?.split(";")[0] || "jpg"}`; return { stream: res.data, filename, contentType: res.headers["content-type"] || "image/jpeg" };}async function upload(source) { const form = new FormData(); if (isUrl(source)) { const { stream, filename, contentType } = await fetchStream(source); form.append("file", stream, { filename, contentType }); } else { if (!fs.existsSync(source)) throw new Error(`File not found: ${source}`); form.append("file", fs.createReadStream(source), { filename: path.basename(source) }); } const res = await axios.post(`${API}?action=upload`, form, { headers: { ...form.getHeaders(), ...H, ...ch(), Accept: "application/json" }, maxBodyLength: Infinity, timeout: 60000, }); saveCookies(res.headers); return Array.isArray(res.data) ? res.data[0] : res.data;}async function pollJob(jobId) { const deadline = Date.now() + 120000; while (Date.now() < deadline) { const res = await axios.post(`${API}?action=getStatus`, new URLSearchParams({ jobId }).toString(), { headers: { ...H, ...ch(), "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", Accept: "*/*" }, timeout: 15000, }); saveCookies(res.headers); if (res.data.status === "done") return res.data.job; if (res.data.status === "error") throw new Error(`job error: ${JSON.stringify(res.data)}`); await sleep(2000); } throw new Error("timeout");}async function convertToJpg(f) { const res = await axios.post(`${API}?action=convertImageFormat`, { files: [{ file: f.file, size: f.size, ctime: f.ctime, host: f.host, name: f.name }], colorSpace: "RGB", dpi: 150, imageFormat: "jpg", jpgQuality: 95, }, { headers: { ...H, ...ch(), "Content-Type": "application/json;charset=UTF-8", Accept: "*/*" }, timeout: 30000 }); saveCookies(res.headers); const job = await pollJob(res.data.jobId); return { file: job["0.out.file"], size: parseInt(job["0.out.size"] || 0), ctime: f.ctime, host: job["0.out.host"] || f.host, name: f.name.replace(/\.webp$/i, ".jpg") };}async function imagesToPdf(sources, output = "result.pdf", opts = {}) { if (!sources?.length) throw new Error("no sources"); const files = []; for (const src of sources) { let f = await upload(src); if (isWebp(src)) f = await convertToJpg(f); files.push(f); } const convRes = await axios.post(`${API}?action=imagesToPdf`, { files: files.map((f) => ({ file: f.file, size: f.size, ctime: f.ctime, host: f.host, name: f.name })), rotations: files.map(() => 0), joinFiles: true, createBookmarks: false, pageSize: opts.pageSize ?? "auto", pageOrientation: opts.pageOrientation ?? "auto", margin: opts.margin ?? "0", }, { headers: { ...H, ...ch(), "Content-Type": "application/json;charset=UTF-8", Accept: "*/*" }, timeout: 30000 }); saveCookies(convRes.headers); const job = await pollJob(convRes.data.jobId); const dlRes = await axios.get(`${API}?action=getFile&file=${job["out.file"]}`, { headers: { ...H, ...ch() }, responseType: "arraybuffer", timeout: 60000, }); fs.writeFileSync(output, dlRes.data); return { ok: true, output, size: dlRes.data.byteLength };}module.exports = { imagesToPdf };if (require.main === module) { const args = process.argv.slice(2); const sources = [], opts = {}; let output = "result.pdf"; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a === "-o") output = args[++i]; else if (a === "--page-size") opts.pageSize = args[++i]; else if (a === "--orientation") opts.pageOrientation = args[++i]; else if (a === "--margin") opts.margin = args[++i]; else if (!a.startsWith("-")) sources.push(a); } imagesToPdf(sources, output, opts) .then(out) .catch((e) => { out({ ok: false, error: e.message }); process.exit(1); });}Komentar
Masuk untuk ikut berkomentar.
Belum ada komentar.