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