d51e0b0d45a11e9719980f80502438cf92e5202b
[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
23 sub new
24 {
25     my $proto=shift;
26     my $class=ref($proto) || $proto;
27     my $self={};
28     bless($self,$class);
29
30     $self->{path}=shift;
31     $self->{mp3}=MP3::Tag->new($self->{path});
32     $self->get_tags();
33     $self->{tags}={};
34
35     return $self;
36 }
37
38 sub set
39 {
40     my ($self, $func, $value)=@_;
41     return $self->choose($func) unless($value);
42     unless(exists($self->{mp3}->{ID3v1}))
43     {
44         $self->{mp3}->new_tag("ID3v1");
45     }
46     unless(exists($self->{mp3}->{ID3v2}))
47     {
48         $self->{mp3}->new_tag("ID3v2");
49     }
50     my $method=$func . "_set";
51     $self->{mp3}->$method($value, 1);
52     return $value;
53 }
54
55 sub choose
56 {
57     my($self, $func)=@_;
58     my $thing=undef;
59     if(exists($self->{mp3}->{ID3v2}))
60     {
61         $thing=$self->{mp3}->{ID3v2}->$func();
62     }
63     if(exists($self->{mp3}->{ID3v1}) && (!defined($thing) || !length($thing)))
64     {
65         $thing=$self->{mp3}->{ID3v1}->$func();
66     }
67     return $thing;
68 }
69
70 sub year      { return(shift->set("year",     @_)); }
71 sub artist    { return(shift->set("artist",   @_)); }
72 sub album     { return(shift->set("album",    @_)); }
73 sub track     { return(shift->set("title",    @_)); }
74 sub tracknum  { return(shift->set("track",    @_)); }
75 sub comment   { return(shift->set("comment",  @_)); }
76
77 sub audiotype { return "mp3";         }
78 sub haspic    { return undef;         } # NEXTVERSION
79
80 sub v1genre
81 {
82     my($self, $val)=@_;
83     if($val)
84     {
85         $self->{mp3}->new_tag("ID3v1") unless(defined($self->{mp3}->{ID3v1}));
86         $self->{mp3}->{ID3v1}->genre($val);
87         return $val;
88     }
89     my $genre=undef;
90     $genre=$self->{ID3v1}->genre() if(defined($self->{ID3v1}));
91     return $genre;
92 }
93
94 sub tags
95 {
96     my $self=shift;
97     return() unless(exists($self->{mp3}->{ID3v2}) && defined($self->{mp3}->{ID3v2}));
98     return($self->{mp3}->{ID3v2}->genre());
99 }
100
101 sub get_tags
102 {
103     my ($self)=@_;
104     # MP3::Tag->get_tags shows cryptic debug info via print when it finds
105     # an unhandled id3v2 version, in addition to the warning, so use
106     # select to send prints to /dev/null
107     my $oldout=undef;
108     if(open(NULL,">/dev/null"))
109     {
110         $oldout=select(NULL);
111     }
112     eval { $self->{mp3}->get_tags; };
113     warn("$self->{path}: $@\n") if($@);
114     if(defined($oldout))
115     {
116         select($oldout);
117         close(NULL);
118     }
119 }
120
121 sub add_tags
122 {
123     my($self, @tags)=@_;
124     my $existing=$self->tags();
125     my @existing=split(/\s*,\s*/, $existing) if($existing);
126     my @merged=$self->uniq(@tags, @existing);
127     my $genre=join(', ', @merged);
128     return($self->set("genre", $genre));
129 }
130
131
132 sub write
133 {
134     my $self=shift;
135     $self->{mp3}->update_tags();
136 }
137
138 sub delete_artist   { shift->delete("artist");  }
139 sub delete_album    { shift->delete("album");   }
140 sub delete_track    { shift->delete("song");    }
141 sub delete_tracknum { shift->delete("track");   }
142 sub delete_year     { shift->delete("year");    }
143 sub delete_v1genre  { shift->delete("v1genre"); }
144 sub delete_comment  { shift->delete("comment"); }
145 sub delete_genre    { shift->delete("genre");   }
146
147 sub delete_tags
148 {
149     my($self, @tags)=@_;
150     my $current=$self->tags();
151     my @current=split(/\s*,\s*/, $current);
152     my %hash=();
153     @hash{@current}=();
154     for my $tag (@tags)
155     {
156         delete($hash{$tag}) if(exists($hash{$tag}));
157     }
158     my @tagsout=sort keys(%hash);
159     my $genre=join(', ', @tagsout);
160     if(length($genre))
161     {
162         return($self->set("genre", $genre));
163     }
164     else
165     {
166         return($self->delete_genre());
167     }
168 }
169
170 sub delete_all
171 {
172     my($self)=@_;
173     if(exists($self->{mp3}->{ID3v1}))
174     {
175         $self->{mp3}->{ID3v1}->remove_tag;
176     }
177     if(exists($self->{mp3}->{ID3v2}))
178     {
179         $self->{mp3}->{ID3v2}->remove_tag;
180     }
181 }
182
183 sub delete
184 {
185     my($self, $thing)=@_;
186
187     if(exists($self->{mp3}->{ID3v1}) && $thing ne "genre")
188     {
189         my $action=$thing;
190         $action="genre" if($action eq "v1genre");
191         if($action eq "track")
192         {
193             $self->{mp3}->{ID3v1}->track("00");
194         }
195         else
196         {
197             $self->{mp3}->{ID3v1}->$action(" ");
198         }
199     }
200
201     if(exists($self->{mp3}->{ID3v2}))
202     {
203         print "2: remove: $thing\n";
204         if($thing eq "artist")
205         {
206             $self->{mp3}->{ID3v2}->remove_frame("TPE1");
207             $self->{mp3}->{ID3v2}->remove_frame("TPE2");
208         }
209         elsif($thing eq "album")
210         {
211             $self->{mp3}->{ID3v2}->remove_frame("TALB");
212         }
213         elsif($thing eq "song")
214         {
215             $self->{mp3}->{ID3v2}->remove_frame("TIT2");
216         }
217         elsif($thing eq "track")
218         {
219             $self->{mp3}->{ID3v2}->remove_frame("TRCK");
220         }
221         elsif($thing eq "year")
222         {
223             $self->{mp3}->{ID3v2}->remove_frame("TYER");
224             $self->{mp3}->{ID3v2}->remove_frame("TDRC");
225         }
226         elsif($thing eq "comment")
227         {
228             $self->{mp3}->{ID3v2}->remove_frame("COMM");
229         }
230         elsif($thing eq "genre")
231         {
232             $self->{mp3}->{ID3v2}->remove_frame("TCON");
233         }
234     }
235 }
236
237 sub uniq
238 {
239     my ($self, @things)=@_;
240     my %hash=();
241     @hash{@things}=();
242     return(sort keys(%hash));
243 }
244
245
246 1;