id3fs-tag: implement -o
[id3fs.git] / bin / id3fs-tag
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::AudioFile;
23 use vars qw($me);
24 $me=($0=~/(?:.*\/)?(.*)/)[0];
25
26 my $verbose=0;
27 my $help=0;
28 my ($artist, $album, $track, $tracknum, $year, $v1genre, $comment,
29     $delete_artist, $delete_album, $delete_track, $delete_tracknum,
30     $delete_year, $delete_v1genre, $delete_comment, $delete_all,
31     $delete_genre, $genre, $add_tags, $delete_tags, $overwrite_tagvals,
32     $delete_tagvals);
33
34 Configure(qw(no_ignore_case));
35 my $optret=GetOptions(
36     "verbose|v"                      => \$verbose,
37     "help|h"                         => \$help,
38     "artist|a=s"                     => \$artist,
39     "album|l=s"                      => \$album,
40     "song|s=s"                       => \$track,
41     "tracknum|n=s"                   => \$tracknum,
42     "year|y=i"                       => \$year,
43     "v1genre|1=s"                    => \$v1genre,
44     "comment|c=s"                    => \$comment,
45     "delete-artist|A"                => \$delete_artist,
46     "delete-album|L"                 => \$delete_album,
47     "delete-song|S"                  => \$delete_track,
48     "delete-tracknum|N"              => \$delete_tracknum,
49     "delete-year|Y"                  => \$delete_year,
50     "delete-v1genre|0"               => \$delete_v1genre,
51     "delete-comment|C"               => \$delete_comment,
52     "delete|delete-all|D"            => \$delete_all,
53     "delete-genre|delete-all-tags|G" => \$delete_genre,
54     "genre|g|replace-all-tags|R=s"   => \$genre,
55     "add-tags|tags|t=s"              => \$add_tags,
56     "overwrite-tagvals|tagvals|o=s"  => \$overwrite_tagvals,
57     "delete-tags|T=s"                => \$delete_tags,
58     "delete-tags-with-values|O=s"    => \$delete_tagvals,
59     );
60
61 usage() if(!@ARGV || !$optret || $help);
62
63 while(my $filename=shift @ARGV)
64 {
65     unless(-f $filename)
66     {
67         warn("$me: $filename: not found\n");
68         next;
69     }
70     my $file=ID3FS::AudioFile->new($filename);
71     my $changes=0;
72     $changes =  do_deletes($file);
73     $changes += do_adds($file);
74     if($changes)
75     {
76         do_write($file);
77     }
78     else
79     {
80         do_display($file);
81     }
82 }
83
84 sub do_deletes
85 {
86     my($file)=@_;
87     if($delete_all)
88     {
89         $file->delete_all();
90         # we don't want to save the tag if we've deleted it
91         return 0;
92     }
93     $file->delete_artist()   if($delete_artist);
94     $file->delete_album()    if($delete_album);
95     $file->delete_track()    if($delete_track);
96     $file->delete_tracknum() if($delete_tracknum);
97     $file->delete_year()     if($delete_year);
98     $file->delete_v1genre()  if($delete_v1genre);
99     $file->delete_comment()  if($delete_comment);
100     $file->delete_genre()    if($delete_genre);
101     $file->delete_tags($delete_tags, 0)       if($delete_tags);
102     $file->delete_tags($delete_tagvals, 1)    if($delete_tagvals);
103     $file->delete_tags($overwrite_tagvals, 1) if($overwrite_tagvals);
104
105     my $donesomething=($delete_artist   || $delete_album || $delete_track   ||
106                        $delete_tracknum || $delete_year  || $delete_v1genre ||
107                        $delete_comment  || $delete_genre || $delete_tags    ||
108                        $delete_tagvals);
109     return($donesomething ? 1 : 0);
110 }
111
112 sub do_adds
113 {
114     my($file)=@_;
115     $file->artist($artist)     if($artist);
116     $file->album($album)       if($album);
117     $file->track($track)       if($track);
118     $file->tracknum($tracknum) if($tracknum);
119     $file->year($year)         if($year);
120     $file->v1genre($v1genre)   if($v1genre);
121     $file->comment($comment)   if($comment);
122     $file->genre($genre)       if($genre);
123     $file->add_tags($add_tags) if($add_tags);
124     $file->add_tags($overwrite_tagvals) if($overwrite_tagvals);
125
126     my $donesomething=(defined($artist)   || defined($album) ||
127                        defined($track)    || defined($tracknum) ||
128                        defined($year)     || defined($v1genre) ||
129                        defined($comment)  || defined($genre) ||
130                        defined($add_tags));
131     return( $donesomething ? 1 : 0 );
132 }
133
134 sub do_write
135 {
136     my($file)=@_;
137     $file->write();
138 }
139
140 sub do_display
141 {
142     my($file)=@_;
143     my $artist=$file->artist();
144     my $album=$file->album();
145     my $track=$file->track();
146     my $tracknum=$file->tracknum();
147     my $year=$file->year();
148     my $comment=$file->comment();
149     my $v1genre=$file->v1genre();
150     my @tags=$file->tags();
151     @tags = map { (ref($_) eq "ARRAY") ? join('/', grep {defined;} @{$_}) : $_; } @tags;
152     if($verbose)
153     {
154         print $file->path(), ":\n";
155         print "  tracknum: $tracknum\n" if($tracknum);
156         print "  artist: $artist\n"     if($artist);
157         print "  album: $album\n"       if($album);
158         print "  song: $track\n"        if($track);
159         print "  year: $year\n"         if($year);
160         print "  v1genre: $v1genre\n"   if($v1genre);
161         print "  comment: $comment\n"   if($comment);
162     }
163     else
164     {
165         my @fields=($file->path(), $tracknum, $artist, $album, $track,
166                     $year, $v1genre, $comment);
167         @fields=map { defined($_) ? $_ : ""; } @fields;
168         print join(':', @fields), "\n";
169     }
170     if(@tags)
171     {
172         if($verbose) { print "  tags: "; }
173         else         { print $file->path() . ":tags:"; }
174         print join(", ", @tags), "\n";
175     }
176 }
177
178 sub usage
179 {
180     die("Usage: $me [-vhALSNY0CDG] [-a ARTIST] [-l ALBUM] [-s SONG] [-n TRACKNUM] FILES...\n".
181         "       $me [-y YEAR] [-g GENRE] [-1 V1GENRE] [-c COMMENT] [--] FILES...\n".
182         "       $me [-t TAGS,TO,ADD] [-T TAGS,TO,DELETE]  FILES...\n".
183         "       $me [-r TAGS,TO,DELETE TAGS,TO,ADD] [-R TAGS,TO,OVERWRITE,WITH] FILES...\n".
184         "With no options, displays current info in tag\n".
185         "Options:\n".
186         " -a|--artist=ARTIST                    Set artist\n".
187         " -l|--album=ALBUM                      Set album\n".
188         " -s|--song=SONG                        Set song\n".
189         " -n|--tracknum=NUM                     Set tracknum\n".
190         " -y|--year=NUM                         Set year\n".
191         " -1|--v1genre=GENRE                    Set ID3v1 genre\n".
192         " -c|--comment=COMMENT                  Set comment\n".
193         " -A|--delete-artist                    Delete artist\n".
194         " -L|--delete-album                     Delete album\n".
195         " -S|--delete-song                      Delete song\n".
196         " -N|--delete-tracknum                  Delete tracknum\n".
197         " -Y|--delete-year                      Delete year\n".
198         " -0|--delete-v1genre                   Delete ID3v1 genre\n".
199         " -C|--delete-comment                   Delete comment\n".
200         " -D|--delete|delete-all                Delete entire ID3 tag\n".
201         " -G|--delete-genre|--delete-all-tags   Delete all tags stored in genre\n".
202         " -g|-R|replace-all-tags|--genre=GENRE  Replace all tags in genre tag\n".
203         " -t|--add-tags|tags=TAG1,TAG2          Add tags to genre tag, merging with existing ones\n".
204         " -T|--delete-tags=TAG1,TAG2            Delete tags from genre\n".
205         " -r|--replace-tags TAGS1 TAGS2         Replace TAGS1 in genre with TAGS2\n".
206         " -v|--verbose                          Verbose display\n".
207         " -h|--help                             This help\n".
208         " --                                    End of options\n");
209 }
210
211 __END__
212
213 =head1 NAME
214
215 id3fs-tag - Add files to id3fs index
216
217 =head1 SYNOPSIS
218
219 B<id3fs-tag> [B<-lvh>] S<[B<-d >I<basedir>]> S<[B<-f >I<dbpath>]> S<[B<-e >I<mp3,ogg,flac>]> [B<-->] [I<DIR>...]
220
221 =head1 DESCRIPTION
222
223 Extracts id3 tags from mp3 files (and comment tags from ogg and flac
224 files) and adds them to a sqlite database, ready for mounting
225 with L<id3fsd(8)>.
226
227 =head1 OPTIONS
228
229 =over 4
230
231 =item B<-l> | B<--list>
232
233 List tags in use in specified database.
234
235 =item S<B<-d >I<PATH>> | S<B<--dir=>I<PATH>>
236
237 Specify base directory of source files. All files will be indexed
238 relative to this point.
239
240 If not specified, defaults to the first non-option argument on the
241 command line. Note that to avoid ambiguities, if more than one
242 directory is specified on the command line, the base directory must
243 be specified explicitly.
244
245 All files indexed must be under the base directory.
246
247 =item S<B<-f >I<FILE>> | S<B<--database=>I<FILE>>
248
249 Database file to use. If not specified, defaults to
250 a hidden file called B<".id3fs"> under the base directory.
251
252 =item S<B<-e >I<EXT1,EXT2>> | S<B<--extensions=>I<EXT1,EXT2>>
253
254 File extensions to consider when indexing.
255 Defaults to B<.mp3>, B<.ogg> and B<.flac>.
256
257 =item B<-v>
258
259 Enable verbose operation.
260
261 =item B<-h>
262
263 Show a short help message.
264
265 =item B<-->
266
267 End of options.
268
269 =back
270
271 =head1 EXAMPLES
272
273 Index all files in the current directory:
274
275     id3fs-tag .
276
277 Index current directory, printing each subdirectory as it recurses
278 into it:
279
280     id3fs-tag -v .
281
282 Just index some sub-directories:
283
284     id3fs-tag -d . dir1 dir2
285
286 Store the database in a custom location:
287
288     id3fs-tag -f ~/.id3fs/index.sqlite .
289
290 Only index .mp3 and .flac files:
291
292     id3fs-tag -e mp3,flac .
293
294 =head1 BUGS
295
296 Please report any found to ianb@erislabs.net
297
298 =head1 SEE ALSO
299
300 L<id3fsd(8)>, L<MP3::Tag>, L<Audio::Flac::Header>, L<Ogg::Vorbis::Header>
301
302 =head1 AUTHOR
303
304 Ian Beckwith <ianb@erislabs.net>
305
306 Many thanks to Aubrey Stark-Toller for help wrangling SQL.
307
308 =head1 AVAILABILITY
309
310 The latest version can be found at:
311
312 L<http://erislabs.net/ianb/projects/id3fs/>
313
314 =head1 COPYRIGHT
315
316 Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
317
318 This program is free software: you can redistribute it and/or modify
319 it under the terms of the GNU General Public License as published by
320 the Free Software Foundation, either version 3 of the License, or
321 (at your option) any later version.
322
323 This program is distributed in the hope that it will be useful,
324 but WITHOUT ANY WARRANTY; without even the implied warranty of
325 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
326 GNU General Public License for more details.
327
328 You should have received a copy of the GNU General Public License
329 along with this program.  If not, see <http://www.gnu.org/licenses/>.
330
331 =cut