I'd like to be able to use FFmpeg to convert a video file from within my C# program. I know I can just call a shell command, but is there a better way?
The issue with invoking a command via the shell, is I'm not sure you could do things like a progress bar, etc... or could you?
If there isn't a way, can anyone suggest the best way to layout some framework for executing shell commands. Passing one big long string is very cumbersome atm.
You can easily implement a progress bar if running ffmpeg. The output of ffmpeg while running is something like:
frame= 3366 fps=465 q=31.0 Lsize= 6474kB time=140.35 bitrate= 377.9kbits/s
And it is refreshed ~twice per second. You can parse that line and get the data you need to display the progress. When you run in the command line, you only see one line being updated all the time, but what ffmpeg does is to write the line followed by \r. That's why you don't see multiple lines. However, when using StreamReader.ReadLine() on the error output of the program, you get one line for every update.
Sample code to read the output follows. You would have to ignore any line that does not begins with 'frame', perhaps use BeginErrorReadLine()+ErrorDataReceived if you want reading lines to be asynchronous, etc., but you get the idea (I've actually tested it):
using System;
using System.Diagnostics;
using System.IO;
class Test {
static void Main (string [] args)
{
Process proc = new Process ();
proc.StartInfo.FileName = "ffmpeg";
proc.StartInfo.Arguments = "-i " + args [0] + " " + args [1];
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.UseShellExecute = false;
if (!proc.Start ()) {
Console.WriteLine ("Error starting");
return;
}
StreamReader reader = proc.StandardError;
string line;
while ((line = reader.ReadLine ()) != null) {
Console.WriteLine (line);
}
proc.Close ();
}
}
There's a wrapper library over FFmpeg for .NET.
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.
I just found fflib at sourceforge. Looks pretty promising, haven't used it though.
How about writing a C++/CLI wrapper around ffmpeg's native interface and then calling your wrapper interface from your application?
Related
I want to pass an image object from my c# project to my python script however from my understanding whatever there is in the arguments it is considered as string and also when I try type(passedImage) in python it identifies it as a string even if I try to put a number instead of the image variable.
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:\Python\Python36\python.exe";
start.Arguments = string.Format("{0} {1}", #"C:\OCRonImage2.py", image );
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
start.CreateNoWindow = true;
using (Process process = Process.Start(start))
{
}
When executing OCRonImage2.py manually, is it an image file location that you would pass as an argument? I would be surprise if you would pass in a stream from the command line. It is no surprise that attempting to put the entire image's bytes into an argument would create a string too long. But with the error you reported, I would also believe that the python script was expecting a file path to the image. However, if you look at that python code, I wouldn't be surprised if you find it using the filepath argument to open the file, probably using Image.open(filepath,mode=r). Mode is optional, r is the default.
You are in luck however, Image.open also takes a stream. If you are willing to modify the python code there are two options:
Try converting the argument to a stream object, since the argument is a string maybe use io.StringIO()
Use input() instead of the argument passed, then you could redirect the input of the process and stream the file into your python.
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:\Python\Python36\python.exe";
start.Arguments = string.Format("{0}", #"C:\OCRonImage2.py");
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
start.RedirectStandardInput = true;
start.CreateNoWindow = true;
using (Process process = Process.Start(start))
{
StreamWriter streamWriter = process.StandardInput;
streamWriter.Write({imageString});
// ...
}
Be sure the way you encode imageString the same as the decoding is performed in the python script.
Hopefully one of these solutions will work for you.
As I work with the Anaconda distribution of Python, in my tests on an isolated conda environment, the OCR is successful with pytesseract through a Python script, on a test image.
Prerequisites to test:
install Anaconda and create an env called py3.7.4: conda create --name py3.7.4
activate the env with conda activate py3.7.4
install pytesseract with conda install -c conda-forge pytesseract
create a folder called Test and place a jpg file called ocr.jpg with the following sample image:
in the same Test folder also place a Python script called ocr_test.py with the following code:
import pytesseract
from PIL import Image
import argparse
parser = argparse.ArgumentParser(
description='perform OCR on image')
parser.add_argument("--path", "-p", help="path for image")
args = parser.parse_args()
print(pytesseract.image_to_string(Image.open(args.path)))
print("done")
The above snippet accepts the image path as a command line argument. The --path flag must be specified in order to pass the image path as an arg.
Now, in the C# code snippet below, we will:
launch the cmd shell
navigate to the workingDirectory Test folder by specifying the WorkingDirectory arg for the process.start() method.
activate Anaconda with the anaconda.bat file(replace the file path as per its location on your computer)
activate the above conda environment
call the Python script passing the imageFileName as an arg.
C# snippet:
using System.Diagnostics;
using System.Threading;
namespace PyTest
{
class Program
{
static void Main(string[] args)
{
string workingDirectory = #"C:\Test";
string imageFileName = "ocr.JPG";
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
RedirectStandardInput = true,
UseShellExecute = false,
RedirectStandardOutput = false,
WorkingDirectory = workingDirectory
}
};
process.Start();
using (var sw = process.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
// Vital to activate Anaconda
sw.WriteLine(#"C:\Users\xxxxxxx\Anaconda3\Scripts\activate.bat");
Thread.Sleep(500);
// Activate your environment
sw.WriteLine("conda activate py3.7.4");
Thread.Sleep(500);
sw.WriteLine($"python ocr_test.py --path {imageFileName}");
Thread.Sleep(50000);
}
}
}
}
}
If you have followed the above steps, you should receive the following output on executing the C# snippet in Visual Studio:
Output:
Microsoft Windows [Version 10.0.18362.535]
(c) 2019 Microsoft Corporation. All rights reserved.
C:\xxxxxxx\Projects\Scripts>C:\Users\xxxxx\Anaconda3\Scripts\activate.bat
(base) C:\xxxxxx\Projects\Scripts>conda activate py3.7.4
(py3.7.4) C:\xxxxxxx\Projects\Scripts>python ocr_test.py --path ocr.JPG
Introduction
This is a test to see accuracy of Tesseract OCR
Test 1
Test 2
done
Note: I am unable to test with a standalone Python distro but I believe it should work just fine with that too. The key is to pass the image file path as an argument to the Python script too. That way, the image file path passed as argument from C# is treated similarly by Python too. Also, using Image.open() does the following(from the docs):
Opens and identifies the given image file. This is a lazy operation;
this function identifies the file, but the file remains open and the
actual image data is not read from the file until you try to process
the data
You can save the image as a file somewhere on your local machine and give the python program the path to read it.
That's the easiest way I think you can do.
Edited: You can use a temporary file to make sure the file can be deleted in the future
http://www.java2s.com/Tutorial/CSharp/0300__File-Directory-Stream/Createatempfileanddeleteit.htm
http://www.vcskicks.com/temporary-file.php
I think this will be bad to pass IMAGE as Argument.
Good options to go with:
Stdin on your python example, and RedirectStandardInput on your c#.
TCP Communication. Using TCP Services (No Internet Needed)
Sharing Memory. (More Info, ReadyLib)
I want to download multiple files say www.google.com, yahoo.com and gmail.com at 3 different locations using wget. How should i go about it? Please help me out..
I am doing all this through c#:
ProcessStartInfo startInfo = new ProcessStartInfo("CMD.exe");
Process p = new Process();
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
p = Process.Start(startInfo);
p.StandardInput.WriteLine(#"wget --output-document=C:\1.xml xyz.com/a.xml");
p.StandardInput.WriteLine(#"wget --output-document=C:\2.xml xyz.com/b.xml");
p.StandardInput.WriteLine(#"wget --output-document=C:\3.xml xyz.com/c.xml");
p.StandardInput.WriteLine(#"EXIT");
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
p.Close();
This is not working. would like to know if there r any othe ways of downloading multiple files using wget..
If you're just talking about retrieving each file from a different location, but still doing it sequentially, you just change the URI in the wget command to point to a different location.
If you want concurrent downloads rather than sequential, you would have to start three separate processes and have them download one file each. These ptocesses could run side by side but I'd probably only consider this for large files (of which an XML file is probably not).
If you're having troubles getting the commands to run at all, the first thing I would do is ditch cmd.exe and its standard input. There's no reason why you can't have a process run wget directly. Or, if you really only want to start the one process, you could output them to a temporary file and use a single process cmd /c tempfile.cmd to run it.
However, there may be a totally different problem you're having unrelated to what you've shown, because that exact code with three echo statements in place of your wget ones runs fine, generating the correct output, at least in Visual C# Express 2010.
And, in fact, once I got my GnuWin32 wget on to the path, the following worked as well, getting real documents off the net and placing them in my top-level directory:
using System;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe");
Process p = new Process();
startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
p = Process.Start(startInfo);
p.StandardInput.WriteLine(
#"wget --output-document=c:\q1.txt http://www.ibm.com");
p.StandardInput.WriteLine(
#"wget --output-document=c:\q2.txt http://www.microsoft.com");
p.StandardInput.WriteLine(
#"wget --output-document=c:\q3.txt http://www.borland.com");
p.StandardInput.WriteLine(#"exit");
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
p.Close();
}
}
}
Here's the proof, the single window partway through the Microsoft download:
So, bottom line, what you have shown us is not inherently unworkable as evidenced by the image above. My only suggestion is to start looking around at other things such as the version of wget you're using, GnuWin32 or CygWin.
Now, things get interesting with larger files, as you've stated in one of your comments. If I change all three URIs to http://download.microsoft.com/download/5/F/C/5FC4F80C-242D-423B-9A11-9510A013152D/Dolphins.themepack, a file of 12,889,103 bytes, the code above hangs at about 18% of the first download (around the 2.3M mark).
However, if I change the commands so that they have >nul: 2>nul: on the end, the download goes through without issue, so I suspect it's most likely an issue with the way wget writes its output (without newlines). It also works fully if you don't use redirection on the output and error streams, which strengthens that assertion.
Well, first of all, you're on Windows. wget is part of the GNU Operating System. Unless you've installed a "clone" of wget for Windows, this is impossible. You are probably better off downloading the pages yourself, with something like the HTTPClient class.
But if you have a form of wget installed, what is not working? And how do you want it to work? Your question is not very detailed, you just ask how to go about it, and provide a seemingly fine solution.
I looked around for a while and didn't find many promising articles on the question I have.
I am attempting to write a program that will query users for the path to a file using the openFileDialog and saving the output to a string in C#. What I want to do with it, is use said path in a command script that will copy the file to another part of the computer for use by the program that I am writing.
I am pretty new to C#, so the dummy version, if possible, would be appreciated. I so far understand that I have to set up a new process to run batch commands in general, but I never could find good examples of how to pass C# strings to the batch script.
Thanks
Why not just use .NET's built in Copy method? You can use a Backgroundworker or a Task to make this occur on a different thread, also.
If you must spin up a separate process, then you can use Process.Start with ProcessInfo set up to the path and pass the arguments in that way.
If your script is long, I would use a StringBuilder then write the string to a file:
// set srcFilename and xcopyOptions (like /Y, for example)
var sb = new StringBuilder();
sb.Append( "XCOPY " )
.Append( xcopyOptions )
.Append( " " )
.Append( srcFilename )
.Append( " " )
.AppendLine( dstDir );
// repeat for as many copy commands as you want
// ...
File.WriteAllText( scriptFilename, sb.ToString() );
In addition to already available post, if you really want to use a batch, to pass a variable to it is the same like a pass arguments to an executable.
instance process , where "ececutable" is a batch file
assign arguments, which will be passed like a parameters to the variables of the batch.
Process thisProcess = new Process();
thisProcess.StartInfo.CreateNoWindow = true; //NO NEED OF WINDOW
thisProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
thisProcess.StartInfo.WorkingDirectory = #"DirectoryOfTheBacth";
thisProcess.StartInfo.FileName = "BatchFileName";
thisProcess.StartInfo.Arguments = "parameters";
thisProcess.StartInfo.UseShellExecute = false; //NO SHELL EXECUTE
thisProcess.StartInfo.RedirectStandardOutput = true; //STDO REDIRECTION, SO WE CAN READ WHAT IS HAPPENNING
thisProcess.Start(); // FINALLY, START PROCESS
How to pass parameter from C# to a batch file?
I'm currently building a WPF application. I want to be able to choose a binary file, decode it using the command prompt command line arguments into a .csv file, edit its value in my application then decode it back to a binary file using a decoding tool.The only part where I'm stuck at is entering my commandline arguments into the command prompt. I googled stuff, but I could only find information on how to open the command prompt from code and not how to execute a command.
Any help would be greatly appreciated.
thanks!
checkout Process class, it is part of the .NET framework - for more information and some sample code see its documentation at MSDN.
EDIT - as per comment:
sample code that start 7zip and reads StdOut
using System;
using System.Diagnostics;
using System.IO;
class Program
{
static void Main()
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:\7za.exe"; // Specify exe name.
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
// Read in all the text from the process with the StreamReader.
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.Write(result);
}
}
}
}
some links to samples:
http://support.microsoft.com/kb/305369/en-us
c# ProcessStartInfo.Start - reading output but with a timeout
http://www.dotnetperls.com/process-start
http://weblogs.asp.net/guystarbuck/archive/2008/02/06/invoking-cmd-exe-from-net.aspx
http://www.jonasjohn.de/snippets/csharp/run-console-app-without-dos-box.htm
http://dfcowell.net/2011/01/printing-a-pdf-in-c-net-adobe-reader/
For a C# project I'm experimenting with ffmpeg to extract a .wav file from a video file (in Windows).
You could do this by running it on the command line like this: "ffmpeg -i inputvid.avi + 'extra parameters' + extracted.wav".
This obviously extracts the audio from the input .avi file to a specified .wav file.
Now, I can easily run this command in C# so it creates the desired .wav file. However, I don't need this wav file to stay on the harddisk. For performance reasons it would be much better if ffmpeg could save this file temporarily to the memory, which can than be used in my C# program. After execution of my program the .wav file is no longer needed.
So the actual question is: can I redirect the outputfile from a program to the memory?
And if this is possible, how can I read this in C#?
I know it's a long shot and I doubt it very much if it's possible, but I can always ask...
Thanks in advance.
Instead of specifying the output filename on the ffmpeg command line, use '-'. The '-' tells ffmpeg to send the output to stdout. Note that you might then have to manually specify your output format in the command line because ffmpeg can no longer derive it from the filename (the '-f' switch might be what you need for this).
Once you have that command line, refer to any number of places for help in reading stdout into your C# program.
Awesome, sounds so easy!
I still have a problem though, I use the following code now:
System.Diagnostics.ProcessStartInfo psi =
new System.Diagnostics.ProcessStartInfo(#"ffmpeg.exe");
psi.Arguments = #"-i movie.avi -vn -acodec pcm_s16le -ar 44100 -ac 1 -f wav -";
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
System.IO.StreamReader myOutput = proc.StandardOutput;
proc.WaitForExit();
string output;
if (proc.HasExited)
{
output = myOutput.ReadToEnd();
}
MessageBox.Show("Done!");
When I execute this now, the black cmd box just pops up and keeps blinking at me. When I open task manager it shows that it isn't doing anything.
If, however, I set the RedirectStandardOutput property to false, it shows the proper output in the cmd screen.
To cut it short: ffmpeg does redirect it's output now, but I can't read it with my C# code.