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