remove "use lib"
[id3fs.git] / bin / id3fs-index
1 #!/usr/bin/perl -w
2 #
3 # id3fs - a FUSE-based filesystem for browsing audio metadata
4 # Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19 use strict;
20 use Getopt::Long qw(Configure);
21 use ID3FS::DB;
22 use File::Find;
23 use vars qw($me);
24 $me=($0=~/(?:.*\/)?(.*)/)[0];
25
26 my $verbose=0;
27 my $help=0;
28 my $basedir=undef;
29 my $dbpath=undef;
30 my $list=0;
31 my $init=0;
32 my @extensions=qw(mp3 flac ogg);
33 my $files_pruned;
34
35 Configure(qw(bundling no_ignore_case));
36 my $optret=GetOptions(
37     "verbose|v"      => \$verbose,
38     "help|h"         => \$help,
39     "dir|d=s"        => \$basedir,
40     "database|f=s"   => \$dbpath,
41     "extensions|e=s" => sub { @extensions=split(/\s+|\s*,\s*/, $_[1]); },
42     "list|l"         => \$list,
43     );
44
45 if($list && !@ARGV)
46 {
47     push(@ARGV, ".");
48 }
49 usage() if(!@ARGV || !$optret || $help);
50 $init=1 unless($list);
51
52 unless(defined($basedir) && defined($dbpath))
53 {
54     $basedir=ID3FS::DB::find_db($me, $init, @ARGV);
55     exit unless($basedir);
56     my $absbase=Cwd::abs_path($basedir);
57     for my $dir (@ARGV)
58     {
59         if(Cwd::abs_path($dir) !~ /^\Q$absbase\E/)
60         {
61             die("$me: $dir: must be under basedir $absbase - use --basedir to specify\n");
62         }
63     }
64 }
65 my $db=ID3FS::DB->new($me, $verbose, $init, $basedir, $dbpath);
66 exit unless($db);
67
68 if($list)
69 {
70     list_tags($db);
71 }
72 else
73 {
74     $db->last_update(time());
75     my $base=$db->base_dir();
76     my $abs_base=Cwd::abs_path($base);
77     while(my $path=shift)
78     {
79         if(Cwd::abs_path($path) !~ /^$abs_base/)
80         {
81             print "$me: $path is outside $base, skipping\n";
82         }
83         File::Find::find( {wanted => \&wanted, follow => 1, no_chdir => 1}, $path);
84     }
85     my $directories_pruned=$db->prune_directories();
86     if($files_pruned || $directories_pruned)
87     {
88         print "$me: pruning removed files from db\n" if $verbose;
89         $db->remove_unused();
90     }
91     print "$me: analyzing db\n" if $verbose;
92     $db->analyze();
93 }
94
95 sub wanted
96 {
97     my $ext='';
98     if(/.*\.(.*)/) { $ext=lc($1); }
99     if(-d)
100     {
101         print("$_\n") if $verbose;
102         prune($_);
103     }
104     elsif(-f && scalar(grep({ $ext eq lc($_);} @extensions)))
105     {
106         s/^\.\///;
107         $db->add($_);
108     }
109 }
110
111 sub prune
112 {
113     my $dir=shift;
114     $dir=Cwd::abs_path($dir);
115     return unless(opendir(DIR, $dir));
116     my $base=Cwd::abs_path($db->base_dir());
117     $dir=~s/^$base\/?//;
118     my @oldfiles=$db->files_in($dir);
119     my @newfiles=grep { !/^\.\.?$/; } readdir(DIR);
120     closedir(DIR);
121     @oldfiles=sort @oldfiles;
122     @newfiles=sort @newfiles;
123     my %hash;
124     # hash slice!
125     @hash{@newfiles}=();
126     for my $file (@oldfiles)
127     {
128         unless(exists($hash{$file}))
129         {
130             $files_pruned=1;
131             $db->unindex($dir, $file);
132         }
133     }
134 }
135
136 sub list_tags
137 {
138     my($db)=@_;
139     my @baretags=$db->bare_tags();
140     my $valtags=$db->tags_with_values();
141     if(@baretags)
142     {
143         print "BARE TAGS\n";
144         print join(', ', sort @baretags), "\n\n";
145     }
146     if(keys(%$valtags))
147     {
148         print "TAGS WITH VALUES\n";
149         for my $key (sort keys %$valtags)
150         {
151             print "$key: ", join(', ', sort(@{$valtags->{$key}})), "\n";
152         }
153     }
154 }
155
156 sub usage
157 {
158     die("Usage: $me [-lvh] [-d basedir] [-f dbpath] [-e mp3,ogg,flac] [--] DIR...\n".
159         " -d|--dir=PATH              Base directory of source files (default: ARGV[0])\n".
160         " -f|--database=FILE         Path to database file (default: basedir/.id3fs)\n".
161         " -e|--extensions=EXT1,EXT2  File extensions to index (default: mp3, ogg, flac)\n".
162         " -l|list                    List tags in use\n" .
163         " -v|--verbose               Verbose\n".
164         " -h|--help                  This help\n".
165         " --                         End of options\n");
166 }
167
168 __END__
169
170 =head1 NAME
171
172 id3fs-index - Add files to id3fs index
173
174 =head1 SYNOPSIS
175
176 B<id3fs-index> [B<-lvh>] S<[B<-d >I<basedir>]> S<[B<-f >I<dbpath>]> S<[B<-e >I<mp3,ogg,flac>]> [B<-->] [I<DIR>...]
177
178 =head1 DESCRIPTION
179
180 Extracts id3 tags from mp3 files (and comment tags from ogg and flac
181 files) and adds them to a sqlite database, ready for mounting
182 with L<id3fsd(8)>.
183
184 =head1 OPTIONS
185
186 =over 4
187
188 =item B<-l> | B<--list>
189
190 List tags in use in specified database.
191
192 =item S<B<-d >I<PATH>> | S<B<--dir=>I<PATH>>
193
194 Specify base directory of source files. All files will be indexed
195 relative to this point.
196
197 If not specified, defaults to the first non-option argument on the
198 command line. Note that to avoid ambiguities, if more than one
199 directory is specified on the command line, the base directory must
200 be specified explicitly.
201
202 All files indexed must be under the base directory.
203
204 =item S<B<-f >I<FILE>> | S<B<--database=>I<FILE>>
205
206 Database file to use. If not specified, defaults to
207 a hidden file called B<".id3fs"> under the base directory.
208
209 =item S<B<-e >I<EXT1,EXT2>> | S<B<--extensions=>I<EXT1,EXT2>>
210
211 File extensions to consider when indexing.
212 Defaults to B<.mp3>, B<.ogg> and B<.flac>.
213
214 =item B<-v>
215
216 Enable verbose operation.
217
218 =item B<-h>
219
220 Show a short help message.
221
222 =item B<-->
223
224 End of options.
225
226 =back
227
228 =head1 EXAMPLES
229
230 Index all files in the current directory:
231
232     id3fs-index .
233
234 Index current directory, printing each subdirectory as it recurses
235 into it:
236
237     id3fs-index -v .
238
239 Just index some sub-directories:
240
241     id3fs-index -d . dir1 dir2
242
243 Store the database in a custom location:
244
245     id3fs-index -f ~/.id3fs/index.sqlite .
246
247 Only index .mp3 and .flac files:
248
249     id3fs-index -e mp3,flac .
250
251 =head1 BUGS
252
253 Please report any found to ianb@erislabs.net
254
255 =head1 SEE ALSO
256
257 L<id3fsd(8)>, L<MP3::Tag>, L<Audio::Flac::Header>, L<Ogg::Vorbis::Header>
258
259 =head1 AUTHOR
260
261 Ian Beckwith <ianb@erislabs.net>
262
263 Many thanks to Aubrey Stark-Toller for help wrangling SQL.
264
265 =head1 AVAILABILITY
266
267 The latest version can be found at:
268
269 L<http://erislabs.net/ianb/projects/id3fs/>
270
271 =head1 COPYRIGHT
272
273 Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
274
275 This program is free software: you can redistribute it and/or modify
276 it under the terms of the GNU General Public License as published by
277 the Free Software Foundation, either version 3 of the License, or
278 (at your option) any later version.
279
280 This program is distributed in the hope that it will be useful,
281 but WITHOUT ANY WARRANTY; without even the implied warranty of
282 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
283 GNU General Public License for more details.
284
285 You should have received a copy of the GNU General Public License
286 along with this program.  If not, see <http://www.gnu.org/licenses/>.
287
288 =cut