#!/usr/bin/perl use strict; use Audio::FLAC::Header; our ($osize, $size, $files, $read, $dudf, $dudl, $dirs, $links, $pic) = (0,0,0,0,0,0,0,0); our ($t,@filespecs, %artists, $min, $max); my %config = ( picmode => 'a', verbose => 0, nozeroes => 0, artistdump => 1, ); my $picmode = 'a'; my @t = qw(year 31557600 month 2627994 week 604800 day 86400 hour 3600 minute 60); die "Usage: $0 /path/to/flac /other/path/to/flac" unless length($ARGV[0]||''); for (@ARGV) { if (/^-([pP])$/) { $config{picmode} = $1; next; } if ($_ eq '-v') { $config{verbose} = 1; next; } if ($_ eq '-z') { $config{nozeroes} = 1; next; } if ($_ eq '-a') { $config{artistdump} = 0; next; } push @filespecs, $_; } for (@filespecs) { $t+=process_dir($_); } print "\n" . plural(scalar keys %artists, "artist") . " found\n"; print "\nTotal: " . sane_time($t); print " ($t secs/" . sprintf("%0.3f",($t/(60*60*24*365.25))); print " years) in $files files (managed to read $read,"; printf " %0d pictured [",$pic; printf $pic && $read ? ("%3d", ($pic / $read) * 100 ) : "0"; print "%]; $dudf were ignored), $links links ($dudl were ignored) and "; print "$dirs directories; on disc: " . sane_size($size) . "; originally " ; print (sane_size($osize)); if ($osize && $size) { printf(" [%3d%]\n", int( ($size/$osize) * 100)); } else {print " [ 0%]\n"; } print "max/min/avg " . sane_time($max) . '/'.sane_time($min) . '/' . (($t && $read) ? sane_time($t/$read) : sane_time(0)) . "\n"; if ($config{artistdump}) { for (sort { $artists{$b}{count} <=> $artists{$a}{count} } keys %artists) { printf("%s %d [%d%% pic] %s\n", $_, $artists{$_}{count}, ($artists{$_}{pic} / $artists{$_}{count}) * 100, sane_time($artists{$_}{time})); } } exit 0; sub flac_inf { my $f = shift; my $a; $links++ if -l $f; $files++ if !-l $f && -f $f; if ($f !~ /\.flac$/) { #print STDERR "Ignore $f\n"; $dudl++ if -l $f; $dudf++ if !-l $f; return 0; } #warn "flac_inf with $f"; my ($info, $t, $flach, $flact); if ($flach = Audio::FLAC::Header->new($f)) { if ($flach->picture()) { next if $config{picmode} eq 'P'; } else { next if $config{picmode} eq 'p'; } $pic++ if $flach->picture(); $info = $flach->info(); $read++; $osize += ($$info{NUMCHANNELS} * $$info{TOTALSAMPLES} * ( $$info{BITSPERSAMPLE} / 8 )); $size+= -s $f if !-l $f; $t = $$info{TOTALSAMPLES} / $$info{SAMPLERATE}; print "$f: " . sane_time($t) . "\n" if $config{verbose}; $min = min($min, $t); $max = max($max, $t); if ($flact = $flach->tags()) { $a = get_artist($flact); $artists{$a}{count}++; $artists{$a}{time}+=$t;; $artists{$a}{pic}++ if $flach->picture(); } return $t; } warn "Can't get header for $f"; return 0; } sub process_dir { my $dir = shift; my $t = 0; my ($d, $i); $dir =~s#/+#/#og; #warn "process_dir with $dir\n"; if (! -d $dir) { #warn "$dir isn't a directory"; $d = flac_inf($dir); print "$dir: " . sane_time($d) . "\n" unless $config{verbose}; return $d; } if (! opendir(D, $dir)) { warn "Can't open $dir - $!"; return; } $dirs++; for (sort grep !/^\.\.?$/, readdir(D)) { $t += -d "$dir/$_" ? process_dir("$dir/$_") : flac_inf("$dir/$_"); } closedir(D); return $t if $t == 0 && $config{nozeroes}; print "$dir: " . sane_time($t) . "\n"; return $t; } sub sane_time { my $t = shift; my ($u, $v, @o, $c); my @x=@t; # nasty while ($u = shift @x) { $v = shift @x; $c = int($t/$v); if ($c) { $t-=$c*$v; push @o, plural($c, $u); } } push @o, plural(int($t),'second'); return $o[0] unless scalar @o > 1; return join(' and ',join(', ',@o[0 .. $#o -1]), $o[-1]); } sub plural { return "@_[0] @_[1]" . (@_[0] != 1 ? 's' : ''); } sub sane_size { my @units = qw(KB MB GB TB PB); my $out = 'bytes'; my $in = shift; while ($in/1024 > 1) { $in/=1024; $out = shift @units; } return (printf("%d.%03d %s", int($in) , (($in*1024) %1024), $out)); } sub get_artist { my $tref = shift; my @at = grep { /artist/i } keys %$tref; if (@at > 1) { warn "> 1 artist tag; got " . (join", ", map {"$_: $$tref{$_}"} @at) . "\n"; } #else { # warn "$at[0]: $$tref{$at[0]}\n"; #} return $$tref{$at[0]}; } sub min { return $_[1] unless defined $_[0]; return $_[0] unless defined $_[1]; return $_[0] < $_[1] ? $_[0] : $_[1]; } sub max { return $_[0] > $_[1] ? $_[0] : $_[1]; } __END__ =head1 NAME flactime - produce summary information for a series of flac files. =head1 DESCRIPTION flactime summarises flac playlist length, in months/weeks/minutes/days/fraction of years. Also detail whether a flac has a picture. =head1 USAGE flactime [options] /dir foo.flac ~/[a-m]*flac Where options are: -p - only summarise flacs with pictures (default all) -p - only summarise flacs without pictures (default all) -z - don't report 0 second leafs/files (default - report everything) -v - be verbose; summarise files in a directory and not just the directory (and its leafs) The defaults behaviour of flactime /dir foo.flac ~/[a-m]*flac is to summaries for each directory under /dir, report on size for foo.flac and for any files matching the wildcard, and to finish with a total time for the whole lot, together with some converted stats and picture information. =head1 WARRANTY This is free software. This comes with no warranty whatsoever. =head1 AUTHORS Andrew White (andy@milky.org.uk) =cut