source: mp3-find/trunk/lib/MP3/Find/Filesystem.pm @ 28

Last change on this file since 28 was 28, checked in by peter, 18 years ago
  • check for presence of MP3::Tag when Filesystem.pm is loaded
  • use the "Text" value of hash MP3::Tag::ID3v2 frames for searching
  • check for definedness of MP3::Tag before trying to read tags (keep from dying in the middle of a search)
File size: 5.3 KB
Line 
1package MP3::Find::Filesystem;
2
3use strict;
4use warnings;
5
6use base 'MP3::Find::Base';
7
8use File::Find;
9use MP3::Info;
10use Scalar::Util qw(looks_like_number);
11
12eval {
13    require Sort::Key;
14    Sort::Key->import(qw(multikeysorter));
15    use Sort::Key::Natural;
16};
17my $USE_SORT_KEY = $@ ? 0 : 1;
18
19
20eval { require MP3::Tag };
21my $CAN_USE_ID3V2 = $@ ? 0 : 1;
22
23use_winamp_genres();
24
25sub search {
26    my $self = shift;
27    my ($query, $dirs, $sort, $options) = @_;
28   
29    # prep the search patterns as regexes
30    foreach (keys(%$query)) {
31        my $ref = ref $$query{$_};
32        # make arrays into 'OR' searches
33        if ($ref eq 'ARRAY') {
34            $$query{$_} = '(' . join('|', @{ $$query{$_} }) . ')';
35        }
36        # convert to a regex unless it already IS a regex       
37        unless ($ref eq 'Regexp') {
38            $$query{$_} = "^$$query{$_}\$" if $$options{exact_match};
39            $$query{$_} = $$options{ignore_case} ? qr[$$query{$_}]i : qr[$$query{$_}];
40        }
41    }
42   
43    if ($$options{exclude_path}) {
44        my $ref = ref $$options{exclude_path};
45        if ($ref eq 'ARRAY') {
46            $$options{exclude_path} = '(' . join('|', @{ $$options{exclude_path} }) . ')';
47        }
48        unless ($ref eq 'Regexp') {
49            $$options{exclude_path} = qr[$$options{exclude_path}];
50        }
51    }
52   
53    if ($$options{use_id3v2} and not $CAN_USE_ID3V2) {
54        # they want to use ID3v2, but don't have MP3::Tag
55        warn "MP3::Tag is required to search ID3v2 tags\n";
56    }
57       
58    # run the actual find
59    my @results;
60    find(sub { match_mp3($File::Find::name, $query, \@results, $options) }, $_) foreach @$dirs;
61   
62    # sort the results
63    if (@$sort) {
64        if ($USE_SORT_KEY) {
65            # use Sort::Key to do a (hopefully!) faster sort
66            #TODO: profile this; at first glance, it doesn't actually seem to be any faster
67            #warn "Using Sort::Key";
68            my $sorter = multikeysorter(
69                sub { my $info = $_; map { $info->{uc $_} } @$sort },
70                map { 'natural' } @$sort
71            );
72            @results = $sorter->(@results);
73        } else {
74            @results = sort {
75                my $compare;
76                foreach (map { uc } @$sort) {
77                    # use Scalar::Util to do the right sort of comparison
78                    $compare = (looks_like_number($a->{$_}) && looks_like_number($b->{$_})) ?
79                        $a->{$_} <=> $b->{$_} :
80                        $a->{$_} cmp $b->{$_};
81                    # we found a field they differ on
82                    last if $compare;
83                }
84                return $compare;
85            } @results;
86        }
87    }
88   
89    return @results
90}
91
92sub match_mp3 {
93    my ($filename, $query, $results, $options) = @_;
94   
95    return unless $filename =~ m{[^/]\.mp3$};
96    if ($$options{exclude_path}) {
97        return if $filename =~ $$options{exclude_path};
98    }
99   
100    my $mp3 = {
101        FILENAME => $filename,
102        %{ get_mp3tag($filename)  || {} },
103        %{ get_mp3info($filename) || {} },
104    };
105   
106    if ($CAN_USE_ID3V2 and $$options{use_id3v2}) {
107        # add ID3v2 tag info, if present
108        my $mp3_tags = MP3::Tag->new($filename);
109        unless (defined $mp3_tags) {
110            warn "Can't get MP3::Tag object for $filename\n";
111        } else {
112            $mp3_tags->get_tags;
113            if (my $id3v2 = $mp3_tags->{ID3v2}) {
114                for my $frame_id (keys %{ $id3v2->get_frame_ids }) {
115                    my ($info) = $id3v2->get_frame($frame_id);
116                    if (ref $info eq 'HASH') {
117                        # use the "Text" value as the value for this frame, if present
118                        $mp3->{$frame_id} = $info->{Text} if exists $info->{Text};
119                    } else {
120                        $mp3->{$frame_id} = $info;
121                    }
122                }
123            }
124        }
125    }
126
127    for my $field (keys(%{ $query })) {
128        my $value = $mp3->{uc($field)};
129        return unless defined $value;
130        return unless $value =~ $query->{$field};
131    }
132   
133    push @{ $results }, $mp3;
134}
135
136# module return
1371;
138
139=head1 NAME
140
141MP3::Find::Filesystem - File::Find-based backend to MP3::Find
142
143=head1 SYNOPSIS
144
145    use MP3::Find::Filesystem;
146    my $finder = MP3::Find::Filesystem->new;
147   
148    my @mp3s = $finder->find_mp3s(
149        dir => '/home/peter/music',
150        query => {
151            artist => 'ilyaimy',
152            album  => 'myxomatosis',
153        },
154        ignore_case => 1,
155    );
156
157=head1 REQUIRES
158
159L<File::Find>, L<MP3::Info>, L<Scalar::Util>
160
161L<MP3::Tag> is also needed if you want to search using ID3v2 tags.
162
163=head1 DESCRIPTION
164
165This module implements the C<search> method from L<MP3::Find::Base>
166using a L<File::Find> based search of the local filesystem.
167
168=head2 Special Options
169
170=over
171
172=item C<exclude_path>
173
174Scalar or arrayref; any file whose name matches any of these paths
175will be skipped.
176
177=item C<use_id3v2>
178
179Boolean, defaults to false. If set to true, MP3::Find::Filesystem will
180use L<MP3::Tag> to get the ID3v2 tag for each file. You can then search
181for files by their ID3v2 data, using the four-character frame names.
182This isn't very useful if you are just search by artist or title, but if,
183for example, you have made use of the C<TOPE> ("Orignal Performer") frame,
184you could search for all the cover songs in your collection:
185
186    $finder->find_mp3s(query => { tope => '.' });
187
188As with the basic query keys, ID3v2 query keys are converted to uppercase
189internally.
190
191=back
192
193=head1 SEE ALSO
194
195L<MP3::Find>, L<MP3::Find::DB>
196
197=head1 AUTHOR
198
199Peter Eichman <peichman@cpan.org>
200
201=head1 COPYRIGHT AND LICENSE
202
203Copyright (c) 2006 by Peter Eichman. All rights reserved.
204
205This program is free software; you can redistribute it and/or
206modify it under the same terms as Perl itself.
207
208=cut
Note: See TracBrowser for help on using the repository browser.