The problem is the following. I need to convert a video to a set of pictures using the ffmpeg process. I have successfully done this before with the following code:
public void VideoToImages1()
{
var inputFile = #"D:\testVideo.avi";
var outputFilesPattern = #"D:\image%03d.jpg";
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
UseShellExecute = false,
CreateNoWindow = true,
Arguments = $"-y -i {inputFile} {outputFilesPattern}",
FileName = "ffmpeg.exe"
},
EnableRaisingEvents = true
};
process.Start();
process.WaitForExit();
}
Now I need to stream video through the input Stream and receive data from the output Stream. For this I have the following code. It's fully working since I used it to convert video and successfully streamed input via Stream and received output via Stream and produced a valid file.
public void VideoToImages2()
{
var inputFile = #"D:\testVideo.avi";
var outputFile = #"D:\resultImages.png";
var process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
Arguments = "-y -i - -f image2 -",
FileName = _ffmpeg
},
EnableRaisingEvents = true
};
process.Start();
//Write input data to input stream
var inputTask = Task.Run(() =>
{
using (var input = new FileStream(inputFile, FileMode.Open))
{
input.CopyTo(process.StandardInput.BaseStream);
process.StandardInput.Close();
}
});
//Read multiple files from output stream
var outputTask = Task.Run(() =>
{
//Problem here
using (var output = new FileStream(outputFile, FileMode.Create))
process.StandardOutput.BaseStream.CopyTo(output);
});
Task.WaitAll(inputTask, outputTask);
process.WaitForExit();
}
The problem here is that instead of creating files in a directory according to the specified pattern, it returns these files in a stream. As a result, I do not know how to write all the files from the Stream and how to process this output, since it contains many files. At the moment I have only 1 image created.
Help me please.
I tried googling but didn't find anything useful
You will have to decode the stream on the fly using the signatures of the output files. Any file has a unique signature and by recognizing these signatures you can keep track of the number of bytes related to this file. For example jpeg file starts with signature 0xFF 0xD8 0xFF and ends with signature 0xF9 xF8 if I'm not mistaken. Matching bytes with these signatures will have to find the files contained in the stream.
Related
I have the following code that passes data to the ffmpeg process through a thread.
public void VideoToImages3()
{
var inputFile = #"C:\testvideo.avi";
var outputFile = #"C:\outputFile.mp4";
var process = new Process
{
StartInfo = new ProcessStartInfo
{
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true,
Arguments = $"-y -i - {outputFile}",
FileName = _ffmpeg
},
EnableRaisingEvents = true
};
process.Start();
//Write input data to input stream
var inputTask = Task.Run(() =>
{
using (var input = new FileStream(inputFile, FileMode.Open))
{
input.CopyTo(process.StandardInput.BaseStream);
}
});
Task.WaitAll(inputTask);
process.WaitForExit();
}
In this case, I only upload 1 file through the stream (-i -). What if I need to stream multiple input files (-i - -i -). For example, when adding an Audio File to a Video File?
"ffmpeg -y -i {audioFilePath} -i {videoFilePath} {outputFilePath}"
How to transfer files via StandardInput if 2 input arguments are specified???
I can't find a solution
so at the moment I got my disord bot to play audio from a filepath with this code
var transmit = vnc.GetTransmitSink();
var pcm = ConvertAudioToPcm(filepath);
await pcm.CopyToAsync(transmit);
Console.WriteLine(duration);
}
private Stream ConvertAudioToPcm(string filePath)
{
var ffmpeg = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $#"-i ""{filePath}"" -ac 2 -f s16le -ar 48000 pipe:1",
RedirectStandardOutput = true,
UseShellExecute = false
});
return ffmpeg.StandardOutput.BaseStream;
}
With YoutubeExplode I am able to get a stream from the URL but when I CopyToAsync I get very loud static on the discord bot.
Does anybody have an idea on how to stream audio properly from a youtube URL using ffmpeg?
Thank you in advance
EDIT:
Got it to work with this code to create PCM stream, but it doesn't use youtube explode, it uses youtube-dl.exe
private Stream ConvertURLToPcm(string url)
{
string args = $"/C youtube-dl --ignore-errors -o - {url} | ffmpeg -err_detect ignore_err -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1";
var ffmpeg = Process.Start(new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = args,
RedirectStandardOutput = true,
UseShellExecute = false
});
return ffmpeg.StandardOutput.BaseStream;
}
I am trying to have a SpeechSynthesizer generate some audio data, pipe it into a Process of FFmpeg, and have FFmpeg save the data to a file (output.wav). Eventually, the audio data will be used for something else, which is why I am using FFmpeg.
using (MemoryStream voiceStream = new MemoryStream())
using (Process ffmpeg = new Process())
{
SpeechSynthesizer synth = new SpeechSynthesizer();
int samplesPerSecond = 48000;
int bitsPerSample = 8;
int channelCount = 2;
int averageBytesPerSecond = samplesPerSecond * (bitsPerSample / 8) * channelCount;
int blockalign = (bitsPerSample / 8) * channelCount;
byte[] formatSpecificData = new byte[0];
synth.SetOutputToAudioStream(
voiceStream,
new System.Speech.AudioFormat.SpeechAudioFormatInfo(
System.Speech.AudioFormat.EncodingFormat.Pcm,
samplesPerSecond,
bitsPerSample,
channelCount,
averageBytesPerSecond,
blockalign,
formatSpecificData
)
);
synth.Speak("Hello there");
synth.SetOutputToNull();
ffmpeg.StartInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"-y -f u8 -ac 2 -ar 48000 -i pipe:0 out.wav",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true
};
ffmpeg.Start();
using (Stream ffmpegIn = ffmpeg.StandardInput.BaseStream)
{
voiceStream.CopyTo(ffmpegIn);
ffmpegIn.FlushAsync();
}
}
When running the program, FFmpeg said that the input stream contains no data, and returns an empty file.
I believe that I do not interface properly with the Process object, however, the problem might also be my incorrect specification of the audio stream, since I do not know much about audio.
Im pretty fresh when it comes to streams in c# but i am somewhat familiar with the basics.
I need help setting up the most efficent way of hooking into a stream of unknown length and send the part read to another function until the end of the stream is reached. Could someone have a look at what i hava and help me fill out the part in the while loop, or maybe if while loop is not the best way tell my what is better. Any help is much appreciated.
var processStartInfo = new ProcessStartInfo
{
FileName = "program.exe",
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
Arguments = " -some -arguments"
};
theProcess.StartInfo = processStartInfo;
theProcess.Start();
while (!theProcess.HasExited)
{
int count = 0;
var b = new byte[32768]; // 32k
while ((count = theProcess.StandardOutput.BaseStream.Read(b, 0, b.Length)) > 0)
{
SendChunck() // ?
}
}
You know how many bytes have been read from the original stream through the count variable, so you can just put them into a buffer
while ((count = theProcess.StandardOutput.BaseStream.Read(b, 0, b.Length)) > 0)
{
byte[] actual = b.Take(count).ToArray();
SendChunck(actual);
}
or if your SendChunk method is designed to take a Stream as parameter you can directly pass it the original object:
SendChunck(theProcess.StandardOutput.BaseStream);
and then the method can take care of reading the data in chunks.
On top of this problem, I have another. I try to get binary data from a external process, but the data(a image) seems to be corrupted. The screenshot below shows the corruption: The left image was done by executing the program on command line, the right one from code.
My Code so far:
var process = new Process
{
StartInfo =
{
Arguments = string.Format(#"-display"),
FileName = configuration.PathToExternalSift,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
},
EnableRaisingEvents = true
};
process.ErrorDataReceived += (ProcessErrorDataReceived);
process.Start();
process.BeginErrorReadLine();
//Reads in pbm file.
using (var streamReader = new StreamReader(configuration.Source))
{
process.StandardInput.Write(streamReader.ReadToEnd());
process.StandardInput.Flush();
process.StandardInput.Close();
}
//redirect output to file.
using (var fileStream = new FileStream(configuration.Destination, FileMode.OpenOrCreate))
{
process.StandardOutput.BaseStream.CopyTo(fileStream);
}
process.WaitForExit();
Is this some kind of encoding problem? I used the Stream.CopyTo-Approach as mentioned here in order to avoid there problems.
I found the problem. The redirection of the output was correct, the reading of the input seems to be the problem. So I changed the code from:
using (var streamReader = new StreamReader(configuration.Source))
{
process.StandardInput.Write(streamReader.ReadToEnd());
process.StandardInput.Flush();
process.StandardInput.Close();
}
to
using (var fileStream = new StreamReader(configuration.Source))
{
fileStream.BaseStream.CopyTo(process.StandardInput.BaseStream);
process.StandardInput.Close();
}
Not it works!
For all the people that might have the same problem, here the corrected code:
var process = new Process
{
StartInfo =
{
Arguments = string.Format(#"-display"),
FileName = configuration.PathToExternalSift,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
},
EnableRaisingEvents = true
};
process.ErrorDataReceived += (ProcessErrorDataReceived);
process.Start();
process.BeginErrorReadLine();
//read in the file.
using (var fileStream = new StreamReader(configuration.Source))
{
fileStream.BaseStream.CopyTo(process.StandardInput.BaseStream);
process.StandardInput.Close();
}
//redirect output to file.
using (var fileStream = new FileStream(configuration.Destination, FileMode.OpenOrCreate))
{
process.StandardOutput.BaseStream.CopyTo(fileStream);
}
process.WaitForExit();