UNPKG

@jrmc/adonis-attachment

Version:

Turn any field on your Lucid model to an attachment data type

116 lines (115 loc) 4.1 kB
/** * @jrmc/adonis-attachment * * @license MIT * @copyright Jeremy Chaufourier <jeremy@chaufourier.fr> */ import os from 'node:os'; import path from 'node:path'; import { $ } from 'execa'; import { cuid } from '@adonisjs/core/helpers'; import logger from '@adonisjs/core/services/logger'; import { attachmentManager } from '@jrmc/adonis-attachment'; import { DateTime } from 'luxon'; export default class Poppler { input; #pdfToPpmPath; #pdfInfoPath; #timeout; #TIMEOUT; constructor(input) { this.input = input; this.#pdfToPpmPath = 'pdftoppm'; this.#pdfInfoPath = 'pdfinfo'; this.#timeout = null; this.#TIMEOUT = attachmentManager.getConfig().timeout || 30_000; } #createAbortController() { this.#cleanup(); const controller = new AbortController(); this.#timeout = setTimeout(() => { controller.abort(); }, this.#TIMEOUT); return controller; } #cleanup() { if (this.#timeout) { clearTimeout(this.#timeout); } } async pdfToPpm(options) { const output = path.join(os.tmpdir(), cuid()); try { await $({ cancelSignal: this.#createAbortController().signal, gracefulCancel: true, timeout: this.#TIMEOUT }) `${this.#pdfToPpmPath} -f ${options.page.toString()} -l ${options.page.toString()} -r ${options.dpi.toString()} -jpeg ${this.input} ${output}`; const pdfInfo = await this.pdfInfo(); const pageNumberFormat = '0'.repeat(String(pdfInfo.pages).length - String(options.page).length); return `${output}-${pageNumberFormat}${options.page}.jpg`; } catch (error) { logger.error('Error while converting PDF to PPM:', error); throw error; } finally { this.#cleanup(); } } async pdfInfo() { try { const { stdout } = await $({ cancelSignal: this.#createAbortController().signal, gracefulCancel: true, timeout: this.#TIMEOUT }) `${this.#pdfInfoPath} ${this.input}`; const metadata = {}; stdout.split('\n').forEach((line) => { const colonIndex = line.indexOf(':'); if (colonIndex > 0) { const key = line.substring(0, colonIndex).trim(); const value = line.substring(colonIndex + 1).trim(); if (key && value) { metadata[key.toLowerCase()] = value; } } }); const pageSizeMatch = metadata['page size']?.match(/(\d+)\s*x\s*(\d+)/); const [width, height] = pageSizeMatch ? [parseInt(pageSizeMatch[1]), parseInt(pageSizeMatch[2])] : [0, 0]; const fileSizeMatch = metadata['file size']?.match(/(\d+)/); const size = fileSizeMatch ? parseInt(fileSizeMatch[1]) : 0; const version = metadata['pdf version'] || ''; const pages = parseInt(metadata['pages'] || '0'); let creationDate = ''; if (metadata['creationdate']) { const dateStr = metadata['creationdate']; const date = DateTime.fromFormat(dateStr, 'EEE MMM dd HH:mm:ss yyyy z', { zone: 'UTC' }); if (date.isValid) { creationDate = date.toFormat('yyyy-MM-dd\'T\'HH:mm:ss'); } } return { size, version, width, height, pages, creationDate }; } catch (error) { logger.error('Error while retrieving metadata:', error); throw error; } finally { this.#cleanup(); } } async setPdfToPpmPath(pdftoppm) { this.#pdfToPpmPath = pdftoppm; } async setPdfInfoPath(pdfinfo) { this.#pdfInfoPath = pdfinfo; } }