[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
res.add(
res.types.LegacyResult(
res.types.Video(
url=entry["play_url"],
title=html_to_text(entry["title"]),
content=html_to_text(entry["description"]),

View File

@@ -127,7 +127,7 @@ from dateutil import parser
from searx import locales, logger
from searx.enginelib.traits import EngineTraits
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.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", {})
thumbnail: dict[str, t.Any] = result.get("thumbnail", {})
return MainResult(
template="videos.html",
return Video(
title=result.get("title", ""),
url=result.get("url"),
content=result.get("description", ""),

View File

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

View File

@@ -10,7 +10,7 @@ from datetime import datetime
from urllib.parse import urlencode
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.network import get # see https://github.com/searxng/searxng/issues/762
@@ -162,13 +162,11 @@ def _image_result(result):
def _video_result(result):
return LegacyResult(
template='videos.html',
return Video(
url=result['content'],
title=result['title'],
content=result['description'],
thumbnail=result['images'].get('small') or result['images'].get('medium'),
source=result['provider'],
length=result['duration'],
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 ""
res.add(
res.types.LegacyResult(
template="videos.html",
res.types.Video(
url=video_src,
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/span")) or "",
iframe_src=video_src or "",

View File

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

View File

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

View File

@@ -178,17 +178,14 @@ def response(resp: "SXNG_Response") -> EngineResults:
length = parse_duration_string(result["props"]["duration"])
res.add(
res.types.LegacyResult(
{
"template": "videos.html",
"url": result["url"],
"title": html_to_text(result.get("title", "no title available")),
"content": html_to_text(result.get("snippet", "")),
"thumbnail": result.get("image", {}).get("url"),
"publishedDate": published_date,
"author": result.get("props", {}).get("creator_name"),
"length": length,
}
res.types.Video(
url=result["url"],
title=html_to_text(result.get("title", "no title available")),
content=html_to_text(result.get("snippet", "")),
thumbnail=result.get("image", {}).get("url"),
publishedDate=published_date,
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):
for result in eval_xpath_list(doc, "//div[@id='mainResults']/div[contains(@class, 'mediaResult')]"):
res.add(
res.types.MainResult(
template="videos.html",
res.types.Video(
url=extract_text(eval_xpath(result, "./@data-url")) or "",
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'mediaResultTitleVideo')]/a")) or "",
content=extract_text(eval_xpath(result, ".//div[contains(@class, 'mediaResultDescription')]")) or "",

View File

@@ -5,6 +5,7 @@ import typing as t
import datetime
from searx.result_types import EngineResults
from searx.utils import parse_duration_string
if t.TYPE_CHECKING:
from searx.extended_types import SXNG_Response
@@ -67,13 +68,12 @@ def response(resp: "SXNG_Response") -> EngineResults:
continue
res.add(
res.types.LegacyResult(
res.types.Video(
url=video_url,
title="%(channel)s: %(title)s (%(hms)s)" % item,
length=item["hms"],
length=parse_duration_string(item["hms"]),
content="%(description)s" % item,
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')]"):
url = eval_xpath_getindex(item, ".//a[contains(@class, 'info_title')]/@href", 0)
thumbnail = None
thumbnail = ""
try:
thumbnail = eval_xpath_getindex(item, ".//img[contains(@class, 'thumb')]/@src", 0)
except (ValueError, TypeError, SearxEngineXPathException):
@@ -214,9 +214,8 @@ def parse_videos(data):
pass
res.add(
res.types.LegacyResult(
template="videos.html",
title=extract_text(eval_xpath(item, ".//a[contains(@class, 'info_title')]")),
res.types.Video(
title=extract_text(eval_xpath(item, ".//a[contains(@class, 'info_title')]")) or "",
url=url,
thumbnail=thumbnail,
length=length,

View File

@@ -4,14 +4,19 @@
.. _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
import babel
from searx.enginelib.traits import EngineTraits
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
about = {
@@ -34,7 +39,7 @@ categories = ["videos"]
base_url = "https://lighthouse.odysee.tv/search"
def request(query, params):
def request(query: str, params: "OnlineParams"):
time_range_dict = {
"day": "today",
"week": "thisweek",
@@ -59,22 +64,17 @@ def request(query, params):
query_params["time_filter"] = time_range_dict[params["time_range"]]
params["url"] = f"{base_url}?{urlencode(query_params)}"
return params
def response(resp):
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
data = resp.json()
results = []
for item in data:
name = item["name"]
claim_id = item["claimId"]
title = item["title"]
thumbnail_url = item["thumbnail_url"]
description = item["description"] or ""
channel = item["channel"]
release_time = item["release_time"]
duration = item["duration"]
release_date = datetime.fromisoformat(release_time.split("T")[0])
formatted_date = datetime.fromtimestamp(release_date.timestamp())
@@ -82,23 +82,21 @@ def response(resp):
url = f"https://odysee.com/{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}"
formatted_duration = format_duration(duration)
results.append(
{
"title": title,
"url": url,
"content": description,
"author": channel,
"publishedDate": formatted_date,
"length": formatted_duration,
"thumbnail": odysee_thumbnail,
"iframe_src": iframe_url,
"template": "videos.html",
}
res.add(
res.types.Video(
title=item["title"],
url=url,
content=item["description"] or "",
author=item["channel"],
publishedDate=formatted_date,
length=timedelta(seconds=item["duration"]),
thumbnail=odysee_thumbnail,
iframe_src=iframe_url,
)
)
return results
return res
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(", ")"))
res.add(
res.types.LegacyResult(
template="videos.html",
res.types.Video(
url=url,
title=extract_text(eval_xpath(result, ".//h2[contains(@class, 'video-card-title')]")) 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)
res.add(
res.types.LegacyResult(
res.types.Video(
title=title,
url=res_url,
content=content,

View File

@@ -7,10 +7,9 @@ results from Google.
import typing as t
from urllib.parse import urlencode
import datetime
from dateutil import parser
from searx.utils import format_duration
from searx.result_types import EngineResults
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)}"
def response(resp: "SXNG_Response"):
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
json_resp = resp.json()
@@ -81,13 +80,12 @@ def response(resp: "SXNG_Response"):
)
elif startpagina_categ == "videos":
res.add(
res.types.LegacyResult(
template="videos.html",
res.types.Video(
url=result["original_url"],
title=result["title"],
content=result["description"],
thumbnail=result["video"]["thumbnail_url"],
length=format_duration(result["video"]["duration"]),
length=datetime.timedelta(seconds=result["video"]["duration"]),
)
)
elif startpagina_categ == "images":

View File

@@ -16,7 +16,7 @@ from urllib.parse import urlencode
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
if t.TYPE_CHECKING:
@@ -215,28 +215,24 @@ def request(query: str, params: "OnlineParams") -> None:
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
if result.get("datePublished"):
published_date = datetime.fromisoformat(result["datePublished"])
view_count = None
view_count = ""
if result.get("viewCount"):
view_count = humanize_number(result["viewCount"]) # pyright: ignore[reportArgumentType]
return LegacyResult(
{
"template": "videos.html",
"url": result["url"],
"title": html_to_text(result.get("title") or result["name"]),
"content": result["description"],
"thumbnail": result.get("thumbnailUrl")
or result.get("thumbnail", {}).get("url"), # pyright: ignore[reportAttributeAccessIssue]
"length": result.get("duration"),
"iframe_src": result.get("embedUrl"),
"publishedDate": published_date,
"views": view_count,
}
return Video(
url=result["url"],
title=html_to_text(result.get("title") or result["name"]),
content=result["description"],
thumbnail=result.get("thumbnailUrl") or result.get("thumbnail", {}).get("url"),
length=result.get("duration"),
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))
elif result["type"] == "ImageObject":
res.add(
res.types.LegacyResult(
{
"template": "images.html",
"url": result["url"],
"thumbnail_src": result["thumbnail"]["url"],
"img_src": result["contentUrl"],
"title": result["name"],
}
res.types.Image(
url=result["url"],
thumbnail_src=result["thumbnail"]["url"],
img_src=result["contentUrl"],
title=result["name"],
)
)
elif result["type"] == "video":

View File

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

View File

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

View File

@@ -143,8 +143,7 @@ def response(resp: "SXNG_Response"):
pass
res.add(
res.types.LegacyResult(
template="videos.html",
res.types.Video(
url=result["url"],
title=html_to_text(result["title"]),
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')]"):
(
res.add(
res.types.MainResult(
template="videos.html",
res.types.Video(
url=extract_text(eval_xpath(result, "./a/@href")) or "",
title=extract_text(eval_xpath(result, "./a/@title")) 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":
res.add(
res.types.LegacyResult(
template="videos.html",
res.types.Video(
title=title,
url=url,
content=content,

View File

@@ -2,7 +2,6 @@
"""Utility functions for the engines"""
from hashlib import pbkdf2_hmac
import time
import re
import importlib
@@ -816,15 +815,6 @@ def parse_duration_string(duration_str: str) -> timedelta | 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:
return arr[: len(prefix)] == prefix