MP4-Info-1.13/000755 000765 000024 00000000000 11424444346 013702 5ustar00jonathanstaff000000 000000 MP4-Info-1.13/Info.pm000755 000765 000024 00000060437 11424442204 015137 0ustar00jonathanstaff000000 000000 # # Copyright (c) 2004-2010 Jonathan Harris # # This program is free software; you can redistribute it and/or modify it # under the the same terms as Perl itself. # package MP4::Info; use overload; use strict; use Carp; use Symbol; use Encode; use Encode::Guess qw(latin1); use IO::String; use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $AUTOLOAD %data_atoms %other_atoms %container_atoms @mp4_genres ); @ISA = 'Exporter'; @EXPORT = qw(get_mp4tag get_mp4info); @EXPORT_OK = qw(use_mp4_utf8); %EXPORT_TAGS = ( utf8 => [qw(use_mp4_utf8)], all => [@EXPORT, @EXPORT_OK] ); $VERSION = '1.13'; my $debug = 0; =head1 NAME MP4::Info - Fetch info from MPEG-4 files (.mp4, .m4a, .m4p, .3gp) =head1 SYNOPSIS #!perl -w use MP4::Info; my $file = 'Pearls_Before_Swine.m4a'; my $tag = get_mp4tag($file) or die "No TAG info"; printf "$file is a %s track\n", $tag->{GENRE}; my $info = get_mp4info($file); printf "$file length is %d:%d\n", $info->{MM}, $info->{SS}; my $mp4 = new MP4::Info $file; printf "$file length is %s, title is %s\n", $mp4->time, $mp4->title; =head1 DESCRIPTION The MP4::Info module can be used to extract tag and meta information from MPEG-4 audio (AAC) and video files. It is designed as a drop-in replacement for L. Note that this module does not allow you to update the information in MPEG-4 files. =over 4 =item $mp4 = MP4::Info-Enew(FILE) OOP interface to the rest of the module. The same keys available via C and C are available via the returned object (using upper case or lower case; but note that all-caps 'VERSION' will return the module version, not the MPEG-4 version). Passing a value to one of the methods will B set the value for that tag in the MPEG-4 file. =cut sub new { my ($class, $file) = @_; # Supported tags my %tag_names = ( ALB => 1, APID => 1, ART => 1, CMT => 1, COVR => 1, CPIL => 1, CPRT => 1, DAY => 1, DISK => 1, GNRE => 1, GRP => 1, NAM => 1, RTNG => 1, TMPO => 1, TOO => 1, TRKN => 1, WRT => 1, TITLE => 1, ARTIST => 1, ALBUM => 1, YEAR => 1, COMMENT => 1, GENRE => 1, TRACKNUM => 1, VERSION => 1, LAYER => 1, BITRATE => 1, FREQUENCY => 1, SIZE => 1, SECS => 1, MM => 1, SS => 1, MS => 1, TIME => 1, COPYRIGHT => 1, ENCODING => 1, ENCRYPTED => 1, ); my $tags = get_mp4tag ($file) or return undef; my $self = { _permitted => \%tag_names, %$tags }; return bless $self, $class; } # Create accessor functions - see perltoot manpage sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion unless (exists $self->{_permitted}->{uc $name} ) { croak "No method '$name' available in class $type"; } # Ignore any parameter return $self->{uc $name}; } sub DESTROY { } ############################################################################ =item use_mp4_utf8([STATUS]) Tells MP4::Info whether to assume that ambiguously encoded TAG info is UTF-8 or Latin-1. 1 is UTF-8, 0 is Latin-1. Default is UTF-8. Function returns new status (1/0). If no argument is supplied, or an unaccepted argument is supplied, function merely returns existing status. This function is not exported by default, but may be exported with the C<:utf8> or C<:all> export tag. =cut my $utf8 = 1; sub use_mp4_utf8 { my ($val) = @_; $utf8 = $val if (($val == 0) || ($val == 1)); return $utf8; } =item get_mp4tag (FILE) Returns hash reference containing the tag information from the MP4 file. The following keys may be defined: ALB Album APID Apple Store ID ART Artist CMT Comment COVR Album art (typically JPEG or PNG data) CPIL Compilation (boolean) CPRT Copyright statement DAY Year DISK Disk number & total (2 integers) GNRE Genre GRP Grouping NAM Title RTNG Rating (integer) TMPO Tempo (integer) TOO Encoder TRKN Track number & total (2 integers) WRT Author or composer For compatibility with L, the MP3 ID3v1-style keys TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE and TRACKNUM are defined as synonyms for NAM, ART, ALB, DAY, CMT, GNRE and TRKN[0]. Any and all of these keys may be undefined if the corresponding information is missing from the MPEG-4 file. On error, returns nothing and sets C<$@>. =cut sub get_mp4tag { my ($file) = @_; my (%tags); return parse_file ($file, \%tags) ? undef : {%tags}; } =item get_mp4info (FILE) Returns hash reference containing file information from the MPEG-4 file. The following keys may be defined: VERSION MPEG version (=4) LAYER MPEG layer description (=1 for compatibility with MP3::Info) BITRATE bitrate in kbps (average for VBR files) FREQUENCY frequency in kHz SIZE bytes in audio stream SECS total seconds, rounded to nearest second MM minutes SS leftover seconds MS leftover milliseconds, rounded to nearest millisecond TIME time in MM:SS, rounded to nearest second COPYRIGHT boolean for audio is copyrighted ENCODING audio codec name. Possible values include: 'mp4a' - AAC, aacPlus 'alac' - Apple lossless 'drms' - Apple encrypted AAC 'samr' - 3GPP narrow-band AMR 'sawb' - 3GPP wide-band AMR 'enca' - Unspecified encrypted audio ENCRYPTED boolean for audio data is encrypted Any and all of these keys may be undefined if the corresponding information is missing from the MPEG-4 file. On error, returns nothing and sets C<$@>. =cut sub get_mp4info { my ($file) = @_; my (%tags); return parse_file ($file, \%tags) ? undef : {%tags}; } ############################################################################ # No user-servicable parts below # Interesting atoms that contain data in standard format. # The items marked ??? contain integers - I don't know what these are for # but return them anyway because the user might know. my %data_atoms = ( AART => 1, # Album artist - returned in ART field no ART found ALB => 1, ART => 1, CMT => 1, COVR => 1, # Cover art CPIL => 1, CPRT => 1, DAY => 1, DISK => 1, GEN => 1, # Custom genre - returned in GNRE field no GNRE found GNRE => 1, # Standard ID3/WinAmp genre GRP => 1, NAM => 1, RTNG => 1, TMPO => 1, TOO => 1, TRKN => 1, WRT => 1, # Apple store APID => 1, AKID => 1, # ??? ATID => 1, # ??? CNID => 1, # ??? GEID => 1, # Some kind of watermarking ??? PLID => 1, # ??? # 3GPP TITL => 1, # title - returned in NAM field no NAM found DSCP => 1, # description - returned in CMT field no CMT found #CPRT=> 1, PERF => 1, # performer - returned in ART field no ART found AUTH => 1, # author - returned in WRT field no WRT found #GNRE=> 1, MEAN => 1, NAME => 1, DATA => 1, ); # More interesting atoms, but with non-standard data layouts my %other_atoms = ( MOOV => \&parse_moov, MDAT => \&parse_mdat, META => \&parse_meta, MVHD => \&parse_mvhd, STSD => \&parse_stsd, UUID => \&parse_uuid, ); # Standard container atoms that contain either kind of above atoms my %container_atoms = ( ILST => 1, MDIA => 1, MINF => 1, STBL => 1, TRAK => 1, UDTA => 1, '----' => 1, # iTunes and aacgain info ); # Standard ID3 plus non-standard WinAmp genres my @mp4_genres = ( 'N/A', 'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', 'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial', 'Alternative', 'Ska', 'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno', 'Ambient', 'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise', 'AlternRock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', 'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic', 'Darkwave', 'Techno-Industrial', 'Electronic', 'Pop-Folk', 'Eurodance', 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40', 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American', 'Cabaret', 'New Wave', 'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', 'Hard Rock', 'Folk', 'Folk/Rock', 'National Folk', 'Swing', 'Fast-Fusion', 'Bebob', 'Latin', 'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock', 'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock', 'Slow Rock', 'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', 'Humour', 'Speech', 'Chanson', 'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Primus', 'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba', 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', 'Duet', 'Punk Rock', 'Drum Solo', 'A capella', 'Euro-House', 'Dance Hall', 'Goa', 'Drum & Bass', 'Club House', 'Hardcore', 'Terror', 'Indie', 'BritPop', 'NegerPunk', 'Polsk Punk', 'Beat', 'Christian Gangsta', 'Heavy Metal', 'Black Metal', 'Crossover', 'Contemporary C', 'Christian Rock', 'Merengue', 'Salsa', 'Thrash Metal', 'Anime', 'JPop', 'SynthPop' ); sub parse_file { my ($file, $tags) = @_; my ($fh, $err, $header, $size); if (not (defined $file && $file ne '')) { $@ = 'No file specified'; return -1; } if (ref $file) # filehandle passed { $fh = $file; } else { $fh = gensym; if (not open $fh, "< $file\0") { $@ = "Can't open $file: $!"; return -1; } } binmode $fh; # Sanity check that this looks vaguely like an MP4 file if ((read ($fh, $header, 8) != 8) || (lc substr ($header, 4) ne 'ftyp')) { close ($fh); $@ = 'Not an MPEG-4 file'; return -1; } seek $fh, 0, 2; $size = tell $fh; seek $fh, 0, 0; $err = parse_container($fh, 0, $size, $tags); close ($fh); return $err if $err; # remaining get_mp4tag() stuff $tags->{CPIL} = 0 unless defined ($tags->{CPIL}); # MP3::Info compatibility $tags->{TITLE} = $tags->{NAM} if defined ($tags->{NAM}); $tags->{ARTIST} = $tags->{ART} if defined ($tags->{ART}); $tags->{ALBUM} = $tags->{ALB} if defined ($tags->{ALB}); $tags->{YEAR} = $tags->{DAY} if defined ($tags->{DAY}); $tags->{COMMENT} = $tags->{CMT} if defined ($tags->{CMT}); $tags->{GENRE} = $tags->{GNRE} if defined ($tags->{GNRE}); $tags->{TRACKNUM} = $tags->{TRKN}[0] if defined ($tags->{TRKN}); # remaining get_mp4info() stuff $tags->{VERSION} = 4; $tags->{LAYER} = 1 if defined ($tags->{FREQUENCY}); $tags->{COPYRIGHT}= (defined ($tags->{CPRT}) ? 1 : 0); $tags->{ENCRYPTED}= 0 unless defined ($tags->{ENCRYPTED}); # Returns actual (not requested) bitrate if (defined($tags->{SIZE}) && $tags->{SIZE} && defined($tags->{SECS}) && ($tags->{MM}+$tags->{SS}+$tags->{MS})) { $tags->{BITRATE} = int (0.5 + $tags->{SIZE} * 0.008 / ($tags->{MM}*60+$tags->{SS}+$tags->{MS}/1000)) } # Post process '---' container if ($tags->{MEAN} && ref($tags->{MEAN}) eq 'ARRAY') { for (my $i = 0; $i < scalar @{$tags->{MEAN}}; $i++) { push @{$tags->{META}}, { MEAN => $tags->{MEAN}->[$i], NAME => $tags->{NAME}->[$i], DATA => $tags->{DATA}->[$i], }; } delete $tags->{MEAN}; delete $tags->{NAME}; delete $tags->{DATA}; } return 0; } # Pre: $size=size of container contents # $fh points to start of container contents # Post: $fh points past end of container contents sub parse_container { my ($fh, $level, $size, $tags) = @_; my ($end, $err); $level++; $end = (tell $fh) + $size; while (tell $fh < $end) { $err = parse_atom($fh, $level, $end-(tell $fh), $tags); return $err if $err; } if (tell $fh != $end) { $@ = 'Parse error'; return -1; } return 0; } # Pre: $fh points to start of atom # $parentsize is remaining size of parent container # Post: $fh points past end of atom sub parse_atom { my ($fh, $level, $parentsize, $tags) = @_; my ($header, $size, $id, $err, $pos); if (read ($fh, $header, 8) != 8) { $@ = 'Premature eof'; return -1; } ($size,$id) = unpack 'Na4', $header; if ($size==0) { # Zero-sized atom extends to eof (14496-12:2004 S4.2) $pos=tell($fh); seek $fh, 0, 2; $size = tell($fh) - $pos; # Error if parent size doesn't match seek $fh, $pos, 0; } elsif ($size == 1) { # extended size my ($hi, $lo); if (read ($fh, $header, 8) != 8) { $@ = 'Premature eof'; return -1; } ($hi,$lo) = unpack 'NN', $header; $size=$hi*(2**32) + $lo; if ($size>$parentsize) { # atom extends outside of parent container - skip to end of parent seek $fh, $parentsize-16, 1; return 0; } $size -= 16; } else { if ($size>$parentsize) { # atom extends outside of parent container - skip to end of parent seek $fh, $parentsize-8, 1; return 0; } $size -= 8; } if ($size<0) { $@ = 'Parse error'; return -1; } $id =~ s/[^\w\-]//; $id = uc $id; printf "%s%s: %d bytes\n", ' 'x(2*$level), $id, $size if $debug; if (defined($data_atoms{$id})) { return parse_data ($fh, $level, $size, $id, $tags); } elsif (defined($other_atoms{$id})) { return &{$other_atoms{$id}}($fh, $level, $size, $tags); } elsif ($container_atoms{$id}) { return parse_container ($fh, $level, $size, $tags); } # Unkown atom - skip past it seek $fh, $size, 1; return 0; } # Pre: $size=size of atom contents # $fh points to start of atom contents # Post: $fh points past end of atom contents sub parse_moov { my ($fh, $level, $size, $tags) = @_; # MOOV is a normal container. # Read ahead to improve performance on high-latency filesystems. my $data; if (read ($fh, $data, $size) != $size) { $@ = 'Premature eof'; return -1; } my $cache=IO::String->new($data); return parse_container ($cache, $level, $size, $tags); } # Pre: $size=size of atom contents # $fh points to start of atom contents # Post: $fh points past end of atom contents sub parse_mdat { my ($fh, $level, $size, $tags) = @_; $tags->{SIZE} = 0 unless defined($tags->{SIZE}); $tags->{SIZE} += $size; seek $fh, $size, 1; return 0; } # Pre: $size=size of atom contents # $fh points to start of atom contents # Post: $fh points past end of atom contents sub parse_meta { my ($fh, $level, $size, $tags) = @_; # META is just a container preceded by a version field seek $fh, 4, 1; return parse_container ($fh, $level, $size-4, $tags); } # Pre: $size=size of atom contents # $fh points to start of atom contents # Post: $fh points past end of atom contents sub parse_mvhd { my ($fh, $level, $size, $tags) = @_; my ($data, $version, $scale, $duration, $secs); if ($size < 32) { $@ = 'Parse error'; return -1; } if (read ($fh, $data, $size) != $size) { $@ = 'Premature eof'; return -1; } $version = unpack('C', $data) & 255; if ($version==0) { ($scale,$duration) = unpack 'NN', substr ($data, 12, 8); } elsif ($version==1) { my ($hi,$lo); print "Long version\n" if $debug; ($scale,$hi,$lo) = unpack 'NNN', substr ($data, 20, 12); $duration=$hi*(2**32) + $lo; } else { return 0; } printf " %sDur/Scl=$duration/$scale\n", ' 'x(2*$level) if $debug; $secs=$duration/$scale; $tags->{SECS} = int (0.5+$secs); $tags->{MM} = int ($secs/60); $tags->{SS} = int ($secs - $tags->{MM}*60); $tags->{MS} = int (0.5 + 1000*($secs - int ($secs))); $tags->{TIME} = sprintf "%02d:%02d", $tags->{MM}, $tags->{SECS} - $tags->{MM}*60; return 0; } # Pre: $size=size of atom contents # $fh points to start of atom contents # Post: $fh points past end of atom contents sub parse_stsd { my ($fh, $level, $size, $tags) = @_; my ($data, $data_format); if ($size < 44) { $@ = 'Parse error'; return -1; } if (read ($fh, $data, $size) != $size) { $@ = 'Premature eof'; return -1; } # Assumes first entry in table contains the data printf " %sSample=%s\n", ' 'x(2*$level), substr ($data, 12, 4) if $debug; $data_format = lc substr ($data, 12, 4); # Is this an audio track? (Ought to look for presence of an SMHD uncle # atom instead to allow for other audio data formats). if (($data_format eq 'mp4a') || # AAC, aacPlus ($data_format eq 'alac') || # Apple lossless ($data_format eq 'drms') || # Apple encrypted AAC ($data_format eq 'samr') || # Narrow-band AMR ($data_format eq 'sawb') || # AMR wide-band ($data_format eq 'sawp') || # AMR wide-band + ($data_format eq 'enca')) # Generic encrypted audio { $tags->{ENCODING} = $data_format; # $version = unpack "n", substr ($data, 24, 2); # s8.16 is inconsistent. In practice, channels always appears == 2. # $tags->{STEREO} = (unpack ("n", substr ($data, 32, 2)) > 1) ? 1 : 0; # Old Quicktime field. No longer used. # $tags->{VBR} = (unpack ("n", substr ($data, 36, 2)) == -2) ? 1 : 0; $tags->{FREQUENCY} = unpack ('N', substr ($data, 40, 4)) / 65536000; printf " %sFreq=%s\n", ' 'x(2*$level), $tags->{FREQUENCY} if $debug; } $tags->{ENCRYPTED}=1 if (($data_format eq 'drms') || (substr($data_format, 0, 3) eq 'enc')); return 0; } # User-defined box. Used by PSP - See ffmpeg libavformat/movenc.c # # Pre: $size=size of atom contents # $fh points to start of atom contents # Post: $fh points past end of atom contents sub parse_uuid { my ($fh, $level, $size, $tags) = @_; my $data; if (read ($fh, $data, $size) != $size) { $@ = 'Premature eof'; return -1; } ($size > 26) || return 0; # 16byte uuid, 10byte psp-specific my ($u1,$u2,$u3,$u4)=unpack 'a4NNN', $data; if ($u1 eq 'USMT') # PSP also uses a uuid starting with 'PROF' { my ($pspsize,$pspid) = unpack 'Na4', substr ($data, 16, 8); printf " %s$pspid: $pspsize bytes\n", ' 'x(2*$level) if $debug; ($pspsize==$size-16) || return 0; # sanity check if ($pspid eq 'MTDT') { my $nblocks = unpack 'n', substr ($data, 24, 2); $data = substr($data, 26); while ($nblocks) { my ($bsize, $btype, $flags, $ptype) = unpack 'nNnn', $data; printf " %s0x%x: $bsize bytes, Type=$ptype\n", ' 'x(2*$level), $btype if $debug; if ($btype==1 && $bsize>12 && $ptype==1 && !defined($tags->{NAM})) { # Could have titles in different langauges - use first $tags->{NAM} = decode("UTF-16BE", substr($data, 10, $bsize-12)); } elsif ($btype==4 && $bsize>12 && $ptype==1) { $tags->{TOO} = decode("UTF-16BE", substr($data, 10, $bsize-12)); } $data = substr($data, $bsize); $nblocks-=1; } } } return 0; } # Pre: $size=size of atom contents # $fh points to start of atom contents # Post: $fh points past end of atom contents sub parse_data { my ($fh, $level, $size, $id, $tags) = @_; my ($data, $atom, $type); if (read ($fh, $data, $size) != $size) { $@ = 'Premature eof'; return -1; } # 3GPP - different format when child of 'udta'. # Let existing tags (if any) override these. if (($id eq 'TITL') || ($id eq 'DSCP') || ($id eq 'CPRT') || ($id eq 'PERF') || ($id eq 'AUTH') || ($id eq 'GNRE')) { my ($ver) = unpack 'N', $data; if ($ver == 0) { ($size > 7) || return 0; $size -= 7; $type = 1; $data = substr ($data, 6, $size); if ($id eq 'TITL') { return 0 if defined ($tags->{NAM}); $id = 'NAM'; } elsif ($id eq 'DSCP') { return 0 if defined ($tags->{CMT}); $id = 'CMT'; } elsif ($id eq 'PERF') { return 0 if defined ($tags->{ART}); $id = 'ART'; } elsif ($id eq 'AUTH') { return 0 if defined ($tags->{WRT}); $id = 'WRT'; } } } # Parse out the tuple that contains aacgain data, etc. if (($id eq 'MEAN') || ($id eq 'NAME') || ($id eq 'DATA')) { # The first 4 or 8 bytes are nulls. if ($id eq 'DATA') { $data = substr ($data, 8); } else { $data = substr ($data, 4); } push @{$tags->{$id}}, $data; return 0; } if (!defined($type)) { ($size > 16) || return 0; # Assumes first atom is the data atom we're after ($size,$atom,$type) = unpack 'Na4N', $data; (lc $atom eq 'data') || return 0; ($size > 16) || return 0; $size -= 16; $type &= 255; $data = substr ($data, 16, $size); } printf " %sType=$type, Size=$size\n", ' 'x(2*$level) if $debug; if ($id eq 'COVR') { # iTunes appears to use random data types for cover art $tags->{$id} = $data; } elsif ($type==0) # 16bit int data array { my @ints = unpack 'n' x ($size / 2), $data; if ($id eq 'GNRE') { $tags->{$id} = $mp4_genres[$ints[0]]; } elsif ($id eq 'DISK' or $id eq 'TRKN') { # Real 10.0 sometimes omits the second integer, but we require it $tags->{$id} = [$ints[1], ($size>=6 ? $ints[2] : 0)] if ($size>=4); } elsif ($size>=4) { $tags->{$id} = $ints[1]; } } elsif ($type==1) # Char data { # faac 1.24 and Real 10.0 encode data as unspecified 8 bit, which # goes against s8.28 of ISO/IEC 14496-12:2004. How tedious. # Assume data is utf8 if it could be utf8, otherwise assume latin1. my $decoder = Encode::Guess->guess ($data); $data = (ref ($decoder)) ? $decoder->decode($data) : # found one of utf8, utf16, latin1 decode($utf8 ? 'utf8' : 'latin1', $data); # ambiguous so force if ($id eq 'GEN') { return 0 if defined ($tags->{GNRE}); $id='GNRE'; } elsif ($id eq 'AART') { return 0 if defined ($tags->{ART}); $id = 'ART'; } elsif ($id eq 'DAY') { $data = substr ($data, 0, 4); # Real 10.0 supplies DAY=0 instead of deleting the atom if the # year is not known. What's wrong with these people? return 0 if $data==0; } $tags->{$id} = $data; } elsif ($type==21) # Integer data { # Convert to an integer if of an appropriate size if ($size==1) { $tags->{$id} = unpack 'C', $data; } elsif ($size==2) { $tags->{$id} = unpack 'n', $data; } elsif ($size==4) { $tags->{$id} = unpack 'N', $data; } elsif ($size==8) { my ($hi,$lo); ($hi,$lo) = unpack 'NN', $data; $tags->{$id} = $hi*(2**32) + $lo; } else { # Non-standard size - just return the raw data $tags->{$id} = $data; } } # Silently ignore other data types return 0; } 1; __END__ ############################################################################ =back =head1 BUGS Doesn't support writing tag information to MPEG-4 files. If you find a bug, please send me a patch. If you cannot figure out why it does not work for you, please put the MP4 file in a place where I can get it (preferably via FTP, or HTTP) and send me mail regarding where I can get the file, with a detailed description of the problem. I will keep a copy of the file only for as long as necessary to debug the problem. =head1 AUTHOR Jonathan Harris Ejhar@cpan.orgE. =head1 THANKS Chris Nandor Epudge@pobox.comE for writing L Dan Sully at Slim Devices for cover art and iTunes/aacgain metadata patches. Ruben Laguna for PSP support. =head1 SEE ALSO =over 4 =item MP4::Info Project Page L =item ISO 14496-12:2004 - Coding of audio-visual objects - Part 12: ISO base media file format L =item ISO 14496-14:2003 - Coding of audio-visual objects - Part 14: MP4 file format L (Not worth buying - the interesting stuff is in Part 12). =item 3GPP TS 26.244 - 3GPP file format (3GP) L =item QuickTime File Format L =item ISO 14496-1 Media Format L =item MP3::Info L =back =head1 COPYRIGHT and LICENSE Copyright (c) 2004-2010, Jonathan Harris Ejhar@cpan.orgE This program is free software; you can redistribute it and/or modify it under the the same terms as Perl itself. =cut # Local Variables: # eval: (cperl-set-style "BSD") # End: MP4-Info-1.13/INSTALL000644 000765 000024 00000002320 10112223603 014710 0ustar00jonathanstaff000000 000000 Method 1a: Standard install on UNIX with make ============================================= Unzip the module as follows: tar -zxvf MP4-Info-1.xx.tar.gz The module can be installed using the standard Perl procedure: perl Makefile.PL make make test make install # You may need to be root make clean # or make realclean Method 1b: Standard install on Windows with nmake ================================================= Use Winzip to Unzip the module. Start a Command Prompt and cd to the directory whete you unzipped the module. You will need an 'nmake' program. This can be obtained from: ftp://ftp.microsoft.com/Softlib/MSLFILES/nmake15.exe The module can be installed using the standard Perl procedure: perl Makefile.PL nmake nmake test nmake install # You may need to be an Administrator nmake clean # or nmake realclean Method 2: Using CPAN.pm (UNIX or Windows) ========================================= If you have CPAN.pm configured you can install the module as follows: perl -MCPAN -e "install 'MP4::Info'" MP4-Info-1.13/Makefile.PL000644 000765 000024 00000000423 10545100665 015647 0ustar00jonathanstaff000000 000000 use ExtUtils::MakeMaker; WriteMakefile ( NAME => 'MP4::Info', VERSION_FROM => 'Info.pm', ABSTRACT_FROM => 'Info.pm', AUTHOR => 'Jonathan Harris ', PREREQ_PM => { 'Encode' => '0', 'IO::String' => '0'}, ); MP4-Info-1.13/MANIFEST000644 000765 000024 00000000305 10571140054 015020 0ustar00jonathanstaff000000 000000 Info.pm INSTALL Makefile.PL MANIFEST META.yml README t/clink.wav t/dumpinfo.pl t/faacenc.bat t/faac.m4a t/iTunes.m4a t/iTunes_utf8.m4a t/lossless.m4a t/nero.mp4 t/real.m4a t/test.t MP4-Info-1.13/META.yml000644 000765 000024 00000000760 11424444346 015156 0ustar00jonathanstaff000000 000000 --- #YAML:1.0 name: MP4-Info version: 1.13 abstract: Fetch info from MPEG-4 files (.mp4, .m4a, .m4p, .3gp) license: ~ author: - Jonathan Harris generated_by: ExtUtils::MakeMaker version 6.42 distribution_type: module requires: Encode: 0 IO::String: 0 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.3.html version: 1.3 MP4-Info-1.13/README000644 000765 000024 00000003616 11424444311 014560 0ustar00jonathanstaff000000 000000 ============================================================================ NAME MP4::Info - Fetch info from MPEG-4 files (.mp4, .m4a, .m4p, .3gp) ============================================================================ CHANGES 1.13 2010-07-30 - Fix reported bitrate ============================================================================ DESCRIPTION The MP4::Info module can be used to extract tag and meta information from MPEG-4 audio (AAC) and video files. It is designed as a drop-in replacement for MP3::Info. Note that this module does not allow you to update the information in MPEG-4 files. ============================================================================ SYNOPSIS use MP4::Info; my $file = 'Pearls_Before_Swine.m4a'; my $tag = get_mp4tag($file) or die "No TAG info"; printf "$file is a %s track\n", $tag->{GENRE}; my $info = get_mp4info($file); printf "$file length is %d:%d\n", $info->{MM}, $info->{SS}; my $mp4 = new MP4::Info $file; printf "$file length is %s, title is %s\n", $mp4->time, $mp4->title; ============================================================================ INSTALLATION See the INSTALL file for details. ============================================================================ AUTHOR Jonathan Harris ============================================================================ SEE ALSO Latest version is available from: http://search.cpan.org/~jhar/MP4-Info/ ============================================================================ COPYRIGHT and LICENSE Copyright (c) 2004-2010, Jonathan Harris This program is free software; you can redistribute it and/or modify it under the the same terms as Perl itself. MP4-Info-1.13/t/000755 000765 000024 00000000000 11424444346 014145 5ustar00jonathanstaff000000 000000 MP4-Info-1.13/t/clink.wav000644 000765 000024 00000020412 06606650427 015770 0ustar00jonathanstaff000000 000000 RIFF!WAVEfmt @@fact data n|t6hA>[Ipjոwvk]mq]z{bUulWp_yUu[uqgw_fjx~l||excqYp_bcQx]}Ungyrno{s{arduiUoYUp[oreoh~zumlxohnWYvQhketU}jny{q{uw]{_Ye]_tU~Wzjojrz}qwd~fo_]sWIualk[tlostvuo_r]SmWfmUuWfshxx~o}}cbo[aqU~Ky_ijatk~pruuqn]r[SnY_oSyYyerkwvzm|cycia[yQMqcbp_zgnusykv][sWceU{Q]nhlpswtotilk_rW}SlYbpY}_|hwm}p}g{]tdakU}SvYmkatfxp|ovmwbY}]fdYrW~[shmpnryssmllcyWYo__n_]yh{qxo~kzdodhtWUzahleyf}jzt}m{dbt_jm]zWcqilqrxqstsmsczcz_kffv_fzktn|tznujqpeva_qentgzi}qp|pizewmjsa}czgsqpuouvysvnyje{ipofvjjynzsxrzswqpqn}h~gxmprp|o~o|tr~o}ouorwl}ioyquuwxuwvzr{n}p|ouqsvpzsr|qu|sytwwqxrp|p}uwvvxvzt{vs}q~uyvvyt{r|vt{s}wywxyv{t|vt}s}vywwzu|t}wu|u~x{wzywzt{us|s{wxyy|x{w{y~v~st{uxxt{t}x~w}wxw~vyuuwt~r~s|wzy{{{y|x|yvyr}qrzvuxu|yy|z{xzu}r~pyruyq}sv}y{w}tszrtvp{prytyy{{}y~v|sxurzmn|rvvu{w|{||xyuvxq}m~mwqsyq~sx~{{yytzpn{nsrp{oq}v}z}z|u|pnzortm{mqzuyy}z~wrnyprvk|koytvyxy~yt}pxprum}jmysuyv{{z~w~syprsn{jkxpuwu|w{|y~v{ruspykjzmuts{t|yz|v}txsqwm~k~mwrtyu}w|{y}u{sttoxllyouuu{v~z|}x}uztsvn|lmxquwu|x|{z~v}txurxo~n~pxtvyw}z{}x~u|tvtqxoo|qxvx{z}|{wu|tvurzpp|sywy{{|~ywt|twvszrr}u{y||}{~x}vt|swvtzst~w~{~}|}y}vt}tyvv{u~vy~|}}{{y|wuu{wx{w~x{}}}{|zy{wuu}xz{y~{~}~}~z}y{{x}vw}y||{|}~}|z~y|{z|xyz}|}~~~~~~{~yy}z{|z~z|~}~}~}|~zz~z|{{~{}~}}~|}}{{|}|}~~~}~||}||~||}}~~~}~~}|~}}~}}~~~~~~~~~}~~}|~~}~}~|}~~}}}~}}~~~~}~||~||~}~~}~||}|~||~}}}~~}}~|{~||~{}~~}~|~}{~z{||{{}~}|||}zy~{}}{}~~~|z}|z~xz}{|~|}|{~{z|yx~z}}|~}~~|{~y||zxz}{|~}~~~}z~y|{y}xy}z}~|~~~}zz|zy}xx}z|}|~~|{z|{z~wx}{{|{~}~}|~z||y~wx|zz}z{~~}}||}yx~x{zz~yz}~}~}~|~yy|yz|yxz}|}}~|{~z{{y~wx}{{}{|}~~}||{~yx~y{{y~zz}~|{y~z|{y}xy}{}}|}}~~}}{}|z~yy}z{}z~z|}}|zz}zz|yyz}||}}~~}~|}|{}zy~y}{z}z||}|{{~z|{z~yz~{}|}~|}~}~}|}{yy~z{{z~{{}}}|z~{{|z~zz~|}}|~}~}}}}|~zzz|{|}z{|~|~}{|}{{~zy{}||~}}~~~}}|~|zz}|{}{{|~~}}|{~|{}{{~{}}}~}~~}~}}~{{{}||~{|}~~~}|{~{}}{z|~}}~~~~~|~}|~{{~|}}}|~~~~~}~||}||~||}~~}~||~}}{|~}}~~~~}~}~{|~}~}}~~}~||~}}|}~~~~~|~}~||~}~~~}}~}}~}|~}~~}~~~~}}~||}}}}}~~~~~|||~}}~|}~~~~~~}|{~}}}|~}}~~~~~|||}|}~||~}~~~||{|}}|}||}~}~~|{{~|}}|~|~}}~~}~}~|{{~||||~|}~~}}}~|~{{{~|}}|}}}~}~~}}}|{{|}|}}}~}~~~}}}}}}{~{{|~|}}}~}~~~~}}}}~{~{{~{~}}}}~~~~}~}~}|~|{{|}|~~~~~~~}~}~~|~{|{~}~}~}~~~}~}~}}}|{||~|}~~~~~~~}~}}~|||~}~}}~~~~~~~~}}}~||}~}}~~~~~~~~~~|||~}~~}~~~~~~~}}|}~}}~}}~~~~}}}~}~}}~~~~}|~}~~}}}~~~}}}}~}}}~~~~~}~~}}}~~~~~~~}~}}~}~~~~~~}~}}}~~}~~~~}}~~~}~~~}}~~~}~~~~}}~~}}~~~~~~}}}~~~~~~~~}}~~~~}~~~}~~~~~~~~}~~~~~~~~}~~~~~~~}~~~~~~~~~~~~~~~~~~}~~}~~~~~~~}~~~~~~~~}}~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~~}}~~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~MP4-Info-1.13/t/dumpinfo.pl000755 000765 000024 00000001142 11424440526 016320 0ustar00jonathanstaff000000 000000 #!perl -w use MP4::Info; if (@ARGV != 1) { print "Usage: dumpinfo \n"; exit 1; } my $tags = get_mp4tag ($ARGV[0]); if (!defined ($tags)) { print "Can't get MPEG-4 info: $@\n"; exit 1; } foreach my $tag (sort keys %$tags) { if (exists $tags->{$tag}[1]) { print "$tag\t", $tags->{$tag}[0], "/", $tags->{$tag}[1], "\n"; } elsif ($tag eq 'COVR') { print "$tag\t",substr($tags->{$tag},0,4),"...\n"; } else { print "$tag\t", $tags->{$tag}, "\n"; } } # Local Variables: # eval: (cperl-set-style "BSD") # End: MP4-Info-1.13/t/faac.m4a000644 000765 000024 00000006626 10111523605 015440 0ustar00jonathanstaff000000 000000 ftypmp42mp42isom 9mdat4libfaac 1.24+ (Jul 14 2004) UNSTABLEx(Q !!8(! B!!L-{HI)s3Zm6I /Vb~>?}sc?};>ɸ91?؄s8{G~#|:3#) WKZ-7z [ߨ%Ύ4?mcP|A_Xfhǎ$, )𤋮u#j_k_ Esť+TX̠F,o번 W}k&J,KQɲɗGٿڽ~UxгB@F[R6z|W7 نx(A3/G#}BY!8%j9z'Y44qhigf $b6^׿埸Wu^ }e7l7u_(<1!_yJ(4008S߼i/X#f@GX/{)p ;32>t>M2AP*s)P|H8o x[_bL}}j;a53t7QO y?" ;`~8 ΰl0k0o|3h-l=͈"3jkX?a9- 싀(T,(& P(v AX(& hҪ$S"":aDA% HyoqpYD†߲{79v_?9xT^ss+TK߾Hgzc K .-JnV ~{ӒY^ͦq5x_/7R40mC⨇BC #HK!uD'c0'NOGz|QAX8M -bsfJ&B?Ðza>X&O$jbp1UVLQ-9~6g;ȁ,W\n ?g2۩ē+ԓ`FZ5N >CQ@“-ÓF4D@ixIh@'ǶV;> fw'q:OͧOt#MUD - Pe!@DV&XD--7LLzɜEJOk}`JU?c6oIJfPT*!62̪ϝKVmz5rh8" h - 18/G2_ڴBǒLo1 .Z PAm=isxU-H=Z] +2F#n! P1ThRF?XI\>;@r+&*)6(%PfU @@-P<ՑuB'iu$$c*t-@X5S=S0!`~)BfoǍ\O`,gV2[C&3z\\i$OPԂc@^ZEE_K8QzF,PI~\")6%#4 U^:-@5}}DkNN UqvJ\K-'BA\s!2,~%X?ÿq'p ,t1(M N>94߰Ќ -2W&'W P_*DJhlen":l_ v,p[Ēp0.N-qRDs@j$J3Q/8@PL8c )6%BPb i4(y_x]ᕾͻ**Fg(ruɃ ] -spxR6ՓJ;$#᱒ti|9F ?"g"X^iDI媎e"uÔ@ @q{bI(x`L4Ra-TK5eP&M:Ѱ`q\{wMS5sYe7Yp /8UHʤ\W }U%L0XӭU( ef^IUZh$DxHfD>O@<b?o!A`_Ty"n.+ҡ3 0U5w`X}Hq)#0$}{g,psr/~j:t=d;N q6@;kR=BGofe춦+5VXit6DVt}Mb ঝ‚)22X A )Zs^ XZ `02̷1Ӽr|&A>IkXzbs|d! w08;\Ѝ7\6h᪇X"C:ra\q~BӸaUs盡Q X*V+a^K@p=u@"$8,T-3L1 2Ƹ)7z6-&=е\w݌VEOM-Q͙VsI貶"Hر% j6MILw&m D& (QVι_ -цEdY]E:H0h({Y8 LF< 4X ,T%Btx,kV `0ۭfwvUvjsv.^lt f/E;"J%>7g:=QLDjԴ-푡caFQf>8a|e0T@qF @Z!4b c{0 $$,%E*ѭ"tJ,xWNi5M#d^*$r"Dv%V.rXnX N׵mnauL~<ͰRN>yI}]rf5CU=+;Ų[6$.U{* R &*A@` B"f Tp.C@Fa@jMo9'9NJ8K˕ohK y\KHvlڪ:NjπfΎ1|ᘺVff1=Qk=«SʴI4/pTQ` (e"Mp.F !J YanI9)'1=D6/۰2:?paU36S3`~,23G{HjQnqW1pz|Mؽ~vw B}VZa0ʆކ@G;*",-Do(PX @` >ClO9n9nu5e6NEaD nm:.L{)}n:Vߓk6CeҤO!WT #crUgw3{v,ҝRsæ$nAiD>ޘ2 (S(ݍ1ng8Ǝ2|uөl|*Wh~SȐUfB3HT[`ZH2ܨ4\@@C.C[{ ~M%y٬=Ւtθ۾ t(|g :/Y9u3q2eC4,Jٍ{o%E5XZ@;L,m;MF3<**.Ħ }٨B a@,M Q").F aN,tFvyi& T@|C3PΡe.sܕq6ˋjۇQEy4I5 + n$=N<3dhf)W9 6ݎaFE.5BUC8ﺛ{. GL61`SF gy q! .E `vhP`9Y;șW'0}qקB/Sm%k:;0ԡZp$*UkRP.f˧6üjQFbK$-Lvw6Ys&ۈFkPv:i :q4@,/`a B@TPBTI`p.E U &DRwkͿl}[і{6!4׍/{{)]K]d=~irWo9)X'[yc`g>nG6;^#7#5]3!\{{)FJԣb* . VA(H.Hp.G\0;-c_ElMQ% ?h K.u.trNuSnw2bG՝*?*Νȷl9IKJ H:rB' EZΰ0^.ԣ{+`9nIk"ɠ 0YQvxPa 4,-BadCe!}^ẃ˓ rHܸl6/5Y22Hָգ(gUob:8#H> y/{zM8L*=Np PI@`8.EP zFv,^G֟9\?zWs4s^Rp C׋\ظVO~EMg/5$C̔O}?*hƑ: =_<ټנvOVm(Jt95sĀ(O"AL3.w@.E P5ƬlFVV{oǞw?sF'y(6߽կ3n4ν (T%$pqǙ3k<>%0Oxg\i7}➳=VǬ* ^0(1.g UP呂,@$J !1\QA.CPqP`*>8ijnFNk1v4_#a{av.ȼlJDaQLz 8rar,,ѡ{ΕnżpI8yV0Jԉ!I_JAz9 \ 4BDD A.F `l,q1UeI>`,E-gD#wB}#q#NmAM+z2sfe-=ĀFג(% w`ʒ@,^@#)::N@&F+S%koF ݿ^ٖe߬T21U^') L#MR[t}LiE ZB RD Ȣ>^ha>j R"d0M!GFi !IEC@TP)(8P/tַOŪ- ieٳfe~0(]BQTxHEGCM. x2B-L⏌nמS{_fD3+E+e`Wmiz;6@qQV%PLFvY={Ĉ[?0)(9(brR811y|Ob䜝 uIr%{(q>Zc{@@RT f`bD HB"B&}c3#NHO)*B*c`_Oܪ][wz<>0OsGz:Ju'ݵ{X 2d砓x5L fe#365EaMbOï<7ߣfږ, e`KBQgޢ`I0p*0eH6e`_⪅ N?֬2A9FfTŁrտ:ђ(V^\WSWB}@:nZ) t Qa~qP5h edn)0# b,?уzgV|> ǞB 9]< qNNSﱡI j@mnPIc' cJnX,͒0}/,@*lS8Dc~L6X9 V)+xġ%jM A'([C(8RR'qdrP Hj%)RY 0 DHUdwpReh*_*D3l39y/7R40mC⨇BC #HK!uD'c0'NOGz|QAX8M -bsfJ&B?Ðza>X&O$jbp1UVLQ-9~6g;ȁ,W\n ?g2۩ē+ԓ`FZ5N >CQ@“-ÓF4D@ixIh@'ǶV;> fw'q:OͧOt#MUD - Pe!@DV&XD--7LLzɜEJOk}`JU?c6oIJfPT*!62̪ϝKVmz5rh8" h - 18/G2_ڴBǒLo1 .Z PAm=isxU-H=Z] +2F#n! P1ThRF?XI\>;@r+&*)6(%PfU @@-P<ՑuB'iu$$c*t-@X5S=S0!`~)BfoǍ\O`,gV2[C&3z\\i$OPԂc@^ZEE_K8QzF,PI~\")6%#4 U^:-@5}}DkNN UqvJ\K-'BA\s!2,~%X?ÿq'p ,t1(M N>94߰Ќ -2W&'W P_*DJhlen":l_ v,p[Ēp0.N-qRDs@j$J3Q/8@PL8c )6%BPb i4(y_x]ᕾͻ**Fg(ruɃ ] -spxR6ՓJ;$#᱒ti|9F ?"g"X^iDI媎e"uÔ@ @q{bI(x`L4Ra-TK5eP&M:Ѱ`q\{wMS5sYe7Yp /8UHʤ\W }U%L0XӭU( ef^IUZh$DxHfD>O@<b?o!A`_Ty"n.+ҡ3 0U5w`X}Hq)#0$}{g,psr/~j:t=d;N q6@;kR=BGofe춦+5VXit6DVt}Mb ঝ‚)22X A )Zs^ XZ `02̷1Ӽr|&A>IkXzbs|d! w08;\Ѝ7\6h᪇X"C:ra\q~BӸaUs盡Q X*V+a^K@p=u@"$8,T-3L1 2Ƹ)7z6-&=е\w݌VEOM-Q͙VsI貶"Hر% j6MILw&m D& (QVι_ -цEdY]E:H0h({Y8 LF< 4X ,T%Btx,kV `0ۭfwvUvjsv.^lt f/E;"J%>7g:=QLDjԴ-푡caFQf>8a|e0T@qF @Z!4b c{0 $$,%E*ѭ"tJ,xWNi5M#d^*$r"Dv%V.rXnX N׵mnauL~<ͰRN>yI}]rf5CU=+;Ų[6$.U{* R &*A@` B"f Tp.C@Fa@jMo9'9NJ8K˕ohK y\KHvlڪ:NjπfΎ1|ᘺVff1=Qk=«SʴI4/pTQ` (e"Mp.F !J YanI9)'1=D6/۰2:?paU36S3`~,23G{HjQnqW1pz|Mؽ~vw B}VZa0ʆކ@G;*",-Do(PX @` >ClO9n9nu5e6NEaD nm:.L{)}n:Vߓk6CeҤO!WT #crUgw3{v,ҝRsæ$nAiD>ޘ2 (S(ݍ1ng8Ǝ2|uөl|*Wh~SȐUfB3HT[`ZH2ܨ4\@@C.C[{ ~M%y٬=Ւtθ۾ t(|g :/Y9u3q2eC4,Jٍ{o%E5XZ@;L,m;MF3<**.Ħ }٨B a@,M Q").F aN,tFvyi& T@|C3PΡe.sܕq6ˋjۇQEy4I5 + n$=N<3dhf)W9 6ݎaFE.5BUC8ﺛ{. GL61`SF gy q! .E `vhP`9Y;șW'0}qקB/Sm%k:;0ԡZp$*UkRP.f˧6üjQFbK$-Lvw6Ys&ۈFkPv:i :q4@,/`a B@TPBTI`p.E U &DRwkͿl}[і{6!4׍/{{)]K]d=~irWo9)X'[yc`g>nG6;^#7#5]3!\{{)FJԣb* . VA(H.Hp.G\0;-c_ElMQ% ?h K.u.trNuSnw2bG՝*?*Νȷl9IKJ H:rB' EZΰ0^.ԣ{+`9nIk"ɠ 0YQvxPa 4,-BadCe!}^ẃ˓ rHܸl6/5Y22Hָգ(gUob:8#H> y/{zM8L*=Np PI@`8.EP zFv,^G֟9\?zWs4s^Rp C׋\ظVO~EMg/5$C̔O}?*hƑ: =_<ټנvOVm(Jt95sĀ(O"AL3.w@.E P5ƬlFVV{oǞw?sF'y(6߽կ3n4ν (T%$pqǙ3k<>%0Oxg\i7}➳=VǬ* ^0(1.g UP呂,@$J !1\QA.CPqP`*>8ijnFNk1v4_#a{av.ȼlJDaQLz 8rar,,ѡ{ΕnżpI8yV0Jԉ!I_JAz9 \ 4BDD A.F `l,q1UeI>`,E-gD#wB}#q#NmAM+z2sfe-=ĀFג(% w`ʒ@,^@#)::N@&F+S%koF ݿ^ٖe߬T21U^') L#MR[t}LiE ZB RD Ȣ>^ha>j R"d0M!GFi !IEC@TP)(8P/tַOŪ- ieٳfe~0(]BQTxHEGCM. x2B-L⏌nמS{_fD3+E+e`Wmiz;6@qQV%PLFvY={Ĉ[?0)(9(brR811y|Ob䜝 uIr%{(q>Zc{@@RT f`bD HB"B&}c3#NHO)*B*c`_Oܪ][wz<>0OsGz:Ju'ݵ{X 2d砓x5L fe#365EaMbOï<7ߣfږ, e`KBQgޢ`I0p*0eH6e`_⪅ N?֬2A9FfTŁrտ:ђ(V^\WSWB}@:nZ) t Qa~qP5h edn)0# b,?уzgV|> ǞB 9]< qNNSﱡI j@mnPIc' cJnX,͒0}/,@*lS8Dc~L6X9 V)+xġ%jM A'([C(8RR'qdrP Hj%)RY 0 DHUdwpReh*_*D3l39yxvبws#K \kJMY-:'aHH5 B ^9+ĄRG)=>yn} xܺK#g.F*̕ӷ@HNͥUfr$]::0BR,pYoh4@!ʛϵ_Ҧ}0 Ęc#`8~`(ؐ/|sszؔhÏ2 Ω X` LgZ&QByE#MeOaz_E5Oo+kor!n,H q1 ;uěJXeUmu|,Ŕ@z\ #~%nLG҄eS0 ݥKwT>$@Ehro܅- ): `"y32OB[ݲќ9bl7q ֶNX _I.vzAbH,0~>O}_Ѳ#vp+{D4 dUPinJR<ܟ@Mc8x$2b%dGطeզH;_3HBe  Wvw8wmҿ,zI[TK~Baq,1l46 A剨iG}Zˌޜ?G?F)CNB\}#2Z]ѿJ|Q6H Ϸ C ӥLr⡄0hnE`izC%}%.A) 8cq~!|XӅ+zF\jDuhIVxRx%txxVټ:S$$*(.kCfBؑG֭CNg=4> Y 1[:{&E2k{*fX7@JI{ќfX]7|4] P !žN9?r6z+o7%/< Zr P'VθV}Ic;X)Fi͓jOs) jkHhq1NwxR$u *_S鮟oxiK+`1֞˸XM^⸑!3J=KГ4w8o<{5lr7"6|?&dW!I";q75%9g_/@E;({{kye"%@yH6l:xSY)ʩNPh~M28|km`ɉy$$)U6HiQ_M-pͭ պnv, Kg6S˵ԙ ^͇ ^T2J9]as. o-}`HANgGlB1ubin1e*ctbǔZ3n>n"4HdZ;137~X>԰IT9/F.RvqMV4pOO <߫>Wͳz, [A[ջ"hڊ.LFhU0ܱt)@rc*@^64X:^Ϊ ϛ0G>YP/@fA˝0ږ%D~ߖ`߻gP$.Z<9hql'K>\8kҏ:ayB;'?ݵVO'&v]Xܻ7$GVa6HZHy981EZ2T#2~BfnFEZvB?_ޅ| H&Qh*F&iBsI ͢ FCH>ց/ T?N(\Tzm'!1f۵(dĠJiJGӳj;y"U?P$خvd̪?A|I͠,TНX(6j*B?H1Mb3 "vxZKNtwD= "LsbUٲߒ\ uūl2ˊ$X5x&˭9\0ve߷r^Lwo&Ux%Fr;NE-bF5 7UL)=#"N=N:(Q %:g{ۄnμ&HU %>ȱ$Uj4;f8htɍdԑ[,B!cfkG "p>KsHVq!ѳ"DD-Rݫ̻}GOF^:شRdR ZP}t*6esNkV:Ok"dmN>`Q[,h$FY ŏpn|Yq2l@*MFF!UځFF~aHs1C0kcQG=y(j޿faC2@Ν5jྪvC^j?@vmzY<"Iq |mm r{^|!H/#~q^%Ch_=(}3b QFd74Al<S[cTw+1a]RvTQ&<uF9nl3 Z&)8䤊9Y\3Ц"Jڪ^/*wK)6:?=#Ӣ ,?6.?*wiScK$$T`믧u3%Y^Cd,ǜ5N;kpGRxiƌڋ%aNtZdۓ%&E$4x{q]#}Ӌb"P(Yŗ՚bx3k ,Xکܿ7Q7`kv'P ճZ}b[5d`~J5Ր%o4"T)%Q_qLsF6۵I zoh!r )H^Zw4ֹk'Idْ}bG| IvjƦKN褖➤OKq ".|m!'N).\q;Ɏnnʦ(,FR Fs y;<$O%'~1j'vow-(UO.lǮ%PiqqVddȽ3sN\[s|! Kϟ=R˒h"bJ#}Lmty*]3Te5(rbTQ \%z Z_SRؕ fޕ4QȼR1ma]wDE@3 ǖ= 4'T-XِU>ŐK'yĖS[/uRf)&6KM}#%$!'ͨ˱$.mrYIݦjV;mdi(Vꇘ<'1,ŘYg&Νx^jqDUWaq. Him =M$CEH(fzή2:yǢՔQE.M d:|c,zϓل7905 lrxr T\Di!,<\}v6J|Y;1zA(}sܦRKgAeW.ky$Vt@YQN3jzPqIMyTS+=t8Ru(*O%rVrr$ߣ>k, #y";QYAݚyXsM0Jν67p_'Lщx:5/eytruIfIkcIe+UIT$Aˆa,C]V-VL:γWA&XɬpӲ<+ 0b88gQmZF)< ٕ֝>QB);aECͰEkyk5PEc' Ub?cNqsQm38"8N@ppy8ؘ)FTtcJD,:-Ն)Gl/9K0ѡb'[n͆!->ӢT{^1ʟ?s6c1x+G¡ w sԦcpT1 s@Ȓ}޿jՄK;.3>B_Cb)K}~ `Wdl+ ~ϧHRm4nns,&VȞ_T!QT!1@&DTGL{vV| N\G2#$P졫rOک.2s߉ߟ!ذ#HW,t۳C`{_(Ū<tGw!`g_O3 3f=hbktg㔸.jnH:EfY[\iD,pZl9p75ƚ6twی JBGU&Z :5eIg\,t9^Iy~l1ұx2),K~>0DdbHÿ́[ܜR&mM)>zޕQ])CP1(6)s΋Zf:D." Ŧ0o=(kӍWؘ8V__bhKz1R|IBTf6bVL싢FE!3!1tR=:Iqg)d>[ +lQe!*_4ta=P|Dn mTbC8`?*yzLM]2M#rCF~8rLvXtr'ТEM2F}{i6|HY"ye,rχ*:?k4U"%fǷcd1WdHs%bgn4 SSKb4G%f3dgz%a^ʳ6^,-Q5,2#N,Ը]u-j>S'HTGz;<}9Pi-&}$&Ya*cd4i8I)-y0ly$l,j-9RerEupKL!-Mh[0X8[S`˟DtЭW3M(՜yC՝{"z C/*iͼ'兪F< Vt'Ld/W>(3;7W"X@OTJ\ m.wFMR -LJ'L$u/k.x6."{'LEo{C)i:c"h>LlKUBٽJ uR,igל0!J*;a|X n5:)-C+Z%Jb38h薼=ۢAJ ⬴69a0Ԧm5U kF(D0Z˽ϑT*<\H`PX pPt !P@9'Ze4*"S3A*IJuEh}& cO5yS3h)s )Gu9Sy3 S ]CHXN4͖) X|C:zJ@X=xE۟dh]"(̆GG6]} BbHwգ qi2«&AK_ bXʞs}$y'^VUjŻ\&U/1Zq++Ɋ!a/fFul{v !EB|16i|b K \RGLV͏N,G*;j$1sB94f_O>U*48zBō]۪=6 E.ЙuE1389 Il<DH6J"!'[UZ*MyI @ 4&A֪"-fHagJ-$f ĚztVwR~4~b^-t<"=)g;tj$SVB|1-5:Qw:GR9< riߧ JsF sRY;V&sm\C{1VZ0-*55%=,9*lz;dS͊Umj_&i$QXX_"Zv8Eѐ;S]8%RBΦ^f*QdDB;O;S7wyxiILNypdǼM+T{X4r,fEqCEL :֝gc}"g瘹Jp^-xe=zH^q' CuW5^gaR[L\ҏ#]3gxRz S3x7j_F6LzyΘ鮙TL1@c->[箔BƆ0k?:7[KTʷevDđ}'^W)$}<;Įp(D]$LHx T$d䍑kZڪkFy6,bVTo+=(%&TW+ͳlScXO3_\Q{零5e6L5: Ay C>zd~])<j#%y߭0P*]y""m+JVD8(ҼRơH!YqVFMo4le-d'GûS}\r-/jK M;9)ڊ®hY[qxXx׿B${!!'Z-r1%pF AD%Nlf7E,.3V%xʹVAצ"[Eg20pu)QC,kkjԈǚ!LOM5-n5.ؚiけ+ ajf ,RVoluanandX1_()%4[J<[·Zώwt X$ H7g8ڥW)ir"4ZHL_b DPoD%1>xe~6bjkdZM/ 3L*S2/^}bq@˄CYDZbwrAk]LabIL6Y4,/ƞ^R6Ud29ҰG B"0~{Tc;=tl6S%=&,DשZLϞd=`+hʽK-.d7".2.}M.7%4{KR: $%+K^p X zwBɐBK$p89`X IUfD+cn{ȓ|sbSُGUL'nh96U^.B+' =-f"!kNHb@RZȒ2]$)\-eJ2RĒVvl9lLtw-'<JD:TZdՖ?m|[l)X۶7:S5\ &|qͻ$h-0p͢TX1T !eʜnMsuKQsy>gmv,N6o d( :Qs@REr,ZNjk&gTca>nLm]1TBNC>)Y؋ QQP(ad 7Hϥ)ŹT-X,<)ՖYJ.7΅BD""fd}8-MD`2:YftGp^ydխe1zk ecK{c"~WlV]xHl+x8˧sEb M#,d%27ݒ,IYrS}9iɶi 5Ag]h龴jԐa9׎yWLm>*2sj>w3;FRe4Nr_)\ɕxU/m,`wc,O|-i!LWri1v%t91+Joz鞯|EWXga#HfR4)j.N}tL*v/Lu \Blb&m7ҋξlYޚJsjMpK"[.cOjRr},1 ~EKm ӣe{F 3JL Drfj8z]ma'PZϿ=4%o%C8f]:7.]7ԁrl62,51:brOih7Jo^e~o2>RGfYt&"n<'IrQ^B yþ!9S[/iX~FfYU8iG-jgͅ ~Jix&XrDb"kPCLYz+-&N"S9=Q6,ol ]fa U;ܔ0qi|inRϗhzbbVzIR;D}9QJ']ԛ&2ZڍyZcZM )iK-fIi^l**e;2i3ҸmV& |V'"nP`I=g.CZ'%,au?m @4¬iMp\otae1] :,#dStîSPe>䇖u"Ǘ M+RS*KGYԑL UGgXc}f; XDvE{o/,f"RQ j jVUc>z)RQ"e:r&DL286i҆TrLr&KH}:-Z!6ڹzy2-6Х3'Uʋ݆riy[^TGBdj#Kܼ[3lH$|5ѹ+ 1+WMʊX4iՔ>%J,DJ"i ů˚vM]LQU5M2<9IF҈!UNOU+JCsPԊutaKV͊\W=2JRYg#]MP[, U57ӛZڟLK41h612^LfK9fX&+F)J`BjtF*-31܌rYܖ)+geV{s6̠mTfD x;3B"a2@gi*&xPJB2,` !,.w؎KkdQwc#j-T:2ܔs~|, @`m5%m}5x۟ZܰXs0Hvbym!7YlڷL'8c92-frqծ\B&nplS4ҳ[ceHYbzbkR0eI Kw2hBЂƲ&rSٯx"N֛u ^*lGuק8Hx!n":r <[z1m;TIJ\բkMwǹ܉X"]PݢrI%׌q)c|TOJ{GqDym٣ i>[a? grLpzd0@QrF$H39!E$FTTtER*V*Ssey$Iޡ RεB/^so};ZLʊ4E|MY ֦fܵ$m B,&/R`︬٦*QYZ^V{H $mk9͞lV TR)NYMs'v)ċ^Xik"eٓWXeR§QZs 8@rPV74SiG:7^zV;kvkP1KHmT6)SYzd",ZPA=nᐝV׾iՊ (,ugD#iX+a*5{$UTRoGm MJ.24MS"LFDsh!e.K5 8TNwH&5UcM hnoEZ-L;)':{w\T3 `;@NC~|4Ӯ\W$s$W:jꕫT,܈)5y6uk] 9Џ6gLnubW$d-ttC&"hl"(#xtPGW Ս'կm<>-c 3RIfohg=_+ejaMJ,MLuFDg-qDB,OB :yS{S:#Vk&Š%R)Jf"t|r察t%BIP+)S-.V"H/ rז 'z2,p *TAV" זoV!d̡ʨ',#R pX2=6<5G}aWT=jے)|bJdլ-bP霅4-sDR#C]s|_hp6񙀇B#h) PB>D"1XKHR%g[,MoP(YԁȔ{w瞮LȐf2-<֋eӷNT"JD%[k2&%Cg=6~ko`A'E_)I/30\I̚sx~|H exX+%$9VO?|8,I4BoOBҺV3D)%kW msču"$&g<SUSZ٫B! &41 &c( srg[xB(q@q&;݉ww-Vl 7Y(ѡArM.IZyezVakbڂ[ CFr*o9[%zunfVSV3&k(Y Im -nڼe_>XoKJaH"i 8Rs^kr+u%@wɁ;Ӧ +URs u+=ޡf YqbvBJ؛(r-jİzO) "ʈhjiT?E P{^e*g>U [$ʻ^,r嘞_*Aݚ|e~|!+`ް+r%IP+ RETE=WV$C9΅jK"/rN7YKǀE<[6lI$$jm IMyg-/S mP2S~`38̲*APзw+;݅w'L"7I;v% j3H|:ptU0R5IL%Қ3:QhNnM*;܈M|}QkZrr!8ldA܍G@3<$eTI}x9rh2ӀЏN_F4$I։ 4RTFoHFDi4TYBHAI(R;4{T^YҩUKU^u`Ov*+) N.*%),{Ti# ɉUMڽ١88p0učlL7ub*Ju:t%aV$ـjHÇ'y&,R$J ؔڼ% 3Tb(zHXg1#Grd k.ݻtκWlEb"bUCx4HJDax-!)'l%iJKHV0%rZ]dH٧ 4Fj\"o׾-'8 ƪeyf]e ܕp"\3s%kC^+ %B47 3yK>8=c2ʹ!ELIs;EHK40tѣDe42AppR20b|tVe2XZ 4"E9W|]XW-\]/8EWU  TtUyI!A˪M&!6Lp`7t&p4nv<:{oW»"֯RU5kֳ<FtdU@ZWQTR ߞu)Zp$hC9Ӟ=$Y*E)ĥKkŖv΄m2*hѐ̗5yNR#-+T !*+pT)ZQtӉHTzi(Өb,Th2*i Pyw 3Ι 0aMu]uTc?0#kIݢw]hM JZTu N$@E" -&{J"VKG'Z#!Q)%^/998)"#SmVƧ{Ƹ"sQ'4ҳ|j:R5`WyuJNhr3[)h \5"dSwu[pk֚NJFVuOMf|ԋ-+v#8o9̌FȌ&vr0lIR[/,9,D%Lhdߔe^4  Zxfbۧy|#ET ̈BV2TTPlJG#ϫ:e>J"jM*АRYUPlo$f)' 8Y6[`NVx$d]C2 Csjb( X毗wIy؊+HW#bGG6j+7 m GUe,JX)Qu,bufIȦEy $JC(u-K] Nڽ%I#=z8 iJBBㆡUx}'45y91!2dRTC|"3vN3*CJتf.39tgҬK*I :R ߽Mmi)Mys CbMҡ$@I&Pc%IIPHm v6ԵI(oU > :zM:tA$ PG|Zҙc-6$0d8nVuw6$ "(HPUL&JRT5PF&10* @H]%"9o`u"duep FYirRI´@%\k,FhBK*:t*0M L@BCBhF1[dJRɑNLP 41 ΁H`pl$o~u[F馕QelẗqE-« ֻ"@Ҳ ;3t⤘q6lq gd%"qlXH@#jĄq Ϭ͔w|v7cDdHxug"Z̪L@7PZoZ hB LR;h(Lp6(aBHQT$i eFSJc\:xM*!$k_;)+ֹ^#ykMƕ͡p6e]!U'%Z}Д\mڼ Sd堷9#E"{YrU-5ʪsWP#XiPIIv1 ė d$F%ҿֽlqwcEBE^ґHp.{Z2 @=sLVU8Adr5 bSzq|cxÛnjzͱ9^ۏ;(dp 4,pB E"9L'{XRupxrˆH>֛tE= Y~E6m`Nr|eVw/|)bՖvڙN,i\ץů}ޯ9xc,2jtC˟!KR"M!ie&U|rL9*m{ό}K_ 㪕k_h+ҿ}k, 8~$$PL3>û]Erm.\g~ڌG@aC8I5bJf*?y/`hL*)"S4Ŕ84)!Fq LTVx:Iwpxw:ח2_βaﷇ&48:U. `ѢcY5ҵ)T 9u(lҭ #Eeq@|frBFpl|[F.WF@#D C9oA3ɶE-4 JD{h [DZf~Uk+H?4 +дo#`2\w?֣*Y/WYdDC-:@ᾍ6f͈?K k;裮icO7ӊbvܹ]~4(&T!B.$ e@R!NzyWqf6~$s@L_M!6!s:|7h;,+ϺGϻEXEe%:,Q6L|L BA(2G+ViF-NRe?8X* Wi\]$'o8^r7>0 |3m!3ؐD&dFLI&ra2UXT"N Z@¢Y?1s5P 0d61;h-jog Pbl-ԛ.ۦ` ')sp4! !FpXeUUQ(s8눗r rrk3km3Xu67&aSlc +Mv8r*|_gThhqYIzgķUqN'>> FZ&e]Mw|TB~jWVs3˪x 4 qcB "[ў6f4 i$Ǿtpu鄐/*DaJVBR Zl%4}hˢKխ'+{fx4G A!T@[5H_-O|jŅlʼnqS=1'rx_E3   r%3W(x_׃r<]XVvLĨx;2=uLP=3#[RA٫47U )/]n=w[A]׃?)?W+3,;3*&Ьq4˹ 4.S=$uNBBO'\@v$wqy DzaPXĤyqzAq+fdf)|) sB&$ &K+q|dM%fY 89x|1/'kg7߭HÐVWxK"J"5AN, 0&6]YjIX/)N>!dXD!4^N7#r[HQ!+֡D B]ByX` +bd1yt=r}i;^ԃ OCy,`ZW )΀>!dX@@kkwO+ ĠDO&p»"ey$VihD  Q5C$0U?1>z69߭ Fiҝ@X!* ~j<}}uBXECfL]Ib"k(9ܖIOj&x^߱yBUAr{M &DC }k[HMA97n'?5ic89`^Blqoh+/+X_ Kq.pCB(@>!LB y[‚^bH>?k+Oi`^JSP( E"R@ Zְ0sQjk0N2M9%&> BVGs% _&X 6qvqsb0Uzϲ.@|pH2ICާ_KP$ q:'B K#`^O-b^9$%K%i(0Cxܼɾq9 !ǤtY>wRT̠~+"{>_ .Z=C֖@T%0zP Ҁ!z$ye{<_k[@|}IC+S2 蠒0$Iy!Ę-H< FFr&F%D"@ DHdvQ'ĀMq,49rHŗu?vH!$GQf4[F4!EbP، BB )\ϖITP2U=ng, J#rpP7}!cb@pJ[h;ՙ+Ԣ70TFE{cǁ 1HjK8<eO .tO`5ˢU ͚4#(n`}c|H!5!׃{k_^] ~`aD Hxa&8$ඃ |x'a V 1S16". xD .@O!'e @! (+ϤL &V +t-:F>&1~1y\^ϸOY8rI˖N< ,Q$ !&A M"x_'nG A}읛t`M@!b؍ /X$75sD1 "$B\O GD/s} F{| 69)ǽ`ЉK A5Je#l>Ϭ*@@X!Eb؄!4p;~lc=rȭ9,KI7A&Q~j2CID>KZ`NM r d1<7㖷#= ڠ&R!UCc، 1 9W\!:O]?wj#Km9|{|n& v۸es0 gtNFvF&{*9r 0}*zӼJ>4!MbHԺ*>Wb_ ތ>ͬv|n? @V8 %%S[X907x@*:3N]gL~YiD(5R@X(!MCc، 1BKT z1!sE9R}_YREwf_ _WԦ8U(@!͒ D4l0}øNpE}c|x!%c؀@p[O}9 o<|0CڪUo ebgV"B;hbWpįH.9γl%Pn`A@X,!5c؈ 1E./Wz|uZaN :P~]H$.,0.á.Rcfeҙ2uHW fMpTP7w!-c، 1 9 <4\r*ēqt'Q.$D9X]B%;@,D &][}#<~dkpU} !Ec@!80?$~yrq&bVF:S_I<ѥ6:\3ҲB0=-jNw? z%V|F@4΅@o!=Eb /אXʹ(ɣ2KIbB nwN? 868ǧC^c/У4a[01՞if|lElP2OP7x!-b0PqǞ J 7g~c|MQYpIJPEu((:7?(G[D\;sMQ 8gZ2(LLBq/U2P+rrov!ED$zU̽>ǵybnKٙ~ eQuU~3֙9_yA[&QP 8ȰY9dVG &2)G0k9 w__o=ol QgaPHWuYR.`sF>(!bDi]Gp]?Wɴ|遑u;w.[çHRj>XW@~^ ga-I0Y#񮨎F^oH_v煔H`P7x! CbD W CDfƋhBC' r+EeX! 9яz{&XZ$D'U&Ap=Gȉg;8k=dO@5˂,C۵ \kGߘS4֒_HEBnUd(o!$;t8'=݊Lm#>9(A@@d-+&SڧJWxh3G ,Y"WO](sS\RX:# 9R׳ <u̲א<#@X!Ņbn}j,2I/P?Vɀ RP~iE@ ~͛}|P(pTVM(YrD} :@.n* w:vUG<}@R$YE,>!ăcA֜*=?TZ2%8upU= t=#7umº^jrX3b$OrZyJ YqP A,fHbTkk* H@Z>!LciXɉj<+ؠt0tBQF'nh[tNTS `+~Rk` !p3x愐s#ۢ' ހ>! FLq N;ܕ@$]1}Vedm|+otFL`byv`6EJI,|NBLj.)lIQBӨΕ𯓠C8 !MHZ 5J K@yP޿*HwJј!5Aa@WPCuQ5E y\t8 +(n`֬n켗:wӬSH?F=[4D $Yw;7#w񫮐k~?H =zlTtAoTot!%JixPGAƠ] /*"aKI-*7v'DD] ۣNmk$I$pg"Q?``)7£xQZ_"Y(2SUlMNѠo|!%JPtOH5rq2E+q&+r0 a]ґ>N̕R3n&Sl8^PSaCVb8~o7yܿ^@E&e}c{!-D`H>QfX C%Y\-oa #0-qUd/ct wtM?-&I#?3 ZE-kS 66Z׾g-`4b@!D }}HɄu*G•@"TcrmkЄ$vrN?k&xjPvEHu3(1A|YX)Pn񲖾frQI;\P7!D0p  S2 wn Ռ*_`K_3!'YXXXFr%<¤6$ T r>΁P`ŶQƔJP7q!N 7.pP)]@⹧оÁ>UJp0T{Vx✳.|aXlXifd^ild'Ud[8gϋ8W",:>!*5N 5;N}8XQNCē:9;nj5'CSh&emtaDJݲ $] 3%DocDZ!'3L~|~2pz8?ɕC#@N,"k1I!&M'D2Tj2LҐIN?cKI3%Z%M,(:,jP7t!LCR!o aܾ?WR%#!`FZWK.m$3}4\e/W(ɺpލ#>Sh&+4>P0nl d^B7FIȀHWHQN>!2E.D,|AM[$9$FOґF0FFN9 &K.jjţ @qALf{~>,?ナ|x[#1Q@Z`!Uc@hK8v >qb߀58s'p%/b[Wj,~V3JnnNus|i+-dL2@tT6KZCv}c|!]f  !5U/dv3ƭH>Y5b3)58/w]=>݃kGڰvq(%s&N"fnu5KB7ŀ}!=g du7Z du.Ҿ33֓e;xXՐpq0{fty4BGv^r ד Ӕ*@ QЌ|-m([F DP7Àq!M҆d㣯}޶d9n'eK "6=(mRA%tk0dl1R;OiûOn&S xBl=PTjSfq{As-7 =Ì<U9]`v O3(:!]\81Jz;+&ĵtU%bpgbbE7(ݧO&ƑJm24D&< n#JXds,ֈȈ$ۇ$%1]A玪aU+U'~uHR+ȌR`,be+9)r|23TH%(Z*@Xr!Ec@jWUSkcEyp`4Du(}1Ԟ/VktNCξOиdL!;}zog\$M_@ΨϤg-'*r.&8(nUBh18 @ c(Kt\׀%+Z!]EbbZMpPF}n~>z7Y%k}5k5qkqV{AxFl=,¾6gjpȫXRE }c|h!=CcbPj76=cpQdws zkyyn4lq:rO>)Lc:<=]C͛=ӈAzsNzDe!:"Ė'Z`!=cbT~_(pC*|掱;ՈyC x2CG'xVcP/V+]8'^z0y:_kLhP1 !Cc@h/Y!լXyּ5\Ol$2or#*OcL~vv|dtߦ{n2&<1=ۚhX,of8 9 P7ƀu!EcbPi-,h:GXj]AD(V9nY 0:> ss]`z+5ʍZ˴Vjajܤn2Oq5@^\>!=c@Bj\/_%`9}AQ'G3 W]|{ iB@/Z[_&8ln`wߝ.P4ۆE=G죁Ho!5"i>ہ$i,TG#mw=O&HLg9nQb!%Hvp(D-tBIW@EYhu"yDyP07[nľ6nbGj]0SW9?ZL , IX u$yDr@e>@@Xތ!%bDn6d?׷蔕SY!cTbIS-&-fNID揱|; +[4,hXUAҊ#E/j5倱`! +By;:7z5R}~H&j!Eb(kgd:-_wIP$AdpFb@Hs_EJS⧞jaUĴ?xQ>Żh[CI` JnORͶϣ1mwϣ$`m԰09;*Y*7IpSG2k:YY DZT6"2v)F  l=5o5f^RP7t! b@ftC{_;]Ъdv Y =I\&'zwxY/s*"yG>w5(`9=6d ϋ妜CB!5bBi7f#{&ʽ|w;ݤqӼ{3"aD(F2\RߴqP"Hv/|q]CZܵʄ«P7ǀ}!b9ҩ~?A+d7.P8gc'<{IR,6ec[L [u"cvO%RÏ.{ɡN~kWlʎP #yJ !%Cc cS&P;\orB0(.T$;ssY2TdT/L9䜻fX+aM$h>PǛ0 RaB 0!!÷yҿ=CFV__g͘6pf)NMdiPbr&'Þ\e_VU#oU >虉!6 !@X"!Cc͙y7߃?>0n]#OaS1!ϟ$CVRf8$] {͜2#e_ }VovM_Sg~A?&x`eL}c|8!%H@hrC` u([f%jgOzT aJ_M?;}=Nuoώ.ЮFqDc.l`af.I+۫,e8\io! ")UNW6G{^}b;yq6g89(%r-=  Qi<-l*Qmm+|ƮVM aձSH6jφvRz(=!!6_m ߾^@8"e0*3֠֎KZgqsHVh d:ypReMy40;@~/*)D]ţqDq@'s,Jy]&b(./ PP7z! !iٯW )1kPfD ;s@Jq[,z^Nqw5-=5ݟ‚|&}3Ktt'@fŊP%nKkTP7Àw!%Մ#r2tuVQiD]XV7)Թ^?uϤy+o]&< =>ӭ?cg|(—q8TUSsR 7"|Z~<곤VET!%Bj<5u~}L#+_ۦG^7J!h_Vk,2դ(<6Iapp 6PrpR~=,O\!Z'ɡ|{N7ξ=X˿n7$4!E)xww%t++9YSE -ů9*W#"j}(`=$ ѯwJy6Js (*j!=V!H;K>4 AJri;;Etg{_<Mf̶hܗ6*kBK#h.́l CUsTA~6vW(?!=P J麹CvW B( vah]DoFL'r2_~L Ͷ(h!SB}Ǔ ާ$Msf9Vϩ!N:fOy!LB(ˑJX FTXF(ހ:!FҕDPiO1Q,ȜDa䴧W$c#4).f1t|O 0`jǨ[meY˧*M3om] Ց*R7Às!Fxp%7Vu"4i(VdR7.h1 s!Ē&5)0tJ;'~Vɬ4%n4Pc}n1qC.hbf\rd͛!-J p›.>~lF&~;g=g +vs<4 .:]cH;ne/՝lIIGm0v%kc{$l R0*̞M(_O4v3K6惽=7(\oo9!L7Ed"UZ_طgqK40#M( K4.'myݧHG[e k gPL;J.dG ;G I hj `}Q.G^J?l%S †ޓ*@WH;78+lK"|TJn)G%LH`(葧DEZ|7C)SRS=WXIJ,HHeQk#MtAHηۄץ#yW:Jn)TCpltbeHv'pgo `DLLbz,4C4*P,!z=B"H `J_]E\v2kH v̩,|hvyI`L0FmR28 n =6bĹ`1J 2I? 2h\"6uAG#:9-QJZGTH'VSHk()U%a$ ]Kfi+JTR<rfXŦ+xPֶ( !5?L9q}p') sIw4>&3IɧRKhL* ӨHN }8jI縷I'0UY!>18Vg`1r DZ&V҈7i Ff ?oh! :%߮xCsnO/ýb>!Eăb` Oo ~KNO9"! Cx)}ڷzC/Le f_&t<_`#^UYM2Z@P4!%cġuҏ kx^1/"@=2ne_RYs=h^J2f>=M'. Q &&Cǘ޹ _%Zg,IhZ !c{ySfli= * ItۊV9]R^YU.GNﴠ aՉKe[@'? ȭ 𸤙cnߡi 0-EI@X!MEbb }mo!?^-x6߷ Bb,haCSzӻC\ggHiL9 MXձG'+cBRS/@X!UEbb5Zq anɝ <1Y]枑& M)ϼ~ўu92HXbȾ"M$!MEb،j4 W?^>/uߐzDyYP |p0O:?\߾wT|sP"KYtO!rkJ)We{%D-d,@X!-b,s?Cۓ"sM?^ʧ"Q#UBhVeBl!% .޷ Rq]χʨ#d=OtnK29}19a&2 `P7{!-Cc، 1&%-4璳%Aw *R!c، A 4fN䆲UkҠN@%lt Og31351|(ԜBO*W+HHW}ɲ\WGAA*!E&9<ڧ|WQ.m$@Tp25 XVM)ޛ3\_#t]$i*h>$;};gsJ}|y~ +r4%rLic!ܛtzdTL4y &T]y}c{@!%L X |ϧw^?dtޏ.?K*Nݡq9PlOo{ La>B *@ϻn5"CM @803#h€#HuXY4,:P!%ePЄL'pxߘhLQcx& fKithI($VZ69p< 'ZmI2$GY*mؓA !-Eb ، 1D]qYЖF2f!4_ݢ}EIҫ:= Hm4sT`Mj g,if=d $LtdA:!-bYZk?Q]ƗTJ2"4"Kc;&σW/-ʇ~a1|[Ұ>A219ocrW!!(A[xJB+j!bAI~y{?_ݹ\D.tr($B`L'Zsg*6ƋHs݌;"% ]3.Y=m> X3Qs1DJ&ݚ:UL  %(:! B+*iB?@4zd'6?P6eR`_B\uB.QX&auF_7[ ,t2ʬG}lP^o'Eo$p@ P7Āt! cUaR@&k>Fd!=Y1R?dLܚHgxi؟W>V<~" 9KrP% MO`{\h\g8wAd(rKn&S-}c|!Ccr?Nʰ-b(A-EJkZ; $'MdWFfI0[Iyj6vAJI)qsۇv[#?,*Q#IJJ! Cc@o{1|96t'R_}o]4eo]+Mrx;1 #{9<”<րUW1h^z\ỤUY{y>g1Oq>eԝ1/C[^ڵvm܀l6964 rrUppy",G"]9[s)NN">!Ńc `wQ|WA+__9UuS9D@NΟ>li\=?(غFZʗ%xS{&~2@֏IFuP7ŀ{! WiR] qQ}NQ #; lj<ۈ(7ut̘>Dʝ*/Eڃ;sEK%pk~Ȯ!䍾|un9/F/VLsgĴ.s,8R_@0AP7z!%L@ 1 YqE;Vr *a"1`¨GzQ(k rFO&8GO~*Bs:)M"3(R^FvZ'Q`Y蘈-F,4% @!-NNk5A\cs So4g}o=YX2Ƚϋ[xdɫ F$ܯ\s*]%chԉm5ݯotmh#7y $sPsH wXP7q!5*6 SȄ\<^i\JCi9m4̇,|pZx\l#l3i],Dzlw >}c|@!MFZiʽ\>7Ϋ* kh@Ɩ_L7@Z+IuۚѣUʄVMI>i~3K"aːi8q= R1]@X!D<:)('H<@伄 |KpYT-Y!B%A Y&Q(0gax|h5CK鱃^{o(`nٲt{;r?w?΂*@zzUaǤF\=KC96;E:pۏNr?'] (:!%"A4|ײ-/学qPGXkbu7ҲfniyuxP .U&\؍4x(PQHR~c -r`h@;!R Tvq׷{Mҽfr>D'|?R FI" W]}qkfWIJ|)W"m4&wbymXy$ϔmL}9z|p5@NI ! 06ǖ^?T_M&f6bo{,syn䏘-hͶGI$=wLU٣O͞U\<{\ \gJtI(FQNl>!*ք-^tXFZBy>O|) To~:EbqvSǐIS!-e1'ߴd8lēb;nOfIm$ssDϓ&! FO!7lC0{|+[;-|i#2nw9FHN,%e\a ktTBam|K"Dm'CgrxC]'#&i,Pl%a{va @(xxz!@GF4H\o !LBR꠷` |!x)SP0 P zyWXFdgXpQ,BQ3p_7U!iɇΫbC MbXcIg HO2=~Gq1KAxLLnj۫NI' C-"Ki7ҖUR1"vS̨frFq3gM 0]X9+hRoÓ ْxBEIN&Ƥ|{:˄nݠַALRd/f(=ZP P0 w!z]BAL=_{ p?KGFTvJ$Ha$9h1 1BJJ#-*?$dP$I2)y fܒx.ZErH$ Gpr0 ~SĄ }$BGmn8~|||f&` *>JC^7-`3 'v>qx;!M#d?պTfK O$gO.'a"> b< t8+pd#.EBXM#|\U(l7,P LCK% (P^T w"htkiAA, yȷKɚ~9J>5n!mg\cUKlr$-lVmS7 [P-rw`%"bnMޥW3 t&͝B<\F^1`Ȱa}w/ o~ @|/(}c|(!ef@@"0 /Kuwy$q/pIYMG/rǏZ u_|(y{6@ɑ[8)e#9 ,!Ec؄ Q5y vR<6CzAAy}ȃ!&xXԥFH7ff('*.cr)c!7Q(fX+cߔY@X!Ccpߏo%?MRq8V+ITɌ=2%QY[B*>"PIѮC1騝w<0Q<2F*@X!cb *1DM" 2=L.u9*7E.dzn+gQg̳3{d '$NVZaZIV "$QaU«-XGd€> !%Eb pZv D/~I8rh~rO٨d>W, 9\eQ_"_.u{x!ɦB$nmԏP^#J7Āv!McjSCt zW9}C+o+b͜@WQ'Ne*,tgb, f>CՐlLT]6,=G5pĄ'p퐤i_>CBTM.qIz":?Q9mlEb Q*`! b`!4S>? J 9"\oXH*"D"7 ;s?+yg9AIOg)%+B4y4oL$XE ;uAo*q|RuHpG9ٙ2th  60Z7oL*G:i̯y4dIb' &m=vhcݍ}!NJnD ׈OQD tB=e%u.t!Cbp`}F' 4DiހD)߳ȑEt9{HeNg^wc#PQDŘ@ r9\.,22N80cR!P]~6!@X!ăbpĠcJc3jop9"|D~Eh[k'Q,u9'.$XʆЬxbfygwTB }c|@!%Eb0؜ 1 B f~+Xď[Xr/t6̰wGr. : V/60 H/ NcKA rzuoVWf^7@X"!%EbP؜ 1(4jTxsT}&E&PmCko'W.yUDnu}A [:3`T—g cbM5Na"q*@X !Cb0X4?ߐ:߃XdR?d;;bH0L!b A`T^?  ^AuԬy> dГdr^Yn]z%ԔKAWRxeSS%zN`Fam"%Sz)}TL;z]j`!b A?s 95() 4 &%̞[X0I <^AݳYrbdʥ\pi8\M^rдG XuL'6T"%DI(;!-bw;6@&/bIȌ^D+9~RlLAC9܄ZnTΆfnL0A#dw!'%([^wFn-ʆ[T><!Cb D FzF}_/.$ϩ1W'v0S?R7,{t=)_鱡YrȷqLmiˋa;z})OM97ai!-"!Xg&41a !s}:'}OG"PnO?&|k`G.݀ϫ Lτ B)IH {h³1ܣSDךC޼t_ۑn֐̫:S:QSuO.g;~"=@A7/N@!B[xit׵ I="?7r_}qO+qUPbBwD_!\/h]!ǡS UN8uVu0Ƒ(Fk4rw@SpMB,wAB|r3P1x(?ݘd%bРo^!P 9*iϊ=:[şRy :Z]5TC닓JNQ/kR4a*J0ݫnm^A'xIl 2ȫL=8K:DSPYu}c|! #B=(;-=Ok;]Nf6#-7K(Ig" ~핀#gD;v-BX("E}c|0! $ 7GOݫřPRuYpY?w#XAw?,<]*^>Ն㰯SlnIXY|p=#E @X!%P 8KG_nM?Uol&A:d܂Io cr;oc 1eBԅn> 48co<;LC<ϴl{Do<[a|C=3NpD" V>!P /G`&x_Pge!ㄢEݏP $n΀PYʸ0j@EI뵰q60"xp,lL|,;C/䞫%_uPtVZr  @X!E%9R@JqfO٨;ΣLv~nK!O`\ЇqK"ˡvkEOA^>Ţuԋ;ɱ`a8qUv琷0>{M [s^Š8 J0]%+]!P G;&1# @.z x#*^! W3߲++I4&ϟtח&pp/g'nӱ|㩬bZϺ>\}[VuKH1:BJ8\P7t!- #e]sY+ D /ⲺnNuG =zy:+iY~-R u5k9];on8lמ-̎_ ԰!%P eA$bNO}o )oxǿ:XQM`9DVhȉ{.KPnyȷL\ ̟D G\e"7X߃ Wuy^W,$Z:BjUM!\L lu(~, 8_$ylh격5iވ0o<[F/d3(:NaA! :ڀ>!A`ut}pP(ȴU3u>-<| N<+wʮ+Y$N r/|ir!twrxd*[5c&k!`ou׵}[o)'2E@:[ ڀ>! 6 рF,Ll c~"":>u|~٘bk܆x?wV7oANQM({4FiiI*0>θE {ZFO29\0 !Ml0J~ͿBg:KP̈r.Ebr>t.b۾sƝQhL(4.QEOgUtOuG*IT@ yq6aڢF _!%=P%<31jYA^ZGx 1'Ӕϗ%‘'Ǡ%_. ]r}R$Ř'j.|(䄱 f"Vv$`$C'Gܧ:*Edt0 +ѩLnqZDWPzC"t#K4*Xq!z}BBhz}oC;Z@t")1N2dLuDDЉKuR'< і`$Ebv` ȹ Dc8xV-Sq~9,GJ 2PH($a]B$ d=0˽>J6T=MQU:*RwC;4 >>-`!M ":vq wڔ7YRU-dIT{K[k/J s2س R(D4<"SAꢀ>9ɀ{'9i@.A{@X!uc00j-x~}gܔ+aN'x'&`%GQzχU8ޝ"BK%9 tS/p6-Sgv8X@X!=c0hEy;A/LNh>.?@18Cu9s IE~ ]Lk&(}?n+nfCI r*XbS/c(o!Ec، Q52! GCZN\|q'|ikNZ> ?eq8F$ݯԆ?,%y!S":!ecrEB/!.f~+TtXd`ә$SEΰa$=9$-~NfZVdR >(7e>+AUCX8*HF#Ai 32"bEh}c|P!%Ebb b=(29oҡ e]Ėq0wi+~@rx@8zL6Hr<osyg!^ BIAg`@Q't0N2Qa JqXOZH7u!CcbPhmx`'A JH`paOʫW*7`4Q[uEtHݛ/$:% 00!(FUi@.X!(@;!-Eb `_8? $D D Bn霿d*6a&E@OCZA5'e~K#pܺ2RH Mnj n{4p *N%(!8|*M@ -V>!UEbz5Ox?l{/ |l?錊EψGwwsT7X$ĘJrTemnC>ߠ#QKu(ڨo!-c4㮂ef" "7~\Y@+2LSɢLI,~A`d1q~B9B!Jɘ$'J9ZD~?Cɖc[ܖE rz̚eay_9a0D/:q?!=ăbDlʹ#cg6 yԵdϽ|vٌ~[K<`()h@}r>ʒ~G}oF"2H2%^|@ҪܘPR¶a %9R͊~O|%XRRص}c{8!5Eb؄ Q4J[݃l"7dȤp;=_sf*Yspz]5 o.pxfDiP,P7ˀv!b0pnZ'*V&x$`ɆB ]ByVP940!tysIwKđh@&6gR%/cJ Z€><!%EbqXFaaP՟ [t 42'q;e5}V |i-p`(?+ڱM4t` 2BF48zP7ƀt!%Cc`18'k.V8>cx;U+(2 ] /@$}c,IAJwž|qO q\6, XwOjߛȉ7ƀr!%CcqXb@r8m`+"̠fW6OF ]uIq}yyu/SDx. 2qVz+?o%"]!Ccb@qWtF bs#ɛϳ!#[GeRSlit 21!$tBŤ&XP+R9K!@WB>H!-bP !4M .o쪐BUBu%*; W ЃCECpG6͑]IA֝8+aK2sFPcEo3ڄl jQr!bA &q|>=yeuVe[7QO; Ӈ:FZV;$Y} rI~Ҭ䝞L ku;I]6+P7x!%bHd@>EA%`A/boK}2313;r=tiX˛Ɍ|ZMCo@528gmMqlz:/42s'=YZ(HW@7P7{!%bĠn`?#?\:($}Z|9,.YD=5;_!htB{3#X9.OfLQL̷`ʈ# lZ tDxR`k`A '*P!EbaPL\.W߹^ r2{~ [h7 _knύcDbzf_ Jv< c8=M=c5jI\d1|O`D OBk(5P7w!B@;Wϰ29\L`U#=&.iޓ/$ -w/p.R'ɀpd{ב,j0%g)IܓnV"!"uWDblv/ҺU5@WHT`!Ђ Ѳ>S!bg>?kCg?2 S:SSvn_\ޢL}rt-'raTे"A1r\@8(YQG#)bbf(QT\o!Cb@Bh2pA'Iܷ 4(r;Fovj==¡|&<=tU5w=Y鐄GĄàePu>l(}c|0!b~;oO%5q"7XQqvf&Ԁ?CÝE$Qs">%h@&PG>׉pilSX湅2VBvB9\ո.']>!b@zjKO'VdF.m,+O9?$ GrV&^dyM->VKp)s@GɄt|VG(ZJ͠'8W !=P K8v6t xA-"ʎ|L,-DR|`P_YGrHϻ>ct{&`k,ٴ9}i ?-n.q<~ɲb!J8쬤SH@V@*Ajv.wE,@H &ZH_8>@AEX@p;@ȠJ{t6g1 V`48ᛞ} {o!5Bz6r_Q ڿ?~*'UfVVaNla4A8iX+cK~5 y:Q;5 3j~ zkn[/_c`C}K4.n֠{n'z ĬT`!Մa0΁/zA n_!FLW|ɞ@>Aȴ<6X=c aɑǺyO &CBh0}Wf0 <,S3k*oo!*ل#os]C ,&m<%`Ŝr!Ց@'nq!LD]d-!o2 yX4[,9CQL=(K8l8PBr~퓆N Q+&a@E E[^|i]ҥ-6Ԋh&fR'v$n$\RLC&M|@DLJDWDOO~>O~^'xA b:ȍ)İ̄dd>^!m,C cpR}+n<zewPdWD=b1k/ {;{8b=CQL=(K!zBOQx3Ŷ| Y+8BSAZфRJ+QI&wҬ:ȐN-$,I߻r=bEzDE"Hp]dyH.1IMJX:B"쥣黮&;*G`SShQ!da+뽫;]'qDvb:ޖm_!]B꿠c <3;ɣA XQrdI- M=T?٤i^5֭*ʐ =2ʄkh&-))J\3 rI_)[ !c؄ b4k>@'V*+ 7ajw&? \k(|I<o5 )ZFt֨NL\JR +>w 0MaǎAHTxx v1 E%hבގ^ ,h !5c  q 1ԉDBdg40e4d:$?kݲœ.i<4FSm𖸦oy9~tɽBYrh=HKڣhaaH0J(?!cbPhx 5~O_\hFD2ΈН{|lX' pRMW]C'(@s ٙHC[m+vvJP*P x_'A*o!DOwhj(VK/}B  Ʉo_+wl)>`A 1j'BDB͒,I1I,Oa<դBX&g@q Y;ltsOg4SHg4&/ 1}cz(!m%SاX=(Hё8}咗FLR+SI)=EGE[Ug *)'*^ Ѕ$.&Dm##@Ɋ."#{x pt!$*VLed0pN#|QHyT6h%2B0D?XE!/s_ E9..}cy!Eb؄15僗>A_\"yqOˬx} ع'WN3//Ǧ*OR VO!:F$Lyͼ6 Zrɠ $ !Cc^ek7B %vF49%c0r"?]QƁ8d8^JM\_sGFZMس#X 嬭`)]D@ɦ^Qv4bf[+i /@A;&Hh'ހ>!MCcUѻ:svE ǾQyja+LT1U~C:tVp@RVQe#I+`;$t \k\PHrbsF>t!-by6e<DmtK;V:~{ݑ)wS|yBxIx [ A(($'=Pcl3d!'&xǠ*P7y!-cb9]o_$S~z]sI.בTnB Nj{睞N@P@k"6Jƶ+&P8r@骒w Zy {!-c،!P^'L4*- VĩGf=O?y:*w>az yc4ZkՆ#wL#FZlO7]#wo!c؄ 1Bm *󜀾¬Ob˩-|R9kSvqZM%tDF_ k1:/AA4JvJuq}֠o!-gTX?4:)2 <2st B3 ,F%5"y #&#CHB-(aUкq' вqUD@P `!5brf_Q5%JVhpXcDʂR$iQAz39s=T"~.Gܲ 69ӧZR @9^ Q`@2A@ ^>@!-bQXb ߦ!’Q#ۮtƺU,mcF>gvoj19!\IēsԥZV =.f)alBKYJ! CcP®k>Aw~Kh VM"Hʝ$)v~6K~_gJ3*Qz`~rJto/¹Q' `! Cc @i9&~Oo^My2I5&uHM4D"!?jQ<[+,oxy+*7e3LiYN̉}%b$+@X!5Eb c~*?Ŋ)AT4SuC٤ G٭T$* Ew&OBwo#UY ЈD|Cx =hvh>!%CbAE_":'{F<#K$'-Nm QwmIU7mM@( 1w(t~C;a?#w[ʏYcr~j")@X&!%B# {Os:=aŻ!%88 PRAѝɐK @Z&Օ\AɭDOpdcN'vnbu"C8[EY'$V9өQSQ` .(NL*HU`!Cb@bj/Q`!&@#N\D7uG9QybGhIz_<`Yy7v/r /ڗ&2M"+a@X! Cb )xH<,B!*0!*p-21N]oL} {\^ӹWKC#)w7ދ^4DiO3*䟟)+" sPbӈF@&qX?!bj }/ `]w k['tMt! X#;M(~O~-ɥWA?Aw]Fq~nOI_ඉz^7{! bBNlͮF"0JQsL/ DB,G#F(HO} o-,s:"=#ORsڊN`!b AJFCv|]P"C ['i y 7&2Jm˙,L Bf=]ZnIӔ;ߵ:/\]DX"Ep !bJ`=@m;Xk76g~NLm*''̼L@"t=Ж:Fڢ ![wڴQ!d37V밚Kp I`1R@X!-c GGb^Yk:8ә_)j$Ye R}%.(p_^$LJԄk~7kN-c(UO_LdBB+>? Xꖠo!MFQo5?jjM 'z.:*.Uijv2!(=wɏH _hMuXJ`Fk%! !AwJC ^;.' Q>l0KpTI* p.z ςFH??9#]$#Ijy:OXw=~񪀹+ʶt?H D bgIV(!@X!ULtܮZl qSOLZ_f`Y5Txݽ*U(@r!wpcoc*tEvw5]a2+?ȑ]@X !!A1_$|8>j"pM4r'OejPa0^s =&yԿ:L9aҲQJP&5O}'cn |~#v bfbB;! XX>Wgh>ycf;%. =3%R1(Ug:n*Mx>LC/~}kծi.lMRϪF%}c{!ՄN*Y+Wjciqm20Fz/pc~I3B6YS2bY,MĐ4ytDccVcUz|G",9vg)OضX}Q!-&2Uêy*bufA>QA/Cutd ~u G릟T8F-wGQ#>(3EA`iǗp}BFsͰZ(g9뿋DX"M\DsmB@ A S6C 0vZl&M@X!*%L l^kOD@QP-0RVk!ʣUM<~u ^V )uF^Wۋ;ٝ[1 n L;$h#{(t;7$=豌Ƿr'J`>!L=]M18uNv9[qf/YwBuZ31uU)8! '_MmN$'-mzNI?B.Y՜kEb)vy ݁+)<[QB1avl LzP>)@zLD_s!L?ݢd-!o uB~&ۇHiC: !舝X'%6DD̨@#4#PJ %d0E@bw*õLau[$$XR ]cAy69ZTVЉ=y&=!X"2U²I2TAh"VAnDϠvLdb˲$7v`cA=;I9R8ѸL=<.Ou! D8>_~PG>?PD944{B(|c&)q%ˇ9> =lɅImN!mcؐ Q5!i3\yתGhH&U|ɅNDM5;cp1JNO "w!G!z9- %IxK4fh?'mbEE!ucdbB`k(4#~o̒6kO$Ϯl~,3mAIBL"'䶺|}/3C%fZ@:mGi$`6Q_ԞQ2R /u}c|X!Ec@h+mp_3R>'zi}!8~Cpڝ26"|5X6BCE J"gfq|nZ#żTX,PKG 1gpW iGCa9 x4"K~ Oز;Ir3Suj%uc@3'2T>!UCc؜`!=8#:=s%0sPSR qW fjȞY: T$% <mj58@Ȅ&* #8Kׅ>:;9# oߋ X e}c|H!Eb0D`ȜaS|GA_Pݭ}5l.$ϭZř21#$cvJ}FP^Mj lDp5}(DO E Ԗo!=b@hOP*'Pu} -0#o %=DAؾXAl)sTEV=ၸ#2bV#MT!EEb؄ 1=O`SLo6MScGZ<0ZB?QLQuh\ r>n\7-GNqV+xWsP7ŀx!=Cc؄ 1E !oʄz9v9,cEjT8.~ ZIkBG_z{8}^AO'0]@X,!5Cc؀bPr;?ow޿=jLruL=0UH?cݞA3{jxqoY49}wܳ%JYc즪z>= 52 O:tE9u@X!Ec؀b0{[RƞҸPEUJ7d*(q? IۂWOp Wm evzt jk[ {Xye&+H!=t's2r:M~!@nPP:jZ-hG4mMRhK6[G5Y ן b$|'+"qh=C>SbZ|W2 =eo* `RQwף5w%9f5?uA.T}cz!=F!mn{O'yX˿ʣ5,S}^i&*< ꈈ 8ElWUO"!$Tx{K{P12L IExԸoel&|o~f.6ovΘH$@UGw~. o^!%bD g7Ŝ^7)-y6ɨSC]rsϨLmڙx }K$, R~v<8I5E:F %T!Eb A5C9ՙzϼ]3vQi_39 ?k\2S: ?a!&]LsVd;'%I8q?3V-E o P7ǀr!LA6(? *{j:l+?V–- DNv^k2aTwthmYg?lǠB‚X=S3jqp!bƖ<^N|GkP\BڜbT:1 <9XS6if}c|@!Cc*?I^R7ܘ'`7:Sk9v0&6 R($dXUÉشglR m١5F♟W=@C(*չ =5p@D !=!L }ceFDS |=n[鱿 &\^bOx5-)8pZdZ'\dKeۼ0~zuì&!c Z?7)D3m6_&,@ʡ$0Πh }r;,uπ/;tD rgPJ9hZxzܟk돯s'3tT;lW)D l'b*!c47? `qC'r]-fGX#{>}d?sz"!MLs$uI(}]D2O+l!ΥKcOT@@A=[1K`W2:ڂT !cANAc9ym.\f9y0v̩? Υybb!2Z/)>&2TmDP~0^l_LX# _HL@X!c+F~ۯ&+I}0A@$-؏ Q18l>ot֓ľVƺh9ɥZH $Gg*UՇ wv:7'1Ez(! Fc*)gJEeMJBEe iѝ?I8s|È DH4ޚ QUk<\-, }Enʔ?3G/rc6-Fy|zJUPR([Dcj/U!LNKh3Ggg?5l4ZԄ'.@KϏm#4?|ޠaTƫ0KQ v94(s/~`;%`c7kGE@5P7r!MNqog2Z{~Jʤ]w]'Ă_ǨM5fi/t$ae`ǸP&dq%<-]'`IÝ5Z>'ڂ& ,*IKMpIt4YbbnA*JK34!]qMY:&P?,=vGgsb*'rLajpV@pdHErAVrhz5}>~U<`t-)ṻ@ q()RdUU?!-#s&}c[I&Y$B @M tw h|̙gQԠ=&3GC2(z2w?+0|~Q8?O{r+[ BD ݀?!*=ڈAuPs7L'lL ӳɢ=ֶe'!bl1 $/NNܕ*ˁ H^u!+lRlT&D<;$E D#K8YDYRUD'1HaghF]"Q9V>L!5c ؐ Q4?td3}^ƤO뻋}f@#F̍iJ³ypS?#ctW*Kiv3@:J%|uj2)`}c|h!=cPX%k_#qjT 㡓|2 +jZ|ӓϵ,`f Ly"Z켰mKg|Hm54{BQ(Rdә !ucؐ A9$/o`5oZ7@Gm=f{5XJd>PTy2eP'HUt<7F#3CP SMdXD"'* !E'X_.r%k](sr1W6eD3ۯ"6>@* BɌ΢my8Q Ǒmt'Ɠ*w\de&``?Gnw' ݮx'pv5_c?{Pft Y5Aq@Xަ!#c|s{jY@Hu J Zq"JrЕkeVԦPF#T@E`mP^Vp{5MlٷtQ$ *Ӧ-lqØqXL0Xǎ%owWqv-S#?/{F.6op}cz!%Cch,RyZH@@[L-{q[@`q""~&T,|Kqenv rT( ldaX)Vl7 QdݞP7x!Mc{D)H~E]Oc$zFa P]kA(}/zv, yd]lJTxݕ$Y]GR DٴqZC(嶀> !5cbkƞ_^@Q`$I:m%vqtTB<_Yt7Y!t@DgԤü!,&w%u95Gt#/L+[9FF{JMet#@X!5CcCϸ03 K M.3)#_l/GN)C\i,B s$CJ zɱ?Rg|9$h^!h,K*2f L*)X*AB !5Ebؔ 1@DV>C&B$2swczCwࠃB!.=nT<_%@3Im#|+0A 4!Eb؄U:j(@ N&]ڪ}$paU"G2|N^Dir-7$IJdfCW:PyGqG)on/`bN4`!=b؈ 1Bh锞?2-86P=AP7Ĕ {h^nf{ ւKd>#y+{Ke#6M- jb>8!-cb@pv1 &GV"$Rma?.GfV`n$,ەl1P`[)u C!@YDz=zR !ke !0RH⺀> ! Cc@hu  d~ dL{ JV;RDygQywFuw!5B$[<_}N@[]2qXxza39V7d}9MZh>&崜L, "Y"%PpG#ϫI&ଇK22G+3sԢ+Pikojt3f [g2u/%Q]Dj3:H(>!%EbqXb@rR1|?ʃ3n*5~ڧ#u=:d,f!c&b^HV >%+SEe_Iw_ȠY9_m@ 'PL>,! EbP؜ 1=we*}m"lmӱAВ`Dgr|b~g{YeXc46 {)+#\/9$lTX4}c|! c0ؔ 1Elbx +WMFY8!{@ɵH8}lBF r} 94*HG٩@ VT|4`!bAuV=nLMG$QxD,'&SϷ Z:, B>X! bx2R-<* 3KhĎ"m7[ܼc?#IaH黬qV1*SM%? ŭwr]uvؽ}c|x!b`ペ3?\\Dij J "%m#L9?̿ϸٳҋz"Kkdz@C$DChQLkHZ^q NX^>0! bs0̢wɦ.x92~YR>dOI<"e=VQ*,跀+ͲeACqRi.úaScDL>8!b !9_4x4pD_QA "iQNAps\_hF :c) ImyiTqe~T6B&5}c|H!c 0vm+p\el3iz"RCˬź&?GgqQV !quvQ>B 88DܡM+4XiF7@?]S }c|H! cͣSfkmQZ(%xQZ?+Z(@nAvޭ[[>|zO&K:?tcw*y;INɷj@ŏ)e(zTZ`!c x2`pBݜv 2h%}HOіL_;R j͓ptf穞&#P>'j) @ќn/kloA^Z@!b ҔK)#XW)`וO;nA5j8EIMwB@N<'r^|yP1JV=~4"S$z ]dK(;!-Ui8P; ,?"0u@rK0. QTI e`1-a"1b];8$ÒD<찌" b d@5kpP#HDb1HMܓ&߫9@GDjRyI̠o]!#A P^xgP WRg“A"A /_cܨ=iw-PG:&`B?ɖzδ&% OJJ-%?=Kez_v-JVAd2L-'LBZ$4%_(*YbJ!5$ŔRLtuջOw =mRq2b2d)Tc_uw7T\R,WR)ޞti oArչ%%D_s3f֞qr: .NXq#Qq7!5FMT/RN?tj|[oEe^_ɉX wR.a(;YS|sRj,y bc طA ^ISr]$JcV}c{!Ja,6r$K]RH|Y*$=*h3wx8,]ĕe1_(=(o]OěDŽߒ[vvg|%uo!FaV#LJqm`>!/Υ/j!-U.+hPe ,\W=Xf DkSrMZf ׌}1OªJzޔRB'tIūNG;TRa `P^4+\11 @X!%JnQ"ϺW,/?;wuEP MR=$1Z"Et!?"X9ky#. &d6F&۰(S 6F?!LA밗h PW(uƚ`*P Ҁ@t:PgD!4"Q:횞5FEeAk&$R9~ꊧ:OJU!鿉>!D%%b|-i%vwW}]] mHbG t,J He8u 9yh$)p+7(CtM!)%e|AhQWSNmU-ۭFzՅ]c'ULgCeN>CĄ~$O$i.0bx{eme=Z`*P Ҁ@t:P!zuGЯ~H7] qBp& W<}@8BHQrG̓0}u읋tvl*( ?%Q$=X$f#DOC"M*K$xp @Vi| 2v-{:!HK6jUԑ#2PN|Y,? }kqBpx!mCcZh !ޔ*CzƁFZ.s}UׇV{ً){ޮKɆ~yq_Y@F'p)$['rX95B@TN|,}c|@!Ee@{{7p2NUf 6^ﳙ]Yj, D&~}VtVj멛{wP)IdU[d) O~[76b!*u V၂8elog!M"'>?_ʍD`eTv,MkOB%VcB~NpؖIBr`$ŨX@Pb4#%a.˓8!Cc`!46Kϑ;Eٲ!I kkR|\MqZf+"CC,0쫖 J} >x6Qr_fX+@o! bb@pSW1n? ŸxQÝg0QJkrM&*rCHJrU{u֥1c37HW۱jLOcT 4Ur~^2KDTYP> !-b`!8p%";-^Ur<(9 T/ 4P\[]wgG̠*Hmr8) ǹ|jFI'?GlPWFJ+!UEbĀ!9 jK)s;OA?=:`YW-]8PE_\AY]QfsbqWTVJf8zc{̰fʢ{t%3@%@!=Eb@!AQ|(;xB~?i;whH)6bbGali| ]B u eod>>etʦ-F,:KTŃ}}c|`!ECcؐ A 5%O4 zOO9`,%Y8|R?#!I!K?5ls ~[kb?8r^.~:!Kٟ6'!%G.:w{nχք+\^ޓ;܄ ֓ %XI9ׯ2A4CA#r=fܖ*LT GgQ~y,,7*yKϑG_]DuT Ma8HGYkmʆZ-8ir+dHzϼoX\htX !5Cc؜ A 4qοO__']+k]ڀ\lKNW< %YJ%ד $*@(GMm97߱p@v82dk@"X@(?!-Eb b YQl?Vl+L}ڿn,s授F[UmLc=n;K ʝ 9yq\. BA/DT!=ϠZu<}?#82`t& Tu3>>[x$A^Z+Y` k'UV2k|l>J(첊!5CboGO6m9~'EJj7dH0A008㤭өNmT}tq}PE2AӢ3!Cb۳ *ͬ咓1l w 9,KasEjD9EIuk:6KaCkkP(JCqǠ<~&¢ b4! ăb'1.sA$Ѱmu79iU7xNIܛb{~/&/KT$d7ó$㶈LX?. PxW{su&**E@X!bĀ765 av%.$)GJ\M(Av`cKPwhj5"yNuB7JwZe6jYsFfw|k1 (@;!b x?Sk.%9 j\bTbKAdV}Ӹ6 }+5,;fL&NtRMo Q)ZQ=7/4[ D\ \ 7Ā~!b =1E"ou qF,!d.|%(17uѿ"oDЏ um*j9(<s !=bW ^R|?K^L/~/>}W/w 8) jC鵳'*C',˾Ɣ-]NCB* ETR !bv~x^m )Q&r(XT2F., N[H'cܖp0^7؊3JVmmgtéW@d\Nw 7cDDF@!b\_\-CYz׌sz 1"j&ЪT n Ḍɒph\rKU> YĝeZfUiϐ /b4!Cb@!{y de9(%JD =ģ,d?|2\ >.e`wX2EMk Z_چ: JLP7~!caB j޷m(TOodƯd"Y߲.gj#V0[O}ZW"*AWTnI-UdžFwaSqm?x/&+5 ϐnTO|, }c{!JVr]~Of<.:8],~~`p9=Crۤ%Gq.6" $A+ܕb3G ˝ZW?m5/^=%럷bim}cz!E !>7L#i/Rϊ2ygI'P 8(ﮒm Lxw>뮀oZɫVŷ dwZt|9ek-`=8ÕdrxP nUP}c{!D˧5*ŏ&hOtGDH'A`S/}ZajVYI_^ܐm܁7#d!@ReWi(]F,CUbSx)YMCx8/ !  m hXq-gy=4839,x|i'[̝OQp+o% 6AuFĀugy7Soܿf/e( F>!9%@X!MH\+Lm=[~c'Bf0v ]Fr7'`kXOn`}"@EƤʚߜ.'殨7B|c&FqV>!5POrdÁEk;6lIp2h8?X% I&+o]΋yAY 0^~ԛS̸&epATDP7t! c+ŵƔVUr &*GLH"i5H"dx{}/WIʵc-عXz&VK71&nI pLiqf0g(f6J E! A Z38U*5ULpThՂMpn}pt70PШ$8!-N rCa%Aw6e |[.z v$OhjKmBP!(p!5L{ {6y.#D$XDdYf"o&Y|yDl:3|?=iCEݳ)6ʵ8LlnB"njzf-~?}9&CQ8-OS-2KA =!-F~4H VC!-9RY C!#%HAHNY 9:U+h92>ӎMm<,'Z` llnHf_'.ܫE< $"Y=LA- vD]*gIcDJ}_!,ڃ\Π@Z#x-!*J ]uQd 5(cr{\ Dr|vIxB jA=Dmd䌞fA"x!j_G $\'V q7uJ90WG$伇 ]b: b=Z`*P0 P(@o!z$"^[<||(FePȴ)I9HR,iI"\B$’$e+hH颂~w$y^ɱE8yVCMI^J>b!]Ńb@D`[OqY]N}1K`$ 6&~7m]+ʩ(zPdC@Ż1Ö'qY#ƇBծ+lZm]ux^1N 0!MfPD`ja)]0&DG o;Pf:l2.Ԑ:eGj&Y] 8,RV0_"I8GED_)7U3P7Ā~!5Ccb0{q,_ֿ.΀pAi3~qeCjt٥2`˧ִvl5:܏yW.˂ZG8]vA-$^I;*(?!=Cc،rzB@4|xp5-@l\'5Ҡ29#uU+eu}Vz<tG'w@a/6 URtJ!UCc؄ 1 9Z6dWT{c/{hd~8T}rY̸_ '\=2 $$ 4PŘFPE])d(0ۤ(J}E?-\ݻ_ 0細=h] ɯ@:RD#GP[yX!H%S07I}c|!%Eb ؐ Q<9?Nhd@,CM HN8`вMFy;wZڌ=oapH|fݍ@jdE譫`Ժ!@X$!%cXT 1 9͸U[~O'n̼["٭}bRSRMf2$v߅$s[@3 E+C1rGkgc80TZ*)qqG4{P N:NSJK.L"Bh@:!-Cc2{={ ԰ZDh$ePpM K$]Y u,xMuM>eH2?DV3Iשw+.\FQ@։D7u!-cNmk_fޑ>n~6O4eCu>Wlw* HdJs10NIC')̝Ge;M.L8ՌM 3A+PQ* !=cӺ.;YkCPAeD_|u ^: 5_w796c4NAB@#atuYWѲ}B 7m"L1`Z% j!%bA7+zWcOk^Ef_gq$FKHP*PP>he1\vJZhZ:`NG[{+38S[Eb?#@X.!%b 6霁`* _CC>+_/`]a~QLmE)'~oqNbK+@El~H5ag&# ` .]@X!bXPPHiS<f?>IYT>A86$ nxK ~}P:b@]8Ve9)=RY GT!ŃcQ:?7~eD~q|8MV]t(}+UsЖ:뱺&n] u%l9`A} [lt>%NcwX(9!B%nV~tI, TYLh (]e/YƸ똌}ASavzIp_&QK ҉+r?_uA]U^KRp&mb kqCZgueSn|Kyϊs T UgU'; SDr[+ۘ.S'lvHU Tz!bOӕCC "Q<*HKLm7O.ebRe ?.1sW]GPn/K4=;zkO&n0* o2K#XY[@&0(}> D/$"!C' G"sN_ Et]A<տ9bǫq nAYj{(rl,Us%Ae @!L=fSۺ~, +9m-Dř?Um19L5 ~]潏 rkz'U[IT$:T 0TXbEEF% ` # r> !8+b) &g;p7U]dsخ4|S3P7~!J[rtuH! xf?}3Rg7_OKX'dVe9f$Tb܎Iq 1~|y'ni:j:qY>8g{@! L TG^&C>TLz$GæǔL $-f8;t,K9!'%jTL&v`cLb%7׻p)O 's2|}VNi $!AÒMkg0D 0[Q!C1k 9M !C$;:c*Ba!WMR@kK<^z3PTzKyLĺ2>=`nnTVz*krXVl׿mWy[[SS Ԙ90 &7!%P AfG6HgO5ˇ _}Z o_';1-)W@ϩd?xf;">G`RiIֱVHXrE7|i+3ڜ\͚Uh(Ljp!FӔi|,4I ($'`CV3ڞHDK_Hyg^syT}Qp`s,)5db#-AXel | Xs 2r zLN\FJ$ 瓖>P$(@>!#%wj1b[Șv0 AH>v<d v0T_*ohRHUb#N*$ԼJ [N\wg|up NLS]@X!* Md.RHh ̔R!L7IK 5ªuɦ||8.!&O_ x+}5"Lefڐ6l+Yq al_t~T״X8G&E_@"8LYhp0ܶJG,+L$A5 4VjS^-pw{-඾U4YQ3!X @!CbTtT)N m|Ec]%H>m k^{Jf{-{.2ug鬭.:OMN lUPg] *s@V@@(;!Cb(îXs((Z#'P i^!ZN-hB~%f'@mpە1.N?quyJ:L_JxD3\&ًq}@æ;3?3̕Ȑ @h@X!bCƕ!^{Qm/v^ zj \}HMwIؓsG@*s%6!u }2 O9? @y Z&vMpLe}c{!5!A4O? ݫ;B-)tWqMJ_0&=/˼Luخ+5fgɳI@D:wHWsmhXHCV>)h2I[)@X! "IC :E%ҙؙ4Dˤ ֧WcL曠ϵ&Q0z\'`d~HLϳ=E g@j\s_xhL&RF]~]TF>!$: ~ 8/D\йIП]YUsdO~rhdW/4.+ Y @Lq7 <2pRd&bvM$V4WwJY Y}(YB! *oӻiAo&a0/!﫠 XwՏ;ca[dԿB .D>c^?w $_`ͯ׻W3'!C*sРo!ؐ 8 q !緉SA&)$/i$ I(Lɫ~֙eYD!2"2&|GJGR$}[PX?n@ِc`?ΊLEJot,: sk8͜I'E@7/$`HP;k[HFT$ -!DFQn֊\|<@ # # This program is free software; you can redistribute it and/or modify it # under the the same terms as Perl itself. # use strict; use Test; BEGIN { plan tests => 538 } use MP4::Info; my @mp4tags = qw(ALB APID ART CMT CPIL CPRT DAY DISK GNRE GRP NAM RTNG TMPO TOO TRKN WRT); my @mp4info = qw(VERSION LAYER BITRATE FREQUENCY SIZE SECS MM SS MS TIME COPYRIGHT ENCODING ENCRYPTED); my %mp4s = ( 't/faac.m4a' => { ALB => 'Album', #APID => ART => 'Artist', CMT => 'This is a Comment', #COVR => CPIL => 1, #CPRT => DAY => '2004', DISK => [3,4], GNRE => 'Acid Jazz', #GRP => NAM => 'Name', #RTNG => #TMPO => TOO => 'FAAC 1.24+ (Jul 14 2004) UNSTABLE', TRKN => [1,2], WRT => 'Composer', VERSION => 4, LAYER => 1, BITRATE => 16, FREQUENCY => 8, SIZE => 2353, SECS => 1, MM => 0, MS => 178, SS => 1, TIME => '00:01', COPYRIGHT => 0, ENCODING => 'mp4a', ENCRYPTED => 0, }, 't/iTunes.m4a' => { ALB => 'Album', #APID => ART => 'Artist', CMT => "Comment\r\n2nd line", #COVR => CPIL => 0, #CPRT => DAY => '2004', DISK => [3,4], GNRE => 'Acid Jazz', GRP => 'Grouping', NAM => 'Name', #RTNG => TMPO => 100, TOO => 'iTunes v4.6.0.15, QuickTime 6.5.1', TRKN => [1,2], WRT => 'Composer', VERSION => 4, LAYER => 1, BITRATE => 51, FREQUENCY => 44.1, SIZE => 6962, SECS => 1, MM => 0, SS => 1, MS => 90, TIME => '00:01', COPYRIGHT => 0, ENCODING => 'mp4a', ENCRYPTED => 0, }, 't/lossless.m4a' => { ALB => 'Album', #APID => ART => 'Artist', CMT => "Comment\r\n2nd line", #COVR => CPIL => 0, #CPRT => DAY => '2004', DISK => [3,4], GNRE => 'Acid Jazz', GRP => 'Grouping', NAM => 'Name', #RTNG => TMPO => 100, TOO => 'iTunes v6.0.5.20', TRKN => [1,2], WRT => 'Composer', VERSION => 4, LAYER => 1, BITRATE => 194, FREQUENCY => 44.1, SIZE => 25345, SECS => 1, MM => 0, SS => 1, MS => 43, TIME => '00:01', COPYRIGHT => 0, ENCODING => 'alac', ENCRYPTED => 0, }, 't/nero.mp4' => { #ALB => #APID => ART => 'Artist', #CMT => #COVR => CPIL => 0, #CPRT => #DAY => #DISK => #GNRE => #GRP => NAM => 'Name', #RTNG => #TMPO => TOO => 'Nero AAC Codec 2.9.9.91', #TRKN => #WRT => VERSION => 4, LAYER => 1, BITRATE => 21, FREQUENCY => 8, SIZE => 3030, SECS => 1, MM => 0, SS => 1, MS => 153, TIME => '00:01', COPYRIGHT => 0, ENCODING => 'mp4a', ENCRYPTED => 0, }, 't/real.m4a' => { ALB => 'Album', #APID => ART => 'Atist', CMT => 'Comment', #COVR => CPIL => 0, #CPRT => DAY => 2004, #DISK => GNRE => 'Acid Jazz', #GRP => NAM => 'Nme', #RTNG => #TMPO => TOO => 'Helix Producer SDK 10.0 for Windows, Build 10.0.0.240', TRKN => [1,0], #WRT => VERSION => 4, LAYER => 1, BITRATE => 95, FREQUENCY => 1, # What part of "the sampling rate of the audio should be ... documented in the samplerate field" don't Real understand? SIZE => 131682, SECS => 11, MM => 0, SS => 11, MS => 53, TIME => '00:11', COPYRIGHT => 0, ENCODING => 'mp4a', ENCRYPTED => 0, }, ); # Basic data foreach my $file (sort keys %mp4s) { my $ref = $mp4s{$file}; # Tags my $tags = get_mp4tag ($file); ok (defined($tags), 1, "file='$file'"); foreach my $tag (@mp4tags) { dodata ($tags, $ref, $tag); } # Mp3::Info compatibility ok ($tags->{TITLE}, $tags->{NAM}); ok ($tags->{ARTIST}, $tags->{ART}); ok ($tags->{ALBUM}, $tags->{ALB}); ok ($tags->{YEAR}, $tags->{DAY}); ok ($tags->{COMMENT}, $tags->{CMT}); ok ($tags->{GENRE}, $tags->{GNRE}); ok ($tags->{TRACKNUM}, $tags->{TRKN}[0]); # File info my $info = get_mp4info ($file); ok (defined($info)); foreach my $tag (@mp4info) { dodata ($tags, $ref, $tag); } # OO my $mp4 = new MP4::Info $file; ok (defined($mp4)); my @mbr = @mp4tags; push @mbr, @mp4info; foreach my $tag (@mbr) { dofunc ($mp4, $ref, $tag) } } # Non-ASCII chars - utf encoding my $tags = get_mp4tag ('t/iTunes_utf8.m4a'); ok (defined($tags)); ok ($tags->{ALB}, "A\x{03bb}bum"); # small Lamda ok ($tags->{ART}, "\x{05d0}tist"); # Alef ok ($tags->{CMT}, "Comm\x{212e}nt"); # Estimated symbol ok ($tags->{GNRE}, "\x{1eb4}cid Jazz"); # A with Breve And Tilde ok ($tags->{GRP}, "Grouing"); ok ($tags->{NAM}, "Nme"); ok ($tags->{WRT}, "omposer"); # Non-ASCII chars - latin1 encoding MP4::Info::use_mp4_utf8(0); $tags = get_mp4tag ('t/iTunes_utf8.m4a'); ok (defined($tags)); ok ($tags->{ALB}, "Aλbum"); ok ($tags->{ART}, "א®tist"); ok ($tags->{CMT}, "Comm℮nt"); ok ($tags->{GNRE}, "Ẵcid Jazz"); ok ($tags->{GRP}, "Grou¶ing"); ok ($tags->{NAM}, "Nªme"); ok ($tags->{WRT}, "©omposer"); sub dodata { my ($tags, $refdata, $tag) = @_; if ((($tag eq 'TRKN') || ($tag eq 'DISK')) && defined($refdata->{$tag})) { ok (@{$tags->{$tag}}, 2, "tag='$tag'"); ok ($tags->{$tag}[0], $refdata->{$tag}[0], "tag='$tag'"); ok ($tags->{$tag}[1], $refdata->{$tag}[1], "tag='$tag'"); } else { ok ($tags->{$tag}, $refdata->{$tag}, "tag='$tag'"); } } sub dofunc { my ($mp4, $refdata, $tag) = @_; for my $fn ($tag, lc $tag) { if ((($tag eq 'TRKN') || ($tag eq 'DISK')) && defined($refdata->{$tag})) { ok (@{$mp4->$fn}, 2, "fn='$fn'"); ok (${$mp4->$fn}[0], $refdata->{$tag}[0], "fn='$fn'"); ok (${$mp4->$fn}[1], $refdata->{$tag}[1], "fn='$fn'"); } else { ok ($mp4->$fn, $refdata->{$tag}, "fn='$fn'") unless ($fn eq 'VERSION'); } } } # Local Variables: # cperl-set-style: BSD # End: