423737cc9f921e7befabb02cf05a17a4da3ebd1b
[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         $self->{mp3tag}->{ID3v1}->write_tag;
139     }
140     if(exists($self->{mp3tag}->{ID3v2}))
141     {
142         $self->{mp3tag}->{ID3v2}->write_tag;
143     }
144 }
145
146 sub delete_artist   { shift->delete("artist");  }
147 sub delete_album    { shift->delete("album");   }
148 sub delete_track    { shift->delete("song");    }
149 sub delete_tracknum { shift->delete("track");   }
150 sub delete_year     { shift->delete("year");    }
151 sub delete_v1genre  { shift->delete("v1genre"); }
152 sub delete_comment  { shift->delete("comment"); }
153 sub delete_genre    { shift->delete("genre");   }
154
155 sub delete_tags
156 {
157     my($self, @tags)=@_;
158     my $current=$self->tags();
159     my @current=split(/\s*,\s*/, $current);
160     my %hash=();
161     @hash{@current}=();
162     for my $tag (@tags)
163     {
164         delete($hash{$tag}) if(exists($hash{$tag}));
165     }
166     my @tagsout=sort keys(%hash);
167     my $genre=join(', ', @tagsout);
168     if(length($genre))
169     {
170         return($self->set("genre", $genre));
171     }
172     else
173     {
174         return($self->delete_genre());
175     }
176 }
177
178 sub delete_all
179 {
180     my($self)=@_;
181     if(exists($self->{mp3tag}->{ID3v1}))
182     {
183         $self->{mp3tag}->{ID3v1}->remove_tag;
184     }
185     if(exists($self->{mp3tag}->{ID3v2}))
186     {
187         $self->{mp3tag}->{ID3v2}->remove_tag;
188     }
189 }
190
191 sub delete
192 {
193     my($self, $thing)=@_;
194
195     if(exists($self->{mp3tag}->{ID3v1}) && $thing ne "genre")
196     {
197         my $action=$thing;
198         $action="genre" if($action eq "v1genre");
199         if($action eq "track")
200         {
201             $self->{mp3tag}->{ID3v1}->track("00");
202         }
203         else
204         {
205             $self->{mp3tag}->{ID3v1}->$action(" ");
206         }
207     }
208
209     if(exists($self->{mp3tag}->{ID3v2}))
210     {
211         print "2: remove: $thing\n";
212         if($thing eq "artist")
213         {
214             $self->{mp3tag}->{ID3v2}->remove_frame("TPE1");
215             $self->{mp3tag}->{ID3v2}->remove_frame("TPE2");
216         }
217         elsif($thing eq "album")
218         {
219             $self->{mp3tag}->{ID3v2}->remove_frame("TALB");
220         }
221         elsif($thing eq "song")
222         {
223             $self->{mp3tag}->{ID3v2}->remove_frame("TIT2");
224         }
225         elsif($thing eq "track")
226         {
227             $self->{mp3tag}->{ID3v2}->remove_frame("TRCK");
228         }
229         elsif($thing eq "year")
230         {
231             $self->{mp3tag}->{ID3v2}->remove_frame("TYER");
232             $self->{mp3tag}->{ID3v2}->remove_frame("TDRC");
233         }
234         elsif($thing eq "comment")
235         {
236             $self->{mp3tag}->{ID3v2}->remove_frame("COMM");
237         }
238         elsif($thing eq "genre")
239         {
240             $self->{mp3tag}->{ID3v2}->remove_frame("TCON");
241         }
242     }
243 }
244
245 sub uniq
246 {
247     my ($self, @things)=@_;
248     my %hash=();
249     @hash{@things}=();
250     return(sort keys(%hash));
251 }
252
253
254 sub channels
255 {
256     my($self)=@_;
257     return undef unless($self->{mp3info});
258     return( ($self->{mp3info}->stereo()) ? 2 : 1 );
259 }
260
261 sub bitrate
262 {
263     my($self)=@_;
264     return undef unless($self->{mp3info});
265     return( int($self->{mp3info}->bitrate()) );
266 }
267
268 sub samplerate
269 {
270     my($self)=@_;
271     return undef unless($self->{mp3info});
272     return(int($self->{mp3info}->frequency() * 1000));
273 }
274
275
276 1;