MP3-Tag

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

%M was not recognized.

parsing for "%n" allows track numbers of the form 123/789 too; such track
numbers are considered as "not fit for ID3v1".

Document that set_id3v2_frame(), select_id3v2_frame() remove the old value(s).

New method interpolate_with_flags().  Make ...::Parse use this method.

New methods composer(), performer(), delete_tag(), _interpolate() in
MP3::Tag.  Allow customization via local configuration modules
MP3::Tag::User MP3::Tag::Site MP3::Tag::Vendor.

New interpolated escape %{I(FLAGS)VALUE}.

New configuration variables id3v23_unsync, composer, performer,
translate_composer, translate_performer.

New test t/interpolate.t.

Incompatible change: kill backslashitis in %-escapes:
    frame_select_by_descr() etc: no backslashitis whatsoever.

Changes  view on Meta::CPAN


Document environment variables.

translate_title_collection etc configuration values were not settable
(neither were they documented...).

New methods of ID3v2: frame_select_by_descr(), frame_have_by_descr(),
frame_list_by_descr(), frame_select_by_descr_simple().

New methods have_id3v2_frame_by_descr(), select_id3v2_frame_by_descr(),
frame_translate(), frames_translate(), shorten_person() in MP3::Tag.
interpolate() significantly simplified; new interpolation %{shP{NAME}}.

New type of interpolation: frame ids connected by &.  Slightly more readable
documentation of interpolate() and parse_rex().

New configuration settings: person_frames, and translate_person
(supported only by select_id3v2_frame_by_descr()).

Music_Translate_Fields.pm: much better detection of non-canonical names
(e.g., year removal), support for person names shortening.  New method

Changes  view on Meta::CPAN

=======================

After removing several of repeated frames, get_frame(s) could get confused.
So could the code using get_frame(s).  Now better documented, and fixed.

New method ->is_modified() of ID3v2.

New method ->as_bin() of ID3v1 and ID3v2.

New methods ->is_id3v2_modified(), ->get_id3v2_frames(), ->select_id3v2_frame(),
->have_id3v2_frame(), ->get_id3v2_frame_ids() of MP3::Tag.

Some `next's needed to be replaced by `return' in mp3info2 and audio_rename.

mp3info2 would not decide that an edit is not needed if an ID3v2 frame was
modified directly.

Avoid re-parsing by ParseData more aggressively (10x speedup in one complicated
real-life example).

set_id3v2_frame() and update_tags() were assuming that get_tags() was already

Changes  view on Meta::CPAN

New options 'b', 'B', 'o', 'O', 'D' for ParseData.  With 'n', 'l' ParseData
does not ignore empty trailing lines any more.

New %-interpolations: %{ID3v1} for the tag as a whole, same for ID3v2.

Be more strict during interpolation: do not recognize WORD1 as a valid short
name of ID3v2 frame.

Two new example scripts: mp3_total_time.pl, eat_wav_mp3_header.pl.

Mention example scripts in documentation of MP3::Tag.

Release Name: 0.94
=======================

The example of the "long" form of ID3v2-comment setting API has arguments swapped.

New methods ->frame_select(), ->frame_have() of ID3v2.

New methods ->pure_filetags() and ->update_tags() of MP3::Tag.

New config variables id3v2_missing_fatal (it was as if it was defaulting to
TRUE; now defaults to FALSE), parse_minmatch.

New test files in t/: v2_comments.t update_tags.t parser.t 

New escape %{COMM(rus,eng,xxx,#4)[short_description]} (chooses a comment with
"short_description" descriptor in the specified list of languages; #4 means
4th comment field, or COMM03; empty language means any language.)  Works
as a condition for conditional interpolation too, and as a target of parsing

Changes  view on Meta::CPAN

(default).

New option 'intact' to get_frame() of ID3v2.

Support YEAR, ID3Y, ID3G in EXTD, EXTT* fields of CDDB_File.  Support also
an alternative syntax "Recorded"/"Recorded on"/"Recorded in"/ with the format
of the date recognized by ID3v2::year(), or just a date field without a prefix.
The declarations of the former form are stripped from the returned comment.

New fields artist_collection, title_track, comment_collection, comment_track
in CDDB_File and corresponding accessor methods in MP3::Tag, and corresponding
escapes aC, tT, cC, cT for interpolate().

New configuration variables 'year_is_timestamp', 'comment_remove_date',
'translate_*' (for different values of *).

New handler module LastResort; currently it uses artist_collection() as comment
if comment is not otherwise defined.

New example scripts audio_rename and mod/Music_Translate_Fields.pm.

The example script mp3info2 will use Music_Translate_Fields.pm (if found)
to postprocess the resulting tag fields via translate_*() methods.

Do not load Compress::Zlib unless when processing compressed data.

Release Name: 0.9
=======================

Revision history for Perl modules MP3::Tag, MP3::Tag::ID3v1, MP3::Tag::ID3v2, MP3::Tag::File,
             and for Perl program tk-tag

Changes:

* Bugfix for ID3v2.pm:
  - Added support for reading ID3v2.2 and ID3v2.4 tags (alpha stage!!) and converting them to ID3v2.3
  - Bugfix for unsynchronization (thanks to Ian Beckwith)
  - Bugfix for number-handling in frames
  - Bugfix for frame compression
  - Set unsynchronization flag only, if unsynchronization was done and changed anything

Changes  view on Meta::CPAN

    the same name

* Makefile.PL:
  - overriding the manifypods() function in MakeMaker to add ID3v2-Data.pod to the MAN3PODS hash
    (thanks to Dagfinn Ilmari Mannsaker)

* Added copyright text, using Perl Artistic License

Thanks to Ilya Zakharevich for the following changes:

* Three new modules MP3::Tag::Inf, MP3::Tag::ParseData, and
  MP3::Tag::CDDB_File added

* Tag.pm:
  - autoinfo() method returns the info for all ID3v1 tags;
  - autoinfo() method may return the info which tag is obtained from
    which source;
  - Methods title(), author() etc (one per each ID3v1 tag) exist and
    have uniform interface through all the subpackages;
  - method song() is renamed to title(); method read_filename() is
    renamed to parse_filename(); backward compatible name still
    preserved;

Changes  view on Meta::CPAN

* misprints in the docs corrected and some general harmonization of function and
  field names in the different modules

* new example script mp3info2 (which provides most of functionality of the
  "standard" mp3info utility, and much more).

Release Name: 0.40
==================
Changes:

* Updated documentation for MP3::Tag, MP3::Tag::ID3v1, MP3::Tag::ID3v2 and MP3::Tag::ID3v2-Data 

* Renamed some functions. The old names will still work, but try to use the new names.
  The following names were changed: 

  - MP3::Tag::getTags() to MP3::Tag::get_tags() 
  - MP3::Tag::newTag() to MP3::Tag::new_tag() 
  - MP3::Tag::ID3v1::removeTag() to MP3::Tag::ID3v1::remove_tag() 
  - MP3::Tag::ID3v1::writeTag() to MP3::Tag::ID3v1::write_tag() 
  - MP3::Tag::ID2v2::getFrameIDs() to MP3::Tag::ID3v2::get_frame_ids() 
  - MP3::Tag::ID2v2::getFrame() to MP3::Tag::ID3v2::get_frame() 
  
* Bugfix for ID3v2.pm:
  - getFrame() returned "undef" as a string instead of simple undef
  - artist() produced an error when TPE1 is missing in TAG
 
* Bugfix for Tag.pm:
  - DESTROY() checked only if $mp3->obj{filename} exists and not if it is defined before trying to 
    close it  

* Bugfix for ID3v1.pm:
  - genres() expected an id3v1-object as first parameter and a genre
    only as second parameter. Now the object can be omitted as in a call like
    MP3::Tag::ID3v1->genres($genre) used by Tag.pm and ID3v2.pm

* bugfix for File.pm:
  - Filenames may contain surrounding whitespaces now

Release Name: 0.30
==================
Changes:
* Tag.pm
  - autoinfo() function added. This returns artist/songtitle/track/album.
    It tries to find this information in an ID3v1 or ID3v2 tag or tries 

Changes  view on Meta::CPAN

    saved one
  - Set Filename from ID3v2-Tag works now at least with Artist (%a),
    Album(%l) and Song (%s)
* ID3v2::what_data returns now also information about possible restricted
  input for some frame fields (APIC; TCON; COMR; TFLT are supported yet).

Release Name: 0.25
==================
Changes:

* Bug-fix for MP3::Tag
  If you created several mp3-objects for different files, the filehandles
  for each file were not used correctly. Thanks to hakimian for reporting
  this bug.
* Bug-fix for ID3v2::remove_tag()
  It was tried twice to rename one temp-file after removing the tag.
  Thanks to Brent Sarten <bsarten@bigfoot.com> for reporting this.
* Bug-fix for ID3v2::add_tag()
  When adding a second (or third, ...) frame of a kind, a wrong header
  could be written for this frame
* Bug-fix for tk-tag 

Changes  view on Meta::CPAN

========================
Changes:

--due to problems when run at windows:
  * Added a second seek to ID3v1::write_tag, as windows writes at a wrong 
    position otherwise
  * Setting Filehandle to binmode after opening a mp3 file 
  * ID3v2: write_tag creates a temp file (if neccessary) now in the same
    directory where the original mp3 files is located and not in /tmp

* Added tk-tag.pl, a graphical interface for MP3::Tag. tk-tag.pl is a alpha
  version
* Added a new manpage MP3::Tag::ID3v2-Data which contains information about
  the ID3v2 frames and the data returned by MP3::Tag::Id3v2::getFrame() 
* Frames RVRB ("Reverb"), COMR ("Commercial frame"), AENC ("Audio encryption"),
  GRID ("Group identification registration"), RBUF ("Recommended buffer size")
  and SYTC ("Synchronized lyric/text") are now supported	
* Added some test to test.pl for creating new tags
* ID3v2::getFrameIDs returns now a hash reference, which contains the found
  frames. The keys are the 4 byte codes of the frames, which are needed for
  getFrame . The according values are the english (long) names of the frames.
* ID3v2::write_tag - Updating tagsize after writing tag 
* ID3v1::all() returns in array context all fields, otherwise only the song
* MP3::ID3v1::write_tag didn't returned an error if a file couldn't be opened
  for writing. Now it does.
* Renamed MP3::TAG to MP3::Tag following a suggestion of ANDK from CPAN
* Makefile.PL : Added that Compress::Zlib and File::Basename is needed for 
  installation of MP3::Tag

Release Name: 0.1 (beta)
========================
Changes:
* Added documentation to the modules

* Writing/removing of ID3v2.3 tags is supported now 
* Adding, changing, removing frames of ID3v2.3 is supported

* Changed directory structure
* Added file for proper install of modules


Release Name: 0.2-alpha
=======================
Changes: 
* ID3v2.3 compressed frames are supported now 
* changed directory structure, support librarys for MP3::Tag are now in a
  subdirectory 
* tagged.pl calls xview to show pictures, which were found in ID3v2 tags
  (sorry, not configurable at the moment, but easy to change in tagged.pl)

Release Name: 0.1-alpha
=======================
This is the first alpha version. It contains perl modules to
read ID3v1/ID3v2 tags, but they are still lacking a lot of
features. 

* Reading / Writing ID3v1 works
* Reading of most frames of ID3v2.3 works

Included is a demo program tagged.pl, and a program to change ID3v1
tags and to set automatically the filename of a mp3 file: tagit.pl See
README.txt for details. More documentation is still lacking. Sorry.



MP3::Tag can be found at http://sourceforge.net/projects/tagged

Makefile.PL  view on Meta::CPAN

directory $scr.

To skip, rerun with option -n given to Makefile.PL.

EOW
}

# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
    'NAME'		=> 'MP3::Tag',
    'VERSION_FROM'	=> 'lib/MP3/Tag.pm', # finds $VERSION
    'EXE_FILES'		=> [ map "examples/$_", @programs_to_install ],
#    'PMLIBDIRS'         => ['Tag', 'MP3::Tag'],
    'AUTHOR'            => '"Thomas Geffert" <thg@users.sourceforge.net>, "Ilya Zakharevich" ilyaz@cpan.org',
    'PREREQ_PM'         => {
				# Compress::Zlib => 0,
			   },
    'PL_FILES'          => {'data_pod.PL'=>'lib/MP3/Tag/ID3v2_Data.pod'},
 #   'clean'             => {FILES => 'ID3v2_Data.pod'},	# is included!
);

# Tell MakeMaker about manifying ID3v2_Data.pod
package MY;
sub manifypods {
    my $self = shift;
    $self->{MAN3PODS}->{'lib/MP3/Tag/ID3v2_Data.pod'} = '$(INST_MAN3DIR)/MP3::Tag::ID3v2_Data.$(MAN3EXT)';
    $self->SUPER::manifypods(@_);
}

sub postamble {	# Not good enough: is done after .pod is moved to INST_LIB...
  '
lib/MP3/Tag/ID3v2_Data.pod :: lib/MP3/Tag/ID3v2.pm # pm_to_blib

';
}

README.txt  view on Meta::CPAN

                           MP3::Tag
============================================================

This is a perl module to read/write ID3v1, ID3v1.1 and ID3v2.3
tags of mp3-files. (Other tags hopefully to follow).

To install the MP3::TAG module you simply do:

perl Makefile.PL
make
make test

README.txt  view on Meta::CPAN

an email describing the problems.

You need to have the modules Compress::Zlib and File::Basename
installed. If you are missing one of these (perl Makefile.PL 
should warn you) you can find them on CPAN (www.cpan.org).

In the directory examples, you find 4 examples, how to use
the module. You can read the documentation of this
module with 

man MP3::Tag
man MP3::Tag::ID3v1
man MP3::Tag::ID3v2
man MP3::Tag::ID3v2_Data

More information about this project, new releases and so on, can
be found at:         

http://tagged.sourceforge.net

Success with this

  Thomas

  <thg@users.sourceforge.net>


                           tk-tag.pl
==============================================================

In the directory tk-tag you can find a graphical interface
for MP3::Tag. See the README file in that directory.


                           Copyright
==============================================================

Copyright (c) 2000-2004 Thomas Geffert.  All rights reserved. 
This program is free software; you can redistribute it and/or
modify it under the terms of the Artistic License, distributed
with Perl.

TODO  view on Meta::CPAN

MP3::Tag.pm
===========

* It seems that to allow intelligent handling of Unicode file names we need to keep a non-decoded name (e.g., from readdir())
  and a decoded one (to extract the info from the file name).  Should not we expect that new() may be called with an unicode name???

    What is below is not 100% clear now :-[

* perhaps restructuring of this wrapper module, as it 
  should be easier to say which Tag::modules should be used

TODO  view on Meta::CPAN

  	Possibly needed on ID3v1/ID3v2 only (others cannot write yet).
  Take into account that title() etc take arguments (write!).
  	Be draconian when rejecting method calls; allow slack via config.
  Allow Packages=*, pass through config.
  Allow passing through field().

  ID3v2::get_frame has wantarray(), but only in one place!

* more testing

MP3::Tag::ID3v1.pm
==================

* more testing



MP3::Tag::ID3v2.pm
==================

* Encryption of frames (read and write)

* Only first tag in front of file is read, tags inside mp3-data are ignored

* Following frames are only supported in RAW mode:
  - EQUA -> Equalization
  - ETCO -> Event timing codes
  - MLLT -> MPEG location lookup table

data_pod.PL  view on Meta::CPAN

#!/usr/bin/perl -w

## data_pod.PL creates the documentation File MP3::Tag::ID3v2_Data

use MP3::Tag;
use MP3::Tag::ID3v2;

$filename=shift || "./lib/MP3/Tag/ID3v2_Data.pod";

open(POD, ">$filename");
$std = select(POD);

@frames = keys %{MP3::Tag::ID3v2::supported_frames()};

print <<"INTRO";

=head1 NAME 

MP3::Tag::ID3v2_Data - get_frame() data format and supported frames

=head1 SYNOPSIS

  \$mp3 = MP3::Tag->new(\$filename);
  \$mp3->get_tags();
  \$id3v2 = \$mp3->{ID3v2} if exists \$mp3->{id3v2};

  (\$info, \$long) = \$id3v2->get_frame(\$id);    # or

  (\$info, \$long) = \$id3v2->get_frame(\$id, 'raw');


=head1 DESCRIPTION 

This document describes how to use the results of the get_frame function of 
MP3::Tag::ID3v2, thus the data format of frames retrieved with 
MP3::Tag::ID3v2::get_frame().

It contains also a list of all supported ID3v2-Frames.

=head2 get_frame()

 (\$info, \$long) = \$id3v2->get_frame(\$id);    # or
 
 (\$info, \$long) = \$id3v2->get_frame(\$id, 'raw');

\$id has to be a name of a frame like "APIC".  For more variants of calling
see L<get_frame()|MP3::Tag::ID3v2>.

The names of all frames found in a tag can be retrieved with the L<get_frame_ids()|MP3::Tag::ID3v2> function.

=head2 Using the returned data

In the ID3v2.3 specifications $#frames frames are defined, which can contain very
different information. That means that get_frame returns the information
of different frames also in different ways.

=over 4

=item Simple Frames

data_pod.PL  view on Meta::CPAN


The frames which (in addition to C<Text>/C<URL>) contain only
C<Description> and C<Language> fields are in some intermediate position
between "simple" and "complex" frames.  They can be handled very similarly
to "simple" frames by using "long names", such as C<COMM[description]>
or C<COMM(LANG)[description]>, and the corresponding "quick" API such
as frame_select().
  
INTRO

@frames = keys %MP3::Tag::ID3v2::long_names;
@other = ();
@text = ();
@complex = ();

foreach (@frames) {
  $data = MP3::Tag::ID3v2::what_data("", $_);
  if (ref $data) {
    if ($#$data == 0 and ($$data[0] =~ /^(Text|URL)$/ or $_ eq 'MCDI')) {
      push @text, $_;
    } else {
      push @complex, $_;
    }
  } else {
    push @other, $_;
  }
}

print "\n\n=head2 List of Simple Frames\n\nFollowing Frames are supported 
and return a single string (text). In the List you can find the frame IDs 
and the long names of the frames as returned by \$id3v2->get_frame():\n\n=over 4\n\n";
foreach (sort @text) {
  $long = $MP3::Tag::ID3v2::long_names{$_};
  print "\n=item $_ : $long\n";
}
print "\n=back\n\n";

print "\n\n=head2 List of Complex Frames\n\n";
print "Following frames are supported and return a reference to a hash. The
list shows which keys can be found in the returned hash:\n";
print "\n=over 4\n\n";
foreach (sort @complex) {
  $long = $MP3::Tag::ID3v2::long_names{$_};
  print "\n=item $_ : $long\n\n";
  $data = MP3::Tag::ID3v2::what_data("", $_);
  print "  Keys: ", join(", ",@$data), "\n";
}
print "\n=back\n\n";

print "\n\n=head2 List of Other Frames\n\n";
print "Following frames are only supported in raw mode:\n";
print "\n=over 4\n\n";
foreach (sort @other) {
  $long = $MP3::Tag::ID3v2::long_names{$_};
  print "\n=item $_ : $long\n";
}
print "\n=back\n\n";

print <<END;

=head1 SEE ALSO

L<MP3::Tag>, L<MP3::Tag::ID3v2>

END

select($std);
close POD;

examples/Music_Normalize_Fields-normalize.pl  view on Meta::CPAN

#!/usr/bin/perl -w
use strict;
use Music_Translate_Fields;
use MP3::Tag;

my $tag = MP3::Tag->new(q(/dev/null)) or die;
$tag->config(parse_data => [qw(mi %a), shift]);
$tag->Music_Translate_Fields::normalize_file_lines(shift);

examples/README.txt  view on Meta::CPAN

   Short documentation of tagged.pl, tagit.pl and extractID3v2.pl
=====================================================================

You will find three perl programs as examples in this release. 
You can use them after installing the MP3::Tag module. Look
at the README.txt in the main directory to see, how to do this.

Then simply run tagged.pl or tagit.pl (see description below), 
giving filename(s) on the command line. mp3info.pl expects the
filename(s) on standard input <STDIN>.

To run the examples you need Perl installed, at least 5.x I think. 
I wrote this on Redhat/Debian Linux with Perl 5.6.0, but I think, 
it should run with other versions too.
I didn't test it with windows. If you try to run it with windows,
please send me a short message, either if you had success or not. 

  Thomas  <thg@users.sourceforge.net> 
          http://tagged.sourceforge.net


tagged.pl
#########

tagged.pl demonstrates at the moment the function of MP3::Tag. It
reads the tags of files, which are given on the command line, and
prints them to the console.

Later tagged.pl should be a program to change tags interactivly, to
check them for consistency (is there different information in ID3v1
and ID3v2 tag and/or the filename?), and so on...


mp3info.pl
##########

examples/audio_rename  view on Meta::CPAN

#!/usr/bin/perl -w

eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
    if 0; # not running under some shell

$VERSION = '1.16';

use MP3::Tag 0.9711;
use Getopt::Std 'getopts';
use File::Spec;
use File::Path;
use strict;

$Getopt::Std::STANDARD_HELP_VERSION = 1;
my %opt = (E => '', p => '%{mA}%{n0}_%t', e => '.inf|.tag|.mp3', r => qr(\.mp3$)i);

# WinCyrillic (win1251), short (CDFS), Keep non-filename chars, Dry run, Glob,
# path via pattern, |-separated list of associated extensions

examples/audio_rename  view on Meta::CPAN

    my $locale;
    if ($opt{U}) {
      $locale = 'UTF-8';
    } else {
      $locale = $ENV{LC_CTYPE} || $ENV{LC_ALL} || $ENV{LANG};
      if ($^O eq 'os2' and not eval {Encode::resolve_alias($locale)} ) {
        require OS2::Process;
        $locale = 'cp' . OS2::Process::out_codepage();
      }
    }
    MP3::Tag->reset_encode_decode_config($locale) if $locale and $opt{L} & 0x08;
#    warn "LOCALE=$locale: e=$opt{L}";
    $skip = !($opt{L} & 1);
    # Reinterpret @ARGV
    @ARGV = map my_decode($locale, $_), @ARGV if $opt{L} & 4;
    # Reinterpret opts
    @opt{keys %opt} = map my_decode_deep($locale, $_), values %opt
      if $opt{L} & 2;
    $opt{L} = $locale;
#  } elsif ($opt{L} eq 'binary') {
#    binmode STDOUT;

examples/audio_rename  view on Meta::CPAN


# Configure stuff...
if (defined $opt{C}) {
  my ($c) = ($opt{C} =~ /^(\W)/);
  $c = quotemeta $c if defined $c;
  $c = '(?!)' unless defined $c;		# Never match
  my @opts = split /$c/, $opt{C};
  shift @opts if @opts > 1;
  for $c (@opts) {
    $c =~ s/^(\w+)=/$1,/;
    MP3::Tag->config(split /,/, $c);
  }
}

my @parse_data;
if (defined $opt{P}) {
  my ($c) = ($opt{P} =~ /^\w*(\W)/s);
  $c = quotemeta $c if defined $c;
  $c = '(?!)' unless defined $c;		# Never match
  @parse_data = map [split /$c/], split /$c$c$c/, $opt{P};
  for $c (@parse_data) {

examples/audio_rename  view on Meta::CPAN

sub mk_path_filename ($) {
  # Assume '/' for dirnames
  (my $d = shift) =~ s,(.*)/.*,$1,s;
  # print "mkpath `$d'\n";
  mkpath $d;
}

sub process_file ($) {
    my $f = shift;
    print "File: $f\n";
    my $mp3=MP3::Tag->new($f);
    if ($mp3) {
	$mp3->config('parse_data', @parse_data) if @parse_data;
	my ($ext, $base);
	if ($opt{x}) {
	  $ext = '';
	  $base = $mp3->interpolate("%A%E");
	} else {
	  $ext = $mp3->filename_extension();
	  $base = $mp3->interpolate("%A");
	}

examples/audio_rename  view on Meta::CPAN

		    no_chdir => 1}, @f);
} else {
  my $f;
  for $f (@f) {
    process_file $f;
  }
}

=head1 NAME

audio_rename - rename an audio file via information got via L<MP3::Tag>.

=head1 SYNOPSIS

  audio_rename -csR -@p "@a/@l/@02n_@t" .

renames all the audio files in this directory and its subdirectories
into a 3-level directory structure given by
F<Artist_Name/Album/Filename>, with the basename of F<Filename> being
the 2-digit track number separated from the title by underscore; it also
transliterates cyrillic, and shortens long names.

examples/audio_rename  view on Meta::CPAN


=head1 Recognized options

General use options:

=over

=item B<-p> C<TARGET_FILENAME_PATTERN>

Target file name/basename pattern; is subject to interpolation via
C<MP3::Tag> method L<C<interpolate()>|MP3::Tag/interpolate>.  Default
is C<%{mA}%{n0}_%t>; in simplest cases this uses 2-digit track number
separated from the title by underscore.  See L<MP3::Tag/interpolate> for
more details.

Here is the explanation of the default value: due to semantic of escapes
C<%{mA}> and C<%{n0}>, if C<TPOS> frame (disk number) is present, it is
encoded as a letter, and put before the track number.  If the track number
has a form C<N1/N2> (meaning track N1 of N2), then N1 is used, and padded
by 0s to the width of N2.  If C<N2> is not present, padding to width=2 is
used.

For example, if C<TPOS> is 3/12, and track is C<14/173>, then what is prepended

examples/audio_rename  view on Meta::CPAN

double quotes, so if one wants shell-transparent examples of command lines,
use -@ and double quotes.)

=item B<-P> C<patterns>

Patterns to parse before application of the rule B<-p>.  See
L<mp3info2> for details.

=item B<-C> C<config_options>

Configuration options for L<MP3::Tag>.  See L<mp3info2> for details.

=back

File name portability options:

=over

=item B<-s>

Make the components of file names short enough to fit on a CD file

examples/audio_rename  view on Meta::CPAN

being moved between systems).

=item B<-L>

The value of option C<-L> is the encoding used for the
output; if the value is a number, system-specific encoding is guessed
(and used for the output if the bit 0x1 is set); if the bit 0x2 is set,
then command line options are assumed to be in the guessed encoding; if
the bit 0x4 is set, then command line arguments are assumed to be in the
guessed encoding.  If the bit 0x8 is set, the encoding/decoding
configuration of file input/output of C<MP3::Tag> is redone with the
the detected encoding.

=item B<-U>

In presence of C<-U> option the default for C<-L> is C<15>, and
the decoding/encoding processing happens as if LANG is set for C<UTF-8>
encoding.  (For example, for C<-Uee 1> the C<STDOUT> the output message
of this script happens in UTF-8 mode, which makes it easier to detect 
decoding/encoding errors in tags.)

examples/audio_rename  view on Meta::CPAN

In practice, I do not recall ever encountering this situation; if the
target file name depends only on the contents of the file, and not its
name, then the second rename will be tautological, so not visible.

=head1 AUTHOR

Ilya Zakharevich <cpan@ilyaz.org>.

=head1 SEE ALSO

MP3::Tag, MP3::Tag::ParseData, mp3info2

=cut

examples/fulltoc_2fake_cddb.pl  view on Meta::CPAN

#!/usr/bin/perl -w

# Read a file (from STDIN) created via (e.g.)
#   readcd2 -fulltoc dev=0,1,0 -f=audio_cd
# , produce a leader of a cddb file on STDOUT
# (processable with `fdquery --i file' from Net::FreeDB2, or cddb2cddb)

use strict;
use MP3::Tag;

my $round_offset = 0;	# Rounding down gives better match to initial size...
binmode STDIN;
my $in = do { local $/;  <STDIN> };
my $s = 2 + unpack 'n', $in;
my $s1 = length $in;
die "TOC size mismatch: header=$s, actual=$s1" unless $s == $s1;
my %chunks = unpack 'x4 (x3 C x4 a3)*', $in;

sub msf2_sector {

examples/mod/manage_M_N_F.pm  view on Meta::CPAN

use strict;
use Normalize::Text::Music_Fields;
use MP3::Tag;

for (qw(read_composer_file prepare_tag_object_comp normalize_file_lines
	emit_as_mail_header merge_info check_persons test_normalize_piece)) {
  no strict;
  *$_ = \&{"Normalize::Text::Music_Fields::$_"};
}

1;

examples/mp3info.pl  view on Meta::CPAN

#!/usr/bin/perl -w
               
use MP3::Tag;

# define how autoinfo tries to get information
# default:
# MP3::Tag->config("autoinfo","ID3v2","ID3v1","filename");
# don't use ID3v2:
# MP3::Tag->config("autoinfo","ID3v1","filename");

while (<STDIN>) {
	chomp;
	if (my $mp3=MP3::Tag->new($_)) {
		print "$_ (Tags: ", join(", ",$mp3->get_tags),")\n";
		@info=$mp3->autoinfo;
		print "* Song: $info[0]\n";
		print "* Track: $info[1]\n";
		print "* Artist: $info[2]\n";
		print "* Album: $info[3]\n";
		print "* Comment: $info[4]\n";
	}
}

examples/mp3info2  view on Meta::CPAN

#!/usr/bin/perl -w

use FindBin;
use lib "$FindBin::Bin";

use MP3::Tag 1.16;		# Need reset_encode_decode_config()
use Getopt::Std 'getopts';
use Config;
use File::Path;

$VERSION = '1.16';
use strict;

$Getopt::Std::STANDARD_HELP_VERSION = 1;
my %opt;
sub MULTIV::TIEHASH {bless \my $a, 'MULTIV'}

examples/mp3info2  view on Meta::CPAN

    my $locale;
    if ($opt{U}) {
      $locale = 'UTF-8';
    } else {
      $locale = $ENV{LC_CTYPE} || $ENV{LC_ALL} || $ENV{LANG};
      if ($^O eq 'os2' and not eval {Encode::resolve_alias($locale)} ) {
        require OS2::Process;
        $locale = 'cp' . OS2::Process::out_codepage();
      }
    }
    MP3::Tag->reset_encode_decode_config($locale) if $locale and $opt{e} & 0x08;
#    warn "LOCALE=$locale: e=$opt{e}";
    $skip = !($opt{e} & 1);
    # Reinterpret @ARGV
    @ARGV = map my_decode($locale, $_), @ARGV if $opt{e} & 4;
    # Reinterpret opts
    @opt{keys %opt} = map my_decode_deep($locale, $_), values %opt
      if $opt{e} & 2;
    $opt{e} = $locale;
  } elsif ($opt{e} eq 'binary') {
    binmode STDOUT;
    $skip = 1;
  }
  binmode STDOUT, ":encoding($opt{e})" if defined $opt{e} and not $skip;
}

my $e_opt = MP3::Tag->get_config('extra_config_keys');
MP3::Tag->config('extra_config_keys', @$e_opt, qw(empty-F-deletes frames_write_creates_dirs));
MP3::Tag->config('empty-F-deletes', 1)
  unless defined MP3::Tag->get_config1('empty-F-deletes');

# keys of %opt to the MP3::Tag keywords:
my %trans = (	't' => 'title',
		'a' => 'artist',
		'l' => 'album',
		'y' => 'year',
		'g' => 'genre',
		'c' => 'comment',
		'n' => 'track'  );

# Interprete Escape sequences:
my %r = ( 'n' => "\n", 't' => "\t", '\\' => "\\"  );

examples/mp3info2  view on Meta::CPAN

my (@del, @del_tag);
for my $o (@{ $opt{d} }) {
  my @D;
  push @D, $1 while $o =~ s/^ ( $FNAME | ID3v[12] ) (,|$) //xo;
  die "Unrecognized part of -d option: `$o'" if length $o;
  push @del_tag, grep  /^ID3v[12]$/, @D;
  push @del,     grep !/^ID3v[12]$/, @D;
}

# Configure stuff...
MP3::Tag->config(autoinfo => qw(ParseData ID3v2 ID3v1)) if $opt{N}; # Naive algo

for my $C (@{ $opt{C} || [] }) {
  my ($c) = ($C =~ /^(\W)/);
  $c = quotemeta $c if defined $c;
  $c = '(?!)' unless defined $c;		# Never match
  my @opts = split /$c/, $C;
  shift @opts if @opts > 1;
  for $c (@opts) {
    $c =~ s/^(\w+)=/$1,/;
    MP3::Tag->config(split /,/, $c);
  }
}

unless ($opt{N}) {{
  my $cfg = $ENV{MP3TAG_NORMALIZE_FIELDS};
  last if defined $cfg and not $cfg;
  last unless defined $cfg or $ENV{HOME} and -d "$ENV{HOME}/.music_fields";
  no strict 'refs';
  eval 'require Normalize::Text::Music_Fields';
  for my $elt ( qw( title track artist album comment year genre
		    title_track artist_collection person ) ) {
    MP3::Tag->config("translate_$elt", \&{"Normalize::Text::Music_Fields::normalize_$elt"})
	if defined &{"Normalize::Text::Music_Fields::normalize_$elt"};
  }
  MP3::Tag->config("short_person", \&Normalize::Text::Music_Fields::short_person)
      if defined &Normalize::Text::Music_Fields::short_person;
  $cfg = '' if not defined $cfg or $cfg =~ /^[01]$/;
  my @d = split /$Config{path_sep}/, $cfg;
  Normalize::Text::Music_Fields::set_path(@d)
   if @d and defined &Normalize::Text::Music_Fields::set_path;
}}

my @parse_data;
die 'Option -P requires ParseData in autoinfo'
  if $opt{P} and not grep $_ eq 'ParseData', @{ MP3::Tag->get_config('autoinfo') };
for my $o (@{ $opt{P} }) {
  my ($c) = ($o =~ /^\w*(\W)/s);
  $c = quotemeta $c if defined $c;
  $c = '(?!)' unless defined $c;		# Never match
  push @parse_data, map [split /$c/, $_, -1], split /$c$c$c/, $o;
}
for my $c (@parse_data) {
  die "Two few parts in parse directive `@$c'.\n" if @$c < 3;
}

# E.g., to make Inf overwrite existing title, do
# mp3info2.pl -C title,Inf,ID3v2,ID3v1,filename -u *.mp3

sub new_tag_object ($) {
  my $fname = shift;
  return MP3::Tag->new($fname) unless $fname eq '';
  MP3::Tag->new_fake('settable');
}

sub process_file ($) {
    my $f = shift;
    my $mp3 = new_tag_object($f); # BUGXX Can't merge into if(): extra refcount
    if ($mp3) {
      print $mp3->interpolate(<<EOC) unless exists $opt{p};
File: %F
EOC
      for my $tag (@del_tag) {	# delete whole tags

examples/mp3info2  view on Meta::CPAN

	    my $write = $1;
	    $have = ($set->[0] =~ /^(TAGS|ID3v[12])$/
		     or $mp3->have_id3v2_frame_by_descr($set->[0]));
	    next if $write and not $have;
	  }
	  my $v = $set->[1];
	  $v = $mp3->interpolate($v) if $e_interp->{F};
	  next if $set->[2] eq '?<' and not -e $v;

	  unless ($whole = ($set->[0] =~ /^(TAGS|ID3v[12])$/)) {
	    my($FF) = MP3::Tag::ID3v2->what_data(substr $set->[0], 0, 4);
	    $b = grep !$textish{$_}, @$FF;
	  }

	  if ($set->[2] eq '>') { # we know frame exists
	    my $o;
	    if ($whole) {
	      $mp3->get_tags;
	      if ($set->[0] eq 'TAGS') {
		next unless exists $mp3->{ID3v2} or exists $mp3->{ID3v1};
		$o = $mp3->interpolate("%{ID3v2}%{ID3v1}");

examples/mp3info2  view on Meta::CPAN

		$o = $mp3->interpolate("%{$set->[0]}");
	      }
	    } else {
	      $o = $mp3->select_id3v2_frame_by_descr($set->[0]);
	    }
	    next unless defined $o; # Should not happen???
	    die "An attempt to extract `non-simple' frame `$set->[0]' to a file"
	      if ref $o;
	    unless (open FF, "> $v") {
	      my $rc;
	      if (MP3::Tag->get_config1('frames_write_creates_dirs')) {
		my ($dir) = ($v =~ m,^(.*)[\\/],s);
		if (defined $dir and not -d $dir) {
		  mkpath $dir;		# would die on error
		  $rc = open FF, "> $v"
		}
	      }
	      die "Can't open `$v' for write: $!" unless $rc;
	    }
	    binmode FF if $b;
	    syswrite FF, $o, length $o or die "syswrite to `$v': $!"
	      if length $o;
	    close FF  or die "Can't close `$v' for read: $!";
	    next;
	  }
	  if ($set->[2]) {	# < or ?<
	    my $cond = ($set->[2] eq '?<');
	    next if $cond and not -e $v;
	    if ($whole) {
	      my $from = MP3::Tag->new($v) or die "Can't create tags for `$v'";
	      $from->get_tags;
	      if ($set->[0] =~ /^(TAGS|(ID3v1)?)$/) { # Process "simple" fields
		my $from1 = ($2 ? $from->{ID3v1} : $from);	# $2: ID3v1
		for my $field (values %trans) {	# Use "named method" for access
		  my $v = ($from1 and $from1->$field());
		  next unless defined $v and length $v;
		  my $check_v = (not $cond or $mp3->$field);
		  next unless defined $check_v and length $check_v;
		  my $ff = $field .= '_set';
		  $mp3->$ff($v);

examples/mp3info2  view on Meta::CPAN

		    no_chdir => 1}, @f);
} else {
  my $f;
  for $f (@f) {
    process_file $f;
  }
}

=head1 NAME

mp3info2 - get/set MP3 tags; uses L<MP3::Tag> to get default values.

=head1 SYNOPSIS

  # Print the information in tags and autodeduced info
  mp3info2 *.mp3

  # In addition, set the year field to 1981
  mp3info2 -y 1981 *.mp3

  # Same without printout of info, recursively in the current directory

examples/mp3info2  view on Meta::CPAN


  # Same, and get the artist from CDDB file
  mp3info2 -C title=Inf,ID3v2,ID3v1,filename -C artist=CDDB_File -u *.mp3

  # Write a script for conversion of .wav to .mp3, autodeducing tags
  mp3info2 -p "lame -h --vbr-new --tt '%t' --tn %n --ta '%a' --tc '%c' --tl '%l' --ty '%y' '%f'\n" *.wav >xxx.sh

=head1 DESCRIPTION

The program prints a message summarizing tag info (obtained via
L<MP3::Tag|MP3::Tag> module) for specified files.

It may also update the information in ID3 tags.  This happens in three
different cases.

=over

=item *

If the information supplied in command-line options C<t a l y g c n>
differs from the content of the corresponding ID3 tags (or there is no
corresponding ID3 tags).

=item *

If options C<-d> or C<-F> were given.

=item *

if C<MP3::Tag> obtains the info from other means than MP3 tags, and
C<-u> forces the update of the ID3 tags.

=back

(All these ways are disabled by C<-D> option.)  ID3v2 tag is written
if needed, or if C<-2> option is given.  (Automatic fill-in of
deduceable fields (via the method id3v2_frames_autofill()) is
performed unless C<-d> or C<-N> options are given.)

The option C<-u> writes (C<u>pdates) the fetched information to the

examples/mp3info2  view on Meta::CPAN

auto-update of "personal name" fields, and corresponding titles
according to values of C<translate_person>, C<person_frames> etc.
configuration settings; see L<"Normalization of fields">).  This option
is ignored if no change to tags is detected; however, one can force an
update by repeating this option (useful if you expect the change the
"format" of the tag, as opposed to its "content").

The option C<-p> prints a message using the next argument as format
(by default C<\\>, C<\t>, C<\n> are replaced by backslash, tab and
newline; governed by the value of C<-E> option); see
L<MP3::Tag/"interpolate"> for details of the format of sprintf()-like
escapes.  If no option C<-p> is given, message in default format will
be emitted.  The value of option C<-e> is the encoding used for the
output; if the value is a number, system-specific encoding is guessed
(and used for the output if the bit 0x1 is set); if the bit 0x2 is set,
then command line options are assumed to be in the guessed encoding; if
the bit 0x4 is set, then command line arguments are assumed to be in the
guessed encoding.  If the bit 0x8 is set, the encoding/decoding
configuration of file input/output of C<MP3::Tag> is redone with the
the detected encoding.  Use the value C<binary> to do binary output.

In presence of C<-U> option the default for C<-e> is C<15>, and
the decoding/encoding processing happens as if LANG is set for C<UTF-8>
encoding.  (For example, for C<-Ue 1> the C<STDOUT> the output message
of this script happens in UTF-8 mode, which makes it easier to detect 
decoding/encoding errors in tags.)

With option C<-D> (dry run) no update is performed, no matter what the
other options are.  With this option, no parsing of tags is performed unless
needed.

Use options

  t a l y g c n

to overwrite the information (title artist album year genre comment
track-number) obtained via C<MP3::Tag> heuristics (C<-u> switch is
implied if any one of these arguments differs from what would be found
otherwise; use C<-D> switch to disable auto-update).  By default, the
values of these options are not C<%>-interpolated; this may be changed by
C<-E> option.

The option C<-d> should contain the comma-separated list of ID3v2
frames to delete.  A frame specification is the same as what might be
given to C<"%{...}"> frame interpolation command, e.g., C<TIT3>,
C<COMM03>, C<COMM(fra)[short title]>; the difference with modify-access
is that B<ALL> (and not the B<first> of) matching frames are deleted.

examples/mp3info2  view on Meta::CPAN

modifications for C<?E<lt>>.)

By default, the "VALUE" for C<-F> is C<%>-interpolated; this can be
changed by option C<-E>.  For user convenience, human-friendlier forms
C<composer, text_by, orchestra, conductor, track, disk_n> can be used instead of
C<TCOM, TEXT, TPE2, TPE3, TRCK, TPOS>.

The option C<-P RECIPE> is a very powerful generalization of what can be done
by options C<-F>, C<-d>, and C<-t -a -l -y -g -c -n>.  It may be
repeated; the values should contain the parse recipes.  They become the
configuration item C<parse_data> of C<MP3::Tag>; eventually this information
is processed by L<MP3::Tag::ParseData|MP3::Tag::ParseData> module (if the
latter is present in the chain of heuristics; see option C<-C>).  The
C<RECIPE> is split into C<$flags, $string, @patterns> on its first
non-alphanumeric character; the first of @patterns which matches
$string is going to be executed (for side effects).  (See examples:
L<EXAMPLES: parse rules>.)

If option C<-G> is specified, the file names on the command line are
considered as glob patterns.  This may be useful if the maximal
command-line length is too low.  With the option C<-R> arguments can
be directories, which are searched recursively for audio (default

examples/mp3info2  view on Meta::CPAN

but this should be governed by flags, not C<-E>; do I<NOT> put C<P>
into the C<%>-interpolated part of C<-E>.)

If the option C<-@> is given, all characters C<@> in the options are
replaced by C<%>.  This may be convenient if the shell treats C<%>
specially (e.g., DOSISH shells).

If option C<-I> is given, no guessworking for I<artist> field is performed
on typeout.

The option C<-C CONFIG_OPT=VALUE1,VALUE2...> sets C<MP3::Tag> configuration
data the same way as C<MP3::Tag->config()> would do (recall that the value
is an array; separate elements by commas if more than one).  The option may
be repeated to set more than one value.  Note that since C<ParseData> is used
to process C<-P> parse recipes, it should be better be kept in the
C<autoinfo> configuration (and related fields C<author> etc) in presence of C<-P>.

If the option C<-x> is given, the technical information about the audio
file is printed (MP3 level, duration, number of frames, padding, copyright,
and the list of ID3v2 frame names in format suitable to C<%{...}> escapes).
If C<-x> is repeated, content of frames is also printed out (may output
non-printable chars, if it is repeated more than twice).

examples/mp3info2  view on Meta::CPAN

C<MP3TAG_NORMALIZE_FIELDS> to be FALSE.  If not prohibited,
the module is attempted to be loaded if directory F<~/.music_fields>
is present, or C<MP3TAG_NORMALIZE_FIELDS> is set and TRUE.)

If loading of the module C<Normalize::Text::Music_Fields> is successful,
the following is applicable:

If the value of C<MP3TAG_NORMALIZE_FIELDS> is defined and not 1, this value
is broken into directories as a PATH, and load path of
C<Normalize::Text::Music_Fields> is set to be this list of directories.
Then L<MP3::Tag> is instructed (via corresponding configuration settings) to
use C<normalize_artist> (etc.) methods defined by this module.  These methods
may normalize certain tag data.  The current version defines methods for
"normalization" of personal names, and titles (based on the composer).  This
normalization is driven through user-editable configuration tables.

In addition to automatical normalization of MP3 tag data, one can use
"fake MP3 files" to manually access some features of this module.
For this, use an empty file name, and C<-D> option.  E.g,

  mp3info2 -D -a beethoven                       -p "%a\n"         ""

examples/mp3info2  view on Meta::CPAN

Update tags if needed.

=back

=head1 Usage strategy: escalation of complexity

The purpose of this script is to to make handling of ID3 tags as simple
I<as possible>.

On one end of the scale, one can perform arbitrarily
complex manipulations with tags using L<C<MP3::Tag>|MP3::Tag> Perl module.

On the other end, it is much more convenient to handle simplest manipulations
with tags using this script's options C<-t -a -l -y -g -c -n> and C<-p
-F -d>.  For slightly more complicated tasks, one may need to use the
more elaborate method of I<parse rules>, provided to this script by
the option C<-P>; the rules depend heavily on I<interpolation>, see
L<MP3::Tag/interpolate>, L<MP3::Tag/interpolate_with_flags>.

To simplify upgrade from "simplest manipulations" to "more elaborate
ones", here we provide "parse rule" I<synonyms> to the simplest
options.  So if you start with C<-t -a -l -y -g -c -n> and C<-p -F -d>
options which "almost work" for you, you have a good chance to be able
to fully achieve your aim by modifying the synonyms described below.

(Below we assume that C<-E> option is set to its default value, so
C<-F -p> are C<%>-interpolated, other options are not.  Note also that
if your TTY's encoding is recognized by Perl, it is highly recommended

examples/mp3info2  view on Meta::CPAN


  -P "mzi/%{TIT2:1}0%{I(fFim)FILE}/10/10%{TIT2}/0%{U1}"

(add C<bB> to C<fFim> for non-text-only frames); the last part may be
omitted if one omits the flag C<m> - it is present to catch misprints
only.

=back

For details on "parse rules", see L<EXAMPLES: parse rules> and
L<MP3::Tag::ParseData/DESCRIPTION>.

=head1 EXAMPLES: parse rules

Only the C<-P> option is complicated enough to deserve comments...
For full details on I<parse rules>, see
L<MP3::Tag::ParseData/DESCRIPTION>; for full details on interpolation,
see L<MP3::Tag/interpolate>, L<MP3::Tag/interpolate_with_flags>.

For a (silly) example, one can replace C<-a Homer -t Iliad> by

  -P mz=Homer=%a -P mz=Iliad=%t

A less silly example is forcing a particular way of parsing a file name via

  -P "im=%{d0}/%f=%a/%n %t.%e"

It is broken into

examples/mp3info2  view on Meta::CPAN

   ...

   12. Rezitativ.
   (Pizarro, Rocco)

   13. Duett: jetzt, Alter, jetzt hat es Eile, (Pizarro, Rocco)

   ...

The following command puts this info into the title of the ID3 tag (provided
the audio file names are informative enough so that MP3::Tag can deduce the
track number):

 mp3info2 -u -C parse_split='\n(?=\d+\.)' -P 'fl;Parts;%=n. %t'

If this paragraph of information has the form C<TITLE (COMMENT)> with the
C<COMMENT> part being optional, then use

 mp3info2 -u -C parse_split='\n(?=\d+\.)' -P 'fl;Parts;%=n. %t (%c);%=n. %t'

If you want to remove a dot or a comma got into the end of the title, use

examples/mp3info2  view on Meta::CPAN

setting a comment field unless it it already present:

  mp3info2 -u -P 'i;%c///with piano;///%c' *.mp3

The last example shows how to actually write "programs" in the
language of the C<-P> option: the example gives a conditional
assignment.  With user variables (as in C<%{U8}>) for temporaries, and
a possibility to use regular expressions, one
could provide arbitrary programmatic logic.  Of course, at some level
of complexity one should better switch to direct interfacing with
C<MP3::Tag> Perl module (use the code of this Perl script as an example!).

Here is a typical task setting "advanced" id3v2 frames: composer (C<TCOM>),
orchestra (C<TPE2>), conductor (C<TPE3>).  We assume a directory tree which
contains MP3 files tagged with the following conventions: C<artist> is
actually a composer; C<comment> is of one of two forms:

  Performers; Orchestra; Conductor
  Orchestra; Conductor

To set the specific MP3 frames via C<-P> rules, use

examples/mp3info2  view on Meta::CPAN

    -F "WXXX[]=@{WXXX[]}" -F "COMM(eng)[]=@{COMM(eng)[]}"
    -y "@y" -P "mi/@n/0@n/@n" *.mp3

=head1 Examples on dealing with broken encodings

One of principal weaknesses of ID3 specification was that it required that
data is provided in C<latin-1> encoding.  Since most languages in the world
are not expressible in C<latin-1>, this lead to (majority?) of ID3 tags being
not standard-conforming.  Newer versions of the specs fixed this shortcoming,
but the damage was already done.  Fortunately, this script can use abilities
of L<C<MP3::Tag>|MP3::Tag/ENVIRONMENT> to convert from non-conforming content
to a conforming one.

The following example converts ID3v2 tags which were written in
(non-standard-conforming) encoding C<cp1251> to be in
standard-conforming encoding.  For the purpose of this example, assume that
ID3v1 tags are in the same encoding (and that one wants to leave them in the
encoding C<cp1251>); the files to process are found in the current directory
and (recursively) in its subdirectories (C<set> syntax for DOSISH shells):

  set MP3TAG_DECODE_V1_DEFAULT=cp1251
  set MP3TAG_DECODE_V2_DEFAULT=cp1251
  mp3info2 -C id3v2_fix_encoding_on_write=1 -u2R .

For more information, see L<MP3::Tag/ENVIRONMENT>, L<MP3::Tag/config>,
and L<MP3::Tag/CUSTOMIZATION>.

=head1 INCOMPATIBILITIES with F<mp3info>

This tool is loosely modeled on the program F<mp3info>; it is "mostly"
backward compatible (especially when in "naive" mode via C<-N>), and
allows a very significant superset of functionality.  Known backward
incompatibilities are:

  -G -h -r -d -x

examples/mp3info2  view on Meta::CPAN

With C<-e> above 0, this script may consult environment variables
C<LC_CTYPE, LC_ALL, LANG> to deduce the current encoding.  Moreover,
we try to auto-detect the C<UTF-8> encoding via C<LANG> environment
variable (disabled if C<${^UNICODE}> sets translation of the command-line
arguments).  (This auto-detection may be switched off by the variable
C<MP3INFO_DECODE_ARGV_DEFAULT_RESET>.)  The
effects of the variable C<MP3TAG_NORMALIZE_FIELDS> is described in
the section L<"Normalization of fields">.  No other environment
variables are directly read by this script.

Note however, that L<MP3::Tag> module has a rich set of defaults for
encoding settings settable by environment variables; see
L<MP3::Tag/"ENVIRONMENT">.  So these variables affect (indirectly) how
this script works.  In particular, starting with v1.14, there is an
autodetection of encoding of setup files (which this script uses for
the C<-P> option).  (However, this autodetection may be switched off.)

=head1 OBSOLETE INTERFACE

If you do not understand what it is about, it is safe to ignore this
announcement:

The old, pre-version=C<1.05> way (by triplication of a separator, without

examples/mp3info2  view on Meta::CPAN


Passing this stub to the script F<cddb2cddb.pl>, it can be transformed
to a "filled" CDDB file via a connection to some online database.  Use
C<-r> option if multiple records in the database match the CD
signature.

  fulltoc2fake_cddb audiocd.toc | cddb2cddb     > audio.cddb
  inf_2fake_cddb	        | cddb2cddb     > audio.cddb
  dir_mp3_2fake_cddb	        | cddb2cddb -r3 > audio.cddb # 3rd record

When such a CDDB file is present, it will be used by L<MP3::Tag>
module to deduce the information about an audio file.  This information
is (by default, transparently) used by this script.

=head1 SEE ALSO

MP3::Tag, MP3::Tag::ParseData, audio_rename, typeset_audio_dir

=cut

examples/tagged.pl  view on Meta::CPAN

#!/usr/bin/perl -w

use strict;
use MP3::Tag;

my ($mp3, $count, $v1, $v2)=(undef,0,0,0);

die "usage: tagged.pl filename(s)" if $#ARGV == -1;

my $t = time;

for my $filename (@ARGV) {
  next until -f $filename;
  print " --  $filename:\n";

  $mp3 = MP3::Tag->new($filename);
  $mp3->get_tags;
  $count++;
  if (exists $mp3->{ID3v1}) {
    $v1++;
    print " ** found ID3v1 - TAG\n";
    print "   Song: " .$mp3->{ID3v1}->song . "\n";
    print " Artist: " .$mp3->{ID3v1}->artist . "\n";
    print "  Album: " .$mp3->{ID3v1}->album . "\n";
    print "Comment: " .$mp3->{ID3v1}->comment . "\n";
    print "   Year: " .$mp3->{ID3v1}->year . "\n";

examples/tagit.pl  view on Meta::CPAN

#!/usr/bin/perl -w

use File::Copy;
use MP3::Tag;

# some settings for getting command-line options
use Getopt::Long;
Getopt::Long::Configure(qw/no_ignore_case_always ignore_case bundling/);
my %options = ( '--song=s'    => "Name of the song",
		'--album=s'   => "Album",
		'--artist=s'  => "Artist",
		'--comment=s' => "Comment",
		'--track=i'   => "Track",
		'--genre=s'   => "Genre",

examples/tagit.pl  view on Meta::CPAN

		'--test'      => "Do NOT change the files. Only print which changes would be made",
		'--skipwithoutv1' => "Don't do anything if no ID3v1 tag exists",
		'--skipwithv1' => "Don't use --getfilename option if ID3v1 tags already exists",
	       );

# get the command line options
my %opt;
getoptions(\%opt, %options);

if (exists $opt{showgenres}) {
  my $genres = MP3::Tag::genres();
  print join (", ", @$genres) ."\n";
}

unless ($#ARGV >=0) {
  print "error: Filename(s) missing\n" unless exists $opt{showgenres};
  exit 0 if exists $opt{showgenres};
  exit 1;
}

# is there only one or more files to work with?

examples/tagit.pl  view on Meta::CPAN

}
$opt{format} = "%a - %s.mp3" unless exists $opt{format};
my ($stencil, $details);
($stencil, $details) = formatstr_setfilename($opt{format}) if exists $opt{setfilename};
($stencil, $details) = formatstr_getfilename($opt{format}) if exists $opt{getfilename};

# loop for each file
chomp(@ARGV = <STDIN>) unless @ARGV;
for my $filename (@ARGV) {
  # get the tags
  $mp3 = MP3::Tag->new($filename);
  unless (defined $mp3) {
    print "Skipping $filename ...\n";
    next;
  }
  $mp3->get_tags;

  unless (exists $mp3->{ID3v1}) {
    print "No ID3v1-Tag found\n" if exists $opt{show} || exists $opt{v};
    next if exists $opt{skipwithoutv1};
    $mp3->new_tag("ID3v1");

examples/typeset_audio_dir  view on Meta::CPAN

#!/usr/bin/perl -w

$VERSION = '1.10';

use strict;
use MP3::Tag '0.9711';		# %{mA}
use File::Find;
use Getopt::Std 'getopts';
use Cwd;

$Getopt::Std::STANDARD_HELP_VERSION = 1;
my %opt = my %ini_opt = (2 => '%a', a => 2, t => 1, F => 'T2A');
my @INI_ARGV = @ARGV;
# Level=2 header; Level=1 header (default - dir),
# use duration, year, whole dates, replace @ by %, basename of output files,
# ignore 'author' on level > this in directory tree

examples/typeset_audio_dir  view on Meta::CPAN

    print "\\posttrack ";
  }
  print to_TeX $this->{title};
  print "$comment$year\\posttitle\n";
}

# Callback for find():
sub print_mp3 {
  return unless -f $_ and /$glob_pat/;
  #print STDERR "... $_\n";
  my $tag = MP3::Tag->new($_);
  my @parts = split m<[/\\]>, $File::Find::dir;
  shift @parts if @parts and $parts[0] eq '.';

  my $this;
  $this->{top} = $opt{1} ? $tag->interpolate($opt{1}) : $performers[-1];
  $this->{author} = $tag->interpolate($opt{2});	# default '%a'
  $this->{title} = $tag->interpolate(@parts <= $opt{t} ? '%t' : '%l');
  $this->{track} = $tag->interpolate($opt{n} eq 1 ? '%{mA}%{n1}' : $opt{n})
    if $opt{n} and @parts <= $opt{t};
  $this->{len} = $tag->interpolate('%S') if $opt{T};

examples/typeset_audio_dir  view on Meta::CPAN

  # Likewise, but the name is based on the album; ignore comments
  typeset_audio_dir -yTn -P short -B All_short

  # Likewise, but produce both long and short listings.  The short one serves
  # as a table-of-contents for the long one
  typeset_audio_dir -ynTL -P short,long -B All

=head1 DESCRIPTION

Scans directory (or directories) given on the command line, using
L<MP3::Tag|MP3::Tag> to obtain information about audio files (to process
non-MP3 files, extra modules may be needed, see L<MP3::Tag>, and B<-r
FILENAME_FILTER> option must be given).  Produces
(one or more, depending on B<-B> option) B<TeX> files
with commands to typeset human-readable listings.  Non-directories on
the command line are ignored.  (May also be used to process non-audio
files, if L<MP3::Tag|MP3::Tag> may extract the title/etc info from them.)

With B<-B>, the file F<*_list.tex> contains all the data about audio
files (when B<-P> with both C<short,long> is given, another similar file
F<*_list_long.tex> is also written); the file F<*_titles.tex> contains
a 0th approximation to the possible "title" of the collection (one
based on B<-N> option and a short summary of toplevel directories).
The file F<*_common.tex> contains macros common for the following
files.  The remaining files define different environments to typeset
the listing (including two TeX files with "content" as needed): a
"normal" listing (for A4/Letter, F<*_text.tex>), two flavors of a

examples/typeset_audio_dir  view on Meta::CPAN

=item B<-n>

Enable emit track number.  Environment variable TYPESET_AUDIO_TRACK
may contain the format to interpolate for typesetting (defaults to
C<%{mA}%{n1}>).  For example, set TYPESET_AUDIO_TRACK to C<%{n1}>
to use "pure" track number instead of combination of media/disk number
and track number.

=item B<-1>

Toplevel header format; is interpolate()d by L<MP3::Tag> based on
the content of the first audio file encountered during scan of this
toplevel directory.  The empty value is the default; in this case the
header is based on the name of the directory (with some normalization:
underscore is converted to space).

=item B<-2>

Second-level heading format; is interpolate()d by L<MP3::Tag>.
Calculated based on the content of each audio file.  The heading is
emited when the interpolated value changes (subject to option L<B<-a>>).

Empty string disables generation.

=item B<-a>

Ignore changes to the second-level heading for directories deeper than
this inside top-level directory.  Defaults to 2.  For example, in

examples/typeset_audio_dir  view on Meta::CPAN


The title-cutoff depth (w.r.t. toplevel directory).  Defaults to 2.
In audio files deeper than this the album C<%l> is used as the name;
otherwise the title C<%t> of the audio file is used.

Set to C<-1e100> to always use C<%l>, and to C<1e100> to always use C<%a>.

=item B<-@>

Replace all C<@> by C<%> in options.  Very useful with DOSISH shells
to include C<%>-escapes necessary for L<MP3::Tag>'s interpolate().

=item B<-e ENCODINGS>

Sets encodings for output files, directory names (when uses to generate
headings), and hint files.  B<ENCODINGS> is a comma-separated list of
directives; each directive is either an encoding name (to use for all targets),
or C<TARGET_LETTERS:encoding>.  Target letters are C<o>, C<d>, and C<h>
for output, names of directories, and files F<.top_heading> correspondingly.
Use 0 instead of an encoding to do byte-oriented read/write.

lib/MP3/Tag.pm  view on Meta::CPAN


package MP3::Tag;

# Copyright (c) 2000-2004 Thomas Geffert.  All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Artistic License, distributed
# with Perl.

################
#
# provides a general interface for different modules, which can read tags
#
# at the moment MP3::Tag works with MP3::Tag::ID3v1 and MP3::Tag::ID3v2

use strict;

{
  package MP3::Tag::__hasparent;
  sub parent_ok {
    my $self = shift;
    $self->{parent} and $self->{parent}->proxy_ok;
  }
  sub get_config {
    my $self = shift;
    return $MP3::Tag::config{shift()} unless $self->parent_ok;
    return $self->{parent}->get_config(@_);
  }
  *get_config1 = \&MP3::Tag::Implemenation::get_config1;
  *get_config1 = 0 if 0;	# quiet a warning
}

use MP3::Tag::ID3v1;
use MP3::Tag::ID3v2;
use MP3::Tag::File;
use MP3::Tag::Inf;
use MP3::Tag::CDDB_File;
use MP3::Tag::Cue;
use MP3::Tag::ParseData;
use MP3::Tag::ImageSize;
use MP3::Tag::ImageExifTool;
use MP3::Tag::LastResort;

use vars qw/$VERSION @ISA/;
$VERSION="1.16";
@ISA = qw( MP3::Tag::User MP3::Tag::Site MP3::Tag::Vendor
	   MP3::Tag::Implemenation ); # Make overridable
*config = \%MP3::Tag::Implemenation::config;

package MP3::Tag::Implemenation;	# XXXX Old mispring...
use vars qw/%config/;
%config = ( autoinfo			  => [qw( ParseData ID3v2 ID3v1 ImageExifTool
						 CDDB_File Inf Cue ImageSize
						 filename LastResort )],
	    cddb_files			  => [qw(audio.cddb cddb.out cddb.in)],
	    v2title			  => [qw(TIT1 TIT2 TIT3)],
	    composer			  => ['TCOM|a'],
	    performer			  => ['TXXX[TPE1]|TPE1|a'],
	    extension			  => ['\.(?!\d+\b)\w{1,4}$'],
	    parse_data			  => [],

lib/MP3/Tag.pm  view on Meta::CPAN

  $e{eV1} = $ENV{MP3TAG_ENCODE_DEFAULT}	  unless defined $e{eV1};
  $e{eV1} = $e{V1}			  unless defined $e{eV1};
  $config{encode_encoding_v1} = [$e{eV1}] if $e{eV1};

  $e{eF} = $ENV{MP3TAG_ENCODE_FILES_DEFAULT} if defined $ENV{MP3TAG_ENCODE_FILES_DEFAULT};
  $e{eF} = $ENV{MP3TAG_ENCODE_DEFAULT}	  unless defined $e{eF};
  $e{eF} = $e{FILES}			  if not defined $e{eF} and
					     (defined $ENV{"MP3TAG_DECODE_FILES_DEFAULT"} or defined $ENV{MP3TAG_DECODE_DEFAULT});
  $config{encode_encoding_files} = [$e{eF}] if $e{eF};
}
MP3::Tag->reset_encode_decode_config();

=pod

=head1 NAME

MP3::Tag - Module for reading tags of MP3 audio files

=head1 SYNOPSIS

  use MP3::Tag;

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

  # get some information about the file in the easiest way
  ($title, $track, $artist, $album, $comment, $year, $genre) = $mp3->autoinfo();
  # Or:
  $comment = $mp3->comment();
  $dedicated_to
    = $mp3->select_id3v2_frame_by_descr('COMM(fre,fra,eng,#0)[dedicated to]');

  $mp3->title_set('New title');		# Edit in-memory copy
  $mp3->select_id3v2_frame_by_descr('TALB', 'New album name'); # Edit in memory

lib/MP3/Tag.pm  view on Meta::CPAN

to most features of this module via command-line options; see
L<mp3info2>.

=head1 AUTHORS

Thomas Geffert, thg@users.sourceforge.net
Ilya Zakharevich, ilyaz@cpan.org

=head1 DESCRIPTION

C<MP3::Tag> is a wrapper module to read different tags of mp3 files.
It provides an easy way to access the functions of separate modules which
do the handling of reading/writing the tags itself.

At the moment MP3::Tag::ID3v1 and MP3::Tag::ID3v2 are supported for
read and write; MP3::Tag::ImageExifTool, MP3::Tag::Inf, MP3::Tag::CDDB_File,
MP3::Tag::File,  MP3::Tag::Cue, MP3::Tag::ImageSize, MP3::Tag::LastResort
are supported for read access (the information obtained by
L<Image::ExifTool|Image::ExifTool> (if present), parsing CDDB files,
F<.inf> file, the filename, and F<.cue> file, and obtained via
L<Image::Size|Image::Size>) (if present).

=over 4

=item new()

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

Creates a mp3-object, which can be used to retrieve/set
different tags.

=cut

sub rel2abs ($) {
  shift;
  if (eval {require File::Spec; File::Spec->can('rel2abs')}) {
    File::Spec->rel2abs(shift);

lib/MP3/Tag.pm  view on Meta::CPAN

    shift;
  }
}

sub new {
    my $class = shift;
    my $filename = shift;
    my $mp3data;
    my $self = {};
    bless $self, $class;
    my $proxy = MP3::Tag::__proxy->new($self);
    if (-f $filename or -c $filename) {
	$mp3data = MP3::Tag::File->new_with_parent($filename, $proxy);
    }
    # later it should hopefully possible to support also http/ftp sources
    # with a MP3::Tag::Net module or something like that
    if ($mp3data) {
	%$self = (filename	=> $mp3data,
		  ofilename	=> $filename,
		  abs_filename	=> $class->rel2abs($filename),
		  __proxy	=> $proxy);
	return $self;
    }
    return undef;
}

{ # Proxy class: to have only one place where to weaken/localize the reference
  # $obj->[0] must be settable to the handle (not needed if weakening succeeds)
  package MP3::Tag::__proxy;
  use vars qw/$AUTOLOAD/;

  my $skip_weaken = $ENV{MP3TAG_SKIP_WEAKEN};
  sub new {
    my ($class, $handle) = (shift,shift);
    my $self = bless [$handle], $class;
    #warn("weaken() failed, falling back"),
    return bless [], $class if $skip_weaken or not
      eval {require Scalar::Util; Scalar::Util::weaken($self->[0]); 1};
    $self;

lib/MP3/Tag.pm  view on Meta::CPAN

  @tags = $mp3->get_tags;

Checks which tags can be found in the mp3-object. It returns
a list @tags which contains strings identifying the found tags, like
"ID3v1", "ID3v2", "Inf", or "CDDB_File" (the last but one if the F<.inf>
information file with the same basename as MP3 file is found).

Each found tag can then be accessed with $mp3->{tagname} , where tagname is
a string returned by get_tags ;

Use the information found in L<MP3::Tag::ID3v1>, L<MP3::Tag::ID3v2> and
L<MP3::Tag::Inf>, L<MP3::Tag::CDDB_File>, L<MP3::Tag::Cue> to see what you can do with the tags.

=cut 

################ tag subs

sub get_tags {
    my $self = shift;
    return @{$self->{gottags}} if exists $self->{gottags};
    my (@IDs, $id);

    # Will not create a reference loop
    local $self->{__proxy}[0] = $self unless $self->{__proxy}[0] or $ENV{MP3TAG_TEST_WEAKEN};
    for $id (qw(ParseData ID3v2 ID3v1 ImageExifTool Inf CDDB_File Cue ImageSize LastResort)) {
	my $ref = "MP3::Tag::$id"->new_with_parent($self->{filename}, $self->{__proxy});
	next unless defined $ref;
	$self->{$id} = $ref;
	push @IDs, $id;
    }
    $self->{gottags} = [@IDs];
    return @IDs;
}

sub _get_tag {
    my $self = shift;
    $self->{shift()};
}

# keep old name for a while
*getTags = \&get_tags;
*getTags = 0 if 0;	# quiet a warning

=item new_fake

  $obj = MP3::Tag->new_fake();

This method produces a "fake" MP3::Tag object which behaves as an MP3
file without tags.  Give a TRUE optional argument if you want to set
some properties of this object.

=cut

sub new_fake {
    my ($class, $settable) = (shift, shift);
    my %h = (gottags => []);
    my $self = bless \%h, $class;
    if ($settable) {
      $h{__proxy} = MP3::Tag::__proxy->new($self);
      $h{ParseData} = MP3::Tag::ParseData->new_with_parent(undef, $h{__proxy});
    }
    \%h;
}


=pod

=item new_tag()

  [old name: newTag() . The old name is still available, but its use is not advised]

lib/MP3/Tag.pm  view on Meta::CPAN

moment ID3v1 and ID3v2 are supported as tagname.

Returns an tag-object: $mp3->{$tagname}.

=cut

sub new_tag {
    my $self = shift;
    my $whichTag = shift;
    if ($whichTag =~ /1/) {
	$self->{ID3v1}= MP3::Tag::ID3v1->new($self->{filename},1);
	return $self->{ID3v1};
    } elsif ($whichTag =~ /2/) {
	$self->{ID3v2}= MP3::Tag::ID3v2->new($self->{filename},1);
	return $self->{ID3v2};
    }
}

# keep old name for a while
*newTag = \&new_tag;
*newTag = 0 if 0;	# quiet a warning

#only as a shortcut to {filename}->close to explicitly close a file

lib/MP3/Tag.pm  view on Meta::CPAN


=item genres()

  $allgenres = $mp3->genres;
  $genreName = $mp3->genres($genreID);
  $genreID   = $mp3->genres($genreName);

Returns a list of all genres (reference to an array), or the according 
name or id to a given id or name.

This function is only a shortcut to MP3::Tag::ID3v1->genres.

This can be also called as MP3::Tag->genres;

=cut

sub genres {
  # returns all genres, or if a parameter is given, the according genre
  my $self=shift;
  return MP3::Tag::ID3v1::genres(shift);
}

=pod

=item autoinfo()

  ($title, $track, $artist, $album, $comment, $year, $genre) = $mp3->autoinfo();
  $info_hashref = $mp3->autoinfo();

autoinfo() returns information about the title, track number,

lib/MP3/Tag.pm  view on Meta::CPAN


    $self->get_tags;

    # ID3v1 has AUTOLOAD
#    my $do_can = ($what =~ /^(cd\w+_id|height|width|bit_depth|mime_type|img_type|_duration)$/);
    foreach my $pack (@$packs) {
	next unless exists $self->{$pack};
	my $do_can = $pack ne 'ID3v1';
	my $out;
	for my $what (@what) {
	  next if $pack eq 'ID3v1' and not $MP3::Tag::ID3v1::ok_length{$what};	# dup of a warning in AUTOLOAD
          next if $do_can and not $self->{$pack}->can($what);
          if ($check_only and $self->{$pack}->can(my $m = $what . '_have')) {
            next unless $self->{$pack}->$m(@$args);
            return $ret_from ? [1, $pack] : 1;
          }
          next unless defined ($out = $self->{$pack}->$what(@$args));
          # Ignore 0-length answers from ID3v1, ImageExifTool, CDDB_File, Cue, ImageSize, and Inf
          undef $out, next if not length $out and $ignore_0length{$pack}; # These return ''
	}
	next unless defined $out;

lib/MP3/Tag.pm  view on Meta::CPAN

    my $translate = ($self->get_config("translate_$elt")
		     || $self->get_config("translate_person")
		     || [])->[0] || sub {$_[1]};
    my $fields = ($self->get_config($elt))->[0];
    return &$translate($self, $self->interpolate("%{$fields}"));
  }
}

=item config

  MP3::Tag->config(item => value1, value2...);	# Set options globally
  $mp3->config(item => value1, value2...);	# Set object options

When object options are first time set or get, the global options are
propagated into object options.  (So if global options are changed later, these
changes are not inherited.)

Possible items are:

=over

lib/MP3/Tag.pm  view on Meta::CPAN

string to put into C<%{}> to interpolate to get the composer.  Default
is C<'TCOM|a'>.

=item performer

string to put into C<%{}> to interpolate to get the main performer.
Default is C<'TXXX[TPE1]|TPE1|a'>.

=item parse_data

the data used by L<MP3::Tag::ParseData> handler; each option is an array
reference of the form C<[$flag, $string, $pattern1, ...]>.  All the options
are processed in the following way: patterns are matched against $string
until one of them succeeds; the information obtained from later options takes
precedence over the information obtained from earlier ones.

=item  parse_split

The regular expression to split the data when parsing with C<n> or C<l> flags.

=item  parse_filename_ignore_case

lib/MP3/Tag.pm  view on Meta::CPAN

If TRUE, when writing ID3v2 tag, create a C<TLEN> tag if the duration
is known (as it is after calling methods like C<total_secs>, or
interpolation the duration value).  If this field is 2 or more, force
creation of ID3v2 tag by update_tags() if the duration is known.

=item  translate_*

FALSE, or a subroutine used to munch a field C<*> (out of C<title
track artist album comment year genre comment_collection comment_track
title_track artist_collection person>) to some "normalized" form.
Takes two arguments: the MP3::Tag object, and the current value of the
field.

The second argument may also have the form C<[value, handler]>, where
C<handler> is the string indentifying the handler which returned the
value.

=item short_person

Similar to C<translate_person>, but the intent is for this subroutine
to translate a personal name field to a shortest "normalized" form.

lib/MP3/Tag.pm  view on Meta::CPAN

supported; allow on your own risk).

=item name_for_field_normalization

interpolation of this string is used as a person name to normalize
title-like fields.  Defaults to C<%{composer}>.

=item extra_config_keys

List of extra config keys (default is empty); setting these would not cause
warnings, and would not affect operation of C<MP3::Tag>.  Applications using
this module may add to this list to allow their configuration by the same
means as configuration of C<MP3::Tag>.

=item is_writable

Contains a boolean value, or a method name and argument list
to call whether the tag may be added to the file.  Defaults to
writable_by_extension().

=item writable_extensions

Contains a list of extensions (case insensitive) for which the tag may be

lib/MP3/Tag.pm  view on Meta::CPAN

				     comment_track title_track
				     composer performer
				     artist_collection person );
    my $e_known = $self->get_config('extra_config_keys');
    $e_known = [map lc, @$e_known];
    $conf_rex = '^(' . join('|', @known, @$e_known, @tr) . ')$' unless $conf_rex;

    if ($item =~ /^(force)$/) {
	return $config->{$item} = {@options};
    } elsif ($item !~ $conf_rex) {
	warn "MP3::Tag::config(): Unknown option '$item' found; known options: @known @$e_known @tr\n REX = <<<$conf_rex>>>\n";
	return;
    }
    undef $conf_rex if $item eq 'extra_config_keys';

    $config->{$item} = \@options;
}

=item get_config

  $opt_array = $mp3->get_config("item");

lib/MP3/Tag.pm  view on Meta::CPAN

    $self->{userdata} ||= [];
    $self->{userdata}[$item] = $val;
}

=item set_id3v2_frame

  $mp3->set_id3v2_frame($name, @values);

When called with only $name as the argument, removes the specified
frame (if it existed).  Otherwise sets the frame passing the specified
@values to the add_frame() function of MP3::Tag::ID3v2.  (The old value is
removed.)

=cut

# With two elements, removes frame
sub set_id3v2_frame ($$;@) {
    my ($self, $item) = (shift, shift);
    $self->get_tags;
    return if not @_ and not exists $self->{ID3v2};
    $self->new_tag("ID3v2") unless exists $self->{ID3v2};

lib/MP3/Tag.pm  view on Meta::CPAN

      if defined $self->{ID3v2}->get_frame($item);
    return unless @_;
    return $self->{ID3v2}->add_frame($item, @_);
}

=item get_id3v2_frames

  ($descr, @frames) = $mp3->get_id3v2_frames($fname);

Returns the specified frame(s); has the same API as
L<MP3::Tag::ID3v2::get_frames>, but also returns undef if no ID3v2
tag is present.

=cut

sub get_id3v2_frames ($$;$) {
    my ($self) = (shift);
    $self->get_tags;
    return if not exists $self->{ID3v2};
    $self->{ID3v2}->get_frames(@_);
}

lib/MP3/Tag.pm  view on Meta::CPAN

    my ($self) = (shift);
    return if not exists $self->{ID3v2};
    $self->{ID3v2}->is_modified();
}

=item select_id3v2_frame

  $frame = $mp3->select_id3v2_frame($fname, $descrs, $langs [, $VALUE]);

Returns the specified frame(s); has the same API as
L<MP3::Tag::ID3v2/frame_select> (args are the frame name, the list of
wanted Descriptors, list of wanted Languages, and possibly the new
contents - with C<undef> meaning deletion).  For read-only access it
returns empty if no ID3v2 tag is present, or no frame is found.

If new contents is specified, B<ALL> the existing frames matching the
specification are deleted.

=item have_id3v2_frame

  $have_it = $mp3->have_id3v2_frame($fname, $descrs, $langs);

Returns TRUE the specified frame(s) exist; has the same API as
L<MP3::Tag::ID3v2::frame_have> (args are frame name, list of wanted
Descriptors, list of wanted Languages).

=item get_id3v2_frame_ids

  $h = $mp3->get_id3v2_frame_ids();
  print "  $_ => $h{$_}" for keys %$h;

Returns a hash reference with the short names of ID3v2 frames present
in the tag as keys (and long description of the meaning as values), or
FALSE if no ID3v2 tag is present.  See
L<MP3::Tags::ID3v2::get_frame_ids> for details.

=item id3v2_frame_descriptors

Returns the list of human-readable "long names" of frames (such as
C<COMM(eng)[lyricist birthdate]>), appropriate for interpolation, or
for select_id3v2_frame_by_descr().

=item select_id3v2_frame_by_descr

=item have_id3v2_frame_by_descr

Similar to select_id3v2_frame(), have_id3v2_frame(), but instead of
arguments $fname, $descrs, $langs take one string of the form

  NAME(langs)[descr]

Both C<(langs)> and C<[descr]> parts may be omitted; langs should
contain comma-separated list of needed languages.  The semantic is
similar to
L<MP3::Tag::ID3v2::frame_select_by_descr_simpler|MP3::Tag::ID3v2/frame_select_by_descr_simpler>.

It is allowed to have C<NAME> of the form C<FRAMnn>; C<nn>-th frame
with name C<FRAM> is chosen (C<-1>-based: the first frame is C<FRAM>,
the second C<FRAM00>, the third C<FRAM01> etc; for more user-friendly
scheme, use C<langs> of the form C<#NNN>, with C<NNN> 0-based; see
L<MP3::Tag::ID3v2/"get_frame_ids()">).

  $frame = $mp3->select_id3v2_frame_by_descr($descr [, $VALUE1, ...]);
  $have_it = $mp3->have_id3v2_frame_by_descr($descr);

select_id3v2_frame_by_descr() will also apply the normalizer in config
setting C<translate_person> if the frame name matches one of the
elements of the configuration setting C<person_frames>.

  $c = $mp3->select_id3v2_frame_by_descr("COMM(fre,fra,eng,#0)[]");
  $t = $mp3->select_id3v2_frame_by_descr("TIT2");
       $mp3->select_id3v2_frame_by_descr("TIT2", "MyT"); # Set/Change
       $mp3->select_id3v2_frame_by_descr("RBUF", $n1, $n2, $n3); # Set/Change
       $mp3->select_id3v2_frame_by_descr("RBUF", "$n1;$n2;$n3"); # Set/Change
       $mp3->select_id3v2_frame_by_descr("TIT2", undef); # Remove

Remember that when select_id3v2_frame_by_descr() is used for
modification, B<ALL> found frames are deleted before a new one is
added.  For gory details, see L<MP3::Tag::ID3v2/frame_select>.

=item frame_translate

  $mp3->frame_translate('TCOM'); # Normalize TCOM ID3v2 frame

assuming that the frame value denotes a person, normalizes the value
using personal name normalization logic (via C<translate_person>
configuration value).  Frame is updated, but the tag is not written
back.  The frame must be in the list of personal names frames
(C<person_frames> configuration value).

lib/MP3/Tag.pm  view on Meta::CPAN


sub id3v2_frame_descriptors ($) {
    my ($self) = (shift);
    $self->get_tags;
    return if not exists $self->{ID3v2};
    $self->{ID3v2}->get_frame_descriptors(@_);
}

=item copy_id3v2_frames($from, $to, $overwrite, [$keep_flags, $f_ids])

Copies specified frames between C<MP3::Tag> objects $from, $to.  Unless
$keep_flags, the copied frames have their flags cleared.
If the array reference $f_ids is not specified, all the frames (but C<GRID>
and C<TLEN>) are considered (subject to $overwrite), otherwise $f_ids should
contain short frame ids to consider. Group ID flag is always cleared.

If $overwrite is C<'delete'>, frames with the same descriptors (as
returned by get_frame_descr()) in $to are deleted first, then all the
specified frames are copied.  If $overwrite is FALSE, only frames with
descriptors not present in $to are copied.  (If one of these two
conditions is not met, the result may be not conformant to standards.)

lib/MP3/Tag.pm  view on Meta::CPAN

  $from->get_tags;
  return 0 unless $from = $from->{ID3v2}; # No need to create it...
  $f_ids ||= [keys %{$from->get_frame_ids}];
  return 0 unless @$f_ids;
  $to->get_tags;
  $to->new_tag("ID3v2") if not exists $to->{ID3v2};
  $from->copy_frames($to->{ID3v2}, $overwrite, $keep_flags, $f_ids);
}

sub _Data_to_MIME ($$;$) {
    goto &MP3::Tag::ID3v2::_Data_to_MIME
}

=item _Data_to_MIME

Internal method to extract MIME type from a string the image file content.
Returns C<application/octet-stream> for unrecognized data
(unless extra TRUE argument is given).

  $format = $id3->_Data_to_MIME($data);

lib/MP3/Tag.pm  view on Meta::CPAN

together with the value of configuration variable C<ampersand_joiner>
(default C<"; ">).  Example:

  %{TXXX[pre-title]&TIT1&TIT2&TIT3&TXXX[post-title]}


=item *

Strings of the form C<method(list,of,packages)[arg1][arg2]> are replaced
by the result of C<method> (with the given arguments) in one of the specified
known subpackages (e.g., for C<Inf>,  C<MP3::Tag::Inf> is used).  Arbitrary number
of arguments is supported.  Instead of a long name C<method> one can use its
standard shortcut (e.g., C<t> for C<title>).  For example,

  $mp3->interpolate('%{t(ID3v1,Cue)}')

returns the title from the ID3v1 tag, or (if not there) from a cue sheet.
One can use this in conditionals etc as well.

=item *

lib/MP3/Tag.pm  view on Meta::CPAN


  $res = $mp3->parse_rex( qr<^%a - %t\.\w{1,4}$>,
			  $mp3->filename_nodir ) or die;
  $author = $res->{author};

2-digit numbers, or I<number1/number2> with number1,2 up to 999 are
allowed for the track number (the leading 0 is stripped); 4-digit
years in the range 1000..2999 are allowed for year.  Alternatively, if
option year_is_timestamp is TRUE (default), year may be a range of
timestamps in the format understood by ID3v2 method year() (see
L<MP3::Tag::ID3v2/"year">).

The escape C<%E> matches the REx in the configuration variable C<extension>;
the escape C<%e> matches the part of %E after the leading dot.

In list context, also returns an array reference with %{handler} groups
parsed (if present).  Such groups can match everything, and a successful match gives an
array element with C<[$method, $packages, $args, $matched]>.

Currently the regular expressions with capturing parens are not supported.

lib/MP3/Tag.pm  view on Meta::CPAN


sub can_write_or_die ($$) {
    my($self, $what) = (shift, shift);
    my $wr = $self->can_write;
    return $wr if $wr;
    $self->die_cant_write($what);
}

=item update_tags( [ $data,  [ $force2 ]] )

  $mp3 = MP3::Tag->new($filename);
  $mp3->update_tags();			# Fetches the info, and updates tags

  $mp3->update_tags({});		# Updates tags if needed/changed

  $mp3->update_tags({title => 'This is not a song'});	# Updates tags

This method updates ID3v1 and ID3v2 tags (the latter only if in-memory copy
contains any data, or $data does not fit ID3v1 restrictions, or $force2
argument is given)
with the the information about title, artist, album, year, comment, track,

lib/MP3/Tag.pm  view on Meta::CPAN

    }				# Skip what is already there...
    # $mp3->{ID3v2}->comment($data->{comment}->[0]);

    $mp3->set_id3v2_frame('TLEN', $mp3->{ms})
      if $do_length and not $mp3->have_id3v2_frame('TLEN');
    $mp3->{ID3v2}->write_tag;
    return $mp3;
}

sub _massage_genres ($;$) {   # Thanks to neil verplank for the prototype
    require MP3::Tag::ID3v1;
    my($data, $how) = (shift, shift);
    my $firstnum = (($how || 0) eq 'num');
    my $prefer_num = (($how || 0) eq 'prefer_num');
    my (%seen, @genres);	# find all genres in incoming data
    $data = $data->[0] if ref $data;
    # clean and split line on both null and parentheses
    $data =~ s/\s+/ /g;
    $data =~ s/\s*\0[\0\s]*/\0/g;
    $data =~ s/^[\s\0]+//;
    $data =~ s/[\s\0]+$//;
    my @data = split m<\0|\s+/\s+>, $data;
    @data = split /\( ( \d+ | rx | cr ) \)/xi, $data[0] if @data == 1;

    # review array, produce a clean, ordered list of unique genres for output
    foreach my $genre (@data) {
        next if $genre eq "";  # (12)(13) ==> in front, and between

        # convert text to number to eliminate collisions, and produce consistent output
        if ($genre =~ /\D/) {{	# Not a pure number
            # return id number
            my $genre_num = MP3::Tag::ID3v1::genres($genre); 
            # 255 is "non-standard text" in ID3v1; pass the rest through
            last if $genre_num eq '255' or $genre_num eq '';
	    return $genre_num if $firstnum;
	    $genre = $genre_num, last if $prefer_num;
	    $genre_num = MP3::Tag::ID3v1::genres($genre_num);
            last unless defined $genre_num;
            $genre = $genre_num;
        }}	# Now converted to a number - if possible
        unless ($prefer_num or $genre =~ /\D/) {{ # Here $genre is a number
	    my $genre_str = MP3::Tag::ID3v1::genres($genre) or last;
	    return $genre if $firstnum;
            $genre = $genre_str;
        }}
        # 2.4 defines these conversions
        $genre = "Remix" if lc $genre eq "rx";
        $genre = "Cover" if lc $genre eq "cr";
        $genre = "($genre)" if length $genre and not $genre =~ /\D/;	# Only digits
        push @genres, $genre unless $seen{$genre}++;
    }
    return if $firstnum;

lib/MP3/Tag.pm  view on Meta::CPAN

    $self->parse_cfg_line($l, $data);
  }
  CORE::close F or die "Can't close `$file' for read: $!";
  for my $k (keys %$data) {
    $self->config($k, @{$data->{$k}});
  }
}

my @parents = qw(User Site Vendor);

@MP3::Tag::User::ISA = qw( MP3::Tag::Site MP3::Tag::Vendor
			   MP3::Tag::Implemenation ); # Make overridable
@MP3::Tag::Site::ISA = qw( MP3::Tag::Vendor MP3::Tag::Implemenation );
@MP3::Tag::Vendor::ISA = qw( MP3::Tag::Implemenation );

sub load_parents {
  my $par;
  while ($par = shift @parents) {
    return 1 if eval "require MP3::Tag::$par; 1"
  }
  return;
}
load_parents() unless $ENV{MP3TAG_SKIP_LOCAL};
MP3::Tag->parse_cfg() unless $ENV{MP3TAG_SKIP_LOCAL};

1;

=pod

=head1 ENVIRONMENT

Some defaults for the operation of this module (and/or scripts distributed
with this module) are set from
environment.  Assumed encodings (0 or encoding name): for read access:

lib/MP3/Tag.pm  view on Meta::CPAN

(if the second one is not set, the value of the first one is used).
Value 0 for more specific variable will cancel the effect of the less
specific variables.

If the C<LANG> environment variable indicates C<UTF-8> encoding, then
the "C<FILES>" flavors default to C<utf8> (unless this effect is already
achieved by the C<${^UNICODE}> variable).  This may be disabled by setting
C<MP3TAG_DECODE_FILES_DEFAULT_RESET> true in the environment (likewise for
C<EN>-code flavor).

These variables set default configuration settings for C<MP3::Tag>;
the values are read during the load time of the module.  After load,
one can use config()/get_config() methods to change/access these
settings.  See C<encode_encoding_*> and C<encode_decoding_*> in
documentation of L<config|config> method.  (Note that C<FILES> variant
govern file read/written in non-binary mode by L<MP3/ParseData> module,
as well as reading of control files of some scripts using this module, such as
L<typeset_audio_dir>.)

=over

lib/MP3/Tag.pm  view on Meta::CPAN


Many aspects of operation of this module are subject to certain subtle
choices.  A lot of effort went into making these choices customizable,
by setting global or per-object configuration variables.

A certain degree of customization of global configuration variables is
available via the environment variables.  Moreover, at startup the local
customization file F<~/.mp3tagprc> is read, and defaults are set accordingly.

In addition, to make customization as flexible as possible, I<ALL> aspects
of operation of C<MP3::Tag> are subject to local override.  Three customization
modules

  MP3::Tag::User	MP3::Tag::Site		MP3::Tag::Vendor

are attempted to be loaded if present.  Only the first module (of
those present) is loaded directly; if sequential load is desirable,
the first thing a customization module should do is to call

  MP3::Tag->load_parents()

method.

The customization modules have an opportunity to change global
configuration variables on load.  To allow more flexibility, they may
override any method defined in C<MP3::Tag>; as usual, the overriden
method may be called using C<SUPER> modifier (see L<perlobj/"Method
invocation">).

E.g., it is recommended to make a local customization file with

  eval 'require Normalize::Text::Music_Fields';
  for my $elt ( qw( title track artist album comment year genre
		    title_track artist_collection person ) ) {
    no strict 'refs';
    MP3::Tag->config("translate_$elt", \&{"Normalize::Text::Music_Fields::normalize_$elt"})
      if defined &{"Normalize::Text::Music_Fields::normalize_$elt"};
  }
  MP3::Tag->config("short_person", \&Normalize::Text::Music_Fields::short_person)
      if defined &Normalize::Text::Music_Fields::short_person;

and install the (supplied, in the F<examples/modules>) module
L<Normalize::Text::Music_Fields> which enables normalization of person
names (to a long or a short form), and of music piece names to
canonical forms.

To simplify debugging of local customization, it may be switched off
completely by setting MP3TAG_SKIP_LOCAL to TRUE (in environment).

lib/MP3/Tag.pm  view on Meta::CPAN

version of the database record.  Can be used to update a file, and/or to
convert a fake CDDB file to a real one.

=back

(Last four do not use these modules!)

Some more examples:

  # Convert from one (non-standard-conforming!) encoding to another
  perl -MMP3::Tag -MEncode -wle '
    my @fields = qw(artist album title comment);
    for my $f (@ARGV) {
      print $f;
      my $t = MP3::Tag->new($f) or die;
      $t->update_tags(
	{ map { $_ => encode "cp1251", decode "koi8-r", $t->$_() }, @fields }
      );
    }' list_of_audio_files

=head1 Problems with ID3 format

The largest problem with ID3 format is that the first versions of these
format were absolutely broken (underspecified).  It I<looks> like the newer
versions of this format resolved most of these problems; however, in reality

lib/MP3/Tag.pm  view on Meta::CPAN

=head1 FILES

There are many files with special meaning to this module and its dependent
modules.

=over 4

=item F<*.inf>

Files with extension F<.inf> and the same basename as the audio file are
read by module C<MP3::Tag::Inf>, and the extracted data is merged into the
information flow according to configuration variable C<autoinfo>.

It is assumed that these files are compatible in format to the files written
by the program F<cdda2wav>.

=item F<audio.cddb> F<cddb.out> F<cddb.in>

in the same directory as the audio file are read by module
C<MP3::Tag::CDDB_File>, and the extracted data is merged into the
information flow according to configuration variable C<autoinfo>.

(In fact, the list may be customized by configuration variable C<cddb_files>.)

=item F<audio_cd.toc>

in the same directory as the audio file may be read by the method
id3v2_frames_autofill() (should be called explicitly) to fill the C<TXXX[MCDI-fulltoc]>
frame.  Depends on contents of configuration variable C<id3v2_frames_autofill>.

=item F<~/.mp3tagprc>

By default, this file is read on startup (may be customized by overriding
the method parse_cfg()).  By default, the name of the file is in the
configuration variable C<local_cfg_file>.

=back

=head1 SEE ALSO

L<MP3::Tag::ID3v1>, L<MP3::Tag::ID3v2>, L<MP3::Tag::File>,
L<MP3::Tag::ParseData>, L<MP3::Tag::Inf>, L<MP3::Tag::CDDB_File>,
L<MP3::Tag::Cue>, L<MP3::Tag::ImageExifTool>, L<MP3::Tag::ImageSize>,
L<MP3::Tag::LastResort>, L<mp3info2>, L<typeset_audio_dir>.

=head1 COPYRIGHT

Copyright (c) 2000-2016 Thomas Geffert, Ilya Zakharevich.  All rights reserved.

This program is free software; you can redistribute it and/or
modify it under the terms of the Artistic License, distributed
with Perl.

=cut

lib/MP3/Tag/CDDB_File.pm  view on Meta::CPAN

package MP3::Tag::CDDB_File;

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

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

=pod

=head1 NAME

MP3::Tag::CDDB_File - Module for parsing CDDB files.

=head1 SYNOPSIS

  my $db = MP3::Tag::CDDB_File->new($filename, $track);	# Name of audio file
  my $db = MP3::Tag::CDDB_File->new_from($record, $track); # Contents of CDDB 

  ($title, $artist, $album, $year, $comment, $track) = $db->parse();

see L<MP3::Tag>

=head1 DESCRIPTION

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

It parses the content of CDDB file.

The file is found in the same directory as audio file; the list of possible
file names is taken from the field C<cddb_files> if set by MP3::Tag config()
method.

=over 4

=cut


# Constructor

sub new_from {

lib/MP3/Tag/CDDB_File.pm  view on Meta::CPAN

	     and $c1 ne substr $c2, 0, length $c1
	     and $c1 ne substr $c2, -length $c1 ) {
	    $c2 =~ s/\s*[.,:;]$//;
	    my $sep = (("$c1$c2" =~ /\n/) ? "\n" : '; ');
	    $c1 = "$c2$sep$c1";
	} else {
	    $c1 = $c2;
	}
    }
    if (defined $cat and $cat =~ /^\d+$/) {
	require MP3::Tag::ID3v1;
	$cat = $MP3::Tag::ID3v1::winamp_genres[$cat] if $cat < scalar @MP3::Tag::ID3v1::winamp_genres;
    }

    @parsed{ qw( title artist album year comment track genre
		 a_in_title t_in_track extt extd) } =
	($tt, $aa, $t, $y, $c1, $track, $cat, $a_in_title, $t_in_track, $cc2, $cc1);
    $parsed{DISCID} = $self->{fields}{DISCID};
    $self->{parsed} = \%parsed;
    $self->return_parsed($what);
}

lib/MP3/Tag/Cue.pm  view on Meta::CPAN

package MP3::Tag::Cue;

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

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

=pod

=head1 NAME

MP3::Tag::Cue - Module for parsing F<.cue> files.

=head1 SYNOPSIS

  my $db = MP3::Tag::Cue->new($filename, $track);	# Name of audio file
  my $db = MP3::Tag::Cue->new_from($record, $track); # Contents of .cue file

  ($title, $artist, $album, $year, $comment, $track) = $db->parse();

see L<MP3::Tag>

=head1 DESCRIPTION

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

It parses the content of a F<.cue> file.

The F<.cue> file is looked for in the same directory as audio file; one of the
following conditions must be satisfied:

=over 4

=item *

lib/MP3/Tag/Cue.pm  view on Meta::CPAN

  @cue = keys %seen;
  my $c = @cue;
  @cue = grep matches($_, $f, 0), @cue if @cue > 1;
  @cue = grep matches($_, $f, 1), @cue if @cue > 1;
  ($c, @cue)
}

sub new_with_parent {
    my ($class, $f, $p, $e, %seen, @cue) = (shift, shift, shift);
    $f = $f->filename if ref $f;
    $f = MP3::Tag->rel2abs($f);
    if ($f =~ /\.cue$/i and -f $f) {
      @cue = $f;
    } else {
      my $d = dirname($f);
      (my $c, @cue) = find_cue($f, $d);
      unless ($c) {
	my $d1 = dirname($d);
        (my $c, @cue) = find_cue($d, $d1);
      }
    }
    return unless @cue == 1;
    local *F;
    open F, "< $cue[0]" or die "Can't open `$cue[0]': $!";
    if ($e = ($p or 'MP3::Tag')->get_config1('decode_encoding_cue_file')) {
      eval "binmode F, ':encoding($e->[0])'"; # old binmode won't compile...
    }
    my @data = <F>;
    close F or die "Error closing `$cue[0]': $!";
    bless {filename => $cue[0], data => \@data, track => shift,
	   parent => $p}, $class;
}

sub new {
    my ($class, $f) = (shift, shift);

lib/MP3/Tag/File.pm  view on Meta::CPAN

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

lib/MP3/Tag/File.pm  view on Meta::CPAN

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

lib/MP3/Tag/ID3v1.pm  view on Meta::CPAN

package MP3::Tag::ID3v1;

# Copyright (c) 2000-2004 Thomas Geffert.  All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Artistic License, distributed
# with Perl.

use strict;
use vars qw /@mp3_genres @winamp_genres $AUTOLOAD %ok_length $VERSION @ISA/;

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

# allowed fields in ID3v1.1 and max length of this fields (except for track and genre which are coded later)
%ok_length = (title => 30, artist => 30, album => 30, comment => 28, track => 3, genre => 3000, year=>4, genreID=>1); 

=pod

=head1 NAME

MP3::Tag::ID3v1 - Module for reading / writing ID3v1 tags of MP3 audio files

=head1 SYNOPSIS

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

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

  # read an existing tag
  $mp3->get_tags();
  $id3v1 = $mp3->{ID3v1} if exists $mp3->{ID3v1};

  # or create a new tag
  $id3v1 = $mp3->new_tag("ID3v1");

See L<MP3::Tag|according documentation> for information on the above used functions.
  
* Reading the tag

    print "  Title: " .$id3v1->title . "\n";
    print " Artist: " .$id3v1->artist . "\n";
    print "  Album: " .$id3v1->album . "\n";
    print "Comment: " .$id3v1->comment . "\n";
    print "   Year: " .$id3v1->year . "\n";
    print "  Genre: " .$id3v1->genre . "\n";
    print "  Track: " .$id3v1->track . "\n";

lib/MP3/Tag/ID3v1.pm  view on Meta::CPAN

	$data = $data->[0] if ref $data;
	return if $data =~ /[^\x00-\xFF]/;
	$s .= $data;
	next if $ok_length{$elt} >= length $data;
	next
	  if $elt eq 'comment' and not $hash->{track} and length $data <= 30;
	return;
    }
    if (defined (my $genre = $hash->{genre})) {
	$genre = $genre->[0] if ref $genre;
        my @g = MP3::Tag::Implemenation::_massage_genres($genre);
	return if @g > 1;
	my $id = MP3::Tag::Implemenation::_massage_genres($genre, 'num');
	return if not defined $id or $id eq '' or $id == 255;
    }
    if ($s =~ /[^\x00-\x7E]/) {
      my $w = ($self->get_config('encode_encoding_v1') || [0])->[0];
      my $r = ($self->get_config('decode_encoding_v1') || [0])->[0];
      $_ = (lc or 'iso-8859-1') for $r, $w;
      # Safe: per-standard and read+write is idempotent:
      return 1 if $r eq $w and $w eq 'iso-8859-1';
      return !(($self->get_config('encoded_v1_fits')||[0])->[0])
	if $w eq 'iso-8859-1';	# read+write not idempotent

lib/MP3/Tag/ID3v1.pm  view on Meta::CPAN

  $genreID   = $id3v1->genres($genreName);  

Returns a list of all genres, or the according name or id to
a given id or name.

=cut

sub genres {
    # return an array with all genres, of if a parameter is given, the according genre
    my ($self, $genre) = @_;
    if ( (defined $self) and (not defined $genre) and ($self !~ /MP3::Tag/)) {
	## genres may be called directly via MP3::Tag::ID3v1::genres()
	## and $self is then not used for an id3v1 object
	$genre = $self;
    }

    return \@winamp_genres unless defined $genre;

    if ($genre =~ /^\d+$/) {
	return $winamp_genres[$genre] if $genre<scalar @winamp_genres;
	return undef;
    }

lib/MP3/Tag/ID3v1.pm  view on Meta::CPAN

	    last;
	}
	$id++;
    }
    $id=255 unless $found;
    return $id;
}

=item new()

  $id3v1 = MP3::Tag::ID3v1->new($mp3fileobj[, $create]);

Generally called from MP3::Tag, because a $mp3fileobj is needed.
If $create is true, a new tag is created. Otherwise undef is
returned, if now ID3v1 tag is found in the $mp3obj.

Please use

   $mp3 = MP3::Tag->new($filename);
   $id3v1 = $mp3->new_tag("ID3v1");	# Empty new tag

or

   $mp3 = MP3::Tag->new($filename);
   $mp3->get_tags();
   $id3v1 = $mp3->{ID3v1};		# Existing tag (if present)

instead of using this function directly

=back

=cut

# create a ID3v1 object

lib/MP3/Tag/ID3v1.pm  view on Meta::CPAN


# convert small integer id to genre name
sub id2genre {
    my $id=shift;
    return "" unless defined $id and $id < @winamp_genres;
    return $winamp_genres[$id];
}

# convert genre name to small integer id
sub genre2id {
    my $genre = MP3::Tag::Implemenation::_massage_genres(shift, 'num');
    return $genre if defined $genre;
    return 255;
}

# nothing to do for destroy
sub DESTROY {
}

1;

lib/MP3/Tag/ID3v1.pm  view on Meta::CPAN

			   'Contemporary Christian Music', 'Christian Rock',
			   'Merengue', 'Salsa', 'Thrash Metal', 'Anime',
			   'JPop', 'SynthPop',			# 149
			 ); 
}

=pod

=head1 SEE ALSO

L<MP3::Tag>, L<MP3::Tag::ID3v2>

ID3v1 standard - http://www.id3.org

=head1 COPYRIGHT

Copyright (c) 2000-2004 Thomas Geffert.  All rights reserved.

This program is free software; you can redistribute it and/or
modify it under the terms of the Artistic License, distributed
with Perl.

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

package MP3::Tag::ID3v2;

# Copyright (c) 2000-2004 Thomas Geffert.  All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Artistic License, distributed
# with Perl.

use strict;
use File::Basename;
# use Compress::Zlib;

use vars qw /%format %long_names %res_inp @supported_majors %v2names_to_v3
	     $VERSION @ISA %field_map %field_map_back %is_small_int
	     %back_splt %embedded_Descr
	    /;

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

my $trustencoding = $ENV{MP3TAG_DECODE_UNICODE};
$trustencoding = 1 unless defined $trustencoding;

my $decode_utf8 = $ENV{MP3TAG_DECODE_UTF8};
$decode_utf8 = 1 unless defined $decode_utf8;
my $encode_utf8 = $decode_utf8;

=pod

=head1 NAME

MP3::Tag::ID3v2 - Read / Write ID3v2.x.y tags from mp3 audio files

=head1 SYNOPSIS

MP3::Tag::ID3v2 supports
  * Reading of ID3v2.2.0 and ID3v2.3.0 tags (some ID3v2.4.0 frames too)
  * Writing of ID3v2.3.0 tags

MP3::Tag::ID3v2 is designed to be called from the MP3::Tag module.  If
you want to make calls from user code, please consider using
highest-level wrapper code in MP3::Tag, such as update_tags() and
select_id3v2_frame_by_descr().

Low-level creation code:

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

  # read an existing tag
  $mp3->get_tags();
  $id3v2 = $mp3->{ID3v2} if exists $mp3->{ID3v2};

  # or create a new tag
  $id3v2 = $mp3->new_tag("ID3v2");

See L<MP3::Tag|according documentation> for information on the above used functions.

* Reading a tag, very low-level:

  $frameIDs_hash = $id3v2->get_frame_ids('truename');

  foreach my $frame (keys %$frameIDs_hash) {
      my ($name, @info) = $id3v2->get_frames($frame);
      for my $info (@info) {
	  if (ref $info) {
	      print "$name ($frame):\n";

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

the result is suitable to give to add_frame(), change_frame()); in addition,
if it is C<'array_nodecode'>, then keys are not returned, and the setting of
C<decode_encoding_v2> is ignored.  (The "return array" flavors don't massage
the fields for better consumption by humans, so the fields should be in format
suitable for frame_add().)

If the data was stored compressed, it is
uncompressed before it is returned (even in raw mode). Then $info contains a string
with all data (which might be binary), and $name the long frame name.

See also L<MP3::Tag::ID3v2_Data> for a list of all supported frames, and
some other explanations of the returned data structure.

If more than one frame with name $ID is present, @rest contains $info
fields for all consequent frames with the same name.  Note that after
removal of frames there may be holes in the list of frame names (as in
C<FRAM FRAM01 FRAM02>) in the case when multiple frames of the given
type were present; the removed frames are returned as C<undef>.

! Encrypted frames are not supported yet !

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

}

=pod

=item add_frame()

  $fn = $id3v2->add_frame($fname, @data);

Add a new frame, identified by the short name $fname.  The number of
elements of array @data should be as described in the ID3v2.3
standard.  (See also L<MP3::Tag::ID3v2_Data>.)  There are two
exceptions: if @data is empty, it is filled with necessary number of
C<"">); if one of required elements is C<encoding>, it may be omitted
or be C<undef>, meaning the arguments are in "Plain Perl (=ISOLatin-1
or Unicode) encoding".

It returns the the short name $fn (which can differ from
$fname, when an $fname frame already exists). If no
other frame of this kind is allowed, an empty string is
returned. Otherwise the name of the newly created frame
is returned (which can have a 01 or 02 or ... appended).

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

    my ($self, $fname) = @_;
    $self->get_frame_ids() unless exists $self->{frameIDs};
    return undef unless exists $self->{frames}->{$fname};
    delete $self->{frames}->{$fname};
    $self->{modified}++;
    return 1;
}

=item copy_frames($from, $to, $overwrite, [$keep_flags, $f_ids])

Copies specified frames between C<MP3::Tag::ID3v2> objects $from, $to.  Unless
$keep_flags, the copied frames have their flags cleared.
If the array reference $f_ids is not specified, all the frames (but C<GRID>
and C<TLEN>) are considered (subject to $overwrite), otherwise $f_ids should
contain short frame ids to consider. Group ID flag is always cleared.

If $overwrite is C<'delete'>, frames with the same descriptors (as
returned by get_frame_descr()) in $to are deleted first, then all the
specified frames are copied.  If $overwrite is FALSE, only frames with
descriptors not present in $to are copied.  (If one of these two
conditions is not met, the result may be not conformant to standards.)

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN


    my $m1 = $field_map{$t} or die; # Human ==> packed
    return $m1->{$v} if exists $m1->{$v}; # translate
    return $v if $m->{_FREE};	# Free-form allowed

    die "Unsupported value `$v' for field `$nfield' of frame `$fname'";
}

=item title( [@new_title] )

Returns the title composed of the tags configured via C<MP3::Tag-E<gt>config('v2title')>
call (with default 'Title/Songname/Content description' (TIT2)) from the tag.
(For backward compatibility may be called by deprecated name song() as well.)

Sets TIT2 frame if given the optional arguments @new_title.  If this is an
empty string, the frame is removed.

=cut

*song = \&title;

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

Frames with multiple "content" fields may be set by providing multiple
values to set.  Alternatively, one can also C<join()> the values with
C<';'> if the splitting is not ambiguous, e.g., for C<POPM RBUF RVRB
SYTC>.  (For frames C<GEOD> and C<COMR>, which have a C<Description>
field, it should be specified among these values.)

   $id3v2->frame_select("RBUF", undef, undef, $n1, $n2, $n3);
   $id3v2->frame_select("RBUF", undef, undef, "$n1;$n2;$n3");

(By the way: consider using the method select_id3v2_frame() on the
"parent" MP3::Tag object instead [see L<MP3::Tag/select_id3v2_frame>],
or L<frame_select_by_descr()>.)

=item _Data_to_MIME

Internal method to extract MIME type from a string the image file
content.  Returns C<application/octet-stream> for unrecognized data
(unless extra TRUE argument is given).

  $format = $id3v2->_Data_to_MIME($data);

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

Both C<(langs)> and C<[descr]> parts may be omitted; I<langs> should
contain comma-separated list of needed languages; no protection by
backslashes is needed in I<descr>.  frame_select_by_descr() will
return a hash if C<(lang)> is omited, but the frame has a language
field; likewise for C<[descr]>; see below for alternatives.

Remember that when frame_select_by_descr() is used for modification,
B<ALL> found frames are deleted before a new one is added.

(By the way: consider using the method select_id3v2_frame_by_descr() on the
"parent" MP3::Tag object instead; see L<MP3::Tag/select_id3v2_frame_by_descr>.)

=item frame_select_by_descr_simple()

Same as frame_select_by_descr(), but if no language is given, will not
consider the frame as "complicated" frame even if it contains a
language field.

=item frame_select_by_descr_simpler()

Same as frame_select_by_descr_simple(), but if no C<Description> is

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

          return ($self->{major}, $self->{revision});
    } else {
	  return $self->{version};
    }
}

=item new()

  $tag = new($mp3fileobj);

C<new()> needs as parameter a mp3fileobj, as created by C<MP3::Tag::File>.  
C<new> tries to find a ID3v2 tag in the mp3fileobj. If it does not find a
tag it returns undef.  Otherwise it reads the tag header, as well as an
extended header, if available. It reads the rest of the tag in a
buffer, does unsynchronizing if necessary, and returns a
ID3v2-object.  At this moment only ID3v2.3 is supported. Any extended
header with CRC data is ignored, so no CRC check is done at the
moment.  The ID3v2-object can be used to extract information from
the tag.

Please use

   $mp3 = MP3::Tag->new($filename);
   $mp3->get_tags();                 ## to find an existing tag, or
   $id3v2 = $mp3->new_tag("ID3v2");  ## to create a new tag

instead of using this function directly

=cut

sub new {
    my ($class, $mp3obj, $create, $r_header) = @_;
    my $self={mp3=>$mp3obj};

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

	}
	return $data;
}

sub TCON {
    my $data = shift;
    my $how = shift;
    if (defined $how) { # called by what_data
	die unless $how eq 1 and $data eq 1;
	my $c=0;
	my %ret = map {$_, "(".$c++.")"} @{MP3::Tag::ID3v1::genres()};
	$ret{"_FREE"}=1;
	$ret{Remix}='(RX)';
	$ret{Cover}="(CR)";
	return \%ret;
    }    # called by extract_data
    join ' / ', MP3::Tag::Implemenation::_massage_genres($data);
}

sub TCON_back {
    my $data = shift;
    $data = join ' / ', map MP3::Tag::Implemenation::_massage_genres($_, 'prefer_num'),
       split ' / ', $data;
    $data =~ s[(?:(?<=\(\d\))|(?<=\(\d\d\d\))|(?<=\((?:RX|CV|\d\d)\))) / ][]ig;
    $data =~ s[ / (?=\((?:RX|CV|\d{1,3})\))][]ig;
    $data;
}

sub TFLT {
    my $text = shift;
    my $how = shift;
    if (defined $how) {	# called by what_data

lib/MP3/Tag/ID3v2.pm  view on Meta::CPAN

layout of information in the byte stream (in other words, in a file
considered as a string) is different;

=item *

semantic of frames is extended in C<v2.4> - more frames are defined, and
more frame flags are defined too.

=back

MP3::Tag does not even try to I<write> frames in C<v2.4>-layout.  However,
when I<reading> the frames, MP3::Tag does not assume any restriction on
the semantic of frames - it allows all the semantical extensions
defined in C<v2.4> even for C<v2.3> (and, probably, for C<v2.2>) layout.

C<[*]> (I expect, any sane program would do the same...)

Likewise, when writing frames, there is no restriction imposed on semantic.
If user specifies a frame the meaning of which is defined only in C<v2.4>,
we would happily write it even when we use C<v2.3> layout.  Same for frame
flags.  (And given the assumption C<[*]>, this is a correct thing to do...)

=head1 SEE ALSO

L<MP3::Tag>, L<MP3::Tag::ID3v1>, L<MP3::Tag::ID3v2_Data>

ID3v2 standard - http://www.id3.org
L<http://www.id3.org/id3v2-00>, L<http://www.id3.org/d3v2.3.0>,
L<http://www.id3.org/id3v2.4.0-structure>,
L<http://www.id3.org/id3v2.4.0-frames>,
L<http://id3lib.sourceforge.net/id3/id3v2.4.0-changes.txt>.

=head1 COPYRIGHT

Copyright (c) 2000-2008 Thomas Geffert, Ilya Zakharevich.  All rights reserved.

lib/MP3/Tag/ID3v2_Data.pod  view on Meta::CPAN


=head1 NAME 

MP3::Tag::ID3v2_Data - get_frame() data format and supported frames

=head1 SYNOPSIS

  $mp3 = MP3::Tag->new($filename);
  $mp3->get_tags();
  $id3v2 = $mp3->{ID3v2} if exists $mp3->{id3v2};

  ($info, $long) = $id3v2->get_frame($id);    # or

  ($info, $long) = $id3v2->get_frame($id, 'raw');


=head1 DESCRIPTION 

This document describes how to use the results of the get_frame function of 
MP3::Tag::ID3v2, thus the data format of frames retrieved with 
MP3::Tag::ID3v2::get_frame().

It contains also a list of all supported ID3v2-Frames.

=head2 get_frame()

 ($info, $long) = $id3v2->get_frame($id);    # or
 
 ($info, $long) = $id3v2->get_frame($id, 'raw');

$id has to be a name of a frame like "APIC".  For more variants of calling
see L<get_frame()|MP3::Tag::ID3v2>.

The names of all frames found in a tag can be retrieved with the L<get_frame_ids()|MP3::Tag::ID3v2> function.

=head2 Using the returned data

In the ID3v2.3 specifications 73 frames are defined, which can contain very
different information. That means that get_frame returns the information
of different frames also in different ways.

=over 4

=item Simple Frames

lib/MP3/Tag/ID3v2_Data.pod  view on Meta::CPAN


=item RVAD : Relative volume adjustment

=item SYLT : Synchronized lyric/text

=back


=head1 SEE ALSO

L<MP3::Tag>, L<MP3::Tag::ID3v2>

lib/MP3/Tag/ImageExifTool.pm  view on Meta::CPAN

package MP3::Tag::ImageExifTool;

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

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

=pod

=head1 NAME

MP3::Tag::ImageExifTool - extract size info from image files via L<Image::ExifTool|Image::ExifTool>.

=head1 SYNOPSIS

  my $db = MP3::Tag::ImageExifTool->new($filename);	# Name of multimedia file

see L<MP3::Tag>

=head1 DESCRIPTION

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

It implements the (standard) methods qw(title track artist album year genre comment),
as well as width(), height(), bit_depth(), _duration() and mime_type() methods (sizes in pixels).

Use method C<field('FieldName')> to access a particular field provided by C<Image::ExifTool>.

These methods return C<undef> if C<Image::ExifTool> is not available, or does not return valid data.

=cut

lib/MP3/Tag/ImageSize.pm  view on Meta::CPAN

package MP3::Tag::ImageSize;

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

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

=pod

=head1 NAME

MP3::Tag::ImageSize - extract size info from image files via L<Image::Size|Image::Size>.

=head1 SYNOPSIS

  my $db = MP3::Tag::ImageSize->new($filename);	# Name of multimedia file

see L<MP3::Tag>

=head1 DESCRIPTION

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

It implements width(), height() and mime_type() methods (sizes in pixels).

They return C<undef> if C<Image::Size> is not available, or does not return valid data.

=head1 SEE ALSO

L<Image::Size>, L<MP3::Tag>

=cut


# Constructor

sub new_with_parent {
    my ($class, $f, $p, $e, %seen, @cue) = (shift, shift, shift);
    $f = $f->filename if ref $f;
    bless [$f], $class;

lib/MP3/Tag/Inf.pm  view on Meta::CPAN

package MP3::Tag::Inf;

use strict;
use vars qw /$VERSION @ISA/;

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

=pod

=head1 NAME

MP3::Tag::Inf - Module for parsing F<.inf> files associated with music tracks.

=head1 SYNOPSIS

  my $mp3inf = MP3::Tag::Inf->new($filename);	# Name of MP3 or .INF file
						# or an MP3::Tag::File object

  ($title, $artist, $album, $year, $comment, $track) = $mp3inf->parse();

see L<MP3::Tag>

=head1 DESCRIPTION

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

It parses the content of F<.inf> file (created, e.g., by cdda2wav).

=over 4

=cut


# Constructor

lib/MP3/Tag/LastResort.pm  view on Meta::CPAN

package MP3::Tag::LastResort;

use strict;
use vars qw /$VERSION @ISA/;

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

=pod

=head1 NAME

MP3::Tag::LastResort - Module for using other fields to fill autoinfo fields.

=head1 SYNOPSIS

  my $mp3extra = MP3::Tag::LastResort::new_with_parent($filename, $parent);
  $comment = $mp3inf->comment();

see L<MP3::Tag>

=head1 DESCRIPTION

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

It uses the artist_collection() as comment() if comment() is not otherwise
defined.

=cut


# Constructor

sub new_with_parent {

lib/MP3/Tag/ParseData.pm  view on Meta::CPAN

package MP3::Tag::ParseData;

use strict;
use vars qw /$VERSION @ISA/;

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

=pod

=head1 NAME

MP3::Tag::ParseData - Module for parsing arbitrary data associated with music files.

=head1 SYNOPSIS

   # parses the file name according to one of the patterns:
   $mp3->config('parse_data', ['i', '%f', '%t - %n - %a.%e', '%t - %y.%e']);
   $title = $mp3->title;

see L<MP3::Tag>

=head1 DESCRIPTION

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

Each option of configuration item C<parse_data> should be of the form
C<[$flag, $string, $pattern1, ...]>.  For each of the option, patterns of
the option are matched agains the $string of the option, until one of them
succeeds.  The information obtained from later options takes precedence over
the information obtained from earlier ones.

The meaning of the patterns is the same as for parse() or parse_rex() methods
of C<MP3::Tag>.  Since the default for C<parse_data> is empty, by default this
handler has no effect.

$flag is split into 1-character-long flags (unknown flags are ignored):

=over

=item C<i>

the string-to-parse is interpolated first;

lib/MP3/Tag/ParseData.pm  view on Meta::CPAN

=back

Unless C<b> option is given, the resulting values have starting and
trailing whitespace trimmed.  (Actually, split()ing into lines is done
using the configuration item C<parse_split>; it defaults to C<"\n">.)

If the configuration item C<parse_data> has multiple options, the $strings
which are interpolated will use information set by preceding options;
similarly, any interolated option may use information obtained by other
handlers - even if these handers are later in the pecking order than
C<MP3::Tag::ParseData> (which by default is the first handler).  For
example, with

  ['i', '%t' => '%t (%y)'], ['i', '%t' => '%t - %c']

and a local CDDB file which identifies title to C<'Merry old - another
interpretation (1905)'>, the first field will interpolate C<'%t'> into this
title, then will split it into the year and the rest.  The second field will
split the rest into a title-proper and comment.

Note that one can use fields of the form

lib/MP3/Tag/ParseData.pm  view on Meta::CPAN


It is possible to set individual id3v2 frames; use %{TIT1} or
some such.  Setting to an empty string deletes the frame if config
parameter C<id3v2_frame_empty_ok> is false (the default value).
Setting ID3v2 frames uses the same translation rules as
select_id3v2_frame_by_descr().

=head2 SEE ALSO

The flags C<i f F B l m I b> are identical to flags of the method
interpolate_with_flags() of MP3::Tag (see L<MP3::Tag/"interpolate_with_flags">).
Essentially, the other flags (C<R m o O D z>) are applied to the result of
calling the latter method.

=cut


# Constructor

sub new_with_parent {
    my ($class, $filename, $parent) = @_;

lib/Normalize/Text/Music_Fields.pm  view on Meta::CPAN

     }
   }
  }
 }
}

#$tr{lc 'Tchaikovsky, Piotyr Ilyich'} = $tr{lc 'Tchaikovsky'};

sub prepare_tag_object_comp ($;$) {
  my ($comp, $piece) = @_;
  require MP3::Tag;
  my $tag = MP3::Tag->new_fake('settable');

  for my $elt ( qw( title track artist album comment year genre
                    title_track artist_collection person ) ) {
    no strict 'refs';
    MP3::Tag->config("translate_$elt", \&{"Normalize::Text::Music_Fields::normalize_$elt"})
      if defined &{"Normalize::Text::Music_Fields::normalize_$elt"};
    # This is needed to expand albums, since pieces file is named so...
    MP3::Tag->config("short_person", \&Normalize::Text::Music_Fields::short_person)
        if defined &Normalize::Text::Music_Fields::short_person;
  }
  $tag->config('parse_data', ['mi', $comp, '%a'], ($piece ? ['mi', $piece, '%l'] : () ));
  $tag;
}

## perl -MNormalize::Text::Music_Fields -e Normalize::Text::Music_Fields::test_normalize_piece
sub test_normalize_piece {
  for (split /\n/, <<EOS) {
beethoven # 28

lib/Normalize/Text/Music_Fields.pm  view on Meta::CPAN

 perl -MNormalize::Text::Music_Fields -wnle "BEGIN {$tag = Normalize::Text::Music_Fields::prepare_tag_object_comp(shift @ARGV); print qq(# format = mail-header\n)} next unless s/^\s*\+\+\s*//; print Normalize::Text::Music_Fields::merge_info($tag,$_, ...

 # Minimal consistency check of persons database.
 perl -MNormalize::Text::Music_Fields -wle "BEGIN{binmode $_, ':encoding(cp866)' for \*STDIN, \*STDOUT, \*STDERR} print Normalize::Text::Music_Fields->check_persons"

 # Minimal testing code:
 perl -MNormalize::Text::Music_Fields -e Normalize::Text::Music_Fields::test_normalize_piece

It may be easier to type these examples if one uses C<manage_M_N_F.pm>, which
exports the mentioned subroutines to the main namespace (available in
F<examples> directory of a distribution of C<MP3::Tag>).  E.g., the last
example becomes:

 perl -Mmanage_M_N_F -e test_normalize_piece


=cut

t/interpolate.t  view on Meta::CPAN

#!/usr/bin/perl -w
# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl test.pl'

######################### We start with some black magic to print on failure.

# Change 1..1 below to 1..last_test_to_print .
# (It may become useful if the test is moved to ./t subdirectory.)

BEGIN { $| = 1; print "1..24\n"; $ENV{MP3TAG_SKIP_LOCAL} = 1}
END {print "MP3::Tag not loaded :(\n" unless $loaded;}
use MP3::Tag;
$loaded = 1;
$count = 0;
ok(1,"MP3::Tag initialized");

######################### End of black magic.

# Insert your test code below (better if it prints "ok 13"
# (correspondingly "not ok 13") depending on the success of chunk 13
# of the test code):

{local *F; open F, '>test12.mp3' or warn; print F 'empty'}
{local *F; open F, '>xxxtest12.mp3' or warn; print F 'content'}

$mp3 = MP3::Tag->new("test12.mp3");

ok( $mp3->interpolate('%{TCOM: %{TCOM} }') eq '',  'false conditional');
ok( $mp3->interpolate('%{TCOM|| <%{TCOM}> }') eq ' <> ',  'false ||-interpolation');
my $res = $mp3->interpolate('aa%{I(f)xxxtest12.mp3}bb');
print "# `$res'\n";
ok($res eq 'aacontentbb', "I(f) interpolates");
$res = $mp3->interpolate('aa%{I(if)xxx%f}bb');
ok($res eq 'aacontentbb', "I(fi) interpolates");
$res = $mp3->interpolate('aa%{I(if)xxx%{f||not_present}}bb');
ok($res eq 'aacontentbb', "I(fi) interpolates with choice");

t/interpolate.t  view on Meta::CPAN

print "# `$res'\n";
ok($res eq '39m05s', "format time 2345");

$res = $mp3->format_time(5.62, qw(?Hh ?{mL}m {SML}s));
print "# `$res'\n";
ok($res eq '5.62s', "format time 5.62");
$res = $mp3->format_time(5, qw(?Hh ?{mL}m {SML}s));
print "# `$res'\n";
ok($res eq '5s', "format time 5");

$res = MP3::Tag->format_time(5.62, qw(?Hh ?{mL}m {SML}s));
print "# `$res'\n";
ok($res eq '5.62s', "format time 5.62");

{ my $f = MP3::Tag->new_fake;
  $res = $f->format_time(5.62, qw(?Hh ?{mL}m {SML}s));
  print "# `$res'\n";
  ok($res eq '5.62s', "format time 5.62");
}

{ local $mp3->{ms} = 2345.62 * 1000;	# Pretend the length is as given
  $res = $mp3->interpolate("%{T[?Hh,?{mL}m,{SML}s]}");
  print "# `$res'\n";
  ok($res eq '39m05.62s', "format time 2345.62");
}

t/mp3tag.t  view on Meta::CPAN

#!/usr/bin/perl -w
# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl test.pl'

######################### We start with some black magic to print on failure.

# Change 1..1 below to 1..last_test_to_print .
# (It may become useful if the test is moved to ./t subdirectory.)

BEGIN { $| = 1; print "1..139\n"; $ENV{MP3TAG_SKIP_LOCAL} = 1}
END {print "MP3::Tag not loaded :(\n" unless $loaded;}
use MP3::Tag;
$loaded = 1;
$count = 0;
ok(1,"MP3::Tag initialized");

######################### End of black magic.

# Insert your test code below (better if it prints "ok 13"
# (correspondingly "not ok 13") depending on the success of chunk 13
# of the test code):

#test - getting the tags
$mp3 = MP3::Tag->new("test.mp3");
$mp3->get_tags;

$v1 = $mp3->{ID3v1};
ok($v1,"Detecting ID3v1");

$v2 = $mp3->{ID3v2};
ok($v2,"Detecting ID3v2");

#test - reading ID3v1
ok(($v1 && ($v1->song eq "Song") && ($v1->track == 10)),"Reading ID3v1");

t/mp3tag.t  view on Meta::CPAN

ok(($mp3->title eq "Only a test with a ID3v1 and ID3v2 tag") && ($mp3->track == 10),"Reading ID3v1/2 via Tag");
ok($mp3->comment eq "test", "Reading ID3v1 comment via Tag");
ok($mp3->year eq 2000, "Reading ID3v1 year via Tag");
ok($mp3->genre eq 'Ska', "Reading ID3v1 genre via Tag");

#test - reading ID3v2
ok($v2 && $v2->get_frame("COMM")->{Description} eq "Test!","Reading ID3v2");

{local *F; open F, '>test2.mp3' or warn; print F 'empty'}

$mp3 = MP3::Tag->new("test2.mp3");
$mp3->new_tag("ID3v1");
$v1 = $mp3->{ID3v1};
$mp3->new_tag("ID3v2");
$v2 = $mp3->{ID3v2};

#test - creating/changing/writing ID3v1
ok($v1 && join("",$v1->all("New","a","a",2000,"c",10,"Ska")), "Creating new ID3v1");
ok($v1 && $v1->write_tag,"Writing ID3v1");
ok($v1 && $v1->artist("Artist"), "Changing ID3v1");
ok($v1 && $v1->write_tag,"Writing ID3v1");

t/mp3tag.t  view on Meta::CPAN

ok($v2 && $v2->add_frame("TLAN","ENG"),"Creating new ID3v2");
ok($v2 && $v2->write_tag,"Writing ID3v2");
ok($v2 && $v2->add_frame("TLAN","GER"),"Changing ID3v2");
ok($v2 && $v2->year('1848-9,1864-1872'),"Writing ID3v2 complex timestamp");
ok($v2 && $v2->write_tag,"Writing ID3v2");

$mp3=$v1=$v2=undef;			# Close the file...

ok(((stat("test2.mp3"))[7] % 512) == 0," ID3v2 rounding size");

$mp3 = MP3::Tag->new("test2.mp3");
$mp3->get_tags;
$v1 = $mp3->{ID3v1};
$v2 = $mp3->{ID3v2};

#test 10 - reading new ID3v1
ok($v1 && $v1->song eq "New" && $v1->artist eq "Artist","Checking new ID3v1");
ok($v1 && $v1->title eq "New","Checking new ID3v1");
ok($v1 && $mp3->autoinfo->{title} eq "New","Checking new ID3v1");


t/mp3tag.t  view on Meta::CPAN

#test 18 - comment
ok($v2 && !defined $v2->comment(), "Checking no comment");

# year
ok($v2 && (@f = $v2->get_frames("TDRC", 'intact')) && @f == 2 && $f[-1] eq "1848-09\0001864/1872", "Checking timestamp(s) in ID3v2");
ok($v2 && ($y = $v2->year) && $y eq "1848-09,1864--1872", "Checking ID3v2 year");

ok($v2 && $v2->add_frame("COMM", "ENG", '', 'Testing...'), "Changing ID3v2 ''-comment");
ok($v2 && $v2->write_tag,"Writing ID3v2");

$mp3 = MP3::Tag->new("test2.mp3");
$mp3->get_tags;
$v2 = $mp3->{ID3v2};
ok($v2 && $v2->comment() eq 'Testing...', "Checking any-language comment");

ok($v2 && $v2->comment('Another test...', '', "ENG"), "Setting ID3v2-comment");
ok($v2 && $v2->write_tag,"Writing ID3v2");

$mp3 = MP3::Tag->new("test2.mp3");
$mp3->get_tags;
$v1 = $mp3->{ID3v1};
$v2 = $mp3->{ID3v2};

ok($v2 && $v2->comment() eq 'Another test...', "Checking any-language comment");
ok($v2 && !defined $v2->_comment('GER'), "Checking no GER comment");
ok($v2 && $v2->_comment('ENG') eq 'Another test...', "Checking ENG comment");
ok($v2 && $mp3->comment() eq 'Another test...', "Checking ID3 comment");

my $s = $mp3->interpolate('%%02t_Title: `%012.12t\'; %{TLAN} %{TLAN01: have %{TLAN01}} %{!TLAN02:, do not have TLAN02}');	#'

t/mp3tag.t  view on Meta::CPAN

Channels=	2
Copy_permitted=	once (copyright protected)
Endianess=	little
# index list
Index=		0
Year=		1988
Trackcomment=	'Chiribim conducts Some Choir; recorded in Mariann'
EOP
close FH or warn;

$mp3 = MP3::Tag->new("./test2.mp3");
$mp3->get_tags;
my $inf = $mp3->{Inf};
#my @a = %$mp3;
#warn "@a";

ok($inf, ".inf file parsed");
ok($inf && $mp3->autoinfo->{title} eq 'It Is Truly Meet', "Checking .inf title");
ok($inf && $mp3->autoinfo->{artist} eq 'Bach (2110)', "Checking .inf artist");
ok($inf && $mp3->autoinfo->{track} eq 11, "Checking .inf track");
ok($inf && $mp3->autoinfo->{album} eq 'Liturgy of St. John; Op 31', "Checking .inf album");

t/mp3tag.t  view on Meta::CPAN

ok($inf && $mp3->autoinfo->{comment} eq 'Chiribim conducts Some Choir; recorded in Mariann', "Checking .inf comment");

ok($inf && $mp3->autoinfo('from')->{comment}[0] eq 'Chiribim conducts Some Choir; recorded in Mariann', "Checking .inf comment+source");
ok($inf && $mp3->autoinfo('from')->{comment}[1] eq 'Inf', "Checking .inf comment source");

use File::Spec;
require File::Basename;
my $i =  $mp3->interpolate('file=%(_)-12f, File=%F, %%comment="%c", dir="%{d0}"');
# Skip a good test on 5.005
my $checkname = (File::Spec->can('rel2abs') ? 'test2.mp3' : './test2.mp3');
my $ii = 'file=test2.mp3___, File=' . MP3::Tag->rel2abs($checkname)
	. ', %comment="Chiribim conducts Some Choir; recorded in Mariann"'
	. ', dir="' . scalar(File::Basename::fileparse(File::Basename::dirname(MP3::Tag->rel2abs('test2.mp3')),"")) . '"';
#warn "$i\n$ii\n";
ok($inf && $i eq $ii, "Checking interpolation: `$i' eq `$ii'");

ok($mp3->filename_nodir eq "test2.mp3", "Checking filename method:");
ok($mp3 && $mp3->interpolate("%A.%e") eq $mp3->interpolate("%F"), "interpolate %A");

# Check CDDB_File...
ok(MP3::Tag->config('cddb_files', qw(cddb.tm1 cddb.tm cddb.tm2)), "Configuring list of cddb_files");

open NH, '>audio07.mp3' or warn;
close NH;
$mp3 = MP3::Tag->new("./audio07.mp3");
ok($mp3 && $mp3->title eq 'Makrokosmos III - I. Nocturnal Sounds (The Awakening)', "Title via CDDB_File");
ok($mp3 && $mp3->artist eq 'Crumb Piece', "Artist via CDDB_File");
ok($mp3 && $mp3->album eq 'Ancient Voices', "Album via CDDB_File");
ok($mp3 && $mp3->year eq '1234', "Year via CDDB_File");
ok($mp3 && $mp3->comment eq 'comment7; Fake entry', "Comment via CDDB_File");
# print STDERR "# Genre=", $mp3->genre, "\n";
ok($mp3 && $mp3->genre eq 'Vocal', "Genre via CDDB_File");
ok($mp3 && $mp3->track eq '7', "Track no with CDDB_File");
ok($mp3 && (not defined $mp3->artist_collection), "artist_collection");

open NH, '>audio08.mp3' or warn;
close NH;
$mp3 = MP3::Tag->new("./audio08.mp3");
ok($mp3 && $mp3->year eq '2001-10-23--30,2002-02-28', "Year via CDDB_File");
ok($mp3 && $mp3->comment_collection eq 'Fake entry', "comment_collection");
# print STDERR "# cT=", $mp3->comment_track, "\n";
ok($mp3 && $mp3->comment_track eq 'comment8; Recorded on 2001-10-23--30,2002-02-28', "comment_track");
ok($mp3 && $mp3->artist_collection eq 'Crumb Piece', "artist_collection");
ok($mp3 && $mp3->artist eq 'Piece of Crumb', "artist");
ok($mp3 && $mp3->interpolate('%{aC}') eq 'Crumb Piece', "artist_collection via %{aC}");

ok(MP3::Tag->config('comment_remove_date', 1), "Configuring comment_remove_date");
$mp3 = MP3::Tag->new("./audio08.mp3");
# print STDERR "# cT=", $mp3->comment_track, "\n";
ok($mp3 && $mp3->comment_track eq 'comment8', "comment_track with removal");

ok(MP3::Tag->config('cddb_files', qw(cddb.tmp1 cddb.tmp cddb.tmp2)), "Configuring2 list of cddb_files");

open NH, '>audio07.mp3' or warn;
close NH;
$mp3 = MP3::Tag->new("./audio07.mp3");
ok($mp3 && $mp3->title eq 'Makrokosmos III - I. Nocturnal Sounds (The Awakening)', "Title via CDDB_File");
ok($mp3 && $mp3->artist eq 'Crumb Piece', "Artist via CDDB_File");
ok($mp3 && $mp3->album eq 'Ancient Voices', "Album via CDDB_File");
ok($mp3 && $mp3->year eq '1234', "Year via CDDB_File");
ok($mp3 && $mp3->comment eq 'comment7; Fake entry', "Comment via CDDB_File");
ok($mp3 && $mp3->genre eq 'A special genre', "Genre via CDDB_File");
ok($mp3 && $mp3->track eq '7', "Track no with CDDB_File");


open NH, '>audio_07.mp3' or warn;
close NH;
$mp3 = MP3::Tag->new("./audio_07.mp3");
$mp3->config(parse_data => ['m', 'no comment', '%c']);
ok($mp3 && $mp3->title eq 'Makrokosmos III - I. Nocturnal Sounds (The Awakening)', "Title via CDDB_File with force");
ok($mp3 && $mp3->comment eq 'no comment', "Forced comment");

$mp3 = MP3::Tag->new("./audio_07.mp3");
$mp3->config(parse_data => ['im', '<%c>', '%t']);
ok($mp3 && $mp3->artist eq 'Crumb Piece', "Artist via CDDB_File with force/interpolate");
ok($mp3 && $mp3->title eq '<comment7; Fake entry>', "Force/interpolated title");

$mp3 = MP3::Tag->new("./audio_07.mp3");
$mp3->config(parse_data => ['im', '[%t]' => '%t'], ['im', '<%t>' => '%c']);
ok($mp3 && $mp3->comment eq '<[Makrokosmos III - I. Nocturnal Sounds (The Awakening)]>', "Force/interpolated recursive comment");
ok($mp3 && $mp3->title eq '[Makrokosmos III - I. Nocturnal Sounds (The Awakening)]', "Force/interpolated recursive title");

$mp3 = MP3::Tag->new("audio_07.mp3");
$mp3->config(parse_data => ['im', '%f', '%c_%n.mp3'], ['mz', '' => '%g']);
ok($mp3 && $mp3->comment eq 'audio', "comment via parse");
ok($mp3 && $mp3->track eq '7', "track via parse");
ok($mp3 && $mp3->comment eq 'audio', "comment via cached parse");
ok($mp3 && $mp3->title eq 'Makrokosmos III - I. Nocturnal Sounds (The Awakening)', "title with parse");

$s = $mp3->interpolate("%03n_%{!g: Have only comment=<%c>}<%g>%c");
ok($mp3 && $s eq '007_ Have only comment=<audio><>audio', "conditional interpolation");

$mp3 = MP3::Tag->new("./audio_07.mp3");
$mp3->config(parse_data => ['iRm', 'my/dir/%f', '/%c/%c%E']);
ok($mp3 && $mp3->comment eq 'dir; audio_07', "multi-%c via parse/interpolate");

$mp3 = MP3::Tag->new("./audio_07.mp3");
$mp3->config(parse_data => ['iRm', 'my/dir/%f', '/%c/%c%=E']);
ok($mp3 && $mp3->comment eq 'dir; audio_07', "multi-%c and %=E via parse/interpolate");

$mp3 = MP3::Tag->new("./audio_07.mp3");
$mp3->config(parse_data => ['im', 'my/dir/%f', '%t/%c/%c.%e']);
$i = $mp3->comment;
#warn "<$i>\n";
ok($mp3 && $i eq 'dir; audio_07', "multi-%c and %e via parse/interpolate");

$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre_set('foobar / baz'), "set genre to something complicated to trigger v2");
ok($mp3 && $mp3->update_tags({genre => '(18)'}), "set genre to (18)");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'Techno', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'Techno', "get v1 genre");
ok($mp3 && $mp3->{ID3v2}, "v2 was set");

$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->update_tags({genre => 18}), "set genre to 18");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'Techno', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'Techno', "get v1 genre");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=147 SynthPop
ok($mp3 && $mp3->update_tags({genre => '(147)'}), "set genre to (147)");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'SynthPop', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'SynthPop', "get v1 genre");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=147 SynthPop
ok($mp3 && $mp3->update_tags({genre => '147'}), "set genre to 147");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'SynthPop', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'SynthPop', "get v1 genre");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=149 SynthPop
ok($mp3 && $mp3->update_tags({genre => '(149)'}), "set genre to (149)");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq '(149)', "get genre");
my $g = $mp3->{ID3v1}->genre();
print "# g=<$g>\n";
ok($mp3 && $g eq '', "get v1 genre");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=149 SynthPop
ok($mp3 && $mp3->update_tags({genre => '149'}), "set genre to 149");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq '(149)', "get genre");
$g = $mp3->{ID3v1}->genre();
print "# g=<$g>\n";
ok($mp3 && $g eq '', "get v1 genre");

open NH, '>audio_07.mp3' or warn;
close NH;

$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3->interpolate("%{ID3v1:have v1}/%{!ID3v1:no v1}; %{ID3v2:have v2}/%{!ID3v2:no v2}") eq '/no v1; /no v2', 'conditional interpolate');
ok($mp3 && $mp3->update_tags({genre => '(18)'}), "set genre to (18)");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'Techno', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'Techno', "get v1 genre");
ok($mp3->interpolate("%{ID3v1:have v1}/%{!ID3v1:no v1}; %{ID3v2:have v2}/%{!ID3v2:no v2}") eq 'have v1/; /no v2', 'conditional interpolate');

$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->update_tags({genre => 18}), "set genre to 18");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'Techno', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'Techno', "get v1 genre");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=147 SynthPop
ok($mp3 && $mp3->update_tags({genre => '(147)'}), "set genre to (147)");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'SynthPop', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'SynthPop', "get v1 genre");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=147 SynthPop
ok($mp3 && $mp3->update_tags({genre => '147'}), "set genre to 147");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq 'SynthPop', "get genre");
ok($mp3 && $mp3->{ID3v1}->genre() eq 'SynthPop', "get v1 genre");
ok($mp3 && !$mp3->{ID3v2}, "no v2 was set");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=147 SynthPop
ok($mp3 && $mp3->update_tags({genre => '(149)'}), "set genre to (149)");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq '(149)', "get genre");
$g = $mp3->{ID3v1}->genre();
print "# g=<$g>\n";
ok($mp3 && $g eq '', "get v1 genre");

$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=147 SynthPop
ok($mp3 && $mp3->update_tags({genre => '149', track => '2/4'}), "set genre to 149, track");
$mp3 = MP3::Tag->new("./audio_07.mp3");
ok($mp3 && $mp3->genre() eq '(149)', "get genre");
$g = $mp3->{ID3v1}->genre();
print "# g=<$g>\n";
ok($mp3 && $g eq '', "get v1 genre");
ok($mp3 && $mp3->track() eq '2/4', "get track");
ok($mp3 && $mp3->{ID3v1}->track() eq '2', "get v1 track");
ok(0 == ((-s "./audio_07.mp3") % 512), "padding to sector");
ok($mp3->interpolate("%{ID3v1:have v1}/%{!ID3v1:no v1}; %{ID3v2:have v2}/%{!ID3v2:no v2}") eq 'have v1/; have v2/', 'conditional interpolate');

open NH, '>audio_07.mp3' or warn;
close NH;
$mp3 = MP3::Tag->new("./audio_07.mp3");	# max=147 SynthPop
ok($mp3 && ($mp3->genre_set(113) or 1), 'set genre');
ok($mp3 && $mp3->{ID3v1} && $mp3->{ID3v1}->genre() eq 'Tango', 'get v1 genre');
ok($mp3 && $mp3->genre() eq 'Tango', 'get genre');

my @failed;
#@failed ? die "Tests @failed failed.\n" : print "All tests successful.\n";

sub ok_test {
  my ($result, $test) = @_;
  printf ("Test %2d %s %s", ++$count, $test, '.' x (45-length($test)));



( run in 1.476 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )