Left: | ||
Right: |
OLD | NEW |
---|---|
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 | |
21 import packager | 24 import packager |
22 from packager import readMetadata, getMetadataPath, getDefaultFileName, getBuild Version, getTemplate, Files | 25 from packager import readMetadata, getMetadataPath, getDefaultFileName, getBuild Version, getTemplate, Files |
23 | 26 |
24 defaultLocale = 'en_US' | 27 defaultLocale = 'en_US' |
25 | 28 |
26 def getIgnoredFiles(params): | 29 def getIgnoredFiles(params): |
27 result = set(('store.description',)) | 30 result = set(('store.description',)) |
28 | 31 |
29 # Hack: ignore all lib subdirectories | 32 # Hack: ignore all lib subdirectories |
30 libDir = os.path.join(params['baseDir'], 'lib') | 33 libDir = os.path.join(params['baseDir'], 'lib') |
(...skipping 241 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
272 'pt': 'pt_PT', | 275 'pt': 'pt_PT', |
273 } | 276 } |
274 for chromeLocale, operaLocale in operaMapping.iteritems(): | 277 for chromeLocale, operaLocale in operaMapping.iteritems(): |
275 chromeFile = '_locales/%s/messages.json' % chromeLocale | 278 chromeFile = '_locales/%s/messages.json' % chromeLocale |
276 operaFile = '_locales/%s/messages.json' % operaLocale if operaLocale != No ne else None | 279 operaFile = '_locales/%s/messages.json' % operaLocale if operaLocale != No ne else None |
277 if chromeFile in files: | 280 if chromeFile in files: |
278 if operaFile != None: | 281 if operaFile != None: |
279 files[operaFile] = files[chromeFile] | 282 files[operaFile] = files[chromeFile] |
280 del files[chromeFile] | 283 del files[chromeFile] |
281 | 284 |
282 # Hack: Replace "Chrome" by "Opera" in the locales | 285 if params['type'] in ('opera', 'safari'): |
286 # Hack: Replace "Chrome" by "Opera" or "Safari" in the locales | |
283 for path, data in files.iteritems(): | 287 for path, data in files.iteritems(): |
284 if path.startswith("_locales/") and path.endswith("/messages.json"): | 288 if path.startswith("_locales/") and path.endswith("/messages.json"): |
285 files[path] = re.sub(r"\bChrome\b", "Opera", data) | 289 files[path] = re.sub(r"\bChrome\b", params['type'].capitalize(), data) |
286 | 290 |
287 def signBinary(zipdata, keyFile): | 291 def signBinary(zipdata, keyFile): |
288 import M2Crypto | 292 import M2Crypto |
289 if not os.path.exists(keyFile): | 293 if not os.path.exists(keyFile): |
290 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None) | 294 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None) |
291 key = M2Crypto.EVP.load_key(keyFile) | 295 key = M2Crypto.EVP.load_key(keyFile) |
292 key.sign_init() | 296 key.sign_init() |
293 key.sign_update(zipdata) | 297 key.sign_update(zipdata) |
294 return key.final() | 298 return key.final() |
295 | 299 |
296 def getPublicKey(keyFile): | 300 def getPublicKey(keyFile): |
297 import M2Crypto | 301 import M2Crypto |
298 return M2Crypto.EVP.load_key(keyFile).as_der() | 302 return M2Crypto.EVP.load_key(keyFile).as_der() |
299 | 303 |
300 def writePackage(outputFile, pubkey, signature, zipdata): | 304 def writePackage(outputFile, pubkey, signature, zipdata): |
301 if isinstance(outputFile, basestring): | 305 if isinstance(outputFile, basestring): |
302 file = open(outputFile, 'wb') | 306 file = open(outputFile, 'wb') |
303 else: | 307 else: |
304 file = outputFile | 308 file = outputFile |
305 if pubkey != None and signature != None: | 309 if pubkey != None and signature != None: |
306 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) | 310 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) |
307 file.write(pubkey) | 311 file.write(pubkey) |
308 file.write(signature) | 312 file.write(signature) |
309 file.write(zipdata) | 313 file.write(zipdata) |
310 | 314 |
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 | |
311 def createBuild(baseDir, type='chrome', outFile=None, buildNum=None, releaseBuil d=False, keyFile=None, experimentalAPI=False, devenv=False): | 435 def createBuild(baseDir, type='chrome', outFile=None, buildNum=None, releaseBuil d=False, keyFile=None, experimentalAPI=False, devenv=False): |
312 metadata = readMetadata(baseDir, type) | 436 metadata = readMetadata(baseDir, type) |
313 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) | 437 version = getBuildVersion(baseDir, metadata, releaseBuild, buildNum) |
314 | 438 |
315 if outFile == None: | 439 if outFile == None: |
316 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el se 'zip') | 440 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el se 'zip') |
317 | 441 |
318 params = { | 442 params = { |
319 'type': type, | 443 'type': type, |
320 'baseDir': baseDir, | 444 'baseDir': baseDir, |
321 'releaseBuild': releaseBuild, | 445 'releaseBuild': releaseBuild, |
322 'version': version, | 446 'version': version, |
323 'experimentalAPI': experimentalAPI, | 447 'experimentalAPI': experimentalAPI, |
324 'devenv': devenv, | 448 'devenv': devenv, |
325 'metadata': metadata, | 449 'metadata': metadata, |
326 } | 450 } |
327 | 451 |
328 files = Files(getPackageFiles(params), getIgnoredFiles(params), | 452 files = Files(getPackageFiles(params), getIgnoredFiles(params), |
329 process=lambda path, data: processFile(path, data, params)) | 453 process=lambda path, data: processFile(path, data, params)) |
330 files['manifest.json'] = createManifest(params) | 454 files['manifest.json'] = createManifest(params) |
331 if metadata.has_section('mapping'): | 455 if metadata.has_section('mapping'): |
332 files.readMappedFiles(metadata.items('mapping')) | 456 files.readMappedFiles(metadata.items('mapping')) |
333 files.read(baseDir) | 457 files.read(baseDir) |
334 | 458 |
335 if metadata.has_section('convert_js'): | 459 if metadata.has_section('convert_js'): |
336 convertJS(params, files) | 460 convertJS(params, files) |
337 | 461 |
462 if metadata.has_section('convert_img'): | |
463 ImageConverter().convert(params, files) | |
464 | |
338 if metadata.has_section('import_locales'): | 465 if metadata.has_section('import_locales'): |
339 importGeckoLocales(params, files) | 466 importGeckoLocales(params, files) |
340 | 467 |
341 if devenv: | 468 if devenv: |
342 files['devenvPoller__.js'] = createPoller(params) | 469 files['devenvPoller__.js'] = createPoller(params) |
343 | 470 |
344 if (metadata.has_option('general', 'backgroundScripts') and | 471 if (metadata.has_option('general', 'backgroundScripts') and |
345 'lib/info.js' in re.split(r'\s+', metadata.get('general', 'backgroundScrip ts')) and | 472 'lib/info.js' in re.split(r'\s+', metadata.get('general', 'backgroundScrip ts')) and |
346 'lib/info.js' not in files): | 473 'lib/info.js' not in files): |
347 files['lib/info.js'] = createInfoModule(params) | 474 files['lib/info.js'] = createInfoModule(params) |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
379 def shutdown_server(server): | 506 def shutdown_server(server): |
380 time.sleep(10) | 507 time.sleep(10) |
381 server.shutdown() | 508 server.shutdown() |
382 thread.start_new_thread(shutdown_server, (server,)) | 509 thread.start_new_thread(shutdown_server, (server,)) |
383 server.serve_forever() | 510 server.serve_forever() |
384 | 511 |
385 if connections[0] == 0: | 512 if connections[0] == 0: |
386 print 'Warning: No incoming connections, extension probably not active in th e browser yet' | 513 print 'Warning: No incoming connections, extension probably not active in th e browser yet' |
387 else: | 514 else: |
388 print 'Handled %i connection(s)' % connections[0] | 515 print 'Handled %i connection(s)' % connections[0] |
OLD | NEW |