Skip to content

Instantly share code, notes, and snippets.

@lstoll
Created February 21, 2010 17:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lstoll/310413 to your computer and use it in GitHub Desktop.
Save lstoll/310413 to your computer and use it in GitHub Desktop.

Exif Date Sync

Because I wanted to use a smart folder to show me photos taken in the last 6 months in my picasa library, I needed to make sure the on disk created attribute matched. Unfortunately it didn't, so I needed to make something to do this.

It didn't seem possible with unix tools, because they dont't have access to the HFS/ Mac specific created metadata. So some googling found a tool that would let me do this, which I've included here as well as a ruby script to read the exif data and call this command

To exit exif dates, I'd suggest using ExifTool (http://owl.phy.queensu.ca/~phil/exiftool/#shift) - it will let you batch shift the date. I'd also reccomend http://www.qdev.de/?location=mac/exifrenamer , It will batch change the filenames to match the exif date (handy for organization)

Usage

(Install exifr if not installed) gem install exifr

./exifDateSync ""

(You will need to quote the pathspec)

myTouch

I didn't write this - It's included here for safekeeping, it's originally from

http://www.clanmills.com/articles/myTouch/

To compile

g++ -framework Carbon -o myTouch myTouch.cpp

require 'exifr'
Dir.glob(::File.expand_path(ARGV[0])) do |f|
begin
exif = EXIFR::JPEG.new(f)
dt = nil
unless dt = exif.date_time
dt = exif.date_time.original
end
dates = dt.strftime('%y%m%d%H%M.%S')
puts `./myTouch -createDate=#{dates} -contentModDate=#{dates} "#{f}"`
rescue
# ignore if it's a dir or not parsable or whatever
end
end
/* (c) Copyright Robin Mills Software Consultancy 2007-
San Jose, CA USA. All rights reserved
http://www.clanmills.com robin@clanmills.com
-----------------------------------------------------------------
* File : myTouch.cpp
* Author : Robin Mills
* Date : 20071019 (ccyymmdd)
-----------------------------------------------------------------
* Purpose:
Report/Modify file timestamps on MacOSX
* Useage :
myTouch [-option[=date]]+ file...
option := { -createDate | -contentModDate | -attributeModDate | -accessDate | -backupDate | -all }
date := { -now | [CC]YYMMhhdd[.ss] }
If you use an option more that once, the last definition will be used.
myTouch -createDate=-now -createDate=200102030405 Image.jpg
Set the createDate of Image.jpg to Feb 03, 2001
* Example:
myTouch Image.jpg Report all dates of Image.jpg
myTouch -createDate Image.jpg Report the createDate of Image.jpg
myTouch -createDate -contentModDate Image.jpg Report the createDate and contentModDate of Image.jpg
myTouch -createDate=200710161039 Image.jpg Set the createDate of Image.jpg
myTouch -all=-now *.jpg Set all time stamps to current time for all .jpg files
* Description:
This program is different from the system touch command
touch enables you to modify the UNIX times atime, ctime and mtime
myTouch enables you to report/modify the MacOSX time stamps on the file object
* Restrictions:
The following restrictions seem to be imposed by FSSetCatalogInfo
createDate is the birthtime of the file (and contentModDate cannot be before createDate)
So:
1 When you set createDate, contentModDate will be reset forward if necessary
2 When you set contentModDate, createDate will be set back if necessary
3 These rules seem random to me because they are not enforced for the other 3 timestamps!
you cannot set -attributeModDate=date because changing any of the attributes
of a file causes this date to be set by the OS (even attributeModDate!)
* Ideas to extend this:
1 add a time definition based on file -option=filename to copy datestamps from another file
2 allow multiple definitions of option/dates and files
Example myTouch -option=date file ... -option=date file...
3 Implement on Linux and Windows
* More information:
http://www.clanmills.com/articles/macosx/myTouch
* Revision history at bottom of file
-----------------------------------------------------------------
*/
#include <Carbon/Carbon.h>
#include <iostream>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#define lengthof(x) (sizeof(x)/sizeof(x[0]))
static char* titles[] = { "-all" , "-createDate" , "-contentModDate" , "-attributeModDate" , "-accessDate" , "-backupDate" } ;
// prototypes and forward declarations
class Option ;
int syntax(bool bAll) ;
bool parseOptionDate(const char* o,char** option,char** date) ;
int getOption(const char* arg) ;
OSStatus reportDate(UInt8* path,int option) ;
CFDateRef getDate(const char* s) ;
OSStatus setDate(UInt8* path,int option,CFDateRef date) ;
bool fexist(const char* path) ;
bool parseOption(const char* s,Option* Options,bool* pbOK) ;
/* ----------------------------------------------------------- */
/* syntax - report syntax of program */
/* ----------------------------------------------------------- */
int syntax(bool bAll)
{
fprintf(stderr,"usage : myTouch [option[=date]]+ file ...\n") ;
if ( bAll )
{
fprintf(stderr,"option : ") ;
for ( int i = 0 ; i < lengthof (titles) ; i++ )
{
if ( i != 0 ) fprintf(stderr," | ") ;
fprintf(stderr,"%s",titles[i]) ;
}
fprintf(stderr,"\n") ;
fprintf(stderr,"date : [-now | [CC]YYDDMMhh[.ss] ]\n") ;
fprintf(stderr,"example: myTouch -all=-now Image.jpg\n") ;
}
return -1 ;
}
/* ----------------------------------------------------------- */
/* parseOptionDate - parse -option=date into two tokens */
/* ----------------------------------------------------------- */
bool parseOptionDate(const char* o,char** option,char** date)
{
bool result = false ;
*date = NULL ;
*option = NULL ;
if ( !o || o[0] != '-' ) return result ;
char* copy = strdup(o) ;
if ( !copy ) return result ;
result = true ;
char* equal = strchr(copy,'=') ;
if ( equal )
{
*equal = 0 ;
*option = strdup(copy) ;
*date = strdup(equal+1);
} else {
*option = strdup(copy) ;
}
// cleanup
free((char*) copy) ;
return result ;
}
/* ----------------------------------------------------------- */
/* getOption - which options? */
/* ----------------------------------------------------------- */
int getOption(const char* arg)
{
for ( int i = 0 ; i < lengthof(titles) ; i++ )
if ( strcasecmp(arg,titles[i]) == 0 )
return i ;
return -1 ;
}
/* ----------------------------------------------------------- */
/* reportDate - report the date of a file */
/* ----------------------------------------------------------- */
OSStatus reportDate(UInt8* path,int option)
{
if ( option < 1 || option >= lengthof(titles)) return paramErr ;
// get the fsRef for the path
FSRef fsRef ;
Boolean isDirectory ;
FSCatalogInfo fsCatalogInfo ;
OSStatus err = FSPathMakeRef ( path,&fsRef,&isDirectory) ;
if ( !err ) err = FSGetCatalogInfo (&fsRef,kFSCatInfoAllDates,&fsCatalogInfo,NULL,NULL,NULL) ;
UTCDateTime* myDateTime = option == 1 ? &fsCatalogInfo.createDate
: option == 2 ? &fsCatalogInfo.contentModDate
: option == 3 ? &fsCatalogInfo.attributeModDate
: option == 4 ? &fsCatalogInfo.accessDate
: option == 5 ? &fsCatalogInfo.backupDate
: NULL
;
// convert the time to a string and report it
if ( !err )
{
CFAbsoluteTime absoluteTime ;
UCConvertUTCDateTimeToCFAbsoluteTime(myDateTime,&absoluteTime) ;
CFDateFormatterRef dateFormatter = CFDateFormatterCreate(NULL,NULL, kCFDateFormatterLongStyle, kCFDateFormatterLongStyle) ;
CFStringRef dateString = dateFormatter
? CFDateFormatterCreateStringWithAbsoluteTime(NULL,dateFormatter,absoluteTime)
: NULL ;
if ( dateString )
{
char buffer[1000] ;
CFStringGetCString(dateString,buffer,sizeof buffer,kCFStringEncodingUTF8) ;
char title[50] ;
sprintf(title,"%s ",titles[option]) ;
title[17] = 0 ;
printf("%s : %s => %s\n",path,title,buffer ) ;
CFRelease(dateString) ;
}
if ( dateFormatter ) CFRelease(dateFormatter) ;
}
return err ;
}
/* ----------------------------------------------------------- */
/* getDate - parse a date string */
/* ----------------------------------------------------------- */
CFDateRef getDate(const char* s)
{
if ( strcasecmp(s,"-now") == 0 ) return CFDateCreate(NULL,CFAbsoluteTimeGetCurrent()) ;
// what type of formatting is this? Syntax: [CC]YYDDMMhh[.ss]
int l = strlen(s) ; // length of input string
int f = -1 ; // which format (0,1,2 or 3)
const char* dateFormats[] = { "yyyyMMddHHmm.ss", "yyyyMMddHHmm" , "yyMMddHHmm.ss" , "yyMMddHHmm" } ;
for ( int i = 0 ; f < 0 && i < lengthof(dateFormats) ; i++ )
if ( l == strlen(dateFormats[i]) )
f = i ;
const char* dateFormat = f != -1 ? dateFormats[f] : NULL ;
// create a formatter
CFLocaleRef currentLocale = CFLocaleCopyCurrent();
CFDateFormatterRef dateFormatter = CFDateFormatterCreate(NULL, currentLocale, kCFDateFormatterNoStyle, kCFDateFormatterNoStyle);
CFStringRef dateString = CFStringCreateWithBytes (NULL,(UInt8*)s,l,kCFStringEncodingUTF8,false) ;
CFStringRef dateFormatString= dateFormat ? CFStringCreateWithBytes (NULL,(UInt8*)dateFormat,strlen(dateFormat),kCFStringEncodingUTF8,false) : NULL ;
if ( dateFormat ) CFDateFormatterSetFormat(dateFormatter, dateFormatString );
// decode the date
CFDateRef date = dateFormat ? CFDateFormatterCreateDateFromString(NULL,dateFormatter,dateString,NULL) : NULL ;
// cleanup
CFRelease(currentLocale);
CFRelease(dateString);
CFRelease(dateFormatter);
if ( dateFormatString ) CFRelease(dateFormatString);
return date ;
}
/* ----------------------------------------------------------- */
/* setDate - set the date on a filename */
/* ----------------------------------------------------------- */
OSStatus setDate(UInt8* path,int option,CFDateRef date)
{
if ( option < 1 || option >= lengthof(titles)) return paramErr ;
// get the fsRef for the path
FSRef fsRef ;
Boolean isDirectory ;
FSCatalogInfo fsCatalogInfo ;
OSStatus err = FSPathMakeRef ( path,&fsRef,&isDirectory) ;
if ( !err ) err = FSGetCatalogInfo (&fsRef,kFSCatInfoAllDates,&fsCatalogInfo,NULL,NULL,NULL) ;
UTCDateTime* myDateTime = option == 1 ? &fsCatalogInfo.createDate
: option == 2 ? &fsCatalogInfo.contentModDate
: option == 3 ? &fsCatalogInfo.attributeModDate
: option == 4 ? &fsCatalogInfo.accessDate
: option == 5 ? &fsCatalogInfo.backupDate
: NULL
;
// set the date
if ( !err )
{
CFAbsoluteTime absoluteTime = CFDateGetAbsoluteTime (date) ;
UCConvertCFAbsoluteTimeToUTCDateTime(absoluteTime,myDateTime) ;
// save the contentModDate
CFAbsoluteTime contentModTime ;
UCConvertUTCDateTimeToCFAbsoluteTime (&fsCatalogInfo.contentModDate,&contentModTime) ;
// if we're setting createData, we also have to set contentModDate!
if ( option == 1 ) {
UCConvertCFAbsoluteTimeToUTCDateTime(absoluteTime,&fsCatalogInfo.contentModDate) ;
}
err = FSSetCatalogInfo (&fsRef,kFSCatInfoAllDates,&fsCatalogInfo) ;
// now restore the contentModDate
if ( !err && option == 1 && absoluteTime < contentModTime ) {
UCConvertCFAbsoluteTimeToUTCDateTime(contentModTime,&fsCatalogInfo.contentModDate) ;
err = FSSetCatalogInfo (&fsRef,kFSCatInfoContentMod,&fsCatalogInfo) ;
}
}
return err ;
}
/* ----------------------------------------------------------- */
/* class Option - parse result of "-option[=date]" */
/* ----------------------------------------------------------- */
class Option
{
public:
Option() : m_date(NULL) , m_report(false) {} ;
virtual ~Option() { setDate(NULL) ; }
void setDate(CFDateRef date)
{
if ( date ) CFRetain(date) ;
if ( m_date ) {
CFRelease(m_date) ;
m_date = NULL ;
}
if ( date ) {
m_date = date ;
}
}
CFDateRef getDate() { return m_date ; }
void setReport(bool report) { m_report = report ; }
bool getReport(void) { return m_report ; }
private:
CFDateRef m_date ;
bool m_report ;
} ;
/* ----------------------------------------------------------- */
/* fexist - does a filename exist (link,dir or file) */
/* ----------------------------------------------------------- */
bool fexist(const char* path)
{
struct stat buff ;
return stat(path,&buff) == 0 ;
}
/* ----------------------------------------------------------- */
/* parseOption -option[=date] */
/* ----------------------------------------------------------- */
bool parseOption(const char* s,Option* Options,bool* pbOK)
{
// is it a file?
if ( !s || fexist(s) ) return false ;
char* o = NULL ;
char* d = NULL ;
bool bOK = parseOptionDate(s,&o,&d) ;
int option = bOK && o ? getOption(o) : -1 ;
if ( !bOK ) fprintf(stderr,"%s: %s\n",s[0]=='-' ? "illegal argument" : "unknown file" ,s) ;
if ( bOK && !o ) {
fprintf(stderr,"no option found %s\n",s) ;
bOK = false ;
}
// found an option?
if ( bOK && option == -1 )
{
fprintf(stderr,"unknown option %s\n",o) ;
bOK = false ;
}
// found a date?
CFDateRef date = NULL ;
if ( bOK && d ) {
date = getDate(d) ;
if ( !date ) {
fprintf(stderr,"date syntax error %s\n",d) ;
bOK = false ;
}
}
// update the Options array
if ( bOK ) {
if ( !option ) // -all
{
for ( int i = 1 ; i < lengthof(titles) ; i++ )
{
Options[i].setDate(date) ;
Options[i].setReport(true) ;
}
} else {
Options[option].setDate(date) ;
Options[option].setReport(true) ;
}
}
// cleanup
if ( date ) CFRelease(date) ;
if ( o ) free((void*) o) ;
if ( d ) free((void*) d) ;
*pbOK = bOK ;
return bOK ;
}
/* ----------------------------------------------------------- */
/* main - main function of the program of course */
/* ----------------------------------------------------------- */
int main (int argc, char * const argv[])
{
if ( argc < 2 ) return syntax(true) ;
// parse the command line
Option aOptions[lengthof(titles)] ;
bool bOK = true ; // argument is good
bool bOption = true ; // still processing options
int nFile = 0 ; // first file
// parse the -option[=date] string and find aFile
for ( int a = 1 ; bOK && a < argc ; a++ )
{
const char* arg = argv[a] ;
if ( bOption )
{
bOption = parseOption(arg,aOptions,&bOK) ;
}
if ( !bOption && bOK )
{
// fixup syntax myTouch filename => myTouch -all filename
if ( a == 1 ) parseOption("-all",aOptions,&bOK) ;
bOK = fexist(arg) ;
if ( !bOK ) {
fprintf(stderr,"file %s does not exist\n",arg) ;
}
if ( bOK && !nFile ) nFile = a ;
}
}
// do we have some files?
if ( bOK && !nFile ) {
bOK = false ;
fprintf(stderr,"no files given\n") ;
}
if ( !bOK ) return syntax(false) ;
// now process the files
OSStatus err = noErr ;
for ( int f = nFile ; !err && f < argc ; f ++ )
{
UInt8* path = (UInt8*) argv[f] ;
for ( int option = 1 ; !err && option < lengthof(titles) ; option++ )
{
CFDateRef date = aOptions[option].getDate() ;
bool report = aOptions[option].getReport() ;
if ( !err && date ) err = setDate (path,option,date) ;
if ( !err && report ) err = reportDate (path,option) ;
}
}
if ( err ) fprintf(stderr,"error %d\n",err) ;
return (int) err ;
}
/*
-----------------------------------------------------------------
* Revision History
Modified Reason
20071019 First published version
-----------------------------------------------------------------
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment