Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 # coding: utf-8 | 1 # coding: utf-8 |
2 | 2 |
3 # This file is part of the Adblock Plus build tools, | 3 # This file is part of the Adblock Plus build tools, |
4 # Copyright (C) 2006-2013 Eyeo GmbH | 4 # Copyright (C) 2006-2013 Eyeo GmbH |
5 # | 5 # |
6 # Adblock Plus is free software: you can redistribute it and/or modify | 6 # Adblock Plus is free software: you can redistribute it and/or modify |
7 # it under the terms of the GNU General Public License version 3 as | 7 # it under the terms of the GNU General Public License version 3 as |
8 # published by the Free Software Foundation. | 8 # published by the Free Software Foundation. |
9 # | 9 # |
10 # Adblock Plus is distributed in the hope that it will be useful, | 10 # Adblock Plus is distributed in the hope that it will be useful, |
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of | 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 # GNU General Public License for more details. | 13 # GNU General Public License for more details. |
14 # | 14 # |
15 # You should have received a copy of the GNU General Public License | 15 # You should have received a copy of the GNU General Public License |
16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 |
18 import sys, os, re, json, struct | 18 import sys, os, re, json, struct |
19 from StringIO import StringIO | 19 from StringIO import StringIO |
20 | 20 |
21 import PIL.Image | |
22 import PIL.ImageMath | |
Wladimir Palant
2013/09/10 10:15:27
Please don't require these modules at the top leve
| |
23 | |
24 import packager | 21 import packager |
25 from packager import readMetadata, getMetadataPath, getDefaultFileName, getBuild Version, getTemplate, Files | 22 from packager import readMetadata, getMetadataPath, getDefaultFileName, getBuild Version, getTemplate, Files |
26 | 23 |
27 defaultLocale = 'en_US' | 24 defaultLocale = 'en_US' |
28 | 25 |
29 def getIgnoredFiles(params): | 26 def getIgnoredFiles(params): |
30 result = set(('store.description',)) | 27 result = set(('store.description',)) |
31 | 28 |
32 # Hack: ignore all lib subdirectories | 29 # Hack: ignore all lib subdirectories |
33 libDir = os.path.join(params['baseDir'], 'lib') | 30 libDir = os.path.join(params['baseDir'], 'lib') |
34 for file in os.listdir(libDir): | 31 for file in os.listdir(libDir): |
35 if os.path.isdir(os.path.join(libDir, file)): | 32 if os.path.isdir(os.path.join(libDir, file)): |
36 result.add(file) | 33 result.add(file) |
37 return result | 34 return result |
38 | 35 |
39 def getPackageFiles(params): | 36 def getPackageFiles(params): |
40 result = set(('_locales', 'icons', 'jquery-ui', 'lib', 'skin', 'ui',)) | 37 result = set(('_locales', 'icons', 'jquery-ui', 'lib', 'skin', 'ui', 'ext')) |
41 | 38 |
42 if params['devenv']: | 39 if params['devenv']: |
43 result.add('qunit') | 40 result.add('qunit') |
44 | 41 |
45 baseDir = params['baseDir'] | 42 baseDir = params['baseDir'] |
46 for file in os.listdir(baseDir): | 43 for file in os.listdir(baseDir): |
47 if file.endswith('.js') or file.endswith('.html') or file.endswith('.xml'): | 44 if file.endswith('.js') or file.endswith('.html') or file.endswith('.xml'): |
48 result.add(file) | 45 result.add(file) |
49 return result | 46 return result |
50 | 47 |
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
275 'pt': 'pt_PT', | 272 'pt': 'pt_PT', |
276 } | 273 } |
277 for chromeLocale, operaLocale in operaMapping.iteritems(): | 274 for chromeLocale, operaLocale in operaMapping.iteritems(): |
278 chromeFile = '_locales/%s/messages.json' % chromeLocale | 275 chromeFile = '_locales/%s/messages.json' % chromeLocale |
279 operaFile = '_locales/%s/messages.json' % operaLocale if operaLocale != No ne else None | 276 operaFile = '_locales/%s/messages.json' % operaLocale if operaLocale != No ne else None |
280 if chromeFile in files: | 277 if chromeFile in files: |
281 if operaFile != None: | 278 if operaFile != None: |
282 files[operaFile] = files[chromeFile] | 279 files[operaFile] = files[chromeFile] |
283 del files[chromeFile] | 280 del files[chromeFile] |
284 | 281 |
285 if params['type'] in ('opera', 'safari'): | |
286 # Hack: Replace "Chrome" by "Opera" or "Safari" in the locales | |
287 for path, data in files.iteritems(): | |
288 if path.startswith("_locales/") and path.endswith("/messages.json"): | |
289 files[path] = re.sub(r"\bChrome\b", params['type'].capitalize(), data) | |
290 | |
291 def signBinary(zipdata, keyFile): | 282 def signBinary(zipdata, keyFile): |
292 import M2Crypto | 283 import M2Crypto |
293 if not os.path.exists(keyFile): | 284 if not os.path.exists(keyFile): |
294 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None) | 285 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None) |
295 key = M2Crypto.EVP.load_key(keyFile) | 286 key = M2Crypto.EVP.load_key(keyFile) |
296 key.sign_init() | 287 key.sign_init() |
297 key.sign_update(zipdata) | 288 key.sign_update(zipdata) |
298 return key.final() | 289 return key.final() |
299 | 290 |
300 def getPublicKey(keyFile): | 291 def getPublicKey(keyFile): |
301 import M2Crypto | 292 import M2Crypto |
302 return M2Crypto.EVP.load_key(keyFile).as_der() | 293 return M2Crypto.EVP.load_key(keyFile).as_der() |
303 | 294 |
304 def writePackage(outputFile, pubkey, signature, zipdata): | 295 def writePackage(outputFile, pubkey, signature, zipdata): |
305 if isinstance(outputFile, basestring): | 296 if isinstance(outputFile, basestring): |
306 file = open(outputFile, 'wb') | 297 file = open(outputFile, 'wb') |
307 else: | 298 else: |
308 file = outputFile | 299 file = outputFile |
309 if pubkey != None and signature != None: | 300 if pubkey != None and signature != None: |
310 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) | 301 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) |
311 file.write(pubkey) | 302 file.write(pubkey) |
312 file.write(signature) | 303 file.write(signature) |
313 file.write(zipdata) | 304 file.write(zipdata) |
314 | 305 |
315 class ImageConverter(object): | |
316 def convert(self, params, files): | |
Wladimir Palant
2013/09/10 10:15:27
Does this need to be an object? You are never real
Sebastian Noack
2013/09/10 12:40:43
I'm using self to access the filters. Yes, I could
Felix Dahlke
2013/09/11 14:44:35
I'd vote for having this as a bunch of free functi
| |
317 for filename, chain in params['metadata'].items('convert_img'): | |
318 steps = re.split(r'\s*->\s*', chain) | |
319 image = PIL.Image.open(steps.pop(0)) | |
Wladimir Palant
2013/09/10 10:15:27
This path should be relative to the metadata file
| |
320 | |
321 for step in steps: | |
322 filter, args = re.match(r'([^(]+)(?:\((.*)\))?', step).groups() | |
323 args = tuple(re.split(r'\s*,\s*', args)) if args else () | |
324 image = getattr(self, 'filter_' + filter)(image, *args) | |
325 | |
326 f = StringIO() | |
327 f.name = filename | |
328 image.save(f) | |
329 files[filename] = f.getvalue() | |
330 | |
331 def filter_blend(self, image, *args): | |
332 if len(args) == 2: # args = (filename, opacity) | |
333 overlay = PIL.Image.open(args[0]) | |
Wladimir Palant
2013/09/10 10:15:27
This file name should be resolved relative to the
| |
334 | |
335 if image.mode != overlay.mode or image.mode == 'P': | |
Wladimir Palant
2013/09/10 10:15:27
Please comment here that 'P' means "palette" (whic
| |
336 overlay = overlay.convert('RGBA') | |
337 image = image.convert('RGBA') | |
Wladimir Palant
2013/09/10 10:15:27
Style nit: we don't usually align equal signs and
| |
338 elif len(args) == 4: # args = (red, green, blue, opacity) | |
339 overlay = PIL.Image.new('RGB', image.size, tuple(map(int, args[:3]))) | |
340 | |
341 if image.mode == 'P': | |
342 image = image.convert('RGBA') | |
343 | |
344 if image.mode in ('RGBA', 'LA'): | |
345 overlay = PIL.Image.merge('RGBA', overlay.split() + image.split()[-1:]) | |
346 | |
347 if image.mode != overlay.mode: | |
348 image = image.convert(overlay.mode) | |
349 else: | |
350 raise TypeError | |
Wladimir Palant
2013/09/10 10:15:27
How about:
raise TypeError("Wrong number of b
| |
351 | |
352 return PIL.Image.blend(image, overlay, float(args[-1])) | |
Wladimir Palant
2013/09/10 10:15:27
I suggest processing the opacity parameter when yo
| |
353 | |
354 def filter_grayscale(self, image): | |
355 if image.mode == 'P': | |
356 image = image.convert('RGBA') | |
357 | |
358 bands = list(image.split()) | |
359 alpha = bands.pop(-1) if image.mode in ('RGBA', 'LA') else None | |
360 | |
361 if len(bands) == 1: | |
362 return image | |
363 | |
364 new_image = PIL.ImageMath.eval( | |
365 "convert((%s) / %d, 'L')" % ( | |
366 ' + '.join('image%d' % i for i in xrange(len(bands))), | |
367 len(bands) | |
368 ), | |
369 **dict(('image%d' % i, band) for i, band in enumerate(bands)) | |
370 ) | |
Wladimir Palant
2013/09/10 10:15:27
Won't image.convert("LA", dither=None) do the righ
| |
371 | |
372 if alpha: | |
373 new_image = PIL.Image.merge('LA', [new_image, alpha]) | |
374 | |
375 return new_image | |
376 | |
377 def filter_colorToAlpha(self, image, *color): | |
Wladimir Palant
2013/09/10 10:15:27
I cannot really imagine what we would use this for
Sebastian Noack
2013/09/10 12:40:43
Safari ignores all image data except the alpha cha
| |
378 bands = [band.convert('F') for band in image.convert('RGBA').split()] | |
379 color = map(float, color) | |
Wladimir Palant
2013/09/10 14:02:12
Shouldn't we verify that three colors are given an
Sebastian Noack
2013/09/10 16:50:05
I'm not a big fan of adding extra code to throw an
Felix Dahlke
2013/09/11 14:44:35
I agree somewhat. However, in this case, I think i
| |
380 | |
381 # Find the maximum difference rate between source and color. I had to use tw o | |
382 # difference functions because ImageMath.eval only evaluates the expression | |
383 # once. | |
384 alpha = PIL.ImageMath.eval('''\ | |
385 float( | |
386 max( | |
387 max( | |
388 max( | |
389 difference1(red_band, cred_band), | |
390 difference1(green_band, cgreen_band) | |
391 ), | |
392 difference1(blue_band, cblue_band) | |
393 ), | |
394 max( | |
395 max( | |
396 difference2(red_band, cred_band), | |
397 difference2(green_band, cgreen_band) | |
398 ), | |
399 difference2(blue_band, cblue_band) | |
400 ) | |
401 ) | |
402 )''', | |
403 difference1=lambda source, col: (source - col) / (255.0 - col), | |
404 difference2=lambda source, col: (col - source) / col, | |
Wladimir Palant
2013/09/10 14:02:12
I have a pretty hard time judging whether this alg
Sebastian Noack
2013/09/10 16:50:05
Yes you could do that in plain python instead of w
Wladimir Palant
2013/09/11 06:41:36
Given the complexity of this implementation I'm go
Felix Dahlke
2013/09/11 14:44:35
I prefer the approach suggested by Wladimir for th
Sebastian Noack
2013/09/11 15:48:06
I still don't see how not using PIL.ImageMath woul
Wladimir Palant
2013/09/11 16:14:56
As I said, converting to greyscale and taking any
| |
405 | |
406 red_band = bands[0], | |
407 green_band= bands[1], | |
408 blue_band = bands[2], | |
409 | |
410 cred_band = color[0], | |
411 cgreen_band= color[1], | |
412 cblue_band = color[2], | |
413 ) | |
Wladimir Palant
2013/09/10 14:02:12
Please indicate the source of your code whenever y
| |
414 | |
415 # Calculate the new image colors after the removal of the selected color | |
416 new_bands = [ | |
417 PIL.ImageMath.eval( | |
418 "convert((image - color) / alpha + color, 'L')", | |
419 image=band, | |
420 color=col, | |
421 alpha=alpha | |
422 ) | |
423 for band, col in zip(bands, color) | |
424 ] | |
425 | |
426 # Add the new alpha band | |
427 new_bands.append(PIL.ImageMath.eval( | |
428 "convert(alpha_band * alpha, 'L')", | |
429 alpha = alpha, | |
430 alpha_band = bands[3] | |
431 )) | |
432 | |
433 return PIL.Image.merge('RGBA', new_bands) | |
434 | |
435 def createBuild(baseDir, type='chrome', outFile=None, buildNum=None, releaseBuil d=False, keyFile=None, experimentalAPI=False, devenv=False): | 306 def createBuild(baseDir, type='chrome', outFile=None, buildNum=None, releaseBuil d=False, keyFile=None, experimentalAPI=False, devenv=False): |
436 metadata = readMetadata(baseDir, type) | 307 metadata = readMetadata(baseDir, type) |
437 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) | 308 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) |
438 | 309 |
439 if outFile == None: | 310 if outFile == None: |
440 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el se 'zip') | 311 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el se 'zip') |
441 | 312 |
442 params = { | 313 params = { |
443 'type': type, | 314 'type': type, |
444 'baseDir': baseDir, | 315 'baseDir': baseDir, |
445 'releaseBuild': releaseBuild, | 316 'releaseBuild': releaseBuild, |
446 'version': version, | 317 'version': version, |
447 'experimentalAPI': experimentalAPI, | 318 'experimentalAPI': experimentalAPI, |
448 'devenv': devenv, | 319 'devenv': devenv, |
449 'metadata': metadata, | 320 'metadata': metadata, |
450 } | 321 } |
451 | 322 |
452 files = Files(getPackageFiles(params), getIgnoredFiles(params), | 323 files = Files(getPackageFiles(params), getIgnoredFiles(params), |
453 process=lambda path, data: processFile(path, data, params)) | 324 process=lambda path, data: processFile(path, data, params)) |
454 files['manifest.json'] = createManifest(params) | 325 files['manifest.json'] = createManifest(params) |
455 if metadata.has_section('mapping'): | 326 if metadata.has_section('mapping'): |
456 files.readMappedFiles(metadata.items('mapping')) | 327 files.readMappedFiles(metadata.items('mapping')) |
457 files.read(baseDir) | 328 files.read(baseDir) |
458 | 329 |
459 if metadata.has_section('convert_js'): | 330 if metadata.has_section('convert_js'): |
460 convertJS(params, files) | 331 convertJS(params, files) |
461 | 332 |
462 if metadata.has_section('convert_img'): | 333 if metadata.has_section('convert_img'): |
463 ImageConverter().convert(params, files) | 334 from imageConversion import convertImages |
335 convertImages(params, files) | |
336 | |
337 if metadata.has_section('preprocess'): | |
338 files.preprocess( | |
339 [f for f, _ in metadata.items('preprocess')], | |
340 {'needsExt': True} | |
341 ) | |
464 | 342 |
465 if metadata.has_section('import_locales'): | 343 if metadata.has_section('import_locales'): |
466 importGeckoLocales(params, files) | 344 importGeckoLocales(params, files) |
467 | 345 |
468 if devenv: | 346 if devenv: |
469 files['devenvPoller__.js'] = createPoller(params) | 347 files['devenvPoller__.js'] = createPoller(params) |
470 | 348 |
471 if (metadata.has_option('general', 'backgroundScripts') and | 349 if (metadata.has_option('general', 'backgroundScripts') and |
472 'lib/info.js' in re.split(r'\s+', metadata.get('general', 'backgroundScrip ts')) and | 350 'lib/info.js' in re.split(r'\s+', metadata.get('general', 'backgroundScrip ts')) and |
473 'lib/info.js' not in files): | 351 'lib/info.js' not in files): |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
506 def shutdown_server(server): | 384 def shutdown_server(server): |
507 time.sleep(10) | 385 time.sleep(10) |
508 server.shutdown() | 386 server.shutdown() |
509 thread.start_new_thread(shutdown_server, (server,)) | 387 thread.start_new_thread(shutdown_server, (server,)) |
510 server.serve_forever() | 388 server.serve_forever() |
511 | 389 |
512 if connections[0] == 0: | 390 if connections[0] == 0: |
513 print 'Warning: No incoming connections, extension probably not active in th e browser yet' | 391 print 'Warning: No incoming connections, extension probably not active in th e browser yet' |
514 else: | 392 else: |
515 print 'Handled %i connection(s)' % connections[0] | 393 print 'Handled %i connection(s)' % connections[0] |
LEFT | RIGHT |