#!/usr/bin/perl use warnings; # it warns about undefined values use strict; # it's a lexically scoped declaration use Data::Dumper; use Fcntl qw( SEEK_SET ); $| = 1; #flushing output my ( $lines , $type , $major_version , $revision_number , $flags , $size , $extended_size , $number_flags , $extended_flags ) = "\0"; my ( $frame_id , $frame_size , $frame_flags , $extended_header , $mp3_size , $length_of_data , $lines_0 , $lines_1 , $lines_2) = "\0"; my ( $lines_3 , $length , $characters , $i ); my @word = "\0" x 5; my @memory = (0) x 5; my $source = $ARGV[0] or die "Please provide one .mp3 file to open!\nCorrect syntax perl name of the program (e.g. Exercise3.pl) and name of the mp3 file (e.g. silence.mp3) $!\n"; open(FH, ,"<", $source) or die "Can not open file: $source $!\n"; binmode(FH); # Open in binary mode. if (@ARGV > 1) { print "Please no more than one argument!\nCorrect syntax perl ".$source." and name of the mp3 file (e.g. silence.mp3)!\n"; exit (); } else { print ("\nUser has chosen file: $source to open for reading!\n"); # Header 10 Bytes in total 3 Bytes + 1 Byte + 1 Byte + 1 Byte + 4 Bytes = 10 Bytes seek( FH , 0 , SEEK_SET ) or die "Could not seek: $!"; # Set pointer at the beggining of file (Define possition with SEEK_SET). read( FH , $lines , 3 ); # Read 24 bits (3 Bytes) ID3 and store the data in $lines. Header_ID ( $type ) = unpack ( "A3" , $lines ); # (A) text (ASCII) string, will be space padded. # print("This is Header_ID: $type\n"); seek( FH , 3 , SEEK_SET ) or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 3 byte. read( FH , $lines , 1 ); # Read 8 bits (1 Byte) and store data in $lines Version (1 Byte Major_Version). ( $major_version ) = unpack ( "h", $lines ); # (h) A hex string (low nybble first). # print("This is Major_Version: $major_version\n"); seek( FH , 4 , SEEK_SET ) or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 4 byte. read( FH , $lines , 1 ); # Read 8 bits (1 Byte) and store the data in $lines Version (1 Byte Revision_Number). ( $revision_number ) = unpack ( "h", $lines ); # (h) hex string (low nybble first). # print("This is Revision_Number: $revision_number\n"); seek( FH , 5 , SEEK_SET ) or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 5 byte. read( FH , $lines, 1 ); # Read 8 bits (1 Byte) and store the data in $lines Flags (1 Byte Flags). ( $flags ) = unpack ( "h" , $lines ); # (h) hex string (low nybble first). # print("This is Byte_Flags: $flags\n"); print "TAG Detected: ".$type."v2.".$major_version.".".$revision_number."\n"; if($flags == 0) { print("\nThe extended flags has no corresponding data: \$00 was detected. Proceeding!\n\n"); } else { print("Flags are not empty, we have found these characters: $flags\n"); } seek( FH , 6 , SEEK_SET ) or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 10 byte. read( FH , $lines, 4 ); # Read 32 bits (4 Bytes) and store the data in $lines Size (4 Bytes Size). ( @memory ) = unpack ( "C4" , $lines ); # (I) An unsigned integer. #print Dumper(@memory); # print ("This is the content of lines_0: ".$memory[0]."\n"); # print ("This is the content of lines_1: ".$memory[1]."\n"); # print ("This is the content of lines_2: ".$memory[2]."\n"); # print ("This is the content of lines_3: ".$memory[3]."\n"); $mp3_size = ($memory[0] & 0xFF) | (( $memory[1] & 0xFF ) << 7) | (( $memory[2] & 0xFF ) << 14) | (( $memory[3] & 0xFF ) << 21); print Dumper($mp3_size); $length_of_data = $mp3_size; # End of Header 10 complete Bytes # At this point we want to make sure that we have an extended header (ID3v2 flags %abcd0000) # Bit 7 of (ID3v2 flags %abcd0000) if is 1 (active indicates that there is extended header if is 0 # it means there is no extended header. If extended header exist proceed else skip. if (( $flags & (0b01000000) ) == 0b01000000 ) { # Begging Extended header (Optional not vital for correct parsing). # Extended Header in tppotal 6 Bytes, size 4 bytes memory size 4 Bytes is enough to read binary no characters # no need for binary to string conversion no need for terminating string character ('\0'). # Emptying memory for future use. @memory = (0) x 5; read( FH , $lines, 4 ); # Read 32 bits (4 Bytes) and store the data in $lines Extended size. ( @memory ) = unpack ( "C4" , $lines ); # (I) An unsigned integer. print ("This is the extended size of lines_0: ".$memory[0]."\n"); print ("This is the extended size of lines_1: ".$memory[1]."\n"); print ("This is the extended size of lines_2: ".$memory[2]."\n"); print ("This is the extended size of lines_3: ".$memory[3]."\n"); # Due to Sync_safe remove the 0 from the beggining of each stored element and Bitwise, # although we are working with unsigned characters and integers it is a good practice. # Synchsafe integers are integers that keep its highest bit (bit 7) zeroed, making # seven bits out of eight available. $extended_size = ($memory[0] & 0xFF) | (($memory[1] & 0xFF) << 7 ) | (($memory[2] & 0xFF) << 14 ) | (($memory[3] & 0xFF) << 21 ); read( FH , $lines, 1 ); # Read 8 bits (1 Byte) and store the data in $lines Flags (1 Byte Flags). ( $number_flags ) = unpack ( "c" , $lines ); # (h) hex string (low nybble first). print("This is the number of flags: $number_flags\n"); read( FH , $lines, 1 ); # Read 8 bits (1 Byte) and store the data in $lines Flags (1 Byte Flags). ( $extended_flags ) = unpack ( "C" , $lines ); # An unsigned character (usually 8 bits). print("This is the extended header flags: $extended_flags\n"); print("This is the extended header size, after sync_safe: $extended_size\n"); # From the stored value we subtract the Extended Header to get the total size so far. $length_of_data = $length_of_data - $extended_size; # Reposition the seek pointer after the Extended Header. seek( FH , $extended_size + $mp3_size , SEEK_SET ) or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move $extended_size + $mp3_size. # End of Extended Header (6 Bytes in total) } else { # Set the pointer after 10 Bytes. seek( FH , 10 , SEEK_SET ) or die "Could not seek: $!"; # Based on position 0 with SEEK_SET we move 10 byte. # print("This is the length of data: ".$length_of_data."\n"); until($length_of_data == 0) { # Begging of Mp3 Frame (10 Bytes in total), 4 Bytes Frame_ID + 4 Bytes Frame_Size + 2 Bytes Frame_Flags = 10 Bytes. # Loop through until the end of length of data previously measured. read( FH , $lines, 4 ); # Read 32 bits (4 Bytes) and store the data in $lines Extended Header. ( $frame_id ) = unpack ( "A4" , $lines ); # (c) signed char (8-bit) value. print("This is the frame_id: $frame_id\n"); read( FH , $lines, 4 ); # Read 32 bits (4 Bytes) and store the data in $lines Extended Header. ( @memory ) = unpack ( "C4" , $lines ); # (I) An unsigned integer. $frame_size = ($memory[0] & 0xFF) | (($memory[1] & 0xFF) << 7 ) | (($memory[2] & 0xFF) << 14 ) | (($memory[3] & 0xFF) << 21 ); read( FH , $lines, 2 ); # Read 16 bits (2 Bytes) and store the data in $lines Frame Flags. ( $frame_flags ) = unpack ( "A2" , $lines ); # (c) signed char (8-bit) value. $length = length($frame_id); # print("This is the length of frame_id: ".$length."\n"); foreach($frame_id) { if ( $frame_id eq "TALB") { print "I have found one matching pattern: TALB\n"; } elsif ( $frame_id eq "TCON") { print "I have found one matching pattern: TCON\n"; } elsif ( $frame_id eq "TIT2") { print "I have found one matching pattern: TIT2\n"; } elsif ( $frame_id eq "TPE1") { print "I have found one matching pattern: TPE1\n"; } elsif ( $frame_id eq "TRCK") { print "I have found one matching pattern: TRCK\n"; } elsif ( $frame_id eq "TYER") { print "I have found one matching pattern: TYER\n"; $length_of_data = 0; } # End of Mp3 Frame (10 Bytes in total), 4 Bytes Frame_ID + 4 Bytes Frame_Size + 2 Bytes Frame_Flags = 10 Bytes. } # End of until } # End foreach } # End of else condition }# End of Big else after argument condition close (FH) or die "Can not close file: $source: $!\n"; $| = 1; #flushing output