The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package MP3::Find::Base;

use strict;
use warnings;

use Carp;

my %format_codes = (
    a => 'ARTIST',
    t => 'TITLE',
    b => 'ALBUM',
    n => 'TRACKNUM',
    y => 'YEAR',
    g => 'GENRE',
);

sub new {
    my $invocant = shift;
    my $class = ref $invocant || $invocant;
    my %options = @_;
    my $self = \%options;
    bless $self, $class;
}

sub find_mp3s {
    my $self = shift;
    my %opt = @_;
    
    my $dir = $opt{dir} || $ENV{HOME};
    my @DIRS = ref $dir eq 'ARRAY' ? @$dir : ($dir);
    
    my %QUERY = %{ $opt{query} || {} };
    
    # array ref for multiple sort fields, but allow
    # a simple scalar for single values
    my @SORT = $opt{sort} ? 
        (ref $opt{sort} eq 'ARRAY' ? @{ $opt{sort} } : ($opt{sort})) :
        ();
    
    foreach (keys %QUERY) {
        if (defined $QUERY{$_}) {
            # package everything uniformly, so subclasses don't need to unpack it
            $QUERY{$_} = [ $QUERY{$_} ] unless ref $QUERY{$_} eq 'ARRAY';
        } else {
            # so we don't have spurious warnings when trying to match against undef        
            delete $QUERY{$_};
        }
    }
    
    # do the search
    my @results = $self->search(\%QUERY, \@DIRS, \@SORT, \%opt);
    
    # maybe they want the unformatted data
    return @results if $opt{no_format};
    
    if ($opt{printf}) {
        # printf style output format
        foreach (@results) {
            my $output = $opt{printf};
            for my $code (keys %format_codes) {
                
                while ($output =~ m/%((-\d)?\d*)$code/g) {
                    # field size modifier
                    my $modifier = $1 || '';
                    # figure out the size of the formating code
                    my $code_size = 2 + length($modifier);
                    my $value = sprintf("%${modifier}s", $_->{$format_codes{$code}} || '');
                    substr($output, pos($output) - $code_size, $code_size, $value);
                }
            }
            # to allow literal '%'
            $output =~ s/%%/%/g;        
            $_ = $output;
        }
    } else {
        # just the filenames, please
        @results = map { $_->{FILENAME} } @results;
    }
    
    return @results;
}

sub search {
    croak "Method 'search' not implemented in " . __PACKAGE__;
}

# module return
1;

=head1 NAME

MP3::Find::Base - Base class for MP3::Find backends

=head1 SYNOPSIS

    package MyFinder;
    use base 'MP3::Find::Base';
    
    sub search {
        my $self = shift;
        my ($query, $dirs, $sort, $options) = @_;
        
        # do something to find and sort the mp3s...
        my @results = do_something(...);
        
        return @results;
    }
    
    package main;
    my $finder = MyFinder->new;
    
    # see MP3::Find for details about %options
    print "$_\n" foreach $finder->find_mp3s(\%options);        

=head1 DESCRIPTION

This is the base class for the classes that actually do the
searching and sorting for L<MP3::Find>.

=head1 METHODS

=head2 new

Really simple constructor. If you pass it a hash of options, it
will hang on to them for you.

=head2 search

This is the one you should override in your subclass. If you
don't, the base class C<search> method will croak.

The C<search> method is called by the C<find_mp3s> method with
the following arguments: the finder object, a hashref of query
parameters, an arrayref of directories to search, and a hashref
of miscellaneous options.

The search method should return a list of hashrefs representing
the results of the search. Each hashref should have the following
keys (all except C<FILENAME> are derived from the keys returned
by the C<get_mp3tag> and C<get_mp3Info> functions from L<MP3::Info>):

    FILENAME
    
    TITLE
    ARTIST
    ALBUM
    YEAR
    COMMENT
    GENRE
    TRACKNUM
    
    VERSION         -- MPEG audio version (1, 2, 2.5)
    LAYER           -- MPEG layer description (1, 2, 3)
    STEREO          -- boolean for audio is in stereo
    
    VBR             -- boolean for variable bitrate
    BITRATE         -- bitrate in kbps (average for VBR files)
    FREQUENCY       -- frequency in kHz
    SIZE            -- bytes in audio stream
    OFFSET          -- bytes offset that stream begins
    
    SECS            -- total seconds
    MM              -- minutes
    SS              -- leftover seconds
    MS              -- leftover milliseconds
    TIME            -- time in MM:SS
    
    COPYRIGHT       -- boolean for audio is copyrighted
    PADDING         -- boolean for MP3 frames are padded
    MODE            -- channel mode (0 = stereo, 1 = joint stereo,
                    -- 2 = dual channel, 3 = single channel)
    FRAMES          -- approximate number of frames
    FRAME_LENGTH    -- approximate length of a frame
    VBR_SCALE       -- VBR scale from VBR header


=head2 find_mp3s

The method that should be called by the program doing the searching.

See L<MP3::Find> for an explanation of the options that can be passed
to C<find_mp3s>.

=head1 TODO

More format codes? Possibly look into using L<String::Format>

=head1 SEE ALSO

L<MP3::Find>, L<MP3::Find::Filesystem>, L<MP3::Find::DB>

See L<MP3::Info> for more information about the fields you can
search and sort on.

=head1 AUTHOR

Peter Eichman <peichman@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2006 by Peter Eichman. All rights reserved.

This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=cut