exiftool .Net wrapper (written in vb.net)

Started by Curtis, June 09, 2014, 05:31:48 PM

Previous topic - Next topic

Curtis

I have a wrapper for exiftool for .Net.  I wrote it in vb.net and have been using it for sometime now and it works very well for me.  I recently noticed some posts related to using exiftool with .Net so I decided to make a small demo program to show how to use the wrapper (ExifToolIO.dll).  (the program I'm using ExifToolIO in is too big to post here)

I believe the code runs nearly as fast as possible, I have tried to optimize it for speed.  Some of the features are:
A of version exiftool.exe is included in the ExifToolIO.dll so no separate exiftool.exe is needed.  Optionally  a separate exiftool.exe file can be used.

I/O to the exiftool process is by default via stdinp and stdout so no disk file I/O is involved, making it very fast. Optionally a text file may be used and then viewed later to see what commands were actually sent to exiftool.

Very simple to use, simply call Initiailize, send commands using Cmd method then call Execute which returns the results of exiftool.

Sample vb.net code:

ExifToolIO.Initiailize()
ExifToolIO.Cmd("-IFD0:Artist")
Dim result as String = ExifToolIO.Execute("C:\pic.jpg")


Once ExifToolIO.Initiailize() is called as many ExifToolIO.Cmd and ExifToolIO.Execute calls can be made as needed.  Then before exiting the program call
ExifToolIO.Close.  The demo program shows several ways to get tag values including using the -X XML exiftool output format.

I have attached the VS 2012 VB.Net Solution that has all the source code to ExifToolIODemo and ExitToolIO.  Also attached to this post are the executable files (.exe and .dll)  Also see the attached screen shot of the demo program. (I have also put it up on CodeProject and the source can be looked at and downloaded from there https://workspaces.codeproject.com/curtis1757/exiftool_wrapper/code, someday I may write a CodeProject article for this)

Possible issues: I've only run this on my Window 7 machine, Framework 4.0 is required.

I welcome any questions, comments, bug reports or suggestions..... please post them here.... (or my email address is in the source code)

Thanks!
Curtis

Update: Added JSON format output example in demo program.

Curtis

Recently I have had some inquiry about the ExifToolIO.Cmd method and spaces in its input string.  Since when Exiftool is running in the -stay_open mode, it wants each of it's commands on a separate line (check the Exiftool documentation for -stay_open and the -@ commands for details on this), the ExifToolIO.Cmd method by default converts spaces to CrLf so you can give the ExifToolIO.Cmd method a whole string of commands in one call.
Like

ExifToolIO.Cmd("-X -t -l")


But some strings sent to ExifTool need spaces, such as tag values and file names.  When this is the case then set the optional second argument to the ExifToolIO.Cmd method to False and then spaces will be preserved.  The ExifToolIO.Cmd method  adds a CrLf after each call to it.  Also note that for the file name given in the ExifToolIO.Execute method, spaces are not converted to CrLf.

Below is sample code fragment that shows a simple way to set tag values in a file. Before calling this SaveChangedTags method ExifToolIO.Initialize needs to have been called.


Imports ExifToolLib

Public Class Sample
    Public Class TagValue
        Public Name As String
        Public Value As String()
    End Class

    '--Some Exiftool commands that I commonly use....

    Private Const CmdOverWriteOriginal = "-overwrite_original"
    Private Const CmdQuiet = "-q"
    Private Const CmdPrintFormat = "-p"
    Private Const CmdSep = "-sep"                                '--set seperator string for between array values
    Private Const CmdListx = "-listx"                            '--gets tag info
    Private Const CmdListff = "-listf"                           '--all supported file extensions
    Private Const CmdListr = "-listr"                            '--all recognized file extensions
    Private Const CmdListwf = "-listwf"                          '--all writable file extensions
    Private Const Cmdl = "-l"                                    '---l expands list to give descriptions too
    Private Const CmdListxFlags = "-f"
    Private Const CmdDisablePrintConversion = "-n"
    Private Const CmdXMLOutput = "-X -t -l"                      '--XML output with table names and get prt and val values at same time
    Private Const CmdBinary = "-b"                               '--get binary values (as base64 text)

    '--Sample routine to save tag values

    Public Sub SaveChangedTags(fn As String, TagValues As TagValue(), Optional OverWriteOriginal As Boolean = False, Optional TagValSep As String = "*")
        If TagValues.Length = 0 Then Return

        Dim first As Boolean = True

        '--write out all the tag values....

        For Each t As TagValue In TagValues
            If first Then

                '--These commands only need to be done once

                ExifToolIO.Cmd(CmdQuiet)
                ExifToolIO.Cmd(CmdSep)
                ExifToolIO.Cmd(TagValSep)

                '--Note the above 3 lines could be converted to:
                'ExifToolIO.Cmd(CmdQuiet & " " & CmdSep & " " & TagValSep)
                '-- OR
                'ExifToolIO.Cmd("-q -sep " & TagValSep)
                '--(all are equivalent, keep in mind ExifTool expects each command to be on a separate line, which is why by default .Cmd converts spaces to CrLf)

                If OverWriteOriginal Then ExifToolIO.Cmd(CmdOverWriteOriginal)

                first = False
            End If

            '--Write out the commands to change tag values (check for value being an array)
            '--Note that the second argument in .Cmd is set to false so spaces are not converted to CrLf (tag values may certainly contain spaces),
            '--this is also needed with ExifTool commands that take a file name since file names may have spaces

            If t.Value.Length > 1 Then

                '--We have an array, so join it into a single string

                ExifToolIO.Cmd("-" & t.Name & "=" & String.Join(TagValSep, t.Value), False)
            Else
                ExifToolIO.Cmd("-" & t.Name & "=" & t.Value(0), False)
            End If
        Next

        '--now Execute the previous commands on the given file to update the tag values

        ExifToolIO.Execute(fn)
    End Sub
End Class


I hope this clears up a few questions concerning this and hope you find the code useful.

Curtis

FixEUser

Thank you very much for your code, Curtis!

Could you please provide a code example how to write back a change in one or more tags?

Curtis

Here is some example code (very hard coded, but hopefully you get the idea) using the routine in my previous post. The example shows writing  tags to a file (the second tag being a list type (array).

  Public Sub TestSaveTags()
        Dim fn As String = "C:\mypic.jpg"
        Dim Tags() As TagValue = {New TagValue With {.Name = "IDF0:Software", .Value = {"MySoftware!"}}, New TagValue With {.Name = "XMP-mwg-rs:RegionName", .Value = {"Curtis*Bob*Joe"}}}

        SaveChangedTags(fn, Tags, True)
    End Sub


HTH

FixEUser

@Curtis:

How can I write an export file with your
Private Const CmdListx = "-listx"
and flags like
Private Const CmdListxFlags = "-f"

Such commands doesn't need a file name.
Whatever I try, I only get a "nothing to write" as .LastErrorMsg, but no written file at all.

Curtis

Hi FixEUser,

The -listx command does not get tag values from an image file.  It simply gets information (properties) of tags that exiftool supports.  So no file is needed for input.  If this is what you want (probably not) the output of the -listx command is given in the string returned by the  ExifToolIO.Execute() function. (Note: the fn parameter is optional and you would not need it here since no input file is needed).  Once you have the string result from the ExifToolIO.Execute() function you could write it to a file if that is what you want to do.

If what you are looking for is the value of a tag in a particular image file, then you would do something like this:

ExifToolIO.Initiailize()
ExifToolIO.Cmd("-IFD0:Artist")
Dim result as String = ExifToolIO.Execute("C:\mypic.jpg")


and result will have the value of the IFD0:Artist tag, which you could change and then write back to the C:\mypic.jpg file with code similar to what I showed in my previous post.

HTH
Curtis

FixEUser

Thank you very much for your fast answer, Curtis.

I try to read & parse the output of the -listx command to verify if a specific tag is writable or not.
If not, I display this information or change the color of such a tag.

I think, I don't have to write an outputfile at all if I can get the result as a string. Let me check it .-)

Curtis

You could also use the  -listw command to get only the writable tag names.  You could get them all once, store them and then simply check the list of writable tag names you stored to see if a tag you got from an image file is writable.

Check out the -list documentation here: www.exiftool.org/exiftool_pod.html if you have not already.

Curtis

FixEUser

It's me again.

Could you please show us how to use the Me.tbxErrorMsgs.Text &= ExifToolIO.LastErrorMsg

I can't loop through a list of filenames and get the .LastErrorMsg for every single processed file with newly written tags.
It seems that .LastErrorMsg accumulate the messages (and use some strange & vbCrLf Syntax) if the result has two lines like:
Quote"Nothing to do." & vbCrLf & "Warning: Sorry, Composite:AdvancedSceneMode doesn't exist or isn't writable" & vbCrLf

Curtis

FixEUser,

Sorry did not respond earlier, I have been away for a few weeks.

Not really sure what your question is, if you have not resolved this and still need help, could you post a more complete code sample?

Curtis

FixEUser

My question is:
How can I get error messages like"Nothing to do." & vbCrLf & "Warning: Sorry, Composite:AdvancedSceneMode doesn't exist or isn't writable" & vbCrLfas one single string, not as multiple strings?

Just try a For Each loop with multiple files that generate error messages like the above. Try to display the error message for every file in a textbox.

Or maybe you can show us a code example how to handle error messages with more then one line?

Curtis

FixEUser,

I think what you want is for each file the error string to not have any CrLf's in it.  What is happening in ExifToolIO in Sub ErrorOutputHandler each time it gets Err output from exiftool it appends a CrLf to it so that it separates the individual err messages for a given execution of exiftool (ie for each file), otherwise it all runs together and is harder to understand.  I believe most users would want it this way and I would suggest not changing the Sub ErrorOutputHandler.

The simple fix, for what I think you want, is to remove the CrLf's from the returned LastErrMsg string.  To my Demo code I added a button on the main form which executes the following code:
   Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        For Each fn As String In Me.lbxFiles.Items
            ExifToolIO.Cmd("-Composite:AdvancedSceneMode=""xx""")
            ExifToolIO.Execute(fn)
            Me.tbxErrorMsgs.Text &= ExifToolIO.LastErrorMsg.Replace(ControlChars.CrLf, " ") & ControlChars.CrLf
        Next
    End Sub


This will replace the CrLf's within each LastErrorMsg with a space and then append a CrLf at the end for each file (fn). So then the Me.tbxErrorMsgs.Text will show the LastErrorMsg as one line for each file.

HTH
Curtis

Spilly

Curtis

May I ask you to scan my https://exiftool.org/forum/index.php/topic,7801.msg39567.html#msg39567 issue posted today (11 Nov) on a problem I have with StdError text getting mixed up with StdOut text?

, having stuck with VBA inside MS Office.

I don't fancy a huge learning curve (at age 77!!). I never graduated to .NET code; just stuck to plain MS Office.
I'm hoping you might be able to help  OR suggest a design improvement.

BTW - My "customer" is still using Office 2000 under Vista, so whatever I do, it needs to work for that
(and there's no money involved either  :) )

Spilly

Curtis

Spilly.....  see my response in the thread you referenced.....

photos

Hi Curtis,

I know its been some time since this thread was active.  I tried using the link to codeproject you provided but it isnt working, is it still there?  I am using VS2010 so I cant open your attached project files.  I have written software that lets me tether a Nikon camera, and I would like to know the shutter count (as part of the UI) as I am taking the photos.  Your wrapper looks like it would definitely hand the shutter count back to a string at the time I transfer the image from camera memory, can your wrapper do that?

regards Tom

StarGeek

Yeah, the link appears dead and it doesn't look like it's been copied to another source and no sign of it in the Wayback Machine.  The post just above yours appears to have been the last post by Curtis.

You might try google for something similar.  I saw at least one other .net exiftool wrapper out there (NExifTool) though no idea how well it works.
* Did you read FAQ #3 and use the command listed there?
* Please use the Code button for exiftool code/output.
 
* Please include your OS, Exiftool version, and type of file you're processing (MP4, JPG, etc).

Curtis

Tom,
Yes, that link is likely no longer good.
Best to just DL the source code  from original post. 
As far as the getting the shutter count I'm not familiar with the tag that would give you the shutter count. But if you know the tag name, then yes you should be able to get the value.
I have not actively worked on that code for quite a while, but if you have some specific questions, I'll do my best to answer.
Curtis

photos

Hi Curtis,

Thanks for the response.  I managed to edit the project files to open the code in VS2010.  Looking at your code and a few trial and error tests found the shutter count in the maker notes, which for the Nikon cameras is under the Nikon TAG.  So;


                ExifToolIO.Initiailize()
                ExifToolIO.Cmd("-Nikon:ShutterCount")
                Dim tmpstr As String = ExifToolIO.Execute(ImageName)
                lblShutter.Text = tmpstr
                ExifToolIO.Close()


worked perfect for me.  Thanks for all of your efforts on your wrapper it looks to be very detailed.  It took five days of searching and many tests to get to a point which I though should only take a couple of hours :)
I had no idea how deep the water was.  Thanks again Curtis.

Curtis

Tom,
Glad you got it to work under VS 2010. I think you can download VS 2015 and 2017 for free... they have some nice new features.
Curtis

gkinney

Hello Cutis, I know this is an old thread. I cannot get Windows 10 to allow the writing. I can do it in dos no problem. Any clue? If you even can remember your tool?

Thanks!

Curtis

Hi gkinney,
I don"t have a Windows 10 machine, only Windows 7, so I can't try to replicate your problem.  I don't understand if you have used my tool and it worked and if so on what kind of system? or ??  .Net I don't think runs under DOS?!.

If you can tell me more what you have tried I may be able to help...
Curtis

gkinney

Hello Curtis, My apologies. I am using Visual Studio Community 2017. Version 15.9.15 on Windows 10 PRO

What I am seeing is that my app reads the tags I need (IPTC:Keywords) fine.
Then at some point, it stops allowing me to update the keywords in the file.  (Ive done it several times with ExifTooolIO from my project) and ExifTool.exe

However, I may be on to something!

I just caused an error when I selected a folder with no files and got an exception error selecting an item in a list control that was null. (my mistake) Hower now im seeing the rename error below. So this may be related to a locked file, or some other windows issue? Im looking at that now.

Attached is what I get when I know I am in fail mode:

Ill report back when I find a solution.

Thank you for your reply!



StarGeek

Error renaming temp files are usually caused by something having a lock on the original file.  The way exiftool works is that it creates a temporary file with the updates to the metadata, deletes the original, and renames the temp file.  If some program has the original file open for reading/writing, then the file can't be deleted and the temp file can't be renamed.
* Did you read FAQ #3 and use the command listed there?
* Please use the Code button for exiftool code/output.
 
* Please include your OS, Exiftool version, and type of file you're processing (MP4, JPG, etc).

Curtis

What StarGeek said is correct.  That error message originates from ExifTool, not a problem with exiftoolio.

I noticed the file name you get the error on is:  C:/Gray/testfile.jpg
should the '/' be '\' ??  Not sure if that is a problem.

Opps... I just remembered I think ExifTool changes the \ to /  internally .. so all should be OK with that it just shows the / in the error message from ExiftTool....

PS: I use Unlocker to unlock locked files... really has helped me a lot in many situations.  Worth a try... it is here:
https://www.softpedia.com/get/System/System-Miscellaneous/Unlocker.shtml

gkinney

Curtis and Stargeek Thanks for your reply!

I was on the same path as the locked file, I just couldn't figure out why?

I FINALLY figured it out. I wrote this app and had a picture control in it to view the image. I loaded the image in the control and then read the keywords. I then did a save on the keywords and then would get the lock error. I suddenly realized, that MAYBE the file loaded by the pic control may be locking the file. BINGO! When I removed the file from being loaded in the pic control, the problem went away!

Thanks to everyone that jumped in here and sorry for the hassle. But may help the next person?

Have a great day!

saul

Hi
Background: I have a C# / .Net application called Timelapse (http://saul.cpsc.ucalgary.ca/timelapse/) which biologists use to inspect and encode data describing thousands / millions of wildlife images captured by camera traps. Timelapse also allows the biologist to specify particular metadata fields that should be added to their data.

The issue is how to quickly extract the metadata field value in bulk over tens or even hundreds of thousands of images at a time.

I have used ExifToolWrapper in the past, but the current version of my system uses Drew Noak's Metadata extractor as it is an integrated dll (https://github.com/drewnoakes/metadata-extractor).

However, I was going to switch back to ExifTool (as it is much better at reading in makernotes from various cameras) but decided to run a speed test first.  (I can post / email the source for the test program if desired).

The time to extract the same metadata field over 1000 images is:
- ExifTool (using the ExifToolWrapper https://github.com/brain2cpu/ExiftoolWrapper): ~6.7 seconds
- MetadataExtractor: ~0.23 seconds
This is obviously a significant difference, and it can have huge impact on my users processing massive photo numbers.

Three questions
1. Are there other wrappers other than ExifToolWrapper that are faster and/or better that you can recommend?
3. Is the inefficiency in ExifTool, or in how ExifToolWrapper invokes it?
2. If you are familiar with ExifToolWrapper, is there a better way to get it to extract metadata fields aside from iterating over each file using MetadataExtractorGetFieldValue  - note that I am using exifTool.Start() before the loop, so I am just using one process vs. restarting ExifTool for every iteration.

StarGeek

I'm not sure how much help can be found here though there may be a few users who might chime in.  There haven't been too many questions along these lines over the years. 

What I can say is that exiftool is still a Perl application.  The Windows "executable" is, at it's heart, the Perl code packed with a minimal Perl interpreter.  There are going to be limitations compared to a purely compiled metadata extractor.

Any more details may have to wait until sometime next week, as Phil is currently away.
* Did you read FAQ #3 and use the command listed there?
* Please use the Code button for exiftool code/output.
 
* Please include your OS, Exiftool version, and type of file you're processing (MP4, JPG, etc).

Curtis

Saul,
Have you tried the .net wrapper I wrote? (see first post on this thread) I have not used it lately, but as I recall it is much faster than taking 6.7 sec for 1,000 images.  Would be a very short C# or vb.net program to try that out with my wrapper. 

Also, I have a version that is multi threaded and will use up to all of the cores that you have to process the images.  I have not made this version available here as it is more complicated and few people had the need for that.

I'm traveling now so I don't have access to that code, but if you try my existing code and if it seems like it could work for you then I could possibly get the multi threaded version code to you later.

HTH,
Curtis

saul

Thanks Curtis - I'll give this a shot and will get back to you. That multi-threaded version sounds very promising too, especially because its not unusual to have people trying to extract metadata from tens of thousands of images at a time.

saul

Hello Curtis
dI just ran your program to test it against MetadataExtractor  and ExiftoolWrapper. Unfortunately, its slower: about 16 seconds vs ExifToolWrapper to extract a field from 1000 images (vs .~.2 secs for metadataExtractor).

For coding, I invoked (according to the documentation in your post):
-ExifToolIO.Initiailize();
-Iterated over (sorta pseudocode)
     for each path in paths
            ExifToolIO.Cmd(-IFD0:Make);
            return ExifToolIO.Execute(path);
-ExifToolIO.Close()

Another reply to my post suggests its an inherent performance limitation due to ExifTool being a Python program. I should test the raw ExifTool program to see how it performs as that would be the 'best result' I could get with it, where I could then compare it with the wrapper overhead.

FYI, My current workaround is to provide both MetadataExtractor and ExifToolWrapper to the user as a choice. If the desired metadata field is displayed using MetadataExtractor, then that is the way to go as it is fast. If not, they can use ExifToolWrapper to see if the desired metadata field is there, and if so use that along with a performance penalty. Not the best solution, but it seems to work.




FixEUser

@saul: Just to be sure
Have you ever tried the ".Net wrapper for the excellent ExifTool" from Mike Morano?
Available as Nuget package. This one even offers a .GetTagsAsync which accepts a FilePath or IO.Stream.

@Curtis: No offense meant  :-X

andrewj

Hi Curtis
I'm trying to run a command which requires embedding a filename within the command itself:
QuotesCmd = "-tagsfromfile """ & sJPEGFilename & """ -exif -makernotericoh -make -model -IFD0 "

For example, that becomes the following, which works fine from the command line:
Quote-tagsfromfile "E:\Pictures\Incoming\Ricoh Theta\161121_RTH_0010233.jpg" -exif -makernotericoh -make -model -IFD0

I'm having great difficulty getting it to process correctly. I have tried wrapping the filename in double quotes, single quotes, double quotes prefixed by a backslash. All to no avail. Either the command returns with a blank result, or it just hangs.

What's the best way to include a filename to avoid this?
Thanks
Andrew

Phil Harvey

Hi Andrew,

Does it work without quotes for files with no spaces in the file name?:

sCmd = "-tagsfromfile " & sJPEGFilename & " -exif -makernotericoh -make -model -IFD0"

- Phil

P.S. What is the -IFD0 for?  I don't think this will do anything.
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

andrewj

Hi Phil
With no quotes the command throws an exception:

Exception thrown: 'System.ArgumentOutOfRangeException' in System.Windows.Forms.dll
-tagsfromfile E:\Pictures\Incoming\Ricoh Theta\161122_RTH_0010236.jpg -exif -makernotericoh -make -model -IFD0

As I said, this works fine from the command line.
Andrew

andrewj

Hi,
I've cracked it. If you use the "tagsfromfile" or similar commands, then stdErr is awaiting input before the command can complete. If you run using "shell" or from the command line this is obvious, as there is a very obvious "Press Enter to continue prompt".

To get this to work you need to redirect stdInput and send it a blank line as input. Then when the command gets to the point where it needs the input, there's a buffered line waiting for it.

Here's my code which runs this. I suspect that the internal implementation of ExifToolIO is very similar, and if Saul can just add the two lines with the 'XXX comments then the problem will go away.
   Public Function RunProcess(sExecutable As String, sParameters As String, Optional sDirectory As String = "") As ProcessOutput
        ' Execute pricess in hidden window and capture output
        Try
            Dim oOutput As New ProcessOutput
            Dim pProcess As New System.Diagnostics.Process()            ' Create process
            pProcess.StartInfo.FileName = sExecutable                   ' Set executable and parameters
            pProcess.StartInfo.Arguments = sParameters
            pProcess.StartInfo.UseShellExecute = False                  ' Control execution visibility
            pProcess.StartInfo.CreateNoWindow = True
            pProcess.StartInfo.RedirectStandardOutput = True            ' Set outputs of program to be written to process output stream
            pProcess.StartInfo.RedirectStandardError = True
            pProcess.StartInfo.RedirectStandardInput = True               ' XXX Add This XXX
            If sDirectory <> "" Then pProcess.StartInfo.WorkingDirectory = sDirectory
            pProcess.Start()                                            ' Start the process
            pProcess.StandardInput.WriteLine("")                        ' XXX Complete processing, equivalent of pressing return to continue XXX
            pProcess.WaitForExit()                                      ' Wait for process to finish
            oOutput.StdOutput = pProcess.StandardOutput.ReadToEnd()     ' Read outputs
            oOutput.StdError = pProcess.StandardError.ReadToEnd
            Return oOutput
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try
    End Function


Andrew

StarGeek

Quote from: andrewj on March 09, 2020, 01:23:23 PMIf you run using "shell" or from the command line this is obvious, as there is a very obvious "Press Enter to continue prompt".

Are you sure you haven't accidentally included the -k (pause) option?  Either directly in the command or as (-k) as part of the exiftool.exe name e.g exiftool (-k).exe?
* Did you read FAQ #3 and use the command listed there?
* Please use the Code button for exiftool code/output.
 
* Please include your OS, Exiftool version, and type of file you're processing (MP4, JPG, etc).

SCM

Longtime fan of ExifTool, first attempt to use it within another program.

I've created a small image watermarking program in VB2019 for others at my work. I load an image, it gets placed in a VB picturebox as a bitmap, the user can carry out minor alterations, and a watermark style is selected and added. While any supported image can be opened, it only saves in JPEG. As part of the save process, and 'in the background', I'd like to add in a standard line to the copyright field of the exif metadata. The bitmap in the picturebox is saved as a JPEG using a standard save file dialog.

I'm been playing around with the wrapper (least I think I have) and cannot seem to get it working with the image from the picturebox. I'm very novice at VB coding so I'm probably overlooking something.

I've tried converting the bitmap directly to a type that Visual Studio doesn't throw an error for but no luck, various errors halt the program at the execute commend. Some Googlefu has turned up bits and pieces, such as sending the bitmap through a memory stream, but I'm at a loss for even a starting spot.

Thanks everyone in advance!