Skip to content

Instantly share code, notes, and snippets.

@feklee
Last active January 8, 2024 09:27
Show Gist options
  • Save feklee/c72a5088686143ea4328f72339db0c9e to your computer and use it in GitHub Desktop.
Save feklee/c72a5088686143ea4328f72339db0c9e to your computer and use it in GitHub Desktop.
Prepends media file names with time stamps of media creation in local time
#!/usr/bin/perl
# Traverses media files in the specified directory tree.
# Prepends the media creation time as a time stamp in the format
# "%HH%MM%SS_" to each media file name where the data is
# available. The time is local time.
# What looks like an existing time stamp is replaced.
# To configure media formats, edit the code below.
# Felix E. Klee <[email protected]>
use strict;
use warnings;
use Image::ExifTool qw(:Public);
use File::Find;
my @media_extensions =
("jpg", "jpeg", "arw", "sr2", "nef", "dng", "png", "mp4", "wav");
# %b = basename without extension, %e = extension:
my @sidecar_formats =
(
"%b.xmp", # Adobe Camera Raw
"%b.%e.xmp", # Darktable
"%b.acr", # Adobe Camera Raw masks, etc.
"%bM01.XML", "%b.%eM02.KLV" # Sony video metadata
);
my $exifTool = Image::ExifTool->new;
$exifTool->Options(DateFormat => '%H:%M:%S');
sub add_time_to_filename {
my ($time, $filename) = @_;
my $new_timestamp = $time =~ s/://gr;
my $part_without_timestamp = $filename =~ s/^\d{6}_//r;
my $new_filename = "${new_timestamp}_$part_without_timestamp";
print "Renaming $filename to $new_filename\n";
rename $filename, $new_filename or
die "Error renaming file from $filename to $new_filename: $!\n";
}
sub add_time_to_sidecar_filenames {
my ($time, $filename) = @_;
my ($base, $ext) = $filename =~ /^(.+)\.([^.]*)$/;
if (!defined $base) { # no match
return;
}
foreach my $format (@sidecar_formats) {
my $sidecar_name = $format;
$sidecar_name =~ s/%b/$base/g;
$sidecar_name =~ s/%e/$ext/g;
add_time_to_filename($time, $sidecar_name) if -f $sidecar_name;
}
}
sub is_supported_media {
my $filename = shift;
my $extensions_regex = join('|', @media_extensions);
return $filename =~ /\.($extensions_regex)$/i;
}
sub media_creation_time_of_mp4 {
my $info = shift;
my $manufacturer = $$info{DeviceManufacturer} // '';
my $model = $$info{DeviceModelName} // '';
my $creation_time;
if ($manufacturer eq "Sony" && $model eq "ZV-1") {
# Exif data has time in UTC, but we want the local time.
$creation_time = $$info{MediaCreateDate};
my $offset = "$$info{TimeZone}:00";
Image::ExifTool::ShiftTime($creation_time, $offset);
}
return $creation_time;
}
# Some files, e.g. some screenshots, don't contain the "Date/Time
# Original" EXIF tag, but they may have the date / time encoded in the
# file name.
sub media_creation_time_from_filename {
my $filename = shift;
my $creation_time;
my $xiaomi_screenshot = # Chinese Redmi Note 12 5G
qr/^Screenshot_(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})/;
my $field_recorder_wav = # Android Field Recorder app
qr/^(\d{2})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})\.wav$/;
if ($filename =~ $xiaomi_screenshot) {
$creation_time = "$4:$5:$6";
} elsif ($filename =~ $field_recorder_wav) {
$creation_time = "$4:$5:$6";
}
return $creation_time;
}
sub media_creation_time {
my ($info, $filename) = @_;
my $creation_time;
if ($filename =~ /\.mp4$/i) {
$creation_time = media_creation_time_of_mp4($info);
} else {
$creation_time = $$info{DateTimeOriginal};
}
if (!defined $creation_time) {
$creation_time = media_creation_time_from_filename($filename);
}
return $creation_time;
}
sub process_file {
my $filename = $_;
if (-f $filename && is_supported_media($filename)) {
my $info = $exifTool->ImageInfo($filename);
my $creation_time = media_creation_time($info, $filename);
if (defined($creation_time)) {
add_time_to_filename($creation_time, $filename);
add_time_to_sidecar_filenames($creation_time, $filename);
}
}
}
die "Usage: $0 directory\n" unless @ARGV == 1;
my $directory = $ARGV[0];
find(\&process_file, $directory);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment