Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

# -*- coding: utf-8 -*- 

 

# Licensed under the MIT license 

# http://opensource.org/licenses/mit-license.php 

 

# Copyright 2007, Frank Scholz <coherence@beebits.net> 

 

""" 

 

Covers by Amazon 

 

methods to retrieve covers/album art via the 

Amazon E-Commerce WebService v4 

http://docs.amazonwebservices.com/AWSECommerceService/2007-04-04/DG/ 

 

The licence agreement says something about only 

one request per second, so we need to serialize 

and delay the calls a bit. 

 

The AWSAccessKeyId supplied is _ONLY_ for the use 

in conjunction with Coherence, http://coherence.beebits.net 

 

If you use this library in your own software please 

apply for your own key @ http://www.amazon.com/webservices 

and follow the rules of their license. 

 

Especially you must add the following disclaimer in a place 

that is reasonably viewable by the user of your application: 

 

PLEASE KEEP IN MIND THAT SOME OF THE CONTENT THAT WE 

MAKE AVAILABLE TO YOU THROUGH THIS APPLICATION COMES 

FROM AMAZON WEB SERVICES. ALL SUCH CONTENT IS PROVIDED 

TO YOU "AS IS." THIS CONTENT AND YOUR USE OF IT 

ARE SUBJECT TO CHANGE AND/OR REMOVAL AT ANY TIME. 

 

Furthermore if you save any of the cover images you 

have to take care that they are stored no longer than 

a maximum of one month and requested then from Amazon 

again. 

 

""" 

import io 

import os 

import urllib.error 

import urllib.parse 

import urllib.request 

 

from lxml import etree 

from twisted.internet import defer 

from twisted.internet import reactor 

from twisted.web import client 

 

aws_server = {'de': 'de', 

              'jp': 'jp', 

              'ca': 'ca', 

              'uk': 'co.uk', 

              'fr': 'fr'} 

 

aws_artist_query = '&Operation=ItemSearch' \ 

                   '&SearchIndex=Music' 

 

aws_asin_query = '&Operation=ItemLookup' 

 

aws_response_group = '&ResponseGroup=Images' 

 

aws_ns = 'http://webservices.amazon.com/AWSECommerceService/2005-10-05' 

 

aws_image_size = {'large': 'LargeImage', 

                  'medium': 'MediumImage', 

                  'small': 'SmallImage'} 

 

 

class WorkQueue(object): 

    _instance_ = None  # Singleton 

    queue = [] 

    workers = [] 

    max_workers = 1 

 

    def __new__(cls, *args, **kwargs): 

        obj = getattr(cls, '_instance_', None) 

        if obj is not None: 

            return obj 

        else: 

            obj = super(WorkQueue, cls).__new__(cls, *args, **kwargs) 

            cls._instance_ = obj 

            obj.max_workers = kwargs.get('max_workers', 1) 

            obj.queue = [] 

            obj.workers = [] 

            return obj 

 

    def __init__(self, method, *args, **kwargs): 

        self.queue.append((method, args, kwargs)) 

        self.queue_run() 

 

    def queue_run(self): 

        if len(self.queue) == 0: 

            return 

        if len(self.workers) >= self.max_workers: 

            # print "WorkQueue - all workers busy" 

            return 

        work = self.queue.pop() 

        d = defer.maybeDeferred(work[0], *work[1], **work[2]) 

        self.workers.append(d) 

        d.addCallback(self.remove_from_workers, d) 

        d.addErrback(self.remove_from_workers, d) 

 

    def remove_from_workers(self, result, d): 

        self.workers.remove(d) 

        reactor.callLater(1, self.queue_run)  # a very,very weak attempt 

 

 

class CoverGetter(object): 

    """ 

    retrieve a cover image for a given ASIN, 

                               a TITLE or 

                               an ARTIST/TITLE combo 

 

    parameters are: 

 

        filename: where to save a received image 

                  if NONE the image will be passed to the callback 

        callback: a method to call with the filename 

                  or the image as a parameter 

                  after the image request and save was successful 

                  can be: 

                  - only a callable 

                  - a tuple with a callable, 

                      - optional an argument or a tuple of arguments 

                      - optional a dict with keyword arguments 

        not_found_callback: a method to call when the search at Amazon failed 

                  can be: 

                  - only a callable 

                  - a tuple with a callable, 

                      - optional an argument or a tuple of arguments 

                      - optional a dict with keyword arguments 

        locale:   which Amazon Webservice Server to use, defaults to .com 

        image_size: request the cover as large|medium|small image 

                    resolution seems to be in pixels for 

                    large: 500x500, medium: 160x160 and small: 75x75 

        asin: the Amazon Store Identification Number 

        artist: the artists name 

        title: the album title 

 

    if the filename extension and the received image extension differ, 

    the image is converted with PIL to the desired format 

    http://www.pythonware.com/products/pil/index.htm 

 

    """ 

 

    def __init__(self, filename, aws_key, callback=None, 

                 not_found_callback=None, 

                 locale=None, 

                 image_size='large', 

                 title=None, artist=None, asin=None): 

        self.aws_base_query = '/onca/xml?Service=AWSECommerceService' \ 

                              '&AWSAccessKeyId=%s' % aws_key 

 

        self.filename = filename 

        self.callback = callback 

        self._errcall = not_found_callback 

        self.server = 'http://ecs.amazonaws.%s' % aws_server.get(locale, 'com') 

        self.image_size = image_size 

 

        def sanitize(s): 

            if s is not None: 

                s = str(s.lower()) 

                s = s.replace(str('ä'), str('ae')) 

                s = s.replace(str('ö'), str('oe')) 

                s = s.replace(str('ü'), str('ue')) 

                s = s.replace(str('ß'), str('ss')) 

                if isinstance(s, str): 

                    s = s.encode('ascii', 'ignore') 

                else: 

                    s = s.decode('utf-8').encode('ascii', 'ignore') 

            return s 

 

        if asin is not None: 

            query = aws_asin_query + '&ItemId=%s' % urllib.parse.quote(asin) 

        elif artist is not None or title is not None: 

            query = aws_artist_query 

            if artist is not None: 

                artist = sanitize(artist) 

                query = '&'.join( 

                    (query, 'Artist=%s' % urllib.parse.quote(artist))) 

            if title is not None: 

                title = sanitize(title) 

                query = '&'.join( 

                    (query, 'Title=%s' % urllib.parse.quote(title))) 

        else: 

            raise KeyError( 

                "Please supply either asin, title " 

                "or artist and title arguments") 

        url = self.server + self.aws_base_query + aws_response_group + query 

        WorkQueue(self.send_request, url) 

 

    def send_request(self, url, *args, **kwargs): 

        # print "send_request", url 

        d = client.getPage(url) 

        d.addCallback(self.got_response) 

        d.addErrback(self.got_error, url) 

        return d 

 

    def got_image(self, result, convert_from='', convert_to=''): 

        # print "got_image" 

        if len(convert_from) and len(convert_to): 

            # print "got_image %d, convert to %s" % (len(result), convert_to) 

            try: 

                import Image 

 

                im = Image.open(io.StringIO(result)) 

                name, file_ext = os.path.splitext(self.filename) 

                self.filename = name + convert_to 

 

                im.save(self.filename) 

            except ImportError: 

                print("we need the Python Imaging Library " 

                      "to do image conversion") 

 

        if self.filename is None: 

            data = result 

        else: 

            data = self.filename 

 

        if self.callback is not None: 

            # print "got_image", self.callback 

            if isinstance(self.callback, tuple): 

                if len(self.callback) == 3: 

                    c, a, kw = self.callback 

                    if not isinstance(a, tuple): 

                        a = (a,) 

                    a = (data,) + a 

                    c(*a, **kw) 

                if len(self.callback) == 2: 

                    c, a = self.callback 

                    if isinstance(a, dict): 

                        c(data, **a) 

                    else: 

                        if not isinstance(a, tuple): 

                            a = (a,) 

                        a = (data,) + a 

                        c(*a) 

                if len(self.callback) == 1: 

                    c = self.callback 

                    c(data) 

            else: 

                self.callback(data) 

 

    def got_response(self, result): 

        convert_from = convert_to = '' 

        result = etree.fromstring(result) 

        image_tag = result.find( 

            './/{%s}%s' % (aws_ns, 

                           aws_image_size.get(self.image_size, 'large'))) 

        if image_tag is not None: 

            image_url = image_tag.findtext('{%s}URL' % aws_ns) 

            if self.filename is None: 

                d = client.getPage(image_url) 

            else: 

                _, file_ext = os.path.splitext(self.filename) 

                if file_ext == '': 

                    _, image_ext = os.path.splitext(image_url) 

                    if image_ext != '': 

                        self.filename = ''.join((self.filename, image_ext)) 

                else: 

                    _, image_ext = os.path.splitext(image_url) 

                    if image_ext != '' and file_ext != image_ext: 

                        # print "hmm, we need a conversion..." 

                        convert_from = image_ext 

                        convert_to = file_ext 

                if len(convert_to): 

                    d = client.getPage(image_url) 

                else: 

                    d = client.downloadPage(image_url, self.filename) 

            d.addCallback(self.got_image, convert_from=convert_from, 

                          convert_to=convert_to) 

            d.addErrback(self.got_error, image_url) 

        else: 

            if self._errcall is not None: 

                if isinstance(self._errcall, tuple): 

                    if len(self._errcall) == 3: 

                        c, a, kw = self._errcall 

                        if not isinstance(a, tuple): 

                            a = (a,) 

                        c(*a, **kw) 

                    if len(self._errcall) == 2: 

                        c, a = self._errcall 

                        if isinstance(a, dict): 

                            c(**a) 

                        else: 

                            if not isinstance(a, tuple): 

                                a = (a,) 

                            c(*a) 

                    if len(self._errcall) == 1: 

                        c = self._errcall 

                        c() 

                else: 

                    self._errcall() 

 

    def got_error(self, failure, url): 

        print("got_error", failure, url) 

 

 

if __name__ == '__main__': 

    from twisted.python import usage 

 

    class Options(usage.Options): 

        optParameters = [['artist', 'a', '', 'artist name'], 

                         ['title', 't', '', 'title'], 

                         ['asin', 's', '', 'ASIN'], 

                         ['filename', 'f', 'cover.jpg', 'filename'], 

                         ] 

 

    options = Options() 

    try: 

        options.parseOptions() 

    except usage.UsageError as errortext: 

        import sys 

 

        print('%s: %s' % (sys.argv[0], errortext)) 

        print('%s: Try --help for usage details.' % (sys.argv[0])) 

        sys.exit(1) 

 

    def got_it(filename, *args, **kwargs): 

        print("Mylady, it is an image and its name is", filename, args, kwargs) 

 

    aws_key = '1XHSE4FQJ0RK0X3S9WR2' 

    print(options['asin'], options['artist'], options['title']) 

    if len(options['asin']): 

        reactor.callWhenRunning(CoverGetter, options['filename'], aws_key, 

                                callback=got_it, asin=options['asin']) 

    elif len(options['artist']) and len(options['title']): 

        reactor.callWhenRunning(CoverGetter, options['filename'], aws_key, 

                                callback=got_it, artist=options['artist'], 

                                title=options['title']) 

 

    reactor.run()