#!/usr/bin/perl use 5.14.0; use strict; use warnings; #use File::Find qw(finddepth); 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 (%files, @files, @dirs); getdirs($library); foreach my $dn (sort(@dirs)) { chomp(my $ls = `ls "$dn"/*.flac 2> /dev/null | head -n 1`); if (-f $ls) { # my @stat = stat($dn); # my $ino = $stat[1]; # say "$dn has $ino"; undef %files; undef @files; getfiles($dn); my $fc = scalar(@files); if ($fc > 0) { #replaygain($dn); for (my $n = 0; $n < $fc; $n++) { #say $files[$n]; my $fn = $files[$n]; #rmtag($fn, 'rating'); #albumartist($fn, $fc); #track($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 = quotemeta(shift); my (%alltags, @mflac); open(OUTPUT, '-|', qq{metaflac --no-utf8-convert --export-tags-to=- $fn}) || die "can't run metaflac: $!"; chomp(@mflac = ()); foreach (@mflac) { my @tag = split('='); my $tagname = lc($tag[0]) or say $fn; if ($tag[1]) { push @{$alltags{$tagname}}, $tag[1]; } else { push @{$alltags{$tagname}}, 'null'; } } close(OUTPUT) || die "couldn't close metaflac: $!"; return %alltags; } 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': $!"; #@dirs = `find -L "$dn" -name "*" -type d -mindepth 1`; } sub getfiles { my $dn = shift; opendir(my $dh, $dn) or die "Can't open directory '$dn': $!"; foreach (readdir $dh) { my $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 (!$files{$fn}{albumartist}) { my $albumartist = $files{$fn}{artist}->[0]; my %artist; my $max; if ($tracks == 1) { $max = $tracks } else { $max = $tracks / 2 } foreach my $fn (keys %files) { $artist{$files{$fn}{artist}->[0]}++ } if (keys(%artist) > $max) { $albumartist = 'Various Artists'; } say "$fn: adding albumartist tag"; system('metaflac', "--set-tag=ALBUMARTIST=$albumartist", $fn); } } sub empty { my $fn = shift; foreach my $k (keys %{$files{$fn}}) { foreach (@{$files{$fn}{$k}}) { if ($_ eq 'null') { say "$fn: WARNING: empty $k tag"; } } } } sub duplicate { my $fn = shift; my %exists; foreach my $k (keys %{$files{$fn}}) { foreach (@{$files{$fn}{$k}}) { my $tag = quotemeta($k . '=' . $_); if ($exists{$tag}) { say "$fn: WARNING: duplicate $k tag"; } else { $exists{$tag} = 1; } } } } sub track { my $fn = shift; my $track = $files{$fn}{tracknumber}->[0]; if (!$track) { say "$fn: doesn't have any tracknumber tag" } elsif ($track =~ m/^0/ && $track != 0) { say "$fn: fixing tracknumber tag"; $track =~ s/^0+//; system('metaflac', '--remove-tag=TRACKNUMBER', "--set-tag=TRACKNUMBER=$track", $fn); } } sub totaltracks { my $fn = shift; my $tracks = shift; if ( !$files{$fn}{totaltracks} && !$files{$fn}{tracktotal} ) { say "$fn: adding totaltracks tag"; system('metaflac', "--set-tag=TOTALTRACKS=$tracks", $fn); } elsif ( $files{$fn}{tracktotal} && !$files{$fn}{totaltracks} ) { say "$fn: replacing tracktotal tag with totaltracks"; system('metaflac', '--remove-tag=TRACKTOTAL', "--set-tag=TOTALTRACKS=$files{$fn}{tracktotal}->[0]", $fn); } } sub discnum { my $fn = shift; my $n = shift; my $fc = shift; my $dn = dirname($fn); my $discnumber = $files{$fn}{discnumber}->[0]; my $album = $files{$fn}{album}->[0]; my ($match, $newfn); my $regex = '[[:punct:]]?(cd|disc) ?[0-9]+[[:punct:]]?$' ; if ($album =~ /$regex/i) { $album =~ s/.?${^MATCH}.?//; system('metaflac', '--remove-tag=ALBUM', $fn); system('metaflac', "--set-tag=ALBUM=$album", $fn); } if (!$discnumber) { if (basename($fn) =~ /^[0-9]+-/) { $match = ${^MATCH}; ${^MATCH} =~ /([0-9]+)/; $discnumber = eval { $1 =~ s/^0+//; }; } elsif ($dn =~ /$regex/i) { $match = ${^MATCH}; ${^MATCH} =~ /([0-9]+)/; $discnumber = eval { $1 =~ s/^0+//; }; $dn =~ s/ .?($match).?//; $newfn = "${dn}/" . "${discnumber}-" . basename($fn); make_path($dn) if (! -d $dn); move($fn, $newfn) or die "Couldn't rename '$fn': $!"; } else { $discnumber = 1; $newfn = "${dn}/" . "${discnumber}-" . basename($fn); move($fn, $newfn) or die "Couldn't rename '$fn': $!"; } if (!$newfn) { $newfn = $fn } say "$newfn: adding discnumber tag"; system('metaflac', "--set-tag=DISCNUMBER=$discnumber", $newfn); } } sub rmtag { my $fn = shift; foreach my $tag (@_) { if ($files{$fn}{$tag}) { say "$fn: removing $tag tag"; system('metaflac', "--remove-tag=$tag", $fn); } } }