-package ID3FS::File::Mp3;
+# id3fs - a FUSE-based filesystem for browsing audio metadata
+# Copyright (C) 2010 Ian Beckwith <ianb@erislabs.net>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+package ID3FS::AudioFile::Mp3;
use strict;
use warnings;
use MP3::Tag;
+use MP3::Info;
sub new
{
bless($self,$class);
$self->{path}=shift;
- $self->{mp3}=MP3::Tag->new($self->{path});
+ $self->{mp3tag}=MP3::Tag->new($self->{path});
+ $self->{mp3info}=MP3::Info->new($self->{path});
$self->get_tags();
- $self->{v1}=undef;
- $self->{v1}=$self->{mp3}->{ID3v1} if(exists($self->{mp3}->{ID3v1}));
- $self->{v2}=undef;
- $self->{v2}=$self->{mp3}->{ID3v2} if(exists($self->{mp3}->{ID3v2}));
-
$self->{tags}={};
return $self;
}
-sub choose
+sub set
{
- my ($self, $func, $verbose)=@_;
- my $thing=undef;
- if(defined($self->{v2}))
+ my ($self, $func, $value)=@_;
+ return $self->choose($func) unless($value);
+ unless(exists($self->{mp3tag}->{ID3v1}))
{
- $thing=$self->{v2}->$func();
+ $self->{mp3tag}->new_tag("ID3v1");
}
- if(defined($self->{v1}) && (!defined($thing) || !length($thing)))
+ unless(exists($self->{mp3tag}->{ID3v2}))
+ {
+ $self->{mp3tag}->new_tag("ID3v2");
+ }
+ my $method=$func . "_set";
+ $self->{mp3tag}->$method($value, 1);
+ return $value;
+}
+
+sub choose
+{
+ my($self, $func)=@_;
+ my $thing=undef;
+ if(exists($self->{mp3tag}->{ID3v2}))
{
- $thing=$self->{v1}->$func();
+ $thing=$self->{mp3tag}->{ID3v2}->$func();
}
- if(!defined($thing) || !length($thing))
+ if(exists($self->{mp3tag}->{ID3v1}) && (!defined($thing) || !length($thing)))
{
- warn("$self->{path}: no $func defined in ID3 tags\n") if($verbose);
- return undef;
+ $thing=$self->{mp3tag}->{ID3v1}->$func();
}
- $thing=~s/\//-/g; # drop slashes
return $thing;
}
-sub artist { shift->choose("artist", 1); }
-sub album { shift->choose("album", 1); }
-# We don't care if year is not set
-sub year { shift->choose("year", 0); }
-sub audiotype { return "mp3"; }
-sub haspic { return undef; } # FIXME
+sub year { return(shift->set("year", @_)); }
+sub artist { return(shift->set("artist", @_)); }
+sub album { return(shift->set("album", @_)); }
+sub track { return(shift->set("title", @_)); }
+sub tracknum { return(shift->set("track", @_)); }
+sub comment { return(shift->set("comment", @_)); }
+
+sub audiotype { return "mp3"; }
+sub haspic { return undef; } # NEXTVERSION
+
sub v1genre
{
- my($self)=@_;
- my $genre=undef;
- if(defined($self->{v1}))
+ my($self, $val)=@_;
+ if($val)
{
- $genre=$self->{v1}->genre();
- $genre =~ s/\//-/g if(defined($genre));
+ $self->{mp3tag}->new_tag("ID3v1") unless(defined($self->{mp3tag}->{ID3v1}));
+ $self->{mp3tag}->{ID3v1}->genre($val);
+ return $val;
}
+ my $genre=undef;
+ $genre=$self->{ID3v1}->genre() if(defined($self->{ID3v1}));
return $genre;
}
sub tags
{
my $self=shift;
- return({}) unless(exists($self->{mp3}->{ID3v2}) && defined($self->{mp3}->{ID3v2}));
- my $genre=$self->{mp3}->{ID3v2}->genre();
- return({}) unless(defined($genre) && length($genre));
- my @tags=split(/\s*,\s*/, $genre);
- for my $tag (@tags)
- {
- if($tag=~/([^\/]+)\/(.*)/)
- {
- my $tagname=$1;
- my $tagval=$2;
- $tagval=~s/\//-/g;
- $self->{tags}->{$tagname}=$tagval;
- }
- else
- {
- $self->{tags}->{$tag}=undef;
- }
- }
- return $self->{tags};
+ return() unless(exists($self->{mp3tag}->{ID3v2}) && defined($self->{mp3tag}->{ID3v2}));
+ return($self->{mp3tag}->{ID3v2}->genre());
}
sub get_tags
{
$oldout=select(NULL);
}
- eval { $self->{mp3}->get_tags; };
+ eval { $self->{mp3tag}->get_tags; };
warn("$self->{path}: $@\n") if($@);
if(defined($oldout))
{
}
}
-1;
+sub add_tags
+{
+ my($self, @tags)=@_;
+ my $existing=$self->tags();
+ my @existing=split(/\s*,\s*/, $existing) if($existing);
+ my @merged=$self->uniq(@tags, @existing);
+ my $genre=join(', ', @merged);
+ return($self->set("genre", $genre));
+}
+
+sub write
+{
+ my $self=shift;
+ if(exists($self->{mp3tag}->{ID3v1}))
+ {
+ my $del=1;
+ my $artist=$self->{mp3tag}->{ID3v1}->artist();
+ $del=0 if($artist && $artist =~ /\S+/);
+ my $album=$self->{mp3tag}->{ID3v1}->album();
+ $del=0 if($album && $album =~ /\S+/);
+ my $track=$self->{mp3tag}->{ID3v1}->title();
+ $del=0 if($track && $track =~ /\S+/);
+ my $tracknum=$self->{mp3tag}->{ID3v1}->track();
+ $del=0 if($tracknum && $tracknum !~ /^0+$/);
+ my $genre=$self->{mp3tag}->{ID3v1}->genre();
+ $del=0 if($genre && $genre =~ /\S+/);
+ my $comment=$self->{mp3tag}->{ID3v1}->comment();
+ $del=0 if($comment && $comment =~ /\S+/);
+ my $year=$self->{mp3tag}->{ID3v1}->year();
+ $del=0 if($year && $year =~ /\S+/ && $year !~ /^0+$/);
+ if($del)
+ {
+ $self->{mp3tag}->{ID3v1}->remove_tag;
+ }
+ else
+ {
+ $self->{mp3tag}->{ID3v1}->remove_tag;
+ }
+ }
+ if(exists($self->{mp3tag}->{ID3v2}))
+ {
+ my $frames=$self->{mp3tag}->{ID3v2}->get_frame_ids();
+ if($frames && scalar(keys(%$frames)))
+ {
+ $self->{mp3tag}->{ID3v2}->write_tag;
+ }
+ else
+ {
+ $self->{mp3tag}->{ID3v2}->remove_tag;
+ }
+ }
+}
+
+sub delete_artist { shift->delete("artist"); }
+sub delete_album { shift->delete("album"); }
+sub delete_track { shift->delete("song"); }
+sub delete_tracknum { shift->delete("track"); }
+sub delete_year { shift->delete("year"); }
+sub delete_v1genre { shift->delete("v1genre"); }
+sub delete_comment { shift->delete("comment"); }
+sub delete_genre { shift->delete("genre"); }
+
+sub delete_tags
+{
+ my($self, @tags)=@_;
+ my $current=$self->tags();
+ my @current=split(/\s*,\s*/, $current);
+ my %hash=();
+ @hash{@current}=();
+ for my $tag (@tags)
+ {
+ delete($hash{$tag}) if(exists($hash{$tag}));
+ }
+ my @tagsout=sort keys(%hash);
+ my $genre=join(', ', @tagsout);
+ if(length($genre))
+ {
+ return($self->set("genre", $genre));
+ }
+ else
+ {
+ return($self->delete_genre());
+ }
+}
+
+sub delete_all
+{
+ my($self)=@_;
+ if(exists($self->{mp3tag}->{ID3v1}))
+ {
+ $self->{mp3tag}->{ID3v1}->remove_tag;
+ }
+ if(exists($self->{mp3tag}->{ID3v2}))
+ {
+ $self->{mp3tag}->{ID3v2}->remove_tag;
+ }
+}
+
+sub delete
+{
+ my($self, $thing)=@_;
+
+ if(exists($self->{mp3tag}->{ID3v1}) && $thing ne "genre")
+ {
+ my $action=$thing;
+ $action="genre" if($action eq "v1genre");
+ if($action eq "track")
+ {
+ $self->{mp3tag}->{ID3v1}->track("00");
+ }
+ else
+ {
+ $self->{mp3tag}->{ID3v1}->$action(" ");
+ }
+ }
+ if(exists($self->{mp3tag}->{ID3v2}))
+ {
+ if($thing eq "artist")
+ {
+ $self->{mp3tag}->{ID3v2}->remove_frame("TPE1");
+ $self->{mp3tag}->{ID3v2}->remove_frame("TPE2");
+ }
+ elsif($thing eq "album")
+ {
+ $self->{mp3tag}->{ID3v2}->remove_frame("TALB");
+ }
+ elsif($thing eq "song")
+ {
+ $self->{mp3tag}->{ID3v2}->remove_frame("TIT2");
+ }
+ elsif($thing eq "track")
+ {
+ $self->{mp3tag}->{ID3v2}->remove_frame("TRCK");
+ }
+ elsif($thing eq "year")
+ {
+ $self->{mp3tag}->{ID3v2}->remove_frame("TYER");
+ $self->{mp3tag}->{ID3v2}->remove_frame("TDRC");
+ }
+ elsif($thing eq "comment")
+ {
+ $self->{mp3tag}->{ID3v2}->remove_frame("COMM");
+ }
+ elsif($thing eq "genre")
+ {
+ $self->{mp3tag}->{ID3v2}->remove_frame("TCON");
+ }
+ }
+}
+
+sub uniq
+{
+ my ($self, @things)=@_;
+ my %hash=();
+ @hash{@things}=();
+ return(sort keys(%hash));
+}
+
+
+sub channels
+{
+ my($self)=@_;
+ return undef unless($self->{mp3info});
+ return( ($self->{mp3info}->stereo()) ? 2 : 1 );
+}
+
+sub bitrate
+{
+ my($self)=@_;
+ return undef unless($self->{mp3info});
+ return( int($self->{mp3info}->bitrate()) );
+}
+
+sub samplerate
+{
+ my($self)=@_;
+ return undef unless($self->{mp3info});
+ return(int($self->{mp3info}->frequency() * 1000));
+}
+
+
+1;