1 # id3fs - a FUSE-based filesystem for browsing audio metadata
2 # Copyright (C) 2010 Ian Beckwith <ianb@erislabs.net>
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package ID3FS::AudioFile::Mp3;
28 my $class=ref($proto) || $proto;
33 $self->{mp3tag}=MP3::Tag->new($self->{path});
34 $self->{mp3info}=MP3::Info->new($self->{path});
43 my ($self, $func, $value)=@_;
44 return $self->choose($func) unless($value);
45 unless(exists($self->{mp3tag}->{ID3v1}))
47 $self->{mp3tag}->new_tag("ID3v1");
49 unless(exists($self->{mp3tag}->{ID3v2}))
51 $self->{mp3tag}->new_tag("ID3v2");
53 my $method=$func . "_set";
54 $self->{mp3tag}->$method($value, 1);
62 if(exists($self->{mp3tag}->{ID3v2}))
64 $thing=$self->{mp3tag}->{ID3v2}->$func();
66 if(exists($self->{mp3tag}->{ID3v1}) && (!defined($thing) || !length($thing)))
68 $thing=$self->{mp3tag}->{ID3v1}->$func();
73 sub year { return(shift->set("year", @_)); }
74 sub artist { return(shift->set("artist", @_)); }
75 sub album { return(shift->set("album", @_)); }
76 sub track { return(shift->set("title", @_)); }
77 sub tracknum { return(shift->set("track", @_)); }
78 sub comment { return(shift->set("comment", @_)); }
80 sub audiotype { return "mp3"; }
81 sub haspic { return undef; } # NEXTVERSION
83 # we only set v2 genre
86 my ($self, $value)=@_;
89 if(exists($self->{mp3tag}->{ID3v2}))
91 $self->{mp3tag}->{ID3v2}->remove_frame("TCON");
95 $self->{mp3tag}->new_tag("ID3v2");
97 $self->{mp3tag}->{ID3v2}->add_frame("TCON", $value);
100 return($self->{mp3tag}->{ID3v2}->genre());
108 $self->{mp3tag}->new_tag("ID3v1") unless(defined($self->{mp3tag}->{ID3v1}));
109 $self->{mp3tag}->{ID3v1}->genre($val);
113 $genre=$self->{ID3v1}->genre() if(defined($self->{ID3v1}));
120 return() unless(exists($self->{mp3tag}->{ID3v2}) && defined($self->{mp3tag}->{ID3v2}));
121 return($self->{mp3tag}->{ID3v2}->genre());
127 # MP3::Tag->get_tags shows cryptic debug info via print when it finds
128 # an unhandled id3v2 version, in addition to the warning, so use
129 # select to send prints to /dev/null
131 if(open(NULL,">/dev/null"))
133 $oldout=select(NULL);
135 eval { $self->{mp3tag}->get_tags; };
136 warn("$self->{path}: $@\n") if($@);
147 my $existing=$self->tags();
148 my @existing=split(/\s*,\s*/, $existing) if($existing);
149 my @merged=ID3FS::AudioFile::uniq(@tags, @existing);
150 my $genre=join(', ', @merged);
151 return($self->set("genre", $genre));
157 if(exists($self->{mp3tag}->{ID3v1}))
160 my $artist=$self->{mp3tag}->{ID3v1}->artist();
161 $del=0 if($artist && $artist =~ /\S+/);
162 my $album=$self->{mp3tag}->{ID3v1}->album();
163 $del=0 if($album && $album =~ /\S+/);
164 my $track=$self->{mp3tag}->{ID3v1}->title();
165 $del=0 if($track && $track =~ /\S+/);
166 my $tracknum=$self->{mp3tag}->{ID3v1}->track();
167 $del=0 if($tracknum && $tracknum !~ /^0+$/);
168 my $genre=$self->{mp3tag}->{ID3v1}->genre();
169 $del=0 if($genre && $genre =~ /\S+/);
170 my $comment=$self->{mp3tag}->{ID3v1}->comment();
171 $del=0 if($comment && $comment =~ /\S+/);
172 my $year=$self->{mp3tag}->{ID3v1}->year();
173 $del=0 if($year && $year =~ /\S+/ && $year !~ /^0+$/);
176 $self->{mp3tag}->{ID3v1}->remove_tag;
180 $self->{mp3tag}->{ID3v1}->write_tag;
183 if(exists($self->{mp3tag}->{ID3v2}))
185 my $frames=$self->{mp3tag}->{ID3v2}->get_frame_ids();
186 if($frames && scalar(keys(%$frames)))
188 $self->{mp3tag}->{ID3v2}->write_tag;
192 $self->{mp3tag}->{ID3v2}->remove_tag;
197 sub delete_artist { shift->delete("artist"); }
198 sub delete_album { shift->delete("album"); }
199 sub delete_track { shift->delete("song"); }
200 sub delete_tracknum { shift->delete("track"); }
201 sub delete_year { shift->delete("year"); }
202 sub delete_v1genre { shift->delete("v1genre"); }
203 sub delete_comment { shift->delete("comment"); }
204 sub delete_genre { shift->delete("genre"); }
208 my($self, $tags, $delvals)=@_;
209 my $current=$self->tags();
211 @current=split(/\s*,\s*/, $current) if($current);
212 my @tags=split(/\s*,\s*/, $tags);
217 delete($hash{$tag}) if(exists($hash{$tag}));
220 my $base=($tag =~ /(.*?)\//)[0];
221 $base=$tag unless($base);
222 for my $curtag (keys %hash)
224 delete($hash{$curtag}) if($curtag =~ /^$base\//);
228 my @tagsout=sort keys(%hash);
229 my $genre=join(', ', @tagsout);
232 return($self->set("genre", $genre));
236 return($self->delete_genre());
243 if(exists($self->{mp3tag}->{ID3v1}))
245 $self->{mp3tag}->{ID3v1}->remove_tag;
247 if(exists($self->{mp3tag}->{ID3v2}))
249 $self->{mp3tag}->{ID3v2}->remove_tag;
255 my($self, $thing)=@_;
257 if(exists($self->{mp3tag}->{ID3v1}) && $thing ne "genre")
260 $action="genre" if($action eq "v1genre");
261 if($action eq "track")
263 $self->{mp3tag}->{ID3v1}->track("00");
267 $self->{mp3tag}->{ID3v1}->$action(" ");
271 if(exists($self->{mp3tag}->{ID3v2}))
273 if($thing eq "artist")
275 $self->{mp3tag}->{ID3v2}->remove_frame("TPE1");
276 $self->{mp3tag}->{ID3v2}->remove_frame("TPE2");
278 elsif($thing eq "album")
280 $self->{mp3tag}->{ID3v2}->remove_frame("TALB");
282 elsif($thing eq "song")
284 $self->{mp3tag}->{ID3v2}->remove_frame("TIT2");
286 elsif($thing eq "track")
288 $self->{mp3tag}->{ID3v2}->remove_frame("TRCK");
290 elsif($thing eq "year")
292 $self->{mp3tag}->{ID3v2}->remove_frame("TYER");
293 $self->{mp3tag}->{ID3v2}->remove_frame("TDRC");
295 elsif($thing eq "comment")
297 $self->{mp3tag}->{ID3v2}->remove_frame("COMM");
299 elsif($thing eq "genre")
301 $self->{mp3tag}->{ID3v2}->remove_frame("TCON");
310 return undef unless($self->{mp3info});
311 return( ($self->{mp3info}->stereo()) ? 2 : 1 );
317 return undef unless($self->{mp3info});
318 return( int($self->{mp3info}->bitrate()) );
324 return undef unless($self->{mp3info});
325 return(int($self->{mp3info}->frequency() * 1000));