I am trying to get a poster frame from a video file, using ffmpeg.
I have been following this tutorial and come up with the following code(which is taken/adapted from the link I gave):
public bool GetVideoThumbnail(string path, string saveThumbnailTo, int seconds)
{
string parameters = string.Format("-i {0} {1} -vcodec mjpeg -ss {2} -vframes 1 -an -f rawvideo", path, saveThumbnailTo, seconds);
if (File.Exists(saveThumbnailTo))
{
return true;
}
else
{
using (Process process = Process.Start(pathToConvertor, parameters))
{
process.WaitForExit();
}
return File.Exists(saveThumbnailTo);
}
}
At the moment this code is successfully creating a file in the correct destination (saveThumbnailTo) only the picture is completely black. I have tried changing the seconds value in the code to ensure that I am not just getting a blank picture from the start of the video. The path refers to where my video is stored, by the way.
I am currently calling the above code like so:
GetVideoThumbnail(videoPath, folderPath + "/poster.jpg", 100)
..and then passing it out to my view to display the picture. I just wonder whether ".jpg" is the extension I should be giving to this file as I am not entirely sure?
Edit: When I run the same command from the command line I get the following errors:
Incompatible pixel format 'yuv420p' for codec 'mjpeg', auto-selecting
format 'yuvj420p'
which appears in yellow, and
[image2 # 02S96AE0] Could not get frame filename number 2 from pattern
'poster.jpg' an_interleaved_write_frame(): Invalid argument
which appears in red.
Could anyone help me with getting this working properly as I am completely unfamiliar with the ffmpeg command line and not sure what I am doing wrong. I have tried removing the vcodec parameter and get the same error message.
Try this:
public bool GetVideoThumbnail(string path, string saveThumbnailTo, int seconds)
{
string parameters = string.Format("-ss {0} -i {1} -f image2 -vframes 1 -y {2}", seconds, path, saveThumbnailTo);
var processInfo = new ProcessStartInfo();
processInfo.FileName = pathToConvertor;
processInfo.Arguments = parameters;
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
File.Delete(saveThumbnailTo);
using(var process = new Process())
{
process.StartInfo = processInfo;
process.Start();
process.WaitForExit();
}
return File.Exists(saveThumbnailTo);
}
Short explanation:
f image2 : output is image
vframes 1 : take one frame from the input
y : overwrite output file
"processInfo.CreateNoWindow = true" : do not show the ffmpeg window
Try several times with different values for the "seconds" parameter.
Also, make sure the "pathToConvertor" is correct.
This worked for me, with recent build of ffmpeg.exe on a Windows machine.
Let me know how it goes.
Related
I am working with ffmpeg via C#'s Process class.
I have a script that runs ffmpeg to generate thumbnails from video. Initially it was called manually from command line - .\ffmpeg.exe -i .\input.mp4 -ss 00:00:01.000 -vframes:v 1 output.png, it starts ffmpeg instance, outputs some warnings/errors during the execution:
[image2 # 000001e51095ec80] The specified filename 'output.png' does not contain an image sequence pattern or a pattern is invalid.
[image2 # 000001e51095ec80] Use a pattern such as %03d for an image sequence or use the -update option (with -frames:v 1 if needed) to write a single image.
frame= 1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.00 bitrate=N/A speed= 0x
video:73kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
but anyway exits the process and correctly generates thumbnail image at output.png.
I want to execute it from C#.
Let's see the code:
var ffmpegProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = _config.FfmpegExecutablePath,
Arguments = CreateArgumentsForFfmpegProcessToRun(videoTempFilePath, thumbnailTempFilePath),
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
},
EnableRaisingEvents = true
};
ffmpegProcess.Start();
await ffmpegProcess.WaitForExitAsync();
Method CreateArgumentsForFfmpegProcessToRun returns exactly the same arguments as in the script above -i .\input.mp4 -ss 00:00:01.000 -vframes:v 1 output.png.
However, when I run this code, it stucks/blocked at line ffmpegProcess.WaitForExitAsync() infinitely and no output written to output path.
If I omit WaitForExitAsync call and just go to the next line, then it doesn't stuck and writes the output as expected and finish the process with -1 exit code.
I am trying to figure out why block/stuck happens and what is the best way to resolve this situation? As far I know, WaitForExitAsync should return as process ends, no matter how process ends - 0 or another exit code, am I right?
Update #1:
Community advised to search if somewhere up the stack I am blocking my code. I wrote xunit-test and it still stucks.
[Theory]
[InlineData("assets/input.mp4", "assets/ffmpeg.exe")]
public async Task CreateThumbnailFromVideo(string videoFilePath, string ffmpegExePath)
{
var config = new VideoThumbnailServiceConfig
{
FfmpegExecutablePath = ffmpegExePath,
ThumbnailImageExtension = ".png"
};
var sut = new VideoThumbnailService(config);
using var fileStream = File.OpenRead(videoFilePath);
await sut.CreateThumbnailFromVideo(fileStream);
}
Inside sut.CreateThumbnailFromVideom I call process start method and awaits WaitForExitAsync().
How do I execute and return the results of a python script in c#?
I am trying to run a python script from my controller.
I have python.exe setup in a virtual environment folder created with the virtualenv command.
So just for testing purposes at the moment I would like to just return resulting string from my phython script:
# myscript.py
print "test"
And display that in a view in my asp.net mvc app.
I got the run_cmd function from a related stackoverflow question.
I've tried adding the -i option to force interactive mode and calling process.WaitForExit() with no luck.
namespace NpApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
ViewBag.textResult = run_cmd("-i C:/path/to/virtualenv/myscript.py", "Some Input");
return View();
}
private string run_cmd(string cmd, string args)
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = #"C:/path/to/virtualenv/Scripts/python.exe";
start.CreateNoWindow = true;
start.Arguments = string.Format("{0} {1}", cmd, args);
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
//Console.Write(result);
process.WaitForExit();
return result;
}
}
}
}
}
It seems like myscript.py never even runs. But I get no errors, just a blank variable in my view.
Edit:
I had tried to simplify the above stuff because I thought it would be easier to explain and get an answer. Eventually I do need to use a package called "nameparser" and store the result of passed name argument into a database. But if I can just get the run_cmd to return a string I think I can take care of the rest of it. This is why I think the rest api and IronPython mentioned in the comments may not work for me here.
Ok, I figured out what the issue was thanks to some leads from the comments. Mainly it was the spaces in the path to the python.exe and the myscript.py. Turns out I didn't need -i or process.WaitForExit(). I just moved the python virtual environment into a path without spaces and everything started working. Also made sure that the myscript.py file was executable.
This was really helpful:
string stderr = process.StandardError.ReadToEnd();
string stdout = process.StandardOutput.ReadToEnd();
Debug.WriteLine("STDERR: " + stderr);
Debug.WriteLine("STDOUT: " + stdout);
That shows the python errors and output in the Output pane in Visual Studio.
I'm attempting to build a java process to execute the docusign retrieve product via the command line. I've written the process to execute based on a given property file.
buildRoot = isWindowsOs() ? "C:" + "\\Program Files (x86)\\DocuSign, Inc\\Retrieve 3.2" : "\\Program Files (x86)\\DocuSign, Inc\\Retrieve 3.2" ;
String[] command = new String [2];
command[0] = "\""+buildRoot+ "\\" + docuSignAppName+"\"";
logger.info(command[0].toString());
//ADDED FOR EXPLANATION - "C:\Program Files (x86)\DocuSign, Inc\Retrieve 3.2\DocuSignRetrieve.exe"
command[1] = arguments;
logger.info(command[1].toString());
ProcessBuilder processBuilder = new ProcessBuilder(command);
logger.info("ProcessBuilder starting directory" +processBuilder.directory());
processBuilder.redirectErrorStream(true);
p = processBuilder.start();
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
stdout = new BufferedReader(isr);
Once I pass in the built out string of parameters the executed code looks like the sample provided but always results in the error back to the screen "Missing "accountid" parameter".
The parameter list looks like the following.
/endpoint "Demo"
/userid "REMOVED"
/password "REMOVED"
/accountid "REMOVED"
/span "-1"
/spanfilter "Completed"
/statusfilter "Completed"
/fieldlog "LIST OF FIELDS"
/nstyle "EnvelopeID"
/save "MergedPdfWithoutCert"
/dir "D:\DocuSignStore"
/includeheaders "true"
Any help or assistance would be appreciated.
The solution was found in a StackOverflow discussion regarding common issues with the ProcessBuilder.
My problem was that I expected by changing the putting in the full path, that I could run the executable. For the reason I'm not sure right now, that wasn't working as expected. The solution was to run the CMD command which exists on the %PATH% on any windows OS.
String[] command = new String [2];
command[0] = "\""+buildRoot+ "\\" + docuSignAppName+"\"";
logger.info(command[0].toString());
//ADDED FOR EXPLANATION - "C:\Program Files (x86)\DocuSign, Inc\Retrieve 3.2\DocuSignRetrieve.exe"
command[1] = arguments;
logger.info(command[1].toString());
//This starts a new command prompt
ProcessBuilder processBuilder = new ProcessBuilder("cmd","/c","DocusignRetreive.exe);
//This sets the directory to run the command prompt from
File newLoc = new File("C:/Program Files (x86)/DocuSign, Inc/Retrieve 3.2");
processBuilder.directory(newLoc);
logger.info("ProcessBuilder starting directory" +processBuilder.directory());
processBuilder.redirectErrorStream(true);
/*When the process builder starts the prompt looks like
*C:\Program Files (x86)\DocuSign, Inc\Retrieve 3.2
*Now DocusignRetrieve.exe is an executable in the directory to be run
*/
p = processBuilder.start();
i want to launch ffmpeg from my app and retrive all console output that ffmpeg produces. Thing seems obvious, i followed many forum threads/articles like this one but i have problem, though i follow all information included there I seem to end up in dead end.
String that should contain output from ffmpeg is always empty. I've tried to see where is the problem so i made simple c# console application that only lists all execution parameters that are passed to ffmpeg, just to check if problem is caused by ffmpeg itself. In that case everything work as expected.
I also did preview console window of my app. When i launch ffmpeg i see all the output in console but the function that should recieve that output for further processing reports that string was empty. When my param-listing app is launched the only thing I see is the expected report from function that gets output.
So my question is what to do to get ffmpeg output as i intended at first place.
Thanks in advance
MTH
This is a long shot, but have you tried redirecting StandardError too?
Here is a part of my ffmpeg wrapper class, in particular showing how to collect the output and errors from ffmpeg.
I have put the Process in the GetVideoDuration() function just so you can see everything in the one place.
Setup:
My ffmpeg is on the desktop, ffPath is used to point to it.
namespace ChildTools.Tools
{
public class FFMpegWrapper
{
//path to ffmpeg (I HATE!!! MS special folders)
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
//outputLines receives each line of output, only if they are not zero length
List<string> outputLines = new List<string>();
//In GetVideoDuration I only want the one line of output and in text form.
//To get the whole output just remove the filter I use (my search for 'Duration') and either return the List<>
//Or joint the strings from List<> (you could have used StringBuilder, but I find a List<> handier.
public string GetVideoDuration(FileInfo fi)
{
outputLines.Clear();
//I only use the information flag in this function
string strCommand = string.Concat(" -i \"", fi.FullName, "\"");
//Point ffPath to my ffmpeg
string ffPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\ffmpeg.exe";
Process processFfmpeg = new Process();
processFfmpeg.StartInfo.Arguments = strCommand;
processFfmpeg.StartInfo.FileName = ffPath;
//I have to say that I struggled for a while with the order that I setup the process.
//But this order below I know to work
processFfmpeg.StartInfo.UseShellExecute = false;
processFfmpeg.StartInfo.RedirectStandardOutput = true;
processFfmpeg.StartInfo.RedirectStandardError = true;
processFfmpeg.StartInfo.CreateNoWindow = true;
processFfmpeg.ErrorDataReceived += processFfmpeg_OutData;
processFfmpeg.OutputDataReceived += processFfmpeg_OutData;
processFfmpeg.EnableRaisingEvents = true;
processFfmpeg.Start();
processFfmpeg.BeginOutputReadLine();
processFfmpeg.BeginErrorReadLine();
processFfmpeg.WaitForExit();
//I filter the lines because I only want 'Duration' this time
string oStr = "";
foreach (string str in outputLines)
{
if (str.Contains("Duration"))
{
oStr = str;
}
}
//return a single string with the duration line
return oStr;
}
private void processFfmpeg_OutData(object sender, DataReceivedEventArgs e)
{
//The data we want is in e.Data, you must be careful of null strings
string strMessage = e.Data;
if outputLines != null && strMessage != null && strMessage.Length > 0)
{
outputLines.Add(string.Concat( strMessage,"\n"));
//Try a Console output here to see all of the output. Particularly
//useful when you are examining the packets and working out timeframes
//Console.WriteLine(strMessage);
}
}
}
}
I want to extract first frame of uploaded video and save it as image file.
Possible video formats are mpeg, avi and wmv.
One more thing to consider is that we are creating an ASP.NET website.
You could use FFMPEG as a separate process (simplest way) and let it decode first IDR for you. Here you have a class FFMPEG that has GetThumbnail() method, to it you pass address of video file, address of the JPEG image to be made, and resolution that you want the image to be:
using System.Diagnostics;
using System.Threading;
public class FFMPEG
{
Process ffmpeg;
public void exec(string input, string output, string parametri)
{
ffmpeg = new Process();
ffmpeg.StartInfo.Arguments = " -i " + input+ (parametri != null? " "+parametri:"")+" "+output;
ffmpeg.StartInfo.FileName = "utils/ffmpeg.exe";
ffmpeg.StartInfo.UseShellExecute = false;
ffmpeg.StartInfo.RedirectStandardOutput = true;
ffmpeg.StartInfo.RedirectStandardError = true;
ffmpeg.StartInfo.CreateNoWindow = true;
ffmpeg.Start();
ffmpeg.WaitForExit();
ffmpeg.Close();
}
public void GetThumbnail(string video, string jpg, string velicina)
{
if (velicina == null) velicina = "640x480";
exec(video, jpg, "-s "+velicina);
}
}
Use like this:
FFMPEG f = new FFMPEG();
f.GetThumbnail("videos/myvid.wmv", "images/thumb.jpg", "1200x223");
For this to work, you must have ffmpeg.exe in folder /utils, or change the code to locate ffmpeg.exe.
There are other ways to use FFMPEG in .NET, like .NET wrappers, you could google for them. They basically do the same thing here, only better. So if FFMPEG gets your job done, I'd recomend to use .NET wrapper.
Try to make argument string format like:
ffmpeg.StartInfo.Arguments =" -i c:\MyPath\MyVideo -vframes 1 c:\MyOutputPath\MyImage%d.jpg"
Instead of
ffmpeg.StartInfo.Arguments = " -i " + input+ (parametri != null? " "+parametri:"")+" "+output;
in the answer code provided above.
I don't know what was the reason, but second mentioned argument line is not working on my machine whereas when I changed argument like the first command it works fine.
Probably the best tool for working with videos programatically is FFMpeg. It has support for many formats, even wmv. I suspect there's even a .net wrapper for it.