.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"""
38
39from datetime import datetime, timedelta
40from urllib.parse import urlencode
41
42import isodate
43
44about = {
45 "website": "https://stock.adobe.com/",
46 "wikidata_id": "Q5977430",
47 "official_api_documentation": None,
48 "use_official_api": False,
49 "require_api_key": False,
50 "results": "JSON",
51}
52
53categories = []
54paging = True
55send_accept_language_header = True
56results_per_page = 10
57
58base_url = "https://stock.adobe.com"
59
60adobe_order: str = ""
61"""Sort order, can be one of:
62
63- ``relevance`` or
64- ``featured`` or
65- ``creation`` (most recent) or
66- ``nb_downloads`` (number of downloads)
67"""
68
69ADOBE_VALID_TYPES = ["photo", "illustration", "zip_vector", "video", "template", "3d", "audio", "image"]
70adobe_content_types: list = []
71"""A list of of content types. The following content types are offered:
72
73- Images: ``image``
74- Videos: ``video``
75- Templates: ``template``
76- 3D: ``3d``
77- Audio ``audio``
78
79Additional subcategories:
80
81- Photos: ``photo``
82- Illustrations: ``illustration``
83- Vectors: ``zip_vector`` (Vectors),
84"""
85
86# Do we need support for "free_collection" and "include_stock_enterprise"?
87
88
89def init(_):
90 if not categories:
91 raise ValueError("adobe_stock engine: categories is unset")
92
93 # adobe_order
94 if not adobe_order:
95 raise ValueError("adobe_stock engine: adobe_order is unset")
96 if adobe_order not in ["relevance", "featured", "creation", "nb_downloads"]:
97 raise ValueError(f"unsupported adobe_order: {adobe_order}")
98
99 # adobe_content_types
100 if not adobe_content_types:
101 raise ValueError("adobe_stock engine: adobe_content_types is unset")
102
103 if isinstance(adobe_content_types, list):
104 for t in adobe_content_types:
105 if t not in ADOBE_VALID_TYPES:
106 raise ValueError("adobe_stock engine: adobe_content_types: '%s' is invalid" % t)
107 else:
108 raise ValueError(
109 "adobe_stock engine: adobe_content_types must be a list of strings not %s" % type(adobe_content_types)
110 )
111
112
113def request(query, params):
114
115 args = {
116 "k": query,
117 "limit": results_per_page,
118 "order": adobe_order,
119 "search_page": params["pageno"],
120 "search_type": "pagination",
121 }
122
123 for content_type in ADOBE_VALID_TYPES:
124 args[f"filters[content_type:{content_type}]"] = 1 if content_type in adobe_content_types else 0
125
126 params["url"] = f"{base_url}/de/Ajax/Search?{urlencode(args)}"
127
128 # headers required to bypass bot-detection
129 if params["searxng_locale"] == "all":
130 params["headers"]["Accept-Language"] = "en-US,en;q=0.5"
131
132 return params
133
134
136 return {
137 "template": "images.html",
138 "url": item["content_url"],
139 "title": item["title"],
140 "content": item["asset_type"],
141 "img_src": item["content_thumb_extra_large_url"],
142 "thumbnail_src": item["thumbnail_url"],
143 "resolution": f"{item['content_original_width']}x{item['content_original_height']}",
144 "img_format": item["format"],
145 "author": item["author"],
146 }
147
148
150
151 # in video items, the title is more or less a "content description", we try
152 # to reduce the length of the title ..
153
154 title = item["title"]
155 content = ""
156 if "." in title.strip()[:-1]:
157 content = title
158 title = title.split(".", 1)[0]
159 elif "," in title:
160 content = title
161 title = title.split(",", 1)[0]
162 elif len(title) > 50:
163 content = title
164 title = ""
165 for w in content.split(" "):
166 title += f" {w}"
167 if len(title) > 50:
168 title = title.strip() + "\u2026"
169 break
170
171 return {
172 "template": "videos.html",
173 "url": item["content_url"],
174 "title": title,
175 "content": content,
176 # https://en.wikipedia.org/wiki/ISO_8601#Durations
177 "length": isodate.parse_duration(item["time_duration"]),
178 "publishedDate": datetime.fromisoformat(item["creation_date"]),
179 "thumbnail": item["thumbnail_url"],
180 "iframe_src": item["video_small_preview_url"],
181 "metadata": item["asset_type"],
182 }
183
184
186 audio_data = item["audio_data"]
187 content = audio_data.get("description") or ""
188 if audio_data.get("album"):
189 content = audio_data["album"] + " - " + content
190
191 return {
192 "url": item["content_url"],
193 "title": item["title"],
194 "content": content,
195 # "thumbnail": base_url + item["thumbnail_url"],
196 "iframe_src": audio_data["preview"]["url"],
197 "publishedDate": datetime.fromisoformat(audio_data["release_date"]) if audio_data["release_date"] else None,
198 "length": timedelta(seconds=round(audio_data["duration"] / 1000)) if audio_data["duration"] else None,
199 "author": item.get("artist_name"),
200 }
201
202
203def response(resp):
204 results = []
205
206 json_resp = resp.json()
207
208 if isinstance(json_resp["items"], list):
209 return None
210 for item in json_resp["items"].values():
211 if item["asset_type"].lower() in ["image", "premium-image", "illustration", "vector"]:
212 result = parse_image_item(item)
213 elif item["asset_type"].lower() == "video":
214 result = parse_video_item(item)
215 elif item["asset_type"].lower() == "audio":
216 result = parse_audio_item(item)
217 else:
218 logger.error("no handle for %s --> %s", item["asset_type"], item)
219 continue
220 results.append(result)
221
222 return results