diff --git a/.gitignore b/.gitignore
index 3c3629e..5171c54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
node_modules
+npm-debug.log
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
index ff37751..3eb5288 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -14,6 +14,9 @@ module.exports = function(grunt) {
gruntfile: {
src: 'Gruntfile.js'
},
+ demo: {
+ src: ['demo/**/*.js']
+ },
lib: {
src: ['lib/**/*.js']
},
@@ -43,6 +46,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
// Default task.
- grunt.registerTask('default', ['jshint', 'nodeunit']);
+ //grunt.registerTask('default', ['jshint', 'nodeunit']);
+ grunt.registerTask('default', ['jshint']);
};
diff --git a/README.md b/README.md
index 74d1c31..887c821 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# exif-renamer [![Build Status](https://secure.travis-ci.org/dylansmith/node-exif-renamer.png?branch=master)](http://travis-ci.org/dylansmith/node-exif-renamer)
-A NodeJS service to rename photos using their EXIF data.
+A NodeJS service to rename images using their EXIF data.
## Installation
Install the module with: `npm install exif-renamer`
@@ -15,7 +15,7 @@ var exifRenamer = require('exif-renamer');
exif-renamer supports node-style callbacks:
```javascript
-exifRenamer.rename('path/to/image.file', '%yyyy-%mm-%dd_%n', function(error, filename) {
+exifRenamer.rename('path/to/image.file', '{{date "yyyy-mm-dd"}}_{{file}}', function(error, filename) {
if (!error) {
console.log('the file was renamed: ', filename);
} else {
@@ -24,12 +24,12 @@ exifRenamer.rename('path/to/image.file', '%yyyy-%mm-%dd_%n', function(error, fil
});
```
-It also supports promises (using the `q` library):
+It also supports promises (using the [Q library](https://www.npmjs.org/package/q)):
```javascript
exifRenamer
- .rename('path/to/image.file', '%yyyy-%mm-%dd_%n')
- .then(function(filename)) {
+ .rename('path/to/image.file', '{{date "yyyy-mm-dd"}}_{{file}}')
+ .then(function(filename) {
console.log('the file was renamed: ', filename);
})
.catch(function(error) {
@@ -39,8 +39,169 @@ exifRenamer
```
## Documentation
+
+### Configuration
Coming soon.
+### Renaming templates
+
+The #process and #rename methods accept a `template` argument which is used to determine the new
+filename for the renamed image. As the name might suggest, the template is a way for you to format
+the filename using values present in the EXIF data.
+
+`exif-renamer` uses [Handlebars](http://handlebarsjs.com/) for templating, which allows you to
+easily access the image file metadata to construct just about any filename you could imagine, e.g.:
+
+> Prefix the filename with the date (defaults to YYYY-MM-DD format):
+> `{{date}}_{{file}}`
+
+> Prefix the filename with a custom date format (see [dateformat](https://www.npmjs.org/package/dateformat)):
+> `{{date "yy-mm"}}_{{file}}'`
+
+> Move the image to a YYYY-MM directory:
+> `{{date "yyyy-mm"}}/{{file}}`
+
+> Prefix the parent directory with the year:
+> `{{date "yyyy"}}-{{dir}}/{{file}}`
+
+> Prefix the filename with the file extension and camera model:
+> `{{EXT}}-{{image.Model}}-{{file}}`
+
+> Prefix the filename with the F-number:
+> `F{{exif.FNumber}}-{{file}}`
+
+`date` (*datetime*, really) is currently the only metadata that supports additional formatting, via
+the [dateformat module](https://www.npmjs.org/package/dateformat) as mentioned above.
+
+#### Custom renaming
+
+It is possible to pass your own custom function rather than a handlebars template, giving you total
+control over the renaming process. Here is an example:
+
+```javascript
+doge_prefixer = function(fileinfo, metadata) {
+ var dogeisms = ['very', 'wow', 'so', 'much'];
+ console.log(arguments);
+ return [dogeisms[Math.floor(Math.random() * dogeisms.length)], fileinfo.basename].join('_');
+}
+
+exifRenamer.process('path/to/image.file', doge_prefixer, function(err, result) {
+ //...
+});
+```
+
+#### Metadata
+
+The metadata available to a handlebar template is a combination of the exif data generated by the
+[exif module](https://www.npmjs.org/package/exif), path information, and some other useful stuff:
+
+```json
+{
+ // EXIF data
+ ,
+
+ // path information
+ path: ,
+ basename:
+ dirname: ,
+ extname: ,
+
+ // other useful stuff
+ 'date': ,
+ 'time': ,
+ 'file': ,
+ 'dir': ,
+ 'ext': ,
+ 'EXT': ,
+}
+```
+
+### Methods
+
+#### #exif
+
+Returns the EXIF data for an image.
+
+** arguments **
+
+- `filepath` the path to the image file
+- `callback` the node-style callback that will receive the response
+
+** usage **
+
+```javascript
+exifRenamer.exif('path/to/image.file', function(err, exifdata) {
+ //...
+});
+```
+
+#### #process
+
+Takes an image and renaming template or callback and returns an object containing the renamed
+image path, but does not actually rename the image (see #rename for this).
+
+** arguments **
+
+- `filepath` the path to the image file
+- `template` the renaming template or a custom callback function
+- `callback` the node-style callback that will receive the response
+
+** usage **
+
+```javascript
+// using a handlebars template
+exifRenamer.process('path/to/image.file', 'renaming-template', function(err, result) {
+ //...
+});
+
+// using a custom function
+exifRenamer.process('path/to/image.file', customRenamer, function(err, result) {
+ //...
+});
+```
+
+#### #rename
+
+Takes an image and renaming template or callback and renames/moves the image.
+
+** arguments **
+
+- `filepath` the path to the image file
+- `template` the renaming template or a custom callback function
+- `callback` the node-style callback that will receive the response
+
+** usage **
+
+```javascript
+// using a handlebars template
+exifRenamer.rename('path/to/image.file', 'renaming-template', function(err, result) {
+ //...
+});
+
+// using a custom function
+exifRenamer.rename('path/to/image.file', customRenamer, function(err, result) {
+ //...
+});
+```
+
+#### #watch
+
+Watches a specified directory, renaming all images that are added to that directory.
+
+** arguments **
+
+- `dirpath` the path to the watch directory
+- `template` the renaming template or a custom callback function
+- `callback` the node-style callback that will receive the response
+
+** usage **
+
+```javascript
+exifRenamer.watch('path/to/watch/dir', 'renaming-template', function(err, result) {
+ //...
+});
+```
+
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
diff --git a/demo/demo-exif.js b/demo/demo-exif.js
new file mode 100644
index 0000000..9edf459
--- /dev/null
+++ b/demo/demo-exif.js
@@ -0,0 +1,11 @@
+var exifRenamer = require('../lib/exif-renamer'),
+ path = require('path'),
+ helpers = require('./helpers'),
+ img = path.resolve(__dirname, 'test.jpg');
+
+helpers.ul('DEMO: exif-renamer#process', '=', '\n');
+console.log('Getting EXIF data for', img, ':\n');
+exifRenamer.exif(img).then(function(exifdata) {
+ console.log(exifdata);
+ console.log('');
+});
diff --git a/demo/demo-process.js b/demo/demo-process.js
index d703913..1f2983e 100644
--- a/demo/demo-process.js
+++ b/demo/demo-process.js
@@ -1,35 +1,42 @@
var path = require('path'),
_ = require('lodash'),
Q = require('q'),
+ helpers = require('./helpers'),
+ log = console.log,
exifRenamer = require('../lib/exif-renamer'),
img = path.resolve(__dirname, 'test.jpg'),
- examples = {
- 'Prefix with a default datetime': '%date_%file',
- 'Prefix with "YY-MM" datetime': '%yy-%mm-%file',
- 'Move to YYYY-MMM directory': '%yyyy-%mm/%file',
- 'Prefix parent directory with year': '%yyyy-%dir/%file',
- 'Prefix with the file extension and camera model': '%EXT-%{image.Model}-%file',
- 'Prefix with F-number': 'F%{exif.FNumber}-%file',
- 'Rename using a custom function': function(filepath, data) { return 'CUSTOM-' + path.basename(filepath); }
- };
+ examples, doge_prefixer;
-function ul(text, chr) {
- chr = chr || '=';
- return text + '\n' + _.times(text.length, function() { return chr; }).join('');
+doge_prefixer = function(fileinfo, metadata) {
+ var dogeisms = ['very', 'wow', 'so', 'much'];
+ console.log(arguments);
+ return [dogeisms[Math.floor(Math.random() * dogeisms.length)], fileinfo.basename].join('_');
}
-function render(description, result) {
- console.log(ul('EXAMPLE: ' + description));
- console.log('template :', result.template);
- console.log('original :', result.original.path);
- console.log('processed:', result.processed.path);
- console.log('');
+examples = {
+ 'Prefix the filename with the date': '{{date}}_{{file}}',
+ 'Prefix the filename with a custom date format': '{{date "yy-mm"}}_{{file}}',
+ 'Move the image to a YYYY-MM directory': '{{date "yyyy-mm"}}/{{file}}',
+ 'Prefix the parent directory with the year': '{{date "yyyy"}}-{{dir}}/{{file}}',
+ 'Prefix the filename with the extension & camera model': '{{EXT}}-{{image.Model}}-{{file}}',
+ 'Prefix the filename with the F-number': 'F{{exif.FNumber}}-{{file}}',
+ 'Rename using a custom function': doge_prefixer
+ };
+
+helpers.ul('DEMO: exif-renamer#process', '=', '\n');
+
+function render(title, result) {
+ helpers.ul('EXAMPLE: ' + title, '-', '\n');
+ log('template :', result.template);
+ log('original :', result.original.path);
+ log('processed:', result.processed.path);
}
// rename using string-based patterns
-console.log('');
-_.forEach(examples, function(template, description) {
- exifRenamer.process(img, template).then(function(result) {
+Q.all(_.map(examples, function(template, description) {
+ return exifRenamer.process(img, template).then(function(result) {
render(description, result);
});
+})).done(function() {
+ log('');
});
diff --git a/demo/demo-watch.js b/demo/demo-watch.js
index 54369bb..155e096 100644
--- a/demo/demo-watch.js
+++ b/demo/demo-watch.js
@@ -1,34 +1,25 @@
var path = require('path'),
fs = require('fs'),
- _ = require('lodash'),
+ helpers = require('./helpers'),
log = console.log,
exifRenamer = require('../lib/exif-renamer'),
- watch_dir = path.resolve(__dirname, 'watch_target');
+ watch_dir = path.resolve(__dirname, 'watch_target'),
+ stdin = process.openStdin(),
+ src_file = path.resolve(__dirname, 'test.jpg');
-function ul(text, chr) {
- chr = chr || '=';
- return text + '\n' + _.times(text.length, function() { return chr; }).join('');
-}
-
-log('\n' + ul('DEMO: watching the filesystem for changes'));
+helpers.ul('DEMO: exif-renamer#watch', '=', '\n', '\n');
+log('Press to trigger file creation in the watch directory...');
-// watch a target dir for new image and rename them
-exifRenamer.watch(watch_dir, 'processed/%date_%file', function(err, result) {
- var u = '';
- description = 'CHANGE DETECTED';
- _.times(description.length, function() { u += '='; });
- log([,description,u,].join('\n'));
+// watch the target dir for new images and rename them
+exifRenamer.watch(watch_dir, 'processed/{{date}}_{{file}}', function(err, result) {
+ helpers.ul('CHANGE DETECTED', '=', '\n');
log('template :', result.template);
log('original :', result.original.path);
log('processed:', result.processed.path);
- log('');
});
-// create file everytime a key is pressed
-log('Press to trigger file creation in the watch directory...');
-var src_file = path.resolve(__dirname, 'test.jpg');
-var stdin = process.openStdin();
-stdin.on('data', function(chunk) {
+// create file every time the Enter key is pressed
+stdin.on('data', function() {
var target_file = path.join(watch_dir, Date.now() + '_test.jpg');
log('creating: ' + target_file);
fs.createReadStream(src_file).pipe(fs.createWriteStream(target_file));
diff --git a/demo/helpers.js b/demo/helpers.js
new file mode 100644
index 0000000..991fc6f
--- /dev/null
+++ b/demo/helpers.js
@@ -0,0 +1,10 @@
+var S = require('string');
+
+module.exports = {
+ ul: function(text, ul, prefix, suffix) {
+ ul = ul || '=';
+ prefix = prefix || '';
+ suffix = suffix || '';
+ console.log(prefix + text + '\n' + S(ul).repeat(text.length) + suffix);
+ }
+};
diff --git a/lib/exif-renamer.js b/lib/exif-renamer.js
index 1ebae7d..0b83794 100644
--- a/lib/exif-renamer.js
+++ b/lib/exif-renamer.js
@@ -10,9 +10,9 @@
var _ = require('lodash'),
Q = require('q'),
+ Handlebars = require('handlebars'),
dateformat = require('dateformat'),
ExifImage = require('exif').ExifImage,
- objectPath = require('object-path'),
path = require('path'),
fs = require('fs'),
mkdirp = require('mkdirp'),
@@ -24,41 +24,31 @@ function log() {
console.log.apply(global, args);
}
+Handlebars.registerHelper('date', function(format) {
+ format = (arguments.length === 1) ? 'yyyy-mm-dd' : format;
+ return dateformat(this.exif.DateTimeOriginal, format);
+});
+
var exifRenamer = {
- generate_name: function(filepath, data, template) {
- var name = template,
- found,
- val,
- refs = {},
- re = /\%\{([a-z\.]+)\}/ig,
- wildcards = {
- '/': path.sep
- ,'%date': function() { return dateformat(data.exif.DateTimeOriginal, 'yyyy-mm-dd'); }
- ,'%file': function() { return path.basename(filepath); }
- ,'%dir': function() { return path.basename(path.dirname(filepath)); }
- ,'%ext': function() { return path.extname(filepath).substr(1).toLowerCase(); }
- ,'%EXT': function() { return path.extname(filepath).substr(1).toUpperCase(); }
- ,'%yyyy': function() { return dateformat(data.exif.DateTimeOriginal, 'yyyy'); }
- ,'%yy': function() { return dateformat(data.exif.DateTimeOriginal, 'yy'); }
- ,'%mm': function() { return dateformat(data.exif.DateTimeOriginal, 'mm'); }
- ,'%dd': function() { return dateformat(data.exif.DateTimeOriginal, 'dd'); }
- };
-
- // process EXIF references
- while ((found = re.exec(name)) !== null) {
- val = objectPath.get(data, found[1]);
- if (val) {
- refs[found[0]] = val;
+ generate_name: function(fileinfo, exifdata, template) {
+ // create a combined metadata object
+ var metadata = _.assign(
+ fileinfo,
+ exifdata,
+ {
+ 'date': dateformat(exifdata.exif.DateTimeOriginal, 'yyyy-mm-dd'),
+ 'time': dateformat(exifdata.exif.DateTimeOriginal, 'HH:MM:ss'),
+ 'file': fileinfo.basename,
+ 'dir': path.basename(fileinfo.dirname),
+ 'ext': fileinfo.extname.toLowerCase().substr(1),
+ 'EXT': fileinfo.extname.toUpperCase().substr(1)
}
- }
-
- // process all placeholders
- _.forEach(_.assign({}, wildcards, refs), function(v, k) {
- name = name.replace(k, (_.isFunction(v) ? v() : v).toString());
- });
+ );
- return name;
+ // pre-process the template
+ template = template.replace('/', path.sep);
+ return Handlebars.compile(template)(metadata);
},
get_exif: function(filepath, callback) {
@@ -78,22 +68,20 @@ var exifRenamer = {
process: function(filepath, template, callback) {
var orig = this.get_file_info(filepath);
- Q.nfcall(this.get_exif, orig.path)
- .catch(callback)
- .done(function(metadata) {
- var fn, name, info;
-
- fn = (_.isFunction(template)) ? template : this.generate_name;
- name = fn(orig.path, metadata, template);
+ this.get_exif(orig.path, function(err, exifdata) {
+ var fn, name, info, result;
+ if (!err) {
+ fn = (_.isFunction(template)) ? template : this.generate_name.bind(this);
+ name = fn(orig, exifdata, template);
info = this.get_file_info(path.join(orig.dirname, name));
-
- callback(null, {
+ result = {
template: template,
original: orig,
processed: info
- });
-
- }.bind(this));
+ };
+ }
+ callback(err, result);
+ }.bind(this));
},
rename: function(filepath, template, callback) {
diff --git a/package.json b/package.json
index 0825834..aed4d87 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "exif-renamer",
"description": "A NodeJS service to rename photos using their EXIF data.",
- "version": "0.1.0",
+ "version": "0.2.0",
"homepage": "https://github.com/dylansmith/node-exif-renamer",
"author": {
"name": "Dylan Smith",
@@ -39,8 +39,9 @@
"q": "~1.0.0",
"dateformat": "~1.0.7-1.2.3",
"lodash": "~2.4.1",
- "object-path": "~0.1.2",
"watchr": "~2.4.11",
- "mkdirp": "~0.3.5"
+ "mkdirp": "~0.3.5",
+ "string": "~1.8.0",
+ "handlebars": "~2.0.0-alpha.1"
}
}
diff --git a/test/exif-renamer_test.js b/test/exif-renamer_test.js
index f7db996..ee74529 100644
--- a/test/exif-renamer_test.js
+++ b/test/exif-renamer_test.js
@@ -1,6 +1,6 @@
'use strict';
-var exif_renamer = require('../lib/exif-renamer.js');
+//var exif_renamer = require('../lib/exif-renamer.js');
/*
======== A Handy Little Nodeunit Reference ========
@@ -22,15 +22,15 @@ var exif_renamer = require('../lib/exif-renamer.js');
test.ifError(value)
*/
-exports['awesome'] = {
- setUp: function(done) {
- // setup here
- done();
- },
- 'no args': function(test) {
- test.expect(1);
- // tests here
- test.equal(exif_renamer.awesome(), 'awesome', 'should be awesome.');
- test.done();
- },
-};
+// exports['awesome'] = {
+// setUp: function(done) {
+// // setup here
+// done();
+// },
+// 'no args': function(test) {
+// test.expect(1);
+// // tests here
+// test.equal(exif_renamer.awesome(), 'awesome', 'should be awesome.');
+// test.done();
+// },
+// };