C# execute external program and capture (stream) the output - c#

I'm making a program to work with some video files.
I'm using the ffmpeg executable to merge several files in a single file.
This command takes several minutes to finish, so, I need a way to "monitor" the output, and show a progress bar on GUI.
Looking at the following stackoverflow topics:
How to parse command line output from c#?
Process.start: how to get the output?
How To: Execute command line in C#, get STD OUT results
I made this code:
Process ffmpeg = new Process
{
StartInfo =
{
FileName = #"d:\tmp\ffmpeg.exe",
Arguments = "-f concat -safe 0 -i __sync.txt -c copy output.mp4",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
WorkingDirectory = #"d:\tmp"
}
}
ffmpeg.EnableRaisingEvents = true;
ffmpeg.OutputDataReceived += (s, e) => Debug.WriteLine(e.Data);
ffmpeg.ErrorDataReceived += (s, e) => Debug.WriteLine($#"Error: {e.Data}");
ffmpeg.Start();
ffmpeg.BeginOutputReadLine();
ffmpeg.WaitForExit();
When I run this code, the ffmpeg start to merge files, I can see the ffmpeg process on Windows Task Manager, and if I wait long enough, the ffmpeg finish the job without any error. But, the Debug.WriteLine(e.Data) is never called (no output on Debug window). Tried to change to Console.WriteLine too (again, no output).
So, after this, I tried this another version:
Process ffmpeg = new Process
{
StartInfo =
{
FileName = #"d:\tmp\ffmpeg.exe",
Arguments = "-f concat -safe 0 -i __sync.txt -c copy output.mp4",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
WorkingDirectory = #"d:\tmp"
}
}
ffmpeg.Start();
while (!ffmpeg.StandardOutput.EndOfStream)
{
var line = ffmpeg.StandardOutput.ReadLine();
System.Diagnostics.Debug.WriteLine(line);
Console.WriteLine(line);
}
ffmpeg.WaitForExit();
Again, the ffmpeg is started without any error, but the C# "hangs" on While (!ffmpeg.StandardOutput.EndOfStream) until ffmpeg is finished.
If I execute the exact command on Windows prompt, a lot of output text is showed with progress of ffmpeg.

I found the problem.
For some reason, ffmpeg output the progress on stderr, not stdout.
So, on first version, after the ffmpeg.BeginOutputReadLine();, I included the following line:
ffmpeg.BeginErrorReadLine();.
Now, I can monitor the progress using the stderr of ffmpeg.
Final code:
Process ffmpeg = new Process
{
StartInfo = {
FileName = #"d:\tmp\videos\ffmpeg.exe",
Arguments = "-f concat -safe 0 -i __sync.txt -c copy output.mp4",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WorkingDirectory = #"d:\tmp\videos\gopro"
}
};
ffmpeg.EnableRaisingEvents = true;
ffmpeg.OutputDataReceived += (s, e) => Debug.WriteLine(e.Data);
ffmpeg.ErrorDataReceived += (s, e) => Debug.WriteLine($#"Error: {e.Data}");
ffmpeg.Start();
ffmpeg.BeginOutputReadLine();
ffmpeg.BeginErrorReadLine();
ffmpeg.WaitForExit();

I have found this post few weeks ago when I was looking for answer for my problem.
I tried to start ffmpeg process and pass arguments to it but it take sooo long to do everything. At this point I use Xabe.FFmpeg as it doing this out of the box and don't have to worry about ffmpeg executables because have feature to download latest version.
bool conversionResult = await new Conversion().SetInput(Resources.MkvWithAudio)
.AddParameter(String.Format("-f image2pipe -i pipe:.bmp -maxrate {0}k -r {1} -an -y {2}",bitrate, fps, outputfilename))
.Start();
There is documentation available here that shows how to get current percent of conversion. At this moment I thing showing output for concatenating videos doesn't work but they are working on it.

Related

Console output gets lost upon starting application as child process in C#

I'm having issues with a process started in C# not outputting to console despite it's output being redirected.
I'm running a console application inside a unityci docker container which allows me to start unity in batch mode and is supposed to output something to console.
If I use bash to start unity using unity-editor -projectPath myProject -executeMethod myMethod -logFile - I get all the output displayed in console as expected.
If I use C# to start a bash process using the same arguments, I get no output.
Here's the code I'm using to start a new process:
void StartProcess()
{
string argsString = "-projectPath myPath -executeMethod myMethod -logFile -";
ProcessStartInfo startInfo = new ProcessStartInfo(argsString)
{
FileName = "/bin/bash",
Arguments = $"-c unity-editor \"{argsString}\"",
RedirectStandardOutput = true,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true,
};
using Process proc = Process.Start(startInfo);
OutputDataReceived += OnOutputDataReceived;
proc.BeginOutputReadLine();
proc.WaitForExit();
}
void OnOutputDataReceived(object obj, DataReceivedEventArgs args)
{
if(args.Data.StartsWith("[")
Console.WriteLine(args.Data);
}
The unity-editor command is part of the unityci docker container I'm using. It does the following:
#!/bin/bash
if [ -d /usr/bin/unity-editor.d ] ; then
for i in /usr/bin/unity-editor.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
fi
xvfb-run -ae /dev/stdout "$UNITY_PATH/Editor/Unity" -batchmode "$#"
So in the end I have a console application that runs an executable that starts another application, and the console output gets lost somewhere in between.
Can someone explain where the output is going and how to get it to display in console?
I figured it out. The problem was how I was starting the process.
Instead of starting a instance of bash to run a script like this:
ProcessStartInfo startInfo = new ProcessStartInfo("/bin/bash", $"-c unity-editor \"{argsString}\"");
I should have just started the script as a process directly:
ProcessStartInfo startInfo = new ProcessStartInfo("unity-editor", argsString);
I was under the impression that you can't start bash scripts as processes directly as that would throw a System.ComponentModel.Win32Exception: Exec format error exception, but having #!/bin/bash as the first line of the script allows them to be run without issue.

How to write a command to cmd.exe when using ProcessStartInfo

I have some script files that would usually be edited and run through a ui, but the offer to run on the command line when using the syntax: program.exe scriptfile.fmw --userparameter "some parameter".
If I simply write
arguments = "program.exe scriptfile.fmw --userparameter \"some parameter\"";
Process process = Process.Start("cmd.exe", $"/K {arguments};
process.WaitForExit();
the commandline starts, calls the correct exe and runs the script.
Now I want to retrieve the response of the script after it ran to catch possible error messages.
As far as I can see this requires to use ProcessStartInfo and that's basically where my problem seems to start.
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = "cmd.exe",
Arguments = $"/K {arguments}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
Process process = Process.Start(startInfo);
process.BeginErrorReadLine();
process.WaitForExit();
The code above opens a commandline window, but never uses the arguments given.
It escapes me on how I should hand the argument line over to the cmd.exe - process.
I was playing around with RedirectStandardInput and its StreamWriter, but never managed to get anything written into the cmd-window.
The same goes for the RedirectStandardOutput, where I would like to gather the script response in the cmd-window as a string or string[], to parse for specific exit codes of the script.
I think this is what you want, have a look below :
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = "program.exe", //You must use your program directly without invoking through cmd.exe
Arguments = "scriptfile.fmw --userparameter \"some parameter\"", //Add parameters and arguments here needed by your application
UseShellExecute = false,
EnableRaisingEvents = true, //You are missing this
RedirectStandardOutput = true,
RedirectStandardError = true
};
process.ErrorDataReceived += process_ErrorDataReceived; //You should listen to its output error data by subscribing to this event
process.BeginErrorReadLine();
process.Start(startInfo);
process.WaitForExit(); // You may now avoid this
Then at here do anything with your received error data!
private static void process_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
DoSomething(e.Data); // Handle your error data here
}
EDIT-(1) : Please try this solution and ping me if it works or if you need some extra help. Everyone in comments is suggesting that you must not use cmd.exe to invoke your program as it may causes debugging overhead, performance issue and you might not get error details as well.
Cheers!

Is standard output of a process flushed if the process crashes?

I'm starting a Python process using the C# Process class. The standard output is redirected as I want to capture it. I'm able to get the standard output properly except when it crashes. We are aware that it will crash. We need to inspect the standard output when it crashes. We are not getting exact stdout once crashed.
When I run that script in the console manually, I can see the exact output at the time of the crash, but when I redirect the stdout using the > operator, the output is not written to the file, if the process crashed. I suspect that the I/O buffers are not getting flushed, if the process crashes. Is this true? Thanks in advance.
var lastLineOfStdOut = null;
var p = new Process
{
StartInfo =
{
FileName = ... ,
Arguments = ... ,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
p.OutputDataReceived += (sender, e) =>
{
if (e.Data == null) return;
lastLineOfStdOut = e.Data;
Console.WriteLine(e.Data);
};
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
var exitCode = p.ExitCode; // Use this to figure out if it crashed

Start process with IO stream redirection

How to start process and run command like this:
mysql -u root --password="some-password" < "some-file.sql"
Is it possible to do with process.Start()?
I need cross-platform solution (we cannot use cmd.exe).
Yes, this is possible through the System.Diagnostics.Process class. You need to set RedirectStandardInput to true, after which you can write the content of a file redirect the standard input of a process, and write the contents of the file to the Process.StandardInput (which is a StreamWriter)
This should get you started:
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "mysql.exe", // assumes mysql.exe is in PATH
Arguments = "-u root --password=\"some-password\"",
RedirectStandardInput = true,
UseShellExecute = false
},
};
process.Start();
process.StandardInput.Write(File.ReadAllText("some-file.sql"));
Update: this is pretty well documented [here](
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.redirectstandardinput)

Calling WSL bash.exe from C#

Mostly just as a curiosity, I wrote a little app to start up Terminator shell on Windows, using Ubuntu/WSL and Xming window server.
Doing things manually from the shell, I can run Firefox, gedit, Terminator, etc on Windows, it's pretty cool.
So I checked the location of bash.exe using where bash and it returned...
C:\Windows\System32\bash.exe
However when I tried to run this code...
using (var xminProc = new Process())
{
xminProc.StartInfo.FileName = #"C:\Program Files (x86)\Xming\Xming.exe";
xminProc.StartInfo.Arguments = ":0 -clipboard -multiwindow";
xminProc.StartInfo.CreateNoWindow = true;
xminProc.Start();
}
using (var bashProc = new Process())
{
bashProc.StartInfo.FileName = #"C:\Windows\System32\bash.exe";
bashProc.StartInfo.Arguments = "-c \"export DISPLAY=:0; terminator; \"";
bashProc.StartInfo.CreateNoWindow = true;
bashProc.Start();
}
I get the error...
System.ComponentModel.Win32Exception: 'The system cannot find the file specified'
And checking my entire system for bash.exe reveals it really be in another place altogether...
I'm not sure if this location is one that I can rely on, I'm worried it's ephemeral and can change during a Windows Store update, although I may be wrong about that.
Why does the command prompt show bash.exe to be in System32 but it's really in another location altogether?
Can I get C# to also use the System32 location?
As #Biswapriyo stated first set the platafrom to x64 on your solution:
Then you may run on your ubuntu machine from c# as:
Console.WriteLine("Enter command to execute on your Ubuntu GNU/Linux");
var commandToExecute = Console.ReadLine();
// if command is null use 'ifconfig' for demo purposes
if (string.IsNullOrWhiteSpace(commandToExecute))
{
commandToExecute = "ifconfig";
}
// Execute wsl command:
using (var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = #"cmd.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
CreateNoWindow = true,
}
})
{
proc.Start();
proc.StandardInput.WriteLine("wsl " + commandToExecute);
System.Threading.Thread.Sleep(500); // give some time for command to execute
proc.StandardInput.Flush();
proc.StandardInput.Close();
proc.WaitForExit(5000); // wait up to 5 seconds for command to execute
Console.WriteLine(proc.StandardOutput.ReadToEnd());
Console.ReadLine();
}

Categories