├── .gitignore ├── README.md ├── cli.js ├── lib ├── FileUtil.js ├── persist.js └── smushit.js ├── package.json ├── persist.json ├── smushit.js └── test └── persist-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | persist.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-smushit 2 | ===== 3 | 4 | using smush.it service to optimize image(s) in node 5 | 6 | How to use? 7 | ------------ 8 | 9 | install by NPM 10 | 11 | ```shell 12 | npm install node-smushit -g 13 | ``` 14 | 15 | use smushit in shell 16 | 17 | ```shell 18 | //view help 19 | smushit -h 20 | 21 | //smash files or directory 22 | smushit file1 file2 file3 23 | 24 | //with recursive 25 | smushit file1 file2 file3 -R 26 | 27 | 28 | //smash file and save by new name 29 | smushit image-file-name -o new-file-name 30 | 31 | 32 | /** 33 | * change the default service. 34 | * by default, `node-smushit` use the !Yahoo smushi.it service, you can create your owne smush service. 35 | * If you are in China, you will know how important to create your own smush.it service. 36 | * FUCK THE GFW & POOR NETWORK SPEED. 37 | */ 38 | 39 | // view all config 40 | smushit -c 41 | 42 | //view service endpoint config, indicate the smush.it HTTP service 43 | smushit -c service 44 | 45 | //change the config 46 | smushit -c service=http://your-own-service-endpoint/ 47 | ``` 48 | 49 | use it in node 50 | 51 | ```javascript 52 | var smushit = require('node-smushit'); 53 | //smash a single file 54 | smushit.smushit('images/need-to-smash.png'); 55 | 56 | //smash files 57 | smushit.smushit(['file1', 'fiel2', ..]); 58 | 59 | //smash images in directory 60 | smushit.smushit('images-folder-path'); 61 | 62 | //smash images in directory or the child-directories with recursive 63 | smushit.smushit('images-folder-path', {recursive: true}); 64 | 65 | //smash images and register callbacks 66 | smushit.smushit('images-folder-path', { 67 | onItemStart: function(item){ 68 | 69 | }, 70 | onItemComplete: function(e, item, response){ 71 | 72 | }, 73 | onComplete: function(reports){ 74 | 75 | }, 76 | service: 'http://my-custom-domain-service/' 77 | }); 78 | ``` 79 | 80 | Changelog 81 | ------------ 82 | > v0.5.1 83 | * modify the lib/smushit.js, change the regexp `/.+\//` -> `/.+[\/\\]/` bugfix: wrong filename writed to the request body under Windows. 84 | 85 | > v0.5.0 86 | * modify the cli.js file to unix format for resolving issue #4 87 | 88 | > v0.4.0 89 | * change path.existsSync to fs.existsSync 90 | 91 | > v0.3.0 92 | * add custom smushit serivce feature 93 | * add global service config for CLI 94 | * can save the smashed file by new name 95 | 96 | > v0.2.0 97 | * add callback while the image(s) smashed completed 98 | 99 | > v0.1.0 100 | * smash by file, filelist, directory 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var smushit = require('./smushit') 4 | , util = require('util') 5 | , Persist = require('./lib/persist').Persist 6 | , persist = new Persist(__dirname + '/persist.json'); 7 | 8 | var responses = { 9 | error: function (message) { 10 | util.puts('Error occurred: ' + message); 11 | }, 12 | help: function () { 13 | util.puts([ 14 | 'Usage: smushit file1 file2 file3', 15 | 'Smash your images down to size. smushit uses !Yahoo online Smush.it web service', 16 | 'to reduce your image size.', 17 | '', 18 | '(Note: due to the way the args are parsed, two hyphens -- are required after', 19 | ' binary flags if they appear before file paths)', 20 | '', 21 | 'Options:', 22 | '', 23 | ' General:', 24 | ' -v, --verbose verbose mode', 25 | '', 26 | ' Traversing:', 27 | ' -R, --recursive scan directories recursively', 28 | '', 29 | ' Output:', 30 | ' -o, --output the path to save', 31 | '', 32 | ' Other:', 33 | ' -h, --help print this help page', 34 | ' --version print program version', 35 | '', 36 | ' Examples:', 37 | ' Single File', 38 | ' smushit image.png', 39 | '', 40 | ' Single Directory', 41 | ' smushit /var/www/mysite.com/images/products/backgrounds', 42 | '', 43 | ' Multiple Files', 44 | ' smushit foo.png bar.png baz.png qux.png', 45 | '', 46 | ' Recursive Directory', 47 | ' smushit -R -- /var/www/mysite.com/images', 48 | '' 49 | ].join('\n')); 50 | }, 51 | report: function () { 52 | 53 | }, 54 | version: function () { 55 | util.puts('smushit v0.3.0'); 56 | } 57 | }; 58 | 59 | function respond (type) { 60 | responses[type].call(); 61 | } 62 | 63 | var argv = require('optimist').argv; 64 | 65 | if (argv.help || argv.h) { 66 | respond('help'); 67 | } else if (argv.version) { 68 | respond('version'); 69 | } else if (argv.c || argv.config){ 70 | var s = argv.c || argv.config; 71 | 72 | if(s === true){ 73 | persist.each(function(key, value){ 74 | console.log(' smushit config: %s = %s ', key, value); 75 | }); 76 | return; 77 | } 78 | s = s.split('='); 79 | var key = s[0], 80 | value = s[1]; 81 | 82 | if(value == undefined){ 83 | console.log(' smushit config: %s = %s ', key, persist.getItem(key)); 84 | }else if(value === ''){ 85 | persist.removeItem(key); 86 | console.log(' smushit delete config key: %s', key); 87 | }else{ 88 | persist.setItem(key, value); 89 | console.log(' smushit config: %s = %s ', key, persist.getItem(key)); 90 | } 91 | }else if(argv._.length) { 92 | var settings = {}; 93 | 94 | if (argv.R || argv.recursive) { 95 | settings.recursive = true; 96 | } 97 | 98 | if (argv.v || argv.verbose) { 99 | settings.verbose = true; 100 | } 101 | 102 | if(argv.o || argv.output){ 103 | settings.output = argv.o || argv.output; 104 | } 105 | 106 | settings.service = persist.getItem('service'); 107 | 108 | smushit.smushit(argv._, settings); 109 | 110 | } else { 111 | respond('help'); 112 | } -------------------------------------------------------------------------------- /lib/FileUtil.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | exports.list = function(dir, options){ 4 | options = options || {}; 5 | dir = dir.replace(/(\/+)$/, ''); 6 | var excludeFile = options.excludeFile, 7 | excludeDirectory = options.excludeDirectory, 8 | matchFunction = options.matchFunction, 9 | recursive = true, 10 | arr = [], 11 | root = fs.statSync(dir), 12 | p; 13 | if(!root.isDirectory()){ 14 | return []; 15 | } 16 | 17 | p = fs.readdirSync(dir); 18 | if(options.recursive === false){ 19 | recursive = false; 20 | } 21 | for(var i = 0, l = p.length; i", 5 | 6 | "repository": { 7 | "type": "git", 8 | "url": "http://github.com/colorhook/node-smushit" 9 | }, 10 | "dependencies": { 11 | "optimist": ">= 0.2.6" 12 | }, 13 | "devDependencies": { 14 | "nodeunit": "~>0.6.4" 15 | }, 16 | "keywords": ["image", "compressor", "compression"], 17 | "directories" : { "lib" : "./lib" }, 18 | "bin" : { "smushit" : "./cli.js" }, 19 | "main" : "smushit", 20 | "engines": { "node": ">= 0.8.0" }, 21 | "licenses": [ 22 | { 23 | "type": "BSD", 24 | "url": "LICENSE" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /persist.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /smushit.js: -------------------------------------------------------------------------------- 1 | var smushit = require('./lib/smushit') 2 | , fs = require('fs') 3 | , url = require('url') 4 | , http = require('http') 5 | , FileUtil = require('./lib/FileUtil.js'); 6 | 7 | 8 | var saveBinary = function(binaryUrl, path, callback){ 9 | var urlObj = url.parse(binaryUrl), 10 | options = { 11 | host: urlObj.host 12 | , port: urlObj.port 13 | , path: urlObj.pathname 14 | } 15 | 16 | var request = http.get(options, function(res){ 17 | var data = ''; 18 | res.setEncoding('binary') 19 | res.on('data', function(chunk){ 20 | data += chunk; 21 | }) 22 | res.on('end', function(){ 23 | fs.writeFile(path, data, 'binary', callback) 24 | }) 25 | }); 26 | 27 | if(callback){ 28 | request.on("error", callback); 29 | } 30 | }; 31 | 32 | 33 | exports.smushit = function(inputs, settings){ 34 | var defaults = { 35 | verbose: true, 36 | inputs: [], 37 | recursive: false, 38 | verbose: true 39 | }; 40 | 41 | settings = settings || {}; 42 | 43 | for (key in defaults) { 44 | settings[key] = (typeof settings[key] !== 'undefined') ? settings[key] : defaults[key]; 45 | } 46 | 47 | // Normalize inputs to array if just given a string 48 | if(typeof inputs === 'string') { 49 | inputs = [inputs]; 50 | } 51 | 52 | var log = function(msg){ 53 | if(settings.verbose){ 54 | console.log("[smushit] " + msg); 55 | } 56 | }; 57 | 58 | var files = []; 59 | inputs.forEach(function(item){ 60 | if(!fs.existsSync(item)){ 61 | log("no such file or directory: " + item); 62 | return; 63 | } 64 | 65 | var stats = fs.statSync(item); 66 | if(stats.isFile()){ 67 | if(files.indexOf(item) == -1){ 68 | files.push(item); 69 | } 70 | }else if(stats.isDirectory()){ 71 | FileUtil.list(item, { 72 | recursive: settings.recursive, 73 | excludeDirectory: true, 74 | matchFunction: function(fileItem){ 75 | if(fileItem.name.match(/\.(png|gif|jpg|jpeg)/i)){ 76 | if(files.indexOf(fileItem.fullName) == -1){ 77 | files.push(fileItem.fullName); 78 | } 79 | return true; 80 | } 81 | return false; 82 | } 83 | }); 84 | } 85 | }); 86 | 87 | 88 | 89 | var finished = 0, 90 | reports = { 91 | total: files.length, 92 | finished: 0, 93 | ok: 0, 94 | items: [] 95 | }, 96 | onItemFinished = function(error, item, response){ 97 | if(settings.onItemComplete){ 98 | settings.onItemComplete(error, item, response); 99 | } 100 | if(++reports.finished >= files.length){ 101 | log("smushit completed: total " + reports.total + ", saving " + reports.ok ); 102 | if(settings.onComplete){ 103 | settings.onComplete(reports); 104 | } 105 | } 106 | }; 107 | 108 | if(files.length == 0){ 109 | log("please specify image(s) need to smushit!"); 110 | if(settings.onComplete){ 111 | settings.onComplete(reports); 112 | } 113 | return; 114 | }; 115 | 116 | files.forEach(function(item){ 117 | log("start smash " + item); 118 | if(settings.onItemStart){ 119 | settings.onItemStart(item); 120 | } 121 | smushit.smushit(item, function(response){ 122 | reports.items.push(response); 123 | try{ 124 | response = JSON.parse(response); 125 | }catch(err){ 126 | log("item: " + item + " response:" + response); 127 | onItemFinished(err, item, response); 128 | return; 129 | } 130 | if(response.error){ 131 | log("item: " + item + " error: " + response.error); 132 | onItemFinished(null, item, response); 133 | return; 134 | } 135 | reports.ok += 1; 136 | log("item: " + item + " saving: " + response.percent + "%"); 137 | 138 | saveBinary(response.dest, settings.output || item, function(e){ 139 | if(e){ 140 | log("Fail to save image at: " + item); 141 | } 142 | onItemFinished(e, item, response); 143 | }); 144 | }, function(error){ 145 | log(error.message || error.msg); 146 | onItemFinished(error, item); 147 | }, settings.service); 148 | }); 149 | }; -------------------------------------------------------------------------------- /test/persist-test.js: -------------------------------------------------------------------------------- 1 | var Persist = require('../lib/persist').Persist; 2 | var nodeunit = require("nodeunit"); 3 | 4 | var persist; 5 | 6 | exports['persist'] = nodeunit.testCase({ 7 | 8 | setUp: function(callback){ 9 | persist = new Persist(); 10 | callback(); 11 | }, 12 | 13 | tearDown: function(callback){ 14 | persist.clear(); 15 | callback(); 16 | }, 17 | 18 | "persist getItem": function(test){ 19 | test.equal(null, persist.getItem('a')); 20 | test.done(); 21 | }, 22 | 23 | "persist setItem & removeItem": function(test){ 24 | persist.clear(); 25 | test.equal(null, persist.getItem('a')); 26 | persist.setItem('a', 'A'); 27 | test.equal('A', persist.getItem('a')); 28 | persist.removeItem('a'); 29 | test.equal(null, persist.getItem('a')); 30 | test.done(); 31 | }, 32 | 33 | "persist clear": function(test){ 34 | test.equal(null, persist.getItem('a')); 35 | persist.setItem('a', 'A'); 36 | persist.setItem('b', 'B'); 37 | persist.setItem('c', 'C'); 38 | test.equal('A', persist.getItem('a')); 39 | test.equal('B', persist.getItem('b')); 40 | test.equal('C', persist.getItem('c')); 41 | persist.removeItem('a'); 42 | test.equal(null, persist.getItem('a')); 43 | test.equal('B', persist.getItem('b')); 44 | test.equal('C', persist.getItem('c')); 45 | persist.clear(); 46 | test.equal(null, persist.getItem('b')); 47 | test.equal(null, persist.getItem('c')); 48 | test.done(); 49 | }, 50 | 51 | "persist length": function(test){ 52 | persist.clear(); 53 | test.equal(0, persist.length); 54 | persist.setItem('a', 'A'); 55 | test.equal(1, persist.length); 56 | persist.setItem('b', 'B'); 57 | persist.setItem('c', 'C'); 58 | test.equal(3, persist.length); 59 | persist.removeItem('a'); 60 | test.equal(2, persist.length); 61 | persist.clear(); 62 | test.equal(0, persist.length); 63 | test.done(); 64 | }, 65 | 66 | "persist each": function(test){ 67 | persist.clear(); 68 | var m = { 69 | 'a': 'A', 70 | 'b': 'B', 71 | 'c': 'C' 72 | } 73 | persist.setItem('a', 'A'); 74 | persist.setItem('b', 'B'); 75 | persist.setItem('c', 'C'); 76 | persist.each(function(key, value){ 77 | test.equal(m[key], value); 78 | delete m[key]; 79 | }); 80 | var mKeyCount = 0; 81 | for(var i in m){ 82 | mKeyCount ++; 83 | } 84 | test.equal(0, mKeyCount); 85 | test.done(); 86 | } 87 | 88 | }); --------------------------------------------------------------------------------