File Coverage

blib/lib/Mac/iPod/GNUpod/Utils.pm
Criterion Covered Total %
statement 60 205 29.2
branch 0 34 0.0
condition 0 47 0.0
subroutine 20 32 62.5
pod 0 12 0.0
total 80 330 24.2


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Mac::iPod::GNUpod::Utils;
4              
5             # This file is based on code from FooBar.pm and XMLhelper.pm in the GNUpod
6             # toolset. The original code is (C) 2002-2003 Adrian Ulrich
7             # blinkenlights.ch>.
8             #
9             # Much rewriting and adaptation by JS Bangs , (C)
10             # 2003-2004.
11              
12 4     4   25 use Exporter;
  4         7  
  4         292  
13 4     4   5985 use Unicode::String;
  4         31681  
  4         258  
14 4     4   45 use File::Spec;
  4         8  
  4         96  
15 4     4   4978 use MP3::Info qw(:all);
  4         883423  
  4         907  
16 4     4   4330 use MP4::Info;
  4         51895  
  4         362  
17 4     4   4596 use Audio::Wav;
  4         12351  
  4         274  
18              
19             @ISA = qw/Exporter/;
20             @EXPORT = qw/shx2int xescaped realpath mkhash mktag matches/;
21              
22 4     4   31 use strict;
  4         8  
  4         119  
23 4     4   20 use warnings;
  4         5  
  4         259  
24              
25             BEGIN {
26 4     4   20 MP3::Info::use_winamp_genres();
27 4         1408 MP3::Info::use_mp3_utf8(0);
28 4         36 MP4::Info::use_mp4_utf8(0);
29             }
30              
31             # Reformat shx numbers
32             sub shx2int {
33 0     0 0   my($shx) = @_;
34 0           my $buff = '';
35 0           foreach(split(//,$shx)) {
36 0           $buff = sprintf("%02X",ord($_)).$buff;
37             }
38 0           return hex($buff);
39             }
40              
41             # Escape strings for XML
42             sub xescaped {
43 0     0 0   my $txt = shift;
44 0           for ($txt) {
45 0           s/&/&/g;
46 0           s/"/"/g;
47 0           s/
48 0           s/>/>/g;
49             #s/'/'/g;
50             }
51              
52 0           return $txt;
53             }
54              
55             # Create a hash
56             sub mkhash {
57 0     0 0   my($base, @content) = @_;
58 0           my $href = ();
59 0           for(my $i=0;$i
60 0           $href->{$base}->{$content[$i]} = Unicode::String::utf8($content[$i+1])->utf8;
61             }
62 0           return $href;
63             }
64              
65             # Create an XML tag
66             sub mktag {
67 0     0 0   my($elm, $attr, %opt) = @_;
68 0           my $r = '<' . xescaped($elm) . ' ';
69 0           foreach (sort keys %$attr) {
70 0 0         next if $attr->{$_} eq ''; # Ignore empty vals
71 0           $r .= xescaped($_). "=\"" . xescaped($attr->{$_}) . "\" ";
72             }
73 0 0         if ($opt{noend}) {
74 0           $r .= ">";
75             }
76             else {
77 0           $r .= " />";
78             }
79              
80 0           return $r;
81             #return getutf8($r);
82             }
83              
84             # Find if two things match, w/ opts
85             sub matches {
86 0     0 0   my ($left, $right, %opts) = @_;
87 4     4   1900 no warnings 'uninitialized';
  4         8  
  4         1553  
88 0 0         if ($opts{nocase}) {
89 0           $left = lc $left;
90 0           $right = lc $right;
91             }
92 0 0         if ($opts{nometachar}) {
93 0           $right = quotemeta $right;
94             }
95              
96 0 0         if ($opts{exact}) {
97 0           return $left eq $right;
98             }
99             else {
100 0           return $left =~ /$right/;
101             }
102             }
103              
104             # Try to discover the file format
105             sub wtf_is {
106 0     0 0   my $file = shift;
107 0           my $h;
108              
109             # Try to recognize by extension
110 0 0         if ($file =~ m/\.mp3$/) {
    0          
    0          
111 0           $h = mp3_info($file);
112             }
113             elsif ($file =~ m/\.wav$/) {
114 0           $h = wav_info($file);
115             }
116             elsif ($file =~ m/\.(mp4|m4a)$/) {
117 0           $h = mp4_info($file);
118             }
119              
120             # Unrecognized file types
121             else {
122 0           $@ = "Unsupported/unknown file type: $file";
123 0           return undef;
124             }
125              
126 0 0         if ($h) {
127 0           $h->{orig_path} = File::Spec->rel2abs($file);
128 0           return $h;
129             }
130             }
131              
132             # Check if the file is an PCM (WAV) File
133             sub wav_info {
134 0     0 0   my $file = shift;
135              
136 0           my $wav = Audio::Wav->new;
137 0           my ($nfo, $details);
138 0           eval {
139 4     4   23 no warnings;
  4         7  
  4         610  
140 0           my $read = $wav->read($file);
141 0           $nfo = $read->get_info;
142 0           $details = $read->details;
143             };
144 0 0         return undef if $@;
145              
146 0           my %rh = ();
147              
148             # Get basic info from $details
149 0           $rh{bitrate} = $details->{bytes_sec} * 8;
150 0           $rh{srate} = $details->{sample_rate};
151 0           $rh{time} = $details->{length};
152 0           $rh{fdesc} = "RIFF Audio File";
153              
154             # No id3 tags for WAV, so we check the nfo hash and file path
155 0           my @path = File::Spec->splitdir((File::Spec->splitpath($file))[1]);
156 4     4   21 no warnings 'uninitialized';
  4         6  
  4         1589  
157 0   0       $rh{title} = $nfo->{name} || $path[-1] || "Unknown Title";
158 0   0       $rh{album} = $nfo->{product} || $path[-2] || "Unknown Album";
159 0   0       $rh{artist} = $nfo->{artist} || $path[-3] || "Unknown Artist";
160 0           $rh{genre} = $nfo->{genre};
161 0           $rh{comment}= $nfo->{comments};
162 0           $rh{year} = int($nfo->{copyright});
163              
164 0           return \%rh;
165             }
166              
167             sub get_last_nested {
168 0     0 0   my $ref = shift;
169 0 0         if (ref($ref) eq 'ARRAY') {
170 0           return get_last_nested($ref->[-1]);
171             }
172 0           return $ref;
173             }
174              
175              
176             # Read mp3 tags, return undef if file is not an mp3
177             sub mp3_info {
178 0     0 0   my $file = shift;
179              
180 0           my $h = MP3::Info::get_mp3info($file);
181 0 0         return undef unless $h; #Not an mp3
182              
183             #This is our default fallback:
184             #If we didn't find a title, we'll use the
185             #Filename.. why? because you are not able
186             #to play the file without a filename ;)
187 0           my $cf = (File::Spec->splitpath($file))[-1];
188              
189 0           my %rh = ();
190              
191 0           $rh{bitrate} = $h->{BITRATE};
192 0           $rh{filesize} = $h->{SIZE};
193 0           $rh{srate} = int($h->{FREQUENCY}*1000);
194 0           $rh{time} = int($h->{SECS}*1000);
195 0           $rh{fdesc} = "MPEG $h->{VERSION} layer $h->{LAYER} file";
196              
197 0           $h = MP3::Info::get_mp3tag($file,1); #Get the IDv1 tag
198 0           my $hs = MP3::Info::get_mp3tag($file, 2, 2); #Get the IDv2 tag
199              
200             # If any of these are array refs (multiple values), take last value
201 0           for (keys %$hs) {
202 0           $hs->{$_} = get_last_nested($hs->{$_});
203             }
204              
205             #IDv2 is stronger than IDv1..
206             #Try to parse things like 01/01
207 4     4   23 no warnings 'uninitialized';
  4         7  
  4         147  
208 4     4   18 no warnings 'numeric';
  4         15  
  4         1804  
209 0   0       my @songa = parseslashes($hs->{TRCK} || $h->{TRACKNUM});
210 0           my @cda = parseslashes($hs->{TPOS});
211 0           $rh{songs} = int($songa[1]);
212 0           $rh{songnum} = int($songa[0]);
213 0           $rh{cdnum} = int($cda[0]);
214 0           $rh{cds} = int($cda[1]);
215 0   0       $rh{year} = $hs->{TYER} || $h->{YEAR} || 0;
216 0   0       $rh{title} = $hs->{TIT2} || $h->{TITLE} || $cf || "Untitled";
217 0   0       $rh{album} = $hs->{TALB} || $h->{ALBUM} || "Unknown Album";
218 0   0       $rh{artist} = $hs->{TPE1} || $h->{ARTIST} || "Unknown Artist";
219 0   0       $rh{genre} = $h->{GENRE} || "";
220 0   0       $rh{comment} = $hs->{COMM} || $h->{COMMENT}|| "";
221 0   0       $rh{composer} = $hs->{TCOM} || "";
222 0   0       $rh{playcount}= int($hs->{PCNT}) || 0;
223              
224 0           return \%rh;
225             }
226              
227             # This subroutine written by Masanori Hara, added in v. 1.22
228             sub mp4_info {
229 0     0 0   my $file = shift;
230              
231 0           my $h = MP4::Info::get_mp4info($file);
232 0 0         return unless $h; #Not an mp3
233              
234             #This is our default fallback:
235             #If we didn't find a title, we'll use the
236             #Filename.. why? because you are not able
237             #to play the file without a filename ;)
238 0           my $cf = (File::Spec->splitpath($file))[-1];
239              
240 0           my %rh = ();
241              
242 0           $rh{bitrate} = $h->{BITRATE};
243 0           $rh{filesize} = $h->{SIZE};
244 0           $rh{srate} = int($h->{FREQUENCY}*1000);
245 0           $rh{time} = int($h->{SECS}*1000);
246 0           $rh{fdesc} = $h->{TOO};
247              
248 0           $h = MP4::Info::get_mp4tag($file,1); #Get the IDv1 tag
249 0           my $hs = MP4::Info::get_mp4tag($file, 2, 2); #Get the IDv2 tag
250             # If any of these are array refs (multiple values), take last value
251 0           for (keys %$hs) {
252 0 0         if (ref($hs->{$_}) eq 'ARRAY') {
253 0           $hs->{$_} = $hs->{$_}->[-1];
254             }
255             }
256              
257             #IDv2 is stronger than IDv1..
258             #Try to parse things like 01/01
259 4     4   25 no warnings 'uninitialized';
  4         8  
  4         145  
260 4     4   19 no warnings 'numeric';
  4         5  
  4         1350  
261 0   0       my @songa = parseslashes($hs->{TRCK} || $h->{TRACKNUM});
262 0           my @cda = parseslashes($hs->{TPOS});
263 0           $rh{songs} = int($songa[1]);
264 0           $rh{songnum} = int($songa[0]);
265 0           $rh{cdnum} = int($cda[0]);
266 0           $rh{cds} = int($cda[1]);
267 0   0       $rh{year} = $hs->{TYER} || $h->{YEAR} || 0;
268 0   0       $rh{title} = $hs->{TIT2} || $h->{TITLE} || $cf || "Untitled";
269 0   0       $rh{album} = $hs->{TALB} || $h->{ALBUM} || "Unknown Album";
270 0   0       $rh{artist} = $hs->{TPE1} || $h->{ARTIST} || "Unknown Artist";
271 0   0       $rh{genre} = $h->{GENRE} || "";
272 0   0       $rh{comment} = $hs->{COMM} || $h->{COMMENT}|| "";
273 0   0       $rh{composer} = $hs->{TCOM} || "";
274 0   0       $rh{playcount}= int($hs->{PCNT}) || 0;
275              
276 0           return \%rh;
277             }
278              
279             # Guess format
280             sub parseslashes {
281 0     0 0   my($string) = @_;
282 4     4   23 no warnings 'numeric';
  4         6  
  4         139  
283 4     4   20 no warnings 'uninitialized';
  4         8  
  4         436  
284 0 0         if(my($s,$n) = $string =~ m!(\d+)/(\d+)!) {
285 0           return int($s), int($n);
286             }
287             else {
288 0           return int($string);
289             }
290             }
291              
292             # Try to 'auto-guess' charset and return utf8
293             sub getutf8 {
294 0     0 0   my $in = shift;
295              
296 4     4   21 no warnings 'uninitialized';
  4         7  
  4         385  
297 0 0 0       if(ord($in) > 0 && ord($in) < 32) {
298 0           $@ = "Unsupported ID3 encoding found: " .ord($in)."\n";
299 0           return undef;
300             }
301             # autoguess (accept invalid id3tags)
302             else {
303             #Remove all 00's
304 0           $in =~ tr/\0//d;
305 4     4   22 no warnings;
  4         6  
  4         564  
306 0           my $bfx = Unicode::String::utf8($in);
307 0 0         if($bfx ne $in) {
308             #Input was not valid utf8, assume latin1 input
309 0           $in =~ s/[\000-\037]//gm; #Kill stupid chars..
310 0           $in = Unicode::String::latin1($in);
311             }
312             else { #Return the unicoded input
313 0           $in = $bfx;
314             }
315             }
316 0           return $in;
317             }
318              
319             1;