APE Tags with taglib-sharp

Posted on Updated on

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.

Screenshot 2019-04-24 23.37.41

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.

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
If bFound Then
'MsgBox(SearchString & " found at byte offset: " & i)
Console.WriteLine(" found at byte offset: " & i2)
SearchOffset = i2
End If
End If

' 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
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.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s