Descargar vídeos de YouTube mediante una sencilla clase

He estado trabajando en un DLNA media server y quería que los usuarios pudieran arrastrar y soltar un video de Youtube desde el navegador en un dispositivo multimedia para, por ejemplo, ver el vídeo en la pantalla del televisor. Pensé que sería sencillo y que no me acarrearía ningún problema. Solo sería enviar la URL del vídeo al dispositivo DLNA debido a que VLC-Player puede funcionar con sólo una URL de YouTube.

Bueno, resultó estaba equivocado, así que rápidamente me descargue un proyecto para descargar videos de YouTube sólo para encontrar que era lo que estaba mal. Al final descubrí que el JSON utilizado en las páginas de YouTube había cambiado. Otro problema era que el proyecto era bastante complejo y utilizaba DLL de terceros. Quiero que mi aplicación multimedia sea autónoma e independiente, de ahí el problema. Por lo tanto no me quedó otra que desarrollar este código que estoy compartiendo con todos vosotros que ayuda a resolver el problema.

El código funciona mediante la lectura de las cadenas Json de una página de Youtube y compila la información de los diferentes vídeos en una colección. Así que todo lo que tiene que hacer el desarrollador es llamar Download() en el video, después de decidir en primer lugar la calidad del vídeo a descargar y el formato del archivo.

El código fue escrito en C # con Visual Studio 2010 y debería funcionar en .NET 3.5 o superior.

Cómo utilizar el código

Llama a LoadVideos y decide qué vídeo quieres guardar en tu disco duro o streamealo a un dispositivo como puedes ver en el código de abajo.

//
Dictionary<int, VidoInfo> Vids=VidoInfo.LoadVideos("https://www.youtube.com/watch?v=9Ni6bHX7a78",null);
Vids[2].DownLoad(@"c:Temp");//Download the 700p quality mp4 video maybe!
//To stream to a DLNA device you would do something like
System.Net.Sockets.Socket SocClient = null;//You need to connect it first
Vids[3].StreamVideo(SocClient);
//

Todo el código necesario para llevar a cabo esto se muestra a continuación. Como te he comentado antes, no es necesario añadir ninguna DLL de terceros en el proyecto.

//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text.RegularExpressions;
namespace YouTube
{
    public class VidoInfo//Use Dictionary<int, VidoInfo> Vids=VidoInfo.LoadVideos("https://www.youtube.com/watch?v=9Ni6bHX7a78",null);
    {//This class will download videos from Youtube and calling "Vids[0].DownLoad(RootSavePath);" will then save the video
        public bool BestPick = false;
        public string VideoID = "";
        public long ContentLength = 0;
        public string Signature = "";
        public string Title = "Some Youtube vid";
        public string FPS = "0";
        public string BitRate = "0";
        public string Quality = "";
        public string Url = "";
        public string Size = "";
        public string FileType = "";
        public string Codecs = "";
        public int Itag =0;//133-137 are no good someone said but it looks like it's got to be below 100 to have any sound
        public string FileName
        {
            get
            {//Me is sure a regx would do a better job here
                return this.Title.Replace(" ", "_").Replace("-", "_").Replace("?", "").Replace("&", "").Replace(":", "_").Replace("|","_") + this.Quality + "_" + this.Itag  + "." + this.FileType;
            }
        }

        public override string ToString()
        {
            return this.Quality + " " + this.Itag + " " + this.FileType + " " + this.Title;
        }

        private static Dictionary<int, VidoInfo> LastVids = null;
        private static string LastVideoID = "";
        public static Dictionary<int, VidoInfo> LoadVideos(string url, string YoutubeHTML)
        {
            string VideoID = url.ChopOffBefore("v=");
            if (LastVideoID == VideoID) 
                return LastVids;//No point loading them again, takes too long
            Dictionary<int, VidoInfo> Vids = new Dictionary<int, VidoInfo>();
            string Title = "";
            if (YoutubeHTML == null || YoutubeHTML.Length == 0) YoutubeHTML = VidoInfo.GetWebPage(url);//Might already have got the HTML if we are using sockets to stream the data
            var Regex = new Regex(@"ytplayer.configs*=s*({.+?});", RegexOptions.Multiline);
            string PageJson = Regex.Match(YoutubeHTML).Result("$1").Replace("/", "/");
            int StartTitle = PageJson.IndexOf(""title":"");
            if (StartTitle > -1)
            {//We need to get the title before we create any VideoInfo's if we can get it
                try { int EndTitle = PageJson.IndexOf(""", StartTitle + 10); Title = PageJson.Substring(StartTitle + 9, EndTitle - StartTitle - 9); }
                catch { ;}
            }
            if (Title.Length == 0) Title = "Unknown";
            PageJson = HttpUtility.UrlDecode(PageJson);
            string[] Hrefs = PageJson.Replace("https://", "¬").Split('¬');
            foreach (string Href in Hrefs)
            {
                if (Href.IndexOf("googlevideo.") > -1 && Href.IndexOf("googlevideo.") -1)
                {
                    string Url = "https://" + Href.ChopOffAfter(",").ChopOffAfter(" ").Replace("u0026", "&").Replace("%2C", ",");
                    string[] Items = Url.Split('&');
                    VidoInfo V = new VidoInfo(Items, Title, VideoID);
                    if (V.Itag > 0 && V.Itag < 100 && V.FileType.Length > 0)
                    {//Movies with high itag values will download but they don't have any sound
                        V.GetFileSize();
                        if (V.ContentLength >0) Vids.Add(Vids.Count, V);
                    }
                }
            }

            long BestSize=0;
            VidoInfo BestVideo = null;
            foreach (VidoInfo V in Vids.Values)
            {//Here we pick out the bigest mp4 file we can find and remember 3gpp are faked as mp4 files
                if (V.FileType =="mp4" && V.ContentLength > BestSize)
                {if (BestVideo != null) BestVideo.BestPick = false;V.BestPick = true;BestSize = V.ContentLength;BestVideo = V;}
            }
            if (BestVideo == null)
            {//No we don't have any good mp4 files so lets try the next best thing
                foreach (VidoInfo V in Vids.Values)
                {//Here we pick out the bigest webm file we can find but some TV's won't play them
                    if (V.FileType == "webm" && V.ContentLength > BestSize)
                    { if (BestVideo != null) BestVideo.BestPick = false; V.BestPick = true; BestSize = V.ContentLength; BestVideo = V; }
                }
            }
            LastVideoID = VideoID;
            LastVids = Vids;
            return Vids;
        }

        public static string GetWebPage(string Url)
        {//Load the web-page so we can pull the json data from the page that point to the video's
            using (WebClient client = new WebClient())
            {
                client.Encoding = System.Text.Encoding.UTF8;
                return client.DownloadString(Url);
            }
        }

        private string GetFileType(string Value)
        {//Some types might be missing, maybe use a select/case here !
            if (Value.IndexOf("mp4") > -1) return "mp4";
            else if (Value.IndexOf("mp3") > -1) return "mp3";
            else if (Value.IndexOf("webm") > -1) return "webm";
            else if (Value.IndexOf("avi") > -1) return "avi";
            else if (Value.IndexOf("3gpp") > -1) return "mp4";//We lie here because these files play as .mp4 on most devices
            else if (Value.IndexOf("flv") > -1) return "flv";
            return "UnKnown";
        }

        public VidoInfo(string[] Data, string title,string videoID)
        {//Constructor for Google video files
            string BadArguemnts = "fallback_host,quality,projection,type";
            string And = "";
            this.Title = title;
            this.VideoID = videoID;
            Dictionary<string, string> Dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            foreach (string Item in Data)
            {
                string[] Items = Item.Split('=');
                if (BadArguemnts.IndexOf(Items[0]) == -1)
                {
                    if (Items.Length == 2 && !Dic.ContainsKey(Items[0]))
                    {
                        Dic.Add(Items[0], Items[1]);
                        this.Url += And + Items[0] + "=" + Items[1];
                        And = "&";
                        if (Items[0] == "signature")this.Signature = Items[1];
                        if (Items[0] == "itag") int.TryParse(Items[1], out this.Itag);
                        if (Items[0] == "mime") this.FileType = GetFileType(Items[1]);
                        if (Items[0] == "size")this.Size = Items[1];
                        if (Items[0] == "quality_label")this.Quality = Items[1];
                        if (Items[0] == "fps")this.FPS = Items[1];
                    }
                }
            }
            if (this.Url.Length > 0 && this.Url.ToLower().IndexOf("ratebypass=") == -1) this.Url += "&ratebypass=yes";//We want it now!
        }

        private long GetFileSize()
        {//Do a quick head resquest to googlevideo to get the size of the file
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.Url);
            request.Method = "HEAD";
            WebResponse response = null;
            try
            {
                response = request.GetResponse();
                response.Close();
                this.ContentLength = response.ContentLength;
                if (response.ContentLength > 0) return response.ContentLength; else return 1;//Needs to be over one or it won't be in the list
            }
            catch { ;}
            return 0;
        }

        public void DownLoad(string SavePath)
        {//Saves the video to disk-drive
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.Url);
            WebResponse response = null;
            try
            {
                response = request.GetResponse();
            }
            catch (Exception Ex) {return;}

            using (FileStream FS = File.Open(SavePath + this.FileName, FileMode.Create, FileAccess.Write))
            {
                using (Stream source = response.GetResponseStream())
                {
                    byte[] Buf = new byte[4096];int Size = 0;
                    while ((Size = source.Read(Buf, 0, Buf.Length)) > 0)
                        FS.Write(Buf, 0, Size); 
                }
            }
        }

        public void SendStreamData(Socket SocClient,ref bool Running)
        {//This is used to stream the google-video stream stight out to DLNA devices
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.Url);
            using (WebResponse response = request.GetResponse())
            {
                using (Stream source = response.GetResponseStream())
                {
                    byte[] Buf = new byte[4096];int Size = 0;long BytesSent = 0;
                    while ((Size = source.Read(Buf, 0, Buf.Length)) > 0 && Running)
                    {
                        if (SocClient.Connected)
                            try { SocClient.Send(Buf, Size, SocketFlags.None); BytesSent += Size; }catch { ;}//Write the data stright to the socket 
                        else if (!SocClient.Connected){SocClient.Close();return;}
                    }
                }
            }
        }
    }
}


//

También necesitarás añadir estos métodos de extensión en una helper class

//

public static class Extentions
{

    public static string ChopOffBefore(this string s, string Before)
    {//Usefull function for chopping up strings
        int End = s.IndexOf(Before, StringComparison.OrdinalIgnoreCase);
        if (End > -1)
        {
            return s.Substring(End + Before.Length);
        }
        return s;
    }



    public static string ChopOffAfter(this string s, string After)
    {//Usefull function for chopping up strings
        int End = s.IndexOf(After, StringComparison.OrdinalIgnoreCase);
        if (End > -1)
        {
            return s.Substring(0, End);
        }
        return s;
    }
}
//

Fuente: Dr Gadgit

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP