How to stream youtube audio with ffmpeg discord bot - c#

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;
}

Related

FFmpeg send multiple inputs through a stream

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

FFmpeg. Reading and writing multiple files from a stream

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.

How do I redirect the output of SpeechSynthesizer to a Process of ffmpeg

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.

Read a byte array from a redirected process

I am using the process object in c#
I am also using FFMPEG.
I am trying to read the bytes from the redirected output. i know the data is an image but when I use the following code I do not get an image byte array.
this is my code:
var process = new Process();
process.StartInfo.FileName = #"C:\bin\ffmpeg.exe";
process.StartInfo.Arguments = #" -i rtsp://admin:admin#192.168.0.8:554/video_1 -an -f image2 -s 360x240 -vframes 1 -";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.Start();
var output = process.StandardOutput.ReadToEnd();
byte[] bytes = Encoding.ASCII.GetBytes(output);
The 1st bytes are not the header of a jpeg?
I think treating the output as a text stream is not the right thing to do here. Something like this worked for me, just directly read the data off the output pipe, it doesn't need conversion.
var process = new Process();
process.StartInfo.FileName = #"C:\bin\ffmpeg.exe";
// take frame at 17 seconds
process.StartInfo.Arguments = #" -i c:\temp\input.mp4 -an -f image2 -vframes 1 -ss 00:00:17 pipe:1";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.Start();
FileStream baseStream = process.StandardOutput.BaseStream as FileStream;
byte[] imageBytes = null;
int lastRead = 0;
using (MemoryStream ms = new MemoryStream())
{
byte[] buffer = new byte[4096];
do
{
lastRead = baseStream.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, lastRead);
} while (lastRead > 0);
imageBytes = ms.ToArray();
}
using (FileStream s = new FileStream(#"c:\temp\singleFrame.jpeg", FileMode.Create))
{
s.Write(imageBytes, 0, imageBytes.Length);
}
Console.ReadKey();

Redirect binary data from Process.StandardOutput causes corrputed data

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();

Categories