Merge branch 'feature/library' into develop

This commit is contained in:
Pierre Rudloff 2020-06-21 15:12:48 +02:00
commit ea2d0bf1d4
30 changed files with 649 additions and 1159 deletions

View file

@ -148,32 +148,8 @@ so that it points to your ffmpeg/avconv binary (`/usr/bin/avconv` on Debian/Ubun
## Use as library ## Use as library
AllTube can also be used as a library to extract a video URL from a webpage. The `Video` class is now available as [a separate package](https://packagist.org/packages/rudloff/alltube-library)
so that you can reuse it in your projects.
You can install it with:
```bash
composer require rudloff/alltube
```
You can then use it in your PHP code:
```php
use Alltube\Config;
use Alltube\Video;
require_once __DIR__.'/vendor/autoload.php';
Config::setOptions(
[
'youtubedl' => '/usr/local/bin/youtube-dl',
]
);
$video = new Video('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
$video->getUrl();
```
You can also have a look at this [example project](https://github.com/Rudloff/alltube-example-project).
## JSON API ## JSON API

View file

@ -6,7 +6,8 @@
namespace Alltube; namespace Alltube;
use Exception; use Alltube\Exception\ConfigException;
use Alltube\Library\Downloader;
use Jawira\CaseConverter\CaseConverterException; use Jawira\CaseConverter\CaseConverterException;
use Symfony\Component\ErrorHandler\Debug; use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@ -141,7 +142,7 @@ class Config
* Config constructor. * Config constructor.
* *
* @param mixed[] $options Options * @param mixed[] $options Options
* @throws CaseConverterException * @throws ConfigException
*/ */
private function __construct(array $options = []) private function __construct(array $options = [])
{ {
@ -197,20 +198,15 @@ class Config
* Throw an exception if some of the options are invalid. * Throw an exception if some of the options are invalid.
* *
* @return void * @return void
* @throws Exception If Python is missing * @throws ConfigException If Python is missing
* * @throws ConfigException If youtube-dl is missing
* @throws Exception If youtube-dl is missing
*/ */
private function validateOptions() private function validateOptions()
{ {
/*
We don't translate these exceptions because they usually occur before Slim can catch them
so they will go to the logs.
*/
if (!is_file($this->youtubedl)) { if (!is_file($this->youtubedl)) {
throw new Exception("Can't find youtube-dl at " . $this->youtubedl); throw new ConfigException("Can't find youtube-dl at " . $this->youtubedl);
} elseif (!Video::checkCommand([$this->python, '--version'])) { } elseif (!Downloader::checkCommand([$this->python, '--version'])) {
throw new Exception("Can't find Python at " . $this->python); throw new ConfigException("Can't find Python at " . $this->python);
} }
if (!class_exists(Debug::class)) { if (!class_exists(Debug::class)) {
@ -241,13 +237,18 @@ class Config
* If the value is an array, you should use the YAML format: "CONVERT_ADVANCED_FORMATS='[foo, bar]'" * If the value is an array, you should use the YAML format: "CONVERT_ADVANCED_FORMATS='[foo, bar]'"
* *
* @return void * @return void
* @throws CaseConverterException * @throws ConfigException
*/ */
private function getEnv() private function getEnv()
{ {
foreach (get_object_vars($this) as $prop => $value) { foreach (get_object_vars($this) as $prop => $value) {
try {
$convert = new Convert($prop); $convert = new Convert($prop);
$env = getenv($convert->toMacro()); $env = getenv($convert->toMacro());
} catch (CaseConverterException $e) {
// This should not happen.
throw new ConfigException('Could not parse option name: ' . $prop, $e->getCode(), $e);
}
if ($env) { if ($env) {
$this->$prop = Yaml::parse($env); $this->$prop = Yaml::parse($env);
} }
@ -273,7 +274,7 @@ class Config
* *
* @param string $file Path to the YAML file * @param string $file Path to the YAML file
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public static function setFile($file) public static function setFile($file)
{ {
@ -282,7 +283,7 @@ class Config
self::$instance = new self($options); self::$instance = new self($options);
self::$instance->validateOptions(); self::$instance->validateOptions();
} else { } else {
throw new Exception("Can't find config file at " . $file); throw new ConfigException("Can't find config file at " . $file);
} }
} }
@ -292,7 +293,7 @@ class Config
* @param mixed[] $options Options (see `config/config.example.yml` for available options) * @param mixed[] $options Options (see `config/config.example.yml` for available options)
* @param bool $update True to update an existing instance * @param bool $update True to update an existing instance
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public static function setOptions(array $options, $update = true) public static function setOptions(array $options, $update = true)
{ {
@ -314,4 +315,21 @@ class Config
{ {
self::$instance = null; self::$instance = null;
} }
/**
* Return a downloader object with the current config.
*
* @return Downloader
*/
public function getDownloader()
{
return new Downloader(
$this->youtubedl,
$this->params,
$this->python,
$this->avconv,
$this->phantomjsDir,
$this->avconvVerbosity
);
}
} }

View file

@ -48,7 +48,6 @@ class UglyRouter extends Router
* *
* @return string * @return string
* @throws InvalidArgumentException If required data not provided * @throws InvalidArgumentException If required data not provided
*
* @throws RuntimeException If named route does not exist * @throws RuntimeException If named route does not exist
*/ */
public function pathFor($name, array $data = [], array $queryParams = []) public function pathFor($name, array $data = [], array $queryParams = [])

View file

@ -1,647 +0,0 @@
<?php
/**
* VideoDownload class.
*/
namespace Alltube;
use Alltube\Exception\EmptyUrlException;
use Alltube\Exception\PasswordException;
use Exception;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
use stdClass;
use Symfony\Component\Process\Process;
/**
* Extract info about videos.
*
* Due to the way youtube-dl behaves, this class can also contain information about a playlist.
*
* @property-read string $title Title
* @property-read string $protocol Network protocol (HTTP, RTMP, etc.)
* @property-read string $url File URL
* @property-read string $ext File extension
* @property-read string $extractor_key youtube-dl extractor class used
* @property-read array $entries List of videos (if the object contains information about a playlist)
* @property-read array $rtmp_conn
* @property-read string|null $_type Object type (usually "playlist" or null)
* @property-read stdClass $downloader_options
* @property-read stdClass $http_headers
*/
class Video
{
/**
* Config instance.
*
* @var Config
*/
private $config;
/**
* URL of the page containing the video.
*
* @var string
*/
private $webpageUrl;
/**
* Requested video format.
*
* @var string
*/
private $requestedFormat;
/**
* Password.
*
* @var string|null
*/
private $password;
/**
* JSON object returned by youtube-dl.
*
* @var stdClass
*/
private $json;
/**
* URLs of the video files.
*
* @var string[]
*/
private $urls;
/**
* LocaleManager instance.
*
* @var LocaleManager
*/
protected $localeManager;
/**
* VideoDownload constructor.
*
* @param string $webpageUrl URL of the page containing the video
* @param string $requestedFormat Requested video format
* (can be any format string accepted by youtube-dl,
* including selectors like "[height<=720]")
* @param string $password Password
*/
public function __construct($webpageUrl, $requestedFormat = 'best/bestvideo', $password = null)
{
$this->webpageUrl = $webpageUrl;
$this->requestedFormat = $requestedFormat;
$this->password = $password;
$this->config = Config::getInstance();
$this->localeManager = LocaleManager::getInstance();
}
/**
* Return a youtube-dl process with the specified arguments.
*
* @param string[] $arguments Arguments
*
* @return Process<string>
*/
private static function getProcess(array $arguments)
{
$config = Config::getInstance();
return new Process(
array_merge(
[$config->python, $config->youtubedl],
$config->params,
$arguments
)
);
}
/**
* List all extractors.
*
* @return string[] Extractors
*
* @throws PasswordException
*/
public static function getExtractors()
{
$video = new self('');
return explode("\n", trim($video->callYoutubedl(['--list-extractors'])));
}
/**
* Call youtube-dl.
*
* @param string[] $arguments Arguments
*
* @return string Result
* @throws Exception If the password is wrong
* @throws Exception If youtube-dl returns an error
*
* @throws PasswordException If the video is protected by a password and no password was specified
*/
private function callYoutubedl(array $arguments)
{
$config = Config::getInstance();
$process = self::getProcess($arguments);
//This is needed by the openload extractor because it runs PhantomJS
$process->setEnv(['PATH' => $config->phantomjsDir]);
$process->run();
if (!$process->isSuccessful()) {
$errorOutput = trim($process->getErrorOutput());
$exitCode = intval($process->getExitCode());
if ($errorOutput == 'ERROR: This video is protected by a password, use the --video-password option') {
throw new PasswordException($errorOutput, $exitCode);
} elseif (substr($errorOutput, 0, 21) == 'ERROR: Wrong password') {
throw new Exception($this->localeManager->t('Wrong password'), $exitCode);
} else {
throw new Exception($errorOutput, $exitCode);
}
} else {
return trim($process->getOutput());
}
}
/**
* Get a property from youtube-dl.
*
* @param string $prop Property
*
* @return string
* @throws PasswordException
*/
private function getProp($prop = 'dump-json')
{
$arguments = ['--' . $prop];
if (isset($this->webpageUrl)) {
$arguments[] = $this->webpageUrl;
}
if (isset($this->requestedFormat)) {
$arguments[] = '-f';
$arguments[] = $this->requestedFormat;
}
if (isset($this->password)) {
$arguments[] = '--video-password';
$arguments[] = $this->password;
}
return $this->callYoutubedl($arguments);
}
/**
* Get all information about a video.
*
* @return stdClass Decoded JSON
*
* @throws PasswordException
*/
public function getJson()
{
if (!isset($this->json)) {
$this->json = json_decode($this->getProp('dump-single-json'));
}
return $this->json;
}
/**
* Magic method to get a property from the JSON object returned by youtube-dl.
*
* @param string $name Property
*
* @return mixed
* @throws PasswordException
*/
public function __get($name)
{
if (isset($this->$name)) {
return $this->getJson()->$name;
}
return null;
}
/**
* Magic method to check if the JSON object returned by youtube-dl has a property.
*
* @param string $name Property
*
* @return bool
* @throws PasswordException
*/
public function __isset($name)
{
return isset($this->getJson()->$name);
}
/**
* Get URL of video from URL of page.
*
* It generally returns only one URL.
* But it can return two URLs when multiple formats are specified
* (eg. bestvideo+bestaudio).
*
* @return string[] URLs of video
* @throws EmptyUrlException
* @throws PasswordException
*/
public function getUrl()
{
// Cache the URLs.
if (!isset($this->urls)) {
$this->urls = explode("\n", $this->getProp('get-url'));
if (empty($this->urls[0])) {
throw new EmptyUrlException($this->localeManager->t('youtube-dl returned an empty URL.'));
}
}
return $this->urls;
}
/**
* Get filename of video file from URL of page.
*
* @return string Filename of extracted video
*
* @throws PasswordException
*/
public function getFilename()
{
return trim($this->getProp('get-filename'));
}
/**
* Get filename of video with the specified extension.
*
* @param string $extension New file extension
*
* @return string Filename of extracted video with specified extension
* @throws PasswordException
*/
public function getFileNameWithExtension($extension)
{
return str_replace('.' . $this->ext, '.' . $extension, $this->getFilename());
}
/**
* Return arguments used to run rtmp for a specific video.
*
* @return string[] Arguments
*/
private function getRtmpArguments()
{
$arguments = [];
if ($this->protocol == 'rtmp') {
foreach (
[
'url' => '-rtmp_tcurl',
'webpage_url' => '-rtmp_pageurl',
'player_url' => '-rtmp_swfverify',
'flash_version' => '-rtmp_flashver',
'play_path' => '-rtmp_playpath',
'app' => '-rtmp_app',
] as $property => $option
) {
if (isset($this->{$property})) {
$arguments[] = $option;
$arguments[] = $this->{$property};
}
}
if (isset($this->rtmp_conn)) {
foreach ($this->rtmp_conn as $conn) {
$arguments[] = '-rtmp_conn';
$arguments[] = $conn;
}
}
}
return $arguments;
}
/**
* Check if a command runs successfully.
*
* @param string[] $command Command and arguments
*
* @return bool False if the command returns an error, true otherwise
*/
public static function checkCommand(array $command)
{
$process = new Process($command);
$process->run();
return $process->isSuccessful();
}
/**
* Get a process that runs avconv in order to convert a video.
*
* @param int $audioBitrate Audio bitrate of the converted file
* @param string $filetype Filetype of the converted file
* @param bool $audioOnly True to return an audio-only file
* @param string $from Start the conversion at this time
* @param string $to End the conversion at this time
*
* @return Process<string> Process
* @throws Exception If avconv/ffmpeg is missing
*
*/
private function getAvconvProcess(
$audioBitrate,
$filetype = 'mp3',
$audioOnly = true,
$from = null,
$to = null
) {
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw new Exception(
$this->localeManager->t(
"Can't find avconv or ffmpeg at @path.",
['@path' => $this->config->avconv]
)
);
}
$durationRegex = '/(\d+:)?(\d+:)?(\d+)/';
$afterArguments = [];
if ($audioOnly) {
$afterArguments[] = '-vn';
}
if (!empty($from)) {
if (!preg_match($durationRegex, $from)) {
throw new Exception($this->localeManager->t('Invalid start time: @from.', ['@from' => $from]));
}
$afterArguments[] = '-ss';
$afterArguments[] = $from;
}
if (!empty($to)) {
if (!preg_match($durationRegex, $to)) {
throw new Exception($this->localeManager->t('Invalid end time: @to.', ['@to' => $to]));
}
$afterArguments[] = '-to';
$afterArguments[] = $to;
}
$urls = $this->getUrl();
$arguments = array_merge(
[
$this->config->avconv,
'-v', $this->config->avconvVerbosity,
],
$this->getRtmpArguments(),
[
'-i', $urls[0],
'-f', $filetype,
'-b:a', $audioBitrate . 'k',
],
$afterArguments,
[
'pipe:1',
]
);
//Vimeo needs a correct user-agent
$arguments[] = '-user_agent';
$arguments[] = $this->getProp('dump-user-agent');
return new Process($arguments);
}
/**
* Get audio stream of converted video.
*
* @param string $from Start the conversion at this time
* @param string $to End the conversion at this time
*
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @throws Exception If your try to convert an M3U8 video
*/
public function getAudioStream($from = null, $to = null)
{
if (isset($this->_type) && $this->_type == 'playlist') {
throw new Exception($this->localeManager->t('Conversion of playlists is not supported.'));
}
if (isset($this->protocol)) {
if (in_array($this->protocol, ['m3u8', 'm3u8_native'])) {
throw new Exception($this->localeManager->t('Conversion of M3U8 files is not supported.'));
} elseif ($this->protocol == 'http_dash_segments') {
throw new Exception($this->localeManager->t('Conversion of DASH segments is not supported.'));
}
}
$avconvProc = $this->getAvconvProcess($this->config->audioBitrate, 'mp3', true, $from, $to);
$stream = popen($avconvProc->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new Exception($this->localeManager->t('Could not open popen stream.'));
}
return $stream;
}
/**
* Get video stream from an M3U playlist.
*
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @throws Exception If avconv/ffmpeg is missing
*/
public function getM3uStream()
{
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw new Exception(
$this->localeManager->t(
"Can't find avconv or ffmpeg at @path.",
['@path' => $this->config->avconv]
)
);
}
$urls = $this->getUrl();
$process = new Process(
[
$this->config->avconv,
'-v', $this->config->avconvVerbosity,
'-i', $urls[0],
'-f', $this->ext,
'-c', 'copy',
'-bsf:a', 'aac_adtstoasc',
'-movflags', 'frag_keyframe+empty_moov',
'pipe:1',
]
);
$stream = popen($process->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new Exception($this->localeManager->t('Could not open popen stream.'));
}
return $stream;
}
/**
* Get an avconv stream to remux audio and video.
*
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
*/
public function getRemuxStream()
{
$urls = $this->getUrl();
if (!isset($urls[0]) || !isset($urls[1])) {
throw new Exception($this->localeManager->t('This video does not have two URLs.'));
}
$process = new Process(
[
$this->config->avconv,
'-v', $this->config->avconvVerbosity,
'-i', $urls[0],
'-i', $urls[1],
'-c', 'copy',
'-map', '0:v:0',
'-map', '1:a:0',
'-f', 'matroska',
'pipe:1',
]
);
$stream = popen($process->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new Exception($this->localeManager->t('Could not open popen stream.'));
}
return $stream;
}
/**
* Get video stream from an RTMP video.
*
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
*/
public function getRtmpStream()
{
$urls = $this->getUrl();
$process = new Process(
array_merge(
[
$this->config->avconv,
'-v', $this->config->avconvVerbosity,
],
$this->getRtmpArguments(),
[
'-i', $urls[0],
'-f', $this->ext,
'pipe:1',
]
)
);
$stream = popen($process->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new Exception($this->localeManager->t('Could not open popen stream.'));
}
return $stream;
}
/**
* Get the stream of a converted video.
*
* @param int $audioBitrate Audio bitrate of the converted file
* @param string $filetype Filetype of the converted file
*
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @throws Exception If your try to convert and M3U8 video
*/
public function getConvertedStream($audioBitrate, $filetype)
{
if (in_array($this->protocol, ['m3u8', 'm3u8_native'])) {
throw new Exception($this->localeManager->t('Conversion of M3U8 files is not supported.'));
}
$avconvProc = $this->getAvconvProcess($audioBitrate, $filetype, false);
$stream = popen($avconvProc->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new Exception($this->localeManager->t('Could not open popen stream.'));
}
return $stream;
}
/**
* Get the same video but with another format.
*
* @param string $format New format
*
* @return Video
*/
public function withFormat($format)
{
return new self($this->webpageUrl, $format, $this->password);
}
/**
* Get a HTTP response containing the video.
*
* @param mixed[] $headers HTTP headers of the request
*
* @return ResponseInterface
* @throws EmptyUrlException
* @throws PasswordException
* @link https://github.com/guzzle/guzzle/issues/2640
*/
public function getHttpResponse(array $headers = [])
{
// IDN conversion breaks with Google hosts like https://r3---sn-25glene6.googlevideo.com/.
$client = new Client(['idn_conversion' => false]);
$urls = $this->getUrl();
$stream_context_options = [];
if (array_key_exists('Referer', (array)$this->http_headers)) {
$stream_context_options = [
'http' => [
'header' => 'Referer: ' . $this->http_headers->Referer
]
];
}
return $client->request(
'GET',
$urls[0],
[
'stream' => true,
'stream_context' => $stream_context_options,
'headers' => array_merge((array)$this->http_headers, $headers)
]
);
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace Alltube\Exception;
use Exception;
class ConfigException extends Exception
{
}

View file

@ -1,16 +0,0 @@
<?php
/**
* EmptyUrlException class.
*/
namespace Alltube\Exception;
use Exception;
/**
* Exception thrown when youtube-dl returns an empty URL.
*/
class EmptyUrlException extends Exception
{
}

View file

@ -1,16 +0,0 @@
<?php
/**
* PasswordException class.
*/
namespace Alltube\Exception;
use Exception;
/**
* Exception thrown when a video requires a password.
*/
class PasswordException extends Exception
{
}

View file

@ -6,8 +6,8 @@
namespace Alltube\Stream; namespace Alltube\Stream;
use Alltube\Video; use Alltube\Library\Exception\AlltubeLibraryException;
use Exception; use Alltube\Library\Video;
use Slim\Http\Stream; use Slim\Http\Stream;
/** /**
@ -21,11 +21,11 @@ class ConvertedPlaylistArchiveStream extends PlaylistArchiveStream
* @param Video $video Video to stream * @param Video $video Video to stream
* *
* @return void * @return void
* @throws Exception * @throws AlltubeLibraryException
*/ */
protected function startVideoStream(Video $video) protected function startVideoStream(Video $video)
{ {
$this->curVideoStream = new Stream($video->getAudioStream()); $this->curVideoStream = new Stream($this->downloader->getAudioStream($video));
$this->init_file_stream_transfer( $this->init_file_stream_transfer(
$video->getFileNameWithExtension('mp3'), $video->getFileNameWithExtension('mp3'),

View file

@ -6,9 +6,9 @@
namespace Alltube\Stream; namespace Alltube\Stream;
use Alltube\Exception\EmptyUrlException; use Alltube\Library\Downloader;
use Alltube\Exception\PasswordException; use Alltube\Library\Exception\AlltubeLibraryException;
use Alltube\Video; use Alltube\Library\Video;
use Barracuda\ArchiveStream\ZipArchive; use Barracuda\ArchiveStream\ZipArchive;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
@ -47,22 +47,32 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
*/ */
private $isComplete = false; private $isComplete = false;
/**
* Downloader object.
*
* @var Downloader
*/
protected $downloader;
/** /**
* PlaylistArchiveStream constructor. * PlaylistArchiveStream constructor.
* *
* We don't call the parent constructor because it messes up the output buffering. * We don't call the parent constructor because it messes up the output buffering.
* *
* @param Downloader $downloader Downloader object
* @param Video $video Video/playlist to download * @param Video $video Video/playlist to download
* @noinspection PhpMissingParentConstructorInspection * @noinspection PhpMissingParentConstructorInspection
*/ */
public function __construct(Video $video) public function __construct(Downloader $downloader, Video $video)
{ {
$this->downloader = $downloader;
$buffer = fopen('php://temp', 'r+'); $buffer = fopen('php://temp', 'r+');
if ($buffer !== false) { if ($buffer !== false) {
$this->buffer = $buffer; $this->buffer = $buffer;
} }
foreach ($video->entries as $entry) { foreach ($video->entries as $entry) {
$this->videos[] = new Video($entry->url); $this->videos[] = $downloader->getVideo($entry->url);
} }
} }
@ -244,12 +254,11 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* @param Video $video Video to stream * @param Video $video Video to stream
* *
* @return void * @return void
* @throws PasswordException * @throws AlltubeLibraryException
* @throws EmptyUrlException
*/ */
protected function startVideoStream(Video $video) protected function startVideoStream(Video $video)
{ {
$response = $video->getHttpResponse(); $response = $this->downloader->getHttpResponse($video);
$this->curVideoStream = $response->getBody(); $this->curVideoStream = $response->getBody();
$contentLengthHeaders = $response->getHeader('Content-Length'); $contentLengthHeaders = $response->getHeader('Content-Length');
@ -266,8 +275,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* @param int $count Number of bytes to read * @param int $count Number of bytes to read
* *
* @return string|false * @return string|false
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
*/ */
public function read($count) public function read($count)
{ {

View file

@ -6,9 +6,9 @@
namespace Alltube\Stream; namespace Alltube\Stream;
use Alltube\Exception\EmptyUrlException; use Alltube\Library\Downloader;
use Alltube\Exception\PasswordException; use Alltube\Library\Exception\AlltubeLibraryException;
use Alltube\Video; use Alltube\Library\Video;
use GuzzleHttp\Psr7\AppendStream; use GuzzleHttp\Psr7\AppendStream;
/** /**
@ -20,15 +20,15 @@ class YoutubeStream extends AppendStream
/** /**
* YoutubeStream constructor. * YoutubeStream constructor.
* *
* @param Downloader $downloader Downloader object
* @param Video $video Video to stream * @param Video $video Video to stream
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
*/ */
public function __construct(Video $video) public function __construct(Downloader $downloader, Video $video)
{ {
parent::__construct(); parent::__construct();
$stream = $video->getHttpResponse(); $stream = $downloader->getHttpResponse($video);
$contentLenghtHeader = $stream->getHeader('Content-Length'); $contentLenghtHeader = $stream->getHeader('Content-Length');
$rangeStart = 0; $rangeStart = 0;
@ -37,7 +37,7 @@ class YoutubeStream extends AppendStream
if ($rangeEnd >= $contentLenghtHeader[0]) { if ($rangeEnd >= $contentLenghtHeader[0]) {
$rangeEnd = intval($contentLenghtHeader[0]) - 1; $rangeEnd = intval($contentLenghtHeader[0]) - 1;
} }
$response = $video->getHttpResponse(['Range' => 'bytes=' . $rangeStart . '-' . $rangeEnd]); $response = $downloader->getHttpResponse($video, ['Range' => 'bytes=' . $rangeStart . '-' . $rangeEnd]);
$this->addStream(new YoutubeChunkStream($response)); $this->addStream(new YoutubeChunkStream($response));
$rangeStart = $rangeEnd + 1; $rangeStart = $rangeEnd + 1;
} }

View file

@ -11,16 +11,15 @@
"aura/session": "^2.1", "aura/session": "^2.1",
"barracudanetworks/archivestream-php": "^1.0", "barracudanetworks/archivestream-php": "^1.0",
"ffmpeg/ffmpeg": "^4.1", "ffmpeg/ffmpeg": "^4.1",
"guzzlehttp/guzzle": "^6.5",
"jawira/case-converter": "^3.4", "jawira/case-converter": "^3.4",
"mathmarques/smarty-view": "^1.1", "mathmarques/smarty-view": "^1.1",
"npm-asset/open-sans-fontface": "^1.4", "npm-asset/open-sans-fontface": "^1.4",
"rinvex/countries": "^6.1", "rinvex/countries": "^6.1",
"rudloff/alltube-library": "^0.1.0",
"symfony/finder": "^5.0", "symfony/finder": "^5.0",
"symfony/process": "^4.0",
"symfony/translation": "^4.0", "symfony/translation": "^4.0",
"symfony/yaml": "^4.0", "symfony/yaml": "^4.0",
"ytdl-org/youtube-dl": "^2020.05", "ytdl-org/youtube-dl": "^2020.06",
"zonuexe/http-accept-language": "^0.4.1" "zonuexe/http-accept-language": "^0.4.1"
}, },
"require-dev": { "require-dev": {
@ -35,13 +34,6 @@
"symfony/error-handler": "^5.0", "symfony/error-handler": "^5.0",
"symfony/var-dumper": "^5.0" "symfony/var-dumper": "^5.0"
}, },
"extra": {
"paas": {
"nginx-includes": [
"resources/nginx.conf"
]
}
},
"repositories": [ "repositories": [
{ {
"type": "composer", "type": "composer",
@ -51,10 +43,10 @@
"type": "package", "type": "package",
"package": { "package": {
"name": "ytdl-org/youtube-dl", "name": "ytdl-org/youtube-dl",
"version": "2020.05.08", "version": "2020.06.16.1",
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://github.com/ytdl-org/youtube-dl/archive/2020.05.08.zip" "url": "https://github.com/ytdl-org/youtube-dl/archive/2020.06.16.1.zip"
} }
} }
}, },

81
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5b43d7dd93a519a8edb85623fcffc639", "content-hash": "03b37b613f8ae3881395adf1cbc72b67",
"packages": [ "packages": [
{ {
"name": "anam/phantomjs-linux-x86-binary", "name": "anam/phantomjs-linux-x86-binary",
@ -164,16 +164,16 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "6.5.3", "version": "6.5.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e" "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e", "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -181,7 +181,7 @@
"guzzlehttp/promises": "^1.0", "guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.6.1", "guzzlehttp/psr7": "^1.6.1",
"php": ">=5.5", "php": ">=5.5",
"symfony/polyfill-intl-idn": "^1.11" "symfony/polyfill-intl-idn": "^1.17.0"
}, },
"require-dev": { "require-dev": {
"ext-curl": "*", "ext-curl": "*",
@ -227,7 +227,7 @@
"rest", "rest",
"web service" "web service"
], ],
"time": "2020-04-18T10:38:46+00:00" "time": "2020-06-16T21:01:06+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
@ -790,6 +790,47 @@
], ],
"time": "2020-03-13T18:04:45+00:00" "time": "2020-03-13T18:04:45+00:00"
}, },
{
"name": "rudloff/alltube-library",
"version": "0.1.0",
"source": {
"type": "git",
"url": "https://github.com/Rudloff/alltube-library.git",
"reference": "09b47e0cf3157a79724177d6cadac8cee8cae588"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Rudloff/alltube-library/zipball/09b47e0cf3157a79724177d6cadac8cee8cae588",
"reference": "09b47e0cf3157a79724177d6cadac8cee8cae588",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/guzzle": "^6.5",
"symfony/process": "^4.0|^5.0"
},
"require-dev": {
"phpro/grumphp": "^0.18.0",
"phpstan/phpstan": "^0.12.29",
"roave/security-advisories": "dev-master",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^5.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Alltube\\Library\\": "classes/",
"Alltube\\Library\\Exception\\": "classes/exceptions/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-only"
],
"description": "PHP wrapper for youtube-dl",
"homepage": "http://alltubedownload.net/",
"time": "2020-06-21T12:25:10+00:00"
},
{ {
"name": "slim/slim", "name": "slim/slim",
"version": "3.12.3", "version": "3.12.3",
@ -1146,16 +1187,16 @@
}, },
{ {
"name": "symfony/polyfill-php72", "name": "symfony/polyfill-php72",
"version": "v1.13.1", "version": "v1.17.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php72.git", "url": "https://github.com/symfony/polyfill-php72.git",
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" "reference": "f048e612a3905f34931127360bdd2def19a5e582"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582",
"reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", "reference": "f048e612a3905f34931127360bdd2def19a5e582",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1164,7 +1205,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.13-dev" "dev-master": "1.17-dev"
} }
}, },
"autoload": { "autoload": {
@ -1197,20 +1238,20 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2019-11-27T13:56:44+00:00" "time": "2020-05-12T16:47:27+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v4.4.8", "version": "v4.4.10",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "4b6a9a4013baa65d409153cbb5a895bf093dc7f4" "reference": "c714958428a85c86ab97e3a0c96db4c4f381b7f5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/4b6a9a4013baa65d409153cbb5a895bf093dc7f4", "url": "https://api.github.com/repos/symfony/process/zipball/c714958428a85c86ab97e3a0c96db4c4f381b7f5",
"reference": "4b6a9a4013baa65d409153cbb5a895bf093dc7f4", "reference": "c714958428a85c86ab97e3a0c96db4c4f381b7f5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1246,7 +1287,7 @@
], ],
"description": "Symfony Process Component", "description": "Symfony Process Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2020-04-15T15:56:18+00:00" "time": "2020-05-30T20:06:45+00:00"
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
@ -1442,10 +1483,10 @@
}, },
{ {
"name": "ytdl-org/youtube-dl", "name": "ytdl-org/youtube-dl",
"version": "2020.05.08", "version": "2020.06.16.1",
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://github.com/ytdl-org/youtube-dl/archive/2020.05.08.zip" "url": "https://github.com/ytdl-org/youtube-dl/archive/2020.06.16.1.zip"
}, },
"type": "library" "type": "library"
}, },

View file

@ -7,12 +7,14 @@
namespace Alltube\Controller; namespace Alltube\Controller;
use Alltube\Config; use Alltube\Config;
use Alltube\Library\Downloader;
use Alltube\Library\Video;
use Alltube\LocaleManager; use Alltube\LocaleManager;
use Alltube\SessionManager; use Alltube\SessionManager;
use Alltube\Video;
use Aura\Session\Segment; use Aura\Session\Segment;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response;
/** /**
* Abstract class used by every controller. * Abstract class used by every controller.
@ -61,6 +63,13 @@ abstract class BaseController
*/ */
protected $localeManager; protected $localeManager;
/**
* Downloader instance.
*
* @var Downloader
*/
protected $downloader;
/** /**
* BaseController constructor. * BaseController constructor.
* *
@ -73,6 +82,7 @@ abstract class BaseController
$session = SessionManager::getSession(); $session = SessionManager::getSession();
$this->sessionSegment = $session->getSegment(self::class); $this->sessionSegment = $session->getSegment(self::class);
$this->localeManager = $this->container->get('locale'); $this->localeManager = $this->container->get('locale');
$this->downloader = $this->config->getDownloader();
if (!$this->config->stream) { if (!$this->config->stream) {
// Force HTTP if stream is not enabled. // Force HTTP if stream is not enabled.
@ -117,4 +127,20 @@ abstract class BaseController
return $password; return $password;
} }
/**
* Display an user-friendly error.
*
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
* @param string $message Error message
*
* @return Response HTTP response
*/
protected function displayError(Request $request, Response $response, $message)
{
$controller = new FrontController($this->container);
return $controller->displayError($request, $response, $message);
}
} }

View file

@ -6,13 +6,19 @@
namespace Alltube\Controller; namespace Alltube\Controller;
use Alltube\Exception\EmptyUrlException; use Alltube\Config;
use Alltube\Exception\PasswordException; use Alltube\Library\Exception\EmptyUrlException;
use Alltube\Library\Exception\InvalidProtocolConversionException;
use Alltube\Library\Exception\PasswordException;
use Alltube\Library\Exception\AlltubeLibraryException;
use Alltube\Library\Exception\PlaylistConversionException;
use Alltube\Library\Exception\PopenStreamException;
use Alltube\Library\Exception\RemuxException;
use Alltube\Library\Exception\WrongPasswordException;
use Alltube\Library\Exception\YoutubedlException;
use Alltube\Stream\ConvertedPlaylistArchiveStream; use Alltube\Stream\ConvertedPlaylistArchiveStream;
use Alltube\Stream\PlaylistArchiveStream; use Alltube\Stream\PlaylistArchiveStream;
use Alltube\Stream\YoutubeStream; use Alltube\Stream\YoutubeStream;
use Alltube\Video;
use Exception;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use Slim\Http\Stream; use Slim\Http\Stream;
@ -29,13 +35,14 @@ class DownloadController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException
*/ */
public function download(Request $request, Response $response) public function download(Request $request, Response $response)
{ {
$url = $request->getQueryParam('url'); $url = $request->getQueryParam('url');
if (isset($url)) { if (isset($url)) {
$this->video = new Video($url, $this->getFormat($request), $this->getPassword($request)); $this->video = $this->downloader->getVideo($url, $this->getFormat($request), $this->getPassword($request));
try { try {
if ($this->config->convert && $request->getQueryParam('audio')) { if ($this->config->convert && $request->getQueryParam('audio')) {
@ -49,14 +56,33 @@ class DownloadController extends BaseController
// Regular download. // Regular download.
return $this->getDownloadResponse($request, $response); return $this->getDownloadResponse($request, $response);
} catch (PasswordException $e) { } catch (PasswordException $e) {
return $response->withRedirect( $frontController = new FrontController($this->container);
$this->container->get('router')->pathFor('info') .
'?' . http_build_query($request->getQueryParams())
);
} catch (Exception $e) {
$response->getBody()->write($e->getMessage());
return $response->withHeader('Content-Type', 'text/plain')->withStatus(500); return $frontController->password($request, $response);
} catch (WrongPasswordException $e) {
return $this->displayError($request, $response, $this->localeManager->t('Wrong password'));
} catch (PlaylistConversionException $e) {
return $this->displayError(
$request,
$response,
$this->localeManager->t('Conversion of playlists is not supported.')
);
} catch (InvalidProtocolConversionException $e) {
if (in_array($this->video->protocol, ['m3u8', 'm3u8_native'])) {
return $this->displayError(
$request,
$response,
$this->localeManager->t('Conversion of M3U8 files is not supported.')
);
} elseif ($this->video->protocol == 'http_dash_segments') {
return $this->displayError(
$request,
$response,
$this->localeManager->t('Conversion of DASH segments is not supported.')
);
} else {
throw $e;
}
} }
} else { } else {
return $response->withRedirect($this->container->get('router')->pathFor('index')); return $response->withRedirect($this->container->get('router')->pathFor('index'));
@ -70,8 +96,7 @@ class DownloadController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws PasswordException * @throws AlltubeLibraryException
* @throws Exception
*/ */
private function getConvertedAudioResponse(Request $request, Response $response) private function getConvertedAudioResponse(Request $request, Response $response)
{ {
@ -86,13 +111,7 @@ class DownloadController extends BaseController
$response = $response->withHeader('Content-Type', 'audio/mpeg'); $response = $response->withHeader('Content-Type', 'audio/mpeg');
if ($request->isGet() || $request->isPost()) { if ($request->isGet() || $request->isPost()) {
try { $process = $this->downloader->getAudioStream($this->video, $this->config->audioBitrate, $from, $to);
$process = $this->video->getAudioStream($from, $to);
} catch (Exception $e) {
// Fallback to default format.
$this->video = $this->video->withFormat($this->defaultFormat);
$process = $this->video->getAudioStream($from, $to);
}
$response = $response->withBody(new Stream($process)); $response = $response->withBody(new Stream($process));
} }
@ -106,38 +125,40 @@ class DownloadController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException
* @throws EmptyUrlException
* @throws PasswordException * @throws PasswordException
* @throws WrongPasswordException
*/ */
private function getAudioResponse(Request $request, Response $response) private function getAudioResponse(Request $request, Response $response)
{ {
if (!empty($request->getQueryParam('from')) || !empty($request->getQueryParam('to'))) {
// Force convert when we need to seek.
$this->video = $this->video->withFormat('bestaudio/' . $this->defaultFormat);
return $this->getConvertedAudioResponse($request, $response);
} else {
try { try {
// First, we try to get a MP3 file directly. // First, we try to get a MP3 file directly.
if (!empty($request->getQueryParam('from')) || !empty($request->getQueryParam('to'))) {
throw new Exception('Force convert when we need to seek.');
}
if ($this->config->stream) { if ($this->config->stream) {
$this->video = $this->video->withFormat('mp3'); $this->video = $this->video->withFormat('mp3');
return $this->getStream($request, $response); return $this->getStream($request, $response);
} else { } else {
$this->video = $this->video->withFormat('mp3[protocol=https]/mp3[protocol=http]'); $this->video = $this->video->withFormat(Config::addHttpToFormat('mp3'));
$urls = $this->video->getUrl(); $urls = $this->video->getUrl();
return $response->withRedirect($urls[0]); return $response->withRedirect($urls[0]);
} }
} catch (PasswordException $e) { } catch (YoutubedlException $e) {
$frontController = new FrontController($this->container);
return $frontController->password($request, $response);
} catch (Exception $e) {
// If MP3 is not available, we convert it. // If MP3 is not available, we convert it.
$this->video = $this->video->withFormat('bestaudio/best'); $this->video = $this->video->withFormat('bestaudio/' . $this->defaultFormat);
return $this->getConvertedAudioResponse($request, $response); return $this->getConvertedAudioResponse($request, $response);
} }
} }
}
/** /**
* Get a video/audio stream piped through the server. * Get a video/audio stream piped through the server.
@ -146,17 +167,15 @@ class DownloadController extends BaseController
* *
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* @return Response HTTP response * @return Response HTTP response
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
* @throws Exception
*/ */
private function getStream(Request $request, Response $response) private function getStream(Request $request, Response $response)
{ {
if (isset($this->video->entries)) { if (isset($this->video->entries)) {
if ($this->config->convert && $request->getQueryParam('audio')) { if ($this->config->convert && $request->getQueryParam('audio')) {
$stream = new ConvertedPlaylistArchiveStream($this->video); $stream = new ConvertedPlaylistArchiveStream($this->downloader, $this->video);
} else { } else {
$stream = new PlaylistArchiveStream($this->video); $stream = new PlaylistArchiveStream($this->downloader, $this->video);
} }
$response = $response->withHeader('Content-Type', 'application/zip'); $response = $response->withHeader('Content-Type', 'application/zip');
$response = $response->withHeader( $response = $response->withHeader(
@ -167,10 +186,10 @@ class DownloadController extends BaseController
return $response->withBody($stream); return $response->withBody($stream);
} elseif ($this->video->protocol == 'rtmp') { } elseif ($this->video->protocol == 'rtmp') {
$response = $response->withHeader('Content-Type', 'video/' . $this->video->ext); $response = $response->withHeader('Content-Type', 'video/' . $this->video->ext);
$body = new Stream($this->video->getRtmpStream()); $body = new Stream($this->downloader->getRtmpStream($this->video));
} elseif ($this->video->protocol == 'm3u8' || $this->video->protocol == 'm3u8_native') { } elseif ($this->video->protocol == 'm3u8' || $this->video->protocol == 'm3u8_native') {
$response = $response->withHeader('Content-Type', 'video/' . $this->video->ext); $response = $response->withHeader('Content-Type', 'video/' . $this->video->ext);
$body = new Stream($this->video->getM3uStream()); $body = new Stream($this->downloader->getM3uStream($this->video));
} else { } else {
$headers = []; $headers = [];
$range = $request->getHeader('Range'); $range = $request->getHeader('Range');
@ -178,7 +197,7 @@ class DownloadController extends BaseController
if (!empty($range)) { if (!empty($range)) {
$headers['Range'] = $range; $headers['Range'] = $range;
} }
$stream = $this->video->getHttpResponse($headers); $stream = $this->downloader->getHttpResponse($this->video, $headers);
$response = $response->withHeader('Content-Type', $stream->getHeader('Content-Type')); $response = $response->withHeader('Content-Type', $stream->getHeader('Content-Type'));
$response = $response->withHeader('Content-Length', $stream->getHeader('Content-Length')); $response = $response->withHeader('Content-Length', $stream->getHeader('Content-Length'));
@ -190,7 +209,7 @@ class DownloadController extends BaseController
if (isset($this->video->downloader_options->http_chunk_size)) { if (isset($this->video->downloader_options->http_chunk_size)) {
// Workaround for Youtube throttling the download speed. // Workaround for Youtube throttling the download speed.
$body = new YoutubeStream($this->video); $body = new YoutubeStream($this->downloader, $this->video);
} else { } else {
$body = $stream->getBody(); $body = $stream->getBody();
} }
@ -210,19 +229,18 @@ class DownloadController extends BaseController
/** /**
* Get a remuxed stream piped through the server. * Get a remuxed stream piped through the server.
* *
* @param Response $response PSR-7 response
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* *
* @param Response $response PSR-7 response
* @return Response HTTP response * @return Response HTTP response
* @throws PasswordException * @throws AlltubeLibraryException
* @throws Exception
*/ */
private function getRemuxStream(Request $request, Response $response) private function getRemuxStream(Request $request, Response $response)
{ {
if (!$this->config->remux) { if (!$this->config->remux) {
throw new Exception($this->localeManager->t('You need to enable remux mode to merge two formats.')); throw new RemuxException('You need to enable remux mode to merge two formats.');
} }
$stream = $this->video->getRemuxStream(); $stream = $this->downloader->getRemuxStream($this->video);
$response = $response->withHeader('Content-Type', 'video/x-matroska'); $response = $response->withHeader('Content-Type', 'video/x-matroska');
if ($request->isGet()) { if ($request->isGet()) {
$response = $response->withBody(new Stream($stream)); $response = $response->withBody(new Stream($stream));
@ -242,9 +260,7 @@ class DownloadController extends BaseController
* *
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* @return Response HTTP response * @return Response HTTP response
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
* @throws Exception
*/ */
private function getDownloadResponse(Request $request, Response $response) private function getDownloadResponse(Request $request, Response $response)
{ {
@ -263,7 +279,7 @@ class DownloadController extends BaseController
return $this->getStream($request, $response); return $this->getStream($request, $response);
} else { } else {
if (empty($videoUrls[0])) { if (empty($videoUrls[0])) {
throw new Exception($this->localeManager->t("Can't find URL of video.")); throw new EmptyUrlException("Can't find URL of video.");
} }
return $response->withRedirect($videoUrls[0]); return $response->withRedirect($videoUrls[0]);
@ -277,8 +293,13 @@ class DownloadController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException
* @throws InvalidProtocolConversionException
* @throws PasswordException * @throws PasswordException
* @throws Exception * @throws PlaylistConversionException
* @throws WrongPasswordException
* @throws YoutubedlException
* @throws PopenStreamException
*/ */
private function getConvertedResponse(Request $request, Response $response) private function getConvertedResponse(Request $request, Response $response)
{ {
@ -290,7 +311,8 @@ class DownloadController extends BaseController
$response = $response->withHeader('Content-Type', 'video/' . $request->getQueryParam('customFormat')); $response = $response->withHeader('Content-Type', 'video/' . $request->getQueryParam('customFormat'));
if ($request->isGet() || $request->isPost()) { if ($request->isGet() || $request->isPost()) {
$process = $this->video->getConvertedStream( $process = $this->downloader->getConvertedStream(
$this->video,
$request->getQueryParam('customBitrate'), $request->getQueryParam('customBitrate'),
$request->getQueryParam('customFormat') $request->getQueryParam('customFormat')
); );

View file

@ -6,12 +6,13 @@
namespace Alltube\Controller; namespace Alltube\Controller;
use Alltube\Exception\PasswordException; use Alltube\Library\Exception\PasswordException;
use Alltube\Library\Exception\AlltubeLibraryException;
use Alltube\Library\Exception\WrongPasswordException;
use Alltube\Locale; use Alltube\Locale;
use Alltube\Video; use Exception;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Throwable; use Throwable;
use Exception;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -94,7 +95,7 @@ class FrontController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws PasswordException * @throws AlltubeLibraryException
*/ */
public function extractors(Request $request, Response $response) public function extractors(Request $request, Response $response)
{ {
@ -103,7 +104,7 @@ class FrontController extends BaseController
'extractors.tpl', 'extractors.tpl',
[ [
'config' => $this->config, 'config' => $this->config,
'extractors' => Video::getExtractors(), 'extractors' => $this->downloader->getExtractors(),
'class' => 'extractors', 'class' => 'extractors',
'title' => $this->localeManager->t('Supported websites'), 'title' => $this->localeManager->t('Supported websites'),
'description' => $this->localeManager->t('List of all supported websites from which Alltube Download ' . 'description' => $this->localeManager->t('List of all supported websites from which Alltube Download ' .
@ -141,7 +142,7 @@ class FrontController extends BaseController
] ]
); );
return $response; return $response->withStatus(403);
} }
/** /**
@ -151,6 +152,7 @@ class FrontController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException
*/ */
private function getInfoResponse(Request $request, Response $response) private function getInfoResponse(Request $request, Response $response)
{ {
@ -158,6 +160,8 @@ class FrontController extends BaseController
$this->video->getJson(); $this->video->getJson();
} catch (PasswordException $e) { } catch (PasswordException $e) {
return $this->password($request, $response); return $this->password($request, $response);
} catch (WrongPasswordException $e) {
return $this->displayError($request, $response, $this->localeManager->t('Wrong password'));
} }
if (isset($this->video->entries)) { if (isset($this->video->entries)) {
@ -205,13 +209,14 @@ class FrontController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException
*/ */
public function info(Request $request, Response $response) public function info(Request $request, Response $response)
{ {
$url = $request->getQueryParam('url') ?: $request->getQueryParam('v'); $url = $request->getQueryParam('url') ?: $request->getQueryParam('v');
if (isset($url) && !empty($url)) { if (isset($url) && !empty($url)) {
$this->video = new Video($url, $this->getFormat($request), $this->getPassword($request)); $this->video = $this->downloader->getVideo($url, $this->getFormat($request), $this->getPassword($request));
if ($this->config->convert && $request->getQueryParam('audio')) { if ($this->config->convert && $request->getQueryParam('audio')) {
// We skip the info page and get directly to the download. // We skip the info page and get directly to the download.
@ -228,29 +233,16 @@ class FrontController extends BaseController
} }
/** /**
* Display an error page. * Display an user-friendly error.
* *
* @param Request $request PSR-7 request * @param Request $request PSR-7 request
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* @param Throwable $error Error to display * @param string $message Error message
* *
* @return Response HTTP response * @return Response HTTP response
*/ */
public function error(Request $request, Response $response, Throwable $error) protected function displayError(Request $request, Response $response, $message)
{ {
if ($this->config->debug) {
$renderer = new HtmlErrorRenderer(true);
$exception = $renderer->render($error);
$response->getBody()->write($exception->getAsString());
return $response->withStatus($exception->getStatusCode());
} else {
if ($error instanceof Exception) {
$message = $error->getMessage();
} else {
$message = '';
}
$this->view->render( $this->view->render(
$response, $response,
'error.tpl', 'error.tpl',
@ -266,6 +258,37 @@ class FrontController extends BaseController
return $response->withStatus(500); return $response->withStatus(500);
} }
/**
* Display an error page.
*
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
* @param Throwable $error Error to display
*
* @return Response HTTP response
*/
public function error(Request $request, Response $response, Throwable $error)
{
if ($this->config->debug) {
$renderer = new HtmlErrorRenderer(true);
$exception = $renderer->render($error);
$response->getBody()->write($exception->getAsString());
foreach ($exception->getHeaders() as $header => $value) {
$response = $response->withHeader($header, $value);
}
return $response->withStatus($exception->getStatusCode());
} else {
if ($error instanceof Exception) {
$message = $error->getMessage();
} else {
$message = '';
}
return $this->displayError($request, $response, $message);
}
} }
/** /**

View file

@ -6,8 +6,7 @@
namespace Alltube\Controller; namespace Alltube\Controller;
use Alltube\Video; use Alltube\Library\Exception\AlltubeLibraryException;
use Exception;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
@ -23,20 +22,20 @@ class JsonController extends BaseController
* @param Response $response PSR-7 response * @param Response $response PSR-7 response
* *
* @return Response HTTP response * @return Response HTTP response
* @throws AlltubeLibraryException
*/ */
public function json(Request $request, Response $response) public function json(Request $request, Response $response)
{ {
$url = $request->getQueryParam('url'); $url = $request->getQueryParam('url');
if (isset($url)) { if (isset($url)) {
try { $this->video = $this->downloader->getVideo(
$this->video = new Video($url, $this->getFormat($request), $this->getPassword($request)); $url,
$this->getFormat($request),
$this->getPassword($request)
);
return $response->withJson($this->video->getJson()); return $response->withJson($this->video->getJson());
} catch (Exception $e) {
return $response->withJson(['error' => $e->getMessage()])
->withStatus(500);
}
} else { } else {
return $response->withJson(['error' => 'You need to provide the url parameter']) return $response->withJson(['error' => 'You need to provide the url parameter'])
->withStatus(400); ->withStatus(400);

View file

@ -1,27 +1,6 @@
msgid "" msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n" msgstr "Content-Type: text/plain; charset=UTF-8\n"
#: templates/playlist.tpl:13
msgid "Videos extracted from @title:"
msgstr ""
#: templates/playlist.tpl:38 templates/password.tpl:11 templates/index.tpl:19
#: templates/info.tpl:98
msgid "Download"
msgstr ""
#: templates/playlist.tpl:39
msgid "More options"
msgstr ""
#: templates/inc/header.tpl:4
msgid "Switch language"
msgstr ""
#: templates/inc/header.tpl:8
msgid "Set language"
msgstr ""
#: templates/inc/footer.tpl:8 #: templates/inc/footer.tpl:8
msgid "Code by @dev" msgid "Code by @dev"
msgstr "" msgstr ""
@ -46,6 +25,87 @@ msgstr ""
msgid "Donate" msgid "Donate"
msgstr "" msgstr ""
#: templates/inc/header.tpl:4
msgid "Switch language"
msgstr ""
#: templates/inc/header.tpl:8
msgid "Set language"
msgstr ""
#: templates/info.tpl:11
msgid "You are going to download @title."
msgstr ""
#: templates/info.tpl:29
msgid "Available formats:"
msgstr ""
#: templates/info.tpl:31
msgid "Generic formats"
msgstr ""
#: templates/info.tpl:36
msgid "Detailed formats"
msgstr ""
#: templates/info.tpl:80
msgid "Stream the video through the server"
msgstr ""
#: templates/info.tpl:86
msgid "Convert into a custom format:"
msgstr ""
#: templates/info.tpl:87
msgid "Custom format"
msgstr ""
#: templates/info.tpl:87
msgid "Format to convert to"
msgstr ""
#: templates/info.tpl:92
msgid "with"
msgstr ""
#: templates/info.tpl:93
msgid "Bit rate"
msgstr ""
#: templates/info.tpl:94
msgid "Custom bitrate"
msgstr ""
#: templates/info.tpl:97
msgid "kbit/s audio"
msgstr ""
#: templates/info.tpl:101 templates/playlist.tpl:38 templates/password.tpl:11
#: templates/index.tpl:19
msgid "Download"
msgstr ""
#: templates/playlist.tpl:12
msgid "Videos extracted from @title:"
msgstr ""
#: templates/playlist.tpl:39
msgid "More options"
msgstr ""
#: templates/extractors.tpl:4 controllers/FrontController.php:109
msgid "Supported websites"
msgstr ""
#: templates/error.tpl:5
msgid "An error occurred"
msgstr ""
#: templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr ""
#: templates/password.tpl:5 #: templates/password.tpl:5
msgid "This video is protected" msgid "This video is protected"
msgstr "" msgstr ""
@ -70,144 +130,51 @@ msgstr ""
msgid "From" msgid "From"
msgstr "" msgstr ""
#: templates/index.tpl:29 #: templates/index.tpl:31
msgid "to" msgid "to"
msgstr "" msgstr ""
#: templates/index.tpl:36 #: templates/index.tpl:39
msgid "See all supported websites" msgid "See all supported websites"
msgstr "" msgstr ""
#: templates/index.tpl:38 #: templates/index.tpl:41
msgid "Drag this to your bookmarks bar:" msgid "Drag this to your bookmarks bar:"
msgstr "" msgstr ""
#: templates/index.tpl:39 #: templates/index.tpl:43
msgid "Bookmarklet" msgid "Bookmarklet"
msgstr "" msgstr ""
#: templates/info.tpl:13 #: classes/Config.php:156
msgid "You are going to download @title."
msgstr ""
#: templates/info.tpl:31
msgid "Available formats:"
msgstr ""
#: templates/info.tpl:33
msgid "Generic formats"
msgstr ""
#: templates/info.tpl:38
msgid "Detailed formats"
msgstr ""
#: templates/info.tpl:80
msgid "Stream the video through the server"
msgstr ""
#: templates/info.tpl:85
msgid "Convert into a custom format:"
msgstr ""
#: templates/info.tpl:86
msgid "Custom format"
msgstr ""
#: templates/info.tpl:86
msgid "Format to convert to"
msgstr ""
#: templates/info.tpl:91
msgid "with"
msgstr ""
#: templates/info.tpl:92
msgid "Bit rate"
msgstr ""
#: templates/info.tpl:93
msgid "Custom bitrate"
msgstr ""
#: templates/info.tpl:95
msgid "kbit/s audio"
msgstr ""
#: templates/error.tpl:5
msgid "An error occurred"
msgstr ""
#: templates/error.tpl:6
msgid "Please check the URL of your video."
msgstr ""
#: templates/extractors.tpl:4 controllers/FrontController.php:109
msgid "Supported websites"
msgstr ""
#: classes/Config.php:158
msgid "Best" msgid "Best"
msgstr "" msgstr ""
#: classes/Config.php:159 #: classes/Config.php:157
msgid "Remux best video with best audio" msgid "Remux best video with best audio"
msgstr "" msgstr ""
#: classes/Config.php:160 #: classes/Config.php:158
msgid "Worst" msgid "Worst"
msgstr "" msgstr ""
#: classes/Video.php:159 #: controllers/DownloadController.php:63 controllers/FrontController.php:164
msgid "Wrong password" msgid "Wrong password"
msgstr "" msgstr ""
#: classes/Video.php:250 #: controllers/DownloadController.php:68
msgid "youtube-dl returned an empty URL."
msgstr ""
#: classes/Video.php:361 classes/Video.php:465
msgid "Can't find avconv or ffmpeg at @path."
msgstr ""
#: classes/Video.php:377
msgid "Invalid start time: @from."
msgstr ""
#: classes/Video.php:384
msgid "Invalid end time: @to."
msgstr ""
#: classes/Video.php:430
msgid "Conversion of playlists is not supported." msgid "Conversion of playlists is not supported."
msgstr "" msgstr ""
#: classes/Video.php:435 classes/Video.php:578 #: controllers/DownloadController.php:75
msgid "Conversion of M3U8 files is not supported." msgid "Conversion of M3U8 files is not supported."
msgstr "" msgstr ""
#: classes/Video.php:437 #: controllers/DownloadController.php:81
msgid "Conversion of DASH segments is not supported." msgid "Conversion of DASH segments is not supported."
msgstr "" msgstr ""
#: classes/Video.php:446 classes/Video.php:488 classes/Video.php:525 #: controllers/FrontController.php:63
#: classes/Video.php:558 classes/Video.php:586
msgid "Could not open popen stream."
msgstr ""
#: classes/Video.php:506
msgid "This video does not have two URLs."
msgstr ""
#: controllers/DownloadController.php:215
msgid "You need to enable remux mode to merge two formats."
msgstr ""
#: controllers/DownloadController.php:255
msgid "Can't find URL of video."
msgstr ""
#: controllers/FrontController.php:64
msgid "" msgid ""
"Easily download videos from Youtube, Dailymotion, Vimeo and other websites." "Easily download videos from Youtube, Dailymotion, Vimeo and other websites."
msgstr "" msgstr ""
@ -227,18 +194,18 @@ msgid ""
"You need a password in order to download this video with Alltube Download" "You need a password in order to download this video with Alltube Download"
msgstr "" msgstr ""
#: controllers/FrontController.php:169 #: controllers/FrontController.php:172
msgid "Video download" msgid "Video download"
msgstr "" msgstr ""
#: controllers/FrontController.php:171 #: controllers/FrontController.php:174
msgid "Download video from @extractor" msgid "Download video from @extractor"
msgstr "" msgstr ""
#: controllers/FrontController.php:177 #: controllers/FrontController.php:180
msgid "Download @title from @extractor" msgid "Download @title from @extractor"
msgstr "" msgstr ""
#: controllers/FrontController.php:253 controllers/FrontController.php:284 #: controllers/FrontController.php:253
msgid "Error" msgid "Error"
msgstr "" msgstr ""

View file

@ -4,6 +4,6 @@
{include file="inc/logo.tpl"} {include file="inc/logo.tpl"}
<h2>{t}An error occurred{/t}</h2> <h2>{t}An error occurred{/t}</h2>
{t}Please check the URL of your video.{/t} {t}Please check the URL of your video.{/t}
<p><i>{$error|escape}</i></p> <p><i>{$error|escape|nl2br}</i></p>
</main> </main>
{include file='inc/footer.tpl'} {include file='inc/footer.tpl'}

View file

@ -7,7 +7,7 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config; use Alltube\Config;
use Exception; use Alltube\Exception\ConfigException;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
/** /**
@ -33,7 +33,7 @@ abstract class BaseTest extends TestCase
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException
*/ */
protected function setUp(): void protected function setUp(): void
{ {

View file

@ -7,7 +7,7 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config; use Alltube\Config;
use Exception; use Alltube\Exception\ConfigException;
/** /**
* Unit tests for the Config class. * Unit tests for the Config class.
@ -23,7 +23,7 @@ class ConfigTest extends BaseTest
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
@ -82,7 +82,7 @@ class ConfigTest extends BaseTest
* Test the setFile function. * Test the setFile function.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testSetFile() public function testSetFile()
{ {
@ -97,7 +97,7 @@ class ConfigTest extends BaseTest
*/ */
public function testSetFileWithMissingFile() public function testSetFileWithMissingFile()
{ {
$this->expectException(Exception::class); $this->expectException(ConfigException::class);
Config::setFile('foo'); Config::setFile('foo');
} }
@ -105,7 +105,7 @@ class ConfigTest extends BaseTest
* Test the setOptions function. * Test the setOptions function.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testSetOptions() public function testSetOptions()
{ {
@ -118,7 +118,7 @@ class ConfigTest extends BaseTest
* Test the setOptions function. * Test the setOptions function.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testSetOptionsWithoutUpdate() public function testSetOptionsWithoutUpdate()
{ {
@ -134,7 +134,7 @@ class ConfigTest extends BaseTest
*/ */
public function testSetOptionsWithBadYoutubedl() public function testSetOptionsWithBadYoutubedl()
{ {
$this->expectException(Exception::class); $this->expectException(ConfigException::class);
Config::setOptions(['youtubedl' => 'foo']); Config::setOptions(['youtubedl' => 'foo']);
} }
@ -145,7 +145,7 @@ class ConfigTest extends BaseTest
*/ */
public function testSetOptionsWithBadPython() public function testSetOptionsWithBadPython()
{ {
$this->expectException(Exception::class); $this->expectException(ConfigException::class);
Config::setOptions(['python' => 'foo']); Config::setOptions(['python' => 'foo']);
} }
@ -153,7 +153,7 @@ class ConfigTest extends BaseTest
* Test the getInstance function with the CONVERT and PYTHON environment variables. * Test the getInstance function with the CONVERT and PYTHON environment variables.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testGetInstanceWithEnv() public function testGetInstanceWithEnv()
{ {

View file

@ -9,13 +9,14 @@ namespace Alltube\Test;
use Alltube\Controller\BaseController; use Alltube\Controller\BaseController;
use Alltube\Controller\DownloadController; use Alltube\Controller\DownloadController;
use Alltube\Controller\FrontController; use Alltube\Controller\FrontController;
use Alltube\Exception\ConfigException;
use Alltube\LocaleManager; use Alltube\LocaleManager;
use Alltube\ViewFactory; use Alltube\ViewFactory;
use Exception;
use Slim\Container; use Slim\Container;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
use SmartyException;
/** /**
* Abstract class used by the controller tests. * Abstract class used by the controller tests.
@ -51,7 +52,7 @@ abstract class ControllerTest extends BaseTest
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException|SmartyException
*/ */
protected function setUp(): void protected function setUp(): void
{ {

View file

@ -6,9 +6,9 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config;
use Alltube\Exception\ConfigException;
use Alltube\Stream\ConvertedPlaylistArchiveStream; use Alltube\Stream\ConvertedPlaylistArchiveStream;
use Alltube\Video;
use Exception;
/** /**
* Unit tests for the ConvertedPlaylistArchiveStream class. * Unit tests for the ConvertedPlaylistArchiveStream class.
@ -18,14 +18,16 @@ class ConvertedPlaylistArchiveStreamTest extends StreamTest
{ {
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$video = new Video('https://www.youtube.com/playlist?list=PL1j4Ff8cAqPu5iowaeUAY8lRgkfT4RybJ'); $config = Config::getInstance();
$downloader = $config->getDownloader();
$video = $downloader->getVideo('https://www.youtube.com/playlist?list=PL1j4Ff8cAqPu5iowaeUAY8lRgkfT4RybJ');
$this->stream = new ConvertedPlaylistArchiveStream($video); $this->stream = new ConvertedPlaylistArchiveStream($downloader, $video);
} }
} }

View file

@ -8,7 +8,11 @@ namespace Alltube\Test;
use Alltube\Config; use Alltube\Config;
use Alltube\Controller\DownloadController; use Alltube\Controller\DownloadController;
use Exception; use Alltube\Exception\ConfigException;
use Alltube\Library\Exception\EmptyUrlException;
use Alltube\Library\Exception\RemuxException;
use Alltube\Library\Exception\YoutubedlException;
use SmartyException;
/** /**
* Unit tests for the FrontController class. * Unit tests for the FrontController class.
@ -18,7 +22,7 @@ class DownloadControllerTest extends ControllerTest
{ {
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException|SmartyException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
@ -64,7 +68,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with streams enabled. * Test the download() function with streams enabled.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testDownloadWithStream() public function testDownloadWithStream()
{ {
@ -80,7 +84,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with an M3U stream. * Test the download() function with an M3U stream.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testDownloadWithM3uStream() public function testDownloadWithM3uStream()
{ {
@ -100,7 +104,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with an RTMP stream. * Test the download() function with an RTMP stream.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testDownloadWithRtmpStream() public function testDownloadWithRtmpStream()
{ {
@ -118,7 +122,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with a remuxed video. * Test the download() function with a remuxed video.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testDownloadWithRemux() public function testDownloadWithRemux()
{ {
@ -140,7 +144,8 @@ class DownloadControllerTest extends ControllerTest
*/ */
public function testDownloadWithRemuxDisabled() public function testDownloadWithRemuxDisabled()
{ {
$this->assertRequestIsServerError( $this->expectException(RemuxException::class);
$this->getRequestResult(
'download', 'download',
[ [
'url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU',
@ -166,7 +171,8 @@ class DownloadControllerTest extends ControllerTest
*/ */
public function testDownloadWithError() public function testDownloadWithError()
{ {
$this->assertRequestIsServerError('download', ['url' => 'http://example.com/foo']); $this->expectException(YoutubedlException::class);
$this->getRequestResult('download', ['url' => 'http://example.com/foo']);
} }
/** /**
@ -177,7 +183,8 @@ class DownloadControllerTest extends ControllerTest
*/ */
public function testDownloadWithEmptyUrl() public function testDownloadWithEmptyUrl()
{ {
$this->assertRequestIsServerError( $this->expectException(EmptyUrlException::class);
$this->getRequestResult(
'download', 'download',
['url' => 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC'] ['url' => 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC']
); );
@ -188,7 +195,7 @@ class DownloadControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires OS Linux * @requires OS Linux
* @throws Exception * @throws ConfigException
*/ */
public function testDownloadWithPlaylist() public function testDownloadWithPlaylist()
{ {
@ -204,7 +211,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with an advanced conversion. * Test the download() function with an advanced conversion.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testDownloadWithAdvancedConversion() public function testDownloadWithAdvancedConversion()
{ {

View file

@ -8,9 +8,12 @@ namespace Alltube\Test;
use Alltube\Config; use Alltube\Config;
use Alltube\Controller\FrontController; use Alltube\Controller\FrontController;
use Alltube\Exception\ConfigException;
use Alltube\Library\Exception\AlltubeLibraryException;
use Exception; use Exception;
use Slim\Http\Environment; use Slim\Http\Environment;
use Slim\Http\Request; use Slim\Http\Request;
use SmartyException;
/** /**
* Unit tests for the FrontController class. * Unit tests for the FrontController class.
@ -25,7 +28,7 @@ class FrontControllerTest extends ControllerTest
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException|SmartyException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
@ -48,7 +51,7 @@ class FrontControllerTest extends ControllerTest
* Test the constructor with streams enabled. * Test the constructor with streams enabled.
* *
* @return void * @return void
* @throws Exception * @throws ConfigException
*/ */
public function testConstructorWithStream() public function testConstructorWithStream()
{ {
@ -99,7 +102,7 @@ class FrontControllerTest extends ControllerTest
*/ */
public function testPassword() public function testPassword()
{ {
$this->assertRequestIsOk('password'); $this->assertRequestIsClientError('password');
} }
/** /**
@ -128,7 +131,7 @@ class FrontControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires download * @requires download
* @throws Exception * @throws ConfigException
*/ */
public function testInfoWithAudio() public function testInfoWithAudio()
{ {
@ -145,7 +148,7 @@ class FrontControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires download * @requires download
* @throws Exception * @throws ConfigException
*/ */
public function testInfoWithVimeoAudio() public function testInfoWithVimeoAudio()
{ {
@ -160,7 +163,7 @@ class FrontControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires download * @requires download
* @throws Exception * @throws ConfigException
*/ */
public function testInfoWithUnconvertedAudio() public function testInfoWithUnconvertedAudio()
{ {
@ -180,6 +183,7 @@ class FrontControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires download * @requires download
* @throws AlltubeLibraryException
*/ */
public function testInfoWithPassword() public function testInfoWithPassword()
{ {
@ -199,8 +203,8 @@ class FrontControllerTest extends ControllerTest
*/ */
public function testInfoWithMissingPassword() public function testInfoWithMissingPassword()
{ {
$this->assertRequestIsOk('info', ['url' => 'http://vimeo.com/68375962']); $this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962']);
$this->assertRequestIsOk('info', ['url' => 'http://vimeo.com/68375962', 'audio' => true]); $this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962', 'audio' => true]);
} }
/** /**
@ -208,7 +212,7 @@ class FrontControllerTest extends ControllerTest
* *
* @return void * @return void
* @requires download * @requires download
* @throws Exception * @throws ConfigException
*/ */
public function testInfoWithStream() public function testInfoWithStream()
{ {

View file

@ -7,7 +7,9 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Controller\JsonController; use Alltube\Controller\JsonController;
use Exception; use Alltube\Exception\ConfigException;
use Alltube\Library\Exception\YoutubedlException;
use SmartyException;
/** /**
* Unit tests for the FrontController class. * Unit tests for the FrontController class.
@ -16,7 +18,7 @@ class JsonControllerTest extends ControllerTest
{ {
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException|SmartyException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
@ -44,7 +46,8 @@ class JsonControllerTest extends ControllerTest
*/ */
public function testJsonWithError() public function testJsonWithError()
{ {
$this->assertRequestIsServerError('json', ['url' => 'http://example.com/foo']); $this->expectException(YoutubedlException::class);
$this->getRequestResult('json', ['url' => 'http://example.com/foo']);
} }
/** /**

View file

@ -6,9 +6,9 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config;
use Alltube\Exception\ConfigException;
use Alltube\Stream\PlaylistArchiveStream; use Alltube\Stream\PlaylistArchiveStream;
use Alltube\Video;
use Exception;
/** /**
* Unit tests for the PlaylistArchiveStream class. * Unit tests for the PlaylistArchiveStream class.
@ -18,14 +18,16 @@ class PlaylistArchiveStreamTest extends StreamTest
{ {
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$video = new Video('https://www.youtube.com/playlist?list=PL1j4Ff8cAqPu5iowaeUAY8lRgkfT4RybJ'); $config = Config::getInstance();
$downloader = $config->getDownloader();
$video = $downloader->getVideo('https://www.youtube.com/playlist?list=PL1j4Ff8cAqPu5iowaeUAY8lRgkfT4RybJ');
$this->stream = new PlaylistArchiveStream($video); $this->stream = new PlaylistArchiveStream($downloader, $video);
} }
} }

View file

@ -6,36 +6,51 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Video; use Alltube\Config;
use Alltube\Exception\ConfigException;
use Alltube\Library\Downloader;
use Alltube\Library\Exception\AlltubeLibraryException;
use Alltube\Library\Exception\PopenStreamException;
use Alltube\Library\Video;
use Mockery; use Mockery;
use phpmock\mockery\PHPMockery; use phpmock\mockery\PHPMockery;
use Exception;
/** /**
* Unit tests for the Video class. * Unit tests for the Video class.
* They are in a separate file so they can safely replace PHP functions with stubs. * They are in a separate file so they can safely replace PHP functions with stubs.
*
* @requires download
*/ */
class VideoStubsTest extends BaseTest class VideoStubsTest extends BaseTest
{ {
/** /**
* Video URL used in many tests. * Video used in many tests.
* *
* @var Video * @var Video
*/ */
private $video; private $video;
/**
* Downloader instance used in tests.
*
* @var Downloader
*/
private $downloader;
/** /**
* Initialize properties used by test. * Initialize properties used by test.
* @throws Exception * @throws ConfigException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
PHPMockery::mock('Alltube', 'popen'); PHPMockery::mock('Alltube\Library', 'popen');
PHPMockery::mock('Alltube', 'fopen'); PHPMockery::mock('Alltube\Library', 'fopen');
$this->video = new Video('https://www.youtube.com/watch?v=XJC9_JkzugE'); $config = Config::getInstance();
$this->downloader = $config->getDownloader();
$this->video = $this->downloader->getVideo('https://www.youtube.com/watch?v=XJC9_JkzugE');
} }
/** /**
@ -52,55 +67,60 @@ class VideoStubsTest extends BaseTest
* Test getAudioStream function with a buggy popen. * Test getAudioStream function with a buggy popen.
* *
* @return void * @return void
* @throws AlltubeLibraryException
*/ */
public function testGetAudioStreamWithPopenError() public function testGetAudioStreamWithPopenError()
{ {
$this->expectException(Exception::class); $this->expectException(PopenStreamException::class);
$this->video->getAudioStream(); $this->downloader->getAudioStream($this->video);
} }
/** /**
* Test getM3uStream function with a buggy popen. * Test getM3uStream function with a buggy popen.
* *
* @return void * @return void
* @throws AlltubeLibraryException
*/ */
public function testGetM3uStreamWithPopenError() public function testGetM3uStreamWithPopenError()
{ {
$this->expectException(Exception::class); $this->expectException(PopenStreamException::class);
$this->video->getM3uStream(); $this->downloader->getM3uStream($this->video);
} }
/** /**
* Test getRtmpStream function with a buggy popen. * Test getRtmpStream function with a buggy popen.
* *
* @return void * @return void
* @throws AlltubeLibraryException
*/ */
public function testGetRtmpStreamWithPopenError() public function testGetRtmpStreamWithPopenError()
{ {
$this->expectException(Exception::class); $this->expectException(PopenStreamException::class);
$this->video->getRtmpStream(); $this->downloader->getRtmpStream($this->video);
} }
/** /**
* Test getRemuxStream function with a buggy popen. * Test getRemuxStream function with a buggy popen.
* *
* @return void * @return void
* @throws AlltubeLibraryException
*/ */
public function testGetRemuxStreamWithPopenError() public function testGetRemuxStreamWithPopenError()
{ {
$this->expectException(Exception::class); $this->expectException(PopenStreamException::class);
$video = $this->video->withFormat('bestvideo+bestaudio'); $video = $this->video->withFormat('bestvideo+bestaudio');
$video->getRemuxStream(); $this->downloader->getRemuxStream($video);
} }
/** /**
* Test getConvertedStream function with a buggy popen. * Test getConvertedStream function with a buggy popen.
* *
* @return void * @return void
* @throws AlltubeLibraryException
*/ */
public function testGetConvertedStreamWithPopenError() public function testGetConvertedStreamWithPopenError()
{ {
$this->expectException(Exception::class); $this->expectException(PopenStreamException::class);
$this->video->getConvertedStream(32, 'flv'); $this->downloader->getConvertedStream($this->video, 32, 'flv');
} }
} }

View file

@ -7,26 +7,61 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config; use Alltube\Config;
use Alltube\Exception\EmptyUrlException; use Alltube\Exception\ConfigException;
use Alltube\Exception\PasswordException; use Alltube\Library\Downloader;
use Alltube\Video; use Alltube\Library\Exception\AlltubeLibraryException;
use Exception; use Alltube\Library\Exception\AvconvException;
use Alltube\Library\Exception\InvalidProtocolConversionException;
use Alltube\Library\Exception\PasswordException;
use Alltube\Library\Exception\PlaylistConversionException;
use Alltube\Library\Exception\RemuxException;
use Alltube\Library\Exception\WrongPasswordException;
use Alltube\Library\Exception\YoutubedlException;
use Alltube\Library\Video;
/** /**
* Unit tests for the Video class. * Unit tests for the Video class.
* @requires download * @requires download
* @todo Split Downloader and Video tests.
*/ */
class VideoTest extends BaseTest class VideoTest extends BaseTest
{ {
/**
* Downloader instance used in tests.
*
* @var Downloader
*/
private $downloader;
/**
* Video format used in tests.
*
* @var string
*/
private $format;
/**
* Prepare tests.
* @throws ConfigException
*/
protected function setUp(): void
{
parent::setUp();
$config = Config::getInstance();
$this->downloader = $config->getDownloader();
$this->format = 'best';
}
/** /**
* Test getExtractors function. * Test getExtractors function.
* *
* @return void * @return void
* @throws PasswordException * @throws AlltubeLibraryException
*/ */
public function testGetExtractors() public function testGetExtractors()
{ {
$this->assertContains('youtube', Video::getExtractors()); $this->assertContains('youtube', $this->downloader->getExtractors());
} }
/** /**
@ -39,8 +74,7 @@ class VideoTest extends BaseTest
* @param string $domain Domain * @param string $domain Domain
* *
* @return void * @return void
* @throws PasswordException * @throws AlltubeLibraryException
* @throws EmptyUrlException
* @dataProvider urlProvider * @dataProvider urlProvider
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
* @dataProvider remuxUrlProvider * @dataProvider remuxUrlProvider
@ -52,7 +86,7 @@ class VideoTest extends BaseTest
/* @scrutinizer ignore-unused */ $extension, /* @scrutinizer ignore-unused */ $extension,
$domain $domain
) { ) {
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
foreach ($video->getUrl() as $videoURL) { foreach ($video->getUrl() as $videoURL) {
$this->assertStringContainsString($domain, $videoURL); $this->assertStringContainsString($domain, $videoURL);
} }
@ -62,12 +96,11 @@ class VideoTest extends BaseTest
* Test getUrl function with a protected video. * Test getUrl function with a protected video.
* *
* @return void * @return void
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
*/ */
public function testgetUrlWithPassword() public function testgetUrlWithPassword()
{ {
$video = new Video('http://vimeo.com/68375962', 'best', 'youtube-dl'); $video = new Video($this->downloader, 'http://vimeo.com/68375962', 'best', 'youtube-dl');
foreach ($video->getUrl() as $videoURL) { foreach ($video->getUrl() as $videoURL) {
$this->assertStringContainsString('vimeocdn.com', $videoURL); $this->assertStringContainsString('vimeocdn.com', $videoURL);
} }
@ -77,13 +110,12 @@ class VideoTest extends BaseTest
* Test getUrl function with a protected video and no password. * Test getUrl function with a protected video and no password.
* *
* @return void * @return void
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
*/ */
public function testgetUrlWithMissingPassword() public function testgetUrlWithMissingPassword()
{ {
$this->expectException(Exception::class); $this->expectException(PasswordException::class);
$video = new Video('http://vimeo.com/68375962'); $video = new Video($this->downloader, 'http://vimeo.com/68375962', $this->format);
$video->getUrl(); $video->getUrl();
} }
@ -91,13 +123,12 @@ class VideoTest extends BaseTest
* Test getUrl function with a protected video and a wrong password. * Test getUrl function with a protected video and a wrong password.
* *
* @return void * @return void
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
*/ */
public function testgetUrlWithWrongPassword() public function testgetUrlWithWrongPassword()
{ {
$this->expectException(Exception::class); $this->expectException(WrongPasswordException::class);
$video = new Video('http://vimeo.com/68375962', 'best', 'foo'); $video = new Video($this->downloader, 'http://vimeo.com/68375962', 'best', 'foo');
$video->getUrl(); $video->getUrl();
} }
@ -107,14 +138,13 @@ class VideoTest extends BaseTest
* @param string $url URL * @param string $url URL
* *
* @return void * @return void
* @throws EmptyUrlException * @throws AlltubeLibraryException
* @throws PasswordException
* @dataProvider ErrorUrlProvider * @dataProvider ErrorUrlProvider
*/ */
public function testgetUrlError($url) public function testgetUrlError($url)
{ {
$this->expectException(Exception::class); $this->expectException(YoutubedlException::class);
$video = new Video($url); $video = new Video($this->downloader, $url, $this->format);
$video->getUrl(); $video->getUrl();
} }
@ -224,13 +254,13 @@ class VideoTest extends BaseTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider urlProvider * @dataProvider urlProvider
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
* @throws PasswordException
*/ */
public function testGetJson($url, $format) public function testGetJson($url, $format)
{ {
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$info = $video->getJson(); $info = $video->getJson();
$this->assertObjectHasAttribute('webpage_url', $info); $this->assertObjectHasAttribute('webpage_url', $info);
$this->assertObjectHasAttribute('url', $info); $this->assertObjectHasAttribute('url', $info);
@ -246,13 +276,13 @@ class VideoTest extends BaseTest
* @param string $url URL * @param string $url URL
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider ErrorURLProvider * @dataProvider ErrorURLProvider
* @throws PasswordException
*/ */
public function testGetJsonError($url) public function testGetJsonError($url)
{ {
$this->expectException(Exception::class); $this->expectException(YoutubedlException::class);
$video = new Video($url); $video = new Video($this->downloader, $url, $this->format);
$video->getJson(); $video->getJson();
} }
@ -265,14 +295,14 @@ class VideoTest extends BaseTest
* @param string $extension File extension * @param string $extension File extension
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider urlProvider * @dataProvider urlProvider
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
* @dataProvider remuxUrlProvider * @dataProvider remuxUrlProvider
* @throws PasswordException
*/ */
public function testGetFilename($url, $format, $filename, $extension) public function testGetFilename($url, $format, $filename, $extension)
{ {
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$this->assertEquals($video->getFilename(), $filename . '.' . $extension); $this->assertEquals($video->getFilename(), $filename . '.' . $extension);
} }
@ -282,13 +312,13 @@ class VideoTest extends BaseTest
* @param string $url URL * @param string $url URL
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider ErrorUrlProvider * @dataProvider ErrorUrlProvider
* @throws PasswordException
*/ */
public function testGetFilenameError($url) public function testGetFilenameError($url)
{ {
$this->expectException(Exception::class); $this->expectException(YoutubedlException::class);
$video = new Video($url); $video = new Video($this->downloader, $url, $this->format);
$video->getFilename(); $video->getFilename();
} }
@ -300,12 +330,12 @@ class VideoTest extends BaseTest
* *
* @return void * @return void
* @dataProvider urlProvider * @dataProvider urlProvider
* @throws Exception * @throws AlltubeLibraryException
*/ */
public function testGetAudioStream($url, $format) public function testGetAudioStream($url, $format)
{ {
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$this->assertStream($video->getAudioStream()); $this->assertStream($this->downloader->getAudioStream($video));
} }
/** /**
@ -315,15 +345,18 @@ class VideoTest extends BaseTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException|ConfigException
* @dataProvider urlProvider * @dataProvider urlProvider
*/ */
public function testGetAudioStreamAvconvError($url, $format) public function testGetAudioStreamAvconvError($url, $format)
{ {
$this->expectException(Exception::class); $this->expectException(AvconvException::class);
Config::setOptions(['avconv' => 'foobar']); Config::setOptions(['avconv' => 'foobar']);
$config = Config::getInstance();
$downloader = $config->getDownloader();
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format, $this->format);
$video->getAudioStream(); $downloader->getAudioStream($video);
} }
/** /**
@ -333,40 +366,44 @@ class VideoTest extends BaseTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
*/ */
public function testGetAudioStreamM3uError($url, $format) public function testGetAudioStreamM3uError($url, $format)
{ {
$this->expectException(Exception::class); $this->expectException(InvalidProtocolConversionException::class);
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$video->getAudioStream(); $this->downloader->getAudioStream($video);
} }
/** /**
* Test getAudioStream function with a DASH URL. * Test getAudioStream function with a DASH URL.
* *
* @return void * @return void
* @throws AlltubeLibraryException
*/ */
public function testGetAudioStreamDashError() public function testGetAudioStreamDashError()
{ {
$this->expectException(Exception::class); $this->expectException(InvalidProtocolConversionException::class);
$video = new Video('https://vimeo.com/251997032', 'bestaudio/best'); $video = new Video($this->downloader, 'https://vimeo.com/251997032', 'bestaudio/best');
$video->getAudioStream(); $this->downloader->getAudioStream($video);
} }
/** /**
* Test getAudioStream function with a playlist. * Test getAudioStream function with a playlist.
* *
* @return void * @return void
* @throws AlltubeLibraryException
*/ */
public function testGetAudioStreamPlaylistError() public function testGetAudioStreamPlaylistError()
{ {
$this->expectException(Exception::class); $this->expectException(PlaylistConversionException::class);
$video = new Video( $video = new Video(
$this->downloader,
'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC', 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC',
'best' 'best'
); );
$video->getAudioStream(); $this->downloader->getAudioStream($video);
} }
/** /**
@ -390,12 +427,12 @@ class VideoTest extends BaseTest
* *
* @return void * @return void
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
* @throws Exception * @throws AlltubeLibraryException
*/ */
public function testGetM3uStream($url, $format) public function testGetM3uStream($url, $format)
{ {
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$this->assertStream($video->getM3uStream()); $this->assertStream($this->downloader->getM3uStream($video));
} }
/** /**
@ -406,12 +443,12 @@ class VideoTest extends BaseTest
* *
* @return void * @return void
* @dataProvider remuxUrlProvider * @dataProvider remuxUrlProvider
* @throws Exception * @throws AlltubeLibraryException
*/ */
public function testGetRemuxStream($url, $format) public function testGetRemuxStream($url, $format)
{ {
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$this->assertStream($video->getRemuxStream()); $this->assertStream($this->downloader->getRemuxStream($video));
} }
/** /**
@ -421,13 +458,14 @@ class VideoTest extends BaseTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider urlProvider * @dataProvider urlProvider
*/ */
public function testGetRemuxStreamWithWrongVideo($url, $format) public function testGetRemuxStreamWithWrongVideo($url, $format)
{ {
$this->expectException(Exception::class); $this->expectException(RemuxException::class);
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$video->getRemuxStream(); $this->downloader->getRemuxStream($video);
} }
/** /**
@ -437,16 +475,16 @@ class VideoTest extends BaseTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider rtmpUrlProvider * @dataProvider rtmpUrlProvider
* @throws Exception
*/ */
public function testGetRtmpStream($url, $format) public function testGetRtmpStream($url, $format)
{ {
$this->markTestIncomplete('We need to find another RTMP video.'); $this->markTestIncomplete('We need to find another RTMP video.');
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$this->assertStream($video->getRtmpStream()); $this->assertStream($this->downloader->getRtmpStream($video));
} }
/** /**
@ -456,15 +494,18 @@ class VideoTest extends BaseTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException|ConfigException
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
*/ */
public function testGetM3uStreamAvconvError($url, $format) public function testGetM3uStreamAvconvError($url, $format)
{ {
$this->expectException(Exception::class); $this->expectException(AvconvException::class);
Config::setOptions(['avconv' => 'foobar']); Config::setOptions(['avconv' => 'foobar']);
$config = Config::getInstance();
$downloader = $config->getDownloader();
$video = new Video($url, $format); $video = new Video($downloader, $url, $format);
$video->getM3uStream(); $downloader->getM3uStream($video);
} }
/** /**
@ -475,12 +516,12 @@ class VideoTest extends BaseTest
* *
* @return void * @return void
* @dataProvider urlProvider * @dataProvider urlProvider
* @throws Exception * @throws AlltubeLibraryException
*/ */
public function testGetConvertedStream($url, $format) public function testGetConvertedStream($url, $format)
{ {
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$this->assertStream($video->getConvertedStream(32, 'flv')); $this->assertStream($this->downloader->getConvertedStream($video, 32, 'flv'));
} }
/** /**
@ -490,12 +531,13 @@ class VideoTest extends BaseTest
* @param string $format Format * @param string $format Format
* *
* @return void * @return void
* @throws AlltubeLibraryException
* @dataProvider m3uUrlProvider * @dataProvider m3uUrlProvider
*/ */
public function testGetConvertedStreamM3uError($url, $format) public function testGetConvertedStreamM3uError($url, $format)
{ {
$this->expectException(Exception::class); $this->expectException(InvalidProtocolConversionException::class);
$video = new Video($url, $format); $video = new Video($this->downloader, $url, $format);
$video->getConvertedStream(32, 'flv'); $this->downloader->getConvertedStream($video, 32, 'flv');
} }
} }

View file

@ -6,9 +6,10 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config;
use Alltube\Exception\ConfigException;
use Alltube\Library\Exception\AlltubeLibraryException;
use Alltube\Stream\YoutubeChunkStream; use Alltube\Stream\YoutubeChunkStream;
use Alltube\Video;
use Exception;
/** /**
* Unit tests for the YoutubeChunkStream class. * Unit tests for the YoutubeChunkStream class.
@ -18,14 +19,17 @@ class YoutubeChunkStreamTest extends StreamTest
{ {
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException
* @throws AlltubeLibraryException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$video = new Video('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); $config = Config::getInstance();
$downloader = $config->getDownloader();
$video = $downloader->getVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
$this->stream = new YoutubeChunkStream($video->getHttpResponse()); $this->stream = new YoutubeChunkStream($downloader->getHttpResponse($video));
} }
} }

View file

@ -6,9 +6,10 @@
namespace Alltube\Test; namespace Alltube\Test;
use Alltube\Config;
use Alltube\Exception\ConfigException;
use Alltube\Library\Exception\AlltubeLibraryException;
use Alltube\Stream\YoutubeStream; use Alltube\Stream\YoutubeStream;
use Alltube\Video;
use Exception;
/** /**
* Unit tests for the YoutubeStream class. * Unit tests for the YoutubeStream class.
@ -18,15 +19,17 @@ class YoutubeStreamTest extends StreamTest
{ {
/** /**
* Prepare tests. * Prepare tests.
* @throws Exception * @throws ConfigException|AlltubeLibraryException
*/ */
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$video = new Video('https://www.youtube.com/watch?v=dQw4w9WgXcQ', '135'); $config = Config::getInstance();
$downloader = $config->getDownloader();
$video = $downloader->getVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ', '135');
$this->stream = new YoutubeStream($video); $this->stream = new YoutubeStream($downloader, $video);
} }
/** /**