16

All the current tutorials on making your own youtube video downloader are outdated. The whole token insertion from video info does not work, and neither does using the javascript method. Does anyone know of a way to do this currently without just downloading someone else's programs? I just want to know the method to go about so I can program my own (for fun :D)

Thanks in advance!

Riveascore
  • 1,724
  • 4
  • 26
  • 46

3 Answers3

40

As of 1/1/2018, the old technique described below doesn't seem to work reliably. Youtube's in-page variable structure seems to have changed and I haven't been able to figure out the updated version.

Here's an updated (June, 2017) version that works directly from the dev tools console available in any browser. Currently should work for any Youtube media type, including up to 4k if its available.

https://gist.github.com/geuis/8b1b2ea57d7f9a9ae22f80d4fbf5b97f

// ES6 version
const videoUrls = ytplayer.config.args.adaptive_fmts
  .split(',')
  .map(item => item
    .split('&')
    .reduce((prev, curr) => (curr = curr.split('='),
      Object.assign(prev, {[curr[0]]: decodeURIComponent(curr[1])})
    ), {})
  )
  .reduce((prev, curr) => Object.assign(prev, {
    [curr.quality_label || curr.type]: curr
  }), {});

console.log(videoUrls);

// ES5 version
var videoUrls = ytplayer.config.args.adaptive_fmts
  .split(',')
  .map(function (item) {
    return item
      .split('&')
      .reduce(function (prev, curr) {
        curr = curr.split('=');
        return Object.assign(prev, {[curr[0]]: decodeURIComponent(curr[1])})
      }, {});
  })
  .reduce(function (prev, curr) {
    return Object.assign(prev, {
      [curr.quality_label || curr.type]: curr
    });
  }, {});

console.log(videoUrls);

Sample output for https://www.youtube.com/watch?v=9bZkp7q19f0

{
  "1080p": {
    "itag": "248",
    "xtags": "",
    "lmt": "1440215955569849",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=248&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "video/webm;+codecs=\"vp9\"",
    "bitrate": "1733724",
    "index": "243-1977",
    "size": "1920x1080",
    "projection_type": "1",
    "fps": "30",
    "clen": "31192903",
    "init": "0-242",
    "quality_label": "1080p"
  },
  "720p": {
    "itag": "247",
    "xtags": "",
    "lmt": "1440215905109639",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=247&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "video/webm;+codecs=\"vp9\"",
    "bitrate": "726076",
    "index": "243-1933",
    "size": "1280x720",
    "projection_type": "1",
    "fps": "30",
    "clen": "15801933",
    "init": "0-242",
    "quality_label": "720p"
  },
  "480p": {
    "itag": "244",
    "xtags": "",
    "lmt": "1440215890236689",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=244&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "video/webm;+codecs=\"vp9\"",
    "bitrate": "396541",
    "index": "243-1933",
    "size": "854x480",
    "projection_type": "1",
    "fps": "30",
    "clen": "7928237",
    "init": "0-242",
    "quality_label": "480p"
  },
  "360p": {
    "itag": "243",
    "xtags": "",
    "lmt": "1440215888783441",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=243&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "video/webm;+codecs=\"vp9\"",
    "bitrate": "223695",
    "index": "243-1933",
    "size": "640x360",
    "projection_type": "1",
    "fps": "30",
    "clen": "5127362",
    "init": "0-242",
    "quality_label": "360p"
  },
  "240p": {
    "itag": "242",
    "xtags": "",
    "lmt": "1440215900971640",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=242&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "video/webm;+codecs=\"vp9\"",
    "bitrate": "113952",
    "index": "242-1931",
    "size": "426x240",
    "projection_type": "1",
    "fps": "30",
    "clen": "2597162",
    "init": "0-241",
    "quality_label": "240p"
  },
  "144p": {
    "itag": "278",
    "xtags": "",
    "lmt": "1440215900119192",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=278&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "video/webm;+codecs=\"vp9\"",
    "bitrate": "60303",
    "index": "242-1930",
    "size": "256x144",
    "projection_type": "1",
    "fps": "15",
    "clen": "1798744",
    "init": "0-241",
    "quality_label": "144p"
  },
  "audio/mp4;+codecs=\"mp4a.40.2\"": {
    "bitrate": "128266",
    "itag": "140",
    "xtags": "",
    "lmt": "1440578358539132",
    "index": "592-1271",
    "clen": "8482615",
    "projection_type": "1",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=140&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "audio/mp4;+codecs=\"mp4a.40.2\"",
    "init": "0-591"
  },
  "audio/webm;+codecs=\"vorbis\"": {
    "bitrate": "118499",
    "itag": "171",
    "xtags": "",
    "lmt": "1440215938192462",
    "index": "4452-5366",
    "clen": "6383456",
    "projection_type": "1",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=171&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "audio/webm;+codecs=\"vorbis\"",
    "init": "0-4451"
  },
  "audio/webm;+codecs=\"opus\"": {
    "bitrate": "154966",
    "itag": "251",
    "xtags": "",
    "lmt": "1440215889283443",
    "index": "272-1186",
    "clen": "9526605",
    "projection_type": "1",
    "url": "https://r16---sn-n4v7kn7r.c.youtube.com/videoplayback?itag=251&keepalive=ye…2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csc%2Csource%2Cexpire&mt=1498245944",
    "type": "audio/webm;+codecs=\"opus\"",
    "init": "0-271"
  }
}
Geuis
  • 41,122
  • 56
  • 157
  • 219
  • do you know if there is a way of accessing the player config namely (ytplayer.config.args.url_encoded_fmt_stream_map) through YouTube Player API for iframe Embeds? – lukejacksonn Jul 11 '16 at 21:39
  • 2
    @lukejacksonn Not in a regular browser. Any site loaded into an iframe (youtube.com in this case) is sandboxed. This is to prevent security issues like cross site scripting, etc. So basically, you have little to no access to the javascript environment of the page in the iframe. You would have to inject your own script into the iframe page to be able to communicate across. And you can't do that on youtube.com – Geuis Jul 11 '16 at 23:53
  • I feared this might be the answer but it makes sense. Thanks for taking the time to reply – lukejacksonn Jul 12 '16 at 13:45
  • 1
    Right, because you can't access the dev tools in mobile Safari *unless* you are connected to a Mac via a usb cable with desktop Safari and its dev tools open. In which case, it will work. – Geuis Jul 22 '16 at 21:07
  • @Geuis, great answer :) But these urls give 403 forbidden. How is the video fetched if the source url is not accessible. Is there a way to get the video ? – raj454raj Nov 07 '16 at 21:05
  • @raj454raj I've run into that issue as well on certain videos. I spent some time looking at it the other day and it seems some videos are fetched in pieces (possibly). Didn't find a way around it, but honestly didn't spend too much time with it. – Geuis Nov 07 '16 at 22:10
  • running this inside the dev console on some youtube video page only prints out undefined. – phil294 Mar 17 '17 at 17:00
  • @Blauhirn Just checked again on a couple of videos, still works. – Geuis Mar 18 '17 at 23:43
  • It doesn't get links upper than 720P, e.g: Test it on https://www.youtube.com/watch?v=W-jPAOonCOc , it has 4K but script doesn't get it – Mohammad Dayyan Jun 23 '17 at 11:59
  • @MohammadDayyan Thanks for catching that. I've updated the script and it gets 4k plus some other media types I was missing before. – Geuis Jun 23 '17 at 19:31
  • that is amazing. how did you manage to find `adaptive_fmts` amongst all the noise? – oldboy Jul 20 '17 at 21:04
  • 1
    @Anthony It was actually thanks to @MohammadDayyan pointing out 4k videos not getting listed. I'm already a bit familiar with the js object, and re-examining it let me find `adaptive_fmts`. – Geuis Jul 20 '17 at 21:27
  • bcuz this whole thing wouldn't work without finding and parsing and reconstructing the adaptive_fmts string, right? – oldboy Jul 20 '17 at 23:15
  • @Geuis hey, how would i dynamically or programatically retrieve `ytplayer.config.args.adaptive_fmts`?? – oldboy Jul 21 '17 at 08:42
  • @Anthony um huh? – Geuis Jul 21 '17 at 18:29
  • @Geuis is there any way to return or access the containing `` or even the containing `
    ...
    ` other than inputting that code in the console?
    – oldboy Jul 21 '17 at 19:50
  • 1
    Same answer as the first comment. Unless you are loading youtube into a 3rd party browser like WKWebKit or UIWebKit where you can inject javascript, or if you are developing a browser extension that lets you do the same thing, you can only access the `ytplayer` object from the console. Or if you scrape the page, you could parse out the script element that contains it. – Geuis Jul 21 '17 at 19:58
  • @Geuis hey, can u give me a hand? i've got almost everything working. i've scraped the info (which is a string instead of an object), and then parsed the string and created an object, but for whatever reason it doesn't spit out the full `url` property. i end up with the url and the first parameter. i.e. `https://r4---sn-uxa0n-t8gl.googlevideo.com/videoplayback?initcwndbps`. something about the `=` sign. – oldboy Jul 22 '17 at 03:51
  • @Geuis **[edit to my last comment]** ...but for whatever reason it doesn't spit out the full `url` for the url property. it ends up with only the url and the very first parameter. i.e. `https://r4---sn-uxa0n-t8gl.googlevideo.com/videoplayback?initcwndbps`. every other property of the object seems fine. something about the `=` sign. – oldboy Jul 22 '17 at 03:57
  • 1
    @Anthony Would be easier if you create a codepen with your code. Will make it easier to collaborate. – Geuis Jul 22 '17 at 19:54
  • @Geuis I will do that right now. I figured out how to get a full URL. the issue was that I was decoding the adaptive_fmts string *before* the parsing process. however, none of the URLs work. im not even getting working URLs if i input your functions directly to console. :/ anyways, **here is the [codepen](https://codepen.io/tOkyO1/pen/przQpp)** – oldboy Jul 22 '17 at 21:29
  • it's not letting me move this to chat :/ i think i got it so it works every time :D **however, there's never any audio on the video links and never any video or images on the audio links?** do u have any idea how i could fix this? it's the same issue with your code too – oldboy Jul 23 '17 at 00:21
  • 1
    This is interesting. I'm noticing now that with adaptive_fmts the video and audio streams are split apart. You have to parse `url_encoded_fmt_stream_map` like I used to in order to get urls that play combined audio and video. – Geuis Jul 23 '17 at 23:24
  • @Geuis hm... but your method doesn't parse url_encoded_fmt_stream_map. and yes, i just realized a similar issue. as i posted on your git, your code in this answer only works when the video page is accessed directly or refreshed instead of using YT's "internal" navigation since `adaptive_fmts` only exists the first time the page is actually loaded – oldboy Jul 24 '17 at 22:46
18

As of today 15/06/2012 is really simple; but you have to be watch for future changes. Here it is (in Javascript)

try{
    var urls = document.body.innerHTML.match(/"url_encoded_fmt_stream_map": "url=([^"]+)/)[1]
    urls = decodeURIComponent(urls).replace(/\\u0026/g,'&')
    urls = urls.replace(/&quality.+?(?=,url)/g,'');;
    urls = urls.split(',url=')
    // urls is an array of all the possible qualities
    // To download one you could use something like:
    // document.location = urls[0]
    // The first one is usually in the highest quality available
} catch(e){
    console.error("Youtube may have changed its API")
}
Ivan Castellanos
  • 8,041
  • 1
  • 47
  • 42
1

Why do you want to reinvent the wheel? use Rapidleech script!

Btw, if you still need to reinvent the wheel, here is source-code of the Youtube plugin of Rapidleech (it's highly readable:


class youtube_com extends DownloadClass {

    /*
    Some blah blah about the age verification and erroneous URLs
    $fmt is quality/format number and is an integer
   */
    public function Download($link) {
        $this->fmts = array(38,37,22,45,35,44,34,43,18,5,17);
        $yt_fmt = empty($_REQUEST['yt_fmt']) ? '' : $_REQUEST['yt_fmt'];
        $this->fmturlmaps = $this->GetVideosArr($fmt_url_maps);

        if (empty($yt_fmt) && !isset($_GET["audl"])) return $this->QSelector($link);
        elseif (isset($_REQUEST['ytube_mp4']) && $_REQUEST['ytube_mp4'] == 'on' && !empty($yt_fmt)) {
            //look for and download the highest quality we can find?
            if ($yt_fmt == 'highest') {
                foreach ($this->fmts as $fmt) {
                    if (array_key_exists($fmt, $this->fmturlmaps)) {
                        $furl = $this->fmturlmaps[$fmt];
                        break;
                    }
                }
            } else { //get the format the user specified (making sure it actually exists)
                if (!$furl = $this->fmturlmaps[$yt_fmt]) html_error ('Specified video format not found');
                $fmt = $yt_fmt;
            }
        } else { //just get the one Youtube plays by default (in some cases it could also be the highest quality format)
            $fmt = key($this->fmturlmaps);
            $furl = $this->fmturlmaps[$fmt];
        }

        if (preg_match ('%^5|34|35$%', $fmt)) $ext = '.flv';
        elseif (preg_match ('%^17$%', $fmt)) $ext = '.3gp';
        elseif (preg_match ('%^18|22|37|38$%', $fmt)) $ext = '.mp4';
        elseif (preg_match ('%^43|44|45$%', $fmt)) $ext = '.webm';
        else $ext = '.flv';

        if (!preg_match('#<title>(.*)\s+-\sYouTube[\r|\n|\t|\s]*</title>#Us', $this->page, $title)) html_error('No video title found! Download halted.');
        if (!preg_match ('/video_id=(.+?)(\\\|"|&|(\\\u0026))/', $this->page, $video_id)) html_error('Video id not found.');

        $FileName = str_replace (Array ("\\", "/", ":", "*", "?", "\"", "<", ">", "|"), "_", html_entity_decode(trim($title[1]), ENT_QUOTES)) . "-[{$video_id[1]}][f$fmt]$ext";

        if (stristr($furl, '|')) {
            $u_arr = explode('|', $furl);
            $furl = preg_replace('#://([^/]+)#', "://".$u_arr[2], $u_arr[0]);
        }
        if (isset($_REQUEST['ytdirect']) && $_REQUEST['ytdirect'] == 'on')
        {
            echo "<br /><br /><h4><a style='color:yellow' href='" . urldecode($furl) . "'>Click here or copy the link to your download manager to download</a></h4>";
            echo "<input name='dlurl' style='width: 1000px; border: 1px solid #55AAFF; background-color: #FFFFFF; padding:3px' value='" . urldecode($furl) . "' onclick='javascript:this.select();' readonly></input>";
        }
        else
        {
            $this->RedirectDownload (urldecode($furl), $FileName, $this->cookie, 0, 0, $FileName);
        }
    }
Abdul Jamil
  • 363
  • 3
  • 13
Sepehr
  • 2,051
  • 19
  • 29