-
-
Save SlowLogicBoy/63037d655cf8c667ae09ccebcf1164aa to your computer and use it in GitHub Desktop.
Example on how to join audio and video streams from Youtube using FFmpeg and YoutubeExplode with pipelines.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Run using dotnet script (https://github.com/filipw/dotnet-script) | |
#r "nuget: YoutubeExplode, *" | |
using System.Diagnostics; | |
using System.IO.Pipes; | |
using YoutubeExplode; | |
using YoutubeExplode.Models; | |
using YoutubeExplode.Models.MediaStreams; | |
var videoId = "c8vbgNLDNDM"; | |
var outputDir = @".\out"; | |
const string FFmpegPath = @".\ffmpeg.exe"; | |
var youtubeClient = new YoutubeClient(); | |
var youtubeStreamInfo = await youtubeClient.GetVideoMediaStreamInfosAsync(videoId); | |
var videoStreamInfo = youtubeStreamInfo.Video.WithHighestVideoQuality(); | |
var audioStreamInfo = youtubeStreamInfo.Audio.WithHighestBitrate(); | |
var outputFileName = $"{videoId}.mp4"; | |
var outputFilePath = Path.Combine(outputDir, outputFileName); | |
//Creates a pipeline stream and downloads media stream to it. | |
//Thanks to https://mathewsachin.github.io/blog/2017/07/28/ffmpeg-pipe-csharp.html | |
async Task DownloadToPipeStreamAsync(string pipeName, MediaStreamInfo mediaStreamInfo) | |
{ | |
using (var pipe = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) | |
{ | |
await pipe.WaitForConnectionAsync(); //We need to wait until someone connects to our pipeline | |
await youtubeClient.DownloadMediaStreamAsync(mediaStreamInfo, pipe); //Download media stream to pipeline | |
} | |
} | |
async Task RunFFMpegAsync() | |
{ | |
const string videoPipeName = "ffvideo"; | |
const string audioPipeName = "ffaudio"; | |
//-movflags frag_keyframe+empty_moov = https://stackoverflow.com/questions/34123272/ffmpeg-transmux-mpegts-to-mp4-gives-error-muxer-does-not-support-non-seekable#34133803 | |
//-f mp4 = we need to specify which format we want out. | |
//-i \\.\pipe\pipeName = \\.\pipe\ = we want input to go from pipeline | |
var processStartInfo = new ProcessStartInfo | |
{ | |
FileName = Path.GetFullPath(FFmpegPath), | |
Arguments = $@"-i \\.\pipe\{videoPipeName} -i \\.\pipe\{audioPipeName} -c:v copy -c:a aac -movflags frag_keyframe+empty_moov -f mp4 -", | |
UseShellExecute = false, | |
RedirectStandardOutput = true, | |
CreateNoWindow = true | |
}; | |
using (var process = Process.Start(processStartInfo)) | |
{ | |
using (var fileStream = new FileStream(outputFilePath, FileMode.CreateNew)) | |
{ | |
//I don't await tasks directly because they all need to be parallel. Otherwise = deadlock | |
var audioDownloadTask = DownloadToPipeStreamAsync(audioPipeName, audioStreamInfo); | |
var videoDownloadTask = DownloadToPipeStreamAsync(videoPipeName, videoStreamInfo); | |
var outputTask = process.StandardOutput.BaseStream.CopyToAsync(fileStream); | |
//Wait for all tasks to finnish | |
await Task.WhenAll(audioDownloadTask, videoDownloadTask, outputTask); | |
process.StandardOutput.Close(); | |
} | |
process.WaitForExit(); | |
} | |
} | |
await RunFFMpegAsync(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Version 2022