index.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. "use strict";
  2. const fs = require("fs");
  3. const path = require("path");
  4. const minimist = require("minimist");
  5. const low = require("lowdb");
  6. const FileSync = require("lowdb/adapters/FileSync");
  7. const chokidar = require("chokidar");
  8. const hbjs = require("handbrake-js");
  9. const args = minimist(process.argv.slice(2), {
  10. alias: {
  11. h: "help",
  12. v: "version",
  13. c: "config",
  14. i: "ignoreInitial",
  15. t: "test"
  16. }
  17. });
  18. const version = () => {
  19. const pkg = require('./package.json');
  20. return console.log(pkg.version);
  21. }
  22. const help = () => {
  23. let message = `Usage:
  24. \tnpm run movies
  25. \tnpm run tvshows
  26. \tnpm run pr0n
  27. \tnpm run all
  28. \tnpm run help
  29. \tnpm run version\n`
  30. return console.log(message);
  31. }
  32. String.prototype.toTitleCase = function() {
  33. var i, j, str, lowers, uppers;
  34. str = this.replace(/([^\W_]+[^\s-]*) */g, function(txt) {
  35. return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  36. });
  37. // Certain minor words should be left lowercase unless
  38. // they are the first or last words in the string
  39. lowers = ['A', 'An', 'The', 'And', 'But', 'Or', 'For', 'Nor', 'As', 'At',
  40. 'By', 'For', 'From', 'In', 'Into', 'Near', 'Of', 'On', 'Onto', 'To', 'With'];
  41. for (i = 0, j = lowers.length; i < j; i++)
  42. str = str.replace(new RegExp('\\s' + lowers[i] + '\\s', 'g'),
  43. function(txt) {
  44. return txt.toLowerCase();
  45. });
  46. // Certain words such as initialisms or acronyms should be left uppercase
  47. uppers = ['Id', 'Tv'];
  48. for (i = 0, j = uppers.length; i < j; i++)
  49. str = str.replace(new RegExp('\\b' + uppers[i] + '\\b', 'g'),
  50. uppers[i].toUpperCase());
  51. return str;
  52. };
  53. if (args.version) return version();
  54. if (args.help || !args.config) return help();
  55. const ignoreInitial = (args.hasOwnProperty('ignoreInitial')) ? args.ignoreInitial : false;
  56. const test = (args.hasOwnProperty('test')) ? args.test : false;
  57. // get our paths
  58. const paths = require(args.config);
  59. // init our defaults
  60. const defaults = {
  61. "preset": "Fast 1080p30",
  62. "clean": {},
  63. "titlecase": false,
  64. "folder": false,
  65. "database": "data/db.json"
  66. }
  67. // setup watcher options
  68. const opts = {
  69. ignored: /(^|[\/\\])\..|([s|S]ample\.*)/,
  70. ignoreInitial: ignoreInitial,
  71. persistent: true,
  72. usePolling: true,
  73. interval: 10000,
  74. depth: 1,
  75. awaitWriteFinish: {
  76. stabilityThreshold: 3000,
  77. pollInterval: 1000
  78. },
  79. ignorePermissionErrors: false
  80. };
  81. // parse the paths to dirs
  82. const dirs = Object.keys(paths);
  83. // initialize watches and db
  84. let watches = []; // array of things to watch
  85. for (let d in dirs) { // loop the dirs
  86. let dir = dirs[d]; // pointer
  87. let options = Object.assign({}, defaults, paths[dir]); // baseline the options
  88. let adapter = new FileSync(options.database); // init our db adapter
  89. let db = low(adapter); // create a db connection
  90. db.defaults({
  91. files: []
  92. }).write(); // init the database
  93. for (let e in options.exts) { // loop the exts to watch
  94. let ext = options.exts[e]; // alias the ext
  95. watches.push(`${dir}/**/*.${ext}`); // push the watch
  96. }
  97. }
  98. const watcher = chokidar.watch(watches, opts); // init our watcher
  99. console.log('Watching', watches);
  100. // when a new file is added
  101. watcher.on('add', (file) => {
  102. let adapter,
  103. db,
  104. options,
  105. preset = 'Fast 1080p30',
  106. clean = {
  107. '.': ' '
  108. },
  109. ext = 'm4v',
  110. filename = path.parse(file).name,
  111. input = file,
  112. output = filename,
  113. titlecase = false,
  114. folder = false,
  115. destination = '';
  116. // determine the presets and clean object
  117. for (let i = 0, l = dirs.length; i < l; i++) {
  118. let dir = dirs[i]; // pointer to the dir
  119. if (file && dir && file.indexOf(dir) > -1) { // is this in this path?
  120. options = Object.assign({}, defaults, paths[dir]); // baseline the options
  121. adapter = new FileSync(options.database); // init the db adapter
  122. db = low(adapter); // connect the db
  123. let found = db.get('files').find({ input:file }).value(); // does it already exist?
  124. if (found && found.status && found.status === 'success') { // was it already processed?
  125. //console.log(`File ${file} has already been successfully processed.`);
  126. return; // break this loop
  127. } else if (!found) { // was it found?
  128. console.log(`File ${file} has been added for processing.`);
  129. db.get('files').push({ input:file, output:'', status:'', date:new Date() }).write(); // push onto the list an entry
  130. } else {
  131. console.log(`File ${file} has been added for re-processing.`);
  132. db.get('files').find({ input: file }).assign({ status:'', date:new Date() }).write(); // set the status to blank
  133. }
  134. preset = options.preset; // apply the specifics ...
  135. clean = options.clean; // ...
  136. ext = options.ext; // ...
  137. titlecase = !!options.titlecase; // ...
  138. folder = !!options.folder; // ...
  139. destination = options.destination; // ...
  140. break; // break the loop
  141. }
  142. }
  143. // clean the output name
  144. let cleanKeys = Object.keys(clean);
  145. for (let c in cleanKeys) {
  146. let key = cleanKeys[c];
  147. let val = clean[key];
  148. let re = new RegExp(key, 'gi');
  149. output = output.replace(re, val);
  150. }
  151. output = output.trim(); // trim the whitespace
  152. output = output.replace(/(\d{4})$/, `($1)`); // if there is a date at the end wrap it
  153. if (folder) { // do we have a sub folder option?
  154. let match = output.match(/^(.*?)?\./gm); // get the name for the file before the first .
  155. folder = (match && match.length > 0) ? match[0].slice(0, -1) : false; // get just the stuff before the dot ... or false
  156. }
  157. // do we have title case enabled?
  158. if (options.titlecase) output = output.toTitleCase(); // titlecase that string
  159. let target = destination + ((folder) ? '/' + folder : ''); // setup the target location
  160. output = target + '/' + output + '.' + ext; // update the new name
  161. db.get('files').find({
  162. input: file
  163. }).assign({
  164. output: output
  165. }).write(); // update database with output
  166. if (target && !fs.existsSync(target)) {
  167. console.log('Creating target directory:', target);
  168. fs.mkdirSync(target, {
  169. recursive: true
  170. });
  171. }
  172. console.log(`Processing "${input}" to "${output}" with "${preset}"`);
  173. // spawn handbrake
  174. if (!test) {
  175. hbjs.spawn({
  176. input: input,
  177. output: output,
  178. preset: preset
  179. }).on('start', (err) => {
  180. process.stdout.clearLine();
  181. process.stdout.cursorTo(0);
  182. process.stdout.write(`Processing "${input}" to "${output}" with "${preset}"`);
  183. }).on('error', (err) => {
  184. process.stdout.clearLine();
  185. process.stdout.cursorTo(0);
  186. process.stdout.write(`Processing ${output} error: ${err.message || err}.\n`);
  187. db.get('files').find({ input: file }).assign({ status: 'failure', date:new Date() }).write(); // update database with status
  188. }).on('progress', (progress) => {
  189. process.stdout.clearLine();
  190. process.stdout.cursorTo(0);
  191. process.stdout.write(`Processing ${output} - Percent complete: ${progress.percentComplete}, ETA: ${progress.eta}`);
  192. }).on('cancelled', () => {
  193. process.stdout.clearLine();
  194. process.stdout.cursorTo(0);
  195. process.stdout.write(`Processing ${output} cancelled.\n`);
  196. db.get('files').find({ input: file }).assign({ status: 'failure', date:new Date() }).write(); // update database with status
  197. }).on('complete', () => {
  198. process.stdout.clearLine();
  199. process.stdout.cursorTo(0);
  200. process.stdout.write(`Processing ${output} complete.\n`);
  201. db.get('files').find({ input: file }).assign({ status: 'success', date:new Date() }).write(); // update database with status
  202. });
  203. }
  204. }).on('change', (file) => {
  205. console.log(`File ${file} has been changed.`);
  206. }).on('unlink', (file) => {
  207. console.log(`File ${file} has been removed.`);
  208. let adapter, db, options;
  209. for (let i = 0, l = dirs.length; i < l; i++) {
  210. let dir = dirs[i]; // pointer to the dir
  211. if (file && dir && file.indexOf(dir) > -1) { // is this in this path?
  212. options = Object.assign({}, defaults, paths[dir]); // baseline the options
  213. adapter = new FileSync(options.database); // init the db adapter
  214. db = low(adapter); // connect the db
  215. db.get('files').remove({ input: file }).write(); // remove file form database
  216. }
  217. }
  218. }).on('error', (error) => {
  219. console.error(`Error: ${error}.`);
  220. });