Merge branch 'release-2.3.0'

This commit is contained in:
Pierre Rudloff 2020-05-14 12:40:03 +02:00
commit c136534723
50 changed files with 1010 additions and 858 deletions

View file

@ -26,7 +26,9 @@ FileETag None
</ifmodule>
<ifmodule mod_filter.c>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/css text/html application/javascript font/truetype
</IfModule>
</ifmodule>
<ifmodule mod_headers.c>

View file

@ -7,6 +7,7 @@
namespace Alltube;
use Exception;
use Jawira\CaseConverter\CaseConverterException;
use Symfony\Component\Yaml\Yaml;
use Jawira\CaseConverter\Convert;
@ -39,7 +40,7 @@ class Config
/**
* youtube-dl parameters.
*
* @var array
* @var string[]
*/
public $params = ['--no-warnings', '--ignore-errors', '--flat-playlist', '--restrict-filenames', '--no-playlist'];
@ -60,7 +61,7 @@ class Config
/**
* List of formats available in advanced conversion mode.
*
* @var array
* @var string[]
*/
public $convertAdvancedFormats = ['mp3', 'avi', 'flv', 'wav'];
@ -121,17 +122,10 @@ class Config
*/
public $appName = 'AllTube Download';
/**
* YAML config file path.
*
* @var string
*/
private $file;
/**
* Generic formats supported by youtube-dl.
*
* @var array
* @var string[]
*/
public $genericFormats = [];
@ -145,7 +139,8 @@ class Config
/**
* Config constructor.
*
* @param array $options Options
* @param mixed[] $options Options
* @throws CaseConverterException
*/
private function __construct(array $options = [])
{
@ -195,10 +190,10 @@ class Config
/**
* Throw an exception if some of the options are invalid.
*
* @throws Exception If youtube-dl is missing
* @return void
* @throws Exception If Python is missing
*
* @return void
* @throws Exception If youtube-dl is missing
*/
private function validateOptions()
{
@ -216,7 +211,7 @@ class Config
/**
* Apply the provided options.
*
* @param array $options Options
* @param mixed[] $options Options
*
* @return void
*/
@ -235,6 +230,7 @@ class Config
* If the value is an array, you should use the YAML format: "CONVERT_ADVANCED_FORMATS='[foo, bar]'"
*
* @return void
* @throws CaseConverterException
*/
private function getEnv()
{
@ -265,11 +261,13 @@ class Config
* Set options from a YAML file.
*
* @param string $file Path to the YAML file
* @return void
* @throws Exception
*/
public static function setFile($file)
{
if (is_file($file)) {
$options = Yaml::parse(file_get_contents($file));
$options = Yaml::parse(strval(file_get_contents($file)));
self::$instance = new self($options);
self::$instance->validateOptions();
} else {
@ -280,8 +278,10 @@ class Config
/**
* Manually set some options.
*
* @param array $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
* @return void
* @throws Exception
*/
public static function setOptions(array $options, $update = true)
{

View file

@ -104,12 +104,14 @@ class Locale
/**
* Get country information from locale.
*
* @return Country|array
* @return Country|Country[]|null
*/
public function getCountry()
{
if (isset($this->region)) {
return country($this->getIso3166());
}
return null;
}
}

View file

@ -7,7 +7,6 @@
namespace Alltube;
use Aura\Session\Segment;
use Symfony\Component\Process\Process;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\PoFileLoader;
@ -19,7 +18,7 @@ class LocaleManager
/**
* Supported locales.
*
* @var array
* @var string[]
*/
private $supportedLocales = ['en_US', 'fr_FR', 'zh_CN', 'es_ES', 'pt_BR', 'de_DE', 'ar', 'pl_PL', 'tr_TR'];
@ -112,6 +111,7 @@ class LocaleManager
* Set the current locale.
*
* @param Locale $locale Locale
* @return void
*/
public function setLocale(Locale $locale)
{
@ -122,6 +122,7 @@ class LocaleManager
/**
* Unset the current locale.
* @return void
*/
public function unsetLocale()
{
@ -133,7 +134,7 @@ class LocaleManager
/**
* Smarty "t" block.
*
* @param array $params Block parameters
* @param mixed[] $params Block parameters
* @param string $text Block content
*
* @return string Translated string
@ -152,6 +153,7 @@ class LocaleManager
*
* @param string $string String to translate
*
* @param mixed[] $params
* @return string Translated string
*/
public function t($string, array $params = [])

View file

@ -36,9 +36,9 @@ class LocaleMiddleware
/**
* Test if a locale can be used for the current user.
*
* @param array $proposedLocale Locale array created by AcceptLanguage::parse()
* @param mixed[] $proposedLocale Locale array created by AcceptLanguage::parse()
*
* @return Locale Locale if chosen, nothing otherwise
* @return Locale|null Locale if chosen, nothing otherwise
*/
public function testLocale(array $proposedLocale)
{
@ -52,6 +52,8 @@ class LocaleMiddleware
return new Locale($proposedLocale['language'] . '_' . $proposedLocale['region']);
}
}
return null;
}
/**

View file

@ -21,7 +21,7 @@ class UglyRouter extends Router
*
* @param ServerRequestInterface $request The current HTTP request object
*
* @return array
* @return mixed[]
*
* @link https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php
*/
@ -43,13 +43,13 @@ class UglyRouter extends Router
* Build the path for a named route including the base path.
*
* @param string $name Route name
* @param array $data Named argument replacement data
* @param array $queryParams Optional query string parameters
*
* @throws RuntimeException If named route does not exist
* @throws InvalidArgumentException If required data not provided
* @param string[] $data Named argument replacement data
* @param string[] $queryParams Optional query string parameters
*
* @return string
* @throws InvalidArgumentException If required data not provided
*
* @throws RuntimeException If named route does not exist
*/
public function pathFor($name, array $data = [], array $queryParams = [])
{

View file

@ -10,7 +10,7 @@ use Alltube\Exception\EmptyUrlException;
use Alltube\Exception\PasswordException;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use stdClass;
use Symfony\Component\Process\Process;
@ -70,7 +70,7 @@ class Video
/**
* URLs of the video files.
*
* @var array
* @var string[]
*/
private $urls;
@ -105,7 +105,7 @@ class Video
*
* @param string[] $arguments Arguments
*
* @return Process
* @return Process<string>
*/
private static function getProcess(array $arguments)
{
@ -124,7 +124,9 @@ class Video
* List all extractors.
*
* @return string[] Extractors
* */
*
* @throws PasswordException
*/
public static function getExtractors()
{
$video = new self('');
@ -135,13 +137,13 @@ class Video
/**
* Call youtube-dl.
*
* @param array $arguments Arguments
* @param string[] $arguments Arguments
*
* @throws PasswordException If the video is protected by a password and no password was specified
* @return string Result
* @throws Exception If the password is wrong
* @throws Exception If youtube-dl returns an error
*
* @return string Result
* @throws PasswordException If the video is protected by a password and no password was specified
*/
private function callYoutubedl(array $arguments)
{
@ -153,7 +155,7 @@ class Video
$process->run();
if (!$process->isSuccessful()) {
$errorOutput = trim($process->getErrorOutput());
$exitCode = $process->getExitCode();
$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') {
@ -172,6 +174,7 @@ class Video
* @param string $prop Property
*
* @return string
* @throws PasswordException
*/
private function getProp($prop = 'dump-json')
{
@ -196,7 +199,9 @@ class Video
* Get all information about a video.
*
* @return stdClass Decoded JSON
* */
*
* @throws PasswordException
*/
public function getJson()
{
if (!isset($this->json)) {
@ -212,12 +217,15 @@ class Video
* @param string $name Property
*
* @return mixed
* @throws PasswordException
*/
public function __get($name)
{
if (isset($this->$name)) {
return $this->getJson()->$name;
}
return null;
}
/**
@ -226,6 +234,7 @@ class Video
* @param string $name Property
*
* @return bool
* @throws PasswordException
*/
public function __isset($name)
{
@ -240,7 +249,9 @@ class Video
* (eg. bestvideo+bestaudio).
*
* @return string[] URLs of video
* */
* @throws EmptyUrlException
* @throws PasswordException
*/
public function getUrl()
{
// Cache the URLs.
@ -259,7 +270,9 @@ class Video
* 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'));
@ -271,23 +284,17 @@ class Video
* @param string $extension New file extension
*
* @return string Filename of extracted video with specified extension
* @throws PasswordException
*/
public function getFileNameWithExtension($extension)
{
return html_entity_decode(
pathinfo(
$this->getFilename(),
PATHINFO_FILENAME
) . '.' . $extension,
ENT_COMPAT,
'ISO-8859-1'
);
return str_replace('.' . $this->ext, '.' . $extension, $this->getFilename());
}
/**
* Return arguments used to run rtmp for a specific video.
*
* @return array Arguments
* @return string[] Arguments
*/
private function getRtmpArguments()
{
@ -324,7 +331,7 @@ class Video
/**
* Check if a command runs successfully.
*
* @param array $command Command and arguments
* @param string[] $command Command and arguments
*
* @return bool False if the command returns an error, true otherwise
*/
@ -345,9 +352,9 @@ class Video
* @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
*
* @return Process Process
*/
private function getAvconvProcess(
$audioBitrate,
@ -420,10 +427,10 @@ class Video
* @param string $from Start the conversion at this time
* @param string $to End the conversion at this time
*
* @throws Exception If your try to convert an M3U8 video
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @return resource popen stream
* @throws Exception If your try to convert an M3U8 video
*/
public function getAudioStream($from = null, $to = null)
{
@ -453,10 +460,10 @@ class Video
/**
* Get video stream from an M3U playlist.
*
* @throws Exception If avconv/ffmpeg is missing
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @return resource popen stream
* @throws Exception If avconv/ffmpeg is missing
*/
public function getM3uStream()
{
@ -495,9 +502,9 @@ class Video
/**
* Get an avconv stream to remux audio and video.
*
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @return resource popen stream
*/
public function getRemuxStream()
{
@ -532,9 +539,9 @@ class Video
/**
* Get video stream from an RTMP video.
*
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @return resource popen stream
*/
public function getRtmpStream()
{
@ -568,10 +575,10 @@ class Video
* @param int $audioBitrate Audio bitrate of the converted file
* @param string $filetype Filetype of the converted file
*
* @throws Exception If your try to convert and M3U8 video
* @return resource popen stream
* @throws Exception If the popen stream was not created correctly
*
* @return resource popen stream
* @throws Exception If your try to convert and M3U8 video
*/
public function getConvertedStream($audioBitrate, $filetype)
{
@ -605,20 +612,34 @@ class Video
/**
* Get a HTTP response containing the video.
*
* @param array $headers HTTP headers of the request
* @param mixed[] $headers HTTP headers of the request
*
* @return Response
* @return ResponseInterface
* @throws EmptyUrlException
* @throws PasswordException
* @link https://github.com/guzzle/guzzle/issues/2640
*/
public function getHttpResponse(array $headers = [])
{
$client = new Client();
// 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

@ -10,6 +10,7 @@ use Psr\Container\ContainerInterface;
use Slim\Http\Request;
use Slim\Views\Smarty;
use Slim\Views\SmartyPlugins;
use SmartyException;
/**
* Create Smarty view object.
@ -23,11 +24,12 @@ class ViewFactory
* @param Request $request PSR-7 request
*
* @return Smarty
* @throws SmartyException
*/
public static function create(ContainerInterface $container, Request $request = null)
{
if (!isset($request)) {
$request = $container['request'];
$request = $container->get('request');
}
$view = new Smarty(__DIR__ . '/../templates/');
@ -35,9 +37,10 @@ class ViewFactory
$request = $request->withUri($request->getUri()->withScheme('https')->withPort(443));
}
$localeManager = $container['locale'];
/** @var LocaleManager $localeManager */
$localeManager = $container->get('locale');
$smartyPlugins = new SmartyPlugins($container['router'], $request->getUri()->withUserInfo(null));
$smartyPlugins = new SmartyPlugins($container->get('router'), $request->getUri()->withUserInfo(null));
$view->registerPlugin('function', 'path_for', [$smartyPlugins, 'pathFor']);
$view->registerPlugin('function', 'base_url', [$smartyPlugins, 'baseUrl']);
$view->registerPlugin('block', 't', [$localeManager, 'smartyTranslate']);

View file

@ -7,6 +7,7 @@
namespace Alltube\Stream;
use Alltube\Video;
use Exception;
use Slim\Http\Stream;
/**
@ -20,6 +21,7 @@ class ConvertedPlaylistArchiveStream extends PlaylistArchiveStream
* @param Video $video Video to stream
*
* @return void
* @throws Exception
*/
protected function startVideoStream(Video $video)
{

View file

@ -6,6 +6,8 @@
namespace Alltube\Stream;
use Alltube\Exception\EmptyUrlException;
use Alltube\Exception\PasswordException;
use Alltube\Video;
use Barracuda\ArchiveStream\ZipArchive;
use Psr\Http\Message\StreamInterface;
@ -48,7 +50,10 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
/**
* PlaylistArchiveStream constructor.
*
* We don't call the parent constructor because it messes up the output buffering.
*
* @param Video $video Video/playlist to download
* @noinspection PhpMissingParentConstructorInspection
*/
public function __construct(Video $video)
{
@ -86,20 +91,21 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
*
* @param string $string The string that is to be written
*
* @return int
* @return int|false
*/
public function write($string)
{
fwrite($this->buffer, $string);
return fwrite($this->buffer, $string);
}
/**
* Get the size of the stream if known.
*
* @return null
* @return int|null
*/
public function getSize()
{
return null;
}
/**
@ -145,7 +151,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
/**
* Returns the remaining contents in a string.
*
* @return string
* @return string|false
*/
public function getContents()
{
@ -170,6 +176,8 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
if (isset($meta[$key])) {
return $meta[$key];
}
return null;
}
/**
@ -194,7 +202,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
{
$this->rewind();
return $this->getContents();
return strval($this->getContents());
}
/**
@ -236,6 +244,8 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* @param Video $video Video to stream
*
* @return void
* @throws PasswordException
* @throws EmptyUrlException
*/
protected function startVideoStream(Video $video)
{
@ -246,7 +256,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
$this->init_file_stream_transfer(
$video->getFilename(),
$contentLengthHeaders[0]
intval($contentLengthHeaders[0])
);
}
@ -256,6 +266,8 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface
* @param int $count Number of bytes to read
*
* @return string|false
* @throws EmptyUrlException
* @throws PasswordException
*/
public function read($count)
{

View file

@ -6,7 +6,7 @@
namespace Alltube\Stream;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
@ -18,16 +18,16 @@ class YoutubeChunkStream implements StreamInterface
/**
* HTTP response containing the video chunk.
*
* @var Response
* @var ResponseInterface
*/
private $response;
/**
* YoutubeChunkStream constructor.
*
* @param Response $response HTTP response containing the video chunk
* @param ResponseInterface $response HTTP response containing the video chunk
*/
public function __construct(Response $response)
public function __construct(ResponseInterface $response)
{
$this->response = $response;
}
@ -41,7 +41,7 @@ class YoutubeChunkStream implements StreamInterface
*/
public function read($length)
{
$size = $this->response->getHeader('Content-Length')[0];
$size = intval($this->response->getHeader('Content-Length')[0]);
if ($size - $this->tell() < $length) {
// Don't try to read further than the end of the stream.
$length = $size - $this->tell();
@ -124,21 +124,21 @@ class YoutubeChunkStream implements StreamInterface
* @param int $offset Stream offset
* @param int $whence Specifies how the cursor position will be calculated
*
* @return mixed
* @return void
*/
public function seek($offset, $whence = SEEK_SET)
{
return $this->response->getBody()->seek($offset, $whence);
$this->response->getBody()->seek($offset, $whence);
}
/**
* Seek to the beginning of the stream.
*
* @return mixed
* @return void
*/
public function rewind()
{
return $this->response->getBody()->rewind();
$this->response->getBody()->rewind();
}
/**

View file

@ -6,6 +6,8 @@
namespace Alltube\Stream;
use Alltube\Exception\EmptyUrlException;
use Alltube\Exception\PasswordException;
use Alltube\Video;
use GuzzleHttp\Psr7\AppendStream;
@ -19,6 +21,8 @@ class YoutubeStream extends AppendStream
* YoutubeStream constructor.
*
* @param Video $video Video to stream
* @throws EmptyUrlException
* @throws PasswordException
*/
public function __construct(Video $video)
{
@ -31,7 +35,7 @@ class YoutubeStream extends AppendStream
while ($rangeStart < $contentLenghtHeader[0]) {
$rangeEnd = $rangeStart + $video->downloader_options->http_chunk_size;
if ($rangeEnd >= $contentLenghtHeader[0]) {
$rangeEnd = $contentLenghtHeader[0] - 1;
$rangeEnd = intval($contentLenghtHeader[0]) - 1;
}
$response = $video->getHttpResponse(['Range' => 'bytes=' . $rangeStart . '-' . $rangeEnd]);
$this->addStream(new YoutubeChunkStream($response));

View file

@ -5,34 +5,35 @@
"homepage": "http://alltubedownload.net/",
"type": "project",
"require": {
"aura/session": "~2.1.0",
"barracudanetworks/archivestream-php": "~1.0.5",
"guzzlehttp/guzzle": "~6.3.0",
"ext-intl": "*",
"ext-json": "*",
"aura/session": "^2.1",
"barracudanetworks/archivestream-php": "^1.0",
"guzzlehttp/guzzle": "^6.5",
"jawira/case-converter": "^3.4",
"mathmarques/smarty-view": "~1.1.0",
"mathmarques/smarty-view": "^1.1",
"npm-asset/open-sans-fontface": "^1.4",
"rinvex/countries": "~3.1.0",
"slim/slim": "~3.12.1",
"rinvex/countries": "^6.1",
"symfony/process": "^4.0",
"symfony/translation": "^4.0",
"symfony/yaml": "^4.0",
"zonuexe/http-accept-language": "~0.4.1"
"zonuexe/http-accept-language": "^0.4.1"
},
"require-dev": {
"anam/phantomjs-linux-x86-binary": "~2.1.1",
"anam/phantomjs-linux-x86-binary": "^2.1",
"consolidation/robo": "^2.0",
"ffmpeg/ffmpeg": "^4.1",
"heroku/heroku-buildpack-php": "^162.0",
"php-mock/php-mock-mockery": "^1.3",
"phpro/grumphp": "^0.17.0",
"phpstan/phpstan": "~0.9.2",
"phpro/grumphp": "^0.18.0",
"phpstan/phpstan": "^0.12.25",
"phpunit/phpunit": "^8.4",
"roave/security-advisories": "dev-master",
"smarty-gettext/smarty-gettext": "^1.6",
"squizlabs/php_codesniffer": "^3.5",
"symfony/debug": "^4.0",
"symfony/var-dumper": "^4.0",
"ytdl-org/youtube-dl": "^2020.02"
"symfony/error-handler": "^5.0",
"symfony/var-dumper": "^5.0",
"ytdl-org/youtube-dl": "^2020.05"
},
"extra": {
"paas": {
@ -50,10 +51,10 @@
"type": "package",
"package": {
"name": "ytdl-org/youtube-dl",
"version": "2020.02.16",
"version": "2020.05.08",
"dist": {
"type": "zip",
"url": "https://github.com/ytdl-org/youtube-dl/archive/2020.02.16.zip"
"url": "https://github.com/ytdl-org/youtube-dl/archive/2020.05.08.zip"
}
}
},

776
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -70,6 +70,8 @@ class DownloadController extends BaseController
* @param Response $response PSR-7 response
*
* @return Response HTTP response
* @throws PasswordException
* @throws Exception
*/
private function getConvertedAudioResponse(Request $request, Response $response)
{
@ -104,6 +106,7 @@ class DownloadController extends BaseController
* @param Response $response PSR-7 response
*
* @return Response HTTP response
* @throws PasswordException
*/
private function getAudioResponse(Request $request, Response $response)
{
@ -130,7 +133,7 @@ class DownloadController extends BaseController
return $frontController->password($request, $response);
} catch (Exception $e) {
// If MP3 is not available, we convert it.
$this->video = $this->video->withFormat('bestaudio');
$this->video = $this->video->withFormat('bestaudio/best');
return $this->getConvertedAudioResponse($request, $response);
}
@ -139,10 +142,13 @@ class DownloadController extends BaseController
/**
* Get a video/audio stream piped through the server.
*
* @param Response $response PSR-7 response
* @param Request $request PSR-7 request
*
* @param Response $response PSR-7 response
* @return Response HTTP response
* @throws EmptyUrlException
* @throws PasswordException
* @throws Exception
*/
private function getStream(Request $request, Response $response)
{
@ -208,6 +214,8 @@ class DownloadController extends BaseController
* @param Request $request PSR-7 request
*
* @return Response HTTP response
* @throws PasswordException
* @throws Exception
*/
private function getRemuxStream(Request $request, Response $response)
{
@ -230,10 +238,13 @@ class DownloadController extends BaseController
* Get approriate HTTP response to download query.
* Depends on whether we want to stream, remux or simply redirect.
*
* @param Response $response PSR-7 response
* @param Request $request PSR-7 request
*
* @param Response $response PSR-7 response
* @return Response HTTP response
* @throws EmptyUrlException
* @throws PasswordException
* @throws Exception
*/
private function getDownloadResponse(Request $request, Response $response)
{
@ -266,6 +277,8 @@ class DownloadController extends BaseController
* @param Response $response PSR-7 response
*
* @return Response HTTP response
* @throws PasswordException
* @throws Exception
*/
private function getConvertedResponse(Request $request, Response $response)
{

View file

@ -9,15 +9,15 @@ namespace Alltube\Controller;
use Alltube\Exception\PasswordException;
use Alltube\Locale;
use Alltube\Video;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Throwable;
use Exception;
use Psr\Container\ContainerInterface;
use Slim\Container;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Smarty;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\Debug\Exception\FlattenException;
/**
* Main controller.
@ -78,7 +78,7 @@ class FrontController extends BaseController
*
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
* @param array $data Query parameters
* @param string[] $data Query parameters
*
* @return Response
*/
@ -96,6 +96,7 @@ class FrontController extends BaseController
* @param Response $response PSR-7 response
*
* @return Response HTTP response
* @throws PasswordException
*/
public function extractors(Request $request, Response $response)
{
@ -240,9 +241,9 @@ class FrontController extends BaseController
public function error(Request $request, Response $response, Throwable $error)
{
if ($this->config->debug) {
$exception = FlattenException::createFromThrowable($error);
$handler = new ExceptionHandler();
$response->getBody()->write($handler->getHtml($exception));
$renderer = new HtmlErrorRenderer(true);
$exception = $renderer->render($error);
$response->getBody()->write($exception->getAsString());
return $response->withStatus($exception->getStatusCode());
} else {

View file

@ -12,6 +12,5 @@ parameters:
- RoboFile.php
phpstan:
level: max
configuration: phpstan.neon
ignore_patterns:
- RoboFile.php

View file

@ -1,6 +1,7 @@
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Alltube\Config;
use Alltube\Controller\DownloadController;
use Alltube\Controller\FrontController;
@ -10,7 +11,8 @@ use Alltube\LocaleMiddleware;
use Alltube\UglyRouter;
use Alltube\ViewFactory;
use Slim\App;
use Symfony\Component\Debug\Debug;
use Slim\Container;
use Symfony\Component\ErrorHandler\Debug;
if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/index.php') !== false) {
header('Location: ' . str_ireplace('/index.php', '/', $_SERVER['REQUEST_URI']));
@ -18,11 +20,17 @@ if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/index.ph
}
if (is_file(__DIR__ . '/config/config.yml')) {
try {
Config::setFile(__DIR__ . '/config/config.yml');
} catch (Exception $e) {
die('Could not load config file: ' . $e->getMessage());
}
}
// Create app.
$app = new App();
/** @var Container $container */
$container = $app->getContainer();
// Load config.
@ -47,7 +55,11 @@ $container['locale'] = LocaleManager::getInstance();
$app->add(new LocaleMiddleware($container));
// Smarty.
try {
$container['view'] = ViewFactory::create($container);
} catch (SmartyException $e) {
die('Could not load Smarty: ' . $e->getMessage());
}
// Controllers.
$frontController = new FrontController($container);
@ -102,4 +114,7 @@ try {
$app->run();
} catch (SmartyException $e) {
die('Smarty could not compile the template file: ' . $e->getMessage());
} catch (Throwable $e) {
// Last resort if the error has not been caught by the error handler for some reason.
die('Error when starting the app: ' . $e->getMessage());
}

View file

@ -1,4 +0,0 @@
parameters:
ignoreErrors:
# The Archive constructor messes up the output buffering.
- '#Alltube\\Stream\\PlaylistArchiveStream::__construct\(\) does not call parent constructor from Barracuda\\ArchiveStream\\ZipArchive\.#'

View file

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE HTML>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="UTF-8"/>
@ -14,7 +14,9 @@
<h1>
<img class="logo" src="../img/logo.png" alt="AllTube Download" width="328" height="284"/>
</h1>
<div>An error occurred in the application and your page could not be served. Please try again in a few moments.</div>
<div>An error occurred in the application and your page could not be served. Please try again in a few
moments.
</div>
</div>
</div>
</body>

View file

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE HTML>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta charset="UTF-8"/>

View file

@ -1,4 +1,5 @@
<h1 class="logobis">
<a class="logocompatible" href="{base_url}">
<span class="logocompatiblemask"><img src="{base_url}/img/logocompatiblemask.png" width="447" height="107" alt="{$config->appName}" /></span>
<span class="logocompatiblemask"><img src="{base_url}/img/logocompatiblemask.png" width="447" height="107"
alt="{$config->appName}"/></span>
</a></h1>

View file

@ -25,8 +25,11 @@
{t}Audio only (MP3){/t}
</label>
<div class="seekOptions">
<label for="from">{t}From{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?" placeholder="HH:MM:SS" value="" name="from" id="from" />
<label for="to">{t}to{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?" placeholder="HH:MM:SS" value="" name="to" id="to" />
<label for="from">{t}From{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?"
placeholder="HH:MM:SS" value="" name="from"
id="from"/>
<label for="to">{t}to{/t}</label> <input type="text" pattern="(\d+:)?(\d+:)?\d+(\.\d+)?"
placeholder="HH:MM:SS" value="" name="to" id="to"/>
</div>
</div>
</div>
@ -36,7 +39,8 @@
<a class="combatiblelink small-font" href="{path_for name="extractors"}">{t}See all supported websites{/t}</a>
<div id="bookmarklet" class="bookmarklet_wrapper">
<p> {t}Drag this to your bookmarks bar:{/t} </p>
<a class="bookmarklet small-font" href="javascript:window.location='{$domain}{path_for name='info'}?url='+encodeURIComponent(location.href);">{t}Bookmarklet{/t}</a>
<a class="bookmarklet small-font"
href="javascript:window.location='{$domain}{path_for name='info'}?url='+encodeURIComponent(location.href);">{t}Bookmarklet{/t}</a>
</div>
</main>

View file

@ -71,12 +71,15 @@
{/if}
{/foreach}
</optgroup>
</select><br/><br/>
</select>
<br/>
<br/>
{/if}
{if $config->stream}
<input type="checkbox" {if $config->stream != 'ask'}checked{/if} name="stream" id="stream"/>
<label for="stream">{t}Stream the video through the server{/t}</label>
<br/><br/>
<br/>
<br/>
{/if}
{if $config->convertAdvanced}
<input type="checkbox" name="customConvert" id="customConvert"/>
@ -88,10 +91,12 @@
</select>
{t}with{/t}
<label for="customBitrate" class="sr-only">{t}Bit rate{/t}</label>
<input type="number" value="{$config->audioBitrate}" title="{t}Custom bitrate{/t}" class="customBitrate"
<input type="number" value="{$config->audioBitrate}" title="{t}Custom bitrate{/t}"
class="customBitrate"
name="customBitrate" id="customBitrate" aria-describedby="customBitrateUnit"/>
<span id="customBitrateUnit">{t}kbit/s audio{/t}</span>
<br/><br/>
<br/>
<br/>
{/if}
<input class="downloadBtn" type="submit" value="{t}Download{/t}"/><br/>
</form>

View file

@ -34,7 +34,8 @@
{$entry->title}
{/if}
</a></h3>
<a target="_blank" class="downloadBtn" href="{path_for name="download"}?url={$entry->url}">{t}Download{/t}</a>
<a target="_blank" class="downloadBtn"
href="{path_for name="download"}?url={$entry->url}">{t}Download{/t}</a>
<a target="_blank" href="{path_for name="info"}?url={$entry->url}">{t}More options{/t}</a>
</div>
{/foreach}

View file

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

View file

@ -23,6 +23,7 @@ class ConfigTest extends BaseTest
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{
@ -39,7 +40,7 @@ class ConfigTest extends BaseTest
public function testGetInstance()
{
$config = Config::getInstance();
$this->assertEquals($config->convert, false);
$this->assertEquals(false, $config->convert);
$this->assertConfig($config);
}
@ -53,7 +54,7 @@ class ConfigTest extends BaseTest
Config::destroyInstance();
$config = Config::getInstance();
$this->assertEquals($config->convert, false);
$this->assertEquals(false, $config->convert);
$this->assertConfig($config);
}
@ -81,6 +82,7 @@ class ConfigTest extends BaseTest
* Test the setFile function.
*
* @return void
* @throws Exception
*/
public function testSetFile()
{
@ -103,24 +105,26 @@ class ConfigTest extends BaseTest
* Test the setOptions function.
*
* @return void
* @throws Exception
*/
public function testSetOptions()
{
Config::setOptions(['appName' => 'foo']);
$config = Config::getInstance();
$this->assertEquals($config->appName, 'foo');
$this->assertEquals('foo', $config->appName);
}
/**
* Test the setOptions function.
*
* @return void
* @throws Exception
*/
public function testSetOptionsWithoutUpdate()
{
Config::setOptions(['appName' => 'foo'], false);
$config = Config::getInstance();
$this->assertEquals($config->appName, 'foo');
$this->assertEquals('foo', $config->appName);
}
/**
@ -149,6 +153,7 @@ class ConfigTest extends BaseTest
* Test the getInstance function with the CONVERT and PYTHON environment variables.
*
* @return void
* @throws Exception
*/
public function testGetInstanceWithEnv()
{
@ -156,7 +161,7 @@ class ConfigTest extends BaseTest
putenv('CONVERT=1');
Config::setFile($this->getConfigFile());
$config = Config::getInstance();
$this->assertEquals($config->convert, true);
$this->assertEquals(true, $config->convert);
putenv('CONVERT');
}
}

View file

@ -6,10 +6,12 @@
namespace Alltube\Test;
use Alltube\Controller\BaseController;
use Alltube\Controller\DownloadController;
use Alltube\Controller\FrontController;
use Alltube\LocaleManager;
use Alltube\ViewFactory;
use Exception;
use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
@ -43,11 +45,13 @@ abstract class ControllerTest extends BaseTest
/**
* Controller instance used in tests.
* @var BaseController
*/
protected $controller;
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{
@ -78,7 +82,7 @@ abstract class ControllerTest extends BaseTest
* Run controller function with custom query parameters and return the result.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param mixed[] $params Query parameters
*
* @return Response HTTP response
*/
@ -94,7 +98,7 @@ abstract class ControllerTest extends BaseTest
* Assert that calling controller function with these parameters returns a 200 HTTP response.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param mixed[] $params Query parameters
*
* @return void
*/
@ -107,7 +111,7 @@ abstract class ControllerTest extends BaseTest
* Assert that calling controller function with these parameters returns an HTTP redirect.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param mixed[] $params Query parameters
*
* @return void
*/
@ -120,7 +124,7 @@ abstract class ControllerTest extends BaseTest
* Assert that calling controller function with these parameters returns an HTTP 500 error.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param mixed[] $params Query parameters
*
* @return void
*/
@ -133,7 +137,7 @@ abstract class ControllerTest extends BaseTest
* Assert that calling controller function with these parameters returns an HTTP 400 error.
*
* @param string $request Controller function to call
* @param array $params Query parameters
* @param mixed[] $params Query parameters
*
* @return void
*/

View file

@ -8,6 +8,7 @@ namespace Alltube\Test;
use Alltube\Stream\ConvertedPlaylistArchiveStream;
use Alltube\Video;
use Exception;
/**
* Unit tests for the ConvertedPlaylistArchiveStream class.
@ -17,6 +18,7 @@ class ConvertedPlaylistArchiveStreamTest extends StreamTest
{
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{

View file

@ -8,6 +8,7 @@ namespace Alltube\Test;
use Alltube\Config;
use Alltube\Controller\DownloadController;
use Exception;
/**
* Unit tests for the FrontController class.
@ -17,6 +18,7 @@ class DownloadControllerTest extends ControllerTest
{
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{
@ -62,6 +64,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with streams enabled.
*
* @return void
* @throws Exception
*/
public function testDownloadWithStream()
{
@ -77,6 +80,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with an M3U stream.
*
* @return void
* @throws Exception
*/
public function testDownloadWithM3uStream()
{
@ -96,6 +100,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with an RTMP stream.
*
* @return void
* @throws Exception
*/
public function testDownloadWithRtmpStream()
{
@ -113,6 +118,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with a remuxed video.
*
* @return void
* @throws Exception
*/
public function testDownloadWithRemux()
{
@ -182,6 +188,7 @@ class DownloadControllerTest extends ControllerTest
*
* @return void
* @requires OS Linux
* @throws Exception
*/
public function testDownloadWithPlaylist()
{
@ -197,6 +204,7 @@ class DownloadControllerTest extends ControllerTest
* Test the download() function with an advanced conversion.
*
* @return void
* @throws Exception
*/
public function testDownloadWithAdvancedConversion()
{

View file

@ -17,8 +17,15 @@ use Slim\Http\Request;
*/
class FrontControllerTest extends ControllerTest
{
/**
* Controller instance used in tests.
* @var FrontController
*/
protected $controller;
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{
@ -41,6 +48,7 @@ class FrontControllerTest extends ControllerTest
* Test the constructor with streams enabled.
*
* @return void
* @throws Exception
*/
public function testConstructorWithStream()
{
@ -120,6 +128,7 @@ class FrontControllerTest extends ControllerTest
*
* @return void
* @requires download
* @throws Exception
*/
public function testInfoWithAudio()
{
@ -136,6 +145,7 @@ class FrontControllerTest extends ControllerTest
*
* @return void
* @requires download
* @throws Exception
*/
public function testInfoWithVimeoAudio()
{
@ -150,6 +160,7 @@ class FrontControllerTest extends ControllerTest
*
* @return void
* @requires download
* @throws Exception
*/
public function testInfoWithUnconvertedAudio()
{
@ -197,6 +208,7 @@ class FrontControllerTest extends ControllerTest
*
* @return void
* @requires download
* @throws Exception
*/
public function testInfoWithStream()
{

View file

@ -7,6 +7,7 @@
namespace Alltube\Test;
use Alltube\Controller\JsonController;
use Exception;
/**
* Unit tests for the FrontController class.
@ -15,6 +16,7 @@ class JsonControllerTest extends ControllerTest
{
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{

View file

@ -6,7 +6,6 @@
namespace Alltube\Test;
use Alltube\Locale;
use Alltube\LocaleManager;
use Alltube\LocaleMiddleware;
use Slim\Container;

View file

@ -8,6 +8,7 @@ namespace Alltube\Test;
use Alltube\Stream\PlaylistArchiveStream;
use Alltube\Video;
use Exception;
/**
* Unit tests for the PlaylistArchiveStream class.
@ -17,6 +18,7 @@ class PlaylistArchiveStreamTest extends StreamTest
{
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{

View file

@ -6,6 +6,7 @@
namespace Alltube\Test;
use Psr\Http\Message\StreamInterface;
use RuntimeException;
/**
@ -15,6 +16,7 @@ abstract class StreamTest extends BaseTest
{
/**
* Stream instance.
* @var StreamInterface
*/
protected $stream;
@ -36,7 +38,7 @@ abstract class StreamTest extends BaseTest
public function testWrite()
{
if ($this->stream->isWritable()) {
$this->assertNull($this->stream->write('foo'));
$this->assertIsInt($this->stream->write('foo'));
} else {
$this->expectException(RuntimeException::class);
$this->stream->write('foo');
@ -103,7 +105,7 @@ abstract class StreamTest extends BaseTest
*/
public function testEof()
{
$this->assertFalse($this->stream->eof());
$this->assertIsBool($this->stream->eof());
}
/**

View file

@ -26,6 +26,7 @@ class VideoStubsTest extends BaseTest
/**
* Initialize properties used by test.
* @throws Exception
*/
protected function setUp(): void
{

View file

@ -7,6 +7,8 @@
namespace Alltube\Test;
use Alltube\Config;
use Alltube\Exception\EmptyUrlException;
use Alltube\Exception\PasswordException;
use Alltube\Video;
use Exception;
@ -20,6 +22,7 @@ class VideoTest extends BaseTest
* Test getExtractors function.
*
* @return void
* @throws PasswordException
*/
public function testGetExtractors()
{
@ -36,6 +39,8 @@ class VideoTest extends BaseTest
* @param string $domain Domain
*
* @return void
* @throws PasswordException
* @throws EmptyUrlException
* @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @dataProvider remuxUrlProvider
@ -57,6 +62,8 @@ class VideoTest extends BaseTest
* Test getUrl function with a protected video.
*
* @return void
* @throws EmptyUrlException
* @throws PasswordException
*/
public function testgetUrlWithPassword()
{
@ -70,6 +77,8 @@ class VideoTest extends BaseTest
* Test getUrl function with a protected video and no password.
*
* @return void
* @throws EmptyUrlException
* @throws PasswordException
*/
public function testgetUrlWithMissingPassword()
{
@ -82,6 +91,8 @@ class VideoTest extends BaseTest
* Test getUrl function with a protected video and a wrong password.
*
* @return void
* @throws EmptyUrlException
* @throws PasswordException
*/
public function testgetUrlWithWrongPassword()
{
@ -96,6 +107,8 @@ class VideoTest extends BaseTest
* @param string $url URL
*
* @return void
* @throws EmptyUrlException
* @throws PasswordException
* @dataProvider ErrorUrlProvider
*/
public function testgetUrlError($url)
@ -112,7 +125,7 @@ class VideoTest extends BaseTest
*/
public function urlProvider()
{
$videos = [
return [
[
'https://www.youtube.com/watch?v=M7IpKCZ47pU', 'best[protocol^=http]',
'It_s_Not_Me_It_s_You_-_Hearts_Under_Fire-M7IpKCZ47pU',
@ -136,11 +149,9 @@ class VideoTest extends BaseTest
'https://vimeo.com/24195442', 'http-720p',
'Carving_the_Mountains-24195442',
'mp4',
'gcs-vimeo.akamaized.net',
'akamaized.net',
]
];
return $videos;
}
/**
@ -167,7 +178,7 @@ class VideoTest extends BaseTest
*/
public function m3uUrlProvider()
{
$videos = [
return [
[
'https://twitter.com/verge/status/813055465324056576/video/1', 'hls-2176',
'The_Verge_-_This_tiny_origami_robot_can_self-fold_and_complete_tasks-813055465324056576',
@ -175,8 +186,6 @@ class VideoTest extends BaseTest
'video.twimg.com',
]
];
return $videos;
}
/**
@ -217,6 +226,7 @@ class VideoTest extends BaseTest
* @return void
* @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @throws PasswordException
*/
public function testGetJson($url, $format)
{
@ -237,6 +247,7 @@ class VideoTest extends BaseTest
*
* @return void
* @dataProvider ErrorURLProvider
* @throws PasswordException
*/
public function testGetJsonError($url)
{
@ -257,6 +268,7 @@ class VideoTest extends BaseTest
* @dataProvider urlProvider
* @dataProvider m3uUrlProvider
* @dataProvider remuxUrlProvider
* @throws PasswordException
*/
public function testGetFilename($url, $format, $filename, $extension)
{
@ -271,6 +283,7 @@ class VideoTest extends BaseTest
*
* @return void
* @dataProvider ErrorUrlProvider
* @throws PasswordException
*/
public function testGetFilenameError($url)
{
@ -287,6 +300,7 @@ class VideoTest extends BaseTest
*
* @return void
* @dataProvider urlProvider
* @throws Exception
*/
public function testGetAudioStream($url, $format)
{
@ -376,6 +390,7 @@ class VideoTest extends BaseTest
*
* @return void
* @dataProvider m3uUrlProvider
* @throws Exception
*/
public function testGetM3uStream($url, $format)
{
@ -391,6 +406,7 @@ class VideoTest extends BaseTest
*
* @return void
* @dataProvider remuxUrlProvider
* @throws Exception
*/
public function testGetRemuxStream($url, $format)
{
@ -422,6 +438,7 @@ class VideoTest extends BaseTest
*
* @return void
* @dataProvider rtmpUrlProvider
* @throws Exception
*/
public function testGetRtmpStream($url, $format)
{
@ -458,6 +475,7 @@ class VideoTest extends BaseTest
*
* @return void
* @dataProvider urlProvider
* @throws Exception
*/
public function testGetConvertedStream($url, $format)
{

View file

@ -12,6 +12,7 @@ use Slim\Container;
use Slim\Http\Environment;
use Slim\Http\Request;
use Slim\Views\Smarty;
use SmartyException;
/**
* Unit tests for the ViewFactory class.
@ -22,6 +23,7 @@ class ViewFactoryTest extends BaseTest
* Test the create() function.
*
* @return void
* @throws SmartyException
*/
public function testCreate()
{
@ -35,6 +37,7 @@ class ViewFactoryTest extends BaseTest
* Test the create() function with a X-Forwarded-Proto header.
*
* @return void
* @throws SmartyException
*/
public function testCreateWithXForwardedProto()
{

View file

@ -8,6 +8,7 @@ namespace Alltube\Test;
use Alltube\Stream\YoutubeChunkStream;
use Alltube\Video;
use Exception;
/**
* Unit tests for the YoutubeChunkStream class.
@ -17,6 +18,7 @@ class YoutubeChunkStreamTest extends StreamTest
{
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{

View file

@ -8,6 +8,7 @@ namespace Alltube\Test;
use Alltube\Stream\YoutubeStream;
use Alltube\Video;
use Exception;
/**
* Unit tests for the YoutubeStream class.
@ -17,6 +18,7 @@ class YoutubeStreamTest extends StreamTest
{
/**
* Prepare tests.
* @throws Exception
*/
protected function setUp(): void
{

View file

@ -9,7 +9,7 @@ use phpmock\mockery\PHPMockery;
// Composer autoload.
require_once __DIR__ . '/../vendor/autoload.php';
ini_set('session.use_cookies', 0);
ini_set('session.use_cookies', '0');
session_cache_limiter('');
session_start();