Youtube-dl-WebUI jQuery and PHP customizations

By | October 5, 2016

Youtube-dl-WebUI is a web interface for the program Youtube-dl, which allows you to download audio and video from streaming sites, and save them to your computer in a variety of formats. Youtube-dl-WebUI is a convenient interface to Youtube-dl but was missing some key features such as previewing the video and selecting the format.

The customizations I made to Youtube-dl-WebUI add functionality for getting a preview of the video (title and thumbnail) and offer options for downloading it in different formats. As well, jQuery AJAX calls are used to preview and download the video, so there is no need to reload the page. The changes I made to the code can be seen here on Github.

Before downloading a video from YouTube (or another streaming site), you may want to read this article and this post to learn more about the legal implications of doing so.

Now let’s take a closer look at the most important code changes!

jQuery Customizations

We use client-side jQuery to control the visibility of the HTML elements (buttons, text, etc) and issue asynchronous post() and get() requests to the server-side PHP scripts (which are explained further down). Here we’ll take a look at the jQuery code behind the Continue, Download, Cancel, Save, and Done buttons.

Continue Button

continue_btn

$("#vidcont").click(function() {
		$("#invalidurl").hide();
		var vidurl = $("#url").val();
		if(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/|www\.)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/.test(vidurl)){
			// Valid URL
		} else {
			$("#invalidurl").show();
			return false;
		}
		$("#spinner1").show();
		$.ajax
		({
			type: "GET",
			url: "class/VideoInfo.php",
			data: { url: vidurl },
			dataType: 'json',
			cache: false,
			success: function(data)
			{
				jQuery.each(data.vidformats, function(index, item) {
					$('#' + item).attr("style", "display: inline; padding: 5px;");
				});
				$("#mp3").attr("style", "display: inline; padding: 5px;");
				$("#vidtitle").text(data.vidinfo[0]);
				$("#vidimg").attr("src", data.vidinfo[1]);
				$("#vidcont").hide();
				$("#spinner1").hide();
				$("#url").attr("disabled", "disabled");
				$("#vidtitle").show();
				$("#vidthumb").show();
				$("#formatlbl").show();
				$("#formatbtns").show();
				// $("#viddown").show();
				$("#viddown").show();
				$("#vidcanc").show();
			}
		});

	});

When the user clicks the continue button, we first want to make sure the #invalidurl HTML element is hidden (line 2), as it may have been revealed if the user entered an invalid URL. The value of the #url box is retrieved (line 3) and validated (line 4). If the URL is invalid, the #invalidurl element is revealed (line 7) to notify the user they need to try the URL again, and the function exits (line 8). If the URL is valid, the #spinner1 progress image is revealed (line 10) and remains visible until the video title and thumbnail have been retrieved. An AJAX GET request passes the video’s URL to the VideoInfo.php script (line 11-18). The returned data is specified to be of type JSON (line 16). If the call is successful, we loop through the returned available formats (line 20) and make their respective radio buttons visible. The “mp3” radio button will always be an available format (line 23). We set the video title (line 24) and thumbnail (line 25) with the returned data. The spinner and continue button are hidden, and the URL box is disabled so that it cannot be modified accidentally (line 28). The video thumbnail appears, and a download and cancel button appear (lines 34 & 35).

Download and Cancel Buttons

download_cancel_btn

	$("#viddown").click(function() {
		$("#downloadbar").show();
		var vidurl = $("#url").val();
		var vidfmt = $("form input[name=format]:checked").val()
		
		$.post('index.php', { urls: vidurl, format: vidfmt }, 
			function(returnedData){
		});

		function checkProgress() {
			$.ajax
			({
				type: "GET",
				url: "class/CheckStatus.php",
				data: { url: vidurl, format: vidfmt },
				dataType: 'json',
				cache: false,
				success: function(data)
				{
					//alert("Exists: " + data.exists);
					//alert("Filename: " + data.filename);
					if(data.exists === "yes") {
						filenameurl = "downloads/" + data.filename
						$("#viddown").hide();
						$("#vidcanc").hide();
						$("#downloadbar").hide();
						$("#vidlink").attr("href", filenameurl);
						$("#vidlink").show();
						$("#vidrestart").show();
						$("#downloadready").attr("style", "display: inline; padding: 5px");
						clearInterval(checkIntervalId);
					}
				}
			});
		}
		checkIntervalId = setInterval(checkProgress, 5000);

	});

If the user clicks the Download button, a progress bar is revealed (line 2) that remains visible until the download has completed. The value of the #url element is retrieved (line 3) and then we check to see which format button the user selected (line 4). The URL and format parameters are passed to the index.php script via a jQuery post() call (line 6-8). We then move into the checkProgress() function, which will run every 5 seconds based on the value we passed to the setInterval() function (line 36). The function checkProgress() passes the video URL and selected format via an AJAX GET request to the CheckStatus.php script (lines 11-18), which checks if the video has completed downloading. When the video has been downloaded, the CheckStatus.php script will return a JSON encoded array containing the value “yes” and the if statement code block will execute. The relative path the filename is put together and stored in the variable “filenameurl” (line 23). The download and cancel buttons, along with the progress bar,  are hidden. The download button link, #vidlink, is updated to point to the downloaded file. A restart button appears and the #downloadready element becomes visible to inform the user that the download has completed. Finally, the interval is cleared so that the checkProgress() function stops repeating.

	$("#vidcanc").click(function() {
		if (typeof checkIntervalId != "undefined") {
			clearInterval(checkIntervalId);
		}
		$.get('index.php', { kill: "all" },
			function(returnedData) {
		});
		clearElements();
	});

If the user clicks the Cancel button, a check is made to see if the 5-second interval has been set (line 2). If so, the interval value is cleared to prevent the checkProgress() function from repeating. Then the parameter “kill” is sent to the index.php script via a jQuery get() call. This cancels any active downloads. The clearElements() function is then called to reset the user interface.

Save and Restart Buttons

save_btn

<a href="" id="vidlink" target="_blank" class="btn btn-primary" style="display: none;" download>Save!</a>

The “Save” button is simply an HTML link that uses the Bootstrap class “btn btn-primary” to make it look like a button. It’s just a link to the video file that’s been downloaded.

	$("#vidrestart").click(function() {
		clearElements();
	});

If the “Done” button is clicked, the clearElements() function is called to reset the user interface.

Clear Elements Function

	function clearElements() { 
		$("#vidthumb").hide();
		$("#viddown").hide();
		$("#vidcanc").hide();
		$("#vidtitle").hide();
		$("#checkboxdiv").hide();
		$("#downloadbar").hide();
		$("#vidlink").hide();
		$("#vidrestart").hide();
		$("#downloadready").hide();
		$("#vidcont").show();
		$("#url").removeAttr("disabled");
		$("#url").val("");
	 }

The clearElements() function hides most of the elements that have been revealed and re-enables the url text box.

PHP Customizations

I created a couple of PHP scripts to handle the jQuery/AJAX post() and get() requests.

VideoInfo.php

<?php

$url = $_GET['url'];

/* Get video title */
$cmd = "youtube-dl";
$cmd .= " -f mp4 "; // To get proper title
$cmd .= " ".escapeshellarg($url);
$cmd .= " --get-title ";

$result = shell_exec($cmd);
$videotitle = str_replace("\n", '', $result);

/* Get video thumbnail */
$cmd = "youtube-dl";
$cmd .= " ".escapeshellarg($url);
$cmd .= " --get-thumbnail";

$result = shell_exec($cmd);
$videothumb = str_replace("\n", '', $result);

/* Get available formats */
$cmd = "youtube-dl";
$cmd .= " ".escapeshellarg($url);
$cmd .= " -F"; // Get formats option

$result = shell_exec($cmd);

//error_log($result);
preg_match_all("#\b(3gp|aac|flv|m4a|mp4|ogg|webm)\b#", $result, $matches); // Find any instances of the file extensions and add them to the 'matches' array
$formats = array_unique($matches[0], SORT_REGULAR); // preg_match_all returns an array for each submatch, so only take values from the first array
//error_log( print_r($formats, true) );

// Enter results into an array
$data = array();
$data["vidinfo"] = array($videotitle, $videothumb);
$data["vidformats"] = $formats;

/* Return JSON encoded array */
echo json_encode($data);

?>

VideoInfo.php does the work of retrieving the video’s title, thumbnail, and available formats, and returns them to the client. First, it stores the video’s URL in a variable (line 3). It then builds a youtube-dl command, which will retrieve the video’s title (lines 6 to 9). The shell_exec() function executes the command (line 11), and the result is stored in the “videotitle” variable. We then concatenate two more commands to get the video thumbnail and the available formats. Again, shell_exec() executes both of the commands and, after some processing, the results are stored in the variables “videothumb” and “formats”. Finally, the data is put into arrays, encoded into JSON, and returned to the client for jQuery to process.

CheckStatus.php

<?php
include_once('FileHandler.php');

$download_path = (new FileHandler())->get_downloads_folder();
$url = $_GET['url'];
$format = $_GET['format'];

/* Get video filename */
$cmd = "youtube-dl";
$cmd .= " -o ";
if ($format === "mp3") { // If format is mp3, manually add .mp3 extension
	$cmd .= escapeshellarg("%(title)s-%(uploader)s.mp3");
	$cmd .= " -x --audio-format mp3";
} else {
	$cmd .= escapeshellarg("%(title)s-%(uploader)s.%(ext)s");
	$cmd .= " -f ".escapeshellarg($format);
}
$cmd .= " ".escapeshellarg($url);
$cmd .= " --restrict-filenames"; 
$cmd .= " --get-filename "; // get video filename

$filename = shell_exec($cmd);
$escaped_filename = str_replace("\n", '', $filename); // Remove newline characters
if ($format === "mp3") {
	$escaped_filename = substr_replace($escaped_filename, "mp3", -3);
}
$fullpath = $download_path."/".$escaped_filename; // Combine download path and filename to get full path

if (file_exists($fullpath)) {
	echo json_encode(array("exists" => "yes", "filename" => $escaped_filename));
} else {
	echo json_encode(array("exists" => "no", "filename" => $escaped_filename));
}

?>

The CheckStatus.php script is invoked every 5 seconds after the user clicks the “Download” button. Essentially, it checks whether or not the requested video exists in the downloads directory. It starts off by storing the URL and video format in two separate variables (lines 5 and 6). It then strings together a command and executes it to determine the filename of the downloaded video (lines 9 to 20). If the file exists, it returns a JSON encoded array to the client that includes a message that “yes” the file has completed downloading (line 30). Otherwise, it returns “no” to the client and the JavaScript loop continues. The returned array also includes the filename itself so that the client can build the download link.

Leave a Reply

Your email address will not be published. Required fields are marked *

*