add |convert_image_format filter tag (#86003) #1111

Merged
fpeters merged 1 commits from wip/86003-convert-image-format into main 2024-03-01 09:29:51 +01:00
3 changed files with 137 additions and 1 deletions

3
debian/control vendored
View File

@ -43,7 +43,8 @@ Depends: graphviz,
uwsgi-plugin-python3,
${misc:Depends},
${python3:Depends},
Recommends: libreoffice-writer-nogui | libreoffice-writer,
Recommends: graphicsmagick,
libreoffice-writer-nogui | libreoffice-writer,

Sur une image de test (forcément égarée depuis) Pillow ne donnait vraiment pas un bon résultat pour produire du PDF, je passe donc par graphicsmagick.

Sur une image de test (forcément égarée depuis) Pillow ne donnait vraiment pas un bon résultat pour produire du PDF, je passe donc par graphicsmagick.
poppler-utils,
python3-docutils,
python3-langdetect,

View File

@ -2,6 +2,8 @@ import datetime
import html
import os
import string
import subprocess
from unittest import mock
import pytest
from django.test import override_settings
@ -1696,3 +1698,83 @@ def test_details_format(pub):
pub.loggederror_class.wipe()
assert tmpl.render(context) == 'String:\n foo'
assert pub.loggederror_class.count() == 0
@pytest.mark.parametrize('image_format', ['jpeg', 'png', 'pdf'])
def test_convert_image_format(pub, image_format):
with pub.complex_data():
img = Template('{{ url|qrcode|convert_image_format:"%s" }}' % image_format).render(
{'url': 'http://example.com/', 'allow_complex': True}
)
assert pub.has_cached_complex_data(img)
value = pub.get_cached_complex_data(img)
assert value.orig_filename == 'qrcode.%s' % image_format
assert value.content_type == {'jpeg': 'image/jpeg', 'png': 'image/png', 'pdf': 'application/pdf'}.get(
image_format
)
with value.get_file_pointer() as fp:
if image_format in ('jpeg', 'png'):
img = PIL.Image.open(fp)
assert img.format == image_format.upper()
assert img.size == (330, 330)
assert (
zbar_decode_qrcode(img, symbols=[ZBarSymbol.QRCODE])[0].data.decode()
== 'http://example.com/'
)
else:
assert b'%PDF-' in fp.read()[:200]
def test_convert_image_format_no_name(pub):
with pub.complex_data():
img = Template('{{ url|qrcode|rename_file:""|convert_image_format:"jpeg" }}').render(
{'url': 'http://example.com/', 'allow_complex': True}
)
assert pub.has_cached_complex_data(img)
value = pub.get_cached_complex_data(img)
assert value.orig_filename == 'file.jpeg'
def test_convert_image_format_errors(pub):
pub.loggederror_class.wipe()
with pub.complex_data():
img = Template('{{ "xxx"|convert_image_format:"gif" }}').render({'allow_complex': True})
assert pub.has_cached_complex_data(img)
assert pub.get_cached_complex_data(img) is None
assert pub.loggederror_class.count() == 1
assert (
pub.loggederror_class.select()[0].summary
== '|convert_image_format: unknown format (must be one of jpeg, pdf, png)'
)
pub.loggederror_class.wipe()
with pub.complex_data():
img = Template('{{ "xxx"|convert_image_format:"jpeg" }}').render({'allow_complex': True})
assert pub.has_cached_complex_data(img)
assert pub.get_cached_complex_data(img) is None
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == '|convert_image_format: missing input'
pub.loggederror_class.wipe()
with mock.patch('subprocess.run', side_effect=FileNotFoundError()):
with pub.complex_data():
img = Template('{{ url|qrcode|convert_image_format:"jpeg" }}').render(
{'url': 'http://example.com/', 'allow_complex': True}
)
assert pub.has_cached_complex_data(img)
assert pub.get_cached_complex_data(img) is None
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == '|convert_image_format: not supported'
pub.loggederror_class.wipe()
with mock.patch(
'subprocess.run', side_effect=subprocess.CalledProcessError(returncode=-1, cmd='xx', stderr=b'xxx')
):
with pub.complex_data():
img = Template('{{ url|qrcode|convert_image_format:"jpeg" }}').render(
{'url': 'http://example.com/', 'allow_complex': True}
)
assert pub.has_cached_complex_data(img)
assert pub.get_cached_complex_data(img) is None
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == '|convert_image_format: conversion error (xxx)'

View File

@ -24,6 +24,7 @@ import math
import os
import random
import string
import subprocess
import urllib.parse
from decimal import Decimal
from decimal import DivisionByZero as DecimalDivisionByZero
@ -1086,6 +1087,58 @@ def rename_file(value, new_name):
return file_object
@register.filter
def convert_image_format(value, new_format):
from wcs.fields import FileField
formats = {
'jpeg': 'image/jpeg',
'pdf': 'application/pdf',
'png': 'image/png',
}
if new_format not in formats:
get_publisher().record_error(
_('|convert_image_format: unknown format (must be one of %s)') % ', '.join(formats.keys())
)
return None
try:
file_object = FileField.convert_value_from_anything(value)
except ValueError:
file_object = None
if not file_object:
get_publisher().record_error(_('|convert_image_format: missing input'))
return None
if file_object.base_filename:
current_name, current_format = os.path.splitext(file_object.base_filename)
if current_format == f'.{new_format}':
return file_object
new_name = f'{current_name}.{new_format}'
else:
new_name = '%s.%s' % (_('file'), new_format)
try:
proc = subprocess.run(
['gm', 'convert', '-', f'{new_format}:-'],
input=file_object.get_content(),
capture_output=True,
check=True,
)
except FileNotFoundError:
get_publisher().record_error(_('|convert_image_format: not supported'))
return None
except subprocess.CalledProcessError as e:
get_publisher().record_error(_('|convert_image_format: conversion error (%s)' % e.stderr.decode()))
return None
new_file_object = FileField.convert_value_from_anything(
{'content': proc.stdout, 'filename': new_name, 'content_type': formats[new_format]}
)
return new_file_object
@register.filter
def first(value):
try: