misc: add |convert_image_format filter tag (#86003)
gitea/wcs/pipeline/head This commit looks good Details

This commit is contained in:
Frédéric Péters 2024-02-08 14:52:08 +01:00
parent 16d1e680d0
commit 445dac2e9b
3 changed files with 137 additions and 1 deletions

3
debian/control vendored
View File

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

View File

@ -2,6 +2,8 @@ import datetime
import html import html
import os import os
import string import string
import subprocess
from unittest import mock
import pytest import pytest
from django.test import override_settings from django.test import override_settings
@ -1696,3 +1698,83 @@ def test_details_format(pub):
pub.loggederror_class.wipe() pub.loggederror_class.wipe()
assert tmpl.render(context) == 'String:\n foo' assert tmpl.render(context) == 'String:\n foo'
assert pub.loggederror_class.count() == 0 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 os
import random import random
import string import string
import subprocess
import urllib.parse import urllib.parse
from decimal import Decimal from decimal import Decimal
from decimal import DivisionByZero as DecimalDivisionByZero from decimal import DivisionByZero as DecimalDivisionByZero
@ -1086,6 +1087,58 @@ def rename_file(value, new_name):
return file_object 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 @register.filter
def first(value): def first(value):
try: try: