Last active
January 8, 2024 09:27
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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