use 5.18.2; use warnings; sub usage { my $err = shift and select STDERR; say "usage: $0 file.mp3"; exit $err; } # usage use Data::Peek; my $source = shift or usage (1); @ARGV and usage (1); open my $in, "<:raw", $source or die "Can not open file '$source': $!\n"; say "\nUser has chosen file: $source to open for reading!"; read $in, my $dta, 10; DHexDump $dta; # Header 10 Bytes in total 3 Bytes + 1 Byte + 1 Byte + 1 Byte + 4 Bytes = 10 Bytes my ($type, $major_version, $revision_number, $flags, $mp3_size) = unpack "A3". # 24 bits (3 Bytes, ASCII text) "ID3". (3 Bytes Header_ID). "h". # 8 bits (1 Byte, hex string) Version (1 Byte Major_Version). "h". # 8 bits (1 Byte, hex string) Version (1 Byte Revision number). "h". # 8 bits (1 Byte, hex string) Flags (1 Byte Flags). "N", # 32 bits (4 Bytes, Integer), Size (4 Bytes Size). $dta; $type =~ m/^[ -~]{3}/ or die "Not a Tagged MP3 file\n"; say "TAG Detected: $type v2.$major_version.$revision_number"; DDumper [ $type, $major_version, $revision_number, $flags, $mp3_size ]; $flags ? say "Flags are not empty, we have found these characters: $flags\n" : say "\nThe extended flags has no corresponding data: \$00 was detected. Proceeding!\n\n"; my $length_of_data = $mp3_size; # say "This is the mp3_size after sync_safe: $mp3_size"; # End of Header # 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) { # 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. read $in, $dta, 6; my ($extended_size, $number_flags, $extended_flags) = unpack "N". # 32 bits (4 Bytes) Extended size. "c". # 8 bits (1 Byte) Flags (1 Byte Flags). "C", # 8 bits (1 Byte) Extended flags (1 Byte Flags). $dta; # 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. say "This is the number of flags: $number_flags"; say "This is the extended header flags: $extended_flags"; say "This is the extended header size, after sync_safe: $extended_size"; # From the stored value we substract the Extended Header to get the total size so far. $length_of_data -= $extended_size; } # say "This is the length of data: $length_of_data"; while ($length_of_data) { # 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, $dta, 10 or die "Couldn't read from $source: $!\n"; my ($frame_id, $frame_size, $frame_flags) = unpack "A4". # 32 bits (4 Bytes) Frame_ID. "N". # 32 bits (4 Bytes) Frame_Size. "C2", # 16 bits (2 Bytes) Frame Flags. $dta; $length_of_data -= 10 + $frame_size; print "Third Part Frame id: $frame_id, Frame Size: $frame_size, Flags: "; if ($frame_size) { read $in, $dta, $frame_size; if ($frame_id =~ m{^( TPE1 | TALB | TYER | TCON | TRCK )$}) { say $dta; next; } if ($frame_id eq "TIT2") { say $dta; last; } } } say "\nFinished reading file: $source Closing file! Goodbye!"; close $in or die "Can not close file: $source: $!\n";