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