[mod] migration of some engine results to the video class (#6743)

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
This commit is contained in:
Markus Heiser
2026-09-20 16:20:15 +02:00
committed by Markus Heiser
parent ce48993997
commit 2e41cf7407
22 changed files with 93 additions and 132 deletions

View File

@@ -52,7 +52,7 @@ def response(resp) -> EngineResults:
published_date = None published_date = None
res.add( res.add(
res.types.LegacyResult( res.types.Video(
url=entry["play_url"], url=entry["play_url"],
title=html_to_text(entry["title"]), title=html_to_text(entry["title"]),
content=html_to_text(entry["description"]), content=html_to_text(entry["description"]),

View File

@@ -127,7 +127,7 @@ from dateutil import parser
from searx import locales, logger from searx import locales, logger
from searx.enginelib.traits import EngineTraits from searx.enginelib.traits import EngineTraits
from searx.exceptions import SearxEngineResponseException from searx.exceptions import SearxEngineResponseException
from searx.result_types import EngineResults, MainResult from searx.result_types import EngineResults, MainResult, Video
from searx.result_types.image import Image from searx.result_types.image import Image
from searx.utils import html_to_text, js_obj_str_to_json_str, js_obj_str_to_python from searx.utils import html_to_text, js_obj_str_to_json_str, js_obj_str_to_python
@@ -331,8 +331,7 @@ def _parse_video_result(result: dict[str, t.Any]) -> MainResult:
video: dict[str, t.Any] = result.get("video", {}) video: dict[str, t.Any] = result.get("video", {})
thumbnail: dict[str, t.Any] = result.get("thumbnail", {}) thumbnail: dict[str, t.Any] = result.get("thumbnail", {})
return MainResult( return Video(
template="videos.html",
title=result.get("title", ""), title=result.get("title", ""),
url=result.get("url"), url=result.get("url"),
content=result.get("description", ""), content=result.get("description", ""),

View File

@@ -5,13 +5,13 @@
""" """
import typing as t import typing as t
from datetime import datetime, timezone from datetime import datetime, timezone, timedelta
import html import html
from searx.enginelib import EngineCache from searx.enginelib import EngineCache
from searx.exceptions import SearxEngineAPIException from searx.exceptions import SearxEngineAPIException
from searx.network import post from searx.network import post
from searx.utils import format_duration, html_to_text, humanize_number from searx.utils import html_to_text, humanize_number
from searx.result_types import EngineResults from searx.result_types import EngineResults
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
@@ -98,14 +98,13 @@ def response(resp: "SXNG_Response"):
) )
elif dogpile_categ == "videos": elif dogpile_categ == "videos":
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html",
url=result["clickUrl"], url=result["clickUrl"],
title=html_to_text(result["title"]), title=html_to_text(result["title"]),
content=html_to_text(result["description"]), content=html_to_text(result["description"]),
thumbnail=result["thumbnailUrl"], thumbnail=result["thumbnailUrl"],
publishedDate=datetime.fromisoformat(result["publishDate"]), publishedDate=datetime.fromisoformat(result["publishDate"]),
length=format_duration(result["duration"]), length=timedelta(seconds=result["duration"]),
views=humanize_number(result["viewCount"]), views=humanize_number(result["viewCount"]),
) )
) )

View File

@@ -10,7 +10,7 @@ from datetime import datetime
from urllib.parse import urlencode from urllib.parse import urlencode
from urllib.parse import quote_plus from urllib.parse import quote_plus
from searx.result_types import EngineResults, MainResult, LegacyResult, Image from searx.result_types import EngineResults, MainResult, Image, Video
from searx.utils import html_to_text, gen_useragent, extr from searx.utils import html_to_text, gen_useragent, extr
from searx.network import get # see https://github.com/searxng/searxng/issues/762 from searx.network import get # see https://github.com/searxng/searxng/issues/762
@@ -162,13 +162,11 @@ def _image_result(result):
def _video_result(result): def _video_result(result):
return LegacyResult( return Video(
template='videos.html',
url=result['content'], url=result['content'],
title=result['title'], title=result['title'],
content=result['description'], content=result['description'],
thumbnail=result['images'].get('small') or result['images'].get('medium'), thumbnail=result['images'].get('small') or result['images'].get('medium'),
source=result['provider'],
length=result['duration'], length=result['duration'],
metadata=result.get('uploader'), metadata=result.get('uploader'),
) )

View File

@@ -92,8 +92,7 @@ def response(resp: "SXNG_Response") -> EngineResults:
): ):
video_src = extract_text(eval_xpath(result, ".//video/@src")) or "" video_src = extract_text(eval_xpath(result, ".//video/@src")) or ""
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html",
url=video_src, url=video_src,
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/span")) or "", title=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/span")) or "",
iframe_src=video_src or "", iframe_src=video_src or "",

View File

@@ -153,16 +153,13 @@ def response(resp: "SXNG_Response") -> EngineResults:
length = result["video"].get("duration") length = result["video"].get("duration")
res.add( res.add(
res.types.LegacyResult( res.types.Video(
{ url=result["url"],
"template": "videos.html", title=html_to_text(result["title"]),
"url": result["url"], content=html_to_text(result["description"]),
"title": html_to_text(result["title"]), thumbnail=result.get("thumbnail", {}).get("original"),
"content": html_to_text(result["description"]), length=length,
"thumbnail": result.get("thumbnail", {}).get("original"), publishedDate=published_date,
"length": length,
"publishedDate": published_date,
}
) )
) )

View File

@@ -73,12 +73,11 @@ def response(resp: "SXNG_Response") -> EngineResults:
break break
results.add( results.add(
results.types.MainResult( results.types.Video(
url=url, url=url,
title=title, title=title,
thumbnail=thumbnail, thumbnail=thumbnail,
length=length, length=length,
template="videos.html",
) )
) )

View File

@@ -178,17 +178,14 @@ def response(resp: "SXNG_Response") -> EngineResults:
length = parse_duration_string(result["props"]["duration"]) length = parse_duration_string(result["props"]["duration"])
res.add( res.add(
res.types.LegacyResult( res.types.Video(
{ url=result["url"],
"template": "videos.html", title=html_to_text(result.get("title", "no title available")),
"url": result["url"], content=html_to_text(result.get("snippet", "")),
"title": html_to_text(result.get("title", "no title available")), thumbnail=result.get("image", {}).get("url"),
"content": html_to_text(result.get("snippet", "")), publishedDate=published_date,
"thumbnail": result.get("image", {}).get("url"), author=result.get("props", {}).get("creator_name"),
"publishedDate": published_date, length=length,
"author": result.get("props", {}).get("creator_name"),
"length": length,
}
) )
) )

View File

@@ -157,8 +157,7 @@ def _news_results(doc: ElementType, res: EngineResults):
def _video_results(doc: ElementType, res: EngineResults): def _video_results(doc: ElementType, res: EngineResults):
for result in eval_xpath_list(doc, "//div[@id='mainResults']/div[contains(@class, 'mediaResult')]"): for result in eval_xpath_list(doc, "//div[@id='mainResults']/div[contains(@class, 'mediaResult')]"):
res.add( res.add(
res.types.MainResult( res.types.Video(
template="videos.html",
url=extract_text(eval_xpath(result, "./@data-url")) or "", url=extract_text(eval_xpath(result, "./@data-url")) or "",
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'mediaResultTitleVideo')]/a")) or "", title=extract_text(eval_xpath(result, ".//div[contains(@class, 'mediaResultTitleVideo')]/a")) or "",
content=extract_text(eval_xpath(result, ".//div[contains(@class, 'mediaResultDescription')]")) or "", content=extract_text(eval_xpath(result, ".//div[contains(@class, 'mediaResultDescription')]")) or "",

View File

@@ -5,6 +5,7 @@ import typing as t
import datetime import datetime
from searx.result_types import EngineResults from searx.result_types import EngineResults
from searx.utils import parse_duration_string
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response from searx.extended_types import SXNG_Response
@@ -67,13 +68,12 @@ def response(resp: "SXNG_Response") -> EngineResults:
continue continue
res.add( res.add(
res.types.LegacyResult( res.types.Video(
url=video_url, url=video_url,
title="%(channel)s: %(title)s (%(hms)s)" % item, title="%(channel)s: %(title)s (%(hms)s)" % item,
length=item["hms"], length=parse_duration_string(item["hms"]),
content="%(description)s" % item, content="%(description)s" % item,
iframe_src=video_url, iframe_src=video_url,
template="videos.html",
) )
) )

View File

@@ -201,7 +201,7 @@ def parse_videos(data):
for item in eval_xpath_list(dom, "//li[contains(@class, 'video_item')]"): for item in eval_xpath_list(dom, "//li[contains(@class, 'video_item')]"):
url = eval_xpath_getindex(item, ".//a[contains(@class, 'info_title')]/@href", 0) url = eval_xpath_getindex(item, ".//a[contains(@class, 'info_title')]/@href", 0)
thumbnail = None thumbnail = ""
try: try:
thumbnail = eval_xpath_getindex(item, ".//img[contains(@class, 'thumb')]/@src", 0) thumbnail = eval_xpath_getindex(item, ".//img[contains(@class, 'thumb')]/@src", 0)
except (ValueError, TypeError, SearxEngineXPathException): except (ValueError, TypeError, SearxEngineXPathException):
@@ -214,9 +214,8 @@ def parse_videos(data):
pass pass
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html", title=extract_text(eval_xpath(item, ".//a[contains(@class, 'info_title')]")) or "",
title=extract_text(eval_xpath(item, ".//a[contains(@class, 'info_title')]")),
url=url, url=url,
thumbnail=thumbnail, thumbnail=thumbnail,
length=length, length=length,

View File

@@ -4,14 +4,19 @@
.. _Odysee: https://github.com/OdyseeTeam/odysee-frontend .. _Odysee: https://github.com/OdyseeTeam/odysee-frontend
""" """
from datetime import datetime import typing as t
from datetime import datetime, timedelta
from urllib.parse import urlencode from urllib.parse import urlencode
import babel import babel
from searx.enginelib.traits import EngineTraits from searx.enginelib.traits import EngineTraits
from searx.locales import language_tag from searx.locales import language_tag
from searx.utils import format_duration from searx.result_types import EngineResults
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
# Engine metadata # Engine metadata
about = { about = {
@@ -34,7 +39,7 @@ categories = ["videos"]
base_url = "https://lighthouse.odysee.tv/search" base_url = "https://lighthouse.odysee.tv/search"
def request(query, params): def request(query: str, params: "OnlineParams"):
time_range_dict = { time_range_dict = {
"day": "today", "day": "today",
"week": "thisweek", "week": "thisweek",
@@ -59,22 +64,17 @@ def request(query, params):
query_params["time_filter"] = time_range_dict[params["time_range"]] query_params["time_filter"] = time_range_dict[params["time_range"]]
params["url"] = f"{base_url}?{urlencode(query_params)}" params["url"] = f"{base_url}?{urlencode(query_params)}"
return params
def response(resp): def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
data = resp.json() data = resp.json()
results = []
for item in data: for item in data:
name = item["name"] name = item["name"]
claim_id = item["claimId"] claim_id = item["claimId"]
title = item["title"]
thumbnail_url = item["thumbnail_url"] thumbnail_url = item["thumbnail_url"]
description = item["description"] or ""
channel = item["channel"]
release_time = item["release_time"] release_time = item["release_time"]
duration = item["duration"]
release_date = datetime.fromisoformat(release_time.split("T")[0]) release_date = datetime.fromisoformat(release_time.split("T")[0])
formatted_date = datetime.fromtimestamp(release_date.timestamp()) formatted_date = datetime.fromtimestamp(release_date.timestamp())
@@ -82,23 +82,21 @@ def response(resp):
url = f"https://odysee.com/{name}:{claim_id}" url = f"https://odysee.com/{name}:{claim_id}"
iframe_url = f"https://odysee.com/$/embed/{name}:{claim_id}" iframe_url = f"https://odysee.com/$/embed/{name}:{claim_id}"
odysee_thumbnail = f"https://thumbnails.odycdn.com/optimize/s:390:0/quality:85/plain/{thumbnail_url}" odysee_thumbnail = f"https://thumbnails.odycdn.com/optimize/s:390:0/quality:85/plain/{thumbnail_url}"
formatted_duration = format_duration(duration)
results.append( res.add(
{ res.types.Video(
"title": title, title=item["title"],
"url": url, url=url,
"content": description, content=item["description"] or "",
"author": channel, author=item["channel"],
"publishedDate": formatted_date, publishedDate=formatted_date,
"length": formatted_duration, length=timedelta(seconds=item["duration"]),
"thumbnail": odysee_thumbnail, thumbnail=odysee_thumbnail,
"iframe_src": iframe_url, iframe_src=iframe_url,
"template": "videos.html", )
}
) )
return results return res
def fetch_traits(engine_traits: EngineTraits): def fetch_traits(engine_traits: EngineTraits):

View File

@@ -146,8 +146,7 @@ def _video_results(doc: "ElementBase") -> EngineResults:
thumbnail = _extract_thumbnail_url(extr(thumbnail_style, ":url(", ")")) thumbnail = _extract_thumbnail_url(extr(thumbnail_style, ":url(", ")"))
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html",
url=url, url=url,
title=extract_text(eval_xpath(result, ".//h2[contains(@class, 'video-card-title')]")) or "", title=extract_text(eval_xpath(result, ".//h2[contains(@class, 'video-card-title')]")) or "",
content=extract_text(eval_xpath(result, ".//p")) or "", content=extract_text(eval_xpath(result, ".//p")) or "",

View File

@@ -292,7 +292,7 @@ def response(resp: "SXNG_Response") -> EngineResults:
thumbnail = thumbnail.replace("https://s2.qwant.com", "https://s1.qwant.com", 1) thumbnail = thumbnail.replace("https://s2.qwant.com", "https://s1.qwant.com", 1)
res.add( res.add(
res.types.LegacyResult( res.types.Video(
title=title, title=title,
url=res_url, url=res_url,
content=content, content=content,

View File

@@ -7,10 +7,9 @@ results from Google.
import typing as t import typing as t
from urllib.parse import urlencode from urllib.parse import urlencode
import datetime
from dateutil import parser from dateutil import parser
from searx.utils import format_duration
from searx.result_types import EngineResults from searx.result_types import EngineResults
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
@@ -49,7 +48,7 @@ def request(query: str, params: "OnlineParams") -> None:
params["url"] = f"{api_url}/api/v2/search/{startpagina_categ}/?{urlencode(args)}" params["url"] = f"{api_url}/api/v2/search/{startpagina_categ}/?{urlencode(args)}"
def response(resp: "SXNG_Response"): def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults() res = EngineResults()
json_resp = resp.json() json_resp = resp.json()
@@ -81,13 +80,12 @@ def response(resp: "SXNG_Response"):
) )
elif startpagina_categ == "videos": elif startpagina_categ == "videos":
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html",
url=result["original_url"], url=result["original_url"],
title=result["title"], title=result["title"],
content=result["description"], content=result["description"],
thumbnail=result["video"]["thumbnail_url"], thumbnail=result["video"]["thumbnail_url"],
length=format_duration(result["video"]["duration"]), length=datetime.timedelta(seconds=result["video"]["duration"]),
) )
) )
elif startpagina_categ == "images": elif startpagina_categ == "images":

View File

@@ -16,7 +16,7 @@ from urllib.parse import urlencode
from babel.core import get_global from babel.core import get_global
from searx.result_types import EngineResults, LegacyResult # pyright: ignore[reportPrivateLocalImportUsage] from searx.result_types import EngineResults, Video
from searx.utils import humanize_number, html_to_text from searx.utils import humanize_number, html_to_text
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
@@ -215,28 +215,24 @@ def request(query: str, params: "OnlineParams") -> None:
params["url"] = f"{base_url}{base_path}?{urlencode(args)}" params["url"] = f"{base_url}{base_path}?{urlencode(args)}"
def _video_result(result: dict[str, str]) -> LegacyResult: def _video_result(result: dict[str, str]) -> Video:
published_date = None published_date = None
if result.get("datePublished"): if result.get("datePublished"):
published_date = datetime.fromisoformat(result["datePublished"]) published_date = datetime.fromisoformat(result["datePublished"])
view_count = None view_count = ""
if result.get("viewCount"): if result.get("viewCount"):
view_count = humanize_number(result["viewCount"]) # pyright: ignore[reportArgumentType] view_count = humanize_number(result["viewCount"]) # pyright: ignore[reportArgumentType]
return LegacyResult( return Video(
{ url=result["url"],
"template": "videos.html", title=html_to_text(result.get("title") or result["name"]),
"url": result["url"], content=result["description"],
"title": html_to_text(result.get("title") or result["name"]), thumbnail=result.get("thumbnailUrl") or result.get("thumbnail", {}).get("url"),
"content": result["description"], length=result.get("duration"),
"thumbnail": result.get("thumbnailUrl") iframe_src=result.get("embedUrl"),
or result.get("thumbnail", {}).get("url"), # pyright: ignore[reportAttributeAccessIssue] publishedDate=published_date,
"length": result.get("duration"), views=view_count,
"iframe_src": result.get("embedUrl"),
"publishedDate": published_date,
"views": view_count,
}
) )
@@ -272,14 +268,11 @@ def response(resp: "SXNG_Response") -> EngineResults:
res.add(_video_result(video)) res.add(_video_result(video))
elif result["type"] == "ImageObject": elif result["type"] == "ImageObject":
res.add( res.add(
res.types.LegacyResult( res.types.Image(
{ url=result["url"],
"template": "images.html", thumbnail_src=result["thumbnail"]["url"],
"url": result["url"], img_src=result["contentUrl"],
"thumbnail_src": result["thumbnail"]["url"], title=result["name"],
"img_src": result["contentUrl"],
"title": result["name"],
}
) )
) )
elif result["type"] == "video": elif result["type"] == "video":

View File

@@ -20,6 +20,8 @@ from datetime import datetime
from urllib.parse import urlencode from urllib.parse import urlencode
import re import re
from searx.result_types import Video
about = { about = {
'website': "https://tagesschau.de", 'website': "https://tagesschau.de",
'wikidata_id': "Q703907", 'wikidata_id': "Q703907",
@@ -87,7 +89,7 @@ def _story(item):
} }
def _video(item): def _video(item) -> Video:
streams = item['streams'] streams = item['streams']
video_url = streams.get('h264s') or streams.get('h264m') or streams.get('h264l') or streams.get('h264xl') video_url = streams.get('h264s') or streams.get('h264m') or streams.get('h264l') or streams.get('h264xl')
title = item['title'] title = item['title']
@@ -99,12 +101,11 @@ def _video(item):
# sometimes, only adaptive m3u8 streams are available, so video_url is None # sometimes, only adaptive m3u8 streams are available, so video_url is None
url = video_url or f"{base_url}/multimedia/video/{item['sophoraId']}.html" url = video_url or f"{base_url}/multimedia/video/{item['sophoraId']}.html"
return { return Video(
'template': 'videos.html', title=title,
'title': title, thumbnail=item.get('teaserImage', {}).get('imageVariants', {}).get('16x9-256'),
'thumbnail': item.get('teaserImage', {}).get('imageVariants', {}).get('16x9-256'), publishedDate=datetime.fromisoformat(item['date'][:19]),
'publishedDate': datetime.fromisoformat(item['date'][:19]), content=item.get('firstSentence', ''),
'content': item.get('firstSentence', ''), iframe_src=video_url,
'iframe_src': video_url, url=url,
'url': url, )
}

View File

@@ -119,8 +119,7 @@ def _video_results(doc: ElementType, res: EngineResults):
continue continue
title_parts: list[ElementType] = eval_xpath(result, ".//a[starts-with(@class, 'tMMReshl')]") title_parts: list[ElementType] = eval_xpath(result, ".//a[starts-with(@class, 'tMMReshl')]")
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html",
url=url, url=url,
title=" - ".join(extract_text(part) or "" for part in title_parts), title=" - ".join(extract_text(part) or "" for part in title_parts),
thumbnail=extract_text(eval_xpath(result, ".//img/@src") or "") or "", thumbnail=extract_text(eval_xpath(result, ".//img/@src") or "") or "",

View File

@@ -143,8 +143,7 @@ def response(resp: "SXNG_Response"):
pass pass
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html",
url=result["url"], url=result["url"],
title=html_to_text(result["title"]), title=html_to_text(result["title"]),
content=html_to_text(result["description"]), content=html_to_text(result["description"]),

View File

@@ -85,8 +85,7 @@ def _video_results(doc: "ElementBase") -> EngineResults:
for result in eval_xpath_list(doc, "//div[contains(@class, 'item video')]"): for result in eval_xpath_list(doc, "//div[contains(@class, 'item video')]"):
( (
res.add( res.add(
res.types.MainResult( res.types.Video(
template="videos.html",
url=extract_text(eval_xpath(result, "./a/@href")) or "", url=extract_text(eval_xpath(result, "./a/@href")) or "",
title=extract_text(eval_xpath(result, "./a/@title")) or "", title=extract_text(eval_xpath(result, "./a/@title")) or "",
content=extract_text(eval_xpath(result, ".//div[contains(@class, 'abaslik')]")) or "", content=extract_text(eval_xpath(result, ".//div[contains(@class, 'abaslik')]")) or "",

View File

@@ -181,8 +181,7 @@ def response(resp: "SXNG_Response") -> EngineResults:
if wc_search_type == "video": if wc_search_type == "video":
res.add( res.add(
res.types.LegacyResult( res.types.Video(
template="videos.html",
title=title, title=title,
url=url, url=url,
content=content, content=content,

View File

@@ -2,7 +2,6 @@
"""Utility functions for the engines""" """Utility functions for the engines"""
from hashlib import pbkdf2_hmac from hashlib import pbkdf2_hmac
import time
import re import re
import importlib import importlib
@@ -816,15 +815,6 @@ def parse_duration_string(duration_str: str) -> timedelta | None:
return None return None
# Format the video duration
def format_duration(duration: str | int) -> str:
seconds = int(duration)
length = time.gmtime(seconds)
if length.tm_hour:
return time.strftime("%H:%M:%S", length)
return time.strftime("%M:%S", length)
def _array_startswith(arr: bytes, prefix: bytes) -> bool: def _array_startswith(arr: bytes, prefix: bytes) -> bool:
return arr[: len(prefix)] == prefix return arr[: len(prefix)] == prefix