7f0c17ca67464afd28edd3f359334c8553b20a52
[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     "quiet|q"        => sub { $verbose=0; },
40     "help|h"         => \$help,
41     "dir|d=s"        => \$basedir,
42     "database|f=s"   => \$dbpath,
43     "extensions|e=s" => sub { @extensions=split(/\s+|\s*,\s*/, $_[1]); },
44     "list|l"         => \$list,
45     );
46
47 if($list && !@ARGV)
48 {
49     push(@ARGV, ".");
50 }
51 usage() if(!@ARGV || !$optret || $help);
52 $init=1 unless($list);
53
54 unless(defined($basedir) && defined($dbpath))
55 {
56     $basedir=ID3FS::DB::find_db($me, $init, @ARGV);
57     exit unless($basedir);
58     my $absbase=Cwd::abs_path($basedir);
59     for my $dir (@ARGV)
60     {
61         if(Cwd::abs_path($dir) !~ /^\Q$absbase\E/)
62         {
63             die("$me: $dir: must be under basedir $absbase - use --basedir to specify\n");
64         }
65     }
66 }
67 my $db=ID3FS::DB->new($me, $verbose, $init, $basedir, $dbpath);
68 exit unless($db);
69
70 if($list)
71 {
72     list_tags($db);
73 }
74 else
75 {
76     $db->last_update(time());
77     my $base=$db->base_dir();
78     my $abs_base=Cwd::abs_path($base);
79     while(my $path=shift)
80     {
81         if(Cwd::abs_path($path) !~ /^$abs_base/)
82         {
83             print "$me: $path is outside $base, skipping\n";
84         }
85         File::Find::find( {wanted => \&wanted, follow => 1, no_chdir => 1}, $path);
86     }
87     my $directories_pruned=$db->prune_directories();
88     if($files_pruned || $directories_pruned)
89     {
90         print "$me: removing data from pruned files\n" if $verbose;
91         $db->remove_unused();
92     }
93     print "$me: analyzing db\n" if $verbose;
94     $db->analyze();
95 }
96
97 sub wanted
98 {
99     my $ext='';
100     if(/.*\.(.*)/) { $ext=lc($1); }
101     if(-d)
102     {
103         print("$_\n") if $verbose;
104         prune($_);
105     }
106     elsif(-f && scalar(grep({ $ext eq lc($_);} @extensions)))
107     {
108         s/^\.\///;
109         $db->add($_);
110     }
111 }
112
113
114 sub prune
115 {
116     my $dir=shift;
117     $dir=Cwd::abs_path($dir);
118     return unless(opendir(DIR, $dir));
119     my $base=Cwd::abs_path($db->base_dir());
120     $dir=~s/^$base\/?//;
121     my @oldfiles=$db->files_in($dir);
122     my @newfiles=grep { !/^\.\.?$/; } readdir(DIR);
123     closedir(DIR);
124     @oldfiles=sort @oldfiles;
125     @newfiles=sort @newfiles;
126     my %hash;
127     @hash{@newfiles}=();
128     for my $file (@oldfiles)
129     {
130         unless(exists($hash{$file}))
131         {
132             $files_pruned=1;
133             $db->unindex($dir, $file);
134         }
135     }
136 }
137
138 sub list_tags
139 {
140     my($db)=@_;
141     my @baretags=$db->bare_tags();
142     my $valtags=$db->tags_with_values();
143     if(@baretags)
144     {
145         print "BARE TAGS\n";
146         print join(', ', sort @baretags), "\n\n";
147     }
148     if(keys(%$valtags))
149     {
150         print "TAGS WITH VALUES\n";
151         for my $key (sort keys %$valtags)
152         {
153             print "$key: ", join(', ', sort(@{$valtags->{$key}})), "\n";
154         }
155     }
156 }
157
158 sub usage
159 {
160     die("Usage: $me [-vqh] [-d basedir] [-f dbpath] [-e mp3,ogg,flac] [--] DIR...\n".
161         " -v|--verbose\t\t\tVerbose\n".
162         " -q|--quiet\t\t\tQuiet (default)\n".
163         " -d|--dir=PATH\t\t\tBase directory of source files (default: ARGV[0])\n".
164         " -f|--database=FILE\t\tPath to database file (default: basedir/.id3fs)\n".
165         " -e|--extensions=EXT1,EXT2\tFile extensions to index (default: mp3, ogg, flac)\n".
166         " -h|--help\t\t\tThis help\n".
167         " --\t\t\t\tEnd of options\n");
168 }
169
170 __END__
171
172 =head1 NAME
173
174 id3fs-index - Add files to id3fs index
175
176 =head1 SYNOPSIS
177
178 B<id3fs-index> [B<-vqh>] S<[B<-d >I<basedir>]> S<[B<-f >I<dbpath>]> S<[B<-e >I<mp3,ogg,flac>]> [B<-->] [I<DIR>...]
179
180 =head1 DESCRIPTION
181
182 Extracts id3 tags from mp3 files (and comment tags from ogg and flac
183 files) and adds them to a sqlite database, ready for mounting
184 with L<id3fsd(8)>.
185
186 =head1 OPTIONS
187
188 =over 4
189
190 =item B<-v>
191
192 Enable verbose operation.
193
194 =item B<-q>
195
196 Quiet (no output). This is the default.
197
198 =item S<B<-d >I<PATH>> | S<B<--dir=>I<PATH>>
199
200 Specify base directory of source files. All files will be indexed
201 relative to this point.
202
203 If not specified, defaults to the first non-option argument on the
204 command line. Note that to avoid ambiguities, if more than one
205 directory is specified on the command line, the base directory must
206 be specified explicitly.
207
208 All files indexed must be under the base directory.
209
210 =item S<B<-f >I<FILE>> | S<B<--database=>I<FILE>>
211
212 Database file to use. If not specified, defaults to
213 a hidden file called B<".id3fs"> under the base directory.
214
215 =item S<B<-e >I<EXT1,EXT2>> | S<B<--extensions=>I<EXT1,EXT2>>
216
217 File extensions to consider when indexing.
218 Defaults to B<.mp3>, B<.ogg> and B<.flac>.
219
220 =item B<-h>
221
222 Show a short help message.
223
224 =item B<-->
225
226 End of options.
227
228 =back
229
230 =head1 EXAMPLES
231
232 Index all files in the current directory:
233
234     id3fs-index .
235
236 Index current directory, printing each subdirectory as it recurses
237 into it:
238
239     id3fs-index -v .
240
241 Just index some sub-directories:
242
243     id3fs-index -d . dir1 dir2
244
245 Store the database in a custom location:
246
247     id3fs-index -f ~/.id3fs/index.sqlite .
248
249 Only index .mp3 and .flac files:
250
251     id3fs-index -e mp3,flac .
252
253 =head1 BUGS
254
255 Please report any found to ianb@erislabs.net
256
257 =head1 SEE ALSO
258
259 L<id3fsd(8)>
260
261 =head1 AUTHOR
262
263 Ian Beckwith <ianb@erislabs.net>
264
265 =head1 AVAILABILITY
266
267 The latest version can be found at:
268
269 B<http://erislabs.net/ianb/projects/id3fs/>
270
271 =head1 COPYRIGHT
272
273 Copyright 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