id3fs-tag: fix removing all tags individually
[id3fs.git] / lib / ID3FS / AudioFile / Mp3.pm
1 # id3fs - a FUSE-based filesystem for browsing audio metadata
2 # Copyright (C) 2010  Ian Beckwith <ianb@erislabs.net>
3 #
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.
8 #
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.
13 #
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/>.
16
17 package ID3FS::AudioFile::Mp3;
18
19 use strict;
20 use warnings;
21 use MP3::Tag;
22 use MP3::Info;
23
24 sub new
25 {
26     my $proto=shift;
27     my $class=ref($proto) || $proto;
28     my $self={};
29     bless($self,$class);
30
31     $self->{path}=shift;
32     $self->{mp3tag}=MP3::Tag->new($self->{path});
33     $self->{mp3info}=MP3::Info->new($self->{path});
34     $self->get_tags();
35     $self->{tags}={};
36
37     return $self;
38 }
39
40 sub set
41 {
42     my ($self, $func, $value)=@_;
43     return $self->choose($func) unless($value);
44     unless(exists($self->{mp3tag}->{ID3v1}))
45     {
46         $self->{mp3tag}->new_tag("ID3v1");
47     }
48     unless(exists($self->{mp3tag}->{ID3v2}))
49     {
50         $self->{mp3tag}->new_tag("ID3v2");
51     }
52     my $method=$func . "_set";
53     $self->{mp3tag}->$method($value, 1);
54     return $value;
55 }
56
57 sub choose
58 {
59     my($self, $func)=@_;
60     my $thing=undef;
61     if(exists($self->{mp3tag}->{ID3v2}))
62     {
63         $thing=$self->{mp3tag}->{ID3v2}->$func();
64     }
65     if(exists($self->{mp3tag}->{ID3v1}) && (!defined($thing) || !length($thing)))
66     {
67         $thing=$self->{mp3tag}->{ID3v1}->$func();
68     }
69     return $thing;
70 }
71
72 sub year      { return(shift->set("year",     @_)); }
73 sub artist    { return(shift->set("artist",   @_)); }
74 sub album     { return(shift->set("album",    @_)); }
75 sub track     { return(shift->set("title",    @_)); }
76 sub tracknum  { return(shift->set("track",    @_)); }
77 sub comment   { return(shift->set("comment",  @_)); }
78
79 sub audiotype { return "mp3";         }
80 sub haspic    { return undef;         } # NEXTVERSION
81
82 sub v1genre
83 {
84     my($self, $val)=@_;
85     if($val)
86     {
87         $self->{mp3tag}->new_tag("ID3v1") unless(defined($self->{mp3tag}->{ID3v1}));
88         $self->{mp3tag}->{ID3v1}->genre($val);
89         return $val;
90     }
91     my $genre=undef;
92     $genre=$self->{ID3v1}->genre() if(defined($self->{ID3v1}));
93     return $genre;
94 }
95
96 sub tags
97 {
98     my $self=shift;
99     return() unless(exists($self->{mp3tag}->{ID3v2}) && defined($self->{mp3tag}->{ID3v2}));
100     return($self->{mp3tag}->{ID3v2}->genre());
101 }
102
103 sub get_tags
104 {
105     my ($self)=@_;
106     # MP3::Tag->get_tags shows cryptic debug info via print when it finds
107     # an unhandled id3v2 version, in addition to the warning, so use
108     # select to send prints to /dev/null
109     my $oldout=undef;
110     if(open(NULL,">/dev/null"))
111     {
112         $oldout=select(NULL);
113     }
114     eval { $self->{mp3tag}->get_tags; };
115     warn("$self->{path}: $@\n") if($@);
116     if(defined($oldout))
117     {
118         select($oldout);
119         close(NULL);
120     }
121 }
122
123 sub add_tags
124 {
125     my($self, @tags)=@_;
126     my $existing=$self->tags();
127     my @existing=split(/\s*,\s*/, $existing) if($existing);
128     my @merged=$self->uniq(@tags, @existing);
129     my $genre=join(', ', @merged);
130     return($self->set("genre", $genre));
131 }
132
133 sub write
134 {
135     my $self=shift;
136     if(exists($self->{mp3tag}->{ID3v1}))
137     {
138         my $del=1;
139         my $artist=$self->{mp3tag}->{ID3v1}->artist();
140         $del=0 if($artist && $artist =~ /\S+/);
141         my $album=$self->{mp3tag}->{ID3v1}->album();
142         $del=0 if($album && $album =~ /\S+/);
143         my $track=$self->{mp3tag}->{ID3v1}->title();
144         $del=0 if($track && $track =~ /\S+/);
145         my $tracknum=$self->{mp3tag}->{ID3v1}->track();
146         $del=0 if($tracknum && $tracknum !~ /^0+$/);
147         my $genre=$self->{mp3tag}->{ID3v1}->genre();
148         $del=0 if($genre && $genre =~ /\S+/);
149         my $comment=$self->{mp3tag}->{ID3v1}->comment();
150         $del=0 if($comment && $comment =~ /\S+/);
151         my $year=$self->{mp3tag}->{ID3v1}->year();
152         $del=0 if($year && $year =~ /\S+/ && $year !~ /^0+$/);
153         if($del)
154         {
155             $self->{mp3tag}->{ID3v1}->remove_tag;
156         }
157         else
158         {
159             $self->{mp3tag}->{ID3v1}->remove_tag;
160         }
161     }
162     if(exists($self->{mp3tag}->{ID3v2}))
163     {
164         my $frames=$self->{mp3tag}->{ID3v2}->get_frame_ids();
165         if($frames && scalar(keys(%$frames)))
166         {
167             $self->{mp3tag}->{ID3v2}->write_tag;
168         }
169         else
170         {
171             $self->{mp3tag}->{ID3v2}->remove_tag;
172         }
173     }
174 }
175
176 sub delete_artist   { shift->delete("artist");  }
177 sub delete_album    { shift->delete("album");   }
178 sub delete_track    { shift->delete("song");    }
179 sub delete_tracknum { shift->delete("track");   }
180 sub delete_year     { shift->delete("year");    }
181 sub delete_v1genre  { shift->delete("v1genre"); }
182 sub delete_comment  { shift->delete("comment"); }
183 sub delete_genre    { shift->delete("genre");   }
184
185 sub delete_tags
186 {
187     my($self, @tags)=@_;
188     my $current=$self->tags();
189     my @current=split(/\s*,\s*/, $current);
190     my %hash=();
191     @hash{@current}=();
192     for my $tag (@tags)
193     {
194         delete($hash{$tag}) if(exists($hash{$tag}));
195     }
196     my @tagsout=sort keys(%hash);
197     my $genre=join(', ', @tagsout);
198     if(length($genre))
199     {
200         return($self->set("genre", $genre));
201     }
202     else
203     {
204         return($self->delete_genre());
205     }
206 }
207
208 sub delete_all
209 {
210     my($self)=@_;
211     if(exists($self->{mp3tag}->{ID3v1}))
212     {
213         $self->{mp3tag}->{ID3v1}->remove_tag;
214     }
215     if(exists($self->{mp3tag}->{ID3v2}))
216     {
217         $self->{mp3tag}->{ID3v2}->remove_tag;
218     }
219 }
220
221 sub delete
222 {
223     my($self, $thing)=@_;
224
225     if(exists($self->{mp3tag}->{ID3v1}) && $thing ne "genre")
226     {
227         my $action=$thing;
228         $action="genre" if($action eq "v1genre");
229         if($action eq "track")
230         {
231             $self->{mp3tag}->{ID3v1}->track("00");
232         }
233         else
234         {
235             $self->{mp3tag}->{ID3v1}->$action(" ");
236         }
237     }
238
239     if(exists($self->{mp3tag}->{ID3v2}))
240     {
241         if($thing eq "artist")
242         {
243             $self->{mp3tag}->{ID3v2}->remove_frame("TPE1");
244             $self->{mp3tag}->{ID3v2}->remove_frame("TPE2");
245         }
246         elsif($thing eq "album")
247         {
248             $self->{mp3tag}->{ID3v2}->remove_frame("TALB");
249         }
250         elsif($thing eq "song")
251         {
252             $self->{mp3tag}->{ID3v2}->remove_frame("TIT2");
253         }
254         elsif($thing eq "track")
255         {
256             $self->{mp3tag}->{ID3v2}->remove_frame("TRCK");
257         }
258         elsif($thing eq "year")
259         {
260             $self->{mp3tag}->{ID3v2}->remove_frame("TYER");
261             $self->{mp3tag}->{ID3v2}->remove_frame("TDRC");
262         }
263         elsif($thing eq "comment")
264         {
265             $self->{mp3tag}->{ID3v2}->remove_frame("COMM");
266         }
267         elsif($thing eq "genre")
268         {
269             $self->{mp3tag}->{ID3v2}->remove_frame("TCON");
270         }
271     }
272 }
273
274 sub uniq
275 {
276     my ($self, @things)=@_;
277     my %hash=();
278     @hash{@things}=();
279     return(sort keys(%hash));
280 }
281
282
283 sub channels
284 {
285     my($self)=@_;
286     return undef unless($self->{mp3info});
287     return( ($self->{mp3info}->stereo()) ? 2 : 1 );
288 }
289
290 sub bitrate
291 {
292     my($self)=@_;
293     return undef unless($self->{mp3info});
294     return( int($self->{mp3info}->bitrate()) );
295 }
296
297 sub samplerate
298 {
299     my($self)=@_;
300     return undef unless($self->{mp3info});
301     return(int($self->{mp3info}->frequency() * 1000));
302 }
303
304
305 1;