awful itunes hack for album listening
It’s bugged me that iTunes makes it hard to listen to things as albums. Sure, it has shuffle-by-album, but smart playlists are all per-track.
After years of meaning to, this morning I wrote a (very very slow) Mac::Glue script to build a playlist of unrated or highly-rated albums that I haven’t listened to lately. When I was nearly done and looked into one little bug, I found some other similar scripts. Oh well!
I’ll eventually update this to avoid having it pick all albums by one artist, but for now, it’s good. Thanks to it, I am re-listening to Method Man’s Tical.
#!/usr/bin/perl
use strict;
use warnings;
use Mac::Glue qw(:glue);
use List::Util qw(sum);
my $itunes = Mac::Glue->new('iTunes');
my $pl = $itunes->obj(
playlist => whose(name => equals => 'Regular Music')
)->get;
my $albumen = $itunes->obj(
playlist => whose(name => equals => 'Albumen')
)->get;
die "no albumen" unless $albumen;
{
my $tracks = $itunes->obj(
'track' => gAll,
playlist => $albumen->prop('index')->get,
);
for my $t ( $tracks->get ){
$t->delete;
}
}
print "getting tracks\n";
my @tracks = $pl->obj('tracks')->get;
my %album;
while (my $track = shift @tracks) {
my $trackid = $track->prop('database ID')->get;
my $album = $track->prop('album')->get;
my $artist = $track->prop('compilation')->get
? '-'
: $track->prop('artist')->get;
next unless defined $album and defined $artist;
next unless length $album and length $artist;
my $rec = $album{ $album, $artist } ||= [];
printf "storing record of $trackid ($album/$artist); %s remain\n",
scalar @tracks;
push @$rec, {
id => $trackid,
rating => scalar $track->prop('rating')->get,
played => scalar $track->prop('played date')->get, # epoch sec
size => scalar $track->prop('size')->get, # in bytes
};
}
my $DEFAULT_TIME = time - 30 * 86_400;
my %avg_age;
ALBUM: for my $key (keys %album) {
my ($album, $artist) = split $;, $key;
printf "considering (%s/%s)\n", $album, $artist;
my @tracks = @{ $album{ $key } };
unless (@tracks > 4) {
printf "skipping (%s/%s); too few tracks\n", $album, $artist;
delete $album{$key};
next ALBUM;
}
my @lp_dates = map { undef $_ if $_ eq 'msng'; $_ || $DEFAULT_TIME }
map { $_->{played} }
@tracks;
my $avg_age = time - (sum(@lp_dates) / @lp_dates);
$avg_age{ $key } = $avg_age;
if ($avg_age < 86_400 * 30) {
printf "skipping (%s/%s); too recent\n", $album, $artist;
delete $album{$key};
next ALBUM;
}
my @ratings = grep { $_ > 0 } map { $_->{rating} } @tracks;
my $avg_rating = sum(@ratings) / @ratings if @ratings;
if ($avg_rating and $avg_rating < 60) {
printf "skipping (%s/%s); too lousy\n", $album, $artist;
delete $album{$key};
next ALBUM;
}
printf "keeping (%s/%s) @ %s\n", $album, $artist, $avg_rating || '(n/a)';
}
my $total_size = 0;
ADDITION: for my $key (sort { $avg_age{$b} <=> $avg_age{$a} } keys %album) {
my @tracks = @{ $album{ $key } };
for my $track (@tracks) {
$total_size += $track->{size};
my $t = $itunes->obj(
track => whose('database id' => equals => $track->{id})
)->get;
$itunes->duplicate($t, to => $albumen);
}
last ADDITION if $total_size > 500_000_000;
}
Written on November 26, 2008