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