ImageMinify.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #! /usr/bin/env node
  2. `use strict`
  3. var fs = require('fs-extra');
  4. var os = require('os');
  5. var path = require('path');
  6. var exec = require('child_process').exec;
  7. var program = require('commander');
  8. var readlineSync = require('readline-sync');
  9. var ProgressBar = require('progress');
  10. program
  11. .option('-r, --removeOrigin', 'Auto remove origin image when jobs done')
  12. .option('-s, --silence', 'Run in background')
  13. .option('-q, --quality <quality>', 'Output image quality.Default is 85')
  14. .option('-e, --ext <ext>', 'Output file extension.Support jpg,png,auto.Default is auto')
  15. .option('-E, --exceptExt <exceptExt>', 'Special extension will not be minified but move(copy) into output.Different ext delimit by ","')
  16. .option('-F, --exceptFolder <exceptFolder>', 'Images in excepted folder(name) will not be minified')
  17. .option('-o, --output <output>', 'Output folder path.Default is current image path with a subfolder named minified')
  18. .option('-w, --worker <worker>', 'Max minify worker at the same time.Default is max CPU Core Number')
  19. .usage('-q [Quality] -e [Extension] -o [Output Path] Path...')
  20. .arguments('[path...]')
  21. .parse(process.argv);
  22. var Config = {
  23. path : program.args[0],
  24. quality : program.quality,
  25. output : program.output,
  26. ext : program.ext,
  27. exceptExt : program.exceptExt,
  28. exceptFolder : program.exceptPath,
  29. worker : program.worker,
  30. isFolder :false,
  31. currentDir : '',
  32. outputDir : ''
  33. }
  34. var ImageList = [],failureList = [],exceptExt = {},exceptFolder = {},workerCount = 0;
  35. var processBar;
  36. if(!Config.path) return program.outputHelp();
  37. exec('gm -version | grep -o -E \'^GraphicsMagick ([0-9]|\.)+\'',function(err,stdout,stderr){
  38. if(err || !stdout) return console.error('GraphicsMagick not installed!!!')
  39. checkPath();
  40. })
  41. function checkPath(){
  42. if(Config.exceptExt){
  43. let arr = Config.exceptExt.split(',');
  44. for(let ext of arr){
  45. exceptExt[ext] = 1;
  46. }
  47. }
  48. if(Config.exceptFolder){
  49. let arr = Config.exceptFolder.split(',');
  50. for(let folder of arr){
  51. exceptFolder[folder] = 1;
  52. }
  53. }
  54. exceptFolder['minified'] = 1;
  55. fs.stat(Config.path,function(err,stats){
  56. if(err || !stats) return console.error(`No such file or directory [${Config.path}]`)
  57. if(stats.isFile()){
  58. let ext = path.extname(Config.path);
  59. if(!ext.match(/^.(jpg|jpeg|png|gif|bmp|raw)$/gi)){
  60. return console.log("ImageMinify only support image with extension name in jpg,jpeg,png,gif,bmp or raw");
  61. }
  62. ImageList.push(Config.path);
  63. Config.currentDir = path.parse(Config.path).dir;
  64. prepareTask();
  65. }else{
  66. Config.isFolder = true;
  67. Config.currentDir = path.resolve(Config.path);
  68. analysisFolder(Config.currentDir);
  69. prepareTask();
  70. }
  71. });
  72. }
  73. function analysisFolder(readPath){
  74. let currentPath = readPath;
  75. let fileList = fs.readdirSync(currentPath);
  76. for(let fileName of fileList){
  77. let filePath = path.join(currentPath,fileName);
  78. let fileStat = fs.statSync(filePath);
  79. if(fileStat.isFile()){
  80. let ext = path.extname(fileName);
  81. if(ext.match(/jpg|jpeg|png|gif|bmp|raw/gi)){
  82. ImageList.push(filePath);
  83. }
  84. }else{
  85. if(exceptFolder[path.parse(filePath).name]) continue;
  86. analysisFolder(filePath);
  87. }
  88. }
  89. }
  90. function prepareTask(){
  91. fs.removeSync(`${Config.outputDir}/error.log`);
  92. if(!Config.output){
  93. if(program.silence){
  94. Config.output = 'minified';
  95. }else{
  96. Config.output = readlineSync.question('Output directory [minified] : ');
  97. if(!Config.output) Config.output = 'minified';
  98. }
  99. }
  100. Config.outputDir = path.resolve(Config.currentDir,Config.output);
  101. if(!Config.quality){
  102. if(program.silence){
  103. Config.quality = 85;
  104. }else{
  105. Config.quality = Number(readlineSync.question('Output quality [85] : '));
  106. if(!Config.quality) Config.quality = 85;
  107. }
  108. }
  109. if(!Config.ext){
  110. if(program.silence){
  111. Config.ext = 'auto';
  112. }else{
  113. Config.ext = readlineSync.question('Output extension,only support jpg,png,gif or auto [auto] : ');
  114. if(!Config.ext || !Config.ext.match(/^(jpg|png|gif)$/i)) Config.ext = 'auto';
  115. }
  116. }
  117. if(!Config.worker){
  118. Config.worker = os.cpus().length;
  119. // if(program.silence){
  120. // }else{
  121. // Config.worker = Number(readlineSync.question('Output quality [85] : '));
  122. // if(!Config.worker) Config.worker = os.cpus().length;
  123. // }
  124. }
  125. startTask();
  126. }
  127. function startTask(){
  128. if(!program.silence){
  129. console.log('-----------------------------------------------------------------');
  130. console.log(`[ Path ] ${Config.path}`);
  131. console.log(`[Folder] ${Config.isFolder}\t[Quality] ${Config.quality}\t[OutputExtension] ${Config.ext}\t[Amount] ${ImageList.length}\t[Worker] ${Config.worker}`);
  132. console.log('-----------------------------------------------------------------');
  133. console.log(`[OutputDirectory] ${Config.outputDir}`);
  134. console.log('-----------------------------------------------------------------');
  135. processBar = new ProgressBar('Minifying: [:bar][:current/:total] [:image]', { total: ImageList.length, width: 20, complete: '*' });
  136. }
  137. for(let i = 0;i<Config.worker;i++){
  138. minify();
  139. }
  140. }
  141. function minify(){
  142. // if(ImageList.length==0) return finished();
  143. if(ImageList.length==0) return;
  144. workerCount++;
  145. let image = ImageList.pop();
  146. let pathParse = path.parse(image);
  147. let imageDir = pathParse.dir;
  148. let fileName = pathParse.name;
  149. let subDir = imageDir.replace(Config.currentDir,'');
  150. let imgExt = pathParse.ext.replace('.','');
  151. let outputExt = (Config.ext=='auto')?imgExt:Config.ext;
  152. let outputDir = Config.outputDir + subDir;
  153. let outputFile = `${outputDir}/${fileName}.${outputExt}`;
  154. fs.mkdirpSync(outputDir);
  155. if(exceptExt[imgExt]){
  156. if(Config.removeOrigin){
  157. fs.moveSync(image,`${outputDir}/${fileName}.${imgExt}`);
  158. }else{
  159. fs.copySync(image,`${outputDir}/${fileName}.${imgExt}`);
  160. }
  161. if(!program.silence) processBar.total--;
  162. workerCount--;
  163. if(ImageList.length==1) return finished();
  164. return minify();
  165. }
  166. exec(`export OMP_NUM_THREADS=4 && gm convert -strip -format '${outputExt}' -quality ${Config.quality} -depth 8 "${image}" "${outputFile}"`,function(err,stdout,stderr){
  167. if(err || stdout || stderr){
  168. // console.log(err,stdout,stderr)
  169. failureList.push(image);
  170. }else{
  171. if(program.removeOrigin) fs.removeSync(image);
  172. }
  173. if(!program.silence) processBar.tick(1,{'image':`${subDir}/${fileName}.${imgExt}`});
  174. workerCount--;
  175. if(workerCount==0 && ImageList.length==0) return finished();
  176. minify();
  177. });
  178. }
  179. function finished(){
  180. if(!program.silence) console.log('\nAll images minified');
  181. if(!failureList.length) return;
  182. let log = '';
  183. log += ('\n-----------------------------------------------------------------\n');
  184. log += ('These image(s) minified failed!');
  185. log += ('-----------------------------------------------------------------\n');
  186. for(let image of failureList){
  187. log += image + '\n';
  188. }
  189. log += ('-----------------------------------------------------------------\n');
  190. fs.writeFileSync(`${Config.outputDir}/error.log`,log,'UTF-8');
  191. console.error(log);
  192. }