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