#!/net/u/1/f/fdc/kermit/wermit + #!/usr/local/bin/kermit + #!/p/kd/fdc/solaris9/kermit + #!/opt/local/bin/kermit + # # RENAMEJPGS -- Rename JPG files by date. # # Frank da Cruz # fdc@kermitproject.org # NYC, created 14 April 2013. # .version = 1.1 .dated = 2013-04-15 .myname := \fbasename(\v(cmdfile)) # Name of this script program forward start # Skip around documentation ############################################################################ PURPOSE: Renames .jpg image files in the current directory according to date, thus putting images from different cameras or devices or wrapped-around numbering systems in chronological order by name. Works in Unix (the operating-system family that includes Linux, Mac OS X, FreeBSD, Android, and many others) and in Windows. MOTIVATION: Suppose images were uploaded to your computer in way that did not preserve their original dates. Or suppose that some of the images were edited, which changed their file date. Or suppose your camera's image names wrapped around from IMG_9999.JPG to IMG_0001.JPG, and now the newest images seem to be the oldest. Or suppose you use two or more cameras in a photo session, that produce JPGs with different kinds of names, but you want to merge the images into a single collection accessible in chronological order. USAGE: Unix usage (at shell prompt): renamejpgs files Windows usage (at K-95> prompt): take renamejpgs files You can also define Kermit or environment variables prior to invocation: SIMULATE=1 Show how files would be renamed without renaming them. SIMULATE=0 (default) Actually rename the files. VERBOSE=0 Only lists the files that are renamed. VERBOSE=1 (default) Lists all files VERBOSE=2 Also shows where image date comes from and some program info. DOALL=0 Only do JPG files whose names start with IMG_, DSCF, etc. DOALL=1 Do all JPG files that match the list or wildcard given. Unix examples: renamejpgs *.jpg SIMULATE=1 renamejpgs *.jpg SIMULATE=1 VERBOSE=0 renamejpgs *.jpg Windows example 1 (normal): take renamejpgs *.jpg Windows example 2 (for testing): define simulate 1 define verbose 2 take renamejpgs *.jpg Windows example 3 (for renaming after testing) define simulate 0 undefine verbose take renamejpgs *.jpg REQUIREMENTS: C-Kermit 9.0.304 Dev.04 or later on Unix is required for full functionality, in which Kermit itself extracts each image's "date taken" from the internal Exif header recorded by the camara or scanner, and uses its own new builtin \ffilecompare() function rather than calling external programs. The script also works with C-Kermit 8.0.201 (2002) or later in Unix and in 32- or 64-bit Windows with Kermit 95 1.1.20 (2001) or later but in these cases, the dates used are the file dates. BUT... if you have Kermit 95 or an older C-Kermit version, you can download and install the jhead ("JPG Header") utility by Matthias Wendel for "date taken" extraction from JPG Exif data, and this script will use it if necessary. As of this writing Jhead is available for both Unix and Windows here: http://www.sentex.net/~mwandel/jhead/ Kermit 95 and older versions of C-Kermit also depend on the standard file comparison program on each platform (cmp in Unix and fc in Windows) for deleting extraneous duplicate files, but the script will operate without them, in which case duplicates will not be eliminated. C-Kermit 9.0.304 uses its own built-in file comparison function, \ffilecompare(file1,file2). Finally, the script assumes that cameras and devices have their dates set correctly. If this is not the case, you can use jhead on its own to repair the the jpgs first. RESTRICTIONS: You have to CD to the directory where the jpgs are before running this script. Also, the script does not descend recursively through a directory tree, although that could be arranged with a little more programming. Also only the ".jpg" extension is handled; with a bit more programming, variations like ".jpeg" and ".jpe" could be handled too. This program won't rename files whose names don't match the "approved" patterns (like IMG_nnnn.JPG) unless you force it to. This mainly to allow it to be used again and again in the same directory, as you add image files to it over time, without danger of renaming already-renamed files, or files that you already gave descriptive names to yourself. It is possible when you are taking pictures with two or more cameras that different pictures will have the same filenames. In this case, you'll need to upload to separate directories, run this script on each set, and then combine them afterwards (since the chances are pretty slim that you took two pictures at exactly the same instant). If you upload them to the same directory, for example in Windows, either some photos will be lost, or else Windows will generate some odd-looking names (such as "Copy(2) of IMG_4321.JPG") that are not handled by this script (but could be, with some additional programming). In a simulation run (SIMULATE=1), this script does not simulate file deletion, because the file that would have caused the conflict was not actually created, so there is no conflict. COMMAND-LINE ARGUMENTS: The "files" argument can be a list of nonwild file names or a single wildcard like "*.jpg". This script operates only on files with names created by scanners or digital cameras, such as IMG_nnnn.JPG, DSCFnnnn.JPG, or SCANnnnn.JPG (case-independent) or others defined below (see \&i[] array declaration in the Configuration Parameters section); anything else is assumed to have already been renamed (or named by a human rather than a device). (The script also handles IMG_nnnnn.JPG [names with five digits], which came from an earlier attempt of mine to deal with wraparound.) A list of filenames that includes wildcards is currently not handled, though it could be with more programming. RESULTS: The renamed files come out with a numeric name: yyyymmddhhmmss.jpg, for example 20130414195624.jpg, so when sorted lexically as is normally done in directory listings, they appear in chronological order. Without the renaming, even if you requested a directory listing by date, that would be based on the file date rather than the date taken, and these could be very different, for example if image files were uploaded or copied in a non-date preserving manner. When renaming, if a collision would occur, for example if img_9724.jpg was about to be renamed to 20130414212633.jpg based on its internal date, but a file named 20130414212633.jpg already exists, then the script checks to see if the two files have identical contents. If they do, the file that was about to be renamed (img_9724.jpg in this example) is simply deleted. If they are not identical, a letter is appended to the name part (before the dot) of the new name: yyyymmddhhmmssa.jpg, yyyymmddhhmmssb.jpg, etc, the first one that would not cause another collision. If we run out of letters, then we append 2 letters, and so on. Note that these filenames are longer than the old MS-DOS limit of 8.3: 14.3 if there are no collisions, otherwise 15.3, 16.3, etc. It takes that many characters to express a date-time to the second in minimally human-legible fashion. RECOMMENDATION: 1. Upload files from each camera or device to a unique directory to prevent filename collisions. 2. Run them through this script before doing anything else with them. 3. Now you can merge the renamed files from the various directories into a common directory for processing or editing. If you "Save for Web" in Photoshop the "date taken" is still preserved in the filename. SIDE EFFECTS: In Windows, if you are using jhead for date extraction, or if there is a filename collision, a command window pops up for each file and then promptly disappears; annoying but normal for command-line applications in Windows that invoke external commands. UNIX INSTALLATION: 1. Download the latest C-Kermit development build from here: http://www.kermitproject.org/ckscripts.html 2. If you can't build or install the new C-Kermit for some reason, download jhead from here: http://www.sentex.net/~mwandel/jhead/ 3. Put this file (renamejpgs) in a directory that is in your PATH. 4. Change the first line of this file to indicate the full path of C-Kermit on your computer, followed by a space and a plus sign (+). 5. Give this file execute permission ("chmod +x renamejpgs"). Then you can invoke this script by name regardless of your current directory. WINDOWS INSTALLATION: 1. (Recommended) Download jhead from here: http://www.sentex.net/~mwandel/jhead/ 2. If necessary, make a SCRIPTS subdirectory of your login directory. 2. Put this file in it. Then you can invoke this script with "take renamejpgs" no matter what your current directory is. Tested with JPGs from Canon, Fuji, Olympus, and other camaras going back to 1999, and from HP flatbed scanners. :START # End of documentation, beginning of program. ############################################################################ # CONFIGURATION PARAMETERS # Array of valid input filename patterns, case independent. # Note dashes for continuation of the initialization list. # Add new patterns using the same syntax. # declare \&i[] - img_[0-9][0-9][0-9][0-9].jpg - # Canon and many others dscf[0-9][0-9][0-9][0-9].jpg - # Fuji and others scan[0-9][0-9][0-9][0-9].jpg - # HP scanner 10[0-9]_[0-9][0-9][0-9][0-9].jpg - # Kodak pb[0-9][0-9][0-9][0-9][0-9][0-9].jpg - # Olympus img_[0-9][0-9][0-9][0-9][0-9].jpg # (special) .oprefix = # Output filename prefix (none by default) if not def verbose { # Verbosity .verbose = 1 # Level of feedback to user (0, 1, or 2) } if not def simulate { # Set simulate to 1 for testing. .simulate = 1 # Show how files would be renamed but don't do it. .simulate = 0 # Actually rename the files. } if not def doall { # Set doall to 1 to do all JPGs rather than only .doall = 0 # those whose name start with IMG, DSCF, etc. } # (See below for how to set these without modifying this script) ############################################################################ # PLATFORM DEPENDENCIES switch \v(system) { :WIN32 # Windows .null = NUL # Null device for output redirection .compare = fc # File comparison command .jhead = jhead.exe # If jhead in PATH # .jhead = c:/users/fdc/bin/jhead.exe # Example if jhead not in PATH break :UNIX # Unix (Linux, Mac OS X, FreeBSD etc) .null = /dev/null # Null device for output redirection .compare = cmp # File comparison command .jhead = jhead # Assumes jhead in PATH break :default echo "Sorry, this script hasn't been adapted to \v(system) yet." exit 1 } ############################################################################ # MACRO DEFINITIONS def fatal { # Fatal error macro echo Fatal - \%* if k-95 stop 1 # Don't exit in Windows because exit 1 # the window will disappear } def usage { # Give usage message and quit echo echo \m(myname) \m(version) \m(dated) - Rename jpg files by date echo if k-95 { # For Windows echo "To be invoked at the K-95> prompt:" echo echo " take \m(myname) wildcard (example: \m(myname) *.jpg)" echo "or:" echo " take \m(myname) file1 file2 file3..." echo echo " Optionally define SIMULATE and/or DOALL and/or VERBOSE first." echo echo "Example: " echo " define simulate 1" echo " define verbose 0" echo " take \m(myname) *.jpg" echo echo "See comments for documentation: \v(cmdfile)" echo stop 1 } echo "Usage: \m(myname) wildcard (example: \m(myname) *.jpg)" echo "or: \m(myname) file1 file2 file3..." echo echo " Optionally precede by SIMULATE=1, VERBOSE=0 (or 2), and/or DOALL=1" echo echo "Example: VERBOSE=0 SIMULATE=1 \m(myname) *.jpg" echo echo "See comments for documentation: \v(cmdfile)" echo exit 1 } def vecho { if verbose echo \%* } def vvecho { if vverbose echo \%* } ############################################################################ # ACTION if not def \%1 usage # Exit with usage message if no args # In Unix you can invoke this script with: SIMULATE=1 renamejpgs args if not equ "\$(SIMULATE)" "" if numeric \$(SIMULATE) .simulate := \$(SIMULATE) # Ditto for VERBOSE=0 to be more quiet or VERBOSE=2 for extra messages if not equ "\$(VERBOSE)" "" if numeric \$(VERBOSE) .verbose := \$(VERBOSE) # Ditto for DOALL=1 to do all jpg files, not just the ones with # camera or scanner generated names. if not equ "\$(DOALL)" "" if numeric \$(DOALL) .doall := \$(DOALL) .vverbose = 0 # Very Verbose if > verbose 1 .vverbose = 1 if vverbose { echo \m(myname) \m(version) executed by \v(program) \v(version) } if verbose { if simulate echo SIMULATING... else echo RENAMING... } # Output file pattern .opattern := \m(oprefix){19,20}[0-9][0-9][0-9][0-9][0-9][0-9]- [0-9][0-9][0-9][0-9][0-9][0-9]*.jpg # Feature selection # The \fpictureinfo() funcion is available only in C-Kermit 9.0 and later. .have_pictureinfo = 0 if not llt \v(version) 900300 .have_pictureinfo = 1 .have_filecompare = 0 if not llt \v(version) 900304 { .have_filecompare = 1 if def \v(test) { if equ "\fleft(\v(test),4)" "Dev." { if llt "\fright(\v(test),3)" ".05" { .have_filecompare = 0 } } } } # Jhead is available only if it has been installed. .have_jhead = 0 !\m(jhead) -V > \m(null) if success .have_jhead = 1 # Note: cmp is assumed to be available on Unix and fc.exe on Windows, # and will be used if not have_filecompare; no harm is done if they # are not found, but in that case duplicate files will not be deleted. if vverbose show mac have_pictureinfo have_jhead have_filecompare # Note: Top-level \&_[] array can not be seen from inside a macro. if wild \%1 void \ffiles(\%1,&a) # Windows doesn't expand command-line args else array copy &_ &a # File list is now in &a array. set case off # Case does not matter below. for i 1 \fdim(&a) 1 { # Loop to do each file... .file := \&a[i] if \findex(/,\m(file)) { echo "This program works only on JPG files in the current directory" exit 1 } if not exist \m(file) { vecho "File not found: \m(file)" continue } if not equ ".jpg" "\fright(\m(file),4)" { # Only do .jpg files vecho "File not .jpg: \m(file)" continue } if directory \m(file) { # Is the file a directory? vecho "Skipping directory: \m(file)" continue } # (Here we could test if it's a directory and if so recurse into it.) .isok = 0 # See if this file is a candidate for renaming... if doall .isok = 1 # If doing all JPG don't consider prefixes if not isok if declared &i { # If we have an array of prefixes for k 1 \fdim(&i) 1 { # Check the prefixed names if match \m(file) \&i[k] { .isok = 1 break } } } if isok if doall { # But if we created this one ourselves if match \m(file) \m(opattern) { # don't rename it again. vecho Skipping already-renamed file: \m(file) continue } } if not isok { vecho Skipping non-camera filename: \m(file) continue } .dt = # Initialize filename stem if have_pictureinfo { # This is not available in K-95. void \fpictureinfo(\m(file),&p) # Get Exif data from JPG contents. if def \&p[3] { .dt := \fsubstitute(\&p[3],:\32,) # remove punctuation vvecho "\m(dt) [DATE TAKEN] \m(file)" } } if not def dt if have_jhead { .xx := \fcommand(\m(jhead) \m(file)) # Get Exif data report from jhead if fail fatal "Jhead error" # Shouldn't happen .\%9 := \findex(Date/Time,\m(xx)) # Search Date Taken from Exif data if \%9 { # If found... .\%8 := \fsubstri(\m(xx),\%9+15,19) # extract the date-time string .dt := \fsubstitute(\%8,:\32,) # and remove punctuation if not numeric \m(dt) { vecho "WARNING - \m(file): non-numeric Exif date '\m(dt)'" .dt = } vvecho "\m(dt) [DATE TAKEN] \m(file)" } } if not def dt { # Still no date? .dt := \fsubstitute(\fdate(\m(file)),:\32,) # Use external file date vvecho "\m(dt) [DATE MODIFIED] \m(file)" } # Use date-time string as filename and check for collisions .dt := \m(oprefix)\m(dt) # Date-based name with any prefix. .newname := \m(dt).jpg # and with .jpg extension. if ( exist \m(newname) && not simulate ) { # Check if the two files are identical... .delete = 0 if = \fsize(\m(file)) \fsize(\m(newname)) { # If sizes are equal # Compare contents if have_filecompare { # Built-in Kermit function if not \ffilecompare(\m(file),\m(newname)) .delete = 1 } else { # External command ! \m(compare) \m(file) \m(newname) > \m(null) if success .delete = 1 } if delete { # If they are the same vecho DELETING duplicate \m(file) = \m(newname)... delete \m(file) # delete this one continue # and go on to next file } } # Files have differnet contents, must make a new name. .\%c := \fcode(a) # Suffix letter for collisions. while exist \m(newname) { # Check for collision .newname := \m(dt)\fchar(\%c).jpg # Add suffix letter incr \%c # Next suffix letter if needed # If we reach z then start doing za zb zc etc if > \%c \fcode(z) { .dt := \m(dt)z, .\%c := \fcode(a) } } } if simulate echo rename /list \m(file) \m(newname) else rename /list \m(file) \m(newname) } if not k-95 exit ; Local Variables: ; comment-column:40 ; comment-start:"# " ; End: