From 30fdf148a880306f55941e533c76a21358aa55d4 Mon Sep 17 00:00:00 2001 From: Jordan Hotmann Date: Thu, 12 Apr 2018 09:52:56 -0600 Subject: [PATCH] Allow multiple RegEx options, width and height from EXIF data --- README.md | 13 ++++++- lib/replacements.js | 37 +++++++++++++++--- lib/userReplacements.js | 4 +- package-lock.json | 83 +++++++++++++++++++++++++---------------- package.json | 10 ++--- rename.js | 52 +++++++++++++++----------- 6 files changed, 129 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index e438e06..81e87c4 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The new file name can contain any number of variables that will be replaced with ```{{i}}``` Index: The index of the file when renaming multiple files. Parameters: starting index, default is 1. ```{{f}}``` File name: The original name of the file. Parameters: upper, lower, camel, pascal, or none for unmodified. - ```{{r}}``` RegEx First: The first match of the RegEx pattern specified in -r "...". + ```{{r}}``` RegEx: The match of the RegEx pattern(s) specified in -r "...". Parameters: the index of the regex match, default is 0. ```{{ra}}``` RegEx All: All matches of the RegEx pattern specified in -r "...". Parameters: separator character(s), default is none. ```{{rn}}``` RegEx Not: Everything except for the matches of the RegEx pattern specified in -r "...". Parameters: replacement character(s), default is none ```{{p}}``` Parent directory: The name of the parent directory. Parameters: upper, lower, camel, pascal, or none for unmodified. @@ -51,6 +51,8 @@ The new file name can contain any number of variables that will be replaced with ```{{efnum}}``` Exif FNumber: Photo FNumber value. ```{{eex}}``` Exif Exposure Time: Photo exposure time value. ```{{ed}}``` Exif Date: The date/time photo was taken. Parameters: date format, default is yyyymmdd. + ```{{eh}}``` Exif Height: The height in pixels of the photo + ```{{ew}}``` Exif Width: The width in pixels of the photo ### RegEx When you specify a RegEx pattern with the -r option, the regular expression will be run against the original file name and the first match will be used to replace {{r}} in the output file name. You can also use {{ra}} in the output file name to keep all matches separated by a string you supply as an argument (or no argument to just append all matches together). If the regular expression fails to match, an empty string will be returned. **DO NOT** include the forward slashes in your RegEx pattern. @@ -89,13 +91,20 @@ When you specify a RegEx pattern with the -r option, the regular expression will ExpenseReport - October 2015.pdf → 2015 - October Expense Report.pdf ``` -1. Use all RegEx matches in the output file name separated by a space. RegEx explaination: ```\w+``` captures a string of 1 or more word characters (A-Z, a-z, and _), ```(?=.+\d{4})``` is a forward lookahead for a number of 4 digits (this means it will only find words before the number), and then ```|``` or, ```\d{4}``` a number of 4 digits. +1. Use all RegEx matches in the output file name separated by a space. RegEx explaination: ```\w+``` captures a string of 1 or more word characters (A-Z, a-z, and _), ```(?=.+\d{4})``` is a forward lookahead for a number of 4 digits (this means it will only find words before the number), and then ```|``` which means 'or', and finally ```\d{4}``` a number of 4 digits. ```sh rename -r "\w+(?=.+\d{4})|\d{4}" My.File.With.Periods.2016.more.info.txt "{{ra| }}" My.File.With.Periods.2016.more.info.txt → My File With Periods 2016.txt ``` +1. Use multiple RegEx options with `{{rn}}` to filter out different parts of the input file name in order. RegEx and parameter explaination: first .2016. and all following characters are replaced due to the first RegEx rule `-r "\.\d{4}\..+"`, then we match just the year with the second RegEx rule for use later with `{{r|1}}`, and then all periods are replaced due to the third RegEx rule. Finally we add back the year inside parenthesis. Since JavaScript uses 0 as the first index of an array, 1 finds the second regex match which is just the year as specified by ` -r "\d{4}"`. + + ```sh + rename -r "\.\d{4}\..+" -r "\d{4}" -r "\." My.File.With.Periods.2016.more.info.txt "{{rn| }}({{r|1}})" + My.File.With.Periods.2016.more.info.txt → My File With Periods (2016).txt + ``` + 1. Extract Exif data from jpg images. ```sh diff --git a/lib/replacements.js b/lib/replacements.js index 379e625..65cf747 100644 --- a/lib/replacements.js +++ b/lib/replacements.js @@ -47,12 +47,17 @@ const replacements = { } }, 'r': { - name: 'RegEx First', - description: 'The first match of the RegEx pattern specified in -r "..."', + name: 'RegEx', + description: 'The specified match of the RegEx pattern(s) specified in -r "..."', + parameters: { + description: 'the number of the regex match, default is 0', + default: '0' + }, unique: false, - function: function(fileObj) { - if (fileObj.regexMatches) { - return fileObj.regexMatches[0]; + function: function(fileObj, arg) { + let matchNum = parseInt(arg); + if (fileObj.regexMatches && fileObj.regexMatches[matchNum]) { + return fileObj.regexMatches[matchNum]; } else { return ''; } @@ -85,7 +90,9 @@ const replacements = { unique: false, function: function(fileObj, args) { args = (args ? args : ''); - return fileObj.name.replace(fileObj.regexPattern, args); + let output = fileObj.name; + fileObj.regexPatterns.forEach((pattern) => { output = output.replace(pattern, args); }); + return output; } }, 'p': { @@ -232,6 +239,24 @@ const replacements = { let formattedDate = data.DateTime.split(/:|\s/)[1] + '/' + data.DateTime.split(/:|\s/)[2] + '/' + data.DateTime.split(/:|\s/)[0] + ' ' + data.DateTime.split(/:|\s/)[3] + ':' + data.DateTime.split(/:|\s/)[4] + ':' + data.DateTime.split(/:|\s/)[5]; return (typeof(data) === 'object' && data.DateTime ? dateFormat(formattedDate, args) : ''); } + }, + 'eh': { + name: 'Exif Height', + description: "The height in pixels of the photo", + unique: false, + function: function(fileObj) { + let data = getExifData(fileObj.dir + '/' + fileObj.base); + return (typeof(data) === 'object' && data.SubExif && data.SubExif.PixelYDimension ? data.SubExif.PixelYDimension : ''); + } + }, + 'ew': { + name: 'Exif Width', + description: "The width in pixels of the photo", + unique: false, + function: function(fileObj) { + let data = getExifData(fileObj.dir + '/' + fileObj.base); + return (typeof(data) === 'object' && data.SubExif && data.SubExif.PixelXDimension ? data.SubExif.PixelXDimension : ''); + } } }; diff --git a/lib/userReplacements.js b/lib/userReplacements.js index af10031..566ae63 100644 --- a/lib/userReplacements.js +++ b/lib/userReplacements.js @@ -80,11 +80,11 @@ newNameExt: '.ext', index: '29', regexMatches: ['match1', 'match2', ...], - regexPattern: /SomeRexEx/g, + regexPatterns: [/SomeRegEx/g], totalFiles: '50' } - Note: regexMatches and regexPattern are only present when the -r option is used, so be sure + Note: regexMatches and regexPatterns are only present when the -r option is used, so be sure to check if they exist before proceeding. You can then use these properties if desired in your functions. See the diff --git a/package-lock.json b/package-lock.json index 98d16f6..7506026 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "rename-cli", - "version": "5.0.1", + "version": "5.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,13 +10,14 @@ "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==" }, "ansi-regex": { - "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "1.9.1" } @@ -84,13 +85,13 @@ "integrity": "sha1-U7ai+BW7d7nChx97mnLDol8djok=" }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", "requires": { - "ansi-styles": "3.2.0", + "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "supports-color": "5.3.0" } }, "character-entities": { @@ -137,9 +138,9 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, "clipboardy": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.2.tgz", - "integrity": "sha512-16KrBOV7bHmHdxcQiCvfUFYVFyEah4FI8vYT1Fr7CGSA4G+xBWMEfUEQJS1hxeHGtI9ju1Bzs9uXSbj5HZKArw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", + "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", "requires": { "arch": "2.1.0", "execa": "0.8.0" @@ -172,6 +173,13 @@ "is-fullwidth-code-point": "1.0.0", "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" } + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "requires": { + "ansi-regex": "2.1.1" + } } } }, @@ -336,9 +344,9 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "hosted-git-info": { "version": "2.5.0", @@ -370,7 +378,7 @@ "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", "requires": { "ansi-escapes": "3.0.0", - "chalk": "2.3.0", + "chalk": "2.3.2", "cli-cursor": "2.1.0", "cli-width": "2.2.0", "external-editor": "2.1.0", @@ -660,9 +668,9 @@ } }, "opn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz", - "integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", "requires": { "is-wsl": "1.1.0" } @@ -797,9 +805,9 @@ } }, "prompt-sync": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.1.5.tgz", - "integrity": "sha1-cJrBgjiLDppKRbVoPtBEntGfPrg=" + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.1.6.tgz", + "integrity": "sha512-dYjDha0af2vefm6soqnPnFEz2tAzwH/kb+pPoaCohRoPUxFXj+mymkOFgxX7Ylv59TdEr7OzktEizdK7MIMvIw==" }, "pseudomap": { "version": "1.0.2", @@ -980,10 +988,10 @@ }, "string-width": { "version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz", - "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=", + "integrity": "sha512-w+YQpeOppRYnIHRftgHpjGYUj9m0XKeam1C4ahbh+vErWcY8JJCcrHi/YhUFhHoVeWADkhplCWYdYwX5Nmhiyw==", "requires": { "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + "strip-ansi": "3.0.1" } }, "stringify-entities": { @@ -998,10 +1006,11 @@ } }, "strip-ansi": { - "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + "ansi-regex": "2.1.1" } }, "strip-bom": { @@ -1015,11 +1024,11 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "requires": { - "has-flag": "2.0.0" + "has-flag": "3.0.0" } }, "through": { @@ -1142,7 +1151,7 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "1.0.2", - "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + "strip-ansi": "3.0.1" }, "dependencies": { "is-fullwidth-code-point": { @@ -1160,7 +1169,15 @@ "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + "strip-ansi": "3.0.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" } } } diff --git a/package.json b/package.json index 37bb486..08ea00b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rename-cli", - "version": "5.0.1", + "version": "5.1.0", "description": "A command line utility for renaming files", "main": "rename.js", "preferGlobal": true, @@ -20,9 +20,9 @@ }, "dependencies": { "blessed": "^0.1.81", - "chalk": "^2.3.0", + "chalk": "^2.3.2", "cli-clear": "^1.0.4", - "clipboardy": "^1.2.2", + "clipboardy": "^1.2.3", "dateformat": "^2.2.0", "fs-extra": "^2.0.0", "globby": "^6.1.0", @@ -30,9 +30,9 @@ "jpeg-exif": "^1.0.6", "named-js-regexp": "^1.3.3", "num2fraction": "^1.2.2", - "opn": "^5.1.0", + "opn": "^5.3.0", "path-exists": "^3.0.0", - "prompt-sync": "^4.1.5", + "prompt-sync": "^4.1.6", "remark": "^8.0.0", "yargs": "^8.0.2" }, diff --git a/rename.js b/rename.js index 4bebd13..85c286e 100644 --- a/rename.js +++ b/rename.js @@ -71,7 +71,7 @@ function getOperations(files, newFileName, options) { function argvToOptions(argv) { return { - regex: (argv.r ? argv.r : false), + regex: (argv.r ? (Array.isArray(argv.r) ? argv.r : [argv.r]) : false), keep: (argv.k ? true : false), force: (argv.f ? true : false), simulate: (argv.s ? true : false), @@ -223,32 +223,40 @@ function buildFileIndex(files, newFileName) { function regexMatch(fileObj, options) { let pattern; - try { - pattern = new RegExp(options.regex.replace(/\(\?\<\w+\>/g, '('), 'g'); - } catch (err) { - console.log(err.message); - process.exit(1); - } - fileObj.regexPattern = pattern; - fileObj.regexMatches = fileObj.name.match(pattern); + let patterns = []; + let matches = []; + options.regex.forEach((regex) => { + try { + pattern = new RegExp(regex.replace(/\(\?\<\w+\>/g, '('), 'g'); + } catch (err) { + console.log(err.message); + process.exit(1); + } + matches = matches.concat(fileObj.name.match(pattern)); + patterns.push(pattern); + }); + fileObj.regexPatterns = patterns; + fileObj.regexMatches = matches; return fileObj; } function regexGroupReplacement(fileObj, options) { - let groupNames = options.regex.match(/\<[A-Za-z]+\>/g); - if (groupNames !== null) { - let re = namedRegexp(options.regex); - let reGroups = re.execGroups(fileObj.name); - groupNames.forEach(function(value) { - let g = value.replace(/\W/g, ''); - if (reGroups && reGroups[g]) { - fileObj.newName = fileObj.newName.replace('{{' + g + '}}', reGroups[g]); - } else { - fileObj.newName = fileObj.newName.replace('{{' + g + '}}', ''); - } - }); - } + options.regex.forEach((regex) => { + let groupNames = regex.match(/\<[A-Za-z]+\>/g); + if (groupNames !== null) { + let re = namedRegexp(regex); + let reGroups = re.execGroups(fileObj.name); + groupNames.forEach(function(value) { + let g = value.replace(/\W/g, ''); + if (reGroups && reGroups[g]) { + fileObj.newName = fileObj.newName.replace('{{' + g + '}}', reGroups[g]); + } else { + fileObj.newName = fileObj.newName.replace('{{' + g + '}}', ''); + } + }); + } + }); return fileObj; }