diff --git a/README.md b/README.md index 68589de..2ed690b 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,6 @@ Options: -f, --filetypes STRING comma-separated list of file extensions to process (jpg and jpeg are default) -l, --list list available template variables - -o, --overwrite overwrite existing files -r, --recursive recursively process the specified directory -t, --template [STRING]renaming template (Default is {{datetime}}_{{file}}) -w, --watch watch the specified directory for changes and @@ -64,7 +63,6 @@ The following configuration options are available when using _exif-renamer_ as a { dryrun: false, // simulate processing without modifying the filesystem fallback_ctime: true, // fallback to filesystem ctime if no EXIF DateTimeOriginal - overwrite: false, // overwrite existing files? require_exif: false, // fail if EXIF data is not found? path_separator: '/', // the character used to separate paths in templates formats: { @@ -310,6 +308,11 @@ your enhancements or bugfix. * Swap out Grunt for Gulp ## Release History +* 1.2.0 + * Introduced filename conflict resolution via sequential filenaming + in response to [#9](https://github.com/dylansmith/node-exif-renamer/issues/9) + * Deprecated the `overwrite` flag + * Reduced size of test/demo images * 1.1.2 * switched back to fixed `exif-parser@0.1.9` dependency * added test for alternate date parsing diff --git a/demo/img/exif.jpg b/demo/img/exif.jpg index 6314a3c..b01bd39 100644 Binary files a/demo/img/exif.jpg and b/demo/img/exif.jpg differ diff --git a/demo/img/no_exif.jpg b/demo/img/no_exif.jpg index 3b14827..b40cd2c 100644 Binary files a/demo/img/no_exif.jpg and b/demo/img/no_exif.jpg differ diff --git a/lib/exif-renamer.js b/lib/exif-renamer.js index 6af7bc6..214b106 100644 --- a/lib/exif-renamer.js +++ b/lib/exif-renamer.js @@ -23,7 +23,7 @@ var exifRenamer, defaults = { dryrun: false, // simulate processing without modifying the filesystem fallback_ctime: true, // fallback to filesystem ctime if no EXIF DateTimeOriginal - overwrite: false, // overwrite existing files? + overwrite: false, // deprecated: uses sequential filenames to resolve conflicts since 1.2.0 require_exif: false, // fail if EXIF data is not found? path_separator: '/', // the character used to separate paths in templates formats: { @@ -31,9 +31,10 @@ var exifRenamer, date: 'yyyymmdd', // default formatting for {{date}} time: 'HHMMss' // default formatting for {{time}} }, - valid_extensions: [ // supported file extensions for processing + valid_extensions: [ // supported file extensions for processing 'jpg','jpeg','tif','tiff' - ] + ], + sequential_template: '{{dir}}/{{name}}({{index}}).{{ext}}' // template used to generate sequential file paths }; Handlebars.registerHelper('datetime', function(format) { @@ -166,7 +167,7 @@ exifRenamer = { } // determine datetime - datetime = exif_data.exif && (exif_data.exif.DateTimeOriginal || exif_data.exif.CreateDate); + datetime = exif_data && exif_data.exif && (exif_data.exif.DateTimeOriginal || exif_data.exif.CreateDate); if (datetime) { datetime = datetime * 1000; } @@ -190,6 +191,19 @@ exifRenamer = { }.bind(this)); }, + /** + * Returns the next available sequential filename for a given conflicting filepath + * @param {String} filepath + * @return {String} + */ + get_sequential_filepath: function(filepath, index) { + index = index || 1; + var params = this.get_path_info(filepath); + params.index = index; + var newpath = Handlebars.compile(this.config.sequential_template)(params); + return (fs.existsSync(newpath)) ? this.get_sequential_filepath(filepath, index + 1) : newpath; + }, + /** * @method #is_supported_file(filepath) * @arg {String} filepath @@ -269,8 +283,8 @@ exifRenamer = { return callback(this.error('rename target "' + result.processed.path + '"" is a directory', result)); } - if (result.processed.stat.isFile() && !this.config.overwrite) { - return callback(this.error('rename target "' + result.processed.path + '"" already exists', result)); + if (result.processed.stat.isFile()) { + result.processed.path = this.get_sequential_filepath(result.processed.path); } } diff --git a/package.json b/package.json index 7441b48..f5974b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "exif-renamer", "description": "A NodeJS library & shell command to rename photos using their EXIF data.", - "version": "1.1.2", + "version": "1.2.0", "homepage": "https://github.com/dylansmith/node-exif-renamer", "author": { "name": "Dylan Smith", @@ -31,6 +31,7 @@ "test": "grunt test" }, "devDependencies": { + "async": "^1.4.2", "grunt": "~0.4.2", "grunt-contrib-jshint": "~0.6.4", "grunt-contrib-watch": "~0.5.3", @@ -41,16 +42,16 @@ }, "keywords": [], "dependencies": { - "exif-parser": "~0.1.9", - "q": "~1.0.0", + "cli": "~0.4.5", + "cli-color": "~0.2.3", "dateformat": "~1.0.7-1.2.3", + "exif-parser": "~0.1.9", + "glob": "~3.2.8", + "handlebars": "~2.0.0-alpha.1", "lodash": "~3.1.0", - "watchr": "~2.4.11", "mkdirp": "~0.3.5", + "q": "~1.0.0", "string": "~1.8.0", - "handlebars": "~2.0.0-alpha.1", - "cli": "~0.4.5", - "cli-color": "~0.2.3", - "glob": "~3.2.8" + "watchr": "~2.4.11" } } diff --git a/test/exif-renamer.spec.js b/test/exif-renamer.spec.js index 5f9cb0f..6087359 100644 --- a/test/exif-renamer.spec.js +++ b/test/exif-renamer.spec.js @@ -8,15 +8,16 @@ var path = require('path'), fs = require('fs'), Q = require('q'), _ = require('lodash'), + async = require('async'), sinon = require('sinon'), dateformat = require('dateformat'), template = '{{datetime}}_{{file}}', - imgPath = path.resolve(__dirname, '../demo/img'), + imgPath = path.resolve(__dirname, 'img'), imgExif = path.join(imgPath, 'exif.jpg'), imgNoExif = path.join(imgPath, 'no_exif.jpg'), notDir = path.join(imgPath, 'NOPE'), notFile = path.join(imgPath, 'NOPE.jpg'), - unsupportedFile = path.join(imgPath, '../helpers.js'), + unsupportedFile = path.resolve(__dirname, '..', 'package.json'), helpers, testExif, exifRenamer; helpers = { @@ -318,29 +319,13 @@ describe('exif-renamer', function() { it('should not overwrite files', function(done) { exifRenamer.config.overwrite.should.be.false; - exifRenamer.rename(tmpExif, '{{file}}', function(err) { - err.should.be.an.instanceOf(Error); + exifRenamer.rename(tmpExif, '{{file}}', function(err, result) { + err.should.be.false; + path.basename(result.processed.path).should.equal('exif(1).jpg') done(); }); }); - it('should overwrite files when config.overwrite=true', function(done) { - var targetPath = path.join(tmpDir, 'target.jpg'); - // create the target file - helpers.cp(imgExif, targetPath, function() { - fs.existsSync(targetPath).should.be.true; - // rename to existing path - exifRenamer.config.overwrite = true; - exifRenamer.rename(tmpExif, '{{dir}}:target.jpg', function(err, result) { - err.should.be.false; - result.processed.path.should.equal(targetPath); - fs.existsSync(targetPath).should.be.true; - fs.existsSync(tmpExif).should.be.false; - done(); - }); - }); - }); - it('should not overwrite directories, regardless of configuration', function(done) { exifRenamer.config.overwrite = true; exifRenamer.rename(tmpExif, '{{dir}}:', function(err) { @@ -370,6 +355,25 @@ describe('exif-renamer', function() { }); }); + it('should handle filename clashes via an auto-incrementing suffix', function(done) { + var results = []; + var renameTest = function(cb) { + helpers.cp(imgExif, tmpExif, function() { + exifRenamer.rename(tmpExif, template, function(err, result) { + err.should.be.false; + results.push(result); + cb(); + }); + }); + } + + async.series([renameTest, renameTest, renameTest], function() { + results[0].processed.path.should.match(/_exif\.jpg$/) + results[1].processed.path.should.match(/_exif\(1\)\.jpg$/) + results[2].processed.path.should.match(/_exif\(2\)\.jpg$/) + done(); + }); + }); }); /** diff --git a/test/img/alt-date-format.jpg b/test/img/alt-date-format.jpg index e7c8c85..6f571a8 100644 Binary files a/test/img/alt-date-format.jpg and b/test/img/alt-date-format.jpg differ diff --git a/test/img/exif.jpg b/test/img/exif.jpg new file mode 100644 index 0000000..b01bd39 Binary files /dev/null and b/test/img/exif.jpg differ diff --git a/test/img/no_exif.jpg b/test/img/no_exif.jpg new file mode 100644 index 0000000..b40cd2c Binary files /dev/null and b/test/img/no_exif.jpg differ