3 #=======================================================================
5 # File ID: 4bec96e4-cc13-11de-a8a7-93dd800a3f5e
7 # Insert timestamp into file names
10 # ©opyleft 2009– Øyvind A. Holm <sunny@sunbase.org>
11 # License: GNU General Public License version 2 or later, see end of
12 # file for legal stuff.
13 #=======================================================================
23 my $std_exif_tag = "DateTimeOriginal";
31 'exif-tag' => $std_exif_tag,
44 $progname =~ s/^.*\/(.*?)$/$1/;
45 our $VERSION = '0.2.0';
47 Getopt
::Long
::Configure
('bundling');
50 'bwf' => \
$Opt{'bwf'},
51 'delete|d' => \
$Opt{'delete'},
52 'dry-run|n' => \
$Opt{'dry-run'},
53 'exif-tag|E=s' => \
$Opt{'exif-tag'},
54 'exif|e' => \
$Opt{'exif'},
55 'force|f' => \
$Opt{'force'},
56 'git|g' => \
$Opt{'git'},
57 'help|h' => \
$Opt{'help'},
58 'quiet|q+' => \
$Opt{'quiet'},
59 'replace|r' => \
$Opt{'replace'},
60 'skew|s=i' => \
$Opt{'skew'},
61 'verbose|v+' => \
$Opt{'verbose'},
62 'version' => \
$Opt{'version'},
64 ) || die("$progname: Option error. Use -h for help.\n");
66 $Opt{'verbose'} -= $Opt{'quiet'};
67 if ($Opt{'delete'} && $Opt{'replace'}) {
68 warn("$progname: Cannot mix -d/--delete and -r/--replace options\n");
71 $Opt{'help'} && usage
(0);
72 if ($Opt{'version'}) {
77 my $d = '[\dX]'; # Legal regexp digits, 0-9 or X (unknown)
78 my $r_date = "[12]$d$d$d" . # year
94 die("$progname: Missing filenames. Use -h for help.\n");
97 my $exiftool_version = `exiftool -ver 2>/dev/null`;
98 if (!defined($exiftool_version) || $exiftool_version !~ /^\d+\.\d+/) {
99 printf(STDERR
"$progname: exiftool(1) not found, " .
100 "required by -e/--exif\n");
105 for my $Curr (@ARGV) {
106 msg
(2, "Curr = '$Curr'");
118 warn("$progname: $File: Not a regular file\n");
121 if (!$Opt{'delete'} && !$Opt{'replace'} && numdates
($File) > 0) {
122 warn("$progname: $File: Filename already has date\n");
125 msg
(3, sprintf("mod_date(%s) = '%s'", $File, mod_date
($File)));
127 my $mod_date = $Opt{'exif'} ? exif_date
($File) : mod_date
($File);
128 my $start_date = start_date
($File);
129 return if (!$mod_date);
130 $mod_date += $Opt{'skew'};
131 $start_date += $Opt{'skew'} if ($start_date);
132 my $dates = sprintf("%s%s%s",
133 $start_date ? sec_to_string
($start_date) : "",
134 $start_date ?
"-" : "",
135 sec_to_string
($mod_date),
137 if (length($dates)) {
138 my ($basename, $dirname) = fileparse
($File);
139 my $new_name = $basename;
140 if ($Opt{'replace'}) {
141 $new_name = strip_date_from_filename
($new_name);
143 if ($Opt{'delete'}) {
144 $new_name = strip_date_from_filename
($new_name);
146 $new_name = "$dates.$new_name";
148 $dirname eq "./" && ($dirname = '');
149 $new_name = "$dirname$new_name";
150 if ($new_name eq "$File") {
151 msg
(1, "Filename for $File is unchanged");
154 if ($Opt{'dry-run'}) {
155 print("$progname: '$File' would be renamed to '$new_name'\n");
157 if (-e
$new_name && !$Opt{'force'}) {
158 warn("$progname: $new_name: File already exists, " .
159 "use --force to overwrite\n");
160 } elsif (rename_file
($File, $new_name)) {
161 print("$progname: '$File' renamed to '$new_name'\n");
163 warn("$progname: $File: Cannot rename file to '$new_name': " .
173 my ($oldname, $newname) = @_;
177 $retval = mysystem
('git', 'mv', $oldname, $newname);
180 $retval = rename($oldname, $newname);
192 msg
(0, "Executing \"" . join(' ', @cmd) . "\"...");
193 $retval = system(@cmd);
200 # Return file modification timestamp {{{
203 my @stat_array = stat($File);
204 if (scalar(@stat_array)) {
205 $Retval = $stat_array[9];
207 warn("$progname: $File: Cannot stat file: $!\n");
218 if ($str =~ /^$r_date-$r_date/) {
220 } elsif ($str =~ /^$r_date/) {
225 msg
(3, "numdates('$str') returns '$retval'");
230 sub strip_date_from_filename
{
234 $retval =~ s/^20......T......*?Z\.(.*$)/$1/;
235 msg
(3, "strip_date_from_filename('$file') returns '$retval'");
238 } # strip_date_from_filename()
241 # Find start of recording {{{
245 my $bwf_date = bwf_date
($File);
250 msg
(2, "start_date($File) returns '$Retval'");
256 # Find start of recording in Broadcast Wave Format files {{{
257 # This is based on examining .wav files from the Zoom H4n, and it
258 # seems to work there. The file format may vary on other devices.
261 unless (open(InFP
, "<", $File)) {
262 warn("$progname: $File: Cannot open file to look for BWF data: $!\n");
266 my $numread = read(InFP
, $buf, 358);
267 if ($numread != 358) {
268 warn("$progname: $File: Could not read 358 bytes, but continuing: " .
271 if ($buf =~ /^.*(\d\d\d\d)-(\d\d)-(\d\d)(\d\d):(\d\d):(\d\d)$/s) {
272 $Retval = timegm
($6, $5, $4, $3, $2-1, $1);
275 msg
(2, "bwf_date($File) returns '$Retval'");
285 $retval = get_exif_data
($File, $Opt{'exif-tag'});
286 msg
(2, "exif_date(): \$retval before check = \"$retval\"");
287 if ($retval =~ /^(\d\d\d\d).(\d\d).(\d\d).(\d\d).(\d\d).(\d\d)$/) {
288 $retval = timegm
($6, $5, $4, $3, $2-1, $1);
293 msg
(1, "$File: No EXIF data found in file");
295 msg
(2, "exif_date() returns \"$retval\"");
302 my ($file, $tag) = @_;
306 if (!open(FromFP
, "exiftool -j \"$file\" |")) {
307 printf(STDERR
"$progname: $file: Cannot open file for read\n");
310 while ($line = <FromFP
>) {
311 if ($line =~ /"$tag"/) {
312 msg
(2, "get_exif_data() found \"$line\"");
313 $line =~ s/^.*?"$tag"\s*:\s*"(.*?)".*/$1/s;
314 msg
(2, "\$line after regexp: \"$line\"");
324 # Convert seconds since 1970 to "yyyymmddThhmmss[.frac]Z" {{{
325 my ($Seconds, $Sep) = @_;
326 length($Seconds) || return('');
327 ($Seconds =~ /^-?(\d*)(\.\d+)?$/) || return(undef);
328 my $Secfrac = ($Seconds =~ /^([\-\d]*)(\.\d+)$/) ?
1.0*$2 : "";
331 defined($Sep) || ($Sep = " ");
332 my @TA = gmtime($Seconds);
333 my($DateString) = sprintf("%04u%02u%02uT%02u%02u%02u%sZ",
334 $TA[5]+1900, $TA[4]+1, $TA[3],
335 $TA[2], $TA[1], $TA[0], $Secfrac);
341 # Print program version {{{
342 print("$progname $VERSION\n");
348 # Send the help message to stdout {{{
351 if ($Opt{'verbose'}) {
357 Insert filemod timestamp into filename, and start of recording if
358 available. At the moment only BWF (Broadcast Wave Format, standard .wav
359 with extra metadata) is supported.
363 No timestamp for start of recording:
364 yyyymmddThhmmssZ.OLDFILENAME
365 With timestamp for start of recording:
366 yyyymmddThhmmssZ-yyyymmddThhmmssZ.OLDFILENAME
368 Usage: $progname [options] file [files [...]]
373 Find start of recording in Broadcast Wave Format files. This is
374 based on examining .wav files from the Zoom H4n, and it seems to
375 work there. The file format may vary on other devices.
377 Delete timestamp from filename. Can not be used with -r/--replace.
379 Use timestamp from EXIF data in the file.
380 -E TAG, --exif-tag TAG
381 Use TAG when creating timestamp from the EXIF data.
382 Default: "$std_exif_tag".
384 If a file with the new name already exists, allow the program to
387 Use git commands when dealing with files. For example, execute the
388 command "git mv oldname newname" when renaming files.
390 Don’t rename files, but report what would happen.
394 Be more quiet. Can be repeated to increase silence.
396 Replace date in filename with new value. Can not be used with
399 Adjust clock skew by adding X seconds to the timestamp. A negative
400 integer can also be specified.
402 Increase level of verbosity. Can be repeated.
404 Print version information.
412 # Print a status message to stderr based on verbosity level {{{
413 my ($verbose_level, $Txt) = @_;
415 if ($Opt{'verbose'} >= $verbose_level) {
416 print(STDERR
"$progname: $Txt\n");
424 # This program is free software; you can redistribute it and/or modify
425 # it under the terms of the GNU General Public License as published by
426 # the Free Software Foundation; either version 2 of the License, or (at
427 # your option) any later version.
429 # This program is distributed in the hope that it will be useful, but
430 # WITHOUT ANY WARRANTY; without even the implied warranty of
431 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
432 # See the GNU General Public License for more details.
434 # You should have received a copy of the GNU General Public License
435 # along with this program.
436 # If not, see L<http://www.gnu.org/licenses/>.
438 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :