ElektroStudios
ElektroStudios

Reputation: 20464

Read APEv2 mp3 tag with TagLibSharp?

SCENARIO


I normally use the MP3Gain application to set the replay gain of mp3 files.

The application can create these fields in the APEv2 tag of mp3 files:

enter image description here

(screenshot is taken from WinAmp player)

QUESTION


With TagLibSharp library I wrote a ID3v1 and ID3v2 parser, now, I wonder if I could read and write the mentioned APEv2 fields using this library?.

RESEARCH


I think that MP3Gain app uses unique names for the fields so probably TagLibsharp does not supports them, however, TagLibsharp library has a ReadBlock(), Removeblock(), Find() and RFind() methods for which I think is what I need to use, but I don't know exactly how to use them in conjunction...

This is the only what I have:

Dim file As New TagLib.Mpeg.AudioFile("C:\input.mp3")
Dim data As Byte() = Encoding.ASCII.GetBytes("MP3GAIN_MINMAX")
Dim vector As New ByteVector(data)
Dim offset As Long = file.Find(vector)

And this is a pseudo-code written in Vb.Net just to demostrate the expected abstraction or behavior.

Imports TagLib

Public NotInheritable Class Mp3File

    Private tagFile As Global.TagLib.Mpeg.AudioFile

    Public ReadOnly Property APEv2 As APEv2Tag
        Get
            Return Me.apeTagB
        End Get
    End Property
    Private ReadOnly apeTagB As APEv2Tag

    Public Sub New(ByVal file As FileInfo)
        Me.tagFile = New Global.TagLib.Mpeg.AudioFile(file.FullName)
        Me.apeTagB = New APEv2Tag(Me.tagFile)
    End Sub

End Class

''' <summary>
''' Represents the APEv2 tag for a MP3 file.
''' </summary>
Public Class APEv2Tag

    Protected ReadOnly mp3File As Global.TagLib.Mpeg.AudioFile

    Public Sub New(ByVal mp3File As Global.TagLib.Mpeg.AudioFile)
        Me.mp3File = mp3File
    End Sub

    Public Overridable Property MP3GAIN_MINMAX As Double
        Get
            If field exists then...
                Return TheValue...
            End If
        End Get
        Set(ByVal value As Double)
            ...
        End Set
    End Property

    ' More properties here...

End Class

UPDATE:

I think that I finally finished by myself the "Read" part, however I'm not sure how to write the blocks because if the field does not exists surelly I could overwritte/corrupt the file...

    ''' ----------------------------------------------------------------------------------------------------
    ''' <summary>
    ''' Gets the <c>MP3GAIN_MINMAX</c> metatada field of the audio file.
    ''' </summary>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <returns>
    ''' The <c>MP3GAIN_MINMAX</c> field value.
    ''' </returns>
    ''' ----------------------------------------------------------------------------------------------------
    <DebuggerStepThrough>
    Private Function GetFieldMP3GainMinMax() As String

        Dim data As Byte() = Encoding.UTF8.GetBytes("MP3GAIN_MINMAX")
        Dim vector As New ByteVector(data)
        Dim offset As Long = Me.mp3File.Find(vector)
        Dim result As String

        If (offset = -1) Then
            Return String.Empty

        Else
            Try
                offset += ("MP3GAIN_MINMAX".Length + 1)
                Me.mp3File.Seek(offset, SeekOrigin.Begin)
                result = Me.mp3File.ReadBlock(8).ToString.TrimEnd()
                Return result

            Catch ex As Exception
                Throw

            Finally
                Me.mp3File.Seek(0, SeekOrigin.Begin)

            End Try

        End If

    End Function

    ''' ----------------------------------------------------------------------------------------------------
    ''' <summary>
    ''' Gets the <c>MP3GAIN_UNDO</c> metatada field of the audio file.
    ''' </summary>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <returns>
    ''' The <c>MP3GAIN_UNDO</c> field value.
    ''' </returns>
    ''' ----------------------------------------------------------------------------------------------------
    <DebuggerStepThrough>
    Private Function GetFieldMP3GainUndo() As String

        Dim data As Byte() = Encoding.UTF8.GetBytes("MP3GAIN_UNDO")
        Dim vector As New ByteVector(data)
        Dim offset As Long = Me.mp3File.Find(vector)
        Dim result As String

        If (offset = -1) Then
            Return String.Empty

        Else
            Try
                offset += ("MP3GAIN_UNDO".Length + 1)
                Me.mp3File.Seek(offset, SeekOrigin.Begin)
                result = Me.mp3File.ReadBlock(12).ToString.TrimEnd()
                Return result

            Catch ex As Exception
                Throw

            Finally
                Me.mp3File.Seek(0, SeekOrigin.Begin)

            End Try

        End If

    End Function

    ''' ----------------------------------------------------------------------------------------------------
    ''' <summary>
    ''' Gets the <c>REPLAYGAIN_TRACK_GAIN</c> metatada field of the audio file.
    ''' </summary>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <returns>
    ''' The <c>REPLAYGAIN_TRACK_GAIN</c> field value.
    ''' </returns>
    ''' ----------------------------------------------------------------------------------------------------
    <DebuggerStepThrough>
    Private Function GetFieldReplayGainTrackGain() As String

        Dim data As Byte() = Encoding.UTF8.GetBytes("REPLAYGAIN_TRACK_GAIN")
        Dim vector As New ByteVector(data)
        Dim offset As Long = Me.mp3File.Find(vector)
        Dim result As String

        If (offset = -1) Then
            Return String.Empty

        Else
            Try
                offset += ("REPLAYGAIN_TRACK_GAIN".Length + 1)
                Me.mp3File.Seek(offset, SeekOrigin.Begin)
                result = Me.mp3File.ReadBlock(12).ToString.TrimEnd()
                Return result

            Catch ex As Exception
                Throw

            Finally
                Me.mp3File.Seek(0, SeekOrigin.Begin)

            End Try

        End If

    End Function

    ''' ----------------------------------------------------------------------------------------------------
    ''' <summary>
    ''' Gets the <c>REPLAYGAIN_TRACK_PEAK</c> metatada field of the audio file.
    ''' </summary>
    ''' ----------------------------------------------------------------------------------------------------
    ''' <returns>
    ''' The <c>REPLAYGAIN_TRACK_PEAK</c> field value.
    ''' </returns>
    ''' ----------------------------------------------------------------------------------------------------
    <DebuggerStepThrough>
    Private Function GetFieldReplayGainTrackPeak() As String

        Dim data As Byte() = Encoding.UTF8.GetBytes("REPLAYGAIN_TRACK_PEAK")
        Dim vector As New ByteVector(data)
        Dim offset As Long = Me.mp3File.Find(vector)
        Dim result As String

        If (offset = -1) Then
            Return String.Empty

        Else
            Try
                offset += ("REPLAYGAIN_TRACK_PEAK".Length + 1)
                Me.mp3File.Seek(offset, SeekOrigin.Begin)
                result = Me.mp3File.ReadBlock(8).ToString.TrimEnd()
                Return result

            Catch ex As Exception
                Throw

            Finally
                Me.mp3File.Seek(0, SeekOrigin.Begin)

            End Try

        End If

    End Function

Upvotes: 2

Views: 861

Answers (1)

Brian Nickel
Brian Nickel

Reputation: 27550

The process for getting APEv2 specific information from a TagLib# file is similar to the process for an ID3v2 tag described in this answer.

Here's how to read the value:

// Get the APEv2 tag if it exists.
TagLib.Ape.Tag ape_tag = (TagLib.Ape.Tag)file.GetTag(TagLib.TagTypes.Ape, false);

if(ape_tag != null) {

    // Get the item.
    TagLib.Ape.Item item = ape_tag.GetItem("MP3GAIN_MINMAX");

    if (item != null) {
        Console.Log(item.ToStringArray());
    }
}

I'm not sure from the screenshot if the field is a single string or two strings shown joined by a comma.

Saving would be the reverse direction but a little simpler:

// Get the APEv2 tag if it exists.
TagLib.Ape.Tag ape_tag = (TagLib.Ape.Tag)file.GetTag(TagLib.TagTypes.Ape, true);

if(ape_tag != null) {
    ape_tag.SetValue("MP3GAIN_MINMAX", value);
}

file.Save();

An example, for Vb.Net:

Fields:

ReadOnly mp3File As Global.TagLib.Mpeg.AudioFile = ...

Properties (MP3Gain related):

Property MP3GainMinMax As String
    Get
        Return Me.GetField("MP3GAIN_MINMAX")
    End Get
    Set(ByVal value As String)
        Me.SetField("MP3GAIN_MINMAX", value)
    End Set
End Property

Property MP3GainUndo As String
    Get
        Return Me.GetField("MP3GAIN_UNDO")
    End Get
    Set(ByVal value As String)
        Me.SetField("MP3GAIN_UNDO", value)
    End Set
End Property

Property ReplayGainTrackGain As String
    Get
        Return Me.GetField("REPLAYGAIN_TRACK_GAIN")
    End Get
    Set(ByVal value As String)
        Me.SetField("REPLAYGAIN_TRACK_GAIN", value)
    End Set
End Property

Property ReplayGainTrackPeak As String
    Get
        Return Me.GetField("REPLAYGAIN_TRACK_PEAK")
    End Get
    Set(ByVal value As String)
        Me.SetField("REPLAYGAIN_TRACK_PEAK", value)
    End Set
End Property

Function 'Get':

Function GetField(ByVal fieldName As String) As String

    Dim apeTag As TagLib.Ape.Tag =
        DirectCast(Me.mp3File.GetTag(TagTypes.Ape, create:=False), TagLib.Ape.Tag)

    If (apeTag IsNot Nothing) Then
        Dim item As TagLib.Ape.Item = apeTag.GetItem(fieldName)

        If (item IsNot Nothing) Then
            Return item.ToString()
        End If
    End If

    Return String.Empty

End Function

Method 'Set':

Sub SetField(ByVal fieldName As String, ByVal value As String)

    Dim apeTag As TagLib.Ape.Tag = 
        DirectCast(Me.mp3File.GetTag(TagTypes.Ape, create:=True), TagLib.Ape.Tag)

    apeTag.SetValue(fieldName, value)

End Sub

Upvotes: 5

Related Questions