#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use Fcntl qw( SEEK_SET );
use constant ARGUMENTS => scalar 1;
$| = 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 , $found , $buffer , $new );
my @word = "\0" x 5;
my @memory = "\0" x 4;
my $source = $ARGV[0]
or die "Please provide one '*.mp3' file to open!\nCorrect syntax perl $0 and name of the mp3 file (e.g. silence.mp3) $!\n";
open(my $in, ,"<", $source)
or die "Can not open file: ".$source." $!\n";
binmode($in); # Open in binary mode.
if (@ARGV > ARGUMENTS) {
print "Please no more than ".ARGUMENTS." argument!\nCorrect syntax perl $0 and name of the mp3 file (e.g. silence.mp3)!\n";
exit(0);
}
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( $in , 0 , SEEK_SET )
or die "Could not seek: $!"; # Set pointer at the beggining of file (Define possition with SEEK_SET).
read( $in , $lines , 3 )
or die "Couldn't read from ".$source." header first: $!\n"; # 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( $in , 3 , SEEK_SET )
or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 3 byte.
read( $in , $lines , 1 )
or die "Couldn't read from ".$source." header second: $!\n"; # 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( $in , 4 , SEEK_SET )
or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 4 byte.
read( $in , $lines , 1 )
or die "Couldn't read from ".$source." header third: $!\n"; # 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( $in , 5 , SEEK_SET )
or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 5 byte.
read( $in , $lines, 1 )
or die "Couldn't read from ".$source." header fourth: $!\n"; # 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( $in , 6 , SEEK_SET )
or die "Could not seek: $!"; # Based on possition 0 with SEEK_SET we move 10 byte.
read( $in , $lines, 4 )
or die "Couldn't read from ".$source." : $!\n"; # Read 32 bits (4 Bytes) and store the data in $lines Size (4 Bytes Size).
( @memory ) = unpack ( "C4" , $lines ); # (I) An unsigned integer.
# 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 = (shift(@memory)) |
(shift(@memory)) |
(shift(@memory)) |
(shift(@memory));
#print Dumper($mp3_size);
$length_of_data = $mp3_size;
#print("This is the mp3_size after sync_safe: $mp3_size\n");
# 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 total 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( $in , $lines, 4 )
or die "Couldn't read from ".$source." : $!\n"; # 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 beginning 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 = (shift(@memory)) |
(shift(@memory)) |
(shift(@memory)) |
(shift(@memory));
read( $in , $lines, 1 )
or die "Couldn't read from ".$source." : $!\n"; # 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( $in , $lines, 1 )
or die "Couldn't read from ".$source." : $!\n"; # 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;
# Re position the seek pointer after the Extended Header.
seek( $in , $extended_size + $mp3_size , SEEK_SET )
or die "Could not seek: $!"; # Based on position 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( $in , 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) {
# Beginning 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( $in , $lines, 4 )
or die "Couldn't read from ".$source." : $!\n"; # Read 32 bits (4 Bytes) and store the data in $lines Frame_ID.
( $frame_id ) = unpack ( "A4" , $lines ); # A ASCII character string padded with spaces (8-bit) value.
#print("This is the frame_id: $frame_id\n");
# emptying memory for correct use.
@memory = 0 x 4;
read( $in , $lines, 4 )
or die "Couldn't read from ".$source." : $!\n"; # Read 32 bits (4 Bytes) and store the data in @memory Frame_Size.
( @memory ) = unpack ( "C4" , $lines ); # C An unsigned character (usually 8 bits).
#print Dumper(@memory);
$frame_size = (shift(@memory)) |
(shift(@memory)) |
(shift(@memory)) |
(shift(@memory));
read( $in , $lines, 2 )
or die "Couldn't read from ".$source.": $!\n"; # Read 16 bits (2 Bytes) and store the data in $lines Frame Flags.
( $frame_flags ) = unpack ( "C2" , $lines ); # C An unsigned character (usually 8 bits).
printf( "Third Part Frame id: ".$frame_id." Frame Size: ".$frame_size." Flags: ");
foreach ($frame_id) {
if ( $frame_id eq "TPE1") {
read( $in, $buffer, $frame_size )
or die "Couldn't read from ".$source." : $!\n";
print("".$buffer."\n");
}
elsif ( $frame_id eq "TALB") {
read( $in, $buffer, $frame_size )
or die "Couldn't read from ".$source." : $!\n";
print("".$buffer."\n");
}
elsif ( $frame_id eq "TYER") {
read( $in, $buffer, $frame_size )
or die "Couldn't read from ".$source." : $!\n";
print("".$buffer."\n");
}
elsif ( $frame_id eq "TCON") {
read( $in, $buffer, $frame_size )
or die "Couldn't read from ".$source." : $!\n";
print("".$buffer."\n");
}
elsif ( $frame_id eq "TRCK") {
read( $in, $buffer, $frame_size )
or die "Couldn't read from ".$source." : $!\n";
print("".$buffer."\n");
}
elsif ( $frame_id eq "TIT2") {
read( $in, $buffer, $frame_size )
or die "Couldn't read from ".$source." : $!\n";
print("".$buffer."\n");
$length_of_data = 0;
} # End of elseif condition
} # End of until
} # End foreach
} # End of else condition
}# End of Big else after argument condition
print("\nFinished reading file: ".$source.". Closing file! Goodbye!\n");
close ($in) or die "Can not close file: $source: $!\n";
__END__
User has chosen file: silence.mp3 to open for reading!
TAG Detected: ID3v2.3.0
The extended flags has no corresponding data: $00 was detected. Proceeding!
Third Part Frame id: TPE1 Frame Size: 10 Flags: An artist
Third Part Frame id: TALB Frame Size: 9 Flags: An album
Third Part Frame id: TYER Frame Size: 5 Flags: 2012
Third Part Frame id: TCON Frame Size: 5 Flags: (39)
Third Part Frame id: TRCK Frame Size: 2 Flags: 1
Third Part Frame id: TIT2 Frame Size: 11 Flags: Song title
Finished reading file: silence.mp3. Clossing file! Goodbye!
####
*** Tag information for silence.mp3
=== TPE1 (Lead performer(s)/Soloist(s)): An artist
=== TALB (Album/Movie/Show title): An album
=== TYER (Year): 2012
=== TCON (Content type): (39)
=== TRCK (Track number/Position in set): 1
=== TIT2 (Title/songname/content description): Song title
*** mp3 info
MPEG1/layer III
Bitrate: 128KBps
Frequency: 44KHz
####
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use Fcntl qw( SEEK_SET );
use constant ARGUMENTS => scalar 3;
use constant SIZE => scalar 9;
$| = 1;
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 , $found , $buffer , $artist_size , $album_size , $artist_pos , $album_pos );
my @word = "\0" x 5;
my @memory = "\0" x 4;
my $source = $ARGV[0]
or die "No '*.mp3' file was provided!\n";
open( my $in , "+<" , $source )
or die "Can not open file: $source $!\n";
binmode($in); # Open in binary mode.
my $artist = $ARGV[1];
my $album = $ARGV[2];
if (@ARGV < ARGUMENTS) {
print ("Please provide not less than ".ARGUMENTS." arguments!\nCorrect syntax perl $0 name of the mp3 file (e.g. silence.mp3) name of Artist (e.g. An Artist) and name of Album (e.g. An Album)!\n");
exit(0);
}
elsif (@ARGV > ARGUMENTS) {
print ("Please provide no more than ".ARGUMENTS." arguments!\nCorrect syntax perl $0 name of the mp3 file (e.g. silence.mp3) name of Artist (e.g. An Artist) and name of Album (e.g. An Album) $!\n");
exit(0);
}
else {
if (length($artist) > SIZE) {
print "Artist name can not exceed ".SIZE." characters please change it!\n";
exit(0);
}
if (length($album) > SIZE) {
print "Album name can not exceed ".SIZE." characters please change it!\n";
exit(0);
}
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( $in , 0 , SEEK_SET )
or die "Could not seek: $!"; # Set pointer at the beginning of file (Define possition with SEEK_SET).
read( $in , $lines , 3 )
or die "Couldn't read from ".$source." header first: $!\n"; # 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( $in , 3 , SEEK_SET )
or die "Could not seek: $!"; # Based on position 0 with SEEK_SET we move 3 byte.
read( $in , $lines , 1 )
or die "Couldn't read from ".$source." header second: $!\n"; # 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( $in , 4 , SEEK_SET )
or die "Could not seek: $!"; # Based on position 0 with SEEK_SET we move 4 byte.
read( $in , $lines , 1 )
or die "Couldn't read from ".$source." header third: $!\n"; # 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( $in , 5 , SEEK_SET )
or die "Could not seek: $!"; # Based on position 0 with SEEK_SET we move 5 byte.
read( $in , $lines, 1 )
or die "Couldn't read from ".$source." header fourth: $!\n"; # 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( $in , 6 , SEEK_SET )
or die "Could not seek: $!"; # Based on position 0 with SEEK_SET we move 10 byte.
read( $in , $lines, 4 )
or die "Couldn't read from ".$source." : $!\n"; # Read 32 bits (4 Bytes) and store the data in $lines Size (4 Bytes Size).
( @memory ) = unpack ( "C4" , $lines ); # (I) An unsigned integer.
# 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 = (shift(@memory)) |
(shift(@memory)) |
(shift(@memory)) |
(shift(@memory));
#print Dumper($mp3_size);
$length_of_data = $mp3_size;
#print("This is the mp3_size after sync_safe: $mp3_size\n");
# 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( $in , $lines, 4 )
or die "Couldn't read from ".$source." extended header first: $!\n"; # 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 beginning 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 = (shift(@memory)) |
(shift(@memory)) |
(shift(@memory)) |
(shift(@memory));
read( $in , $lines, 1 )
or die "Couldn't read from ".$source." extended header second: $!\n"; # 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( $in , $lines, 1 )
or die "Couldn't read from ".$source." extended header third: $!\n"; # 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( $in , $extended_size + $mp3_size , SEEK_SET )
or die "Could not seek: $!"; # Based on position 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( $in , 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) {
# Beginning 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( $in , $lines, 4 )
or die "Couldn't read from ".$source." at Footer first: $!\n"; # Read 32 bits (4 Bytes) and store the data in $lines Frame_ID.
( $frame_id ) = unpack ( "A4" , $lines ); # A ASCII character string padded with spaces (8-bit) value.
#print("This is the frame_id: $frame_id\n");
# emptying memory for correct use.
@memory = 0 x 4;
read( $in , $lines, 4 )
or die "Couldn't read from ".$source." at Footer second: $!\n"; # Read 32 bits (4 Bytes) and store the data in @memory Frame_Size.
( @memory ) = unpack ( "C4" , $lines ); # C An unsigned character (usually 8 bits).
#print Dumper(@memory);
$frame_size = (shift(@memory)) |
(shift(@memory)) |
(shift(@memory)) |
(shift(@memory));
read( $in , $lines, 2 )
or die "Couldn't read from ".$source." at Footer third: $!\n"; # Read 16 bits (2 Bytes) and store the data in $lines Frame Flags.
( $frame_flags ) = unpack ( "C2" , $lines ); # C An unsigned character (usually 8 bits).
foreach ($frame_id) {
if ( $frame_id eq "TPE1") {
$artist_pos = tell($in);
$artist_size = $frame_size;
print("This is the artist size: ".$artist_size."\n");
}
elsif ( $frame_id eq "TALB") {
$album_pos = tell($in);
$album_size = $frame_size;
print("This is the album size: ".$album_size."\n");
$length_of_data = 0;
} # End of elseif condition
} # End of until
} # End foreach
} # End of else condition
}# End of Big else after argument condition
$artist = "\0" x $artist_size;
seek( $in , $artist_pos , SEEK_SET )
or die "Could not seek: $!";
print $in $artist;
$artist = $ARGV[1];
print("This is artist input chosen by the user: ".$artist."\n");
seek( $in , $artist_pos , SEEK_SET )
or die "Could not seek: $!";
print $in $artist;
$album = "\0" x $album_size;
seek( $in , $album_pos , SEEK_SET )
or die "Could not seek: $!";
print $in $album;
$album = $ARGV[2];
print("This is album input chosen by the user: ".$album."\n");
seek( $in , $album_pos , SEEK_SET )
or die "Could not seek: $!";
print $in $album;
close ($in) or die "Can not close file: $source: $!\n";
__END__
User has chosen file: silence.mp3 to open for reading!
TAG Detected: ID3v2.3.0
This is the artist size: 10
This is the album size: 9
This is artist input chosen by the user: Thanos
This is album input chosen by the user: Test
####
*** Tag information for silence.mp3
=== TPE1 (Lead performer(s)/Soloist(s)): hanos
=== TALB (Album/Movie/Show title): est
=== TYER (Year): 2012
=== TCON (Content type): (39)
=== TRCK (Track number/Position in set): 1
=== TIT2 (Title/songname/content description): Song title
*** mp3 info
MPEG1/layer III
Bitrate: 128KBps
Frequency: 44KHz
####
my $null = chr(0);
print $in $null
print $in $artist;
####
=== TPE1 (Lead performer(s)/Soloist(s)): Thanos
=== TALB (Album/Movie/Show title): Test
=== TYER (Year): 2012
=== TCON (Content type): (39)
=== TRCK (Track number/Position in set): 1
=== TIT2 (Title/songname/content description): Song title
*** mp3 info
MPEG1/layer III
Bitrate: 128KBps
Frequency: 44KHz