1
2
3 """ imgur image hosting.
4
5 Copyright (c) 2012 The PyroScope Project <pyroscope.project@gmail.com>
6 """
7
8
9
10
11
12
13
14
15
16
17
18
19
20 from __future__ import with_statement
21
22 import os
23 import sys
24 import socket
25 import hashlib
26 import httplib
27 import logging
28 from contextlib import closing
29
30 from pyrobase import parts, pyutil, logutil, fmt
31 from pyrobase.io import http
32
33 json = pyutil.require_json()
34 LOG = logging.getLogger(__name__)
35
36
37 UploadError = (socket.error, httplib.HTTPException)
38
39
41 """ Upload an image to "imgur.com".
42
43 Sample code::
44 imgur = ImgurUploader()
45 image = imgur.upload("favicon.jpg")
46 # OR: image = imgur.upload(open("favicon.jpg", "rb"))
47 # OR: image = imgur.upload(open("favicon.jpg", "rb").read())
48 # OR: image = imgur.upload("http://i.imgur.com/5EuUx.jpg")
49 print image.links.original
50 """
51 UPLOAD_URL = "http://api.imgur.com/2/upload.json"
52
53
54 - def __init__(self, api_key=None, mock_http=False):
55 """ Initialize upload parameters.
56
57 @param api_key: the API key (optionally taken from IMGUR_APIKEY environment variable).
58 """
59 self.api_key = api_key or os.environ.get("IMGUR_APIKEY")
60 self.mock_http = mock_http
61
62
63 - def upload(self, image, name=None):
64 """ Upload the given image, which can be a http[s] URL, a path to an existing file,
65 binary image data, or an open file handle.
66 """
67 assert self.api_key, "imgur API key is not set! Export the IMGUR_APIKEY environment variable..."
68
69
70 try:
71 image_data = (image + '')
72 except (TypeError, ValueError):
73 assert hasattr(image, "read"), "Image is neither a string nor an open file handle"
74 image_type = "file"
75 image_data = image
76 image_repr = repr(image)
77 else:
78 if image.startswith("http:") or image.startswith("https:"):
79 image_type = "url"
80 image_data = image
81 image_repr = image
82 elif all(ord(i) >= 32 for i in image) and os.path.exists(image):
83 image_type = "file"
84 image_data = open(image, "rb")
85 image_repr = "file:" + image
86 else:
87 image_type = "base64"
88 image_data = image_data.encode(image_type)
89 image_repr = "<binary data>"
90
91
92 fields = [
93 ("key", self.api_key),
94 ("type", image_type),
95 ("image", image_data),
96 ("name", name or hashlib.md5(str(image)).hexdigest()),
97 ]
98 handle = http.HttpPost(self.UPLOAD_URL, fields, mock_http=self.mock_http)
99
100 response = handle.send()
101 if response.status >= 300:
102 LOG.warn("Image %s upload failed with result %d %s" % (image_repr, response.status, response.reason))
103 else:
104 LOG.debug("Image %s uploaded with result %d %s" % (image_repr, response.status, response.reason))
105 body = response.read()
106 LOG.debug("Response size: %d" % len(body))
107 LOG.debug("Response headers:\n %s" % "\n ".join([
108 "%s: %s" % item for item in response.getheaders()
109 ]))
110
111 try:
112 result = json.loads(body)
113 except (ValueError, TypeError), exc:
114 raise httplib.HTTPException("Bad JSON data from imgur upload%s [%s]: %s" % (
115 ", looking like a CAPTCHA challenge" if "captcha" in body else "",
116 exc, logutil.shorten(body)))
117
118 if "error" in result:
119 raise httplib.HTTPException("Error response from imgur.com: %(message)s" % result["error"], result)
120
121 return parts.Bunch([(key, parts.Bunch(val))
122 for key, val in result["upload"].items()
123 ])
124
125
127 """ Return a 'fake' upload data record, so that upload errors
128 can be mitigated by using an original / alternative URL.
129 """
130 return parts.Bunch(
131 image=parts.Bunch(
132 animated='false', bandwidth=0, caption=None, views=0, deletehash=None, hash=None,
133 name=(url.rsplit('/', 1) + [url])[1], title=None, type='image/*', width=0, height=0, size=0,
134 datetime=fmt.iso_datetime(),
135 ),
136 links=parts.Bunch(
137 delete_page=None, imgur_page=None,
138 original=url, large_thumbnail=url, small_square=url,
139 ))
140
141
143 """ Call uploader and cache its results.
144 """
145 use_cache = True
146 if "use_cache" in kwargs:
147 use_cache = kwargs["use_cache"]
148 del kwargs["use_cache"]
149
150 json_path = None
151 if cache_dir:
152 json_path = os.path.join(cache_dir, "cached-img-%s.json" % cache_key)
153 if use_cache and os.path.exists(json_path):
154 LOG.info("Fetching %r from cache..." % (args,))
155 try:
156 with closing(open(json_path, "r")) as handle:
157 img_data = json.load(handle)
158
159 return parts.Bunch([(key, parts.Bunch(val))
160 for key, val in img_data.items()
161 ])
162 except (EnvironmentError, TypeError, ValueError), exc:
163 LOG.warn("Problem reading cached data from '%s', ignoring cache... (%s)" % (json_path, exc))
164
165 LOG.info("Copying %r..." % (args,))
166 img_data = uploader(*args, **kwargs)
167
168 if json_path:
169 with closing(open(json_path, "w")) as handle:
170 json.dump(img_data, handle)
171
172 return img_data
173
174
176 """ Copy image from given URL and return upload metadata.
177 """
178 return cache_image_data(cache_dir, hashlib.sha1(url).hexdigest(), ImgurUploader().upload, url, use_cache=use_cache)
179
180
182 """ Command line interface for testing.
183 """
184 import pprint
185 import tempfile
186
187 try:
188 image = sys.argv[1]
189 except IndexError:
190 print("Usage: python -m pyrobase.webservice.imgur <url>")
191 else:
192 try:
193 pprint.pprint(copy_image_from_url(image, cache_dir=tempfile.gettempdir()))
194 except UploadError, exc:
195 print("Upload error. %s" % exc)
196
197
198
199
200 if __name__ == "__main__":
201 _main()
202