.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
tineye.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""This engine implements *Tineye - reverse image search*
3
4Using TinEye, you can search by image or perform what we call a reverse image
5search. You can do that by uploading an image or searching by URL. You can also
6simply drag and drop your images to start your search. TinEye constantly crawls
7the web and adds images to its index. Today, the TinEye index is over 50.2
8billion images `[tineye.com] <https://tineye.com/how>`_.
9
10.. hint::
11
12 This SearXNG engine only supports *'searching by URL'* and it does not use
13 the official API `[api.tineye.com] <https://api.tineye.com/python/docs/>`_.
14
15"""
16
17from typing import TYPE_CHECKING
18from urllib.parse import urlencode
19from datetime import datetime
20from flask_babel import gettext
21
22if TYPE_CHECKING:
23 import logging
24
25 logger = logging.getLogger()
26
27about = {
28 "website": 'https://tineye.com',
29 "wikidata_id": 'Q2382535',
30 "official_api_documentation": 'https://api.tineye.com/python/docs/',
31 "use_official_api": False,
32 "require_api_key": False,
33 "results": 'JSON',
34}
35
36engine_type = 'online_url_search'
37""":py:obj:`searx.search.processors.online_url_search`"""
38
39categories = ['general']
40paging = True
41safesearch = False
42base_url = 'https://tineye.com'
43search_string = '/api/v1/result_json/?page={page}&{query}'
44
45FORMAT_NOT_SUPPORTED = gettext(
46 "Could not read that image url. This may be due to an unsupported file"
47 " format. TinEye only supports images that are JPEG, PNG, GIF, BMP, TIFF or WebP."
48)
49"""TinEye error message"""
50
51NO_SIGNATURE_ERROR = gettext(
52 "The image is too simple to find matches. TinEye requires a basic level of"
53 " visual detail to successfully identify matches."
54)
55"""TinEye error message"""
56
57DOWNLOAD_ERROR = gettext("The image could not be downloaded.")
58"""TinEye error message"""
59
60
61def request(query, params):
62 """Build TinEye HTTP request using ``search_urls`` of a :py:obj:`engine_type`."""
63
64 params['raise_for_httperror'] = False
65
66 if params['search_urls']['data:image']:
67 query = params['search_urls']['data:image']
68 elif params['search_urls']['http']:
69 query = params['search_urls']['http']
70
71 logger.debug("query URL: %s", query)
72 query = urlencode({'url': query})
73
74 # see https://github.com/TinEye/pytineye/blob/main/pytineye/api.py
75 params['url'] = base_url + search_string.format(query=query, page=params['pageno'])
76
77 params['headers'].update(
78 {
79 'Connection': 'keep-alive',
80 'Accept-Encoding': 'gzip, defalte, br',
81 'Host': 'tineye.com',
82 'DNT': '1',
83 'TE': 'trailers',
84 }
85 )
86 return params
87
88
89def parse_tineye_match(match_json):
90 """Takes parsed JSON from the API server and turns it into a :py:obj:`dict`
91 object.
92
93 Attributes `(class Match) <https://github.com/TinEye/pytineye/blob/main/pytineye/api.py>`__
94
95 - `image_url`, link to the result image.
96 - `domain`, domain this result was found on.
97 - `score`, a number (0 to 100) that indicates how closely the images match.
98 - `width`, image width in pixels.
99 - `height`, image height in pixels.
100 - `size`, image area in pixels.
101 - `format`, image format.
102 - `filesize`, image size in bytes.
103 - `overlay`, overlay URL.
104 - `tags`, whether this match belongs to a collection or stock domain.
105
106 - `backlinks`, a list of Backlink objects pointing to the original websites
107 and image URLs. List items are instances of :py:obj:`dict`, (`Backlink
108 <https://github.com/TinEye/pytineye/blob/main/pytineye/api.py>`__):
109
110 - `url`, the image URL to the image.
111 - `backlink`, the original website URL.
112 - `crawl_date`, the date the image was crawled.
113
114 """
115
116 # HINT: there exists an alternative backlink dict in the domains list / e.g.::
117 #
118 # match_json['domains'][0]['backlinks']
119
120 backlinks = []
121 if "backlinks" in match_json:
122
123 for backlink_json in match_json["backlinks"]:
124 if not isinstance(backlink_json, dict):
125 continue
126
127 crawl_date = backlink_json.get("crawl_date")
128 if crawl_date:
129 crawl_date = datetime.strptime(crawl_date, '%Y-%m-%d')
130 else:
131 crawl_date = datetime.min
132
133 backlinks.append(
134 {
135 'url': backlink_json.get("url"),
136 'backlink': backlink_json.get("backlink"),
137 'crawl_date': crawl_date,
138 'image_name': backlink_json.get("image_name"),
139 }
140 )
141
142 return {
143 'image_url': match_json.get("image_url"),
144 'domain': match_json.get("domain"),
145 'score': match_json.get("score"),
146 'width': match_json.get("width"),
147 'height': match_json.get("height"),
148 'size': match_json.get("size"),
149 'image_format': match_json.get("format"),
150 'filesize': match_json.get("filesize"),
151 'overlay': match_json.get("overlay"),
152 'tags': match_json.get("tags"),
153 'backlinks': backlinks,
154 }
155
156
157def response(resp):
158 """Parse HTTP response from TinEye."""
159
160 # handle the 422 client side errors, and the possible 400 status code error
161 if resp.status_code in (400, 422):
162 json_data = resp.json()
163 suggestions = json_data.get('suggestions', {})
164 message = f'HTTP Status Code: {resp.status_code}'
165
166 if resp.status_code == 422:
167 s_key = suggestions.get('key', '')
168 if s_key == "Invalid image URL":
169 # test https://docs.searxng.org/_static/searxng-wordmark.svg
170 message = FORMAT_NOT_SUPPORTED
171 elif s_key == 'NO_SIGNATURE_ERROR':
172 # test https://pngimg.com/uploads/dot/dot_PNG4.png
173 message = NO_SIGNATURE_ERROR
174 elif s_key == 'Download Error':
175 # test https://notexists
176 message = DOWNLOAD_ERROR
177 else:
178 logger.warning("Unknown suggestion key encountered: %s", s_key)
179 else: # 400
180 description = suggestions.get('description')
181 if isinstance(description, list):
182 message = ','.join(description)
183
184 # see https://github.com/searxng/searxng/pull/1456#issuecomment-1193105023
185 # results.append({'answer': message})
186 logger.error(message)
187 return []
188
189 # Raise for all other responses
190 resp.raise_for_status()
191
192 results = []
193 json_data = resp.json()
194
195 for match_json in json_data['matches']:
196
197 tineye_match = parse_tineye_match(match_json)
198 if not tineye_match['backlinks']:
199 continue
200
201 backlink = tineye_match['backlinks'][0]
202 results.append(
203 {
204 'template': 'images.html',
205 'url': backlink['backlink'],
206 'thumbnail_src': tineye_match['image_url'],
207 'source': backlink['url'],
208 'title': backlink['image_name'],
209 'img_src': backlink['url'],
210 'format': tineye_match['image_format'],
211 'widht': tineye_match['width'],
212 'height': tineye_match['height'],
213 'publishedDate': backlink['crawl_date'],
214 }
215 )
216
217 # append number of results
218
219 number_of_results = json_data.get('num_matches')
220 if number_of_results:
221 results.append({'number_of_results': number_of_results})
222
223 return results
parse_tineye_match(match_json)
Definition tineye.py:89
request(query, params)
Definition tineye.py:61