.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
adobe_stock.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""`Adobe Stock`_ is a service that gives access to millions of royalty-free
3assets. Assets types include photos, vectors, illustrations, templates, 3D
4assets, videos, motion graphics templates and audio tracks.
5
6.. Adobe Stock: https://stock.adobe.com/
7
8Configuration
9=============
10
11The engine has the following mandatory setting:
12
13- SearXNG's :ref:`engine categories`
14- Adobe-Stock's :py:obj:`adobe_order`
15- Adobe-Stock's :py:obj:`adobe_content_types`
16
17.. code:: yaml
18
19 - name: adobe stock
20 engine: adobe_stock
21 shortcut: asi
22 categories: [images]
23 adobe_order: relevance
24 adobe_content_types: ["photo", "illustration", "zip_vector", "template", "3d", "image"]
25
26 - name: adobe stock video
27 engine: adobe_stock
28 network: adobe stock
29 shortcut: asi
30 categories: [videos]
31 adobe_order: relevance
32 adobe_content_types: ["video"]
33
34Implementation
35==============
36
37"""
38from __future__ import annotations
39
40from typing import TYPE_CHECKING
41from datetime import datetime, timedelta
42from urllib.parse import urlencode
43
44import isodate
45
46if TYPE_CHECKING:
47 import logging
48
49 logger: logging.Logger
50
51about = {
52 "website": "https://stock.adobe.com/",
53 "wikidata_id": "Q5977430",
54 "official_api_documentation": None,
55 "use_official_api": False,
56 "require_api_key": False,
57 "results": "JSON",
58}
59
60categories = []
61paging = True
62send_accept_language_header = True
63results_per_page = 10
64
65base_url = "https://stock.adobe.com"
66
67adobe_order: str = ""
68"""Sort order, can be one of:
69
70- ``relevance`` or
71- ``featured`` or
72- ``creation`` (most recent) or
73- ``nb_downloads`` (number of downloads)
74"""
75
76ADOBE_VALID_TYPES = ["photo", "illustration", "zip_vector", "video", "template", "3d", "audio", "image"]
77adobe_content_types: list = []
78"""A list of of content types. The following content types are offered:
79
80- Images: ``image``
81- Videos: ``video``
82- Templates: ``template``
83- 3D: ``3d``
84- Audio ``audio``
85
86Additional subcategories:
87
88- Photos: ``photo``
89- Illustrations: ``illustration``
90- Vectors: ``zip_vector`` (Vectors),
91"""
92
93# Do we need support for "free_collection" and "include_stock_enterprise"?
94
95
96def init(_):
97 if not categories:
98 raise ValueError("adobe_stock engine: categories is unset")
99
100 # adobe_order
101 if not adobe_order:
102 raise ValueError("adobe_stock engine: adobe_order is unset")
103 if adobe_order not in ["relevance", "featured", "creation", "nb_downloads"]:
104 raise ValueError(f"unsupported adobe_order: {adobe_order}")
105
106 # adobe_content_types
107 if not adobe_content_types:
108 raise ValueError("adobe_stock engine: adobe_content_types is unset")
109
110 if isinstance(adobe_content_types, list):
111 for t in adobe_content_types:
112 if t not in ADOBE_VALID_TYPES:
113 raise ValueError("adobe_stock engine: adobe_content_types: '%s' is invalid" % t)
114 else:
115 raise ValueError(
116 "adobe_stock engine: adobe_content_types must be a list of strings not %s" % type(adobe_content_types)
117 )
118
119
120def request(query, params):
121
122 args = {
123 "k": query,
124 "limit": results_per_page,
125 "order": adobe_order,
126 "search_page": params["pageno"],
127 "search_type": "pagination",
128 }
129
130 for content_type in ADOBE_VALID_TYPES:
131 args[f"filters[content_type:{content_type}]"] = 1 if content_type in adobe_content_types else 0
132
133 params["url"] = f"{base_url}/de/Ajax/Search?{urlencode(args)}"
134
135 # headers required to bypass bot-detection
136 if params["searxng_locale"] == "all":
137 params["headers"]["Accept-Language"] = "en-US,en;q=0.5"
138
139 return params
140
141
143 return {
144 "template": "images.html",
145 "url": item["content_url"],
146 "title": item["title"],
147 "content": item["asset_type"],
148 "img_src": item["content_thumb_extra_large_url"],
149 "thumbnail_src": item["thumbnail_url"],
150 "resolution": f"{item['content_original_width']}x{item['content_original_height']}",
151 "img_format": item["format"],
152 "author": item["author"],
153 }
154
155
157
158 # in video items, the title is more or less a "content description", we try
159 # to reduce the lenght of the title ..
160
161 title = item["title"]
162 content = ""
163 if "." in title.strip()[:-1]:
164 content = title
165 title = title.split(".", 1)[0]
166 elif "," in title:
167 content = title
168 title = title.split(",", 1)[0]
169 elif len(title) > 50:
170 content = title
171 title = ""
172 for w in content.split(" "):
173 title += f" {w}"
174 if len(title) > 50:
175 title = title.strip() + "\u2026"
176 break
177
178 return {
179 "template": "videos.html",
180 "url": item["content_url"],
181 "title": title,
182 "content": content,
183 # https://en.wikipedia.org/wiki/ISO_8601#Durations
184 "length": isodate.parse_duration(item["time_duration"]),
185 "publishedDate": datetime.strptime(item["creation_date"], "%Y-%m-%d"),
186 "thumbnail": item["thumbnail_url"],
187 "iframe_src": item["video_small_preview_url"],
188 "metadata": item["asset_type"],
189 }
190
191
193 audio_data = item["audio_data"]
194 content = audio_data.get("description") or ""
195 if audio_data.get("album"):
196 content = audio_data["album"] + " - " + content
197
198 return {
199 "url": item["content_url"],
200 "title": item["title"],
201 "content": content,
202 # "thumbnail": base_url + item["thumbnail_url"],
203 "iframe_src": audio_data["preview"]["url"],
204 "publishedDate": datetime.fromisoformat(audio_data["release_date"]) if audio_data["release_date"] else None,
205 "length": timedelta(seconds=round(audio_data["duration"] / 1000)) if audio_data["duration"] else None,
206 "author": item.get("artist_name"),
207 }
208
209
210def response(resp):
211 results = []
212
213 json_resp = resp.json()
214
215 if isinstance(json_resp["items"], list):
216 return None
217 for item in json_resp["items"].values():
218 if item["asset_type"].lower() in ["image", "premium-image", "illustration", "vector"]:
219 result = parse_image_item(item)
220 elif item["asset_type"].lower() == "video":
221 result = parse_video_item(item)
222 elif item["asset_type"].lower() == "audio":
223 result = parse_audio_item(item)
224 else:
225 logger.error("no handle for %s --> %s", item["asset_type"], item)
226 continue
227 results.append(result)
228
229 return results