1 // ***************************************************************** -*- C++ -*-
2 /*
3  * Copyright (C) 2009 Brad Schick <schickb@gmail.com>
4  *
5  * This file is part of the organize tool.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
20  */
21 // *****************************************************************************
22 
23 #include <boost/algorithm/string.hpp>
24 #include <boost/regex.hpp>
25 #include <boost/format.hpp>
26 #include <boost/lexical_cast.hpp>
27 #include <exiv2/image.hpp>
28 #include <exiv2/easyaccess.hpp>
29 #include <exiv2/exif.hpp>
30 #include <exiv2/iptc.hpp>
31 #include <exiv2/tags.hpp>
32 //#include <exiv2/xmp.hpp>
33 #include <cassert>
34 #include <sstream>
35 #include <ctime>
36 #include "helpers.hpp"
37 
38 #define BOOST_FILESYSTEM_NO_DEPRECATED
39 
40 namespace fs = boost::filesystem;
41 typedef Exiv2::ExifData::const_iterator (*EasyAccessFct)(const Exiv2::ExifData& ed);
42 
43 
scrub(const std::string & dirty,bool strip_space=false)44 std::string scrub(const std::string &dirty, bool strip_space = false)
45 {
46     std::string scrub = boost::trim_copy(dirty);
47     if(strip_space) {
48         boost::regex space("\\s");
49         scrub = boost::regex_replace(scrub, space, "");
50     }
51     boost::regex dash("[:/\\\\|<>]");
52     boost::regex under("[\"'\\[\\]\\{\\}#=%\\$\\?,\\+\\*]");
53     scrub = boost::regex_replace(scrub, dash, "-");
54 
55     return boost::regex_replace(scrub, under, "_");
56 }
57 
exif_data(const Exiv2::Image * image,const char * key,Exiv2::ExifData::const_iterator & md)58 bool exif_data(const Exiv2::Image *image, const char *key, Exiv2::ExifData::const_iterator &md)
59 {
60     assert(image && key);
61     bool ok = false;
62     try {
63         const Exiv2::ExifData &exifData = image->exifData();
64         Exiv2::ExifKey exifKey(key);
65         md = exifData.findKey(exifKey);
66         if(md != exifData.end() && md->typeId() != Exiv2::undefined)
67             ok = true;
68     }
69     catch(const Exiv2::AnyError&) {
70     }
71     return ok;
72 }
73 
exif_data_easy(const Exiv2::Image * image,EasyAccessFct easy,Exiv2::ExifData::const_iterator & md)74 bool exif_data_easy(const Exiv2::Image *image, EasyAccessFct easy, Exiv2::ExifData::const_iterator &md)
75 {
76     assert(image && easy);
77     bool ok = false;
78     try {
79         const Exiv2::ExifData &exifData = image->exifData();
80         md = easy(exifData);
81         if(md != exifData.end() && md->typeId() != Exiv2::undefined)
82             ok = true;
83     }
84     catch(const Exiv2::AnyError&) {
85     }
86     return ok;
87 }
88 
89 
iptc_data(const Exiv2::Image * image,const char * key,Exiv2::IptcData::const_iterator & md)90 bool iptc_data(const Exiv2::Image *image, const char *key, Exiv2::IptcData::const_iterator &md)
91 {
92     bool ok = false;
93     assert(image && key);
94     try {
95         const Exiv2::IptcData &iptcData = image->iptcData();
96         Exiv2::IptcKey iptcKey(key);
97         md = iptcData.findKey(iptcKey);
98         if(md != iptcData.end() && md->typeId() != Exiv2::undefined)
99             ok = true;
100     }
101     catch(const Exiv2::AnyError&) {
102     }
103     return ok;
104 }
105 
exif_date(const Exiv2::Image * image,const fs::path &)106 std::string exif_date(const Exiv2::Image *image, const fs::path &)
107 {
108     Exiv2::ExifData::const_iterator md;
109     bool done = exif_data(image, "Exif.Photo.DateTimeDigitized", md);
110     if(!done)
111         done = exif_data(image, "Exif.Photo.DateTimeOriginal", md);
112     if(!done)
113         return "";
114 
115     std::string date = scrub(md->print().substr(0,10));
116     // Some files have zeros for dates, just fail in that case
117     if(boost::lexical_cast<int>(date.substr(0,4))==0)
118         return "";
119 
120     return date;
121 }
122 
exif_year(const Exiv2::Image * image,const fs::path & path)123 std::string exif_year(const Exiv2::Image *image, const fs::path &path)
124 {
125     std::string date = exif_date(image, path);
126     if(date.length())
127         return date.substr(0,4);
128     else
129         return date;
130 }
131 
exif_month(const Exiv2::Image * image,const fs::path & path)132 std::string exif_month(const Exiv2::Image *image, const fs::path &path)
133 {
134     std::string date = exif_date(image, path);
135     if(date.length())
136         return date.substr(5,2);
137     else
138         return date;
139 }
140 
exif_day(const Exiv2::Image * image,const fs::path & path)141 std::string exif_day(const Exiv2::Image *image, const fs::path &path)
142 {
143     std::string date = exif_date(image, path);
144     if(date.length())
145         return date.substr(8,2);
146     else
147         return date;
148 }
149 
iptc_get_date(const Exiv2::Image * image,Exiv2::DateValue::Date & date)150 bool iptc_get_date(const Exiv2::Image *image, Exiv2::DateValue::Date &date)
151 {
152     Exiv2::IptcData::const_iterator md;
153     bool done = iptc_data(image, "Iptc.Application2.DigitizationDate", md);
154     if(!done)
155         done = iptc_data(image, "Iptc.Application2.DateCreated", md);
156     if(!done)
157         return false;
158     date = ((Exiv2::DateValue*)md->getValue().get())->getDate();
159     return date.year > 0;
160 }
161 
iptc_date(const Exiv2::Image * image,const fs::path &)162 std::string iptc_date(const Exiv2::Image *image, const fs::path &)
163 {
164     Exiv2::DateValue::Date date;
165     if(iptc_get_date(image, date))
166         return str(boost::format("%4d-%02d-%02d") % date.year % date.month % date.day);
167     else
168         return "";
169 }
170 
iptc_year(const Exiv2::Image * image,const fs::path &)171 std::string iptc_year(const Exiv2::Image *image, const fs::path &)
172 {
173     Exiv2::DateValue::Date date;
174     if(iptc_get_date(image, date))
175         return str(boost::format("%4d") % date.year);
176     else
177         return "";
178 }
179 
iptc_month(const Exiv2::Image * image,const fs::path &)180 std::string iptc_month(const Exiv2::Image *image, const fs::path &)
181 {
182     Exiv2::DateValue::Date date;
183     if(iptc_get_date(image, date))
184         return str(boost::format("%02d") % date.month);
185     else
186         return "";
187 }
188 
iptc_day(const Exiv2::Image * image,const fs::path &)189 std::string iptc_day(const Exiv2::Image *image, const fs::path &)
190 {
191     Exiv2::DateValue::Date date;
192     if(iptc_get_date(image, date))
193         return str(boost::format("%02d") % date.day);
194     else
195         return "";
196 }
197 
file_get_tm(const fs::path & path,std::tm & tm)198 bool file_get_tm(const fs::path &path, std::tm &tm)
199 {
200     std::time_t timer = fs::last_write_time(path);
201     if(time > 0) {
202         tm = *localtime(&timer);
203         return true;
204     }
205     else {
206         return false;
207     }
208 }
209 
file_date(const Exiv2::Image *,const fs::path & path)210 std::string file_date(const Exiv2::Image *, const fs::path &path)
211 {
212     std::tm tm;
213     if(file_get_tm(path, tm))
214         return str(boost::format("%4d-%02d-%02d") % (tm.tm_year + 1900) % (tm.tm_mon + 1) % tm.tm_mday);
215     else
216         return "";
217 }
218 
file_year(const Exiv2::Image *,const fs::path & path)219 std::string file_year(const Exiv2::Image *, const fs::path &path)
220 {
221     std::tm tm;
222     if(file_get_tm(path, tm))
223         return str(boost::format("%4d") % (tm.tm_year + 1900));
224     else
225         return "";
226 }
227 
file_month(const Exiv2::Image *,const fs::path & path)228 std::string file_month(const Exiv2::Image *, const fs::path &path)
229 {
230     std::tm tm;
231     if(file_get_tm(path, tm))
232         return str(boost::format("%02d") % (tm.tm_mon + 1));
233     else
234         return "";
235 }
236 
file_day(const Exiv2::Image *,const fs::path & path)237 std::string file_day(const Exiv2::Image *, const fs::path &path)
238 {
239     std::tm tm;
240     if(file_get_tm(path, tm))
241         return str(boost::format("%02d") % tm.tm_mday);
242     else
243         return "";
244 }
245 
246 /*
247 std::string xmp_date(const Exiv2::Image *image, const fs::path &)
248 {
249     return "";
250 }
251 
252 std::string xmp_year(const Exiv2::Image *image, const fs::path &)
253 {
254     return "";
255 }
256 
257 std::string xmp_month(const Exiv2::Image *image, const fs::path &)
258 {
259     return "";
260 }
261 
262 std::string xmp_day(const Exiv2::Image *image, const fs::path &)
263 {
264     return "";
265 }*/
266 
exif_time(const Exiv2::Image * image,const fs::path &)267 std::string exif_time(const Exiv2::Image *image, const fs::path &)
268 {
269     Exiv2::ExifData::const_iterator md;
270     bool done = exif_data(image, "Exif.Photo.DateTimeDigitized", md);
271     if(!done)
272         done = exif_data(image, "Exif.Photo.DateTimeOriginal", md);
273     if(!done)
274         return "";
275 
276     std::string datetime = md->print();
277     // Some files have zeros for dates, just fail in that case
278     if(boost::lexical_cast<int>(datetime.substr(0,4)) == 0)
279         return "";
280 
281     return scrub(datetime.substr(11));
282 }
283 
exif_hour(const Exiv2::Image * image,const fs::path & path)284 std::string exif_hour(const Exiv2::Image *image, const fs::path &path)
285 {
286     std::string time = exif_time(image, path);
287     if(time.length())
288         return time.substr(0,2);
289     else
290         return time;
291 }
292 
exif_minute(const Exiv2::Image * image,const fs::path & path)293 std::string exif_minute(const Exiv2::Image *image, const fs::path &path)
294 {
295     std::string time = exif_time(image, path);
296     if(time.length())
297         return time.substr(3,2);
298     else
299         return time;
300 }
301 
exif_second(const Exiv2::Image * image,const fs::path & path)302 std::string exif_second(const Exiv2::Image *image, const fs::path &path)
303 {
304     std::string time = exif_time(image, path);
305     if(time.length())
306         return time.substr(6,2);
307     else
308         return time;
309 }
310 
iptc_get_time(const Exiv2::Image * image,Exiv2::TimeValue::Time & time)311 bool iptc_get_time(const Exiv2::Image *image, Exiv2::TimeValue::Time &time)
312 {
313     Exiv2::IptcData::const_iterator md;
314     bool done = iptc_data(image, "Iptc.Application2.DigitizationTime", md);
315     if(!done)
316         done = iptc_data(image, "Iptc.Application2.TimeCreated", md);
317     if(!done)
318         return false;
319     time = ((Exiv2::TimeValue*)md->getValue().get())->getTime();
320     // Zero is a valid time, so this one is hard to check.
321     return true;
322 }
323 
iptc_time(const Exiv2::Image * image,const fs::path &)324 std::string iptc_time(const Exiv2::Image *image, const fs::path &)
325 {
326     Exiv2::TimeValue::Time time;
327     if(iptc_get_time(image, time))
328         return str(boost::format("%02d-%02d-%02d") % time.hour % time.minute % time.second);
329     else
330         return "";
331 }
332 
iptc_hour(const Exiv2::Image * image,const fs::path &)333 std::string iptc_hour(const Exiv2::Image *image, const fs::path &)
334 {
335     Exiv2::TimeValue::Time time;
336     if(iptc_get_time(image, time))
337         return str(boost::format("%02d") % time.hour);
338     else
339         return "";
340 }
341 
iptc_minute(const Exiv2::Image * image,const fs::path &)342 std::string iptc_minute(const Exiv2::Image *image, const fs::path &)
343 {
344     Exiv2::TimeValue::Time time;
345     if(iptc_get_time(image, time))
346         return str(boost::format("%02d") % time.minute);
347     else
348         return "";
349 }
350 
iptc_second(const Exiv2::Image * image,const fs::path &)351 std::string iptc_second(const Exiv2::Image *image, const fs::path &)
352 {
353     Exiv2::TimeValue::Time time;
354     if(iptc_get_time(image, time))
355         return str(boost::format("%02d") % time.second);
356     else
357         return "";
358 }
359 
file_time(const Exiv2::Image *,const fs::path & path)360 std::string file_time(const Exiv2::Image *, const fs::path &path)
361 {
362     std::tm tm;
363     if(file_get_tm(path, tm))
364         return str(boost::format("%02d-%02d-%02d") % tm.tm_hour % tm.tm_min % tm.tm_sec);
365     else
366         return "";
367 }
368 
file_hour(const Exiv2::Image *,const fs::path & path)369 std::string file_hour(const Exiv2::Image *, const fs::path &path)
370 {
371     std::tm tm;
372     if(file_get_tm(path, tm))
373         return str(boost::format("%02d") % tm.tm_hour);
374     else
375         return "";
376 }
377 
file_minute(const Exiv2::Image *,const fs::path & path)378 std::string file_minute(const Exiv2::Image *, const fs::path &path)
379 {
380     std::tm tm;
381     if(file_get_tm(path, tm))
382         return str(boost::format("%02d") % tm.tm_min);
383     else
384         return "";
385 }
386 
file_second(const Exiv2::Image *,const fs::path & path)387 std::string file_second(const Exiv2::Image *, const fs::path &path)
388 {
389     std::tm tm;
390     if(file_get_tm(path, tm))
391         return str(boost::format("%02d") % tm.tm_sec);
392     else
393         return "";
394 }
395 
396 /*std::string xmp_time(const Exiv2::Image *image, const fs::path &)
397 {
398     return "";
399 }
400 
401 std::string xmp_hour(const Exiv2::Image *image, const fs::path &)
402 {
403     return "";
404 }
405 
406 std::string xmp_minute(const Exiv2::Image *image, const fs::path &)
407 {
408     return "";
409 }
410 
411 std::string xmp_second(const Exiv2::Image *image, const fs::path &)
412 {
413     return "";
414 }*/
415 
exif_dimension(const Exiv2::Image * image,const fs::path & path)416 std::string exif_dimension(const Exiv2::Image *image, const fs::path &path)
417 {
418     return exif_width(image, path) + "-" + exif_height(image, path);
419 }
420 
exif_width(const Exiv2::Image * image,const fs::path &)421 std::string exif_width(const Exiv2::Image *image, const fs::path &)
422 {
423     Exiv2::ExifData::const_iterator md;
424     bool done = exif_data(image, "Exif.Photo.PixelXDimension", md);
425     if(!done)
426         return "";
427     return scrub(md->print());
428 }
429 
exif_height(const Exiv2::Image * image,const fs::path &)430 std::string exif_height(const Exiv2::Image *image, const fs::path &)
431 {
432     Exiv2::ExifData::const_iterator md;
433     bool done = exif_data(image, "Exif.Photo.PixelYDimension", md);
434     if(!done)
435         return "";
436     return scrub(md->print());
437 }
438 
file_dimension(const Exiv2::Image * image,const fs::path & path)439 std::string file_dimension(const Exiv2::Image *image, const fs::path &path)
440 {
441     if(image)
442         return file_width(image, path) + "-" + file_height(image, path);
443     else
444         return "";
445 }
446 
file_width(const Exiv2::Image * image,const fs::path &)447 std::string file_width(const Exiv2::Image *image, const fs::path &)
448 {
449     if(image)
450         return str(boost::format("%02d") % image->pixelWidth());
451     else
452         return "";
453 }
454 
file_height(const Exiv2::Image * image,const fs::path &)455 std::string file_height(const Exiv2::Image *image, const fs::path &)
456 {
457     if(image)
458         return str(boost::format("%02d") % image->pixelHeight());
459     else
460         return "";
461 }
462 
463 /*
464 std::string xmp_dimension(const Exiv2::Image *image, const fs::path &)
465 {
466     return ""
467 }
468 
469 std::string xmp_width(const Exiv2::Image *image, const fs::path &)
470 {
471     return "";
472 }
473 
474 std::string xmp_height(const Exiv2::Image *image, const fs::path &)
475 {
476     return "";
477 }*/
478 
exif_model(const Exiv2::Image * image,const fs::path &)479 std::string exif_model(const Exiv2::Image *image, const fs::path &)
480 {
481     Exiv2::ExifData::const_iterator md;
482     bool done = exif_data(image, "Exif.Image.Model", md);
483     if(!done)
484         return "";
485     return scrub(md->print());
486 }
487 
exif_make(const Exiv2::Image * image,const fs::path &)488 std::string exif_make(const Exiv2::Image *image, const fs::path &)
489 {
490     Exiv2::ExifData::const_iterator md;
491     bool done = exif_data(image, "Exif.Image.Make", md);
492     if(!done)
493         return "";
494     return scrub(md->print());
495 }
496 
497 /*std::string xmp_model(const Exiv2::Image *image, const fs::path &)
498 {
499     return "";
500 }*/
501 
exif_speed(const Exiv2::Image * image,const fs::path &)502 std::string exif_speed(const Exiv2::Image *image, const fs::path &)
503 {
504     Exiv2::ExifData::const_iterator md;
505     bool done = exif_data(image, "Exif.Photo.ShutterSpeedValue", md);
506     if(!done)
507         done = exif_data(image, "Exif.Photo.ExposureTime", md);
508     if(!done)
509         return "";
510     return scrub(md->print());
511 }
512 
513 /*std::string xmp_speed(const Exiv2::Image *image, const fs::path &)
514 {
515     return "";
516 }*/
517 
exif_aperture(const Exiv2::Image * image,const fs::path &)518 std::string exif_aperture(const Exiv2::Image *image, const fs::path &)
519 {
520     Exiv2::ExifData::const_iterator md;
521     bool done = exif_data(image, "Exif.Photo.ApertureValue", md);
522     if(!done)
523         done = exif_data(image, "Exif.Photo.FNumber", md);
524     if(!done)
525         return "";
526     return scrub(md->print());
527 }
528 
529 /*std::string xmp_aperture(const Exiv2::Image *image, const fs::path &)
530 {
531     return "";
532 }*/
533 
exif_focal(const Exiv2::Image * image,const fs::path &)534 std::string exif_focal(const Exiv2::Image *image, const fs::path &)
535 {
536     Exiv2::ExifData::const_iterator md;
537     bool done = exif_data(image, "Exif.Photo.FocalLength", md);
538     if(!done)
539         return "";
540     return scrub(md->print());
541 }
542 
543 /*std::string xmp_focal(const Exiv2::Image *image, const fs::path &)
544 {
545     return "";
546 }*/
547 
exif_distance(const Exiv2::Image * image,const fs::path &)548 std::string exif_distance(const Exiv2::Image *image, const fs::path &)
549 {
550     Exiv2::ExifData::const_iterator md;
551     bool done = exif_data(image, "Exif.Photo.SubjectDistance", md);
552     if(!done)
553         return "";
554     return scrub(md->print());
555 }
556 
557 /*std::string xmp_distance(const Exiv2::Image *image, const fs::path &)
558 {
559     return "";
560 }*/
561 
exif_meter(const Exiv2::Image * image,const fs::path &)562 std::string exif_meter(const Exiv2::Image *image, const fs::path &)
563 {
564     Exiv2::ExifData::const_iterator md;
565     bool done = exif_data(image, "Exif.Photo.MeteringMode", md);
566     if(!done)
567         return "";
568     return scrub(md->print());
569 }
570 
exif_macro(const Exiv2::Image * image,const fs::path &)571 std::string exif_macro(const Exiv2::Image *image, const fs::path &)
572 {
573     Exiv2::ExifData::const_iterator md;
574     bool done = exif_data_easy(image, Exiv2::macroMode, md);
575     if(!done)
576         return "";
577     return scrub(md->print());
578 }
579 
exif_orientation(const Exiv2::Image * image,const fs::path &)580 std::string exif_orientation(const Exiv2::Image *image, const fs::path &)
581 {
582     Exiv2::ExifData::const_iterator md;
583     bool done = exif_data_easy(image, Exiv2::orientation, md);
584     if(!done)
585         return "";
586     return scrub(md->print(), true);
587 }
588 
exif_lens(const Exiv2::Image * image,const fs::path &)589 std::string exif_lens(const Exiv2::Image *image, const fs::path &)
590 {
591     Exiv2::ExifData::const_iterator md;
592     bool done = exif_data_easy(image, Exiv2::lensName, md);
593     if(!done)
594         return "";
595     return scrub(md->print());
596 }
597 
598 
exif_iso(const Exiv2::Image * image,const fs::path &)599 std::string exif_iso(const Exiv2::Image *image, const fs::path &)
600 {
601     Exiv2::ExifData::const_iterator md;
602     bool done = exif_data_easy(image, Exiv2::isoSpeed, md);
603     if(!done)
604         return "";
605     return scrub(md->print());
606 }
607 
608 /*std::string xmp_meter(const Exiv2::Image *image, const fs::path &)
609 {
610     return "";
611 }*/
612 
exif_keyword(const Exiv2::Image * image,const fs::path &)613 std::string exif_keyword(const Exiv2::Image *image, const fs::path &)
614 {
615     Exiv2::ExifData::const_iterator md;
616     bool done = exif_data(image, "Exif.Photo.UserComment", md);
617     if(!done)
618         return "";
619     return scrub(md->print());
620 }
621 
iptc_keyword(const Exiv2::Image * image,const fs::path &)622 std::string iptc_keyword(const Exiv2::Image *image, const fs::path &)
623 {
624     Exiv2::IptcData::const_iterator md;
625     bool done = iptc_data(image, "Iptc.Application2.Keywords", md);
626     if(!done)
627         return "";
628     return scrub(md->print());
629 }
630 
631 /*std::string xmp_keyword(const Exiv2::Image *image, const fs::path &)
632 {
633     return "";
634 }*/
635 
636