Renaming file using multiple date references mixed in with other tags?

Started by Archetyped, April 05, 2017, 05:42:31 PM

Previous topic - Next topic

Archetyped

Hi, what would be the best way to rename files using differently-formatted references to DateTimeOriginal separated by other tags?

For example: 2017_Canon_12-17_080123.jpg (year_make_month-date_HMS.extension)

Since -d can only be used once, and it doesn't look like it's possible to include other tags in the date format string, what would be the best way to mix formatted date values and other tags in the new file name?

Thanks.

Archetyped

I poked around a bit more and this is what I have so far.  I made a custom DateFMT tag that takes additional parameters to format an image's other date tags using custom formatting.  Error checking is purposefully kept at a minimum (since it was just built for my needs), but Perl is not my 1st language, so I'm sure I'm doing some things wrong.  Let me know if you see any improvements I can make.

Here's the custom tag added to ExifTool's config file:


%Image::ExifTool::UserDefined = (
  'Image::ExifTool::Composite' => {
    # Formatted Date
    DateFMT => {
      ValueConv => sub {
        use Time::Piece;
        my ($val, $tag, $fmt, $dt);
        # Clean args
        my $args = $1;
        $args =~ s/^[\'\"]+|[;\'\"]+$//g;
        @args = split(';', $args, 2);
        #Tag is defined
        if ( @args > 1 ) {
          # Get tag data
          $tag = shift(@args);
          $tag =~ s/^\s+|\s+$//g;
        }
        # Tag is not defined. Use default tag value (DateTimeOriginal)
        else {
          $tag = 'DateTimeOriginal';
        }
        # Get Tag data
        my $et = pop;
        $val = $et->GetInfo($tag);
        $val = $$val{$tag};
        # Prepare date format string
        my $fmtParse = "%Y:%m:%d %H:%M:%S";
        if ( @args > 0 ) {
          $fmt = pop(@args);
          $fmt =~ s/^[\'\"]+|[\'\"]+$//g;
        } else {
          $fmt = $fmtParse;
        }
        # Clean date: Strip Subseconds/Timezone
        $val =~ s/^(.+)[-\.]\d+.+$/$1/;
        # Parse Date
        $dt = Time::Piece->strptime($val, $fmtParse);
        return $dt->strftime($fmt);
      },
    },
  },
);


The tag can then be used on the command-line to format dates based on the parameters you give it.

By default (without passing any additional parameters), it returns DateTimeOriginal in the standard EXIF date format:

exiftool -p "${DateFMT}" File
# Example Output: 2017:03:31 16:30:29


The date can be formatted using standard date-formatting codes:
exiftool -p "${DateFMT;'%Y-%m-%d'}" File
# Example Output: 2017-03-31


You can also specify a different date tag to format:
exiftool -p "${DateFMT;'CreateDate;%Y-%m-%d_%H%M%S'}" File
# Example Output: 2017-03-31_163029


This allows me to insert formatted date strings anywhere I need to when renaming files, etc.  The example file name format in my original post would look like this:
exiftool "-filename<${DateFMT;'%Y'}_${Make}_${DateFMT;'%m-%d_%H%M%S'}.%e" image0001.jpg
# Example Output: 2017_Canon_12-17_080123.jpg


Hayo Baan

Looking good! However, wouldn't your purpose be sufficiently serverved with the -d date format option of exiftool itself? https://exiftool.org/exiftool_pod.html#Input-output-text-formatting
Hayo Baan – Photography
Web: www.hayobaan.nl

Archetyped

It's definitely possible that it could meet my needs, but I just haven't figured out how yet.  As per my original question, can the -d option contain other tags so that I can create file names where date data can be intermingled with other tags when renaming files (example file name in the original post)?  If so, how?

Thanks.

StarGeek

I don't believe the -d can contain other tags, but there are some workarounds.

For example, you can use -d for the most complex part of the date, then directly edit the least complex using the hashtag # and regex.  For example:
exiftool -d "%m-%d_%H%M%S.%%e" "-filename<${DateTimeOriginal#;s/^(\d{4}).*/$1/}_${Make;}_${DateTimeOriginal}"

Here, ${DateTimeOriginal#;s/^(\d{4}).*/$1/} accesses the original value because of the #.  The regex that follows grabs the first four digits, i.e. the year, and saves only that part.

But for a cleaner command, a user-defined tag is better.  And you are less likely to have errors due to typos as you might with a more complex command like the one above.
* Did you read FAQ #3 and use the command listed there?
* Please use the Code button for exiftool code/output.
 
* Please include your OS, Exiftool version, and type of file you're processing (MP4, JPG, etc).

Phil Harvey

Your config file is genius!  It took me a while to figure out how you were doing this.  Very very smart.  You are obviously very good at Perl and have studied the ExifTool source code.

I have a few comments/suggestions:

1. The ValueConv relies on the value of $1 being the formatting expression.  The fact that $1 contains the expression is pure luck, and may change in future versions.  I will add an ExifTool member variable called "FMT_EXPR" in version 10.49 and guarantee that this will work for future releases.

2. As written, the optional tag name was case sensitive.  I have changed this to make it insensitive.

3. I reserve the right to add additional arguments in function calls, so I have changed "pop" to "$_[1]" to avoid future problems.

4. I have changed the code to fail gracefully if Time::Piece is missing and added better handling of strptime errors.

5. I have changed the way quotes are stripped to allow the format string to have embedded quotes at the start or end.

Attached is a new config file with these updates that will work the current and future versions of ExifTool.

- Phil

Edit:  This config file may be useful to others so I'm thinking about packaging it with the next ExifTool release.
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Archetyped

Thanks Phil!  This is actually the first time I've written Perl, but the example.config gave me pointers on what to search for as I cobbled the custom tag together.  Finding a reference to the format expression was indeed a random discovery, but I was nonetheless happy to get access to it, and am doubly happy to know that it will be directly accessible in future updates.

Thanks for the updates to the config as well, I've now learned a few more things about Perl ;)  I did notice that if the custom tag is matched by a wildcard tag on the command-line (e.g. exiftool -Date* Image001.jpg), it will cause nothing to be output (instead of a list of matching tags and their values).  This appears to occur if $et->GetInfo($tag); is called.  Perhaps you might know why or if there is a fix?

After making this tag, it occurred to me that it would be great if simple strftime format strings could be passed to any date-based tag using the built-in advanced formatting functionality.  Basically, if the tag contains a standard EXIF date string, and the format expression was a simple string containing date formatting codes, then it would format the date accordingly.
exiftool -p ${DateTimeOriginal;'%Y'} Image001.jpg
# Outputs: 2017


Alternatively, is there a way to hook into the formatting of all tags (like a global PrintConv)?  This could be used to check the tag value for a date and format it based on the format string (if included).

Phil Harvey

Wow.  I'm impressed that this was your first time with Perl.  Sort of miraculous actually.

Quote from: Archetyped on April 06, 2017, 02:54:48 PM
I did notice that if the custom tag is matched by a wildcard tag on the command-line (e.g. exiftool -Date* Image001.jpg), it will cause nothing to be output (instead of a list of matching tags and their values).  This appears to occur if $et->GetInfo($tag); is called.  Perhaps you might know why or if there is a fix?

I don't have time to look into this right now, but I'll post back later about this.

QuoteAfter making this tag, it occurred to me that it would be great if simple strftime format strings could be passed to any date-based tag using the built-in advanced formatting functionality.  Basically, if the tag contains a standard EXIF date string, and the format expression was a simple string containing date formatting codes, then it would format the date accordingly.

Interesting idea.  Let me think about this too.

QuoteAlternatively, is there a way to hook into the formatting of all tags (like a global PrintConv)?

There is a global API Filter option that can be used to evaluate an expression to modify the values of all tags.  But the beauty of what you did is that it allows different formatting of individual tags.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Phil Harvey

OK, I've had time to look at the GetInfo() problem.  You're right.  I shouldn't be calling GetInfo() recursively -- it was doing bad things.

About your idea of allowing simple strftime formatting codes in the advanced formatting:  I don't like the idea of having to parse the date and the expression before deciding whether or not to apply the format.  Instead, ExifTool could define a DateFmt() utility function that you could use like this:

exiftool -p "${datetimeoriginal;DateFmt('%Y')}" FILE

I'm thinking of adding this to the next release.

- Phil

Edit:  This could be the documentation for the new feature:

            ExifTool provides a "DateFmt" utility to simplify reformatting of
            individual date/time values.  The function acts on a standard
            EXIF-formatted date/time value in $_ and formats it according to
            the specified format string (see the -d option).  For example:

                exiftool -p "${createdate#;DateFmt('%Y-%m-%d_%H%M%S')}" a.jpg


(Compare this to the command when using your config file:)

                exiftool -p "${DateFmt;'CreateDate;%Y-%m-%d_%H%M%S'}" a.jpg
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Archetyped

Thanks Phil, does that mean GetInfo() cannot be used in any custom tags?

Thanks for the DateFmt function as well, it will make formatting dates much easier :)

Phil Harvey

...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).