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