A PHP script used on my home server to download YouTube videos
Initial thoughts

I must admit I hadn't heard of the youtube-dl program but it seems quite nifty. Perhaps it was what many of the websites I have seen for downloading YT videos use behind the scenes.

I tried using the script locally and found that the URL must contain an ampersand (i.e. have 2+ query string variables), otherwise the logic to look for a playlist parameter and remove it would lead to an empty string passed to the youtube-dl program. But some URLs I see from Youtube only have zero or one parameters (e.g. https://www.youtube.com/watch?v=DuB6UjEsY_Y and the equivalent sharing link: https://youtu.be/DuB6UjEsY_Y). 

Initially I was wondering why $numberToUse couldn't be set to count($directoriesInUse) + 1 but then I realized that some of those directories could be deleted and that might lead to issues. Then I considered that if you could parse out a video id (which seems to have remained the same since 2014)  from the URL then that could be used instead of an integer for the sub-directory name.

Suggestions

Use a mapping instead of a switch

The switch could be simplified:


switch($_GET['format']) {
    case 'bestvideo': case 'worstvideo': $formatExt = "MP4"; break;
    case 'bestaudio': $formatExt = "m4a"; break;
    case 'worstaudio': $formatExt = "webm";
        break;
    default:
        echo("Illegal video format '".$_GET['format']."'!");
        exit(1);
}



A mapping could be defined of formats to extensions:

const FORMAT_MAPPING = [
    'bestvideo' =&gt; "MP4",
    'worstvideo' =&gt; "MP4",
    'bestaudio' =&gt; "m4a",
    'worstaudio' =&gt; "webm"
];


Then if the query string is missing that key or that key does not exist in the mapping, show the error and exit early.

if (!isset($_GET['format']) || !array_key_exists($_GET['format'], FORMAT_MAPPING)) {
    echo("Illegal video format '".$_GET['format']."'!");
    exit(1);
}


Otherwise just assign the value to $formatExt:

$formatExt = FORMAT_MAPPING[$_GET['format']];


Control structures not using statement groups

While it may seem unlikely that there would be a need for multiple lines following if, else or for statements, it is best to put the statements to execute in a group (i.e. with curly braces/brackets). Then when you determine that you need to add a line to one of those statement groups, it eliminates any chance of logic error.


if($format === "bestaudio" or $format === "worstaudio" or $format === "worstvideo") {
        $response = exec('mkdir ./workers/'.$numberToUse.' &amp;&amp; cd ./workers/'.$numberToUse.'/ &amp;&amp; '.$ytcmd.' --no-playlist --format '.$format.' -o \'%(title)s.%(ext)s\' '.$url);
}
else {
        $response = exec('mkdir ./workers/'.$numberToUse.' &amp;&amp; cd ./workers/'.$numberToUse.'/ &amp;&amp; '.$ytcmd.' --no-playlist -o \'%(title)s.%(ext)s\' '.$url);
}



or has lower precedence than || and others like assignment

Note the operator precedence list and how or is at the end of the list, while logical or i.e. ||, is above assignment and others:





There is a good illustration of the difference in the user contributed notes by fabmlk:


  Watch out for the difference of priority between 'and vs &amp;&amp;' or '|| vs or':

&lt;?php
$bool = true &amp;&amp; false;
var_dump($bool); // false, that's expected

$bool = true and false;
var_dump($bool); // true, ouch!
?&gt;

  
  Because 'and/or' have lower priority than '=' but '||/&amp;&amp;' have higher.


So it is wise to get in the practice of using || unless you know you need or 

use foreach instead of for

Instead of this for loop:


for($i = 0; $i &lt; sizeof($directoriesInUse); $i++)
    $integerIDs[$i] = (int)explode("/", $directoriesInUse[$i])[2];



A foreach could be used 

foreach ($directoriesInUse as $i =&gt; $directory) {
    $integerIDs[$i] = (int)explode("/", $directory)[2];
}


One could also use array_map(). Instead of storing an array of integers, you could just keep track of the maximum number seen - using max(). That would avoid the need to sort the array. This likely wouldn’t be a major optimization unless there were a large number of directories. 

Logical flaw when no ampersand exists in URL

As was mentioned above, this line doesn't work well when there is no ampersand in $url


//url may contain &amp; for a playlist, so it will be removed here to prevent other issues.
$url = substr($url, 0, strpos($url, "&amp;"));



This is because strpos() "Returns FALSE if the needle was not found."1. I would update the code to only perform that alteration if the string actually contains an ampersand.

1http://php.net/strpos