3 # id3fs - a FUSE-based filesystem for browsing audio metadata
4 # Copyright (C) 2010 Ian Beckwith <ianb@erislabs.net>
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.
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.
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/>.
19 use lib '/home/ianb/projects/id3fs/id3fs/lib'; # FIXME: remove
21 use Getopt::Long qw(Configure);
25 $me=($0=~/(?:.*\/)?(.*)/)[0];
27 my @extensions=qw(mp3); # FIXME: flac ogg
30 my ($artist, $album, $track, $tracknum, $year, $v1genre, $comment,
31 $delete_artist, $delete_album, $delete_track, $delete_tracknum,
32 $delete_year, $delete_v1genre, $delete_comment, $delete_all,
33 $delete_genre, $genre, $add_tags, $delete_tags, $overwrite_tagvals,
36 Configure(qw(bundling no_ignore_case));
37 my $optret=GetOptions(
38 "verbose|v" => \$verbose,
40 "artist|a=s" => \$artist,
41 "album|l=s" => \$album,
42 "song|s=s" => \$track,
43 "tracknum|n=s" => \$tracknum,
45 "v1genre|1=s" => \$v1genre,
46 "comment|c=s" => \$comment,
47 "delete-artist|A" => \$delete_artist,
48 "delete-album|L" => \$delete_album,
49 "delete-song|S" => \$delete_track,
50 "delete-tracknum|N" => \$delete_tracknum,
51 "delete-year|Y" => \$delete_year,
52 "delete-v1genre|0" => \$delete_v1genre,
53 "delete-comment|C" => \$delete_comment,
54 "delete|delete-all|D" => \$delete_all,
55 "delete-genre|delete-all-tags|G" => \$delete_genre,
56 "genre|g|replace-all-tags|R=s" => \$genre,
57 "add-tags|tags|t=s" => \$add_tags,
58 "overwrite-tagvals|tagvals|o=s" => \$overwrite_tagvals,
59 "delete-tags|T=s" => \$delete_tags,
60 "delete-tags-with-values|O=s" => \$delete_tagvals,
63 usage() if(!@ARGV || !$optret || $help);
65 while(my $path=shift @ARGV)
69 warn("$me: $path: not found\n");
72 File::Find::find( {wanted => \&wanted, follow => 1, no_chdir => 1}, $path);
78 if(/.*\.(.*)/) { $ext=lc($1); }
79 if(-f && scalar(grep({ $ext eq lc($_);} @extensions)))
81 my $file=ID3FS::AudioFile->new($_);
84 $changes = do_deletes($file);
85 $changes += do_adds($file);
103 # we don't want to save the tag if we've deleted it
106 $file->delete_artist() if($delete_artist);
107 $file->delete_album() if($delete_album);
108 $file->delete_track() if($delete_track);
109 $file->delete_tracknum() if($delete_tracknum);
110 $file->delete_year() if($delete_year);
111 $file->delete_v1genre() if($delete_v1genre);
112 $file->delete_comment() if($delete_comment);
113 $file->delete_genre() if($delete_genre || $genre);
114 $file->delete_tags($delete_tags, 0) if($delete_tags);
115 $file->delete_tags($delete_tagvals, 1) if($delete_tagvals);
116 $file->delete_tags($overwrite_tagvals, 1) if($overwrite_tagvals);
118 my $donesomething=($delete_artist || $delete_album || $delete_track ||
119 $delete_tracknum || $delete_year || $delete_v1genre ||
120 $delete_comment || $delete_genre || $delete_tags ||
121 $delete_tagvals || defined($genre) || $overwrite_tagvals);
122 return($donesomething ? 1 : 0);
128 $file->artist($artist) if($artist);
129 $file->album($album) if($album);
130 $file->track($track) if($track);
131 $file->tracknum($tracknum) if($tracknum);
132 $file->year($year) if($year);
133 $file->v1genre($v1genre) if($v1genre);
134 $file->comment($comment) if($comment);
135 $file->add_tags($add_tags) if($add_tags);
136 $file->add_tags($genre) if($genre);
137 $file->add_tags($overwrite_tagvals) if($overwrite_tagvals);
139 my $donesomething=(defined($artist) || defined($album) ||
140 defined($track) || defined($tracknum) ||
141 defined($year) || defined($v1genre) ||
142 defined($comment) || defined($genre) ||
143 defined($add_tags) || defined($overwrite_tagvals));
144 return( $donesomething ? 1 : 0 );
156 my $artist=$file->artist();
157 my $album=$file->album();
158 my $track=$file->track();
159 my $tracknum=$file->tracknum();
160 my $year=$file->year();
161 my $comment=$file->comment();
162 my $v1genre=$file->v1genre();
163 my @tags=$file->tags();
164 @tags = map { (ref($_) eq "ARRAY") ? join('/', grep {defined;} @{$_}) : $_; } @tags;
167 print $file->path(), ":\n";
168 print " tracknum: $tracknum\n" if($tracknum);
169 print " artist: $artist\n" if($artist);
170 print " album: $album\n" if($album);
171 print " song: $track\n" if($track);
172 print " year: $year\n" if($year);
173 print " v1genre: $v1genre\n" if($v1genre);
174 print " comment: $comment\n" if($comment);
178 my @fields=($file->path(), $tracknum, $artist, $album, $track,
179 $year, $v1genre, $comment);
180 @fields=map { defined($_) ? $_ : ""; } @fields;
181 print join(':', @fields), "\n";
185 if($verbose) { print " tags: "; }
186 # else { print $file->path() . ":tags:"; }
187 else { print "tags:"; }
188 print join(", ", @tags), "\n";
194 die("Usage: $me [-vhALSNY0CDG] [-a ARTIST] [-l ALBUM] [-s SONG] [-n TRACKNUM] FILES...\n".
195 " $me [-y YEAR] [-g GENRE] [-1 V1GENRE] [-c COMMENT] [--] FILES...\n".
196 " $me [-t TAGS,TO,ADD] [-T TAGS,TO,DELETE] FILES...\n".
197 " $me [-r TAGS,TO,DELETE TAGS,TO,ADD] [-R TAGS,TO,OVERWRITE,WITH] FILES...\n".
198 "With no options, displays current info in tag\n".
200 " -a|--artist=ARTIST Set artist\n".
201 " -l|--album=ALBUM Set album\n".
202 " -s|--song=SONG Set song\n".
203 " -n|--tracknum=NUM Set tracknum\n".
204 " -y|--year=NUM Set year\n".
205 " -1|--v1genre=GENRE Set ID3v1 genre\n".
206 " -c|--comment=COMMENT Set comment\n".
207 " -A|--delete-artist Delete artist\n".
208 " -L|--delete-album Delete album\n".
209 " -S|--delete-song Delete song\n".
210 " -N|--delete-tracknum Delete tracknum\n".
211 " -Y|--delete-year Delete year\n".
212 " -0|--delete-v1genre Delete ID3v1 genre\n".
213 " -C|--delete-comment Delete comment\n".
214 " -D|--delete|delete-all Delete entire ID3 tag\n".
215 " -G|--delete-genre|--delete-all-tags Delete all tags stored in genre\n".
216 " -g|-R|replace-all-tags|--genre=GENRE Replace all tags in genre tag\n".
217 " -t|--add-tags|tags=TAG1,TAG2 Add tags to genre tag, merging with existing ones\n".
218 " -T|--delete-tags=TAG1,TAG2 Delete tags from genre\n".
219 " -r|--replace-tags TAGS1 TAGS2 Replace TAGS1 in genre with TAGS2\n".
220 " -v|--verbose Verbose display\n".
221 " -h|--help This help\n".
222 " -- End of options\n");
229 id3fs-tag - Add files to id3fs index
233 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>...]
237 Extracts id3 tags from mp3 files (and comment tags from ogg and flac
238 files) and adds them to a sqlite database, ready for mounting
245 =item B<-l> | B<--list>
247 List tags in use in specified database.
249 =item S<B<-d >I<PATH>> | S<B<--dir=>I<PATH>>
251 Specify base directory of source files. All files will be indexed
252 relative to this point.
254 If not specified, defaults to the first non-option argument on the
255 command line. Note that to avoid ambiguities, if more than one
256 directory is specified on the command line, the base directory must
257 be specified explicitly.
259 All files indexed must be under the base directory.
261 =item S<B<-f >I<FILE>> | S<B<--database=>I<FILE>>
263 Database file to use. If not specified, defaults to
264 a hidden file called B<".id3fs"> under the base directory.
266 =item S<B<-e >I<EXT1,EXT2>> | S<B<--extensions=>I<EXT1,EXT2>>
268 File extensions to consider when indexing.
269 Defaults to B<.mp3>, B<.ogg> and B<.flac>.
273 Enable verbose operation.
277 Show a short help message.
287 Index all files in the current directory:
291 Index current directory, printing each subdirectory as it recurses
296 Just index some sub-directories:
298 id3fs-tag -d . dir1 dir2
300 Store the database in a custom location:
302 id3fs-tag -f ~/.id3fs/index.sqlite .
304 Only index .mp3 and .flac files:
306 id3fs-tag -e mp3,flac .
310 Please report any found to ianb@erislabs.net
314 L<id3fsd(8)>, L<MP3::Tag>, L<Audio::Flac::Header>, L<Ogg::Vorbis::Header>
318 Ian Beckwith <ianb@erislabs.net>
320 Many thanks to Aubrey Stark-Toller for help wrangling SQL.
324 The latest version can be found at:
326 L<http://erislabs.net/ianb/projects/id3fs/>
330 Copyright (C) 2010 Ian Beckwith <ianb@erislabs.net>
332 This program is free software: you can redistribute it and/or modify
333 it under the terms of the GNU General Public License as published by
334 the Free Software Foundation, either version 3 of the License, or
335 (at your option) any later version.
337 This program is distributed in the hope that it will be useful,
338 but WITHOUT ANY WARRANTY; without even the implied warranty of
339 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
340 GNU General Public License for more details.
342 You should have received a copy of the GNU General Public License
343 along with this program. If not, see <http://www.gnu.org/licenses/>.