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