I’ve been working on a solution recently to try and read and write data tags from an APE data set in an MP3.
This usually works, however, I’ve discovered a problem with the library I’ve been using when it’s returning incorrect data, of which has been confirmed by a HEX editor.
I’ve been working with a tagging format which has various non-standard fields are saved by myself, but also one done with a third party audio program, which reads and writes the tag of “XRestrictions”, and where expected the subsequent next bytes represent data values – the data of the tag.
This has been rather interesting, firstly, because the program writing the initial tags is written Delphi, but mainly because of how it is handling the data it’s writing. It’s storing a sequence of string hex values, that represents dates and times, but the HEX date values are interestingly stored around the wrong way.
Let me explain further.
The is the HEX code from the file, as per a HEX editor.
The 40AA value is a date value.
But, what the Delphi code has done, is invert the HEX date value. In short, the dates are calculating the days from 31 December 1899.
If we invert those values, it is AA40, and we convert from HEX to Decimal, it’s actually 43584. Storing values like this is very efficient, but the values are swapped – I have no idea why though.
Either way, we have the following values now.
Start Date: AA400000
End Date: AA410000
You can see the workings out here for calculating how many days since 31 Dec 1899, which is 43584 and brings us to: Tuesday, 30 April 2019.
So, moving forward I can now calculate these given values. But, how did I actually get the HEX values, and what went wrong in the first place with the library?
I’ve been using the TagLib-Sharp library for VB.NET with no problems. However, once I started reading these date tags, that is where it all went wrong.
Plain ASCII data is stored nicely… the standard characters… But, once it gets to other values, the library kind of freaks out and gives the wrong response.
Earlyier we saw what the correct HEX values should have beeen. As shown below, these are the results of what the library gave out, and thus, you will see why I discovered that I needed to wrint my own code to extract the correct data, and how I did it – in very rough un-edited code.
This was the raw ASCII text of the “XRestrictions” APE tag given by the TagLib-Sharp library:
@?, , A?, , , , , , , , Q, , , , , Q, , , , , Q, , , , , Q, , , , , Q, , , , , Q, , , , , Q
This can be extracted with the following:
Dim XRestriction_value As String = GetField(“XRestrictions”, Full_Path)
Console.WriteLine(“XRestriction_value: ” + XRestriction_value)
Unfortunately it was wrong, and it took me much of the day to establish what was wrong.
Next, I tried converting the raw value to HEX values, which is also incorrect via TagLib-Sharp:
This can be extracted with the following:
Dim test As String = StringToHex(GetField_Date(“XRestrictions”, Full_Path))
test = test.Replace(“2C”, “”) ‘ remove commas
test = test.Replace(“20”, “00”) ‘ remove spaces
Console.WriteLine(“raw: ” + test)
This was also incorrect for some reason. Notice the text replacements? This created a very long string with more unnecessary characters as well.
After a day of trying to figure this all out, here is the correct HEX via my search code (with a bit of help from others).
Here is my spaghetti code extracting the correct values.
So, the way I did it in the end, was to do a search in the file contents looking for the phrase “XRestrictions”. Once I got the offset, I could then read in the data in bytes at (14 characters + the offset) to get the correct starting point, and then read the data.
Debug.Print(Full_Path) Dim XRestriction_value As String = GetField("XRestrictions", Full_Path) XRestriction.Text = XRestriction_value Console.WriteLine("XRestriction_value: " + XRestriction_value) Dim test As String = StringToHex(GetField_Date("XRestrictions", Full_Path)) test = test.Replace("2C", "") ' remove commas test = test.Replace("20", "00") ' remove spaces Console.WriteLine("raw: " + test) SPLDateRangeraw.Text = test ' works Dim SearchOffset As Integer Dim b2() As Byte = IO.File.ReadAllBytes(Full_Path) Dim encoding As New System.Text.ASCIIEncoding Dim SearchString As String = "XRestrictions" Dim bSearch As Byte() = encoding.GetBytes(SearchString) Dim bFound As Boolean = True For i2 = 0 To b2.Length - bSearch.Length - 1 If b2(i2) = bSearch(0) Then bFound = True For j As Integer = 0 To bSearch.Length - 1 If b2(i2 + j) <> bSearch(j) Then bFound = False Exit For End If Next If bFound Then 'MsgBox(SearchString & " found at byte offset: " & i) Console.WriteLine(" found at byte offset: " & i2) SearchOffset = i2 End If End If Next ' end works. Dim helloworld As String ' Get the file name. Dim file_name As String = Full_Path ' Open the file. Dim fileData(100) As Byte Dim oFileStream As New FileStream(file_name, FileMode.Open, FileAccess.Read) Dim oBinaryReader As New BinaryReader(oFileStream) Dim lBytes As Long = oFileStream.Length Console.WriteLine("lBytes 2: " + lBytes.ToString) oBinaryReader.BaseStream.Position = SearchOffset + 14 ' for the word search offset. Dim fdi As Integer = 0 For i = 0 To 100 fileData(i) = oBinaryReader.ReadByte() Next i oBinaryReader.Close() oFileStream.Close() helloworld = "" For loop1 = 0 To 80 'helloworld = helloworld & fileData(loop1) & "/" helloworld = helloworld & Convert.ToString(Convert.ToInt32(fileData(loop1)), 16).ToUpper Next loop1 Console.WriteLine("Correct Values: " + helloworld) Dim StartOutput As String StartOutput = test.Substring(test.Length - 4, 4) Dim Startoutputleft As String = helloworld.Substring(0, 2) Dim Startoutputright As String = helloworld.Substring(2, 2) Dim Startnewoutput = Startoutputright + Startoutputleft Dim FinalHEXStartDate = Startoutputright + Startoutputleft + "00" + "00" Console.WriteLine("Start Date newoutput: " + FinalHEXStartDate) Dim EndStartOutput As String EndStartOutput = test.Substring(test.Length - 4, 4) Dim EndStartoutputleft As String = helloworld.Substring(6, 2) Dim EndStartoutputright As String = helloworld.Substring(8, 2) Dim EndStartnewoutput = EndStartoutputright + EndStartoutputleft Dim EndFinalHEXStartDate = EndStartoutputright + EndStartoutputleft + "00" + "00" Console.WriteLine("End Date newoutput: " + EndFinalHEXStartDate)
It’s a crazy thing to have to figure out, but if you are working on VB.NET, then this may help a little bit with your MP3 APE metadata.
Either way, this is the full output – set for April 30, 2019.
C:\Users\steve\Desktop\test 2.mp3 XRestriction_value: @?, , A?, , , , , , , , Q, , , , , Q, , , , , Q, , , , , Q, , , , , Q, , , , , Q, , , , , Q raw: 403F0000413F000000001000000007F511001000000007F511001000000007F511001000000007F511001000000007F511001000000007F511001000000007F511 found at byte offset: 279628
APE data fields are great and are totally customisable which don’t have to adhere to ID3 name field standards. This is especially helpful when you want to embed application specific tags in files without effecting the usability of the file.
Next step, getting the data written correctly. This shouldn’t be too hard in theory.