[mod] typification of SearXNG: add new result type Video (#6743)

- Python class:   searx/result_types/video.py
- Jinja template: searx/templates/simple/result_templates/videos.html
- CSS (less)      client/simple/src/less/result_types/video.less

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
This commit is contained in:
Markus Heiser
2026-09-20 16:12:32 +02:00
committed by Markus Heiser
parent fdd8525b1a
commit e40de2d992
9 changed files with 87 additions and 63 deletions

View File

@@ -0,0 +1,25 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
/*
Layout of the Video result class
*/
.result-videos {
a.thumbnail_link {
.ltr-float-left();
.ltr-margin-right(1rem);
.ltr-margin-left(0);
width: 10rem !important;
aspect-ratio: 16 / 9 !important;
}
.content {
overflow: hidden;
}
.embedded-video iframe {
width: 100%;
aspect-ratio: 16 / 9;
padding: 10px 0 0 0;
}
}

View File

@@ -431,34 +431,6 @@ article[data-vim-selected].category-social {
padding: 10px 0 0 0; padding: 10px 0 0 0;
} }
.result-videos {
a.thumbnail_link {
.ltr-float-left();
.ltr-margin-right(1rem);
.ltr-margin-left(0);
width: 10rem !important;
aspect-ratio: 16 / 9 !important;
}
.content {
overflow: hidden;
}
.embedded-video iframe {
width: 100%;
aspect-ratio: 16 / 9;
padding: 10px 0 0 0;
}
}
@supports not (aspect-ratio: 1 / 1) {
// support older browsers which do not have aspect-ratio
// https://caniuse.com/?search=aspect-ratio
.result-videos .embedded-video iframe {
height: calc(@results-width * 9 / 16);
}
}
.engines { .engines {
.ltr-float-right(); .ltr-float-right();
clear: both; clear: both;

View File

@@ -0,0 +1,7 @@
.. _result_types.video:
=============
Video Results
=============
.. automodule:: searx.result_types.video

View File

@@ -19,13 +19,13 @@ following types have been implemented so far ..
main/paper main/paper
main/file main/file
main/image main/image
main/video
The :ref:`LegacyResult <LegacyResult>` is used internally for the results that The :ref:`LegacyResult <LegacyResult>` is used internally for the results that
have not yet been typed. The templates can be used as orientation until the have not yet been typed. The templates can be used as orientation until the
final typing is complete. final typing is complete.
- :ref:`template default` / :py:obj:`Result` - :ref:`template default` / :py:obj:`Result`
- :ref:`template videos`
- :ref:`template torrent` - :ref:`template torrent`
- :ref:`template map` - :ref:`template map`
- :ref:`template packages` - :ref:`template packages`

View File

@@ -129,29 +129,6 @@ audio_src : uri,
URL of an embedded ``<audio controls>``. URL of an embedded ``<audio controls>``.
.. _template videos:
``videos.html``
---------------
Displays result fields from:
- :ref:`macro result_header` and
- :ref:`macro result_sub_header`
Additional fields used in the :origin:`videos.html
<searx/templates/simple/result_templates/videos.html>`:
iframe_src : :py:class:`str`
URL of an embedded ``<iframe>`` / the frame is collapsible.
The videos are displayed as small thumbnails in the main results list, there
is an additional button to collaps/open the embeded video.
content : :py:class:`str`
Description of the code fragment.
.. _template torrent: .. _template torrent:
``torrent.html`` ``torrent.html``

View File

@@ -28,6 +28,7 @@ __all__ = [
"File", "File",
"Image", "Image",
"ImageRef", "ImageRef",
"Video",
] ]
import typing as t import typing as t
@@ -40,6 +41,7 @@ from .code import Code
from .paper import Paper from .paper import Paper
from .file import File from .file import File
from .image import Image, ImageRef from .image import Image, ImageRef
from .video import Video
class ResultList(list[Result | LegacyResult], abc.ABC): class ResultList(list[Result | LegacyResult], abc.ABC):
@@ -56,6 +58,7 @@ class ResultList(list[Result | LegacyResult], abc.ABC):
Paper = Paper Paper = Paper
File = File File = File
Image = Image Image = Image
Video = Video
MainResult = MainResult MainResult = MainResult
Result = Result Result = Result
Translations = Translations Translations = Translations

View File

@@ -31,7 +31,6 @@ from collections.abc import Callable
import msgspec import msgspec
from searx import logger from searx import logger
from searx.utils import get_embedded_stream_url
log = logger.getChild("result_types") log = logger.getChild("result_types")
@@ -386,7 +385,11 @@ class MainResult(Result): # pylint: disable=missing-class-docstring
"""URL of a image that is displayed in the result item.""" """URL of a image that is displayed in the result item."""
iframe_src: str = "" iframe_src: str = ""
"""URL of an embedded ``<iframe>`` / the frame is collapsible.""" """URL of an embedded ``<iframe>`` / the frame is collapsible.
To convert a standard video URL from a widely know video services into its
embed format, have a look at :obj:`searx.utils.get_embedded_stream_url`.
"""
audio_src: str = "" audio_src: str = ""
"""URL of an embedded ``<audio controls>``.""" """URL of an embedded ``<audio controls>``."""
@@ -542,10 +545,6 @@ class LegacyResult(dict[str, t.Any]):
DeprecationWarning, DeprecationWarning,
) )
# TODO: move into typed video results class once it is implemented # pylint: disable=fixme
if self.template == "videos.html" and self.url and not self.iframe_src:
self.iframe_src = get_embedded_stream_url(self.url)
def __getattr__(self, name: str, default: t.Any = UNSET) -> t.Any: def __getattr__(self, name: str, default: t.Any = UNSET) -> t.Any:
if default == UNSET and name not in self: if default == UNSET and name not in self:
raise AttributeError(f"LegacyResult object has no field named: {name}") raise AttributeError(f"LegacyResult object has no field named: {name}")

View File

@@ -0,0 +1,30 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Typification of the *video* results. Results of this type are rendered in
the :origin:`videos.html <searx/templates/simple/result_templates/videos.html>`
template.
.. autoclass:: Video
:members:
:show-inheritance:
"""
# pylint: disable=too-few-public-methods
__all__ = ["Video"]
import typing as t
from searx.utils import get_embedded_stream_url
from ._base import MainResult
@t.final
class Video(MainResult, kw_only=True):
"""Result type suitable for displaying videos."""
template: str = "videos.html"
def __post_init__(self):
super().__post_init__()
if self.url and not self.iframe_src:
self.iframe_src = get_embedded_stream_url(self.url)

View File

@@ -593,13 +593,24 @@ def eval_xpath_getindex(
return default return default
def get_embedded_stream_url(url: str): def get_embedded_stream_url(url: str) -> str:
""" """Converts a standard video URL into its embed format.
Converts a standard video URL into its embed format. Supported services include Youtube,
Facebook, Instagram, TikTok, Dailymotion, and Bilibili. Supported services include:
- Youtube
- Facebook
- Instagram
- TikTok
- Dailymotion
- Bilibili
The function is suitable for the field :obj:`MainResult.iframe_src
<searx.result_types.MainResult.iframe_src>`. If ``url`` is not from one of
the supported services, an empty string is returned.
""" """
parsed_url = urlparse(url) parsed_url = urlparse(url)
iframe_src = None iframe_src = ""
# YouTube # YouTube
if parsed_url.netloc in ['www.youtube.com', 'youtube.com'] and parsed_url.path == '/watch' and parsed_url.query: if parsed_url.netloc in ['www.youtube.com', 'youtube.com'] and parsed_url.path == '/watch' and parsed_url.query: