B4J Question Bug? Or something else?

GuyBooth

Active Member
Licensed User
Longtime User
For the sake of completeness I've attached the whole project I am trying to build to extract ID tags from flac and mp3 files. I am using the jaudiotagger external library (too large to upload here), and have run into a problem with inconsistency.
When I run the program (from within B4J), I get a set of results. If I re-run the test, multiple times, without closing the program, I get the same results. But if I close the program and restart it (from within B4J) I get different results. Usually (but not always) I get error messages concerning jaudiotagger.tag.Fieldkeys, eg:
B4X:
java.lang.IllegalArgumentException: No enum constant org.jaudiotagger.tag.FieldKey.DATE
Can I do something wrong in my program to produce this inconsistency? I'm not changing any code, parameters or anything else between tests. Just shutting down B4J, restarting it, rerunning the program.

Extracts from the results that demonstrate the problem (I have included three complete sets of logs in a single text file in the attached zip - each set from a different run with different results):

From result set #1 - I consider this to be a "clean" set, the results I expect to see:

B4X:
(FlacTag) FLAC OGG Tag content:
...
    ALBUM:Summer Side Of Life
    DATE:1971
    TRACKNUMBER:05
…
HasField DATE is true
HasField YEAR is false
…
Map Values:
...
DATE,  1971
...
YEAR,  Empty
...

From result set #2 - Date = null, Year = 1971
B4X:
(FlacTag) FLAC OGG Tag content:
...
    ALBUM:Summer Side Of Life
    DATE:1971
    TRACKNUMBER:05
...
java.lang.IllegalArgumentException: No enum constant org.jaudiotagger.tag.FieldKey.DATE
HasField YEAR is true
...
Map Values:
...
YEAR,  1971
...
DATE,  null
...


From result set #3 - Date = null, Year = empty
B4X:
(FlacTag) FLAC OGG Tag content:
...
    ALBUM:Summer Side Of Life
    DATE:1971
...
java.lang.IllegalArgumentException: No enum constant org.jaudiotagger.tag.FieldKey.DATE
HasField YEAR is true
...
Map Values:
...
YEAR,  Empty
...
DATE,  null

The code that produces these results are a Main module and a Class.
Main:
B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
    #AdditionalJar: jaudiotagger-2.2.6-SNAPSHOT
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private btnRunTest As Button
    Private SourceFolder As String
    Private MusicFile As String
    Private IDTagger As clIDTag
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("TestButtonPanel") 'Load the layout file.
    MainForm.Show
   
'' You must set these to a valid folder and music file,
'' For example:
''    SourceFolder = "C:\Buffers\CD-R\RippedFiles"
''    MusicFile = "Summer Side Of Life-05.flac"
'' Flac files seem to be especially troublesome

    SourceFolder = "C:/Buffers/CD-R/Ripped Files"
    MusicFile = "Summer Side Of Life-05.flac"
   
    IDTagger.Initialize    (SourceFolder)
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Sub btnRunTest_Click
    If SourceFolder = "" Or MusicFile = "" Then
        fx.Msgbox2(MainForm, "You must enter a valid SourceFolder and MusicFile name in the code in AppStart", _
                        "Test ID Tags", "", "OK", "", fx.MSGBOX_INFORMATION)
    Else
        Dim mapTags As Map
        mapTags.Initialize
        mapTags.Clear
        Wait For (IDTagger.Set_TagSet(MusicFile)) Complete (TagIsSet As Boolean)
        If TagIsSet Then
            wait for (IDTagger.Map_AllTags) Complete (mapTags As Map)
            Log($"Map Values:"$)
            For Each k As String In mapTags.Keys
                If mapTags.Get(k) <> "null" Then
                    If mapTags.Get(k) <> "" Then
                        Log($"${k},  ${mapTags.Get(k)}"$)
                    End If
                End If
            Next
            Log($"Map Values (Empty):"$)
            For Each k As String In mapTags.Keys
                If mapTags.Get(k) <> "null" Then
                    If mapTags.Get(k) = "" Then
                        Log($"${k},  Empty"$)
                    End If
                End If
            Next
            Log($"Map Values (Null):"$)
            For Each k As String In mapTags.Keys
                If mapTags.Get(k) = "null" Then
                    Log($"${k},  null"$)
                End If
            Next
        End If
    End If
End Sub
Class clIDTag:
B4X:
Sub Class_Globals
    Private fx As JFX
    Dim Fi As JavaObject
    Dim audioFile As JavaObject
'    Dim audioHeader As JavaObject
    Dim TagSet As JavaObject
    Dim Fi As JavaObject
    Dim audioTaggerFileIO As JavaObject
    Dim mSourceFolder As String
    Dim mapTags As Map
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(Folder As String)
    setSourceFolder(Folder)
    Fill_TagMap
End Sub
' Change this property if the SourceFolder changes
Public Sub setSourceFolder (sVar As String)
    mSourceFolder = sVar  
End Sub

Sub Fill_TagMap
    ' Initialize the Tags map and enter known tags with empty values
    mapTags.Initialize
    mapTags.Put("ALBUM", "")
    mapTags.Put("ARTIST", "")
    mapTags.Put("GENRE", "")
    mapTags.Put("DATE", "")
    mapTags.Put("YEAR", "")
    mapTags.Put("DISCNUMBER", "")
    mapTags.Put("TOTALDISCS", "")
    mapTags.Put("TOTALTRACKS", "")
    mapTags.Put("ALBUM", "")
    mapTags.Put("TRACKNUMBER", "")
    mapTags.Put("TITLE", "")
    ' May not exist
    mapTags.Put("ALBUM_ARTIST", "")
    mapTags.Put("COMPOSER", "")
    mapTags.Put("CONDUCTOR", "")
    mapTags.Put("ORCHESTRA", "")
    mapTags.Put("SOLOISTS", "")
    mapTags.Put("COMMENT", "")
End Sub
' Run this to set up each new track file
Public Sub Set_TagSet (FileName As String) As ResumableSub
    Dim TagIsSet As Boolean
    Try
        ' get an instance of AudioFileIO so we can read the file
        audioTaggerFileIO.InitializeNewInstance("org.jaudiotagger.audio.AudioFileIO",Null)
        ' Check that the file (Folder, Name) exists:
        If File.Exists(mSourceFolder, FileName) Then
            ' file to read must be a java.io.File just pass full path to it
            Fi.InitializeNewInstance("java.io.File",Array(File.Combine(mSourceFolder, FileName)))
            audioFile = audioTaggerFileIO.RunMethod("read", Array(Fi))
            ' read the headers from audioFile and put into audioHeaders
            ' again audioHeaders will assume the correct class as it's a java object
'            audioHeader = audioFile.RunMethod("getAudioHeader",Null)          
            ' read the tags from the audioFile
            TagSet = audioFile.RunMethod("getTag",Null)
            TagIsSet = True
        Else
            fx.Msgbox2(Null, $"File ${FileName} not found."$, "TagSet Error", _
                "", "OK", "", fx.MSGBOX_ERROR)
        End If
    Catch
        Log($"Exception Message: ${LastException.Message}"$)
    End Try
    Log(TagSet)
    Return TagIsSet
End Sub

Public Sub Map_Tag (Key As String) As ResumableSub
    Try
    Dim HasField As Boolean
    Dim TagValue As String
    HasField = TagSet.RunMethod("hasField", Array(Key))
        Log($"HasField ${Key} is ${HasField}"$)
    If HasField Then
        TagValue = TagSet.runmethod("getFirst", Array(Key))
        TagValue = TagValue.Trim
    Else
        TagValue = ""
    End If
    Catch
        Log(LastException.Message)
        TagValue = Null
    End Try  
    Return TagValue
End Sub
'
Public Sub Map_AllTags As ResumableSub
    For Each s As String In mapTags.Keys
        Wait for (Map_Tag (s)) complete (TagValue As String)
        mapTags.Put(s, TagValue)
    Next
    Return mapTags
End Sub
I would love to see a simple solution to this, even if it's a dumb mistake that I have made!
 

Attachments

  • audiotagger tests.zip
    4.9 KB · Views: 315

Daestrum

Expert
Licensed User
Longtime User
maybe this line
B4X:
 If mapTags.Get(k) <> "null" Then

needs to be
B4X:
 If mapTags.Get(k) <> Null Then
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
Perhaps I am missing something but I can't see why you need to use Resumable Subs as I can't see any asynchronous operations.
Turns out, as you point out, that I don't need the resumable subs approach. But I've tested both ways, and it doesn't make any difference to the results.
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
maybe this line
B4X:
 If mapTags.Get(k) <> "null" Then

needs to be
B4X:
 If mapTags.Get(k) <> Null Then
I played around with this, but it only affects how the results are presented. For example, the error messages concerning the tags:
B4X:
java.lang.IllegalArgumentException: No enum constant org.jaudiotagger.tag.FieldKey.DATE
occur long before that code is used.
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
This smacks of timing. Have you tested with all the Wait Fors, especially in Main.btnRunTest_Click removed and all the Sub returns changed to their real types rather then ResumableSubs.
I agree that it smacks of timing … and yes I have removed all the Wait Fors. I also, at one point, added sleep(x) expressions to give the file creations time to be completed - particularly for each of these lines
B4X:
        audioTaggerFileIO.InitializeNewInstance("org.jaudiotagger.audio.AudioFileIO",Null)[/COLOR][/FONT][/LEFT][/COLOR][/FONT][/LEFT]
[FONT=verdana][COLOR=rgb(20, 20, 20)]
[LEFT][FONT=verdana][COLOR=rgb(20, 20, 20)]
[LEFT][LEFT]Fi.InitializeNewInstance("java.io.File",Array(File.Combine(mSourceFolder, FileName)))
   audioFile = audioTaggerFileIO.RunMethod("read", Array(Fi))


As much as ten seconds didn't help. (ignor the font and color references - don't know where they came from , can't delete them)
 
Upvote 0

agraham

Expert
Licensed User
Longtime User
As there is nothing asynchronous in your code you don't ever want to call Sleep() without understanding exactly what it does - which is to return immediately to the message loop. It wont give time for anything to happen as there doesn't seem to be any asynchronous calls in the code. Just edit the code to be pure procedural synchronous code and it will be much easier to debug what is actually happening.
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
This smacks of timing. Have you tested with all the Wait Fors, especially in Main.btnRunTest_Click removed and all the Sub returns changed to their real types rather then ResumableSubs.
One of the things that puzzles me is that when I don't get a "clean" result, I usually(always?) see this line:
B4X:
(FlacTag) FLAC OGG Tag content:
...
    DATE:1971
and yet further down I have this error:
B4X:
java.lang.IllegalArgumentException: No enum constant org.jaudiotagger.tag.FieldKey.DATE

Am I pulling the "HasField" value from one file and the the actual tag field from a different one?
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
As there is nothing asynchronous in your code you don't ever want to call Sleep() without understanding exactly what it does - which is to return immediately to the message loop. It wont give time for anything to happen as there doesn't seem to be any asynchronous calls in the code. Just edit the code to be pure procedural synchronous code and it will be much easier to debug what is actually happening.
Appreciate that, and I have done that. If it's of any value I can repost the project with no Wait Fors. The code I posted has already had the sleeps removed, since they weren't helping anyway.
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
Looking at the source code for org.jaudiotagger.tag.FieldKey

Vorbis DATE get mapped to YEAR

So I would suggest just use YEAR.
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
I think the problem is you are reading the tags (hasField) at a lower level than getTag
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
I think the problem is you are reading the tags (hasField) at a lower level than getTag
Does this mean I may be reading IDv3.1mp3 tags mixed in with flac tags perhaps?
When it comes to interpreting the APIs for audiotagger I'm still stumbling around … my java isn't up to it, I'm afraid. Some methods (hasField and getFirst) seem to be working, but others that look like they should work, I have had no success with.
Seems also I should be able to store the results from the line
B4X:
TagSet = audioFile.RunMethod("getTag",Null)
into a map or an array, but all I have been able to do is write them out in a log with
B4X:
    Log(TagSet)
Both the hasField and getFirst methods are used with TagSet:
B4X:
TagSet.RunMethod("hasField", Array(Key))
' and
TagValue = TagSet.runmethod("getFirst", Array(Key))
so I'm not sure how I can be working at a different level ...
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
I think the problem is that hasField method uses a string and getFirst uses an enum, yet you are using a string.

http://www.jthink.net/jaudiotagger/javadoc/index.html
https://bitbucket.org/ijabz/jaudiot...y.java?at=master&fileviewer=file-view-default

To get the enum value, I think you need the following code:
B4X:
dim jo as JavaObject
jo.InitializeStatic("org.jaudiotagger.tag.FieldKey")
'Populate your map with the right enum's
'I'm just doing a couple here for demo purposes
mapTags.Put("ALBUM", jo.GetField("ALBUM"))
mapTags.Put("ARTIST", jo.GetField("ARTIST"))
mapTags.Put("GENRE", jo.GetField("GENRE"))

Please note that @DonManfred is correct that there is no DATE enumeration in FieldKey.

Then when using getFirst, do
B4X:
TagValue = TagSet.RunMethod("getFirst", Array(mapTags.Get(key)))

Please note: None of the code has been tested.
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
i dont care about the log. i just checked the documentation based on the error.
Seems like the Tag (or whereever it is stored) is interpreted differently in the different files you tried...
Back to my original post:
I'm getting different results from the SAME files, each time I close B4J and reopen it. The three sets of logs I attached in the text file, each of which has different results, are from identical code reading the identical music file.
Using YEAR as the FieldKey doesn't make any difference to the variability in the results. In the last test I ran, the tag comes back as "DATE:1971" in the log(TagSet), HasField("YEAR") comes back as True, and TagSet.runmethod("getFirst", Array("YEAR")) comes back empty.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
the tag comes back as "DATE:1971" in the log(TagSet), HasField("YEAR") comes back as True, and TagSet.runmethod("getFirst", Array("YEAR")) comes back empty.
getFirst does not expect a string, but an enum, so yeah, different results may happen (like hasField producing a result - your calling it correctly, but getFirst not - your calling it incorrectly). Actually, I'm surprised getFirst does not bomb out, but then maybe I'm just reading the docs wrong. Until you call getFirst correctly, nothing can be said about your strange results.
 
Upvote 0

GuyBooth

Active Member
Licensed User
Longtime User
getFirst does not expect a string, but an enum, so yeah, different results may happen (like hasField producing a result - your calling it correctly, but getFirst not - your calling it incorrectly). Actually, I'm surprised getFirst does not bomb out, but then maybe I'm just reading the docs wrong. Until you call getFirst correctly, nothing can be said about your strange results.
Ok, so if I understand you correctly, I should use a User Defined Type, eg:
B4X:
 Type udtFieldKey ( _
  ALBUM As String, _
  ARTIST As String, _
  GENRE As String, _
  YEAR As String, _

Dim FieldKey as udtFieldKey

etc then
 FieldKey.ALBUM  = "ALBUM"
 FieldKey.ARTIST = "ARTIST"
 FieldKey.YEAR  = "YEAR"
 FieldKey.GENRE  = "GENRE"
and finally
   TagValue = TagSet.runmethod("getFirst", Array(FieldKey.ALBUM))
   TagValue = TagSet.runmethod("getFirst", Array(FieldKey.ARTIST))
   TagValue = TagSet.runmethod("getFirst", Array(FieldKey.TITLE))
   TagValue = TagSet.runmethod("getFirst", Array(FieldKey.YEAR))

But I don't believe I can iterate through a udt so the code is going to get pretty clumsy. Is there a better way to do it?
 
Upvote 0
Top