#!/usr/bin/perl -w # # id3fs - a FUSE-based filesystem for browsing audio metadata # Copyright (C) 2010 Ian Beckwith # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use lib '/home/ianb/projects/id3fs/id3fs/lib'; # FIXME: remove use strict; use Getopt::Long qw(Configure); use ID3FS::AudioFile; use vars qw($me); $me=($0=~/(?:.*\/)?(.*)/)[0]; my $verbose=0; my $help=0; my ($artist, $album, $track, $tracknum, $year, $v1genre, $comment, $delete_artist, $delete_album, $delete_track, $delete_tracknum, $delete_year, $delete_v1genre, $delete_comment, $delete_all, $delete_genre, $genre, $add_tags, $delete_tags, $overwrite_tagvals, $delete_tagvals); Configure(qw(bundling no_ignore_case)); my $optret=GetOptions( "verbose|v" => \$verbose, "help|h" => \$help, "artist|a=s" => \$artist, "album|l=s" => \$album, "song|s=s" => \$track, "tracknum|n=s" => \$tracknum, "year|y=i" => \$year, "v1genre|1=s" => \$v1genre, "comment|c=s" => \$comment, "delete-artist|A" => \$delete_artist, "delete-album|L" => \$delete_album, "delete-song|S" => \$delete_track, "delete-tracknum|N" => \$delete_tracknum, "delete-year|Y" => \$delete_year, "delete-v1genre|0" => \$delete_v1genre, "delete-comment|C" => \$delete_comment, "delete|delete-all|D" => \$delete_all, "delete-genre|delete-all-tags|G" => \$delete_genre, "genre|g|replace-all-tags|R=s" => \$genre, "add-tags|tags|t=s" => \$add_tags, "overwrite-tagvals|tagvals|o=s" => \$overwrite_tagvals, "delete-tags|T=s" => \$delete_tags, "delete-tags-with-values|O=s" => \$delete_tagvals, ); usage() if(!@ARGV || !$optret || $help); while(my $filename=shift @ARGV) { unless(-f $filename) { warn("$me: $filename: not found\n"); next; } my $file=ID3FS::AudioFile->new($filename); next unless($file); my $changes=0; $changes = do_deletes($file); $changes += do_adds($file); if($changes) { do_write($file); } else { do_display($file); } } sub do_deletes { my($file)=@_; if($delete_all) { $file->delete_all(); # we don't want to save the tag if we've deleted it return 0; } $file->delete_artist() if($delete_artist); $file->delete_album() if($delete_album); $file->delete_track() if($delete_track); $file->delete_tracknum() if($delete_tracknum); $file->delete_year() if($delete_year); $file->delete_v1genre() if($delete_v1genre); $file->delete_comment() if($delete_comment); $file->delete_genre() if($delete_genre || $genre); $file->delete_tags($delete_tags, 0) if($delete_tags); $file->delete_tags($delete_tagvals, 1) if($delete_tagvals); $file->delete_tags($overwrite_tagvals, 1) if($overwrite_tagvals); my $donesomething=($delete_artist || $delete_album || $delete_track || $delete_tracknum || $delete_year || $delete_v1genre || $delete_comment || $delete_genre || $delete_tags || $delete_tagvals || defined($genre) || $overwrite_tagvals); return($donesomething ? 1 : 0); } sub do_adds { my($file)=@_; $file->artist($artist) if($artist); $file->album($album) if($album); $file->track($track) if($track); $file->tracknum($tracknum) if($tracknum); $file->year($year) if($year); $file->v1genre($v1genre) if($v1genre); $file->comment($comment) if($comment); $file->add_tags($add_tags) if($add_tags); $file->add_tags($genre) if($genre); $file->add_tags($overwrite_tagvals) if($overwrite_tagvals); my $donesomething=(defined($artist) || defined($album) || defined($track) || defined($tracknum) || defined($year) || defined($v1genre) || defined($comment) || defined($genre) || defined($add_tags) || defined($overwrite_tagvals)); return( $donesomething ? 1 : 0 ); } sub do_write { my($file)=@_; $file->write(); } sub do_display { my($file)=@_; my $artist=$file->artist(); my $album=$file->album(); my $track=$file->track(); my $tracknum=$file->tracknum(); my $year=$file->year(); my $comment=$file->comment(); my $v1genre=$file->v1genre(); my @tags=$file->tags(); @tags = map { (ref($_) eq "ARRAY") ? join('/', grep {defined;} @{$_}) : $_; } @tags; if($verbose) { print $file->path(), ":\n"; print " tracknum: $tracknum\n" if($tracknum); print " artist: $artist\n" if($artist); print " album: $album\n" if($album); print " song: $track\n" if($track); print " year: $year\n" if($year); print " v1genre: $v1genre\n" if($v1genre); print " comment: $comment\n" if($comment); } else { my @fields=($file->path(), $tracknum, $artist, $album, $track, $year, $v1genre, $comment); @fields=map { defined($_) ? $_ : ""; } @fields; print join(':', @fields), "\n"; } if(@tags) { if($verbose) { print " tags: "; } # else { print $file->path() . ":tags:"; } else { print "tags:"; } print join(", ", @tags), "\n"; } } sub usage { die("Usage: $me [-vhALSNY0CDG] [-a ARTIST] [-l ALBUM] [-s SONG] [-n TRACKNUM] FILES...\n". " $me [-y YEAR] [-g GENRE] [-1 V1GENRE] [-c COMMENT] [--] FILES...\n". " $me [-t TAGS,TO,ADD] [-T TAGS,TO,DELETE] FILES...\n". " $me [-r TAGS,TO,DELETE TAGS,TO,ADD] [-R TAGS,TO,OVERWRITE,WITH] FILES...\n". "With no options, displays current info in tag\n". "Options:\n". " -a|--artist=ARTIST Set artist\n". " -l|--album=ALBUM Set album\n". " -s|--song=SONG Set song\n". " -n|--tracknum=NUM Set tracknum\n". " -y|--year=NUM Set year\n". " -1|--v1genre=GENRE Set ID3v1 genre\n". " -c|--comment=COMMENT Set comment\n". " -A|--delete-artist Delete artist\n". " -L|--delete-album Delete album\n". " -S|--delete-song Delete song\n". " -N|--delete-tracknum Delete tracknum\n". " -Y|--delete-year Delete year\n". " -0|--delete-v1genre Delete ID3v1 genre\n". " -C|--delete-comment Delete comment\n". " -D|--delete|delete-all Delete entire ID3 tag\n". " -G|--delete-genre|--delete-all-tags Delete all tags stored in genre\n". " -g|-R|replace-all-tags|--genre=GENRE Replace all tags in genre tag\n". " -t|--add-tags|tags=TAG1,TAG2 Add tags to genre tag, merging with existing ones\n". " -T|--delete-tags=TAG1,TAG2 Delete tags from genre\n". " -r|--replace-tags TAGS1 TAGS2 Replace TAGS1 in genre with TAGS2\n". " -v|--verbose Verbose display\n". " -h|--help This help\n". " -- End of options\n"); } __END__ =head1 NAME id3fs-tag - Add files to id3fs index =head1 SYNOPSIS B [B<-lvh>] S<[B<-d >I]> S<[B<-f >I]> S<[B<-e >I]> [B<-->] [I...] =head1 DESCRIPTION Extracts id3 tags from mp3 files (and comment tags from ogg and flac files) and adds them to a sqlite database, ready for mounting with L. =head1 OPTIONS =over 4 =item B<-l> | B<--list> List tags in use in specified database. =item SI> | SI> Specify base directory of source files. All files will be indexed relative to this point. If not specified, defaults to the first non-option argument on the command line. Note that to avoid ambiguities, if more than one directory is specified on the command line, the base directory must be specified explicitly. All files indexed must be under the base directory. =item SI> | SI> Database file to use. If not specified, defaults to a hidden file called B<".id3fs"> under the base directory. =item SI> | SI> File extensions to consider when indexing. Defaults to B<.mp3>, B<.ogg> and B<.flac>. =item B<-v> Enable verbose operation. =item B<-h> Show a short help message. =item B<--> End of options. =back =head1 EXAMPLES Index all files in the current directory: id3fs-tag . Index current directory, printing each subdirectory as it recurses into it: id3fs-tag -v . Just index some sub-directories: id3fs-tag -d . dir1 dir2 Store the database in a custom location: id3fs-tag -f ~/.id3fs/index.sqlite . Only index .mp3 and .flac files: id3fs-tag -e mp3,flac . =head1 BUGS Please report any found to ianb@erislabs.net =head1 SEE ALSO L, L, L, L =head1 AUTHOR Ian Beckwith Many thanks to Aubrey Stark-Toller for help wrangling SQL. =head1 AVAILABILITY The latest version can be found at: L =head1 COPYRIGHT Copyright (C) 2010 Ian Beckwith This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . =cut