繁体   English   中英

PHP 将 PDF 转换为 Image 时,Imagick 会挂起整个服务器

[英]PHP Imagick hangs the whole server when converting PDF to Image

我在 PHP 上使用扩展名 Imagick 将 PDF 文件转换为图像,具体来说是 PNG 文件。 当转换正在进行时,整个服务器总是挂起。 我所做的是上传一堆 PDF 文件,遍历它及其页面以将其转换为图像。

这是代码。

foreach ($uploaded_file as $key => $value) {
  $upload_file_path = $upload_path.'/'.$value->name;
  $imagick = new Imagick();
  $imagick->setResourceLimit(6, 1);
  $imagick->setResolution(300, 300);
  $imagick->readImage($upload_file_path);
  $pages = $imagick->getNumberImages();

  for ($x=0; $x<$pages; $x++) {
    $imagick->readImage($upload_file_path.'['.$x.']');
    $imagick->setImageFormat('png');
    $imagick->writeImage($image_path.'/page-'.$x.'.png');
  }

  $imagick->clear();
  $imagick->destroy();
}

转换正在进行时,我无法访问服务器上的其他站点,并且转换时间过长。 请帮忙。 谢谢!

根据 PDF 的内容,转换可能确实非常繁重。 您对此无能为力。

您已经尝试将线程总数降低到 2,但仍然需要考虑 memory、I/O 延迟和 CPU 资源——而且您不知道服务器是否可以使用两个以上的线程。

可以做的是尝试使用niceionice降低正在运行的进程的 I/O 和 CPU 优先级,如果它们可用的话 您需要有一个合适的平台(Linux、BSD 或类似平台),并且可以使用niceionice工具; 并且您需要一个专用的可执行 PHP 进程(那么可能不是 FastCGI)并能够查询其 PID。

在 Linux 系统上,您可能会尝试将转换外包给 ImageMagick 二进制文件,并将其包装在niceionice中,使其 go 变得容易--缓慢。

另一种可能性是看看您是否可以使用ghostscript代替 Imagick。

php-vips将 pdf 转换为 png 更快,您可以尝试一下。

我给你做了一个示例程序:

#!/usr/bin/env php
<?php

require __DIR__ . '/vendor/autoload.php';

use Jcupitt\Vips;

for ($i = 1; $i < count($argv); $i++) {
  $image = Vips\Image::newFromFile($argv[$i]);
  $n_pages = $image->get("n-pages");
  echo($argv[$i] . " has " . $n_pages . " pages\n");

  for ($n = 0; $n < $n_pages; $n++) {
    echo("  rendering page " . $n . " ...\n");
    $page = Vips\Image::newFromFile($argv[$i], [
      "dpi" => 300,
      "page" => $n,
      # this enables image streaming
      "access" => "sequential"
    ]);
    $page->writeToFile($argv[$i] . "_page_" . $n . ".png");
  }
}

我可以这样运行它:

$ /usr/bin/time -f %M:%e ../convert-vips.php nipguide.pdf 
nipguide.pdf has 58 pages
  rendering page 0 ...
...
  rendering page 57 ...
107808:31.72

所以它在 32 秒内制作了 58 个 png,并且最多需要 110mb 的内存。 它不会创建任何临时文件——110mb 涵盖了所有内容。

png 是一种非常慢的文件格式。 如果你另存为 jpg,一切都需要大约 6 秒。

我尝试了您的 imagick 代码的一个版本:

#!/usr/bin/env php
<?php

for ($i = 1; $i < count($argv); $i++) {
  $imagick = new Imagick();
  $imagick->setResourceLimit(6, 1);
  $imagick->setResolution(300, 300);
  $imagick->readImage($argv[$i]);
  $pages = $imagick->getNumberImages();
  echo($argv[$i] . " has " . $pages . " pages\n");

  for ($x = 0; $x < $pages; $x++) {
    echo("  rendering page " . $x . " ...\n");
    $imagick->readImage($argv[$i] . "[" . $x . "]");
    $imagick->setImageFormat("png");
    $imagick->writeImage($argv[$i] . "_page_" . $x . ".png");
  }

  $imagick->clear();
  $imagick->destroy();
} 

运行我看到的:

$ /usr/bin/time -f %M:%e ../convert-imagick.php nipguide.pdf 
nipguide.pdf has 58 pages
  rendering page 0 ...
...
  rendering page 57 ...
255640:223.26

所以 memory 的 220 秒(几乎慢 7 倍)和 260 mb。 使用 memory 并不是全部——在 300 DPI 下,imagick 将为每个页面在 /tmp 中创建一个 65mb 的文件,因此总共需要大约 5gb 的存储空间。

我想分享我的发现和我认为可以有见地的针对我的情况的解决方案。

我注意到每页创建一个新的imagick实例比尝试读取整个文件然后让它 1)读取页面数量和 2)让它在所有页面上迭代要快得多。

我只想要 PDF 文件的前 10 页。 当我有一个 50 页的 PDF 文件(大多数只有文本,只有 450KB)时,你阅读这个文件的方式真的很重要。

这样,它只会从第一页开始阅读,并尝试阅读到 10 页。 如果页面少于 10 页,imagick 一旦抛出错误,就会意识到它已经到达页面末尾并停止迭代。

<?php
$reachedLastPage = false;
for ($i = 0; $i <= 10 && empty($reachedLastPage); $i++) {
    $im = new imagick();
    $im->setResolution(300,300);
    try {
        $im->readimage($tempFile.'['.$i.']');
        if ($im->valid()) {
            $im->setImageBackgroundColor('white');
            $im->setImageAlphaChannel(Imagick::VIRTUALPIXELMETHOD_WHITE);
            $im->setImageCompression(imagick::COMPRESSION_JPEG);
            $im->setImageCompressionQuality(60);
            $im->setImageFormat('jpeg');

            $extraFile = microtime(true).'__pdfpage'.".".strtolower('jpg');
            $im->writeImage(rtrim($targetPath) . $extraFile);

            if (is_file(rtrim($targetPath) . $extraFile)) {
                $imageArray[] = $extraFile;
            }
        }
    }
    catch(ImagickException $e) {
        $reachedLastPage = true;
    }
    $im->clear();
    $im->destroy();
}
?>

为了更深入地了解我的其他尝试。

  1. 我首先尝试阅读整个 pdf 文件,然后使用getNumberImages()setIteratorIndex()设置循环并读取各个页面。 让 imagick 阅读整个内容将需要很长时间,甚至还没有开始处理所有页面。

  2. 也可以使用 imagick 的轻量级实例来查看有多少页面,这将是一个比让 imagick 尝试读取不存在的页面更简洁的解决方案:

$im->pingImage($tempFile);
$nrOfPages = $im->getNumberImages();

然而,这在我的测试中已经花费了 10 秒,只是为了读取页数。 这就是我最终采用上述方法的原因。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM