Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

G'day all, In need of ya'all's wisdom!

I have created this mess, yet fail to be able to get back at the nested elements.
<At the very end of the script is the cleaned up attempt>

I would explain the structure but don't know how to convey it
<The picture's in my mind and I can't get it out, Arrrg! >
The best place to get the structure is in sub HashMPEG.

Could I get a kick in the right direction please!

Also Other ideas/methods/concepts welcome!

To run the following code, TagLib 1.5 and Audio-TagLib-1.50_01 is required.
The '$MusicRoot' will also need to be edited to your needs

#!/usr/bin/perl use warnings; use strict; ###################################################################### # # BCE Project 19, Exercise accessing nested data # # Search for *.mp3, build structure of each mp3's info. # # print structured info # my $Filename = '\*.mp3'; my $MusicRoot = '/Your/Music/Location'; # no trailing slash # REQUIRES TagLib 1.5 avaliable at # REQUIRES Audio-TagLib-1.50_01 avaliable at cpan.org use Audio::TagLib::MPEG::Properties; use Data::Dumper; # For testing $Data::Dumper::Indent = 1; # For testing # Subs expect (Audio::TagLib::MPEG::File->new('Filename')) sub _Get_Artist { my $t = shift; return $t->tag()->artist()->toCSt +ring(); } sub _Get_Album { my $t = shift; return $t->tag()->album()->toCStr +ing(); } sub _Get_Title { my $t = shift; return $t->tag()->title()->toCStr +ing(); } sub _Get_Comment { my $t = shift; return $t->tag()->comment()->toCS +tring(); } sub _Get_Genre { my $t = shift; return $t->tag()->genre()->toCStr +ing(); } sub _Get_Year { my $t = shift; return $t->tag()->year(); } sub _Get_Track { my $t = shift; return $t->tag()->track(); } sub _Get_Length { my $t = shift; return $t->audioProperties->lengt +h(); } sub _Get_Bitrate { my $t = shift; return $t->audioProperties->bitra +te(); } sub _Get_Samplerate { my $t = shift; return $t->audioProperties->sampl +eRate(); } sub _Get_Channels { my $t = shift; return $t->audioProperties->chann +els(); } # Sub expects ('Filename') sub HashMPEG { my $f = shift; my %Artist; my %Album; my %Title; my %TagInfo; my $m = Audio::TagLib::MPEG::File->new($f); my %TagsMPEG = ('comment' => \&_Get_Comment, 'genre' => \&_Get_Genre +, 'year' => \&_Get_Year, 'track' => \&_Get_Track, 'length' => \&_Get_ +Length, 'bitrate' => \&_Get_Bitrate, 'samplerate' => \&_Get_Samplerat +e, 'channels' => \&_Get_Channels); for my $t (keys %TagsMPEG) { $TagInfo{$t} = $TagsMPEG{$t}->($m); } $Title{_Get_Title($m)} = \%TagInfo; $Album{_Get_Album($m)} = \%Title; $Artist{_Get_Artist($m)} = \%Album; return \%Artist; } sub HashFiles { my $d = shift; my $f = shift; my %FH; my @flist = `find $d -type f -name $f`; if ($#flist) { for my $t (@flist) { chomp $t; $FH{$t} = HashMPEG $t; } } return %FH; } my %FilesHash = HashFiles $MusicRoot, $Filename; # for testing #print Dumper(\%FilesHash); for my $File (keys %FilesHash) { print "File : " . $File . "\n"; }

Desired Output, or whatever explains access to the nested elements the best.

File : /Common/Music/Stevie Ray Vaughan and Double Trouble/Live +Alive/03-Pride and Joy.mp3 Artist : Stevie Ray Vaughan and Double Trouble Album : Live Alive Title : Pride and Joy Comment : Genre : Blues Year : 1986 Track : 3 Length : 304 Bitrate : 256 Samplerate : 44100 Channels : 2

Dumper output

'/mnt/RaidOne/Music/Stevie Ray Vaughan and Double Trouble/Live Alive +/03-Pride and Joy.mp3' => { 'Stevie Ray Vaughan and Double Trouble' => { 'Live Alive' => { 'Pride and Joy' => { 'bitrate' => 256, 'channels' => 2, 'track' => 3, 'samplerate' => 44100, 'genre' => 'Blues', 'length' => 304, 'comment' => '', 'year' => 1986 } } } },

Thanks
-Enjoy

Replies are listed 'Best First'.
Re: Accessing nested elements
by ELISHEVA (Prior) on Sep 29, 2009 at 07:18 UTC

    Could you be more specific about what you want help with? It looks like you've gotten far enough to have useful dumper output. Do you need to learn how to pretty print it? Or are you expecting different contents your hash, for example, more than one author/title in your hash? Your project assignment looks like you are supposed to be working with what looks like a directory name ($MusicRoot) that perhaps contains many MPEG files.

    In the meantime, here are some coding tips:

    Your intent for %TagsMPEG would be clearer if you put one assignment per line, like this:

    #I like commas at the beginning so that I know the line #is a continuation of the previous, but others like commas #at the end of each line. The main thing is one assignment #per line. my %TagsMPEG = ('comment' => \&_Get_Comment , 'genre' => \&_Get_Genre , 'year' => \&_Get_Year , 'track' => \&_Get_Track , 'length' => \&_Get_Length , 'bitrate' => \&_Get_Bitrate , 'samplerate' => \&_Get_Samplerate , 'channels' => \&_Get_Channels );

    Also, you don't need to create a hash variable to assign a hash to a data element. You can nest hash keys directly, like this:

    $Artist{_Get_Artist($m)}{_Get_Album{$m}}{_Get_Title{$m}}=\%TagInfo;

    Finally, your current code creates and returns a hash with only one artist. Is this really what you want? Or did you want to build a hash with many artists? If you want to build up a hash with many artists, the best way is to pass HashMPEG() a reference to an artist hash rather than trying to create the artist hash inside HashMPEG(). It would look something like this:

    sub HashMPEG { # assign parameters useful names # $hArtist is a hash reference my ($f, $hArtist) = @_; #... extract data from MPEG file # -> operator is used when working with hash references rather # than hashes $hArtist->{_Get_Artist($m)}{_Get_Album{$m}}{_Get_Title{$m}}=\%TagInf +o; # no need to return anything. artist is in hash reference # and caller has access to hash reference }

    You may find it helpful to take a look at the following Perl documentation, perlreftut, perldata and perldsc.

    Best, beth

    Update: fixed misinterpretation of code

      I prefer the perltidy version ( i also prefer a final trailing comma)
      my %TagsMPEG = ( 'comment' => \&_Get_Comment, 'genre' => \&_Get_Genre, 'year' => \&_Get_Year, 'track' => \&_Get_Track, 'length' => \&_Get_Length, 'bitrate' => \&_Get_Bitrate, 'samplerate' => \&_Get_Samplerate, 'channels' => \&_Get_Channels, );

        my %TagsMPEG = ('comment' => \&_Get_Comment, 'genre' => \&_Get_Genre, 'year' => \&_Get_Year, 'track' => \&_Get_Track, 'length' => \&_Get_Length, 'bitrate' => \&_Get_Bitrate, 'samplerate' => \&_Get_Samplerate, 'channels' => \&_Get_Channels, );

        DONE! :-), Thank you both for this one! Mucho easier to read.

        -Enjoy

      Beth, Thank you... The ultimate goal is to create the neatest slickest data structure of all mp3's found, that one could then sort (all elements, by any element), print, edit, etc... etc... This was my first attempt, I am very new to Perl, my first line of Perl was on 08/26/2009!

      Any more pointers are surely welcome.

      In reality no serious goal other than brain food!

      This just happens to be something that I can make use of to learn with, I have a few mp3's :)

      Thanks

      -Enjoy
      fh :_)~

Re: Accessing nested elements
by ig (Vicar) on Sep 29, 2009 at 07:41 UTC

    Given the structure you have, you should be able to access the nested elements with something like the following:

    foreach my $file (keys %FilesHash) { foreach my $artist (keys %{$FilesHash{$file}}) { foreach my $album (keys %{$FilesHash{$file}{$artist}}) { foreach my $title (keys %{$FilesHash{$file}{$artist}{$albu +m}}) { print "File : $file\n"; print "Artist : $artist\n"; print "Album : $album\n"; print "Title : $title\n"; foreach my $attribute (keys %{$FilesHash{$file}{$artis +t}{$album}{$title}}) { printf("%-11s: %s\n", ucfirst($attribute), $FilesH +ash{$file}{$artist}{$album}{$title}{$attribute}); } } } } }

      Arrg! I now see where I was not smart enough!

      (keys %{$FilesHash{$file}})

      I did not enclose with %{...}

      THANK YOU!

      -Enjoy

Re: Accessing nested elements
by Anonymous Monk on Sep 29, 2009 at 07:15 UTC
    References quick reference

    I would get rid of all those _Get subroutines, and hashes of hashes (makes no sense)

    $TagInfo{bitrate} = $m->audioProperties->bitrate(); $TagInfo{channels} = $m->audioProperties->channels(); $TagInfo{comment} = $m->tag()->comment()->toCString(); $TagInfo{genre} = $m->tag()->genre()->toCString(); $TagInfo{length} = $m->audioProperties->length(); $TagInfo{samplerate} = $m->audioProperties->sampleRate(); $TagInfo{track} = $m->tag()->track(); $TagInfo{year} = $m->tag()->year(); $TagInfo{artist} = $m->tag()->artist()->toCString(); $TagInfo{title} = $m->tag()->title()->toCString(); $TagInfo{album} = $m->tag()->album()->toCString(); return \%TagInfo;
    Then I would stuff %TagInfo into a SQLite database, and run SQL queries against it.
Re: Accessing nested elements
by Anonymous Monk on Sep 29, 2009 at 07:37 UTC

    Aye! Great tips! I will be looking into them, HARD!, Thanks.

    Yes, Pretty print would work, As stated in the second line of my OP,
    'yet fail to be able to get back at the nested elements.'.
    meaning I am unable to print (or anything) each element.

    Thanks
    -Enjoy