Subject: | [PATCH] basic implementation of playlists |
Drew,
First, thanks for the Mac::iTunes::Library module. I'm trying to sync
music libraries between iTunes on a Mac and a PC, and custom music
software on Linux. With your module, it just might be possible.
One of the primary things I need is the ability to transfer playlists,
so I added playlist parsing to your module. It is very much tossed
together, but it works for me and all the tests pass. My changes are in
the attached patch.
Hope this is useful for you and others.
-Mark
Subject: | 0001-Added-very-basic-playlist-parsing.patch |
From 568ba764bf2ef86a01f9c911532f5b45d90eab59 Mon Sep 17 00:00:00 2001
From: Mark Grimes <mgrimes@new-host-2.grimes.homeip.net>
Date: Mon, 19 Oct 2009 21:52:57 -0400
Subject: [PATCH] Added very basic playlist parsing
---
lib/Mac/iTunes/Library.pm | 19 +++++++++-
lib/Mac/iTunes/Library/Playlist.pm | 5 ++-
lib/Mac/iTunes/Library/XML.pm | 71 +++++++++++++++++++++++++++++-------
t/ParsePlaylist.t | 31 ++++++++++++++++
4 files changed, 110 insertions(+), 16 deletions(-)
create mode 100644 t/ParsePlaylist.t
diff --git a/lib/Mac/iTunes/Library.pm b/lib/Mac/iTunes/Library.pm
index 6d2c49d..d2cb52b 100644
--- a/lib/Mac/iTunes/Library.pm
+++ b/lib/Mac/iTunes/Library.pm
@@ -69,7 +69,8 @@ sub new {
Genre => {}, # Genre counts by tracks
PGenre => {}, # Genre counts by playcount
Type => {}, # Track types, file or URL
- Items => {}
+ Items => {},
+ Playlists => {},
};
bless $self, $class;
@@ -457,6 +458,10 @@ sub _item {
# First occurrence of this artist
$self->{'Items'}{$artist}{$name} = [$item];
}
+
+ # print "creating item: " . $item->trackID . "\n";
+ $self->{'ItemsById'}{$item->trackID} = \$item; # TODO: this should be a ref
+
} #_item
=item add( Mac::iTunes::Library::Item )
@@ -499,6 +504,18 @@ sub add {
$self->_item($item);
} #add
+sub addPlaylist {
+ my $self = shift;
+ my $playlist = shift;
+
+ $self->{'Playlists'}->{ $playlist->{'Playlist ID'} } = $playlist;
+}
+
+sub playlists {
+ my $self = shift;
+ return %{$self->{'Playlists'}};
+}
+
1;
=head1 SEE ALSO
diff --git a/lib/Mac/iTunes/Library/Playlist.pm b/lib/Mac/iTunes/Library/Playlist.pm
index a5a6e1a..a5f983c 100644
--- a/lib/Mac/iTunes/Library/Playlist.pm
+++ b/lib/Mac/iTunes/Library/Playlist.pm
@@ -170,10 +170,10 @@ sub name {
if (@_) {
my $name = shift;
- $self->{'name'} = $name;
+ $self->{'Name'} = $name;
}
- return $self->{'name'};
+ return $self->{'Name'};
} #name
=item playlistID( id )
@@ -288,6 +288,7 @@ sub addItem {
unless ($item->isa('Mac::iTunes::Library::Item'));
push @{$self->{'items'}}, $item;
+ # TODO: shouldn't this be referencing a track already in the lib?
} #addItem
=item addItems( Mac::iTunes::Library::Item )
diff --git a/lib/Mac/iTunes/Library/XML.pm b/lib/Mac/iTunes/Library/XML.pm
index 5b8d30a..4c11079 100644
--- a/lib/Mac/iTunes/Library/XML.pm
+++ b/lib/Mac/iTunes/Library/XML.pm
@@ -7,7 +7,9 @@ use Carp;
use Mac::iTunes::Library;
use Mac::iTunes::Library::Item;
+use Mac::iTunes::Library::Playlist;
use XML::Parser;
+# use Data::Dump qw(pp);
require Exporter;
our @ISA = qw(Exporter);
@@ -53,7 +55,7 @@ my $library;
# Characters that we collect
my $characters = undef;
# Keep track of where we are; push on each element name as we hit it
-my @stack;
+my (@stack, @pl_stack);
my ($inTracks, $inPlaylists, $inMajorVersion, $inMinorVersion,
$inApplicationVersion, $inFeatures, $inMusicFolder,
$inLibraryPersistentID) = undef;
@@ -75,14 +77,14 @@ sub parse {
### Parser start element
sub start_element {
my ($expat, $element, %attrs) = @_;
-
- # Don't deal with playlists yet
- return if ($inPlaylists);
# Keep a trail of our depth
push @stack, $element;
my $depth = scalar(@stack);
+ # print "*" if $inPlaylists;
+ # print "> $depth: $element" . pp( %attrs) . "\n" if $inPlaylists;
+
if ( $depth == 0 ) {
} elsif ( $depth == 1 ) {
# Hit the initial <plist version=""> tag
@@ -91,12 +93,22 @@ sub start_element {
}
} elsif ( $depth == 2 ) {
} elsif ( $depth == 3 ) {
- if (($element eq 'true') or ($element eq 'false')) {
- $library->showContentRatings($element);
+ if( $inPlaylists ){
+ } else {
+ if (($element eq 'true') or ($element eq 'false')) {
+ $library->showContentRatings($element);
+ }
}
} elsif ( $depth == 4 ) {
# We hit a new item in the XML; create a new object
- $item = Mac::iTunes::Library::Item->new() if ($element eq 'dict');
+
+ if( $inPlaylists ){
+ # print "create new playlist\n";
+ $item = Mac::iTunes::Library::Playlist->new() if ($element eq 'dict');
+ } else {
+ $item = Mac::iTunes::Library::Item->new() if ($element eq 'dict');
+ }
+ } elsif( $depth == 5 ){
}
} #start_element
@@ -104,13 +116,13 @@ sub start_element {
sub end_element {
my ($expat, $element) = @_;
- # Don't deal with playlists yet
- return if ($inPlaylists);
-
# Prune the trail
my $depth = scalar(@stack);
pop @stack;
+ # print "*" if $inPlaylists;
+ # print "< $depth: $element\n" if $inPlaylists;
+
if ( $depth == 0 ) { # plist version
} elsif ( $depth == 1 ) { # dict
} elsif ( $depth == 2 ) {
@@ -126,19 +138,43 @@ sub end_element {
}
} elsif ( $depth == 4 ) {
# Ending an item; add it to the library and clean up
- if ( $item ) {
- $library->add($item);
+ if( $inPlaylists ){
+ if ( $item ) {
+ # print pp $item;
+ # print "add pl to library\n";
+ $library->addPlaylist($item);
+ }
+ } else {
+ if ( $item ) {
+ $library->add($item);
+ }
}
$item = undef if ($element eq 'dict');
} elsif ( $depth == 5 ) {
# Set the attributes of the Mac::iTunes::Library::Item directly
if ( $element =~ /(integer|string|date)/ ) {
+ if( $inPlaylists ){
+ # print "$curKey = $characters\n";
+ }
$item->{$curKey} = $characters;
$characters = undef;
} elsif ( $element =~ /true/ ) {
$item->{$curKey} = 1;
}
+ } elsif( $depth == 7 ){
+ if ( $element =~ /(integer)/ ) {
+ # print "Adding $curKey => $characters\n";
+
+ my $track = $library->{'ItemsById'}{$characters};
+ if( ref $track and $$track ){
+ $item->addItem( $$track );
+ } else {
+ warn "Couldn't find track '$characters'\n";
+ }
+
+ $characters = undef;
+ }
}
} #end_element
@@ -147,7 +183,8 @@ sub characters {
my ($expat, $string) = @_;
my $depth = scalar(@stack);
- return if ($inPlaylists);
+ # print "*" if $inPlaylists;
+ # print "= $depth: $string\n" if $inPlaylists;
if ( $depth == 0 ) { # plist version
} elsif ( $depth == 1 ) { # dict
@@ -203,6 +240,14 @@ sub characters {
# Append it to the characters that we've gathered so far
$characters .= $string;
}
+ } elsif( $depth == 7 ) {
+ if ( $stack[$#stack] eq 'key' ) {
+ # Grab the key's name; always comes in a single chunk
+ $curKey = $string;
+ } elsif ( $stack[$#stack] =~ /(integer|string|date)/ ) {
+ # Append it to the characters that we've gathered so far
+ $characters .= $string;
+ }
}
} #characters
diff --git a/t/ParsePlaylist.t b/t/ParsePlaylist.t
new file mode 100644
index 0000000..d88788e
--- /dev/null
+++ b/t/ParsePlaylist.t
@@ -0,0 +1,31 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl Mac-iTunes-Item.t'
+
+=head1 SVN INFO
+
+$Revision: 66 $
+$Date: 2009-05-03 22:18:46 -0700 (Sun, 03 May 2009) $
+$Author: drewgstephens $
+
+=cut
+
+#########################
+use lib ".";
+use 5;
+use Test::More tests => 5;
+BEGIN { use_ok('Mac::iTunes::Library::XML') };
+#########################
+
+my $lib = Mac::iTunes::Library::XML->parse('t/iTunes_Music_Library.xml');
+
+use Data::Dump;
+
+# dd $lib;
+# dd $lib->playlists;
+
+my %playlists = $lib->playlists;
+is( scalar keys %playlists, 2, 'playlist count' );
+my $playlist = $playlists{10073};
+ok( $playlist, 'found expected playlist' );
+is( $playlist->name, '5 Stars', '... has the right name' );
+is( scalar $playlist->items, 17, '... has the right track count' );
--
1.6.4.4