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 |
| 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): |
| 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)) |
| 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]) |
| 334 |
| 335 if image.mode != overlay.mode or image.mode == 'P': |
| 336 overlay = overlay.convert('RGBA') |
| 337 image = image.convert('RGBA') |
| 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 |
| 351 |
| 352 return PIL.Image.blend(image, overlay, float(args[-1])) |
| 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 ) |
| 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): |
| 378 bands = [band.convert('F') for band in image.convert('RGBA').split()] |
| 379 color = map(float, color) |
| 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, |
| 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 ) |
| 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 |