Python Wand: MagickReadImage возвращает значение false, но не вызывает исключения ImageMagick
У меня есть некоторый давний код в базе кода Django, который считывается в формате PDF и использует Wand, чтобы сделать снимок экрана первой страницы PDF, который затем отображается на экране. веб-сайт. Недавно мы перенесли серверы (обновление с Ubuntu 22 LTS до 24 LTS), и что-то сломалось, и я ни за что на свете не смогу в этом разобраться.
Сначала немного потенциально полезной информации:
- ОПЕРАЦИОННАЯ система: Ubuntu 24 LTS
- Python 3.12.3
- Джанго 5.2.4
- Жезл 0.6.13
- Веб-сервер: nginx 1.24.0
- версия gunicorn: 23.0.0
- Мы не используем Docker. Это приложение Django запускается непосредственно на сервере в локальной виртуальной среде.
Код для преобразования PDF в PNG находится на стороне администратора веб-приложения. Вот в чем суть:
with Image(filename=pdf_location) as pdf:
with Image(pdf.sequence[0]) as first_page_pdf:
with first_page_pdf.convert('png') as first_page_png:
first_page_png.background_color = Color('white')
first_page_png.alpha_channel = 'remove'
return first_page_png.make_blob()
Когда я загружаю PDF-файл на сайт администратора для обработки, я получаю эту ошибку:
MagickReadImage returns false, but did not raise ImageMagick exception. This can occur when a delegate is missing, or returns EXIT_SUCCESS without generating a raster.
После долгих поисков я перепробовал все, что смог придумать, но ничего не работает:
- У меня установлен ghostscript:
$ gs --version
10.02.1
$ which gs
/usr/bin/gs
- Мой ImageMagick
policy.xml
содержит содержимое по умолчанию, найденное в файлеpolicy-debian.xml
, который входит в состав пакета ImageMagick, за заметным исключением того, что<policy domain="coder" rights="read|write" pattern="PDF" />
находится вpolicy.xml
. Я могу убедиться, что политика в отношении PDF настроена правильно:
$ identify -list policy
Path: /etc/ImageMagick-6/policy.xml
Policy: Resource
name: disk
value: 2GiB
Policy: Resource
name: map
value: 2048MiB
Policy: Resource
name: memory
value: 1024MiB
Policy: Resource
name: area
value: 256MP
Policy: Resource
name: height
value: 32KP
Policy: Resource
name: width
value: 32KP
Policy: Undefined
rights: None
Policy: Path
rights: None
pattern: @*
Policy: Delegate
rights: None
pattern: URL
Policy: Delegate
rights: None
pattern: HTTPS
Policy: Delegate
rights: None
pattern: HTTP
Policy: Coder
rights: Read Write
pattern: PDF
Path: [built-in]
Policy: Undefined
rights: None
- Преобразование одного и того же PDF-файла работает ли, когда я выполняю это вручную с помощью ghostscript и imagemagick (как предлагается здесь):
$ gs -sDEVICE=pngalpha -o page-%03d.png -r120 pdf-test.pdf
GPL Ghostscript 10.02.1 (2023-11-01)
Copyright (C) 2023 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
Processing pages 1 through 1.
Page 1
Loading font ArialMT (or substitute) from /usr/share/ghostscript/10.02.1/Resource/Font/NimbusSans-Regular
и
$ convert -density 120 pdf-test.pdf page-%03d.png
Оба правильно создают page-001.png
при использовании этого тестового PDF-файла.
- И, наконец, когда я выполняю это вручную в моей оболочке Django (т.е. в том же venv, который использует nginx), это также работает должным образом:
$ ./manage_dev.py shell
19 objects imported automatically (use -v 2 for details).
Python 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from wand.image import Image, Color
>>> with Image(filename='pdf-test.pdf') as pdf:
... with Image(pdf.sequence[0]) as first_page_pdf:
... with first_page_pdf.convert('png') as first_page_png:
... first_page_png.background_color = Color('white')
... first_page_png.alpha_channel = 'remove'
... blob = first_page_png.make_blob()
... with open('screenshot.png', 'wb') as png:
... png.write(blob)
...
24420
- Немного странно то, что когда я перечисляю делегатов ImageMagick,
gs
в списке нет. Это единственное, что я могу придумать, что может быть причиной проблемы, но я не могу понять, как включить это в список:
$ convert -list configure | grep DELEGATES
DELEGATES bzlib djvu fftw fontconfig freetype heic jbig jng jpeg lcms lqr lzma openexr openjp2 pango png ps raw tiff webp wmf x xml zlib zstd
DELEGATES bzlib djvu fftw fontconfig freetype heic jbig jng jp2 jpeg lcms lqr ltdl lzma openexr pangocairo png raw tiff webp wmf x xml zlib
Обратите внимание, что это не опечатка; здесь 2 строки DELEGATES
, и ни одна из них не содержит gs
.
Еще раз повторю, что этот код отлично работал в течение многих лет до переноса / обновления сервера, так что все это наводит меня на мысль, что это должен быть какой-то конфигурационный файл (ImageMagick, nginx?) где-то за пределами моего кода, но я просто не могу его определить. Я очень надеюсь, что у кого-нибудь из вас появятся какие-то идеи.
Заранее спасибо!
# ---- PERSONAL Preamble for testing the function DO NOT ADD TO function LEAVE THIS ALONE
import pymupdf, os, subprocess, tempfile, base64
gs = os.environ.get("GS_EXE") or r"C:\Users\WDAGUtilityAccount\Desktop\Apps\PDF\gs\10.05.1\bin\gs.exe"
def create_hello_world_pdf():
fd, pdf_location = tempfile.mkstemp(suffix=".pdf"); os.close(fd) # Close the file descriptor immediately
doc = pymupdf.open(); page = doc.new_page(); page.insert_text((200, 72), "HELLO WORLD", fontsize=20)
doc.save(pdf_location); doc.close()
return pdf_location
# ---- PERSONAL Preamble for testing the function DO NOT ADD TO function LEAVE THIS ALONE
# ---- Replacement Function that returns dotted call: first_page_png.make_blob()
# OP required white background, no alpha
gs_opts = "-dSAFER -sDEVICE=png16m -r120 -dFirstPage=1 -dLastPage=1 -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -o"
def first_page_png_make_blob(pdf_location):
with tempfile.TemporaryDirectory() as tmpdir:
png_path = os.path.join(tmpdir, "page.png")
cmd = [gs] + gs_opts.split() + [png_path, pdf_location]
subprocess.run(cmd, check=True)
class FirstPagePNG:
def make_blob(self):
with open(png_path, "rb") as f:
return f.read()
first_page_png = FirstPagePNG()
return first_page_png.make_blob()
# ---- TEST: Prove first_page_png.make_blob() returns valid PNG
pdf_location = create_hello_world_pdf()
blob = first_page_png_make_blob(pdf_location)
encoded = base64.b64encode(blob).decode("utf-8")
html = f"""
<html><body><h2>Proof: PNG from dotted call first_page_png.make_blob()</h2><img src="data:image/png;base64,{encoded}" /></body></html>
"""
with open("proof.html", "w", encoding="utf-8") as f:
f.write(html)
print("PNG proof written to proof.html — open it in your browser to verify.")