Kgee
Kgee

Reputation: 53

youtube video downloader with php

i am trying to make a youtube video downloader with php it works fine, but if the youtube video id has a character like - or _ it shows an error undefined index url,it seems like php isn't seeing this characters e,g if the youtube video id is N65RvNkZFGE it will work but if its something like wXhTHyIgQ_U it wont work cause of the underscore here's the code:

// Check whether the url is valid 
    if(!empty($youtubeURL) && !filter_var($youtubeURL, FILTER_VALIDATE_URL) === false){ 
    // Get the downloader object 
    $downloader = $handler->getDownloader($youtubeURL); 

    // Set the url 
    $downloader->setUrl($youtubeURL); 

    // Validate the youtube video url 
    if($downloader->hasVideo()){ 
        // Get the video download link info 
        $videoDownloadLink = $downloader->getVideoDownloadLink(); 

        $videoTitle = $videoDownloadLink[0]['title']; 
        $videoQuality = $videoDownloadLink[0]['qualityLabel']; 
        $videoFormat = $videoDownloadLink[0]['format']; 
        $videoFileName = strtolower(str_replace(' ', '_', $videoTitle)).'.'.$videoFormat; 
        $downloadURL = $videoDownloadLink[0]['url']; 
        $fileName = preg_replace('/[^A-Za-z0-9.\_\-]/', '', basename($videoFileName)); 

the YouTubeDownloader.class.php file which holds the setUrl() function, the get extractVideoId() here's the code:

     public function setUrl($url){ 
        $this->video_url = $url; 
    } 

private function extractVideoId($video_url){ 
        //parse the url 
        $parsed_url = parse_url($video_url); 
        if($parsed_url["path"] == "youtube.com/watch"){ 
            $this->video_url = "https://www.".$video_url; 
        }elseif($parsed_url["path"] == "www.youtube.com/watch"){ 
            $this->video_url = "https://".$video_url; 
        } 

        if(isset($parsed_url["query"])){ 
            $query_string = $parsed_url["query"]; 
            //parse the string separated by '&' to array 
            parse_str($query_string, $query_arr); 
            if(isset($query_arr["v"])){ 
                return $query_arr["v"]; 
            } 
        }    
    } 

Upvotes: 3

Views: 3718

Answers (1)

gre_gor
gre_gor

Reputation: 6808

It has nothing to do with "_" or "-". "n85KukOXc0A" doesn't work either. Some videos don't return the "url" field, but "cipher". It's an attempt from YouTube to obfuscate the URL.

Your $videoDownloadLink, for the video ID "wXhTHyIgQ_U", looks like:

array (
  0 => 
  array (
    'itag' => 18,
    'bitrate' => 568627,
    'width' => 640,
    'height' => 360,
    'lastModified' => '1575010363774854',
    'contentLength' => '16085704',
    'quality' => 'medium',
    'qualityLabel' => '360p',
    'projectionType' => 'RECTANGULAR',
    'averageBitrate' => 568472,
    'audioQuality' => 'AUDIO_QUALITY_LOW',
    'approxDurationMs' => '226371',
    'audioSampleRate' => '44100',
    'audioChannels' => 2,
    'cipher' => 's=__L8kZ2zTIc_OfmovvG91jyFU3WN4QTERuPCxA7rHfbHICEhCrCQkmqPth6pmfw5wmrIPOT_ijWceGCWdCeK-lVYXgIARwMGkhKDv&url=https%3A%2F%2Fr4---sn-hpa7kn7s.googlevideo.com%2Fvideoplayback%3Fexpire%3D1583898090%26ei%3DigloXtGYD4bngAeu8YXQCg%26ip%3D2a00%253Aee2%253A1200%253Ae400%253A8c11%253A6897%253A2e00%253Abef0%26id%3Do-AAcaOp-0syooPWmAUuzOfm6gHGPWYCiDlfa-RNdIP34W%26itag%3D18%26source%3Dyoutube%26requiressl%3Dyes%26mm%3D31%252C26%26mn%3Dsn-hpa7kn7s%252Csn-nv47lnly%26ms%3Dau%252Conr%26mv%3Dm%26mvi%3D3%26pl%3D32%26gcr%3Dsi%26initcwndbps%3D1023750%26vprv%3D1%26mime%3Dvideo%252Fmp4%26gir%3Dyes%26clen%3D16085704%26ratebypass%3Dyes%26dur%3D226.371%26lmt%3D1575010363774854%26mt%3D1583876448%26fvip%3D4%26fexp%3D23842630%26c%3DWEB%26txp%3D5531432%26sparams%3Dexpire%252Cei%252Cip%252Cid%252Citag%252Csource%252Crequiressl%252Cgcr%252Cvprv%252Cmime%252Cgir%252Cclen%252Cratebypass%252Cdur%252Clmt%26lsparams%3Dmm%252Cmn%252Cms%252Cmv%252Cmvi%252Cpl%252Cinitcwndbps%26lsig%3DABSNjpQwRQIgBvV2KI0zNTv-7PsmdoRnpyNBvxeMRJIHSlKjfScxihcCIQDlHa5A-1cGAVReyssZ4YkH2nV2rdN1fel6_-Bkv7CAjA%253D%253D&sp=sig',
    'title' => 'Post Malone - Circles',
    'mime' => 'video/mp4',
    'format' => 'mp4',
  ),
)

As you see, there is no "url" field, but there is a "cipher" field.
If we decode it with parse_str($videoDownloadLink[0]['cipher'], $cipher) we get:

array (
  's' => '__L8kZ2zTIc_OfmovvG91jyFU3WN4QTERuPCxA7rHfbHICEhCrCQkmqPth6pmfw5wmrIPOT_ijWceGCWdCeK-lVYXgIARwMGkhKDv',
  'url' => 'https://r4---sn-hpa7kn7s.googlevideo.com/videoplayback?expire=1583898090&ei=igloXtGYD4bngAeu8YXQCg&ip=2a00%3Aee2%3A1200%3Ae400%3A8c11%3A6897%3A2e00%3Abef0&id=o-AAcaOp-0syooPWmAUuzOfm6gHGPWYCiDlfa-RNdIP34W&itag=18&source=youtube&requiressl=yes&mm=31%2C26&mn=sn-hpa7kn7s%2Csn-nv47lnly&ms=au%2Conr&mv=m&mvi=3&pl=32&gcr=si&initcwndbps=1023750&vprv=1&mime=video%2Fmp4&gir=yes&clen=16085704&ratebypass=yes&dur=226.371&lmt=1575010363774854&mt=1583876448&fvip=4&fexp=23842630&c=WEB&txp=5531432&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cgcr%2Cvprv%2Cmime%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&lsparams=mm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=ABSNjpQwRQIgBvV2KI0zNTv-7PsmdoRnpyNBvxeMRJIHSlKjfScxihcCIQDlHa5A-1cGAVReyssZ4YkH2nV2rdN1fel6_-Bkv7CAjA%3D%3D',
  'sp' => 'sig',
)

You need to properly scramble the "s" field value and add it to the URL as the field named with the "sp" field value.
The way it needs to be scrambled changes regularly. The current way from https://www.youtube.com/yts/jsbin/player_ias-vfle4a9aa/en_US/base.js is:

var Ps = function(a) { a = a.split(""); Os.Dw(a, 1); Os.hZ(a, 21); Os.An(a, 24); Os.hZ(a, 34); Os.hZ(a, 18); Os.hZ(a, 63); return a.join("") };

var Os = {
    Dw: function(a, b) {
        a.splice(0, b)
    },
    An: function(a) {
        a.reverse()
    },
    hZ: function(a, b) {
        var c = a[0];
        a[0] = a[b % a.length];
        a[b % a.length] = c
    }
};

Which translates into PHP as:

function scramble($a) {
    $a = str_split($a);
    scr_splice($a, 1);
    scr_swap($a, 21);
    scr_reverse($a, 24);
    scr_swap($a, 34);
    scr_swap($a, 18);
    scr_swap($a, 63);
    return implode('', $a);
}

function scr_reverse(&$a) {
    $a = array_reverse($a);
}
function scr_splice(&$a, $b) {
    array_splice($a, 0, $b);
}
function scr_swap(&$a, $b) {
    $c = $a[0];
    $a[0] = $a[$b % count($a)];
    $a[$b % count($a)] = $c;
}

In your code, you need to check which type of URL you got and get the proper URL.

if (isset($videoDownloadLink[0]['url'])) {
    $downloadURL = $videoDownloadLink[0]['url'];
}
else if (isset($videoDownloadLink[0]['cipher'])) {
    parse_str($videoDownloadLink[0]['cipher'], $cipher);
    $downloadURL = $cipher['url']."&".$cipher["sp"]."=".scramble($cipher["s"]);
}
else {
    die('Error getting YouTube URL!');
}

Note:
This will only work until YouTube changes the way it's scrambled again.

Upvotes: 3

Related Questions