B4J Question How to get the EXIF information from an image?

Mark Read

Well-Known Member
Licensed User
Longtime User
I need to get the EXIF information from a series of image files (about 200). In particular, the gps information. I have an app which was working using the inline java - meta-extractor-2.6.2, as detailed in this post.

I have two cameras, a Nikon D90 and a D200. The D90 is not problem a but the D200 causes a crash with an error which I cannot understand (will post later as I have no files to test at work). Looking at the EXIF information with Irfanview, both files are readable and the format of both sets of EXIF data seem to be the same.


There is an update for the meta-extractor which I tried as is but no luck.

Is there another way? I have a workaround using Matlab but would like to use B4J.

Thanks in advance.

#If JAVA
import com.drew.imaging.*;
import com.drew.metadata.*;
import com.drew.lang.*;

import java.io.*;
import java.util.*;

public static List GetImgMeta(String filename)
{
File file = new File(filename);
List lstMeta = new ArrayList<Object>();

try {
Metadata metadata = ImageMetadataReader.readMetadata(file);

for (Directory directory : metadata.getDirectories()) {
for (Tag tag : directory.getTags()) {
lstMeta.add(tag.toString());
}

if (directory.hasErrors()) {
for (String error : directory.getErrors()) {
System.err.println("ERROR: " + error);
}
}
}
}
catch (ImageProcessingException e) {
// handle exception
lstMeta.add("ImageProcessingException");
}
catch (IOException e) {
// handle exception
lstMeta.add("IOException");
}

return(lstMeta);
}
#End If

Dim imgmeta As List = NativeMe.RunMethod("GetImgMeta", Array(fn))
 

DonManfred

Expert
Licensed User
Longtime User
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
Waiting for debugger to connect...
Program started.
Error occurred on line: 126 (Main)
java.lang.ClassCastException: java.lang.NoClassDefFoundError cannot be cast to java.lang.Exception
at anywheresoftware.b4a.BA.setLastException(BA.java:359)
at b4j.example.main._listfiles(main.java:292)
at b4j.example.main._btnselectimgpath_mouseclicked(main.java:326)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:625)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:234)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:168)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:90)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:94)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:77)
at anywheresoftware.b4j.objects.NodeWrapper$1.handle(NodeWrapper.java:93)
at anywheresoftware.b4j.objects.NodeWrapper$1.handle(NodeWrapper.java:1)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3470)
at javafx.scene.Scene$ClickGenerator.access$8100(Scene.java:3398)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3766)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:394)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$353(GlassViewEventHandler.java:432)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:937)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
Are you sure the Methodname is correctly spelled? I would guess it should be getImgMeta

gives the error: (RuntimeException) java.lang.RuntimeException: Method: getImgMeta not found in: b4j.example.main
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
This is my project:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 800
    #AdditionalJar: metadata-extractor-2.6.2
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private btnExit As Button
    Private btnGenerate As Button
    Private btnSelectImgPath As Button
  
    Private ImgPath As Label
    Private Label1 As Label
  
    Private lstFiles As ListView
  
  
    Dim OpenFile As DirectoryChooser
    Dim ImagePath, ThumbPath As String
  
    Private NativeMe As JavaObject
  
    Dim Lat, Lon As String
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
    MainForm.Show
    NativeMe = Me
    Lat=""
    Lon=""
  
End Sub


Sub btnSelectImgPath_MouseClicked (EventData As MouseEvent)
    OpenFile.Initialize
    OpenFile.InitialDirectory="D:\"
    ImagePath=OpenFile.Show(MainForm)
    ImagePath=ImagePath & "\"
    ImgPath.Text=ImagePath
    ListFiles
End Sub

Sub btnGenerate_MouseClicked (EventData As MouseEvent)
    ' Save the data to a new file
    '<a href="images/elde/elde-55.jpg" data-lightbox="Elde" data-title=""><img border="0" src="images/elde/thumbs/elde-55.jpg" width="200" height="133"> </a>

    Dim tw As TextWriter
    tw.Initialize(File.OpenOutput(ImagePath,"images.html",False))
      
    For c=0 To lstFiles.Items.Size-1
        Dim imgFile, Textline As String
      
        imgFile=lstFiles.Items.Get(c)
        Log(imgFile)
        imgFile=imgFile.SubString(14)
        imgFile.Replace("\", "/")
      
        Textline="<a href=" & QUOTE
        Textline=Textline & imgFile.Replace(ImagePath, "") & QUOTE
        Textline=Textline & " data-lightbox=" & QUOTE & "lb" & QUOTE & " data-title=" & QUOTE & QUOTE
        Textline=Textline & "><img border=" & QUOTE & "0" & QUOTE & " src=" & QUOTE & ThumbPath
        Textline=Textline & imgFile.Replace(ImagePath, "") & QUOTE
        Textline=Textline & " width=" & QUOTE & "200" & QUOTE & "height=" & QUOTE & "133" & QUOTE
        Textline=Textline & "> </a>"
        Log(Textline)
        tw.WriteLine(Textline)
    Next
      
    tw.Close
End Sub

Sub btnExit_MouseClicked (EventData As MouseEvent)
    MainForm.Close
End Sub

Sub btnGetFiles_MouseClicked (EventData As MouseEvent)
    ListFiles
End Sub

Sub ListFiles
   Try
 
           For Each FileName As String In File.ListFiles(ImagePath)
            
                Dim FileExtension As String = FileName.Substring2(FileName.length-4, FileName.length)
                FileExtension=FileExtension.ToLowerCase
                If FileExtension = ".jpg" Then
                    lstFiles.Items.Add(GetExifDate(FileName))
                End If
        Next
        
    Catch
        Log(LastException)
    End Try
    lstFiles.Items.Sort(True)    'sort list according to date and time  "yyyymmddhhmmss filename"
End Sub

Sub GetExifDate(fn As String) As String
  
    '[Exif SubIFD] Date/Time Original - 2013:09:02 12:54:02
    fn = File.Combine(ImagePath, fn)
      Dim imgmeta As List = NativeMe.RunMethod("GetImgMeta", Array(fn))
    
      For Each s1 As String In imgmeta
        'Log(s1)
        If s1.Contains("GPS Latitude") And s1.Contains("°") Then
            s1=s1.SubString(21)
            s1=s1.Replace("°","")
            s1=s1.Replace("'","")
            s1=s1.Replace("""","")
            Log(s1)
          
            Dim Stamp() As String
            Dim Val As Double
          
            Stamp=Regex.Split(" ",s1)
            Val=Stamp(0) + (Stamp(1)/60) + (Stamp(2)/3600)
            Log(Val)
          

            Return
        End If
      Next
End Sub

#If JAVA
import com.drew.imaging.*;
import com.drew.metadata.*;
import com.drew.lang.*;

import java.io.*;
import java.util.*;

public static List GetImgMeta(String filename)
{
  File file = new File(filename);
  List lstMeta = new ArrayList<Object>();
 
  try {
    Metadata metadata = ImageMetadataReader.readMetadata(file);

    for (Directory directory : metadata.getDirectories()) {
      for (Tag tag : directory.getTags()) {
        lstMeta.add(tag.toString());
      }

      if (directory.hasErrors()) {
        for (String error : directory.getErrors()) {
          System.err.println("ERROR: " + error);
        }
      }
    }
  }
  catch (ImageProcessingException e) {
    // handle exception
    lstMeta.add("ImageProcessingException");
  }
  catch (IOException e) {
    // handle exception
    lstMeta.add("IOException");
  }
 
  return(lstMeta);
}
#End If

And the error.

B4X:
Waiting for debugger to connect...
Program started.
Error occurred on line: 107 (Main)
java.lang.ClassCastException: java.lang.NoClassDefFoundError cannot be cast to java.lang.Exception
    at anywheresoftware.b4a.BA.setLastException(BA.java:359)
    at b4j.example.main._listfiles(main.java:242)
    at b4j.example.main._btnselectimgpath_mouseclicked(main.java:276)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:625)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:234)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:168)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:90)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:94)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:77)
    at anywheresoftware.b4j.objects.NodeWrapper$1.handle(NodeWrapper.java:93)
    at anywheresoftware.b4j.objects.NodeWrapper$1.handle(NodeWrapper.java:1)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3470)
    at javafx.scene.Scene$ClickGenerator.access$8100(Scene.java:3398)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3766)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$355(GlassViewEventHandler.java:388)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
test.jpg
Sorry, as thumbnail deletes the EXIF information!

I always forget about the zip feature. Sorry. Here the complete project including test image.
 

Attachments

  • gps2html.zip
    19.9 KB · Views: 301
Last edited:
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
Note that the "spoiler" buttons only make it more difficult to read your posts.

Noted, will not use it so much in the future.
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
This is very strange. I have done nothing to the code. I zipped theproject and posted it here. Now it works again???
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
I spoke too soon. The image "test.jpg" works but the same image, in original size 1366 x 768 with 647KB (too large to upload here) crashes the program.
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
I don't see how your code can work.

You are correct, I missed that. But my code does not even get that far.

Here is a small demo. Try each of the image files.
 

Attachments

  • inlinejava2.zip
    314.4 KB · Views: 282
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
You should really learn how to work with smart strings. Much simpler then your current code.

OMG how right you are! Thanks for the tip.

This:

B4X:
'write html header
    tw.WriteLine("<!DOCTYPE html> " )
    tw.WriteLine("<html lang="& QUOTE & "de" & QUOTE & ">" )
    tw.WriteLine("<head>" )
    tw.WriteLine("<meta charset=" & QUOTE & "utf-8"& QUOTE & " />")
    tw.WriteLine("<meta name=" & QUOTE & "viewport"& QUOTE & "content=" & QUOTE & "width=device-width, initial-scale=1.0"& QUOTE & "/>" )
    tw.WriteLine("<title>Image Viewer</title>" )
    tw.WriteLine("<script>" )
    tw.WriteLine("// Wpcluster = true;" )
    tw.WriteLine("</script>" )
    tw.WriteLine("<script src=" & QUOTE & "../GM_Utils/GPX2GM.js" & QUOTE & "></script>" )
    tw.WriteLine("<style>" )
    tw.WriteLine("  #map { width: 90%; width:calc(100vw - 50px); height:700px; height:calc(100vh - 5em); margin:0; padding:0 }" )
    tw.WriteLine("  #map_img { display:none }" )
    tw.WriteLine("</style>" )
    tw.WriteLine("</head>" )
    tw.WriteLine("<body>" )
    tw.WriteLine("<div id=" & QUOTE & "map class=" & QUOTE & "gpxview::Karte" & QUOTE & "><noscript><p>Zum Anzeigen der Karte wird Javascript benötigt.</p></noscript></div>" )

becomes:

B4X:
Dim t As String
    
    t=$"
    <!DOCTYPE html>
    <html lang="de">
    <head>
    <meta charset="utf-8" />
    <meta name="viewport"content="width=device-width, initial-scale=1.0"/>
    <title>Image Viewer</title>
    <script>
        // Wpcluster = true;
    </script>
    <script src="../GM_Utils/GPX2GM.js"></script>
    <style>
          #map { width: 90%; width:calc(100vw - 50px); height:700px; height:calc(100vh - 5em); margin:0; padding:0 }
          #map_img { display:none }
    </style>
    </head>
    <body>
    <div id="map class="gpxview::Karte"><noscript><p>Zum Anzeigen der Karte wird Javascript benötigt.</p></noscript></div>
    
    
    "$
    'write html header
    tw.WriteLine(t)

Brilliant. Will use this in future. Much easier to read.
 
Upvote 0
Top