GS Firebase 功能无法设置偏移量?

GS Firebase function can't set offset?

我正在使用 firebase 函数裁剪 pdf 的某些区域并使用 ghostscript 将它们转换为图像 [包装纸 https://www.npmjs.com/package/node-gs and compiled version of gs v9.2 "https://github.com/sina-masnadi/node-gs/tarball/master" ]

这是我正在使用的代码:

const functions = require('firebase-functions');
const { Storage } = require('@google-cloud/storage');
const gcs = new Storage();
const spawn = require('child-process-promise').spawn;
const path = require('path');
const os = require('os');
const fs = require('fs');
var gs = require('gs');


const THUMB_MAX_HEIGHT = 200;
const THUMB_MAX_WIDTH = 200;
const THUMB_SUFFIX = '-thumb';

//This function triggers whenever any pdf is uploaded to the firebase storage
//and attempts to generate

exports.makePreviews = functions.storage.object().onFinalize(async (object, event) => {

  //Checking for pdf files
  if (!object.name.endsWith('.pdf')) return false;

  const filePath = object.name;

  //slicing name and path
  const splitFileName = object.name.split(".");
  console.log(splitFileName);
  const fileID = splitFileName;

  //creating temporary path strings for gcp file system
  const fileName = path.basename(filePath);
  const tempFilePath = path.join(os.tmpdir(), fileName);

  const newName1 = path.basename(filePath, '.pdf') + '01.jpeg';
  const tempNewPath1 = path.join(os.tmpdir(), newName1);

  const newName2 = path.basename(filePath, '.pdf') + '02.jpeg';
  const tempNewPath2 = path.join(os.tmpdir(), newName2);

  const thumbName = path.basename(filePath, '.pdf') + THUMB_SUFFIX + '.jpeg';
  const tempThumbPath = path.join(os.tmpdir(), thumbName);


  //downloading file from firebase storage
  const bucket = gcs.bucket(object.bucket);

  return bucket.file(filePath).download({
    destination: tempFilePath
  }).then(async () => {
    console.log('PDF downloaded locally to', tempFilePath);

    //generating two preview JPEGS
    await new Promise((resolve, reject) => {
      gs()
        .safer()
        .batch()
        .nopause()
        .option('-dTextAlphaBits=4')
        .option('-dGraphicsAlphaBits=4')
        .option('-dDEVICEWIDTHPOINTS=238')
        .option('-dDEVICEHEIGHTPOINTS=149.5')
        .option('-dFIXEDMEDIA')
        .res(600)
        .option('-dDownScaleFactor=2')
        .executablePath('gs')
        .device('jpeg')
        .output(tempNewPath2)
        .option('-c "<</PageOffset[-308.5 40]>> setpagedevice"')
        .option('-sPDFPassword=01011977')
        .input(tempFilePath)
        .exec((err, stdout, stderr) => {
          if (!err) {
            console.log('Part One Exceuted');
            bucket.upload(tempNewPath1, {
              destination: 'files/' + fileID + '.jpeg'
            }).then(() => {
              console.log('stdout', stdout);
              console.log('stderr', stderr);
            }).catch(err => {
              console.log(err);
            });
            resolve();
          } else {
            console.log('gs error:', err);
            reject(err);
          }
        });
    });

    await new Promise((resolve, reject) => {
      gs()
        .safer()
        .batch()
        .nopause()
        .option('-dTextAlphaBits=4')
        .option('-dGraphicsAlphaBits=4')
        .option('-dDEVICEWIDTHPOINTS=238')
        .option('-dDEVICEHEIGHTPOINTS=149.5')
        .option('-dFIXEDMEDIA')
        .res(600)
        .option('-dDownScaleFactor=2')
        .executablePath('gs')
        .device('jpeg')
        .output(tempNewPath2)
        .option('-c "<</PageOffset[-308.5 40]>> setpagedevice"')
        .option('-sPDFPassword=01011977')
        .input(tempFilePath)
        .exec((err, stdout, stderr) => {
          if (!err) {
            console.log('gs Part two excuted');
            bucket.upload(tempNewPath1, {
              destination: 'files/' + fileID + '-2.jpeg'
            }).then(() => {
              console.log('stdout', stdout);
              console.log('stderr', stderr);
            })
              .catch(err => {
                console.log(err);
              });
            resolve();
          } else {
            console.log('gs error:', err);
            reject(err);
          }
        });
    });

    //generating thumbnail from the first JPEG
    return spawn('convert', [tempNewPath1, '-thumbnail', `${THUMB_MAX_WIDTH}x${THUMB_MAX_HEIGHT}>`, tempThumbPath], {
      capture: ['stdout', 'stderr']
    });

  }).then(async () => {
    console.log('PNG created at', tempNewPath1 + 'and' + tempNewPath2);
    console.log('Thumbnail created at', tempThumbPath);

    //uploading the files back to firebase storage
    return bucket.upload(tempThumbPath, {
      destination: 'files/' + fileID + 'thumb.jpeg'
    });


  }).then(() => {
    //once the files have been uploaded delete the local temporary 
    //files to free up disk space.
    fs.unlinkSync(tempNewPath1);
    fs.unlinkSync(tempNewPath2);
    fs.unlinkSync(tempThumbPath);
    return fs.unlinkSync(tempFilePath);
  }).catch((err) => {
    console.log('exception:', err);
    return err;
  });
});

部署上述代码,日志:

[ 'PAN_01011977', 'pdf' ]

PDF downloaded locally to /tmp/PAN_01011977.pdf

gs command: -dSAFER,-dBATCH,-dNOPAUSE,-dTextAlphaBits=4,-dGraphicsAlphaBits=4,-dDEVICEWIDTHPOINTS=238,-dDEVICEHEIGHTPOINTS=149.5,-dFIXEDMEDIA,-r600,-dDownScaleFactor=2,-sDEVICE=jpeg,-sOutputFile=/tmp/PAN_0101197702.jpeg,-c "<</PageOffset[-308.5 40]>> setpagedevice",-sPDFPassword=01011977,/tmp/PAN_01011977.pdf

Part One Exceuted

gs command: -dSAFER,-dBATCH,-dNOPAUSE,-dTextAlphaBits=4,-dGraphicsAlphaBits=4,-dDEVICEWIDTHPOINTS=238,-dDEVICEHEIGHTPOINTS=149.5,-dFIXEDMEDIA,-r600,-dDownScaleFactor=2,-sDEVICE=jpeg,-sOutputFile=/tmp/PAN_0101197702.jpeg,-c "<</PageOffset[-308.5 40]>> setpagedevice",-sPDFPassword=01011977,/tmp/PAN_01011977.pdf

{ Error: ENOENT: no such file or directory, stat '/tmp/PAN_0101197701.jpeg'
  errno: -2,
  code: 'ENOENT',
  syscall: 'stat',
  path: '/tmp/PAN_0101197701.jpeg' }

gs Part two excuted

{ Error: ENOENT: no such file or directory, stat '/tmp/PAN_0101197701.jpeg'
  errno: -2,
  code: 'ENOENT',
  syscall: 'stat',
  path: '/tmp/PAN_0101197701.jpeg' }

和错误:

and the error log

exception: { ChildProcessError: `convert /tmp/PAN_0101197701.jpeg -thumbnail 200x200> /tmp/PAN_01011977-thumb.jpeg` failed with code 1
    at ChildProcess.<anonymous> (/srv/node_modules/child-process-promise/lib/index.js:132:23)
    at emitTwo (events.js:126:13)
    at ChildProcess.emit (events.js:214:7)
    at maybeClose (internal/child_process.js:915:16)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:209:5)
  name: 'ChildProcessError',
  code: 1,
  childProcess: 
   ChildProcess {
     domain: 
      Domain {
        domain: null,
        _events: [Object],
        _eventsCount: 1,
        _maxListeners: undefined,
        members: [Array] },
     _events: { error: [Function], close: [Function] },
     _eventsCount: 2,
     _maxListeners: undefined,
     _closesNeeded: 3,
     _closesGot: 3,
     connected: false,
     signalCode: null,
     exitCode: 1,
     killed: false,
     spawnfile: 'convert',
     _handle: null,
     spawnargs: 
      [ 'convert',
        '/tmp/PAN_0101197701.jpeg',
        '-thumbnail',
        '200x200>',
        '/tmp/PAN_01011977-thumb.jpeg' ],
     pid: 14,
     stdin: 
      Socket {
        connecting: false,
        _hadError: false,
        _handle: null,
        _parent: null,
        _host: null,
        _readableState: [Object],
        readable: false,
        domain: [Object],
        _events: [Object],
        _eventsCount: 2,
        _maxListeners: undefined,
        _writableState: [Object],
        writable: false,
        allowHalfOpen: false,
        _bytesDispatched: 0,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        _idleNext: null,
        _idlePrev: null,
        _idleTimeout: -1,
        [Symbol(asyncId)]: 4540,
        [Symbol(bytesRead)]: 0 },
     stdout: 
      Socket {
        connecting: false,
        _hadError: false,
        _handle: null,
        _parent: null,
        _host: null,
        _readableState: [Object],
        readable: false,
        domain: [Object],
        _events: [Object],
        _eventsCount: 3,
        _maxListeners: undefined,
        _writableState: [Object],
        writable: false,
        allowHalfOpen: false,
        _bytesDispatched: 0,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        _idleNext: null,
        _idlePrev: null,
        _idleTimeout: -1,
        write: [Function: writeAfterFIN],
        [Symbol(asyncId)]: 4541,
        [Symbol(bytesRead)]: 0 },
     stderr: 
      Socket {
        connecting: false,
        _hadError: false,
        _handle: null,
        _parent: null,
        _host: null,
        _readableState: [Object],
        readable: false,
        domain: [Object],
        _events: [Object],
        _eventsCount: 3,
        _maxListeners: undefined,
        _writableState: [Object],
        writable: false,
        allowHalfOpen: false,
        _bytesDispatched: 0,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        _idleNext: null,
        _idlePrev: null,
        _idleTimeout: -1,
        write: [Function: writeAfterFIN],
        [Symbol(asyncId)]: 4542,
        [Symbol(bytesRead)]: 232 },
     stdio: [ [Object], [Object], [Object] ] },
  stdout: '',
  stderr: 'convert-im6.q16: unable to open image `/tmp/PAN_0101197701.jpeg\': No such file or directory @ error/blob.c/OpenBlob/2701.\nconvert-im6.q16: no images defined `/tmp/PAN_01011977-thumb.jpeg\' @ error/convert.c/ConvertImageCommand/3258.\n' }

Error serializing return value: TypeError: Converting circular structure to JSON


Function execution took 9561 ms, finished with status: 'ok'

问题在于在没有此功能的情况下使用 gs 中的以下选项,但它没有裁剪 pdf 只是转换为整页图像。

  //.option('-c "<</PageOffset [ -64.2 40 ]>> setpagedevice"')
  //.option('-c "<</PageOffset [ -308.5 40 ]>> setpagedevice"')

如何使用上述选项?

编辑

尝试用 -f 终止 -c 但没有成功

$ node index.js
gs command: -dSAFER,-dBATCH,-dNOPAUSE,-dTextAlphaBits=4,-dGraphicsAlphaBits=4,-dDEVICEWIDTHPOINTS=238,-dDEVICEHEIGHTPOINTS=149.5,-dFIXEDMEDIA,-r150,-dDownScaleFactor=2,-sPDFPassword=01011977,-sDEVICE=jpeg,-sOutputFile=/home/jcol/Desktop/gs_offline/functions/output.jpeg,-c <</PageOffset[-64.2 40]>>setpagedevice,-f,/home/jcol/Desktop/gs_offline/functions/pan.pdf
Suceess
GPL Ghostscript 9.20 (2016-09-26)
Copyright (C) 2016 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 1.
Page 1
Loading NimbusSans-Regular font from %rom%Resource/Font/NimbusSans-Regular... 4244908 2808684 2600016 1250276 3 done. 

在没有示例文件(理想情况下是发送到 Ghostscript 的实际命令行)以及缺少反向通道输出(stout 和 stderr)的情况下,我唯一可以观察到的是 'option' 你指的(实际上是一段 PostScript 编程)使用 -c 开关引入 PostScript 输入,但不会用 -f 终止它。这意味着命令行上的任何内容都将被视为更多 PostScript,这可能会导致错误或 'hang',等待更多输入。

致未来读者

对于像我这样的新手

由于我是新手,所以在使用 NODEJS 中的库时有些困惑。

首先,我使用了一些 gs-wrapper 来自一些不在 NPM 上的教程,这是错误的原因,因为由于某些原因不支持命令。

经过一些研究,我从 npm 来到 Node-gs,我在 NPM 的页面上检查了 Node-GS API,它有专门的 command 选项。

现在我为什么要发布这个答案:

NODE-GS 库是唯一支持像 Firebsae Functions 这样的无服务器架构的库。但是这里的错误处理是最糟糕的,因为它只会直接从可执行文件中生成 Ghostscirpt。

例如,如果您为 PDF 提供了错误的密码,那么图书馆只会在错误消息中推送 Ghostscript Unrecoverable Error

(我知道如果你在无服务器功能中使用 Ghostscirpt,你足够聪明,可以在客户端检查密码是否正确,但只是举个例子)

在撰写本文时,我发现 Ghostscript4JS for NODE 使用 Ghostscript 的 C++ API 但不幸的是,该库不支持无服务器架构,因为该库依赖于系统安装了 Ghostscirpt,但开发人员表示它有一些计划。

检查库现在是否支持无服务器架构。

最后,作为新手我不得不经历的挣扎是找到 Ghostscirpt 的便携式版本与 NODE-GS 一起使用,你可以在这里找到它 Ghostscript Releases

对于 Firebase 函数用户 Firebase 函数是基于 Ubuntu Bionic 18.04 arch x64 构建的,因此您必须使用 x86_64 版本的 Ghostscript,撰写本文时最新版本是 Ghostscript 9.52

再见:)