File: File.pm

package info (click to toggle)
libmp3-tag-perl 1.16-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 1,488 kB
  • sloc: perl: 10,139; makefile: 16; sh: 11
file content (481 lines) | stat: -rw-r--r-- 10,815 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
package MP3::Tag::File;

use strict;
use Fcntl;
use File::Basename;
use vars qw /$VERSION @ISA/;

$VERSION="1.00";
@ISA = 'MP3::Tag::__hasparent';

=pod

=head1 NAME

MP3::Tag::File - Module for reading / writing files

=head1 SYNOPSIS

  my $mp3 = MP3::Tag->new($filename);

  ($title, $artist, $no, $album, $year) = $mp3->parse_filename();

see L<MP3::Tag>

=head1 DESCRIPTION

MP3::Tag::File is designed to be called from the MP3::Tag module.

It offers possibilities to read/write data from files via read(), write(),
truncate(), seek(), tell(), open(), close(); one can find the filename via
the filename() method.

=cut


# Constructor

sub new_with_parent {
    my ($class, $filename, $parent) = @_;
    return undef unless -f $filename or -c $filename;
    return bless {filename => $filename, parent => $parent}, $class;
}
*new = \&new_with_parent;	# Obsolete handler

# Destructor

sub DESTROY {
    my $self=shift;
    if (exists $self->{FH} and defined $self->{FH}) {
	$self->close;
    }
}

# File subs

sub filename { shift->{filename} }

sub open {
    my $self=shift;
    my $mode= shift;
    if (defined $mode and $mode =~ /w/i) {
	$mode=O_RDWR;    # read/write mode
    } else {
	$mode=O_RDONLY;  # read only mode
    }
    unless (exists $self->{FH}) {
	local *FH;
	if (sysopen (FH, $self->filename, $mode)) {
	    $self->{FH} = *FH;
	    binmode $self->{FH};
	} else {
	    warn "Open `" . $self->filename() . "' failed: $!\n";
	}
    }
    return exists $self->{FH};
}


sub close {
    my $self=shift;
    if (exists $self->{FH}) {
	close $self->{FH};
	delete $self->{FH};
    }
}

sub write {
    my ($self, $data) = @_;
    if (exists $self->{FH}) {
	local $\ = '';
	print {$self->{FH}} $data;
    }
}

sub truncate {
    my ($self, $length) = @_;
    if ($length<0) {
	my @stat = stat $self->{FH};
	$length = $stat[7] + $length;
    }
    if (exists $self->{FH}) {
	truncate $self->{FH}, $length;
    }
}

sub size {
    my ($self) = @_;
    return -s $self->{FH} if exists $self->{FH};
    return -s ($self->filename);
}

sub seek {
    my ($self, $pos, $whence)=@_;
    $self->open unless exists $self->{FH};
    seek $self->{FH}, $pos, $whence;
}

sub tell {
    my ($self, $pos, $whence)=@_;
    return undef unless exists $self->{FH};
    return tell $self->{FH};
}

sub read {
    my ($self, $buf_, $length) = @_;
    $self->open unless exists $self->{FH};
    return read $self->{FH}, $$buf_, $length;
}

sub is_open {
    return exists shift->{FH};
}

# keep the old name
*isOpen = \&is_open;

# read and decode the header of the mp3 part of the file
# the raw content of the header fields is stored, the values
# are not interpreted in any way (e.g. layer==3 means 'Layer I'
# as specified in the mp3 format)
sub get_mp3_frame_header {
    my ($self, $start) = @_;

    $start = 0 unless $start;

    if (exists $self->{mp3header}) {
	return $self->{mp3header};
    }

    $self->seek($start, 0);
    my ($data, $bits)="";
    while (1) {
	my $nextdata;
	$self->read(\$nextdata, 512);
	return unless $nextdata; # no header found
	$data .= $nextdata;
	if ($data =~ /(\xFF[\xE0-\xFF]..)/) {
	    $bits = unpack("B32", $1);
	    last;
	}
	$data = substr $data, -3
    }

    my @fields;
    for (qw/11 2 2 1 4 2 1 1 1 2 2 1 1 2/) {
	push @fields, oct "0b" . substr $bits, 0, $_;
	$bits = substr $bits, $_ if length $bits > $_;
    }

    $self->{mp3header}={};
    for (qw/sync version layer proctection bitrate_id sampling_rate_id padding private
	 channel_mode mode_ext copyright original emphasis/) {
	$self->{mp3header}->{$_}=shift @fields;
    }

    return $self->{mp3header}
}


# use filename to determine information about song/artist/album

=pod

=over 4

=item parse_filename()

  ($title, $artist, $no, $album, $year) = $mp3->parse_filename($what, $filename);

parse_filename() tries to extract information about artist, title,
track number, album and year from the filename.  (For backward
compatibility it may be also called by deprecated name
read_filename().)

This is likely to fail for a lot of filenames, especially the album will
be often wrongly guessed, as the name of the parent directory is taken as
album name.

$what and $filename are optional. $what maybe title, track, artist, album
or year. If $what is defined parse_filename() will return only this element.

If $filename is defined this filename will be used and not the real
filename which was set by L<MP3::Tag> with
C<MP3::Tag-E<gt>new($filename)>.  Otherwise the actual filename is used
(subject to configuration variable C<decode_encoding_filename>).

Following formats will be hopefully recognized:

- album name/artist name - song name.mp3

- album_name/artist_name-song_name.mp3

- album.name/artist.name_song.name.mp3

- album name/(artist name) song name.mp3

- album name/01. artist name - song name.mp3

- album name/artist name - 01 - song.name.mp3

If artist or title end in C<(NUMBER)> with 4-digit NUMBER, it is considered
the year.

=cut

*read_filename = \&parse_filename;

sub return_parsed {
    my ($self,$what) = @_;
    if (defined $what) {
	return $self->{parsed}{album}  if $what =~/^al/i;
	return $self->{parsed}{artist} if $what =~/^a/i;
	return $self->{parsed}{no}     if $what =~/^tr/i;
	return $self->{parsed}{year}   if $what =~/^y/i;
	return $self->{parsed}{title};
    }

    return $self->{parsed} unless wantarray;
    return map $self->{parsed}{$_} , qw(title artist no album year);
}

sub parse_filename {
    my ($self,$what,$filename) = @_;
    unless (defined $filename) {
      $filename = $self->filename;
      my $e;
      if ($e = $self->get_config('decode_encoding_filename') and $e->[0]) {
	require Encode;
	$filename = Encode::decode($e->[0], $filename);
      }
    }
    my $pathandfile = $filename;

    $self->return_parsed($what)	if exists $self->{parsed_filename}
				   and $self->{parsed_filename} eq $filename;

    # prepare pathandfile for easier use
    my $ext_rex = $self->get_config('extension')->[0];
    $pathandfile =~ s/$ext_rex//;		# remove extension
    $pathandfile =~ s/ +/ /g; # replace several spaces by one space

    # Keep two last components of the file name
    my ($file, $path) = fileparse($pathandfile, "");
    ($path) = fileparse($path, "");
    my $orig_file = $file;

    # check which chars are used for seperating words
    #   assumption: spaces between words

    unless ($file =~/ /) {
	# no spaces used, find word seperator
	my $Ndot = $file =~ tr/././;
	my $Nunderscore = $file =~ tr/_/_/;
	my $Ndash = $file =~ tr/-/-/;
	if (($Ndot>$Nunderscore) && ($Ndot>1)) {
	    $file =~ s/\./ /g;
	}
	elsif ($Nunderscore > 1) {
	    $file =~ s/_/ /g;
	}
	elsif ($Ndash>2) {
	    $file =~ s/-/ /g;
	}
    }

    # check wich chars are used for seperating parts
    #   assumption: " - " is used

    my $partsep = " - ";

    unless ($file =~ / - /) {
	if ($file =~ /-/) {
	    $partsep = "-";
	} elsif ($file =~ /^\(.*\)/) {
	    # replace brackets by -
	    $file =~ s/^\((.*?)\)/$1 - /;
	    $file =~ s/ +/ /;
	    $partsep = " - ";
	} elsif ($file =~ /_/) {
	    $partsep = "_";
	} else {
	    $partsep = "DoesNotExist";
	}
    }

    # get parts of name
    my ($title, $artist, $no, $album, $year)=("","","","","");

    # try to find a track-number in front of filename
    if ($file =~ /^ *(\d+)[\W_]/) {
	$no=$1;                 # store number
	$file =~ s/^ *\d+//; # and delete it
	$file =~ s/^$partsep// || $file =~ s/^.//;
	$file =~ s/^ +//;
    }

    $file =~ s/_+/ /g unless $partsep =~ /_/; #remove underscore unless they are needed for part seperation
    my @parts = split /$partsep/, $file;
    if (@parts == 1) {
	$title=$parts[0];
	$no = $file if $title and $title =~ /^\d{1,2}$/;
    } elsif (@parts == 2) {
	if ($parts[0] =~ /^\d{1,2}$/) {
	  $no = $parts[0];
	  $title = $file;
	} elsif ($parts[1] =~ /^\d{1,2}$/) {
	  $no = $parts[1];
	  $title = $file;
	} else {
	  $artist=$parts[0];
	  $title=$parts[1];
	}
    } elsif (@parts > 2) {
	my $temp = "";
	$artist = shift @parts;
	foreach (@parts) {
	    if (/^ *(\d+)\.? *$/) {
		$artist.= $partsep . $temp if $temp;
		$temp="";
		$no=$1;
	    } else {
		$temp .= $partsep if $temp;
		$temp .= $_;
	    }
	}
	$title=$temp;
    }

    $title =~ s/ +$//;
    $artist =~ s/ +$//;
    $no =~ s/ +$//;

    # Special-case names like audio12 etc created by some software
    # (cdda2wav, gramofile, etc)
    $no = $+ if not $no and $title =~ /^(\d+)?(?:audio|track|processed)\s*(\d+)?$/i and $+;

    $no =~ s/^0+//;

    if ($path) {
	unless ($artist) {
	    $artist = $path;
	} else {
	    $album = $path;
	}
    }
    # Keep the year in the title/artist (XXXX Should we?)
    $year = $1 if $title =~ /\((\d{4})\)/ or $artist =~ /\((\d{4})\)/;

    $self->{parsed_filename} = $filename;
    $self->{parsed} = { artist=>$artist, song=>$title, no=>$no,
		        album=>$album,  title=>$title, year => $year};
    $self->return_parsed($what);
}


=pod

=item title()

 $title = $mp3->title($filename);

Returns the title, guessed from the filename. See also parse_filename().  (For
backward compatibility, can be called by deprecated name song().)

$filename is optional and will be used instead of the real filename if defined.

=cut

*song = \&title;

sub title {
    my $self = shift;
    return $self->parse_filename("title", @_);
}

=pod

=item artist()

 $artist = $mp3->artist($filename);

Returns the artist name, guessed from the filename. See also parse_filename()

$filename is optional and will be used instead of the real filename if defined.

=cut

sub artist {
    my $self = shift;
    return $self->parse_filename("artist", @_);
}

=pod

=item track()

 $track = $mp3->track($filename);

Returns the track number, guessed from the filename. See also parse_filename()

$filename is optional and will be used instead of the real filename if defined.

=cut

sub track {
    my $self = shift;
    return $self->parse_filename("track", @_);
}

=item year()

 $year = $mp3->year($filename);

Returns the year, guessed from the filename. See also parse_filename()

$filename is optional and will be used instead of the real filename if defined.

=cut

sub year {
    my $self = shift;
    my $y = $self->parse_filename("year", @_);
    return $y if length $y;
    return;
}

=pod

=item album()

 $album = $mp3->album($filename);

Returns the album name, guessed from the filename. See also parse_filename()
The album name is guessed from the parent directory, so it is very likely to fail.

$filename is optional and will be used instead of the real filename if defined.

=cut

sub album {
    my $self = shift;
    return $self->parse_filename("album", @_);
}

=item comment()

 $comment = $mp3->comment($filename);	# Always undef

=cut

sub comment {}

=item genre()

 $genre = $mp3->genre($filename);	# Always undef

=cut

sub genre {}

1;