ImageMinify.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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 file 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 = [],OtherFileList = [],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. }else{
  84. OtherFileList.push(filePath);
  85. }
  86. }else{
  87. if(exceptFolder[path.parse(filePath).name]) continue;
  88. analysisFolder(filePath);
  89. }
  90. }
  91. }
  92. function prepareTask(){
  93. fs.removeSync(`${Config.outputDir}/error.log`);
  94. if(!Config.output){
  95. if(program.silence){
  96. Config.output = 'minified';
  97. }else{
  98. Config.output = readlineSync.question('Output directory [minified] : ');
  99. if(!Config.output) Config.output = 'minified';
  100. }
  101. }
  102. Config.outputDir = path.resolve(Config.currentDir,Config.output);
  103. if(!Config.quality){
  104. if(program.silence){
  105. Config.quality = 85;
  106. }else{
  107. Config.quality = Number(readlineSync.question('Output quality [85] : '));
  108. if(!Config.quality) Config.quality = 85;
  109. }
  110. }
  111. if(!Config.ext){
  112. if(program.silence){
  113. Config.ext = 'auto';
  114. }else{
  115. Config.ext = readlineSync.question('Output extension,only support jpg,png,gif or auto [auto] : ');
  116. if(!Config.ext || !Config.ext.match(/^(jpg|png|gif)$/i)) Config.ext = 'auto';
  117. }
  118. }
  119. if(!Config.worker){
  120. Config.worker = os.cpus().length;
  121. // if(program.silence){
  122. // }else{
  123. // Config.worker = Number(readlineSync.question('Output quality [85] : '));
  124. // if(!Config.worker) Config.worker = os.cpus().length;
  125. // }
  126. }
  127. startTask();
  128. }
  129. function startTask(){
  130. if(!program.silence){
  131. console.log('-----------------------------------------------------------------');
  132. console.log(`[ Path ] ${Config.path}`);
  133. console.log(`[Folder] ${Config.isFolder}\t[Quality] ${Config.quality}\t[OutputExtension] ${Config.ext}\t[Amount] ${ImageList.length}\t[Worker] ${Config.worker}`);
  134. console.log('-----------------------------------------------------------------');
  135. console.log(`[OutputDirectory] ${Config.outputDir}`);
  136. console.log('-----------------------------------------------------------------');
  137. processBar = new ProgressBar('Minifying: [:bar][:current/:total] [:image]', { total: ImageList.length, width: 20, complete: '*' });
  138. }
  139. for(let i = 0;i<Config.worker;i++){
  140. minify();
  141. }
  142. }
  143. function minify(){
  144. // if(ImageList.length==0) return finished();
  145. if(ImageList.length==0) return;
  146. workerCount++;
  147. let image = ImageList.pop();
  148. let pathParse = path.parse(image);
  149. let imageDir = pathParse.dir;
  150. let fileName = pathParse.name;
  151. let subDir = imageDir.replace(Config.currentDir,'');
  152. let imgExt = pathParse.ext.replace('.','');
  153. let outputExt = (Config.ext=='auto')?imgExt:Config.ext;
  154. let outputDir = Config.outputDir + subDir;
  155. let outputFile = `${outputDir}/${fileName}.${outputExt}`;
  156. fs.mkdirpSync(outputDir);
  157. if(exceptExt[imgExt] || !imgExt.match(/jpg|jpeg|png|gif|bmp|raw/gi)){
  158. if(program.removeOrigin){
  159. fs.moveSync(image,`${outputDir}/${fileName}.${imgExt}`,{overwrite:true});
  160. }else{
  161. fs.copySync(image,`${outputDir}/${fileName}.${imgExt}`,{overwrite:true});
  162. }
  163. if(!program.silence) processBar.total--;
  164. workerCount--;
  165. if(ImageList.length==1) return finished();
  166. return minify();
  167. }
  168. exec(`export OMP_NUM_THREADS=4 && gm convert -strip -format '${outputExt}' -quality ${Config.quality} -depth 8 "${image}" "${outputFile}"`,function(err,stdout,stderr){
  169. if(err || stdout || stderr){
  170. // console.log(err,stdout,stderr)
  171. failureList.push(image);
  172. }else{
  173. if(program.removeOrigin){
  174. try{
  175. fs.removeSync(image);
  176. }catch(e){}
  177. }
  178. }
  179. if(!program.silence) processBar.tick(1,{'image':`${subDir}/${fileName}.${imgExt}`});
  180. workerCount--;
  181. if(workerCount==0 && ImageList.length==0) return moveOrtherFiles();
  182. minify();
  183. });
  184. }
  185. function moveOrtherFiles(){
  186. if(OtherFileList.length==0) return finished();
  187. for(var filePath of OtherFileList){
  188. let pathParse = path.parse(filePath);
  189. let fileDir = pathParse.dir;
  190. let fileName = pathParse.name;
  191. let subDir = fileDir.replace(Config.currentDir,'');
  192. let fileExt = pathParse.ext.replace('.','');
  193. let outputDir = Config.outputDir + subDir;
  194. let outputFile = `${outputDir}/${fileName}.${fileExt}`;
  195. fs.mkdirpSync(outputDir);
  196. fs.moveSync(filePath,`${outputDir}/${fileName}.${fileExt}`);
  197. }
  198. finished();
  199. }
  200. function finished(){
  201. if(!program.silence) console.log('\nAll images minified');
  202. if(!failureList.length) return;
  203. let log = '';
  204. log += ('\n-----------------------------------------------------------------\n');
  205. log += ('These image(s) minified failed!');
  206. log += ('-----------------------------------------------------------------\n');
  207. for(let image of failureList){
  208. log += image + '\n';
  209. }
  210. log += ('-----------------------------------------------------------------\n');
  211. fs.writeFileSync(`${Config.outputDir}/error.log`,log,'UTF-8');
  212. console.error(log);
  213. }