Capture commandline output

Hi

I am creating a program which runs a commandline in cmd.  When the command is running, it displays progress information in the cmd.exe window.  This progress is displayed as a %.  How do I get that percentage into my program in a label?

Please have a look at the image below:
http://img249.imageshack.us/img249/1656/imageak2.png

As you can see, when i click the button, a cmd window opens and the processing starts, the red circle shows the percentage the processing is complete.  I would like to hide the cmd window and display that percentage in my windows application instead.

How can I do this?
LVL 1
narmi2Asked:
Who is Participating?
 
gnoonCommented:
>Do you have any links about how to get flac to decode directly into .net byte?
You've done it using this command:   flac -d --stdout

Here, I got the code of my idea as shown below. It's just a workaround to get percent of progress, but not the number from string produced by flac.exe but from calculation.

A --> flac.exe --> B --> lame.exe --> C

A = length of file.flac's bytes
B = length of WAV's bytes
C = length of file.mp3's bytes

You will see that C# can handle on B, and A = B x ratio. I approximate the ratio = 0.72 (from research).

Whenever I receive a part of B I send it to LAME directly.
So, I can use the formula (A = B x ratio) to calculate progress done.
Imports System.IO
Imports System.Threading
Imports System.Text
Imports System.Text.RegularExpressions
 
Public Class Form1
 
    Private lame_proc As Process
    Private flac_proc As Process
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEncode.Click
        'Go to convert in another thread than UI thread
        Dim t As Thread = New Thread(AddressOf LaunchConvertInThread)
        t.Name = "ConvertThread"
        t.IsBackground = False '-- terminate thread on program exited
        t.Start()
    End Sub
 
    Private Sub LaunchConvertInThread()
        'Firstly, starts LAME to wait for input produced by FLAC
        lame_proc = New Process()
        lame_proc.StartInfo.WorkingDirectory = Application.StartupPath
        lame_proc.StartInfo.FileName = "lame.exe"
        lame_proc.StartInfo.Arguments = txtSettings.Text & " - """ & txtOutput.Text & """"
        lame_proc.StartInfo.UseShellExecute = False
        lame_proc.StartInfo.CreateNoWindow = True
        lame_proc.StartInfo.RedirectStandardInput = True '-- make LAME wait for input from <stdin>
        lame_proc.StartInfo.RedirectStandardError = False
        lame_proc.Start()
 
        'and then starts FLAC to produce raw bytes used by LAME
        flac_proc = New Process()
        flac_proc.StartInfo.WorkingDirectory = Application.StartupPath
        flac_proc.StartInfo.FileName = "flac.exe"
        flac_proc.StartInfo.Arguments = "-d -f --stdout """ & txtInput.Text & """"
        flac_proc.StartInfo.UseShellExecute = False
        flac_proc.StartInfo.CreateNoWindow = True
        flac_proc.StartInfo.RedirectStandardOutput = True '-- make FLAC output be read synchronously
        flac_proc.StartInfo.RedirectStandardError = False
        flac_proc.EnableRaisingEvents = False
        flac_proc.Start()
 
        'output ratio (approx. ratio of input/output of flac.exe)
        Const OUTPUT_RATIO As Double = 0.72
 
        Dim totalBytes As Long = New FileInfo(txtInput.Text).Length
        Dim progressBytes As Long = 0
        Dim percentProgress As Double = 0
 
        Dim buffSize As Integer = 4096
        Dim buffData(buffSize) As Byte
        Dim bytesRead As Integer
 
        'read first 4096-bytes from FLAC
        bytesRead = flac_proc.StandardOutput.BaseStream.Read(buffData, 0, buffSize)
        While bytesRead > 0
            'write to LAME
            lame_proc.StandardInput.BaseStream.Write(buffData, 0, buffSize)
 
            'update progress
            progressBytes += bytesRead * OUTPUT_RATIO
            If totalBytes > 0 Then
                'just make sure progress in this loop will not over 99%
                percentProgress = Math.Min(progressBytes / totalBytes * 100, 99)
            End If
 
            'display percent
            Control.CheckForIllegalCrossThreadCalls = False
            TextBox1.Text = String.Format("{0:##0} %", percentProgress)
 
            'read next block
            bytesRead = flac_proc.StandardOutput.BaseStream.Read(buffData, 0, buffSize)
        End While
 
        'Finally, update progress to 100%
        Control.CheckForIllegalCrossThreadCalls = False
        TextBox1.Text = "100 %"
    End Sub
 
    Private Sub btnChoose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnChoose.Click
        OpenFileDialog1.Filter = "Flac files (*.flac)|*.flac"
        If OpenFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
            txtInput.Text = OpenFileDialog1.FileName
            txtOutput.Text = Replace(OpenFileDialog1.FileName, ".flac", ".mp3")
        End If
    End Sub
 
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        SaveFileDialog1.Filter = "mp3 files (*.mp3)|*.mp3"
        If SaveFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
            txtOutput.Text = SaveFileDialog1.FileName
        End If
    End Sub
End Class

Open in new window

0
 
gnoonCommented:
Try to use an OutputDataReceived event of Process class by set output redirect to True

start_info.RedirectStandardOutput = True

then just before proc.Start(), add an event handler

AddHandler proc.OutputDataReceived, AddressOf OutputHandler

Next, define a sub to handle the event

Private Sub OutputHandler(sendingProcess As Object, outLine As DataReceivedEventArgs)
    '-- Here, you can analyse the output using outLine.Data
End Sub

Finally, after you call proc.Start(), just call

proc.BeginOutputReadLine()
0
 
narmi2Author Commented:
I have tried implimenting that but i an not sure what I am doing... help

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEncode.Click
        Dim myfile As New StreamWriter(Application.StartupPath & "\encode.bat")
        myfile.WriteLine("flac -d --stdout """ & txtInput.Text & """ | lame " & txtSettings.Text & " - """ & txtOutput.Text & """")
        myfile.Close()
        myfile.Dispose()
 
        ' Set start information.
        Dim start_info As New ProcessStartInfo
        start_info.WorkingDirectory = Application.StartupPath
        start_info.FileName = "encode.bat"
        start_info.UseShellExecute = False
        start_info.CreateNoWindow = False
        start_info.RedirectStandardOutput = True
        start_info.RedirectStandardError = False
 
        ' Make the process and set its start information.
        Dim proc As New Process()
        proc.StartInfo = start_info
 
        ' Start the process.
        AddHandler proc.OutputDataReceived, AddressOf OutputHandler
        proc.Start()
        proc.BeginOutputReadLine()
        proc.WaitForExit()
        proc.Close()
 
        File.Delete(Application.StartupPath & "\encode.bat")
    End Sub
 
    Private Sub OutputHandler(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
        '-- Here, you can analyse the output using outLine.Data
        Control.CheckForIllegalCrossThreadCalls = False
        TextBox1.Text = outLine.Data.ToString
    End Sub

Open in new window

0
Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
gnoonCommented:
After run, any output at TextBox1?

I've modified the code, is it work for you?
PS: I dont have VS2005 for run test ;-)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEncode.Click
	Dim myfile As New StreamWriter(Application.StartupPath & "\encode.bat")
	myfile.WriteLine("flac -d --stdout """ & txtInput.Text & """ | lame " & txtSettings.Text & " - """ & txtOutput.Text & """")
	myfile.Close()
	myfile.Dispose()
 
	' Set start information.
	Dim start_info As New ProcessStartInfo
	start_info.WorkingDirectory = Application.StartupPath
	start_info.FileName = "encode.bat"
	start_info.UseShellExecute = False
	start_info.CreateNoWindow = True   '<------------- (1) Hide the output
	start_info.RedirectStandardOutput = True
	start_info.RedirectStandardError = False
 
	' Make the process and set its start information.
	Dim proc As New Process()
	proc.StartInfo = start_info
 
	' Start the process.
	AddHandler proc.OutputDataReceived, AddressOf OutputHandler
	AddHandler proc.Exited, AddressOf Batch_Exited  '<------ (2) Delete file on batch finished
 
	proc.Start()
	proc.BeginOutputReadLine()  '<----(3) Begin asynchronous read the output
	'proc.WaitForExit()  '<----------(4) Don't need to wait
	'proc.Close()
End Sub
 
Private Sub OutputHandler(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
	'-- Here, you can analyse the output using outLine.Data
	Control.CheckForIllegalCrossThreadCalls = False
	TextBox1.Text = GetPercent( outLine.Data.ToString )  '<---------(5) Send the output to get only percent number
End Sub
 
Private Sub Batch_Exited(ByVal sender As Object, ByVal e As System.EventArgs)
	File.Delete(Application.StartupPath & "\encode.bat") '<-------(6) Perform delete file on process finished
End Sub
 
Function GetPercent(ByVal output As String) As String  '<----(7) Function to parse for percent number using regular expression
	Dim rx As System.Text.RegularExpressions.Regex = _
		New System.Text.RegularExpressions.Regex("([0-9]{1,3})\s*%", _
		RegexOptions.IgnoreCase Or RegexOptions.Multiline)
	Dim founds As MatchCollection = rx.Matches(output)
	If founds.Count > 0 Then Return founds(0).Groups(1).Value
	Return Nothing
End Function

Open in new window

0
 
narmi2Author Commented:
Thank you gnoon.  I will try this when I get home.  Thanks again.
0
 
narmi2Author Commented:
I am getting an error on the following line

TextBox1.Text = GetPercent(outLine.Data.ToString)

The error message is

Object reference not set to an instance of an object

Any ideas?
0
 
gnoonCommented:
The error will be happen if an variable has not been initialized, but its method/property is being access.

I think, two possible cases
- outLine.Data is Nothing, then we're trying to call outLine.Data.ToString
- GetPercent is not visible as a function in your class, but it's looked like an array

Resolution
- check outLine.Data before access its methods
- define the function in class, copy/paste from my code above

Also, try this
Private Sub OutputHandler(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
	'-- Here, you can analyse the output using outLine.Data
	If Not String.IsNullOrEmpty(outLine.Data) Then
		Control.CheckForIllegalCrossThreadCalls = False
		TextBox1.Text = GetPercent( outLine.Data.ToString )  '<---------(5) Send the output to get only percent number
	End If
End Sub

Open in new window

0
 
narmi2Author Commented:
Ok i tried that but it shows nothing in the textbox1?  the processing part of it works fine, it is just that, it gives me no feedback as to how much is done
0
 
gnoonCommented:
Sorry narmi2, I just installed VS2005 on my comp and did test the code above.
I see that it need a line of code to achieve asynchronous read

        proc.WaitForExit()

It works if this code exists, otherwise it ends the program before the next read is reached.
Sorry again that I made you confused.
0
 
narmi2Author Commented:
No problem at all.  I will try that out as soon as I can, which will probably be at the end of the day.  Thanks again for all the help. :)
0
 
narmi2Author Commented:
gnoon: Where do I add that line of code?
0
 
gnoonCommented:
After the line of proc.BeginOutputReadLine().
0
 
narmi2Author Commented:
i did that and it still did not work.  So I did a little testing by changing the following line:

TextBox1.Text = GetPercent( outLine.Data.ToString )  '<---------(5) Send the output to get only percent number

to this:

TextBox1.Text = outLine.Data.ToString

And it only returned the following text:

C:\Document and Settings\narmi2\My Documents\Visual Studio 2000\Projects\Windows\Application1\WindowsApplication1\bin\Debug\flac -d --stdout "c:\audio.flac" |lame -V9 - "c:\audio.mp3"

That is it!

it did not return the rest of the output for some reason, which explains why it did not display the percentage complete in the textbox.

do you know why it is not returning the full text?  You can see the full text in the image in the first post i made.
0
 
gnoonCommented:
It prints the full text because the @echo command is turned on. If you turn it off, the full text should not printed.

myfile.WriteLine("@echo off") '<------------ turn off echo command itself
myfile.WriteLine("flac -d --stdout """ & txtInput.Text & """ | lame " & txtSettings.Text & " - """ & txtOutput.Text & """")

This is code I used to test capture. It's Console Application. Also, you can test it :-)
Imports System.IO
Imports System.Text.RegularExpressions
 
Module Module1
 
    Dim BaseDir As String = "c:"
 
    Sub Main()
        RunBatch()
    End Sub
 
    Sub RunBatch()
        Dim myfile As New StreamWriter(BaseDir & "\encode.bat")
        myfile.WriteLine("@ECHO OFF")
        myfile.WriteLine("SET COUNT=0")
        myfile.WriteLine(":Loop")
        myfile.WriteLine("cls")
        myfile.WriteLine("echo Encoding as 24 KHz VBR(q=9) j-stereo MPEG-2 Layer III (ca. 16.5x) qval=3")
        myfile.WriteLine("echo audio.flac: %COUNT%%% complete")
        myfile.WriteLine("SET /A COUNT=COUNT+1")
        myfile.WriteLine("@ping 127.0.0.1 -n 2 -w 1000 > nul")
        myfile.WriteLine("@ping 127.0.0.1 -n %1% -w 1000> nul")
        myfile.WriteLine("goto Loop")
        myfile.Close()
        myfile.Dispose()
 
        ' Set start information.
        Dim start_info As New ProcessStartInfo
        start_info.WorkingDirectory = BaseDir
        start_info.FileName = BaseDir & "\encode.bat"
        start_info.UseShellExecute = False
        start_info.CreateNoWindow = True   '<------------- (1) Hide the output
        start_info.RedirectStandardOutput = True
        start_info.RedirectStandardError = False
 
        ' Make the process and set its start information.
        Dim proc As New Process()
        proc.StartInfo = start_info
 
        ' Start the process.
        AddHandler proc.OutputDataReceived, AddressOf OutputHandler
        AddHandler proc.Exited, AddressOf Batch_Exited  '<------ (2) Delete file on batch finished
 
        proc.Start()
        proc.BeginOutputReadLine()  '<----(3) Begin asynchronous read the output
        proc.WaitForExit()  '<----------(4) Don't need to wait
        'proc.Close()
 
    End Sub
 
    Private Sub OutputHandler(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
        '-- Here, you can analyse the output using outLine.Data
        If Not String.IsNullOrEmpty(outLine.Data) Then
            Console.WriteLine(GetPercent(outLine.Data.ToString))  '<---------(5) Send the output to get only percent number
        Else
            Console.WriteLine("NULL!!!")
        End If
    End Sub
 
    Private Sub Batch_Exited(ByVal sender As Object, ByVal e As System.EventArgs)
        File.Delete(BaseDir & "\encode.bat") '<-------(6) Perform delete file on process finished
    End Sub
 
    Function GetPercent(ByVal output As String) As String  '<----(7) Function to parse for percent number using regular expression
        Dim rx As System.Text.RegularExpressions.Regex = _
         New System.Text.RegularExpressions.Regex("([0-9]{1,3})\s*%", _
         RegexOptions.IgnoreCase Or RegexOptions.Multiline)
        Dim founds As MatchCollection = rx.Matches(output)
        If founds.Count > 0 Then Return founds(0).Groups(1).Value
        Return Nothing
    End Function
 
End Module

Open in new window

0
 
narmi2Author Commented:
Thank you.  I will try this tonight.
0
 
narmi2Author Commented:
I tried that and it is not working, it produces no output at all in the text box.  Can you please have a look at the project, you might see something which I have not seen.  I have zipped the whole project: https://filedb.experts-exchange.com/incoming/ee-stuff/6028-WindowsApplication1.zip
0
 
gnoonCommented:
OK, I will check it out this tonight when I got home.
0
 
narmi2Author Commented:
To get it to run, you will need lame.exe from this download: http://audio.ciara.us/rarewares/lame3.97.zip
and flac.exe from this download: http://switch.dl.sourceforge.net/sourceforge/flac/flac-1.2.1-win.zip

also, you will need a .flac audio file to convert.  you can easily make on of those with the flac encoder above if you have a wav file already.  if you need help with any of the above, please let me know.
0
 
narmi2Author Commented:
Hi

Looks like I need to capture the StandardError instead of the StandardOutput as the bit that I am interested in, the percentage complete, comes in the StandardError and not in the StandardOutput.  I will try this when I get home in the evening...

Please let me know if you have any luck with this.
0
 
gnoonCommented:
I tested the code. Yes, as you said. Lame prints out the percent message on error stream.

There are issues here about the code
- better start the process in another thread than UI-thread to prevent screen frozen.
- you need to check for not empty output before set to textbox.
- proc.Exited will be invoked only if proc.EnableRaisingEvents = True
- since you use AddHandler (asynchronous reading) and it's Win Form App (event driven programming), proc.WaitForExit() is not needed ;-)

Here the code I modified
Imports System.IO
Imports System.Threading
Imports System.Text.RegularExpressions
 
Public Class Form1
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEncode.Click
        'move code here into a thread to prevent screen frozen
        Dim t As Thread = New Thread(AddressOf ProcessInThread)
        t.Name = "ConvertThread"
        t.IsBackground = True
        t.Start()
        'ProcessInThread()
    End Sub
 
    Private Sub ProcessInThread()
        Dim myfile As New StreamWriter(Application.StartupPath & "\encode.bat")
        myfile.WriteLine("@echo off")
        myfile.WriteLine("flac -d --stdout """ & txtInput.Text & """ | lame " & txtSettings.Text & " - """ & txtOutput.Text & """")
        myfile.Close()
        myfile.Dispose()
 
        ' Set start information.
        Dim start_info As New ProcessStartInfo
        start_info.WorkingDirectory = Application.StartupPath
        start_info.FileName = "encode.bat"
        start_info.UseShellExecute = False
        start_info.CreateNoWindow = True
        start_info.RedirectStandardOutput = True
        start_info.RedirectStandardError = True
 
        ' Make the process and set its start information.
        Dim proc As New Process()
        proc.StartInfo = start_info
        proc.EnableRaisingEvents = True
 
        ' Start the process.
        AddHandler proc.OutputDataReceived, AddressOf OutputHandler '<----- isn't need
        AddHandler proc.ErrorDataReceived, AddressOf ErrorHandler
        AddHandler proc.Exited, AddressOf Batch_Exited
 
        proc.Start()
        proc.BeginOutputReadLine()
        proc.BeginErrorReadLine()
        'proc.WaitForExit()
        'proc.Close()
    End Sub
 
    Private Sub OutputHandler(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
        If Not String.IsNullOrEmpty(outLine.Data) Then
            Dim percent As String = GetPercent(outLine.Data.ToString)
            Console.WriteLine("output: " & outLine.Data & " [" & percent & "]")
            If Not String.IsNullOrEmpty(percent) Then
                Control.CheckForIllegalCrossThreadCalls = False
                TextBox1.Text = percent
                'Console.WriteLine(percent)
            End If
        End If
    End Sub
 
    Private Sub ErrorHandler(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
        If Not String.IsNullOrEmpty(outLine.Data) Then
            Dim percent As String = GetPercent(outLine.Data.ToString)
            Console.WriteLine("error: " & outLine.Data & " [" & percent & "]")
            If Not String.IsNullOrEmpty(percent) Then
                Control.CheckForIllegalCrossThreadCalls = False
                TextBox1.Text = percent
                'Console.WriteLine(percent)
            End If
        End If
    End Sub
 
    Private Sub Batch_Exited(ByVal sender As Object, ByVal e As System.EventArgs)
        Console.WriteLine("exit")
        File.Delete(Application.StartupPath & "\encode.bat")
    End Sub
 
    Function GetPercent(ByVal output As String) As String
        Dim rx As System.Text.RegularExpressions.Regex = _
                New System.Text.RegularExpressions.Regex("([0-9]{1,3})\s*%", _
                                                         RegexOptions.IgnoreCase Or RegexOptions.Multiline)
        Dim founds As MatchCollection = rx.Matches(output)
        If founds.Count > 0 Then Return founds(0).Groups(1).Value
        Return Nothing
    End Function
 
    Private Sub btnChoose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnChoose.Click
        OpenFileDialog1.Filter = "Flac files (*.flac)|*.flac"
        If OpenFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
            txtInput.Text = OpenFileDialog1.FileName
            txtOutput.Text = Replace(OpenFileDialog1.FileName, ".flac", ".mp3")
        End If
    End Sub
 
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        SaveFileDialog1.Filter = "mp3 files (*.mp3)|*.mp3"
        If SaveFileDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
            txtOutput.Text = SaveFileDialog1.FileName
        End If
    End Sub
End Class

Open in new window

0
 
narmi2Author Commented:
Hi, thanks for that.  It is not doing what I was hoping for.  This code displays the percentage when the process is complete, I was hoping to display the percentage as the process was processing, so you would see the percentage incrementing in the textbox.
0
 
gnoonCommented:
>I was hoping to display the percentage as the process was processing
Actually, it did that. I think, the process is too fast, so you can see only 100% progress. You may try with a larger file. It's also happen on my comp.
However, I will help to check it again.
0
 
narmi2Author Commented:
I tried a large 80Mb flac file and nothing came in the textbox at all.  It just displayed 100 when the processing finished.  The time it took to process that 80Mb was 1 or 2 minutes.
0
 
narmi2Author Commented:
Please click on the link below to see an swf file of what happens.
http://www40.brinkster.com/namiiii/encoding.swf.html

you might have to use internet explorer 7 to open that link.

anyway, as you can see, when it is processing, nothing happens in the textbox.  only after the processing is finished, does the percentage go into the textbox.

how do i make it more real time?
0
 
gnoonCommented:
Hi, sorry for late response.

I tried to run only flac in the Process
  flac -d -f "c:\audio.flac"
, get the same result. It's lazy of print out.

Also, I searched over the net, and get nothing helpful.
I think it's something about cache of flac.exe or the difference between 32-bit GUI and 16-bit Console.
(This article is useful http://www.codeproject.com/KB/threads/redir.aspx)

However, I have an idea of solution. Lets called "C# pipeline".

Instead of redirecting flac's output to lamp directly, as input, just redirect it to C# then forward to lamp afterward.
This can be done by define two Process in code. One handles flac.exe and get its output at the ErrorHandler, then send the output (array of byte) to the second process of lame.exe. The second process produces MP3 file using the array of byte received. Count the bytes received as progress.

Just an idea. I will look for a free time to test it.
0
 
narmi2Author Commented:
That sounds interesting!

Do you have any links about how to get flac to decode directly into .net byte?
0
 
gnoonCommented:
Line 58 should be this

lame_proc.StandardInput.BaseStream.Write(buffData, 0, bytesRead)

not this

lame_proc.StandardInput.BaseStream.Write(buffData, 0, buffSize)

Sorry.
0
 
narmi2Author Commented:
Hi

Thanks

That works a lot better than before.  But just 1 question, why is there a delay between 99 and 100?
0
 
gnoonCommented:
>why is there a delay between 99 and 100?
Just to make sure that user will see 100% only when the convert was already done.

We use the fixed-ratio = 0.72, but in real this number can be vary depend on option used by flac.
So, it's possible to get 100% (or more than 100) inside the while loop, but not long time it will break out.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.