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