id3fs-tag: -V: summarize tags in directories
[id3fs.git] / lib / ID3FS / AudioFile.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;
18
19 use strict;
20 use warnings;
21 use ID3FS::AudioFile::Mp3;
22 use ID3FS::AudioFile::Ogg;
23 use ID3FS::AudioFile::Flac;
24
25 sub new
26 {
27     my $proto=shift;
28     my $class=ref($proto) || $proto;
29     my $self={};
30     bless($self,$class);
31
32     $self->{path}=shift;
33     my $ext=($self->{path}=~/.*\.(.*)/)[0];
34     return undef unless($ext);
35     my $me=shift;
36     $ext=lc($ext);
37     if($ext eq "mp3")
38     {
39         $self->{audiofile}=ID3FS::AudioFile::Mp3->new($self->{path});
40     }
41     elsif($ext eq "ogg")
42     {
43         $self->{audiofile}=ID3FS::AudioFile::Ogg->new($self->{path});
44     }
45     elsif($ext eq "flac")
46     {
47         $self->{audiofile}=ID3FS::AudioFile::Flac->new($self->{path});
48     }
49     else
50     {
51         print("$me: $self->{path}: Unknown extension: $ext\n");
52         return undef;
53     }
54     return $self;
55 }
56
57 sub set
58 {
59     my ($self, $thing, $value)=@_;
60     if($value)
61     {
62         $value=$self->sanitise($self->stripslashes($value));
63         $self->{audiofile}->$thing($value);
64     }
65     else
66     {
67         $value=$self->sanitise($self->stripslashes($self->{audiofile}->$thing()));
68     }
69     return $value;
70 }
71
72 sub artist    { return(shift->set("artist", @_));   }
73 sub album     { return(shift->set("album", @_));    }
74 sub track     { return(shift->set("track", @_));    }
75 sub tracknum  { return(shift->set("tracknum", @_)); }
76 sub v1genre   { return(shift->set("v1genre", @_));  }
77 sub comment   { return(shift->set("comment", @_));  }
78 sub audiotype { return(shift->set("audiotype"));    }
79 sub haspic    { return(shift->set("haspic"));       }
80 sub path      { return(shift->{path});              }
81
82 sub delete_artist   { shift->{audiofile}->delete_artist();   }
83 sub delete_album    { shift->{audiofile}->delete_album();    }
84 sub delete_track    { shift->{audiofile}->delete_track();    }
85 sub delete_tracknum { shift->{audiofile}->delete_tracknum(); }
86 sub delete_year     { shift->{audiofile}->delete_year();     }
87 sub delete_v1genre  { shift->{audiofile}->delete_v1genre();  }
88 sub delete_comment  { shift->{audiofile}->delete_comment();  }
89 sub delete_all      { shift->{audiofile}->delete_all();      }
90 sub delete_genre    { shift->{audiofile}->delete_genre();    }
91 sub delete_tags     { shift->{audiofile}->delete_tags(@_);   }
92 sub channels        { shift->{audiofile}->channels();        }
93 sub bitrate         { shift->{audiofile}->bitrate();         }
94 sub samplerate      { shift->{audiofile}->samplerate(@_);    }
95
96 sub year
97 {
98     my ($self, $year)=@_;
99     if($year)
100     {
101         $year=$self->format_year($year);
102         $self->{audiofile}->year($year);
103     }
104     else
105     {
106         $year=$self->{audiofile}->year();
107     }
108     return $year;
109 }
110
111 sub format_year
112 {
113     my ($self, $year)=@_;
114     if($year)
115     {
116         $year=$self->sanitise($self->stripslashes($year));
117         if(defined($year) && $year =~/(\d{4})/)
118         {
119             $year=$1;
120         }
121     }
122     return $year;
123 }
124
125 sub add_tags
126 {
127     my($self, $tags)=@_;
128     my @tags=split(/\s*,\s*/, $tags);
129     $self->{audiofile}->add_tags(@tags);
130 }
131
132 sub tags
133 {
134     my $self=shift;
135     my @intags=$self->{audiofile}->tags();
136     my @outtags=();
137     return() unless(@intags);
138     @intags = grep { defined($_); } @intags;
139     # combine then split on commas
140     # so multiple comma-delimited tags will work
141     @intags=split(/\s*,\s*/, join(', ', @intags));
142     for my $tag (@intags)
143     {
144         next unless(length($tag));
145         next unless($tag =~ /\S+/);
146         $tag=$self->sanitise($tag);
147         my ($tagname, $tagval)=($tag, undef);
148         if($tag=~/^([^\/]+)\/(.*)/)
149         {
150             ($tagname, $tagval)=($1, $2);
151         }
152         push(@outtags, [ $tagname, $tagval ]);
153     }
154     return @outtags;
155 }
156
157 sub write
158 {
159     shift->{audiofile}->write();
160 }
161
162 sub sanitise
163 {
164     my ($self, $text)=@_;
165     $text =~ s/[^[:print:]]//g if(defined($text));
166     return $text;
167 }
168
169 sub stripslashes
170 {
171     my ($self, $text)=@_;
172     $text =~ s/\//-/g if(defined($text));
173     return $text;
174 }
175
176 # This location for these subs is pretty much arbitrary
177 sub uniq
178 {
179     # class method
180     shift if(ref($_[0]) eq "ID3FS::AudioFile");
181     my (@things)=@_;
182     my %hash=();
183     @hash{@things}=();
184     return(sort keys(%hash));
185 }
186
187 sub list_remove
188 {
189     my($remove, $list)=@_;
190     return(()) unless($list && @$list);
191     my @list=@$list;
192     for my $tag (@$remove)
193     {
194         @list=grep { $_ ne $tag; } @list;
195     }
196     return(@list);
197 }
198
199 1;