From e3bd7f5df173b4188d49424c1395b7bbb8e6c27c Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Sat, 13 Jun 2026 13:28:05 +0200 Subject: [PATCH] [mod] image results: add list of alternative formats (#6153) * [mod] template images.html: reformatted for readability (no func change) In preparation for upcoming changes, the template is being reformatted for better readability; no functional changes are being made. Signed-off-by: Markus Heiser * [mod] image results: add list of alternative formats To test alternatives formats apply patch from below, query ``!flaticon bmw`` and open the detail view for the image. diff --git a/searx/engines/flaticon.py b/searx/engines/flaticon.py index 06b6a8e25..d88388705 100644 --- a/searx/engines/flaticon.py +++ b/searx/engines/flaticon.py @@ -8,7 +8,7 @@ from urllib.parse import urlencode import typing as t -from searx.result_types import EngineResults +from searx.result_types import EngineResults, ImageRef if t.TYPE_CHECKING: from searx.extended_types import SXNG_Response @@ -61,6 +61,14 @@ def response(resp: "SXNG_Response"): thumbnail_src=_fix_url(result["png"]), img_src=_fix_url(result["png512"]), author=result["team_name"], + formats=[ + ImageRef(label="PNG 100x100", url="https://example.org/test.png", subtype="png"), + ImageRef(label="SVG", url="https://example.org/test.svg", subtype="svg+xml"), + ImageRef(url="https://example.org/test.jpg", subtype="jpeg"), + ImageRef(url="https://example.org/test.bmp", subtype="bmp"), + ImageRef(url="https://example.org/test.ico", subtype="x-icon"), + ImageRef(url="https://example.org/test.tif", subtype="tiff"), + ], ) ) Signed-off-by: Markus Heiser --------- Signed-off-by: Markus Heiser --- searx/engines/flaticon.py | 1 + searx/engines/startpage.py | 5 +- searx/result_types/__init__.py | 4 +- searx/result_types/image.py | 69 +++++++++++++- .../simple/result_templates/images.html | 95 +++++++++++++------ 5 files changed, 142 insertions(+), 32 deletions(-) diff --git a/searx/engines/flaticon.py b/searx/engines/flaticon.py index ab1bd654d..2b3309640 100644 --- a/searx/engines/flaticon.py +++ b/searx/engines/flaticon.py @@ -63,6 +63,7 @@ def response(resp: "SXNG_Response"): url=_fix_url(result["slug"]), thumbnail_src=_fix_url(result["png"]), img_src=_fix_url(result["png512"]), + img_format="PNG", author=result["team_name"], ) ) diff --git a/searx/engines/startpage.py b/searx/engines/startpage.py index ee9979b2c..f5a1a1381 100644 --- a/searx/engines/startpage.py +++ b/searx/engines/startpage.py @@ -382,6 +382,9 @@ def _get_image_result(result) -> dict[str, t.Any] | None: size_str = "".join(filter(str.isdigit, result["filesize"])) filesize = humanize_bytes(int(size_str)) + img_format = result.get("format").upper() + if img_format == "UNKNOWN": + img_format = "" return { "template": "images.html", "url": url, @@ -390,7 +393,7 @@ def _get_image_result(result) -> dict[str, t.Any] | None: "img_src": result.get("rawImageUrl"), "thumbnail_src": thumbnailUrl, "resolution": resolution, - "img_format": result.get("format"), + "img_format": img_format, "filesize": filesize, } diff --git a/searx/result_types/__init__.py b/searx/result_types/__init__.py index e49ace224..2ea989149 100644 --- a/searx/result_types/__init__.py +++ b/searx/result_types/__init__.py @@ -24,6 +24,8 @@ __all__ = [ "Code", "Paper", "File", + "Image", + "ImageRef", ] import typing as t @@ -35,7 +37,7 @@ from .keyvalue import KeyValue from .code import Code from .paper import Paper from .file import File -from .image import Image +from .image import Image, ImageRef class ResultList(list[Result | LegacyResult], abc.ABC): diff --git a/searx/result_types/image.py b/searx/result_types/image.py index 460264eb1..c98fa2c73 100644 --- a/searx/result_types/image.py +++ b/searx/result_types/image.py @@ -7,14 +7,51 @@ template. :members: :show-inheritance: +.. autoclass:: ImageRef + :members: + """ +# pylint: disable=too-few-public-methods +__all__ = ["Image", "ImageRef"] -__all__ = ["Image"] - +import types import typing as t +from collections.abc import Callable + +import msgspec + +from ._base import MainResult, Result, log, LegacyResult + +MimeSubType = t.Literal["png", "svg+xml", "jpeg", "bmp", "x-icon", "tiff"] + +MIMESUB: dict[MimeSubType, str] = { + "png": "PNG", + "svg+xml": "SVG", + "jpeg": "JPG", + "bmp": "BMP", + "x-icon": "ICO", + "tiff": "TIF", +} -from ._base import MainResult +class ImageRef(msgspec.Struct, kw_only=True): + """Reference to an (alternative) image format""" + + url: str + """URL of the image reference.""" + + subtype: MimeSubType + """Subtype (mimetype) of the image format.""" + + label: str = "" + """Label of the reference, default is build from the uppercase of + :py:obj:`Image.ImageRef.subtype`.""" + + mtype: t.Literal["image"] = "image" + + def __post_init__(self): + if not self.label: + self.label = MIMESUB.get(self.subtype, self.subtype.upper()) @t.final @@ -42,3 +79,29 @@ class Image(MainResult, kw_only=True): filesize: str = "" """Size of bytes in :py:obj:`human readable ` notation (e.g. ``1MB`` for ``1024*1024`` Bytes filesize).""" + + formats: list[ImageRef] = [] + """List of links to alternative image formats.""" + + def filter_urls(self, filter_func: "Callable[[Result | LegacyResult, str, str], str | bool ]"): + + for _ref in self.formats[:]: + _name = f"Image.formats:{_ref.label}" + try: + _url = filter_func(self, _name, _ref.url) + except Exception as exc: # pylint: disable=broad-exception-caught + # pylint: disable=no-member + _tb: types.TracebackType = exc.__traceback__.tb_next.tb_next # type: ignore + _fn = _tb.tb_frame.f_code.co_filename + _lno = _tb.tb_lineno + log.error("filter_urls: [%s] ignore %s from callback %s:%s", _name, repr(exc), _fn, _lno) + continue + + if isinstance(_url, str): + log.debug("filter_urls: [%s] URL %s -> %s", _name, _ref.url, _url) + _ref.url = _url + elif not _url: + log.debug("filter_urls: [%s] drop ref %s", _name, _ref) + self.formats.remove(_ref) + + return super().filter_urls(filter_func) diff --git a/searx/templates/simple/result_templates/images.html b/searx/templates/simple/result_templates/images.html index 12c2e8194..31ee663c2 100644 --- a/searx/templates/simple/result_templates/images.html +++ b/searx/templates/simple/result_templates/images.html @@ -1,28 +1,69 @@ -
{{- "" -}} - {{- "" -}} - {{ result.title|striptags }}{{- "" -}} - {%- if result.resolution %} {{ result.resolution }} {%- endif -%} - {{ result.title|striptags }}{{- "" -}} - {{- result.parsed_url.netloc -}}{{- "" -}} - {{- "" -}} -
{{- "" -}} - {{ icon('close') }}{{- "" -}} - {{ icon('navigate-left') }}{{- "" -}} - {{ icon('navigate-right') }}{{- "" -}} - - {{ result.title|striptags }}{{- "" -}} - {{- "" -}} -
{{- "" -}} -

{{ result.title|striptags }}

{{- "" -}} -

{%- if result.content %}{{ result.content|striptags }}{% else %} {% endif -%}

{{- "" -}} -
{{- "" -}} -

{%- if result.author %}{{ _('Author') }}:{{ result.author|striptags }}{% else %} {% endif -%}

{{- "" -}} -

{%- if result.resolution %}{{ _('Resolution') }}:{{ result.resolution }}{% else %} {% endif -%}

{{- "" -}} -

{%- if result.img_format %}{{ _('Format') }}:{{ result.img_format }}{% else %} {% endif -%}

{{- "" -}} -

{%- if result.filesize %}{{ _('Filesize') }}:{{ result.filesize}}{% else %} {% endif -%}

{{- "" -}} -

{%- if result.source %}{{ _('Source') }}:{{ result.source }}{% else %} {% endif -%}

{{- "" -}} -

{{ _('Engine') }}:{{ result.engine }}

{{- "" -}}{{- "" -}} -

{{ _('View source') }}:{{ result.url }}

{{- "" -}} -
{{- "" -}} -
{{- "" -}} +{% macro _target(url, new_tab=False) -%} + {%- if new_tab %} target="_blank" rel="noopener noreferrer" + {%- else %} rel="noreferrer" + {%- endif %} +{%- endmacro %} + +{% macro _label(label, value) -%} + {%- if value -%}{{ label }}:{{ value }} + {%- else %}   + {%- endif -%} +{%- endmacro %} + +
+ + {{ result.title | striptags }} + {%- if result.resolution %} + {{ result.resolution }} + {%- endif -%} + {{ result.title | striptags }} + {{- result.parsed_url.netloc -}} + +
+ {{ icon("close") }} + {{ icon("navigate-left") }} + {{ icon("navigate-right") }} + + {{ result.title | striptags }} + +
+

{{ result.title | striptags }}

+

+ {%- if result.content %} {{ result.content | striptags }} + {%- else %}   + {%- endif -%} +

+
+

{{ _label(_("Author"), result.author) }}

+

{{ _label(_("Resolution"), result.resolution) }}

+

+ {{ _("Image formats") }}: + {{- "" -}}{{- result.img_format or _("original format") -}} + {%- for ref in result.formats -%} +  | {{ ref.label }} + {%- endfor %} +

+

{{ _label(_("Filesize"), result.filesize) }}

+

{{ _label(_("Source"), result.source) }}

+

{{ _label(_("Engine"), result.engine) }}

+

{{ _("View source") }}:{{- "" -}} + {{ result.url }} +

+
+