2025, Oct 20 16:32
Ubuntu 24 LTS अपग्रेड के बाद Django में PDF प्रीव्यू फेल: Ghostscript delegate समस्या का समाधान
Ubuntu 24 LTS पर gunicorn/nginx में Django का PDF→PNG प्रीव्यू फेल हो रहा है? Wand, ImageMagick, Ghostscript delegate missing त्रुटि के कारण और समाधान जानें.
Ubuntu 22 LTS से 24 LTS पर सर्वर माइग्रेशन के बाद, प्रोडक्शन में PDF की पहली पेज का PNG पूर्वावलोकन रेंडर करने वाली पुरानी Django सुविधा टूटने लगी। वही कन्वर्ज़न कमांड लाइन पर, और उसी वर्चुअल एन्वायरनमेंट में Django शेल के अंदर भी, ठीक चलता था—लेकिन gunicorn और nginx के पीछे एडमिन इंटरफ़ेस से ट्रिगर होने पर फेल हो जाता था। Wand और ImageMagick से मिलने वाली रनटाइम त्रुटि वही क्लासिक delegate failure थी।
समस्या का संदर्भ
एप्लिकेशन एक PDF पढ़ता है, सिर्फ पहली पेज को रैस्टराइज़ करता है और एक PNG ब्लॉब लौटाता है। कोर लॉजिक सीधा है और सालों से भरोसेमंद रहा है। अब एन्वायरनमेंट Ubuntu 24 LTS, Python 3.12.3, Django 5.2.4, Wand 0.6.13, nginx 1.24.0 और gunicorn 23.0.0 का उपयोग करता है; Docker शामिल नहीं है। अपलोड पर प्रोसेस ImageMagick की read त्रुटि के साथ फेल होता है, जो missing delegate की शिकायत करती है।
MagickReadImage false लौटाता है, लेकिन ImageMagick अपवाद नहीं उठाता। ऐसा तब हो सकता है जब कोई delegate गायब हो, या वह EXIT_SUCCESS लौटाए लेकिन कोई रैस्टर न बनाए।
विफलता दिखाने वाला न्यूनतम कोड
प्रीव्यू जनरेशन का मूल Wand और ImageMagick का उपयोग करता है ताकि PDF का पहला पेज निकाला जाए और PNG ब्लॉब बनाया जाए। लॉजिक सरल है और अपरिवर्तित है; यहाँ सिर्फ स्थानीय नाम अलग हैं:
from wand.image import Image, Color
def pdf_preview_bytes(src_path):
    with Image(filename=src_path) as doc_img:
        with Image(doc_img.sequence[0]) as page_zero:
            with page_zero.convert('png') as png_page:
                png_page.background_color = Color('white')
                png_page.alpha_channel = 'remove'
                return png_page.make_blob()
असल में क्या गलत हो रहा है
Wand, ImageMagick का बाइंडिंग है। ImageMagick, PDF रैस्टराइज़ेशन के लिए Ghostscript को delegate करता है। अपग्रेडेड सिस्टम पर, सीधे कन्वर्ज़न चलाना काम करता है। Ghostscript PDF को PNG में ठीक से बदल देता है। ImageMagick का convert भी सफल होता है। वही Wand स्निपेट प्रोजेक्ट के venv में Django शेल के भीतर भी पास हो जाता है। लेकिन gunicorn के पीछे एडमिन से ट्रिगर किया गया कोड delegate त्रुटि के साथ फेल होता है। मुख्य सुराग यह है कि उस रनटाइम में ImageMagick की configuration आउटपुट में Ghostscript एक delegate के रूप में दिखाई नहीं देता, और त्रुटि संदेश साफ़-साफ़ missing delegate का ज़िक्र करता है। जड़ कारण यह था कि gunicorn एक सीमित PATH के साथ शुरू किया गया था जिसमें सिर्फ वर्चुअल एन्वायरनमेंट की bin डायरेक्टरी शामिल थी, और /usr/bin नहीं था, जहाँ gs रहता है। नतीजतन, वेब वर्कर प्रोसेस में Wand के ज़रिए बुलाए जाने पर ImageMagick रनटाइम पर Ghostscript ढूंढ नहीं पाया।
PDF-संबंधित policy पहले से ही पर्याप्त उदार थी, और सीधे CLI कन्वर्ज़न ने साबित किया कि टूलचेन इंस्टॉल और काम कर रहा है। असंगति महज़ इंटरैक्टिव शेल और सर्विस यूनिट के एन्वायरनमेंट के बीच थी।
समाधान 1: gunicorn सेवा में Ghostscript को खोजने योग्य बनाएं
समाधान यह है कि gunicorn प्रोसेस ऐसा PATH विरासत में ले जो सिस्टम की Ghostscript बाइनरी डायरेक्टरी को शामिल करे। यदि आप gunicorn को systemd से मैनेज करते हैं, तो सेवा कॉन्फ़िगरेशन में PATH में /usr/bin जोड़ें (virtualenv की bin को पहले रखते हुए), फिर सेवा रीस्टार्ट करें। इस बदलाव के बाद, मौजूदा Wand कोड पहले की तरह चलता है।
[Service]
Environment="PATH=/path/to/venv/bin:/usr/bin"
एक बार gunicorn इस एन्वायरनमेंट के साथ रीस्टार्ट हो जाए, ImageMagick को Ghostscript मिल जाता है, delegate चेन अक्षुण्ण रहती है, और ऊपर वाला Wand कोड बिना किसी बदलाव के अपेक्षित PNG बाइट्स लौटा देता है।
समाधान 2: Ghostscript को सीधे कॉल करके delegates को बायपास करें
एक वैकल्पिक तरीका जो delegate discovery की दिक्कतों से बचाता है, वह है Ghostscript को सीधे चलाना और PNG बाइट्स लौटाना। यह मूल व्यवहार को प्रतिबिंबित करता है: सफेद बैकग्राउंड और बिना अल्फा वाला पहले पेज का PNG बनता है, और जहाँ पहले ब्लॉब लौटाया जा रहा था वहीं फिट हो जाता है।
import os
import subprocess
import tempfile
# Ghostscript विकल्प मूल व्यवहार का अनुकरण करते हैं
_GS_FLAGS = "-dSAFER -sDEVICE=png16m -r120 -dFirstPage=1 -dLastPage=1 -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -o"
def pdf_cover_blob(pdf_path, gs_exec="gs"):
    with tempfile.TemporaryDirectory() as tmp_dir:
        out_png = os.path.join(tmp_dir, "page.png")
        cmd = [gs_exec] + _GS_FLAGS.split() + [out_png, pdf_path]
        subprocess.run(cmd, check=True)
        with open(out_png, "rb") as fh:
            return fh.read()
यह रिटर्न टाइप को png_page.make_blob() जैसा ही रखता है, और तब उपयोगी फ़ॉलबैक बनता है जब आप रनटाइम पर ImageMagick की delegate कॉन्फ़िगरेशन पर किसी भी निर्भरता से बचना चाहें।
यह क्यों मायने रखता है
systemd जैसे प्रोसेस मैनेजर से शुरू की गई सेवाओं का एन्वायरनमेंट अक्सर आपके शेल से ज्यादा कसा हुआ या अलग होता है। ट्रबलशूटिंग के समय यह अंतर नज़रअंदाज़ करना आसान है, खासकर तब जब OS अपग्रेड पैकेज पाथ या delegates बदल दे। Wand में PDF रेंडरिंग केवल Python पैकेज पर नहीं, बल्कि ImageMagick की Ghostscript ढूँढने की क्षमता पर भी निर्भर करती है। अगर वर्कर प्रोसेस का PATH कटा-छँटा है, तो आपको "delegate missing" जैसे अस्पष्ट एरर मिलेंगे, भले ही सारे कंपोनेंट्स इंस्टॉल हों और कमांड लाइन पर बेहतरीन काम कर रहे हों।
व्यावहारिक निष्कर्ष
यदि Wand + ImageMagick के साथ PDF कन्वर्ज़न सिर्फ प्रोडक्शन में gunicorn और nginx के पीछे फेल होता है, पर आपके शेल और Django शेल में सफल है, तो सबसे पहले रनटाइम एन्वायरनमेंट जाँचें। सुनिश्चित करें कि gunicorn सेवा के PATH में /usr/bin शामिल है ताकि Ghostscript रिज़ॉल्व हो सके। यदि आप इस निर्भरता को पूरी तरह delegate कॉन्फ़िगरेशन से अलग रखना चाहते हैं, तो subprocess के जरिये सीधा Ghostscript कॉल करके PNG बाइट्स लौटाना एक मजबूत विकल्प है। दोनों तरीकों से आपका मूल व्यवहार बहाल हो जाता है—बिना एप्लिकेशन की लॉजिक या आउटपुट बदले।