I have just begun to learn SPOPS by our own lachoy. Last night (after RTFM'ing for a good two hours), i finally made some progress. The result is a script that reads MP3 files in a given directory and stores their ID3 tag's into a database table. The table i used has an 'id' field which is an auto incremented primary key, and five other fields (title, artist, album, year, and genre) which can be any database type you want. Mine were just varchars that can be NULL, for simplicity's sake.

SPOPS stands for Simple Perl Object Persistence with Security. SPOPS is not one module, but instead a collection of almost FIFTY modules! There is a lot to learn up front, and this post will not go deep into SPOPS (it barely scratches the surface). This post only covers instantiating fairly simple objects that can communicate to a database, it doesn't cover persistence. A review and a tutorial for SPOPS are 'in the works'. Think of this as just a primer.

This code uses File::Find and MP3::Info to find the MP3 files and extract the tag info, respectively. With these two tools, it is easy to write DBI code to store the found tags into a database. SPOPS is used here to abstract the SQL completely away from the client. This is currently implemented in SPOPS by subclassing a SPOPS::DBI object:

package MySPOPS::DBI; use strict; use SPOPS::DBI; @MySPOPS_DBI::ISA = qw(SPOPS::DBI); use constant DBI_DSN => 'DBI:vendor:database:host'; use constant DBI_USER => 'user'; use constant DBI_PASS => 'pass'; my ($DB); sub global_datasource_handle { unless (ref $DB) { $DB = DBI->connect( DBI_DSN, DBI_USER, DBI_PASS, {RaiseError => 1} ); } return $DB; } 1;

This subclassed SPOPS::DBI class can be reused by different clients. The client that i wrote uses the SPOPS::Initialize object to create my MySPOPS::MP3 object.

use strict; use MP3::Info; use File::Find; use SPOPS::Initialize; $|++; @ARGV = ('.') unless @ARGV; # the configuration SPOPS::Initialize->process({ config => { myobject => { class => 'MySPOPS::MP3', isa => [qw( MySPOPS::DBI )], field => [qw(title artist album year genre)], id_field => 'id', object_name => 'mp3', base_table => 'songs', } }}); find sub { return unless m/\.mp3$/; my $tag = get_mp3tag($_) or return; my $mp3 = MySPOPS::MP3->new({map {lc($_)=>$tag->{$_}} keys %$tag}); print STDERR join(':',values %$mp3),"\n"; $mp3->save(); }, @ARGV;

I use File::Find in a similar manner as the code from the Perl Cookbook, recipe number 9.7. If no argument is supplied, then the current directory is recursively scanned.

MP3::Info is used to obtain the MP3 tag from the file. I have not bothered to validate in the interest of keeping the code simple. Adding validation should be trivial, see Identifying MP3 files with no MP3 tag for some tips on that. Another possiblity is to use CDDB.

The trick is the instantiation of the MySPOPS::MP3 object. If you compare the Data::Dumper outputs of a MP3::Info with the Dumper output of what a MySPOSP::MP3 object _should_ look like, you will see that they are very similar:

$VAR1 = bless( { 'ARTIST' => 'The Fixx', 'GENRE' => 'Pop', 'ALBUM' => 'Phantoms', 'TITLE' => 'Woman On A Train', 'YEAR' => '1984', 'COMMENT' => 'underrated band', }, 'MP3::Info' );
$VAR1 = bless( { 'artist' => '', 'genre' => '', 'album' => '', 'title' => '', 'year' => '', 'id' => , }, 'MySPOPS::MP3' );

The MySPOPS::MP3 constructor accepts a hash reference as an argument and will use that hash reference to define it's attributes. All that is needed is to lower case the keys of the MP3::Info object and the two will have virtually the same keys ('comment' will be ignored because it is not in the configuration and 'id' is not needed because it will be handled for you).

So, the MySPOPS::MP3 object is instantiated with a transformed copy of the MP3::Info's internal attribute hash. I could have have named my database table fields with all upper case letters and there would be no need for the transformation.

Finally, a message is printed to standard error and the MySPOPS::MP3::save() method is called, which stores the object's attributes in the database.

The next version of SPOPS (0.56) will allow you skip having to subclass a SPOPS::DBI object and simply pass the the connection credentials along with your configuration:

myobject => { class => 'MySPOPS::MP3', isa => [qw( MySPOPS_DBI )], field => [qw(title artist album year genre)], id_field => 'id', object_name => 'mp3', base_table => 'songs', dbi_config => { dsn => 'DBI:vendor:database:host', username => 'user', password => 'pass', }, }

But this should be used for 'one-offs' only. By subclassing SPOPS::DBI you allow other clients to share and have the databse connection code abstracted away.


(the triplet paradiddle)

In reply to Save MP3 Tag's to Database with SPOPS by jeffa

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":