B4J Question jaudiotagger: How to associate these moving parts?

GuyBooth

Active Member
Licensed User
An approach to saving new data to ID3 tags that I have tried, but which I don't see how to complete, is to use these interfaces(?) from the API documentation:
I thought if I could implement particularly the TagTextField I would be able to use its setContent method. I am able to get started using:
B4X:
    Dim joTag2 As JavaObject
    Dim joTag2Field As JavaObject
    Dim joTag2TextField As JavaObject
followed by
B4X:
    joTag2.InitializeStatic("org.jaudiotagger.tag.Tag")
    joTag2Field.InitializeStatic("org.jaudiotagger.tag.TagField")
    joTag2TextField.InitializeStatic("org.jaudiotagger.tag.TagTextField")
which compiles and "runs" without throwing errors - but the resulting javaobjects are not associated with any music file, therefore don't "know" what ID3 tags they refer to.
How do I link them up? I tried an approach involving
B4X:
joTag2.InitializeNewInstance("org.jaudiotagger.tag.Tag", Array(audioMP3File))
    joTag2Field.InitializeNewInstance("org.jaudiotagger.tag.TagField", Array(audioMP3File))
    joTag2TextField.InitializeNewInstance("org.jaudiotagger.tag.TagTextField", Array(audioMP3File))
where the audioMP3File is set up properly using code I have shown in my other jaudiotagger posts, but the result was a log error:
B4X:
java.lang.RuntimeException: Constructor not found.
Am I completely off track here - do I need to go back to school for a year or so to learn how to do this? Or am I missing something really simple … it's happened before ;)
 

GuyBooth

Active Member
Licensed User
I believe this is essentially what I have been trying unsuccessfully to do … but to simplify it here:
Relevant Code in example 10:
B4X:
private static void editTags(List<String> files,String song_title,int position,int disc_no,String album_name,String artist_name,int date,String genre){
  for (  String path : files) {
    File file=new File(path);
    try {
      AudioFile f=AudioFileIO.read(file);
      Tag tag=f.getTag();
      if (album_name != null && album_name.trim().length() > 0) {
        tag.setField(FieldKey.ALBUM,album_name);
      }

Code in M4J that I have tried to implement:
B4X:
            ' get an instance of audioMP3FileIO so we can read the file
            audioTaggerMP3FileIO.InitializeNewInstance("org.jaudiotagger.audio.AudioFileIO",Null)
            Dim MP3Fi As JavaObject
            MP3Fi.InitializeNewInstance("java.io.File",Array(File.Combine(mSourceFolder, mFileName)))
            audioMP3File = audioTaggerMP3FileIO.RunMethod("read", Array(MP3Fi))
            ' Set up the FieldKeys
            joFieldKeys.InitializeStatic("org.jaudiotagger.tag.FieldKey")
            Dim Tag As JavaObject
            Tag = audioMP3File.RunMethod("getTag", Null)
            Dim album_name As String
            album_name = "My Album Name"
            Tag.RunMethod("setField", Array(joFieldKeys.GetField("ALBUM"), album_name))
Resulting error:
B4X:
Error occurred on line: 146 (clIDTag)
java.lang.RuntimeException: Method: setField not matched.
    at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:130)
    at b4j.DestopMusicMachine.clidtag._do_tags(clidtag.java:281)
    at b4j.DestopMusicMachine.clidtag._initialize(clidtag.java:131)
    at b4j.DestopMusicMachine.mb_add$ResumableSub_btnCreateIDTags_Click.resume(mb_add.java:1522)
    at anywheresoftware.b4a.shell.DebugResumableSub$DelegatableResumableSub.resumeAsUserSub(DebugResumableSub.java:47)
    at jdk.internal.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:632)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:237)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at jdk.internal.reflect.GeneratedMethodAccessor3.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:91)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:98)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:78)
    at anywheresoftware.b4a.shell.DebugResumableSub$DelegatableResumableSub.resume(DebugResumableSub.java:42)
    at anywheresoftware.b4a.BA.checkAndRunWaitForEvent(BA.java:136)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:85)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:98)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:78)
    at anywheresoftware.b4a.keywords.Common$3.run(Common.java:1086)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    at java.base/java.lang.Thread.run(Thread.java:834)
Line 146 is the last line in the code I show above.
What is it looking for that I am not giving it?
 
Upvote 0

Daestrum

Well-Known Member
Licensed User
Does the file you are using actually have an ALBUM tag, as I think the setField only works on existing tags, it doesn't create new ones.
 
Upvote 0

GuyBooth

Active Member
Licensed User
Yes it does. I am able to view the tags (ID3V1, ID3V2 and Flac tags) using commands like:
B4X:
 Tag.RunMethod("getFirst", Array(joFieldKeys.GetField("ALBUM")))

Just as a matter of interest, is their a possibility that javaobject doesn't support the runmethod "setField" (possible conflicts)?
Is there an equivalent approach using reflector?
 
Upvote 0

Daestrum

Well-Known Member
Licensed User
I can't get it to work using javaobject, but it will work with pure java

B4X:
#if java
import org.jaudiotagger.tag.*;
public static void setField(Tag tag,String album_name) throws FieldDataInvalidException{
 tag.setField(FieldKey.ALBUM,album_name); 
}
#End If

I call it using
B4X:
 asJO(Me).RunMethod("setField",Array(audioTags,"a good album"))

with this routine
B4X:
Sub asJO(o As JavaObject) As JavaObject
 Return o
End Sub
 
Upvote 0

GuyBooth

Active Member
Licensed User
Thank you Daestrom. I will have to spend some time figuring out how to make this work - my first attempt yielded the same error as before, but I have never implemented in-line java code, so I am sure I don't yet understand it - for instance:
Where do I add the
B4X:
#if java
import org.jaudiotagger.tag.*;
public static void setField(Tag tag,String album_name) throws FieldDataInvalidException{
 tag.setField(FieldKey.ALBUM,album_name);
}
#End If
code?
What does the audioTags variable refer to? Do I just substitute the specific tag I am trying to set?

Obviously I will need to modify this also to handle all the different tags, since the java code refers to the FieldKey.ALBUM specifically.
It also looks as though, if I can understand how it works, that I might want to manipulate everything in in-line java (including reading the tags) instead of using javaobjects if there are some functions that don't work that way.
 
Upvote 0

Daestrum

Well-Known Member
Licensed User
audioTags is the Tags returned from the file.

B4X:
 audioFile = audioTaggerFileIO.RunMethod("read",Array(fi))
...
 audioTags = audioFile.RunMethod("getTag",Null)
...
 
Upvote 0

GuyBooth

Active Member
Licensed User
For anyone following this thread, I am posting the code with which I have finally succeeded (although still some questions left). This code inserted into my B4J clIDTagger class:
B4X:
#if java
import org.jaudiotagger.*;
import org.jaudiotagger.tag.*;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.audio.*;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.tag.id3.ID3v24Tag;
import org.jaudiotagger.tag.id3.ID3v23Tag;
import org.jaudiotagger.tag.id3.ID3v23Tag;
import org.jaudiotagger.tag.id3.ID3v1Tag;
import org.jaudiotagger.tag.flac.FlacTag;
import java.io.File;

public String getTagContents(AudioFile audiofile, FieldKey fieldkey) {
    Tag tag = audiofile.getTag();  
    return tag.getFirst(fieldkey);  
}
public static void setTagContents(Tag tag, FieldKey fieldkey, String sContents) {
    try {
        if (sContents.length() > 0) {
            if (!tag.hasField(fieldkey)) {
                tag.addField(fieldkey, sContents);
            } else {
                tag.setField(fieldkey, sContents);
            }
        }
    }
 catch (Throwable e) {
/*    e.printStackTrace(); */
    }              
}
public static void createNewID3v1Tag(AudioFile audiofile) {
    ID3v1Tag newTag = new ID3v1Tag();
    audiofile.setTag(newTag);
}
public static void createNewID3v2Tag(AudioFile audiofile) {
    ID3v24Tag newTag = new ID3v24Tag();
    audiofile.setTag(newTag);
}
public static void createNewFlacTag(AudioFile audiofile) {
    FlacTag newTag = new FlacTag();
    audiofile.setTag(newTag);
}
#End If

and called from the B4J subs through the "asJO" sub Daestrum suggested.
B4X:
Sub Write_Tags(Folder As String, PG As udtPrimaryGroup, PieceNo As Int)
    ' Set the music file source folder (same for mp3 and flac)
    ' DoV1, DoV2 and DoFlac are boolean properties set up when the class is initialized
    setSourceFolder(Folder)
    ' PrimaryGroup info is here
    mPG = PG
    ' Piece info is here:
    mPiece = PG.Pieces(PieceNo)
    Try
        If DoV1 Or DoV2 Then
            ' Set the (MP3) filename
            mFileName = $"${mPiece.FileStub}$2.0{mPiece.SourceTrack}.${mPiece.TrackFileExt}"$
            ' Check that the file (Folder, Name) exists:
            If File.Exists(mSourceFolder, mFileName) Then
                Dim Fi As JavaObject
                Fi.InitializeNewInstance("java.io.File",Array(File.Combine(mSourceFolder, mFileName)))
                audioFile = audioTaggerFileIO.RunMethod("read", Array(Fi))
                If DoV1 Then
                    '    TagType = "ID3v1"
                    '    Not used: Method0 = "hasID3v1Tag"
                    '    Method1 = "getID3v1Tag"
                    '    Method2 = "createNewID3v1Tag"
                    Set_TagsToTagRecordPGValues ("ID3v1", "getID3v1Tag", "createNewID3v1Tag")
                End If
                If DoV2 Then
                    '    TagType = "ID3v2"
                    '    Not used: Method0 = "hasID3v2Tag"
                    '    Method1 = "getID3v2Tag"
                    '    Method2 = "createNewID3v2Tag"
                    Set_TagsToTagRecordPGValues  ("ID3v2", "getID3v2TagAsv24", "createNewID3v2Tag")
                End If
                ' Commit the result to the music file
                audioFile.RunMethod("commit", Null)
            End If
        End If
        If DoFlac Then
            mFileName = $"${mPiece.FileStub}$2.0{mPiece.SourceTrack}.${mPiece.LTFE}"$
            If File.Exists(mSourceFolder, mFileName) Then
                Dim Fi As JavaObject
                Fi.InitializeNewInstance("java.io.File", Array(File.Combine(mSourceFolder, mFileName)))
                audioFile = audioTaggerFileIO.RunMethod("read", Array(Fi))
                '    TagType = "Flac"
                '    Not used: Method0 = "hasTag"
                '    Method1 = "getFlacTag"
                '    Method2 = "createNewFlacTag"
                Set_TagsToTagRecordPGValues  ("Flac", "getTag", "createNewFlacTag")
                ' Commit the result to the music file
                audioFile.RunMethod("commit", Null)
            End If
        End If
    Catch
        If LastException.Message.Contains("No enum constant") Then
            Log($"Enum FieldKey ${LastException.Message.SubString(LastException.Message.LastIndexOf("FieldKey")+9)} does not exist."$)
            Log($"Skipping it."$)
        Else
            Log($"Write_Tags: ${LastException.Message}"$)
        End If
    End Try
End Sub
' eg. Set_TagsToFormValues ("ID3v2", "getID3v1Tag", "createNewID3v1Tag")
Sub Set_TagsToTagRecordPGValues (TagType As String, Method1 As String, Method2 As String)
    Dim joTag As JavaObject
    Dim Message1, Message2 As String
    Try
        Message1 = $"There is no ${TagType} tag."$
        Message2 = $"There is STILL no ${TagType} tag."$
        ' Does a set of tags exist in this music file?
        ' Try to get the tag set
        joTag = audioFile.RunMethod(Method1, Null)
        ' If it doesn't exist
        If Not(joTag.IsInitialized) Then   
            ' Create one
            Log(Message1)
            asJO(Me).RunMethod(Method2, Array(audioFile))
            ' Make sure it is written to the file on disk
            audioFile.RunMethod("commit", Null)
            ' Try again
            joTag = audioFile.RunMethod(Method1, Null)
        End If
        ' Check again - Does a set of tags exist in this music file?
        If joTag.IsInitialized Then
                For Each K As String In mapTags.Keys
                    ' Get the Tag Record
                    Dim TagRecord As udtTagRecord
                    TagRecord = mapTags.Get(K)
                    ' Write it to the tag
                    asJO(Me).RunMethod("setTagContents", Array(joTag, TagRecord.joTagKey, TagRecord.PGValue))
                Next
                ' Commit the result to the music file
                audioFile.RunMethod("commit", Null)
        Else
            Log(Message2)
        End If
    Catch
        If LastException.Message.Contains("No enum constant") Then
            Log($"Enum FieldKey ${LastException.Message.SubString(LastException.Message.LastIndexOf("FieldKey")+9)} does not exist."$)
            Log($"Skipping it."$)
        Else
            Log($"Process_Tags2: ${LastException.Message}"$)
        End If
    End Try
End Sub

It certainly appears that the javaobject "runmethod" doesn't work with all the APIs in the jaudiotagger library, but this approach succeeds.

I have a couple of questions left which I will post in different threads.
 
Upvote 0
Top