#!/usr/bin/perl use 5.14.0; use strict; use warnings; use File::Copy qw(move); use File::Basename qw(basename dirname); use File::Path qw(make_path); #my $library = "/home/sir/Music/Songbird Music"; my $library = "/home/sir/Music/Songbird Music"; my (%t, %files, @files, @dirs); my $flac_version = '1.2.1'; getdirs($library); foreach my $dn (sort(@dirs)) { #my $ino = (stat($fn))[1]; #say "$dn has $ino"; undef %files; undef @files; getfiles($dn); my $fc = @files; if ($fc > 0) { replaygain($dn); for (my $n = 0; $n < $fc; $n++) { my $fn = $files[$n]; undef(%t); foreach my $tag (keys($files{$fn})) { $t{$tag} = $files{$fn}{$tag}->[0]; #say "$tag = $t{$tag}"; } vendor($fn); rmtag($fn, 'rating'); albumartist($fn, $fc); track($fn); spaces($fn); totaltracks($fn, $fc); # empty($fn); duplicate($fn); discnum($fn, $n, $fc); } } } # Make this subroutine and the whole script so that each file name is stored in a hash and that each member contains an anonymous hash with the tags (the alltags hash). # This means it won't have to look up the tags again for the albumartist subroutine. sub gettags { my $fn = shift; my (%alltags, @mflac); open(OUTPUT, '-|', qq{metaflac --no-utf8-convert --show-vendor-tag --export-tags-to=- "$fn"}) || die "can't run metaflac: $!"; chomp(@mflac = ()); foreach (@mflac) { my (@tag, $tagname); if (/^reference/) { @tag = split(' '); $tagname = 'vendor_ref'; $tag[1] = $tag[2]; } else { @tag = split('='); $tagname = lc($tag[0]) or next; } if ($tag[1]) { push(@{$alltags{$tagname}}, $tag[1]); } else { push(@{$alltags{$tagname}}, 'null'); } } close(OUTPUT) || die "couldn't close metaflac: $!"; return %alltags; } sub vendor { my $fn = shift; if ($t{vendor_ref} ne $flac_version) { my $newfn = $fn . '.' . int(rand(10000)); say "$fn: old encoder ($t{vendor_ref}), re-encoding..."; system('flac', '--silent', '-8', "$fn", '--output-name=' . "$newfn"); if ($? == 0) { move($newfn, $fn) or die "Couldn't rename '$newfn': $!"; } elsif ($? == 2) { say "Interrupted by user!"; unlink($newfn) if (-f $newfn); exit; } } } sub getdirs { my $dn = shift; open(FIND, '-|', qq(find -L "$dn" -name "*" -type d)) or die "Can't run 'find': $!"; chomp(@dirs = ()); close(FIND) or die "Can't close 'find': $!"; } sub getfiles { my $dn = shift; my $fn; opendir(my $dh, $dn) or die "Can't open directory '$dn': $!"; foreach (readdir $dh) { $fn = $dn . '/' . $_; if (/.flac$/ && -f $fn) { push(@files, $fn); $files{$fn} = { gettags($fn) }; } } closedir $dh or die "Can't close directory '$dn': $!"; } sub replaygain { my $dn = shift; my %replaygain; #foreach ('REFERENCE_LOUDNESS', 'TRACK_GAIN', 'TRACK_PEAK', 'ALBUM_GAIN', 'ALBUM_PEAK') { # my $tag = 'REPLAYGAIN_' . $_; # system('metaflac', '--remove-tag=' . $tag, keys(%files)); #} foreach my $fn (sort(keys %files)) { if ($files{$fn}{replaygain_album_gain}) { $replaygain{$files{$fn}{replaygain_album_gain}->[0]}++; } } if (keys(%replaygain) != 1) { print "$dn: adding ReplayGain..."; system('metaflac', '--add-replay-gain', keys(%files)); say " done"; } } sub albumartist { my $fn = shift; my $tracks = shift; if (! $t{albumartist} || $t{albumartist} eq 'null') { my %artist; my $max; if ($tracks == 1) { $max = $tracks } else { $max = $tracks / 2 } foreach my $fn (sort(keys(%files))) { $artist{$t{artist}} = 1; } if (keys(%artist) > $max) { $t{albumartist} = 'Various Artists'; } else { $t{albumartist} = $t{artist} }; say "$fn: adding albumartist tag"; system('metaflac', '--remove-tag=ALBUMARTIST', '--set-tag=ALBUMARTIST=' . $t{albumartist}, "$fn"); } } sub empty { my $fn = shift; foreach my $tag (sort(keys(%{$files{$fn}}))) { foreach (@{$files{$fn}{$tag}}) { if ($_ eq 'null') { say "$fn: WARNING: empty $tag tag"; } } } } sub duplicate { my $fn = shift; my %exists; foreach my $field (sort(keys(%{$files{$fn}}))) { foreach my $tag (@{$files{$fn}{$field}}) { if ($exists{$field} && $exists{$field} eq $tag) { say "$fn: removing duplicate $field tag"; system('metaflac', '--remove-tag=' . $field, '--set-tag=' . $field . '=' . $tag, "$fn"); } else { $exists{$field} = $tag; } } } } sub track { my $fn = shift; if (!$t{tracknumber}) { say "$fn: doesn't have any tracknumber tag" } elsif ($t{tracknumber} =~ m/^0/ && $t{tracknumber} != 0) { say "$fn: fixing tracknumber tag"; $t{tracknumber} =~ s/^0+//; system('metaflac', '--remove-tag=TRACKNUMBER', '--set-tag=TRACKNUMBER=' . $t{tracknumber}, "$fn"); } } sub spaces { my $fn = shift; my %temp; foreach my $tag (sort(keys(%t))) { $temp{$tag} = $t{$tag}; $temp{$tag} =~ s/(^\s*)|(\s*$)//g; } foreach my $tag (sort(keys(%t))) { if ($temp{$tag} ne $t{$tag} && "$temp{$tag}") { say "$fn: removing leading and trailing whitespaces from tags"; #say $temp{$tag}; my $ufield = uc($tag); system('metaflac', '--remove-tag=' . $ufield, '--set-tag=' . $ufield . '=' . $temp{$tag}, "$fn"); } } } sub totaltracks { my $fn = shift; my $tracks = shift; if ( !$t{totaltracks} && !$t{tracktotal} ) { say "$fn: adding totaltracks tag"; system('metaflac', '--set-tag=TOTALTRACKS=' . $tracks, "$fn"); } elsif ( $t{tracktotal} && !$t{totaltracks} ) { say "$fn: replacing tracktotal tag with totaltracks"; system('metaflac', '--remove-tag=TRACKTOTAL', '--set-tag=TOTALTRACKS=' . $t{tracktotal}, "$fn"); } } sub discnum { my $fn = shift; my $n = shift; my $fc = shift; my $dn = dirname($fn); my ($match, $newfn); my $regex = '[[:punct:]]?(cd|disc) ?[0-9]+[[:punct:]]?$'; if ($t{album} =~ /$regex/i) { $t{album} =~ s/.?(${^MATCH}).?//; system('metaflac', '--remove-tag=ALBUM', '--set-tag=ALBUM=' . $t{album}, "$fn"); } if (!$t{discnumber}) { if (basename($fn) =~ /^[0-9]+-/) { $match = ${^MATCH}; ${^MATCH} =~ /([0-9]+)/; $t{discnumber} = eval { $1 =~ s/^0+//; }; } elsif ($dn =~ /$regex/i) { $match = ${^MATCH}; ${^MATCH} =~ /([0-9]+)/; $t{discnumber} = eval { $1 =~ s/^0+//; }; $dn =~ s/ .?($match).?//; make_path($dn) if (! -d $dn); $newfn = "${dn}/" . $t{discnumber} . '-' . basename($fn); unless (-f $newfn) { move($fn, $newfn) or die "Couldn't rename '$fn': $!"; } } else { $t{discnumber} = 1; $newfn = "${dn}/" . $t{discnumber} . '-' . basename($fn); unless (-f $newfn) { move($fn, $newfn) or die "Couldn't rename '$fn': $!"; } } if (!$newfn) { $newfn = $fn } say "$newfn: adding discnumber tag"; system('metaflac', '--set-tag=DISCNUMBER=' . $t{discnumber}, "$newfn"); } } sub rmtag { my $fn = shift; foreach my $field (@_) { if ($t{$field}) { say "$fn: removing $field tag"; system('metaflac', '--remove-tag=' . $field, "$fn"); } } }