How to download Youtube videos by PHP
Maybe PHP is not the best language for writing a download manager, but in this article, we are going to learn how we can download videos from Youtube. Because PHP does not support multi-threading, we cannot make a proper download manager that splits the file into several chunks and download it concurrently. There are some ways to add multi-threading to PHP, but for the sake of this article, we won’t go through those ways and will stick to single threading.
At the end of this article, we have a command called dl.
It gets a Youtube video URL to download.
What we will learn
- How to create a command-line application
- How to use the Factory design pattern
- How to get information about a Youtube video such as formats, titles, etc.
- How to download a file with PHP with a progress bar
Create the project
Create a new directory and run composer init
inside it:
After running composer init
you need to answer a few questions. This command creates a composer.json
file inside the youtube-downloader
directory.
Install dependencies
- symfony/console is a great package that helps us to create a command-line application and make it easier to handle input and outputs.
- smoqadam/youtube-video-info gives us information about a Youtube video: formats, subtitles, download URL, title, etc.
Config autoloading
Create a directory and call it src
we will put all of our classes inside this directory. For taking advantage of composer autloading, open composer.json
and add autoload
section such as below:
Downloader
is our chosen namespace for this project, and whenever the composer wants to load a class within this namespace, it looks inside the src
directory.
You can find more information about PSR4 here.
Handle Command Line arguments
Create a Download.php
file inside the src/Commands
directory and with the following content:
Download
class extended Command
from the symfony/console
package, which gives us many features to handle command-line input and outputs.
$defaultName
is the name of our command.configure
method is using to config our command, such as add description, add arguments, options, and so on.- And finally,
execute
will call when we run thedl
command from CLI. It receives two parameters,$input
for handling the inputs such as options and arguments, and$output
for managing the output. As you can see, we used<info></info>
tags in the write method to colorize the output.
Let’s make our dl
command work by creating a index.php
file under root directory:
Application
is the main class responsible for handling the command line application, so to make our dl
command work, we must use the add
method to pass a new instance of Download
class to it.
Now, open your terminal and run the following command:
$ php index.php dl http://example.com
you should see http://example.com in green color as output.
Downloader
In this section, we are going to make a downloader class. This class receives a URL and a file name, and it will download the file for us. But before we create our Downloader
class, let’s think about how we can make it extendable? By extendable, I mean how we can have a Downloader
that downloads files from different sources such as Youtube or Vimeo videos or maybe a direct link.
We know that Youtube videos have different qualities, and each quality has a different download link. So before we can download a video, we must get all qualities and ask users which quality they want to download. But this process would be different if they’re going to download from Vimeo or a direct link.
In the SOLID principle, S is stand for Single responsibility, which means each class should only have a single responsibility. With that explanation, we need to have different classes for different sources.
To achieve this, we can use Factory design pattern to instantiate a new object based on the URL that the user wants to download.
For example, if it was a Youtube URL, it will return a new object from Youtube
class and so on.
Factory.php
class under src/
directory would be something like this:
Factory Design pattern is a creational pattern that create a different object based on some conditions. In our case, we checked the $url
and if it was a Yoututbe link, it returns a new instance of Yoututbe
class, otherwise it throws an Exception
.
As we saw earlier, we decided to let our app download from different sources, but these sources or providers must follow some rules to make it easier to extend and debug. We define our rules inside ProviderAbstract
, and every provider (e.g., Youtube class) must extend from this class, and they must implement the init
method.
ProviderAbstract
Every provider must initialize and prepare the download link and output file name inside the init
method.
Create a directory under src
and call it Providers
then create Youtube.php
inside it:
Youtube
class extended from ProviderAbstract
, which means it must implement the init
method. Inside init
, we took advantage of smoqadam/youtube-video-info package to parse a Youtube video. Then created a simple menu with each format in a new line. readline
function receives an input which in our case it's an index of a menu item and whenever the user enters the number associated to the format, we set $this->url
and $this->name
.
The menu would be something like this:
Note: File sizes are in byte, you can convert to MB by yourself.
How to download a file
Now we are going to see how we can download a file and how to show a progress bar with PHP. Create a file under the src
directory and call it Downloader.php
:
We will talk about the download
method in the next section.
What is Stream Context?
In PHP, Stream Context is using to enhance or modify a stream, and it can pass to most file handling functions. From the PHP website:
A context is a set of parameters and wrapper specific options which modify or enhance the behavior of a stream. Contexts are created using stream_context_create() and can be passed to most filesystem related stream creation functions (i.e. fopen(), file(), file_get_contents(), etc…).
stream_context_set_params
receives a stream context created by stream_context_create
and an array of parameters. One of the parameters we can set is notification
that is a callback function. This function will be called whenever our context state changes. (see more: https://www.php.net/manual/en/function.stream-notification-callback.php)
In download
method we passed the URL we want to download and the context we made earlier to fopen
, it will open the remote file in a read mode, and whenever the state of the file changes, it will call streamCallback
method. Inside the streamCallback
method, we checked for the notification type and printed the proper information about each notification.
Now open sr/Command/Download.php
and change the execute
method as below:
Then open your terminal and try to download a Youtube video:
Cool, right?
Download from a direct link
So far, we can download Youtube videos, but what about direct links such as http://example.com/file.mp3 .
Because we used the Factory design pattern and Single Responsibility principle, it would be as easy as create a separate class for direct links.
Create Direct.php
file under src/Providers
directory with the following contents:
Because direct links are direct, the only thing we should do is to set $this->url
and $this->name
. Now, open src/Factory.php
and change it as below:
As you can see, we changed the default
section to return a new object of Direct
class.
You can find the project in this repository:
Summary
Although PHP is not an excellent tool to write a downloader, we saw how we could manage and download a file from Youtube or a direct link. We learned about Stream Context and how we can use them of change or get information about a stream. We used the Factory design pattern and saw what the heck Single Responsibility means. Now it’s your time to use your imagination and creativity to extend this project. Maybe by adding more providers or how to make use of multi-threading extensions such as https://github.com/krakjoe/parallel to make it more usable.