Executing ExifTool from C# with -stay_open option

Started by lokatz, June 06, 2020, 07:03:14 AM

Previous topic - Next topic

lokatz

Hope it is ok to use this forum to share some hard-won wisdom.  With great help from StarGeek and Phil (thanks again, guys!), I spent the last day-and-a-half to figure out how to run ExifTool from within my C# application.  Other posts on this forum suggest that I'm not the only one struggling with this, so I thought I should share my newfound expertise by giving a code example that shows how this can be done in the most simple of ways, without the use of a wrapper or library:

    bool ExifToolHasBeenLoaded = false;
    Process pExifTool = new Process();

    private void RunExiftoolWhileStayingOpen()
    {
        //  Start ExifTool and keep it in memory if that has not been done yet.
        if (!ExifToolHasBeenLoaded)
        {
            string command = "\"" + ExifToolFolderPath + "exiftool.exe\" -stay_open true -@ args.txt";

            pExifTool.StartInfo = new ProcessStartInfo("cmd", String.Format("/c \"{0}\"", @command));
            pExifTool.StartInfo.RedirectStandardOutput = true;

            //  NOTE:  If you do not implement an asynchronous error handler like in this example, instead simply using pExifTool.StandardError.ReadLine()
            //         in the following, you risk that your program might stall.  This is because ExifTool sometimes reports failure only through a
            //         StandardOutput line saying something like "0 output files created" without reporting anything in addition via StandardError, so
            //         pExifTool.StandardError.ReadLine() would wait indefinitely for an error message that never comes.
            pExifTool.StartInfo.RedirectStandardError = true;
            pExifTool.ErrorDataReceived += new DataReceivedEventHandler(ETErrorHandler);

            pExifTool.StartInfo.UseShellExecute = false;
            pExifTool.StartInfo.CreateNoWindow = true;
            pExifTool.Start();
            pExifTool.BeginErrorReadLine();  //  This command starts the error handling, meaning ETErrorHandler() will now be called whenever ExifTool reports an error.

            ExifToolHasBeenLoaded = true;
        }

        //  Append the args file for Exiftool to start reading and executing the command.
        //  NOTE:  NEVER use WriteAllLines here - ExifTool expects the args file to be appended continually, not re-written.

        string[] args = new string[] { ImageFileName, "-execute" };  //  This tells ExifTool to read out all of the image's metadata.
        File.AppendAllLines(ExifToolFolderPath + "args.txt", args);  //  args.txt gets written into the folder where exiftool.exe resides here.

        string line;
        do
        {
            line = pExifTool.StandardOutput.ReadLine();

            //  NOTE:  Depending on the command you issued, line will either contain a progress report of an operation (e.g., "1 output files created"),
            //         give line-by-line data, such as an image's metadata (e.g. "Orientation                     : Horizontal (normal)"), or
            //         read "{ready}", which indicates that executing the last command in args.txt has completed.
               
                ...  do something with the information provided in line ...
        }
        while (!line.Contains("{ready}"));

        //  At this point, you can issue the next command for ExifTool, following the very same approach.  For instance, this tells ExifTool to
        //  extract a PreviewImage from a RAW image, using the trick of adding "%0f" at the beginning of the new filename as a way to
        //  give the PreviewImage a different name from the orginal file ...
        args = new string[] { "-b", "-PreviewImage", "-w", ImagePreviewFolderPath + "%0f" + ImagePreviewFileName, RAWImageFileName, "-execute" };
        File.AppendAllLines(ExifToolFolderPath + "args.txt", args);

        ...  read and process pExifTool.StandardOutput again as per the above ...

        //  ... and this starts the conversion of an appropriate original image, for instance a .NEF, to .JPG:
        args = new string[] { "-b", "-JpgFromRaw", "-w", JPGImageFolderPath + "%0f" + JPGImageName, RAWImageFileName, "-execute" };
        File.AppendAllLines(ExifToolFolderPath + "args.txt", args);

        ...  read and process pExifTool.StandardOutput again as per the above ...
    }


    //  Finally, this is the asynchronous error handler

    private void ETErrorHandler(object sendingProcess, DataReceivedEventArgs errLine)
    {
        if (!String.IsNullOrEmpty(errLine.Data))
        {
            ...  do something with the information provided in errLine.Data ...
        }
    }


A cakewalk once you figured it all out.   ;)

Phil Harvey

Thanks!  I've added a link from the exiftool home page to this post.

- Phil
...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 ($).

PatD

#2
I'm trying to get this to work but all I get back are nulls. What did I miss?

        bool _ExifToolHasBeenLoaded = false;
        Process _ExifTool = new Process();
        string _ExifToolFolderPath = AppDomain.CurrentDomain.BaseDirectory;
       
       
    Private void RunExiftoolWhileStayingOpen(string ImageFileName) {
            //  Start ExifTool and keep it in memory if that has not been done yet.
            if (!_ExifToolHasBeenLoaded) {
                string command = _ExifToolFolderPath + "exiftool.exe -stay_open true -@ args.txt";

                _ExifTool.StartInfo = new ProcessStartInfo("cmd", String.Format("/c \"{0}\"", @command));
                _ExifTool.StartInfo.RedirectStandardOutput = true;

                //  NOTE:  If you do not implement an asynchronous error handler like in this example, instead simply using _ExifTool.StandardError.ReadLine()
                //        in the following, you risk that your program might stall.  This is because ExifTool sometimes reports failure only through a
                //        StandardOutput line saying something like "0 output files created" without reporting anything in addition via StandardError, so
                //        _ExifTool.StandardError.ReadLine() would wait indefinitely for an error message that never comes.
                _ExifTool.StartInfo.RedirectStandardError = true;
                _ExifTool.ErrorDataReceived += new DataReceivedEventHandler(ETErrorHandler);

                _ExifTool.StartInfo.UseShellExecute = false;
                _ExifTool.StartInfo.CreateNoWindow = true;
                _ExifTool.Start();
                _ExifTool.BeginErrorReadLine();  //  This command starts the error handling, meaning ETErrorHandler() will now be called whenever ExifTool reports an error.

                _ExifToolHasBeenLoaded = true;
            }

            //  Append the args file for Exiftool to start reading and executing the command.
            //  NOTE:  NEVER use WriteAllLines here - ExifTool expects the args file to be appended continually, not re-written.

            string[] args = new string[] { ImageFileName, "-execute" };  //  This tells ExifTool to read out all of the image's metadata.
            File.AppendAllLines(_ExifToolFolderPath + "args.txt", args);  //  args.txt gets written into the folder where exiftool.exe resides here.
            args = null;
            string line;
            do {
                line = _ExifTool.StandardOutput.ReadLine();

                //  NOTE:  Depending on the command you issued, line will either contain a progress report of an operation (e.g., "1 output files created"),
                //        give line-by-line data, such as an image's metadata (e.g. "Orientation                    : Horizontal (normal)"), or
                //        read "{ready}", which indicates that executing the last command in args.txt has completed.
                if (line == null) {
                    line = "**null line returned**";
                    break;
                }
                Debug.Print(line);
            }
            while (!line.Contains("{ready}"));
        }


        //  Finally, this is the asynchronous error handler
        private void ETErrorHandler(object sendingProcess, DataReceivedEventArgs errLine) {
            if (!String.IsNullOrEmpty(errLine.Data)) {
                ////// ...  do something with the information provided in errLine.Data...
            }
        }

rhenley

Its easily been over 12 years ago when I dealt with this Process API, but it didn't make any sense to me to hide the ProcessStartInfo initialization in the Process before fully setting it. But maybe newer versions of .NET work better now. But it doesn't seem though does it?

                    Process _ExifTool = new Process();
                    ProcessStartInfo startInfo = new ProcessStartInfo(_EXIFtoolFile);
                    startInfo.Arguments = strArgsLine;
                    // startInfo.WindowStyle = ProcessWindowStyle.Normal;
                    startInfo.ErrorDialog = false;
                    startInfo.UseShellExecute = false;
                    startInfo.CreateNoWindow = true;
                    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
                    startInfo.RedirectStandardError = true;
                    startInfo.RedirectStandardOutput = true;
                    _ExifTool.StartInfo = startInfo;