Loading snippets...
convert image bisa single dan multiple select image dan menjadi pdf
bashnode img2pdf.js <file|url> [file|url...] [-o output.pdf] [--page-size A4] [--orientation portrait] [--margin 0]
Example
bash# 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
bash{"ok":true,"output":"hasilnya.pdf","size":7669941}
/**
* 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); });
}