diff --git a/.gitignore b/.gitignore index 0875093..950d07a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,9 @@ -youtube-dl -dist/ -node_modules/ -config.php vendor/ templates_c/ -ffmpeg.tar.xz -ffmpeg-*/ alltube-*.zip coverage/ -bower_components/ config/config.yml -docs/ clover.xml i18n/*/LC_MESSAGES/*.mo .phpunit.result.cache +.git/ diff --git a/.travis.yml b/.travis.yml index c3316a3..02d0a2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,6 @@ addons: - language-pack-fr install: composer install --no-progress script: + - composer check-platform-reqs - composer lint - composer test diff --git a/Dockerfile b/Dockerfile index 949dd54..e5ac935 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,13 @@ FROM php:7.3-apache RUN apt-get update -RUN apt-get install -y libicu-dev xz-utils git zlib1g-dev python libgmp-dev gettext libzip-dev +RUN apt-get install -y libicu-dev xz-utils git python libgmp-dev unzip ffmpeg RUN docker-php-ext-install mbstring RUN docker-php-ext-install intl -RUN docker-php-ext-install zip RUN docker-php-ext-install gmp -RUN docker-php-ext-install gettext RUN a2enmod rewrite -RUN curl -sS https://getcomposer.org/installer | php +RUN curl -sS https://getcomposer.org/installer | php -- --quiet COPY resources/php.ini /usr/local/etc/php/ COPY . /var/www/html/ -RUN php composer.phar install --prefer-dist --no-progress +RUN php composer.phar check-platform-reqs --no-dev +RUN php composer.phar install --prefer-dist --no-progress --no-dev --optimize-autoloader ENV CONVERT=1 diff --git a/README.md b/README.md index dc606a4..e5f3151 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,6 @@ composer install This will download all the required dependencies. -(Note that it will download the ffmpeg binary for 64-bits Linux. -If you are on another platform, -you might want to specify the path to avconv/ffmpeg in your config file.) - You should also ensure that the *templates_c* folder has the right permissions: ```bash @@ -66,19 +62,22 @@ cp config/config.example.yml config/config.yml You will need PHP 7.2 (or higher) and the following PHP modules: -* fileinfo * intl * mbstring -* curl +* gmp ## Web server configuration ### Apache -You will need the following modules: +The following modules are recommended: * mod_mime * mod_rewrite +* mod_expires +* mod_filter +* mod_deflate +* mod_headers ### Nginx @@ -133,47 +132,22 @@ server { ## Other dependencies -You need [avconv](https://libav.org/avconv.html) +You need [ffmpeg](https://ffmpeg.org/) in order to enable conversions. -If you don't want to enable conversions, you can disable it in `config.yml`. +(Conversions are disabled by default.) On Debian-based systems: ```bash -sudo apt-get install libav-tools +sudo apt-get install ffmpeg ``` -You also probably need to edit the `avconv` variable in `config.yml` -so that it points to your ffmpeg/avconv binary (`/usr/bin/avconv` on Debian/Ubuntu). +If your ffmpeg binary is not installed at `/usr/bin/ffmpeg`, you also need to edit the `ffmpeg` variable in `config.yml`. -## Use as library +## Use as a library -AllTube can also be used as a library to extract a video URL from a webpage. - -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). +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. ## JSON API diff --git a/RoboFile.php b/RoboFile.php index a3a53af..53f97fd 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -1,7 +1,6 @@ stopOnFail(); + $result = $this->taskExec('git') - ->args('describe') - ->printOutput(false) + ->arg('describe') ->run(); $result->provideOutputdata(); - $tag = $result->getOutputData(); - // We don't want the whole vendor directory. - $finder = new Finder(); - $finder->files() - ->in(__DIR__ . '/vendor/') - ->exclude( - [ - 'ffmpeg/', - 'bin/', - 'anam/phantomjs-linux-x86-binary/', - 'phpunit/', - 'squizlabs/', - 'rinvex/countries/resources/geodata/', - 'rinvex/countries/resources/flags/' - ] - ); + $tmpDir = $this->_tmpDir(); - $zipTask = $this->taskPack('alltube-' . $tag . '.zip') - ->add('index.php') - ->add('config/config.example.yml') - ->add('.htaccess') - ->add('img') - ->add('LICENSE') - ->add('README.md') - ->add('robots.txt') - ->add('resources') - ->add('templates') - ->add('templates_c/') - ->add('classes') - ->add('controllers') - ->add('css') - ->add('i18n'); + $filename = 'alltube-' . trim($result->getOutputData()) . '.zip'; - foreach ($finder as $file) { - if ($path = $file->getRelativePathname()) { - $zipTask->add('vendor/' . $path); - } - } + $this->taskFilesystemStack() + ->remove($filename) + ->run(); - $zipTask->run(); + $this->taskGitStack() + ->cloneRepo(__DIR__, $tmpDir) + ->run(); + + $this->taskComposerInstall() + ->dir($tmpDir) + ->optimizeAutoloader() + ->noDev() + ->run(); + + $this->taskPack($filename) + ->addDir('alltube', $tmpDir) + ->run(); } } diff --git a/app.json b/app.json index 60c06c2..32b2202 100644 --- a/app.json +++ b/app.json @@ -19,10 +19,6 @@ } ], "env": { - "CONVERT": { - "description": "Enable audio conversion", - "value": "true" - }, "PYTHON": { "description": "Path to python binary", "value": "/app/.heroku/python/bin/python" diff --git a/classes/Config.php b/classes/Config.php index c68d5b3..0460765 100644 --- a/classes/Config.php +++ b/classes/Config.php @@ -6,8 +6,12 @@ namespace Alltube; -use Exception; +use Alltube\Exception\ConfigException; +use Alltube\Library\Downloader; use Jawira\CaseConverter\CaseConverterException; +use Jean85\PrettyVersions; +use PackageVersions\Versions; +use Symfony\Component\ErrorHandler\Debug; use Symfony\Component\Yaml\Yaml; use Jawira\CaseConverter\Convert; @@ -66,18 +70,18 @@ class Config public $convertAdvancedFormats = ['mp3', 'avi', 'flv', 'wav']; /** - * avconv or ffmpeg binary path. + * ffmpeg binary path. * * @var string */ - public $avconv = 'vendor/bin/ffmpeg'; + public $ffmpeg = '/usr/bin/ffmpeg'; /** * Path to the directory that contains the phantomjs binary. * * @var string */ - public $phantomjsDir = 'vendor/bin/'; + public $phantomjsDir = '/usr/bin/'; /** * Disable URL rewriting. @@ -108,12 +112,12 @@ class Config public $audioBitrate = 128; /** - * avconv/ffmpeg logging level. + * ffmpeg logging level. * Must be one of these: quiet, panic, fatal, error, warning, info, verbose, debug. * * @var string */ - public $avconvVerbosity = 'error'; + public $ffmpegVerbosity = 'error'; /** * App name. @@ -140,7 +144,7 @@ class Config * Config constructor. * * @param mixed[] $options Options - * @throws CaseConverterException + * @throws ConfigException */ private function __construct(array $options = []) { @@ -151,9 +155,9 @@ class Config if (empty($this->genericFormats)) { // We don't put this in the class definition so it can be detected by xgettext. $this->genericFormats = [ - 'best' => $localeManager->t('Best'), + 'best/bestvideo' => $localeManager->t('Best'), 'bestvideo+bestaudio' => $localeManager->t('Remux best video with best audio'), - 'worst' => $localeManager->t('Worst'), + 'worst/worstvideo' => $localeManager->t('Worst'), ]; } @@ -165,46 +169,51 @@ class Config } } elseif (!$this->stream) { // Force HTTP if stream is not enabled. - $this->replaceGenericFormat($format, $format . '[protocol=https]/' . $format . '[protocol=http]'); + $keys = array_keys($this->genericFormats); + $keys[array_search($format, $keys)] = $this->addHttpToFormat($format); + if ($genericFormats = array_combine($keys, $this->genericFormats)) { + $this->genericFormats = $genericFormats; + } } } } /** - * Replace a format key. + * Add HTTP condition to a format. * - * @param string $oldFormat Old format - * @param string $newFormat New format + * @param string $format Format * - * @return void + * @return string */ - private function replaceGenericFormat($oldFormat, $newFormat) + public static function addHttpToFormat($format) { - $keys = array_keys($this->genericFormats); - $keys[array_search($oldFormat, $keys)] = $newFormat; - if ($genericFormats = array_combine($keys, $this->genericFormats)) { - $this->genericFormats = $genericFormats; + $newFormat = []; + foreach (explode('/', $format) as $subformat) { + $newFormat[] = $subformat . '[protocol=https]'; + $newFormat[] = $subformat . '[protocol=http]'; } + + return implode('/', $newFormat); } /** * Throw an exception if some of the options are invalid. * * @return void - * @throws Exception If Python is missing - * - * @throws Exception If youtube-dl is missing + * @throws ConfigException If Python is missing + * @throws ConfigException If youtube-dl is missing */ 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)) { - throw new Exception("Can't find youtube-dl at " . $this->youtubedl); - } elseif (!Video::checkCommand([$this->python, '--version'])) { - throw new Exception("Can't find Python at " . $this->python); + throw new ConfigException("Can't find youtube-dl at " . $this->youtubedl); + } elseif (!Downloader::checkCommand([$this->python, '--version'])) { + throw new ConfigException("Can't find Python at " . $this->python); + } + + if (!class_exists(Debug::class)) { + // Dev dependencies are probably not installed. + $this->debug = false; } } @@ -225,18 +234,23 @@ class Config } /** - * Override options from environement variables. + * Override options from environment variables. * Environment variables should use screaming snake case: CONVERT, PYTHON, AUDIO_BITRATE, etc. * If the value is an array, you should use the YAML format: "CONVERT_ADVANCED_FORMATS='[foo, bar]'" * * @return void - * @throws CaseConverterException + * @throws ConfigException */ private function getEnv() { foreach (get_object_vars($this) as $prop => $value) { - $convert = new Convert($prop); - $env = getenv($convert->toMacro()); + try { + $convert = new Convert($prop); + $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) { $this->$prop = Yaml::parse($env); } @@ -262,7 +276,7 @@ class Config * * @param string $file Path to the YAML file * @return void - * @throws Exception + * @throws ConfigException */ public static function setFile($file) { @@ -271,7 +285,7 @@ class Config self::$instance = new self($options); self::$instance->validateOptions(); } else { - throw new Exception("Can't find config file at " . $file); + throw new ConfigException("Can't find config file at " . $file); } } @@ -281,7 +295,7 @@ class Config * @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 + * @throws ConfigException */ public static function setOptions(array $options, $update = true) { @@ -303,4 +317,31 @@ class Config { 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->ffmpeg, + $this->phantomjsDir, + $this->ffmpegVerbosity + ); + } + + /** + * @return string + */ + public function getAppVersion() + { + $version = PrettyVersions::getVersion(Versions::ROOT_PACKAGE_NAME); + + return $version->getPrettyVersion(); + } } diff --git a/classes/LocaleManager.php b/classes/LocaleManager.php index 813ea79..df7661c 100644 --- a/classes/LocaleManager.php +++ b/classes/LocaleManager.php @@ -7,6 +7,7 @@ namespace Alltube; use Aura\Session\Segment; +use Symfony\Component\Finder\Finder; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Loader\PoFileLoader; @@ -15,12 +16,11 @@ use Symfony\Component\Translation\Loader\PoFileLoader; */ class LocaleManager { + /** - * Supported locales. - * - * @var string[] + * Path to locales. */ - private $supportedLocales = ['en_US', 'fr_FR', 'zh_CN', 'es_ES', 'pt_BR', 'de_DE', 'ar', 'pl_PL', 'tr_TR']; + private const PATH = __DIR__ . '/../i18n/'; /** * Current locale. @@ -75,7 +75,7 @@ class LocaleManager foreach ($this->getSupportedLocales() as $locale) { $this->translator->addResource( 'gettext', - __DIR__ . '/../i18n/' . $locale->getIso15897() . '/LC_MESSAGES/Alltube.po', + self::PATH . $locale->getIso15897() . '/LC_MESSAGES/Alltube.po', $locale->getIso15897() ); } @@ -88,10 +88,17 @@ class LocaleManager */ public function getSupportedLocales() { - $return = []; + $return = [ + new Locale('en_US') + ]; - foreach ($this->supportedLocales as $supportedLocale) { - $return[] = new Locale($supportedLocale); + $finder = new Finder(); + $finder->depth(0) + ->directories() + ->in(self::PATH); + + foreach ($finder as $file) { + $return[] = new Locale($file->getFilename()); } return $return; diff --git a/classes/UglyRouter.php b/classes/UglyRouter.php index f03efe7..a5b1020 100644 --- a/classes/UglyRouter.php +++ b/classes/UglyRouter.php @@ -48,7 +48,6 @@ class UglyRouter extends Router * * @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 = []) diff --git a/classes/Video.php b/classes/Video.php deleted file mode 100644 index f88a088..0000000 --- a/classes/Video.php +++ /dev/null @@ -1,647 +0,0 @@ -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 - */ - 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 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) - ] - ); - } -} diff --git a/classes/exceptions/ConfigException.php b/classes/exceptions/ConfigException.php new file mode 100644 index 0000000..f533b35 --- /dev/null +++ b/classes/exceptions/ConfigException.php @@ -0,0 +1,10 @@ +curVideoStream = new Stream($video->getAudioStream()); + $this->curVideoStream = new Stream($this->downloader->getAudioStream($video)); $this->init_file_stream_transfer( $video->getFileNameWithExtension('mp3'), diff --git a/classes/streams/PlaylistArchiveStream.php b/classes/streams/PlaylistArchiveStream.php index 2e682fb..d55e554 100644 --- a/classes/streams/PlaylistArchiveStream.php +++ b/classes/streams/PlaylistArchiveStream.php @@ -6,9 +6,9 @@ namespace Alltube\Stream; -use Alltube\Exception\EmptyUrlException; -use Alltube\Exception\PasswordException; -use Alltube\Video; +use Alltube\Library\Downloader; +use Alltube\Library\Exception\AlltubeLibraryException; +use Alltube\Library\Video; use Barracuda\ArchiveStream\ZipArchive; use Psr\Http\Message\StreamInterface; @@ -47,22 +47,32 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface */ private $isComplete = false; + /** + * Downloader object. + * + * @var Downloader + */ + protected $downloader; + /** * PlaylistArchiveStream constructor. * * 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 * @noinspection PhpMissingParentConstructorInspection */ - public function __construct(Video $video) + public function __construct(Downloader $downloader, Video $video) { + $this->downloader = $downloader; + $buffer = fopen('php://temp', 'r+'); if ($buffer !== false) { $this->buffer = $buffer; } 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 * * @return void - * @throws PasswordException - * @throws EmptyUrlException + * @throws AlltubeLibraryException */ protected function startVideoStream(Video $video) { - $response = $video->getHttpResponse(); + $response = $this->downloader->getHttpResponse($video); $this->curVideoStream = $response->getBody(); $contentLengthHeaders = $response->getHeader('Content-Length'); @@ -266,8 +275,7 @@ class PlaylistArchiveStream extends ZipArchive implements StreamInterface * @param int $count Number of bytes to read * * @return string|false - * @throws EmptyUrlException - * @throws PasswordException + * @throws AlltubeLibraryException */ public function read($count) { diff --git a/classes/streams/YoutubeStream.php b/classes/streams/YoutubeStream.php index 3995d11..fdd19dd 100644 --- a/classes/streams/YoutubeStream.php +++ b/classes/streams/YoutubeStream.php @@ -6,9 +6,9 @@ namespace Alltube\Stream; -use Alltube\Exception\EmptyUrlException; -use Alltube\Exception\PasswordException; -use Alltube\Video; +use Alltube\Library\Downloader; +use Alltube\Library\Exception\AlltubeLibraryException; +use Alltube\Library\Video; use GuzzleHttp\Psr7\AppendStream; /** @@ -20,15 +20,15 @@ class YoutubeStream extends AppendStream /** * YoutubeStream constructor. * + * @param Downloader $downloader Downloader object * @param Video $video Video to stream - * @throws EmptyUrlException - * @throws PasswordException + * @throws AlltubeLibraryException */ - public function __construct(Video $video) + public function __construct(Downloader $downloader, Video $video) { parent::__construct(); - $stream = $video->getHttpResponse(); + $stream = $downloader->getHttpResponse($video); $contentLenghtHeader = $stream->getHeader('Content-Length'); $rangeStart = 0; @@ -37,7 +37,7 @@ class YoutubeStream extends AppendStream if ($rangeEnd >= $contentLenghtHeader[0]) { $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)); $rangeStart = $rangeEnd + 1; } diff --git a/composer.json b/composer.json index 0bd4fc6..df69396 100644 --- a/composer.json +++ b/composer.json @@ -9,21 +9,20 @@ "ext-json": "*", "aura/session": "^2.1", "barracudanetworks/archivestream-php": "^1.0", - "guzzlehttp/guzzle": "^6.5", "jawira/case-converter": "^3.4", + "jean85/pretty-package-versions": "^1.3", "mathmarques/smarty-view": "^1.1", "npm-asset/open-sans-fontface": "^1.4", "rinvex/countries": "^6.1", - "symfony/process": "^4.0", + "rudloff/alltube-library": "^0.1.0", + "symfony/finder": "^5.0", "symfony/translation": "^4.0", "symfony/yaml": "^4.0", + "ytdl-org/youtube-dl": "^2020.06", "zonuexe/http-accept-language": "^0.4.1" }, "require-dev": { - "anam/phantomjs-linux-x86-binary": "^2.1", - "consolidation/robo": "^2.0", - "ffmpeg/ffmpeg": "^4.1", - "heroku/heroku-buildpack-php": "^162.0", + "consolidation/robo": "^2.1", "php-mock/php-mock-mockery": "^1.3", "phpro/grumphp": "^0.18.0", "phpstan/phpstan": "^0.12.25", @@ -32,15 +31,7 @@ "smarty-gettext/smarty-gettext": "^1.6", "squizlabs/php_codesniffer": "^3.5", "symfony/error-handler": "^5.0", - "symfony/var-dumper": "^5.0", - "ytdl-org/youtube-dl": "^2020.05" - }, - "extra": { - "paas": { - "nginx-includes": [ - "resources/nginx.conf" - ] - } + "symfony/var-dumper": "^5.0" }, "repositories": [ { @@ -51,26 +42,12 @@ "type": "package", "package": { "name": "ytdl-org/youtube-dl", - "version": "2020.05.08", + "version": "2020.06.16.1", "dist": { "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": "package", - "package": { - "name": "ffmpeg/ffmpeg", - "version": "4.1.4", - "dist": { - "url": "https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.1.4-amd64-static.tar.xz", - "type": "xz" - }, - "bin": [ - "ffmpeg" - ] - } } ], "authors": [ @@ -97,10 +74,9 @@ } }, "scripts": { - "compile": "composer install --ignore-platform-reqs", "lint": "grumphp run --ansi", "test": "phpunit", - "release": "robo release", + "release": "robo release --ansi", "update-locales": [ "tsmarty2c.php templates > i18n/template.pot", "xgettext --omit-header -kt -j -o i18n/template.pot classes/*.php classes/*/*.php controllers/*" diff --git a/composer.lock b/composer.lock index 15ca205..44749d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5fdb499464d2fcf04d97dca8c099a075", + "content-hash": "3c17df3876a2c769f866f845e4491763", "packages": [ { "name": "aura/session", @@ -109,17 +109,72 @@ "time": "2018-08-10T13:58:33+00:00" }, { - "name": "guzzlehttp/guzzle", - "version": "6.5.3", + "name": "composer/package-versions-deprecated", + "version": "1.8.1", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e" + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b9805885293f3957ee0dd42616ac6915c4ac9a4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e", - "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b9805885293f3957ee0dd42616ac6915c4ac9a4b", + "reference": "b9805885293f3957ee0dd42616ac6915c4ac9a4b", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7" + }, + "replace": { + "ocramius/package-versions": "1.8.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "time": "2020-06-19T07:59:31+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", "shasum": "" }, "require": { @@ -127,7 +182,7 @@ "guzzlehttp/promises": "^1.0", "guzzlehttp/psr7": "^1.6.1", "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.11" + "symfony/polyfill-intl-idn": "^1.17.0" }, "require-dev": { "ext-curl": "*", @@ -173,7 +228,7 @@ "rest", "web service" ], - "time": "2020-04-18T10:38:46+00:00" + "time": "2020-06-16T21:01:06+00:00" }, { "name": "guzzlehttp/promises", @@ -355,6 +410,57 @@ ], "time": "2019-12-15T14:31:43+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "e3517fb11b67e798239354fe8213927d012ad8f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/e3517fb11b67e798239354fe8213927d012ad8f9", + "reference": "e3517fb11b67e798239354fe8213927d012ad8f9", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.8.0", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A wrapper for ocramius/package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "time": "2020-04-24T14:19:45+00:00" + }, { "name": "mathmarques/smarty-view", "version": "1.1.2", @@ -456,7 +562,7 @@ "version": "1.4.2", "source": { "type": "git", - "url": "git@github.com:FontFaceKit/open-sans.git", + "url": "https://github.com/FontFaceKit/open-sans.git", "reference": "2285c0300e6a4c8b102b98fb030fb38c26aa081c" }, "dist": { @@ -736,6 +842,47 @@ ], "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", "version": "3.12.3", @@ -863,17 +1010,66 @@ "time": "2018-09-12T20:54:16+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.13.1", + "name": "symfony/finder", + "version": "v5.0.8", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" + "url": "https://github.com/symfony/finder.git", + "reference": "600a52c29afc0d1caa74acbec8d3095ca7e9910d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", - "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "url": "https://api.github.com/repos/symfony/finder/zipball/600a52c29afc0d1caa74acbec8d3095ca7e9910d", + "reference": "600a52c29afc0d1caa74acbec8d3095ca7e9910d", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2020-03-27T16:56:45+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", "shasum": "" }, "require": { @@ -885,7 +1081,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -918,7 +1114,7 @@ "polyfill", "portable" ], - "time": "2019-11-27T13:56:44+00:00" + "time": "2020-05-12T16:14:59+00:00" }, { "name": "symfony/polyfill-intl-idn", @@ -984,16 +1180,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.13.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", - "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", "shasum": "" }, "require": { @@ -1005,7 +1201,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -1039,20 +1235,20 @@ "portable", "shim" ], - "time": "2019-11-27T14:18:11+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.13.1", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" + "reference": "f048e612a3905f34931127360bdd2def19a5e582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", - "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", "shasum": "" }, "require": { @@ -1061,7 +1257,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -1094,20 +1290,20 @@ "portable", "shim" ], - "time": "2019-11-27T13:56:44+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/process", - "version": "v4.4.0", + "version": "v4.4.10", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "75ad33d9b6f25325ebc396d68ad86fd74bcfbb06" + "reference": "c714958428a85c86ab97e3a0c96db4c4f381b7f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/75ad33d9b6f25325ebc396d68ad86fd74bcfbb06", - "reference": "75ad33d9b6f25325ebc396d68ad86fd74bcfbb06", + "url": "https://api.github.com/repos/symfony/process/zipball/c714958428a85c86ab97e3a0c96db4c4f381b7f5", + "reference": "c714958428a85c86ab97e3a0c96db4c4f381b7f5", "shasum": "" }, "require": { @@ -1143,7 +1339,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-10-28T20:30:34+00:00" + "time": "2020-05-30T20:06:45+00:00" }, { "name": "symfony/translation", @@ -1337,6 +1533,15 @@ "homepage": "https://symfony.com", "time": "2019-11-12T14:51:11+00:00" }, + { + "name": "ytdl-org/youtube-dl", + "version": "2020.06.16.1", + "dist": { + "type": "zip", + "url": "https://github.com/ytdl-org/youtube-dl/archive/2020.06.16.1.zip" + }, + "type": "library" + }, { "name": "zonuexe/http-accept-language", "version": "0.4.1", @@ -1380,78 +1585,48 @@ } ], "packages-dev": [ - { - "name": "anam/phantomjs-linux-x86-binary", - "version": "v2.1.1", - "source": { - "type": "git", - "url": "https://github.com/anam-hossain/phantomjs-linux-x86-binary.git", - "reference": "cb90cd0e7bd4ad34f52e6834783b1c5adae15014" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/anam-hossain/phantomjs-linux-x86-binary/zipball/cb90cd0e7bd4ad34f52e6834783b1c5adae15014", - "reference": "cb90cd0e7bd4ad34f52e6834783b1c5adae15014", - "shasum": "" - }, - "bin": [ - "bin/phantomjs" - ], - "type": "library", - "autoload": { - "psr-4": { - "Anam\\PhantomLinux\\": "/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Anam hossain", - "email": "enam33@gmail.com" - } - ], - "description": "PhantomJS static linked binary for 64 bit linux systems. Most of Linux distributions are supported including Ubuntu, Debian, Fedora and CentOS.", - "homepage": "http://phantomjs.org/download.html", - "keywords": [ - "binary", - "phantomjs", - "phantomjs binary" - ], - "time": "2016-04-05T05:24:48+00:00" - }, { "name": "consolidation/annotated-command", - "version": "4.0.0", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "be011ba6314d76cabf78afbeb275694795c88360" + "reference": "efc58dc0f34a45539787c5190b41b5d2a50a08da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/be011ba6314d76cabf78afbeb275694795c88360", - "reference": "be011ba6314d76cabf78afbeb275694795c88360", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/efc58dc0f34a45539787c5190b41b5d2a50a08da", + "reference": "efc58dc0f34a45539787c5190b41b5d2a50a08da", "shasum": "" }, "require": { - "consolidation/output-formatters": "^4", + "consolidation/output-formatters": "^4.1.1", "php": ">=7.1.3", - "psr/log": "^1", - "symfony/console": "^4", - "symfony/event-dispatcher": "^4", - "symfony/finder": "^4" + "psr/log": "^1|^2", + "symfony/console": "^4.4.8|^5", + "symfony/event-dispatcher": "^4.4.8|^5", + "symfony/finder": "^4.4.8|^5" }, "require-dev": { "g1a/composer-test-scenarios": "^3", - "php-coveralls/php-coveralls": "^1", + "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^6", - "squizlabs/php_codesniffer": "^2.7" + "squizlabs/php_codesniffer": "^3" }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + } + }, "branch-alias": { "dev-master": "4.x-dev" } @@ -1472,34 +1647,35 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2019-10-29T13:44:48+00:00" + "time": "2020-05-27T21:11:36+00:00" }, { "name": "consolidation/config", - "version": "1.2.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/consolidation/config.git", - "reference": "cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1" + "reference": "9842670aad3406dbc8df3069fd680a9f8cd6edd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1", - "reference": "cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1", + "url": "https://api.github.com/repos/consolidation/config/zipball/9842670aad3406dbc8df3069fd680a9f8cd6edd7", + "reference": "9842670aad3406dbc8df3069fd680a9f8cd6edd7", "shasum": "" }, "require": { "dflydev/dot-access-data": "^1.1.0", "grasmash/expander": "^1", - "php": ">=5.4.0" + "php": ">=7.1.3" }, "require-dev": { "g1a/composer-test-scenarios": "^3", - "php-coveralls/php-coveralls": "^1", - "phpunit/phpunit": "^5", - "squizlabs/php_codesniffer": "2.*", - "symfony/console": "^2.5|^3|^4", - "symfony/yaml": "^2.8.11|^3|^4" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^3", + "symfony/console": "^4|^5", + "symfony/event-dispatcher": "^4|^5", + "symfony/yaml": "^4|^5" }, "suggest": { "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" @@ -1509,32 +1685,17 @@ "scenarios": { "symfony4": { "require-dev": { - "symfony/console": "^4.0" + "symfony/console": "^4" }, "config": { "platform": { "php": "7.1.3" } } - }, - "symfony2": { - "require-dev": { - "symfony/console": "^2.8", - "symfony/event-dispatcher": "^2.8", - "phpunit/phpunit": "^4.8.36" - }, - "remove": [ - "php-coveralls/php-coveralls" - ], - "config": { - "platform": { - "php": "5.4.8" - } - } } }, "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -1553,78 +1714,49 @@ } ], "description": "Provide configuration services for a commandline tool.", - "time": "2019-03-03T19:37:04+00:00" + "time": "2020-05-27T17:11:23+00:00" }, { "name": "consolidation/log", - "version": "1.1.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/consolidation/log.git", - "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a" + "reference": "ba0bf6af1fbd09ed4dc18fc2f27b12ceff487cbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/b2e887325ee90abc96b0a8b7b474cd9e7c896e3a", - "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a", + "url": "https://api.github.com/repos/consolidation/log/zipball/ba0bf6af1fbd09ed4dc18fc2f27b12ceff487cbf", + "reference": "ba0bf6af1fbd09ed4dc18fc2f27b12ceff487cbf", "shasum": "" }, "require": { - "php": ">=5.4.5", + "php": ">=7.1.3", "psr/log": "^1.0", - "symfony/console": "^2.8|^3|^4" + "symfony/console": "^4|^5" }, "require-dev": { "g1a/composer-test-scenarios": "^3", - "php-coveralls/php-coveralls": "^1", + "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^6", - "squizlabs/php_codesniffer": "^2" + "squizlabs/php_codesniffer": "^3" }, "type": "library", "extra": { "scenarios": { "symfony4": { - "require": { - "symfony/console": "^4.0" + "require-dev": { + "symfony/console": "^4" }, "config": { "platform": { "php": "7.1.3" } } - }, - "symfony2": { - "require": { - "symfony/console": "^2.8" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.36" - }, - "remove": [ - "php-coveralls/php-coveralls" - ], - "config": { - "platform": { - "php": "5.4.8" - } - } - }, - "phpunit4": { - "require-dev": { - "phpunit/phpunit": "^4.8.36" - }, - "remove": [ - "php-coveralls/php-coveralls" - ], - "config": { - "platform": { - "php": "5.4.8" - } - } } }, "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -1643,41 +1775,53 @@ } ], "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", - "time": "2019-01-01T17:30:51+00:00" + "time": "2020-05-27T17:06:13+00:00" }, { "name": "consolidation/output-formatters", - "version": "4.0.0", + "version": "4.1.1", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "8e747762963ab48912fb75fc24dbe39175f47057" + "reference": "9deeddd6a916d0a756b216a8b40ce1016e17c0b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/8e747762963ab48912fb75fc24dbe39175f47057", - "reference": "8e747762963ab48912fb75fc24dbe39175f47057", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/9deeddd6a916d0a756b216a8b40ce1016e17c0b9", + "reference": "9deeddd6a916d0a756b216a8b40ce1016e17c0b9", "shasum": "" }, "require": { "dflydev/dot-access-data": "^1.1.0", - "php": ">=5.4.0", - "symfony/console": "^4", - "symfony/finder": "^4" + "php": ">=7.1.3", + "symfony/console": "^4|^5", + "symfony/finder": "^4|^5" }, "require-dev": { "g1a/composer-test-scenarios": "^3", - "php-coveralls/php-coveralls": "^1", - "phpunit/phpunit": "^5.7.27", - "squizlabs/php_codesniffer": "^2.7", + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^3", "symfony/var-dumper": "^4", - "victorjonsson/markdowndocs": "^1.3" + "symfony/yaml": "^4" }, "suggest": { "symfony/var-dumper": "For using the var_dump formatter" }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + } + }, "branch-alias": { "dev-master": "4.x-dev" } @@ -1698,54 +1842,49 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2019-10-29T13:39:04+00:00" + "time": "2020-05-27T20:51:17+00:00" }, { "name": "consolidation/robo", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "f21c960ebec8c94a85ead5dc22c1a7d581a24055" + "reference": "a0415a2663f6d9426d3cb9013446d3f00225d76d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/f21c960ebec8c94a85ead5dc22c1a7d581a24055", - "reference": "f21c960ebec8c94a85ead5dc22c1a7d581a24055", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/a0415a2663f6d9426d3cb9013446d3f00225d76d", + "reference": "a0415a2663f6d9426d3cb9013446d3f00225d76d", "shasum": "" }, "require": { - "consolidation/annotated-command": "^4", - "consolidation/config": "^1.2.1", - "consolidation/log": "^1.1.1", - "consolidation/output-formatters": "^4", - "consolidation/self-update": "^1", + "consolidation/annotated-command": "^4.1.1", + "consolidation/config": "^1.2.1|^2", + "consolidation/log": "^1.1.1|^2.0.1", + "consolidation/output-formatters": "^4.1.1", + "consolidation/self-update": "^1.2", "grasmash/yaml-expander": "^1.4", - "league/container": "^2.2", + "league/container": "^2.4.1", "php": ">=7.1.3", - "symfony/console": "^4.3.5", - "symfony/event-dispatcher": "^4", - "symfony/filesystem": "^4", - "symfony/finder": "^4", - "symfony/process": "^4" + "symfony/console": "^4.4.8|^5", + "symfony/event-dispatcher": "^4.4.8|^5", + "symfony/filesystem": "^4.4.8|^5", + "symfony/finder": "^4.4.8|^5", + "symfony/process": "^4.4.8|^5" }, "conflict": { "codegyre/robo": "*" }, "require-dev": { - "codeception/aspect-mock": "^3.0.2", - "codeception/base": "^3.1.2", - "codeception/phpunit-wrapper": "^7.7.1", - "codeception/stub": "^3", - "doctrine/annotations": "^1.8.0", "g1a/composer-test-scenarios": "^3", "natxet/cssmin": "3.0.4", - "nikic/php-parser": "^3.1.5", "patchwork/jsqueeze": "^2", "pear/archive_tar": "^1.4.4", - "php-coveralls/php-coveralls": "^1", + "php-coveralls/php-coveralls": "^2.2", "phpdocumentor/reflection-docblock": "^4.3.2", - "squizlabs/php_codesniffer": "^2.8" + "phpunit/phpunit": "^6.5.14", + "squizlabs/php_codesniffer": "^3" }, "suggest": { "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", @@ -1758,6 +1897,27 @@ ], "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.4.8", + "symfony/event-dispatcher": "^4.4.8", + "symfony/filesystem": "^4.4.8", + "symfony/finder": "^4.4.8", + "symfony/process": "^4.4.8", + "phpunit/phpunit": "^6", + "nikic/php-parser": "^2" + }, + "remove": [ + "codeception/phpunit-wrapper" + ], + "config": { + "platform": { + "php": "7.1.3" + } + } + } + }, "branch-alias": { "dev-master": "2.x-dev" } @@ -1778,26 +1938,26 @@ } ], "description": "Modern task runner", - "time": "2019-10-29T14:11:02+00:00" + "time": "2020-05-27T22:03:57+00:00" }, { "name": "consolidation/self-update", - "version": "1.1.5", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/consolidation/self-update.git", - "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54" + "reference": "dba6b2c0708f20fa3ba8008a2353b637578849b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/self-update/zipball/a1c273b14ce334789825a09d06d4c87c0a02ad54", - "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/dba6b2c0708f20fa3ba8008a2353b637578849b4", + "reference": "dba6b2c0708f20fa3ba8008a2353b637578849b4", "shasum": "" }, "require": { "php": ">=5.5.0", - "symfony/console": "^2.8|^3|^4", - "symfony/filesystem": "^2.5|^3|^4" + "symfony/console": "^2.8|^3|^4|^5", + "symfony/filesystem": "^2.5|^3|^4|^5" }, "bin": [ "scripts/release" @@ -1818,17 +1978,17 @@ "MIT" ], "authors": [ - { - "name": "Greg Anderson", - "email": "greg.1.anderson@greenknowe.org" - }, { "name": "Alexander Menk", "email": "menk@mestrona.net" + }, + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" } ], "description": "Provides a self:update command for Symfony Console applications.", - "time": "2018-10-28T01:52:03+00:00" + "time": "2020-04-13T02:49:20+00:00" }, { "name": "container-interop/container-interop", @@ -2047,18 +2207,6 @@ ], "time": "2019-10-21T16:45:58+00:00" }, - { - "name": "ffmpeg/ffmpeg", - "version": "4.1.4", - "dist": { - "type": "xz", - "url": "https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.1.4-amd64-static.tar.xz" - }, - "bin": [ - "ffmpeg" - ], - "type": "library" - }, { "name": "gitonomy/gitlib", "version": "v1.1.0", @@ -2258,50 +2406,6 @@ ], "time": "2016-01-20T08:20:44+00:00" }, - { - "name": "heroku/heroku-buildpack-php", - "version": "v162", - "source": { - "type": "git", - "url": "https://github.com/heroku/heroku-buildpack-php.git", - "reference": "343fa959d4c195beb7febe1996340624baf713b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/343fa959d4c195beb7febe1996340624baf713b9", - "reference": "343fa959d4c195beb7febe1996340624baf713b9", - "shasum": "" - }, - "bin": [ - "bin/heroku-hhvm-apache2", - "bin/heroku-hhvm-nginx", - "bin/heroku-php-apache2", - "bin/heroku-php-nginx" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Zuelke", - "email": "dz@heroku.com" - } - ], - "description": "Toolkit for starting a PHP application locally, with or without foreman, using the same config for PHP/HHVM and Apache2/Nginx as on Heroku", - "homepage": "https://github.com/heroku/heroku-buildpack-php", - "keywords": [ - "apache", - "apache2", - "foreman", - "heroku", - "hhvm", - "nginx", - "php" - ], - "time": "2019-09-27T22:17:17+00:00" - }, { "name": "league/container", "version": "2.4.1", @@ -3511,20 +3615,20 @@ }, { "name": "phpro/grumphp", - "version": "v0.18.0", + "version": "v0.18.1", "source": { "type": "git", "url": "https://github.com/phpro/grumphp.git", - "reference": "b3d80bbcbac8068f21ea5f5da18a4af01d2d471d" + "reference": "d07e59ebfdd48cf41d12b2af3670abcd1a2b01ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpro/grumphp/zipball/b3d80bbcbac8068f21ea5f5da18a4af01d2d471d", - "reference": "b3d80bbcbac8068f21ea5f5da18a4af01d2d471d", + "url": "https://api.github.com/repos/phpro/grumphp/zipball/d07e59ebfdd48cf41d12b2af3670abcd1a2b01ef", + "reference": "d07e59ebfdd48cf41d12b2af3670abcd1a2b01ef", "shasum": "" }, "require": { - "composer-plugin-api": "~1.0", + "composer-plugin-api": "~1.0 || ~2.0", "doctrine/collections": "~1.2", "ext-json": "*", "gitonomy/gitlib": "^1.0.3", @@ -3542,10 +3646,9 @@ "symfony/yaml": "~3.4 || ~4.0 || ~5.0" }, "require-dev": { - "brianium/paratest": "~3.1", - "composer/composer": "~1.9", + "brianium/paratest": "~3.1 || dev-master", + "composer/composer": "~1.9 || ^2.0@dev", "ergebnis/composer-normalize": "~2.1", - "friendsofphp/php-cs-fixer": "~2.16", "jakub-onderka/php-parallel-lint": "~1.0", "nikic/php-parser": "~3.1", "phpspec/phpspec": "~6.1", @@ -3612,7 +3715,7 @@ } ], "description": "A composer plugin that enables source code quality checks.", - "time": "2020-02-25T17:51:17+00:00" + "time": "2020-05-27T04:48:38+00:00" }, { "name": "phpspec/prophecy", @@ -4099,6 +4202,52 @@ ], "time": "2019-11-06T09:42:23+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/log", "version": "1.1.3", @@ -4152,12 +4301,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "5a342e2dc0408d026b97ee3176b5b406e54e3766" + "reference": "6d2e5ab854782830911ddd33b7d4649b9f18c10f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5a342e2dc0408d026b97ee3176b5b406e54e3766", - "reference": "5a342e2dc0408d026b97ee3176b5b406e54e3766", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/6d2e5ab854782830911ddd33b7d4649b9f18c10f", + "reference": "6d2e5ab854782830911ddd33b7d4649b9f18c10f", "shasum": "" }, "conflict": { @@ -4166,12 +4315,14 @@ "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amphp/artax": "<1.0.6|>=2,<2.0.6", "amphp/http": "<1.0.1", + "amphp/http-client": ">=4,<4.4", "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", "aws/aws-sdk-php": ">=3,<3.2.1", "bagisto/bagisto": "<0.1.5", - "barrelstrength/sprout-base-email": "<3.9", - "bolt/bolt": "<3.6.10", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", + "bolt/bolt": "<3.7.1", "brightlocal/phpwhois": "<=4.2.5", "buddypress/buddypress": "<5.1.2", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", @@ -4197,10 +4348,10 @@ "doctrine/mongodb-odm": ">=1,<1.0.2", "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", - "dolibarr/dolibarr": "<=10.0.6", + "dolibarr/dolibarr": "<11.0.4", "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", - "drupal/drupal": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", + "drupal/core": ">=7,<7.72|>=8,<8.8.8|>=8.9,<8.9.1|>=9,<9.0.1", + "drupal/drupal": ">=7,<7.72|>=8,<8.8.8|>=8.9,<8.9.1|>=9,<9.0.1", "endroid/qr-code-bundle": "<3.4.2", "enshrined/svg-sanitize": "<0.13.1", "erusev/parsedown": "<1.7.2", @@ -4210,8 +4361,9 @@ "ezsystems/ezplatform": ">=1.7,<1.7.9.1|>=1.13,<1.13.5.1|>=2.5,<2.5.4", "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6", "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2", + "ezsystems/ezplatform-kernel": ">=1,<1.0.2.1", "ezsystems/ezplatform-user": ">=1,<1.0.1", - "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.1|>=6,<6.7.9.1|>=6.8,<6.13.6.2|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.6.2", + "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.2|>=6,<6.7.9.1|>=6.8,<6.13.6.3|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.7.1", "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.14.1|>=2011,<2017.12.7.2|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3|>=2019.3,<2019.3.4.2", "ezsystems/repository-forms": ">=2.3,<2.3.2.1", "ezyang/htmlpurifier": "<4.1.1", @@ -4249,6 +4401,7 @@ "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", + "october/october": ">=1.0.319,<1.0.466", "onelogin/php-saml": "<2.10.4", "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", "openid/php-openid": "<2.3", @@ -4260,7 +4413,8 @@ "paypal/merchant-sdk-php": "<3.12", "pear/archive_tar": "<1.4.4", "phpfastcache/phpfastcache": ">=5,<5.0.13", - "phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6", + "phpmailer/phpmailer": "<6.1.6", + "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<4.9.2", "phpoffice/phpexcel": "<1.8.2", "phpoffice/phpspreadsheet": "<1.8", @@ -4275,6 +4429,7 @@ "propel/propel": ">=2-alpha.1,<=2-alpha.7", "propel/propel1": ">=1,<=1.7.1", "pusher/pusher-php-server": "<2.2.1", + "rainlab/debugbar-plugin": "<3.1", "robrichards/xmlseclibs": "<3.0.4", "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", @@ -4341,6 +4496,7 @@ "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "t3g/svg-sanitizer": "<1.0.3", "tecnickcom/tcpdf": "<6.2.22", "thelia/backoffice-default-template": ">=2.1,<2.1.2", "thelia/thelia": ">=2.1-beta.1,<2.1.3", @@ -4412,7 +4568,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2020-05-12T11:18:47+00:00" + "time": "2020-06-19T13:23:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -5250,41 +5406,41 @@ }, { "name": "symfony/console", - "version": "v4.4.0", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "35d9077f495c6d184d9930f7a7ecbd1ad13c7ab8" + "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/35d9077f495c6d184d9930f7a7ecbd1ad13c7ab8", - "reference": "35d9077f495c6d184d9930f7a7ecbd1ad13c7ab8", + "url": "https://api.github.com/repos/symfony/console/zipball/5fa1caadc8cdaa17bcfb25219f3b53fe294a9935", + "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", "symfony/service-contracts": "^1.1|^2" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3|>=5", + "symfony/dependency-injection": "<4.4", + "symfony/event-dispatcher": "<4.4", "symfony/lock": "<4.4", - "symfony/process": "<3.3" + "symfony/process": "<4.4" }, "provide": { "psr/log-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.3", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", "symfony/lock": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^4.3|^5.0" + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" }, "suggest": { "psr/log": "For using the console logger", @@ -5295,7 +5451,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5322,7 +5478,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-11-13T07:39:40+00:00" + "time": "2020-03-30T11:42:42+00:00" }, { "name": "symfony/dependency-injection", @@ -5454,37 +5610,37 @@ }, { "name": "symfony/event-dispatcher", - "version": "v4.4.0", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "ab1c43e17fff802bef0a898f3bc088ac33b8e0e1" + "reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ab1c43e17fff802bef0a898f3bc088ac33b8e0e1", - "reference": "ab1c43e17fff802bef0a898f3bc088ac33b8e0e1", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/24f40d95385774ed5c71dbf014edd047e2f2f3dc", + "reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/event-dispatcher-contracts": "^1.1" + "php": "^7.2.5", + "symfony/event-dispatcher-contracts": "^2" }, "conflict": { - "symfony/dependency-injection": "<3.4" + "symfony/dependency-injection": "<4.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" + "symfony/event-dispatcher-implementation": "2.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" + "symfony/stopwatch": "^4.4|^5.0" }, "suggest": { "symfony/dependency-injection": "", @@ -5493,7 +5649,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5520,33 +5676,33 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-11-08T22:40:51+00:00" + "time": "2020-03-27T16:56:45+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.7", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + "reference": "af23c2584d4577d54661c434446fb8fbed6025dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/af23c2584d4577d54661c434446fb8fbed6025dd", + "reference": "af23c2584d4577d54661c434446fb8fbed6025dd", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.2.5", + "psr/event-dispatcher": "^1" }, "suggest": { - "psr/event-dispatcher": "", "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -5578,30 +5734,30 @@ "interoperability", "standards" ], - "time": "2019-09-17T09:54:03+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/filesystem", - "version": "v4.4.0", + "version": "v5.0.8", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d12b01cba60be77b583c9af660007211e3909854" + "reference": "7cd0dafc4353a0f62e307df90b48466379c8cc91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d12b01cba60be77b583c9af660007211e3909854", - "reference": "d12b01cba60be77b583c9af660007211e3909854", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7cd0dafc4353a0f62e307df90b48466379c8cc91", + "reference": "7cd0dafc4353a0f62e307df90b48466379c8cc91", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.2.5", "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -5628,56 +5784,7 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-11-12T14:51:11+00:00" - }, - { - "name": "symfony/finder", - "version": "v4.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "ce8743441da64c41e2a667b8eb66070444ed911e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ce8743441da64c41e2a667b8eb66070444ed911e", - "reference": "ce8743441da64c41e2a667b8eb66070444ed911e", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Finder Component", - "homepage": "https://symfony.com", - "time": "2019-11-17T21:56:56+00:00" + "time": "2020-04-12T14:40:17+00:00" }, { "name": "symfony/options-resolver", @@ -5735,16 +5842,16 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.13.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", - "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", "shasum": "" }, "require": { @@ -5753,7 +5860,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.13-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -5789,24 +5896,24 @@ "portable", "shim" ], - "time": "2019-11-27T16:25:15+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.0.0", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "9d99e1556417bf227a62e14856d630672bf10eaf" + "reference": "144c5e51266b281231e947b51223ba14acf1a749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/9d99e1556417bf227a62e14856d630672bf10eaf", - "reference": "9d99e1556417bf227a62e14856d630672bf10eaf", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", "shasum": "" }, "require": { - "php": "^7.2.9", + "php": "^7.2.5", "psr/container": "^1.0" }, "suggest": { @@ -5847,7 +5954,7 @@ "interoperability", "standards" ], - "time": "2019-11-09T09:18:34+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { "name": "symfony/var-dumper", @@ -6011,15 +6118,6 @@ "validate" ], "time": "2019-11-24T13:36:37+00:00" - }, - { - "name": "ytdl-org/youtube-dl", - "version": "2020.05.08", - "dist": { - "type": "zip", - "url": "https://github.com/ytdl-org/youtube-dl/archive/2020.05.08.zip" - }, - "type": "library" } ], "aliases": [], diff --git a/config/config.example.yml b/config/config.example.yml index 7bf520c..8aeb99a 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -22,14 +22,14 @@ convertAdvanced: false # List of formats available in advanced conversion mode convertAdvancedFormats: [mp3, avi, flv, wav] -# Path to your avconv or ffmpeg binary -avconv: vendor/bin/ffmpeg +# Path to your ffmpeg binary +ffmpeg: /usr/bin/ffmpeg -# avconv/ffmpeg logging level. -avconvVerbosity: error +# ffmpeg logging level. +ffmpegVerbosity: error # Path to the directory that contains the phantomjs binary. -phantomjsDir: vendor/bin/ +phantomjsDir: /usr/bin/ # True to disable URL rewriting uglyUrls: false @@ -49,9 +49,9 @@ appName: AllTube Download # Generic formats supported by youtube-dl genericFormats: - best: Best + best/bestvideo: Best bestvideo+bestaudio: Remux best video with best audio - worst: Worst + worst/worstvideo: Worst # Enable debug mode. debug: false diff --git a/config/config_test.yml b/config/config_test.yml index 99b704b..893344a 100644 --- a/config/config_test.yml +++ b/config/config_test.yml @@ -1,3 +1,3 @@ --- convert: false -avconvVerbosity: fatal +ffmpegVerbosity: fatal diff --git a/config/config_test_windows.yml b/config/config_test_windows.yml deleted file mode 100644 index 34d8f13..0000000 --- a/config/config_test_windows.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -convert: false -python: C:\Python36\python.exe -avconv: C:\ProgramData\chocolatey\bin\ffmpeg.exe -avconvVerbosity: fatal -phantomjsDir: C:\ProgramData\chocolatey\bin\ -youtubedl: C:\Python36\Lib\site-packages\youtube_dl\__main__.py -params: - - --no-warnings - - --ignore-errors - - --flat-playlist - - --restrict-filenames - - --no-check-certificate diff --git a/controllers/BaseController.php b/controllers/BaseController.php index 59e9862..05d3eb2 100644 --- a/controllers/BaseController.php +++ b/controllers/BaseController.php @@ -7,12 +7,14 @@ namespace Alltube\Controller; use Alltube\Config; +use Alltube\Library\Downloader; +use Alltube\Library\Video; use Alltube\LocaleManager; use Alltube\SessionManager; -use Alltube\Video; use Aura\Session\Segment; use Psr\Container\ContainerInterface; use Slim\Http\Request; +use Slim\Http\Response; /** * Abstract class used by every controller. @@ -31,7 +33,7 @@ abstract class BaseController * * @var string */ - protected $defaultFormat = 'best[protocol=https]/best[protocol=http]'; + protected $defaultFormat = 'best/bestvideo'; /** * Slim dependency container. @@ -61,6 +63,13 @@ abstract class BaseController */ protected $localeManager; + /** + * Downloader instance. + * + * @var Downloader + */ + protected $downloader; + /** * BaseController constructor. * @@ -73,9 +82,11 @@ abstract class BaseController $session = SessionManager::getSession(); $this->sessionSegment = $session->getSegment(self::class); $this->localeManager = $this->container->get('locale'); + $this->downloader = $this->config->getDownloader(); - if ($this->config->stream) { - $this->defaultFormat = 'best'; + if (!$this->config->stream) { + // Force HTTP if stream is not enabled. + $this->defaultFormat = Config::addHttpToFormat($this->defaultFormat); } } @@ -116,4 +127,20 @@ abstract class BaseController 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); + } } diff --git a/controllers/DownloadController.php b/controllers/DownloadController.php index 30f1e27..dd3e46f 100644 --- a/controllers/DownloadController.php +++ b/controllers/DownloadController.php @@ -6,13 +6,19 @@ namespace Alltube\Controller; -use Alltube\Exception\EmptyUrlException; -use Alltube\Exception\PasswordException; +use Alltube\Config; +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\PlaylistArchiveStream; use Alltube\Stream\YoutubeStream; -use Alltube\Video; -use Exception; use Slim\Http\Request; use Slim\Http\Response; use Slim\Http\Stream; @@ -25,17 +31,18 @@ class DownloadController extends BaseController /** * Redirect to video file. * - * @param Request $request PSR-7 request + * @param Request $request PSR-7 request * @param Response $response PSR-7 response * * @return Response HTTP response + * @throws AlltubeLibraryException */ public function download(Request $request, Response $response) { $url = $request->getQueryParam('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 { if ($this->config->convert && $request->getQueryParam('audio')) { @@ -49,14 +56,33 @@ class DownloadController extends BaseController // Regular download. return $this->getDownloadResponse($request, $response); } catch (PasswordException $e) { - return $response->withRedirect( - $this->container->get('router')->pathFor('info') . - '?' . http_build_query($request->getQueryParams()) - ); - } catch (Exception $e) { - $response->getBody()->write($e->getMessage()); + $frontController = new FrontController($this->container); - 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 { return $response->withRedirect($this->container->get('router')->pathFor('index')); @@ -70,8 +96,7 @@ class DownloadController extends BaseController * @param Response $response PSR-7 response * * @return Response HTTP response - * @throws PasswordException - * @throws Exception + * @throws AlltubeLibraryException */ private function getConvertedAudioResponse(Request $request, Response $response) { @@ -86,13 +111,7 @@ class DownloadController extends BaseController $response = $response->withHeader('Content-Type', 'audio/mpeg'); if ($request->isGet() || $request->isPost()) { - try { - $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); - } + $process = $this->downloader->getAudioStream($this->video, $this->config->audioBitrate, $from, $to); $response = $response->withBody(new Stream($process)); } @@ -106,36 +125,38 @@ class DownloadController extends BaseController * @param Response $response PSR-7 response * * @return Response HTTP response + * @throws AlltubeLibraryException + * @throws EmptyUrlException * @throws PasswordException + * @throws WrongPasswordException */ private function getAudioResponse(Request $request, Response $response) { - try { - // 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) { - $this->video = $this->video->withFormat('mp3'); - - return $this->getStream($request, $response); - } else { - $this->video = $this->video->withFormat('mp3[protocol=https]/mp3[protocol=http]'); - - $urls = $this->video->getUrl(); - - return $response->withRedirect($urls[0]); - } - } catch (PasswordException $e) { - $frontController = new FrontController($this->container); - - return $frontController->password($request, $response); - } catch (Exception $e) { - // If MP3 is not available, we convert it. - $this->video = $this->video->withFormat('bestaudio/best'); + 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 { + // First, we try to get a MP3 file directly. + if ($this->config->stream) { + $this->video = $this->video->withFormat('mp3'); + + return $this->getStream($request, $response); + } else { + $this->video = $this->video->withFormat(Config::addHttpToFormat('mp3')); + + $urls = $this->video->getUrl(); + + return $response->withRedirect($urls[0]); + } + } catch (YoutubedlException $e) { + // If MP3 is not available, we convert it. + $this->video = $this->video->withFormat('bestaudio/' . $this->defaultFormat); + + return $this->getConvertedAudioResponse($request, $response); + } } } @@ -146,17 +167,15 @@ class DownloadController extends BaseController * * @param Response $response PSR-7 response * @return Response HTTP response - * @throws EmptyUrlException - * @throws PasswordException - * @throws Exception + * @throws AlltubeLibraryException */ private function getStream(Request $request, Response $response) { if (isset($this->video->entries)) { if ($this->config->convert && $request->getQueryParam('audio')) { - $stream = new ConvertedPlaylistArchiveStream($this->video); + $stream = new ConvertedPlaylistArchiveStream($this->downloader, $this->video); } else { - $stream = new PlaylistArchiveStream($this->video); + $stream = new PlaylistArchiveStream($this->downloader, $this->video); } $response = $response->withHeader('Content-Type', 'application/zip'); $response = $response->withHeader( @@ -167,10 +186,10 @@ class DownloadController extends BaseController return $response->withBody($stream); } elseif ($this->video->protocol == 'rtmp') { $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') { $response = $response->withHeader('Content-Type', 'video/' . $this->video->ext); - $body = new Stream($this->video->getM3uStream()); + $body = new Stream($this->downloader->getM3uStream($this->video)); } else { $headers = []; $range = $request->getHeader('Range'); @@ -178,7 +197,7 @@ class DownloadController extends BaseController if (!empty($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-Length', $stream->getHeader('Content-Length')); @@ -190,7 +209,7 @@ class DownloadController extends BaseController if (isset($this->video->downloader_options->http_chunk_size)) { // Workaround for Youtube throttling the download speed. - $body = new YoutubeStream($this->video); + $body = new YoutubeStream($this->downloader, $this->video); } else { $body = $stream->getBody(); } @@ -201,7 +220,7 @@ class DownloadController extends BaseController $response = $response->withHeader( 'Content-Disposition', 'attachment; filename="' . - $this->video->getFilename() . '"' + $this->video->getFilename() . '"' ); return $response; @@ -210,19 +229,18 @@ class DownloadController extends BaseController /** * Get a remuxed 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 PasswordException - * @throws Exception + * @throws AlltubeLibraryException */ private function getRemuxStream(Request $request, Response $response) { 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'); if ($request->isGet()) { $response = $response->withBody(new Stream($stream)); @@ -242,9 +260,7 @@ class DownloadController extends BaseController * * @param Response $response PSR-7 response * @return Response HTTP response - * @throws EmptyUrlException - * @throws PasswordException - * @throws Exception + * @throws AlltubeLibraryException */ private function getDownloadResponse(Request $request, Response $response) { @@ -263,7 +279,7 @@ class DownloadController extends BaseController return $this->getStream($request, $response); } else { 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]); @@ -277,8 +293,13 @@ class DownloadController extends BaseController * @param Response $response PSR-7 response * * @return Response HTTP response + * @throws AlltubeLibraryException + * @throws InvalidProtocolConversionException * @throws PasswordException - * @throws Exception + * @throws PlaylistConversionException + * @throws WrongPasswordException + * @throws YoutubedlException + * @throws PopenStreamException */ private function getConvertedResponse(Request $request, Response $response) { @@ -290,7 +311,8 @@ class DownloadController extends BaseController $response = $response->withHeader('Content-Type', 'video/' . $request->getQueryParam('customFormat')); if ($request->isGet() || $request->isPost()) { - $process = $this->video->getConvertedStream( + $process = $this->downloader->getConvertedStream( + $this->video, $request->getQueryParam('customBitrate'), $request->getQueryParam('customFormat') ); diff --git a/controllers/FrontController.php b/controllers/FrontController.php index 01030aa..2b6109a 100644 --- a/controllers/FrontController.php +++ b/controllers/FrontController.php @@ -6,14 +6,13 @@ 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\Video; -use Symfony\Component\ErrorHandler\ErrorHandler; -use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorHandler\Exception\FlattenException; -use Throwable; use Exception; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Throwable; use Psr\Container\ContainerInterface; use Slim\Http\Request; use Slim\Http\Response; @@ -96,7 +95,7 @@ class FrontController extends BaseController * @param Response $response PSR-7 response * * @return Response HTTP response - * @throws PasswordException + * @throws AlltubeLibraryException */ public function extractors(Request $request, Response $response) { @@ -105,7 +104,7 @@ class FrontController extends BaseController 'extractors.tpl', [ 'config' => $this->config, - 'extractors' => Video::getExtractors(), + 'extractors' => $this->downloader->getExtractors(), 'class' => 'extractors', 'title' => $this->localeManager->t('Supported websites'), 'description' => $this->localeManager->t('List of all supported websites from which Alltube Download ' . @@ -143,7 +142,7 @@ class FrontController extends BaseController ] ); - return $response; + return $response->withStatus(403); } /** @@ -153,6 +152,7 @@ class FrontController extends BaseController * @param Response $response PSR-7 response * * @return Response HTTP response + * @throws AlltubeLibraryException */ private function getInfoResponse(Request $request, Response $response) { @@ -160,6 +160,8 @@ class FrontController extends BaseController $this->video->getJson(); } catch (PasswordException $e) { return $this->password($request, $response); + } catch (WrongPasswordException $e) { + return $this->displayError($request, $response, $this->localeManager->t('Wrong password')); } if (isset($this->video->entries)) { @@ -207,13 +209,14 @@ class FrontController extends BaseController * @param Response $response PSR-7 response * * @return Response HTTP response + * @throws AlltubeLibraryException */ public function info(Request $request, Response $response) { $url = $request->getQueryParam('url') ?: $request->getQueryParam('v'); 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')) { // We skip the info page and get directly to the download. @@ -229,6 +232,33 @@ class FrontController extends BaseController } } + /** + * 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) + { + $this->view->render( + $response, + 'error.tpl', + [ + 'config' => $this->config, + 'error' => $message, + 'class' => 'video', + 'title' => $this->localeManager->t('Error'), + 'canonical' => $this->getCanonicalUrl($request), + 'locale' => $this->localeManager->getLocale(), + ] + ); + + return $response->withStatus(500); + } + /** * Display an error page. * @@ -243,7 +273,11 @@ class FrontController extends BaseController 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 { @@ -253,20 +287,7 @@ class FrontController extends BaseController $message = ''; } - $this->view->render( - $response, - 'error.tpl', - [ - 'config' => $this->config, - 'error' => $message, - 'class' => 'video', - 'title' => $this->localeManager->t('Error'), - 'canonical' => $this->getCanonicalUrl($request), - 'locale' => $this->localeManager->getLocale(), - ] - ); - - return $response->withStatus(500); + return $this->displayError($request, $response, $message); } } diff --git a/controllers/JsonController.php b/controllers/JsonController.php index bfc9d75..9e37cc2 100644 --- a/controllers/JsonController.php +++ b/controllers/JsonController.php @@ -6,8 +6,7 @@ namespace Alltube\Controller; -use Alltube\Video; -use Exception; +use Alltube\Library\Exception\AlltubeLibraryException; use Slim\Http\Request; use Slim\Http\Response; @@ -19,24 +18,24 @@ class JsonController extends BaseController /** * Return the JSON object generated by youtube-dl. * - * @param Request $request PSR-7 request + * @param Request $request PSR-7 request * @param Response $response PSR-7 response * * @return Response HTTP response + * @throws AlltubeLibraryException */ public function json(Request $request, Response $response) { $url = $request->getQueryParam('url'); if (isset($url)) { - try { - $this->video = new Video($url, $this->getFormat($request), $this->getPassword($request)); + $this->video = $this->downloader->getVideo( + $url, + $this->getFormat($request), + $this->getPassword($request) + ); - return $response->withJson($this->video->getJson()); - } catch (Exception $e) { - return $response->withJson(['error' => $e->getMessage()]) - ->withStatus(500); - } + return $response->withJson($this->video->getJson()); } else { return $response->withJson(['error' => 'You need to provide the url parameter']) ->withStatus(400); diff --git a/docs/.htaccess b/docs/.htaccess deleted file mode 100644 index 4ee1d3d..0000000 --- a/docs/.htaccess +++ /dev/null @@ -1,3 +0,0 @@ - - Header set Content-Security-Policy "default-src 'self'; object-src 'none'; script-src http: 'unsafe-inline'; img-src http:; style-src 'self' 'unsafe-inline' https:; font-src http:" - diff --git a/i18n/it_IT/LC_MESSAGES/Alltube.po b/i18n/it_IT/LC_MESSAGES/Alltube.po new file mode 100644 index 0000000..cd7ab21 --- /dev/null +++ b/i18n/it_IT/LC_MESSAGES/Alltube.po @@ -0,0 +1,248 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +#: templates/playlist.tpl:13 +msgid "Videos extracted from @title:" +msgstr "Video estratti da @title:" + +#: templates/playlist.tpl:38 templates/password.tpl:11 templates/index.tpl:19 +#: templates/info.tpl:98 +msgid "Download" +msgstr "Download" + +#: templates/playlist.tpl:39 +msgid "More options" +msgstr "Più opzioni" + +#: templates/inc/header.tpl:4 +msgid "Switch language" +msgstr "Cambia lingua" + +#: templates/inc/header.tpl:8 +msgid "Set language" +msgstr "Imposta lingua" + +#: templates/inc/footer.tpl:8 +msgid "Code by @dev" +msgstr "Codice di @dev" + +#: templates/inc/footer.tpl:16 +msgid "Design by @designer" +msgstr "Design di @designer" + +#: templates/inc/footer.tpl:21 +msgid "Get the code" +msgstr "Ottieni il codice" + +#: templates/inc/footer.tpl:29 +msgid "Based on @youtubedl" +msgstr "Basato su @youtubedl" + +#: templates/inc/footer.tpl:33 +msgid "Donate using Liberapay" +msgstr "Fai una donazione con Liberapay" + +#: templates/inc/footer.tpl:35 +msgid "Donate" +msgstr "Dona" + +#: templates/password.tpl:5 +msgid "This video is protected" +msgstr "Questo video è protetto" + +#: templates/password.tpl:6 +msgid "You need a password in order to download this video." +msgstr "Ti serve una password per scaricare questo video." + +#: templates/password.tpl:8 +msgid "Video password" +msgstr "Password del video" + +#: templates/index.tpl:8 +msgid "Copy here the URL of your video (Youtube, Dailymotion, etc.)" +msgstr "Copia qui l'URL del video (Youtube, Dailymotion, ecc.)" + +#: templates/index.tpl:25 +msgid "Audio only (MP3)" +msgstr "Solo audio (MP3)" + +#: templates/index.tpl:28 +msgid "From" +msgstr "Da" + +#: templates/index.tpl:29 +msgid "to" +msgstr "a" + +#: templates/index.tpl:36 +msgid "See all supported websites" +msgstr "Guarda tutti i siti supportati" + +#: templates/index.tpl:38 +msgid "Drag this to your bookmarks bar:" +msgstr "Trascina questo nella tua barra dei segnalibri:" + +#: templates/index.tpl:39 +msgid "Bookmarklet" +msgstr "Bookmarklet" + +#: templates/info.tpl:13 +msgid "You are going to download @title." +msgstr "Stai per scaricare @title." + +#: templates/info.tpl:31 +msgid "Available formats:" +msgstr "Formati disponibili:" + +#: templates/info.tpl:33 +msgid "Generic formats" +msgstr "Formati generici" + +#: templates/info.tpl:38 +msgid "Detailed formats" +msgstr "Formati dettagliati" + +#: templates/info.tpl:80 +msgid "Stream the video through the server" +msgstr "Riproduci il video attraverso il server" + +#: templates/info.tpl:85 +msgid "Convert into a custom format:" +msgstr "Converti in un formato personalizzato:" + +#: templates/info.tpl:86 +msgid "Custom format" +msgstr "Formato personalizzato" + +#: templates/info.tpl:86 +msgid "Format to convert to" +msgstr "Formato verso cui convertire" + +#: templates/info.tpl:91 +msgid "with" +msgstr "con" + +#: templates/info.tpl:92 +msgid "Bit rate" +msgstr "Bit rate" + +#: templates/info.tpl:93 +msgid "Custom bitrate" +msgstr "bitrate personalizzato" + +#: templates/info.tpl:95 +msgid "kbit/s audio" +msgstr "kbit/s audio" + +#: templates/error.tpl:5 +msgid "An error occurred" +msgstr "Si è verificato un errore" + +#: templates/error.tpl:6 +msgid "Please check the URL of your video." +msgstr "Per favore controlla l'URL del video." + +#: templates/extractors.tpl:4 controllers/FrontController.php:109 +msgid "Supported websites" +msgstr "Siti supportati" + +#: classes/Config.php:158 +msgid "Best" +msgstr "Migliore" + +#: classes/Config.php:159 +msgid "Remux best video with best audio" +msgstr "Fai il remux del video migliore con l'audio migliore" + +#: classes/Config.php:160 +msgid "Worst" +msgstr "Peggiore" + +#: classes/Video.php:159 +msgid "Wrong password" +msgstr "Password errata" + +#: classes/Video.php:250 +msgid "youtube-dl returned an empty URL." +msgstr "youtube-dl ha restituito un URL vuoto." + +#: classes/Video.php:361 classes/Video.php:465 +msgid "Can't find avconv or ffmpeg at @path." +msgstr "Impossibile trovare avconv o ffmpeg in @path." + +#: classes/Video.php:377 +msgid "Invalid start time: @from." +msgstr "Punto di inizio non valido: @from." + +#: classes/Video.php:384 +msgid "Invalid end time: @to." +msgstr "Punto di fine non valido: @to." + +#: classes/Video.php:430 +msgid "Conversion of playlists is not supported." +msgstr "La conversione delle playlist non è supportata." + +#: classes/Video.php:435 classes/Video.php:578 +msgid "Conversion of M3U8 files is not supported." +msgstr "La conversione di file M3U8 non è supportata." + +#: classes/Video.php:437 +msgid "Conversion of DASH segments is not supported." +msgstr "La conversione di segmenti DASH non è supportata." + +#: classes/Video.php:446 classes/Video.php:488 classes/Video.php:525 +#: classes/Video.php:558 classes/Video.php:586 +msgid "Could not open popen stream." +msgstr "Non è stato possibile aprire il popen stream." + +#: classes/Video.php:506 +msgid "This video does not have two URLs." +msgstr "Questo video non ha due URL." + +#: controllers/DownloadController.php:215 +msgid "You need to enable remux mode to merge two formats." +msgstr "Devi abilitare la modalità remux per unire due formati." + +#: controllers/DownloadController.php:255 +msgid "Can't find URL of video." +msgstr "Impossibile trovare l'URL del video." + +#: controllers/FrontController.php:64 +msgid "" +"Easily download videos from Youtube, Dailymotion, Vimeo and other websites." +msgstr "" +"Scarica facilmente video da Youtube, Dailymotion, Vimeo e altri siti." + +#: controllers/FrontController.php:110 +msgid "" +"List of all supported websites from which Alltube Download can extract video " +"or audio files" +msgstr "" +"Lista di tutti i siti supportati da cui Alltube Download può estrarre file " +"video o audio" + +#: controllers/FrontController.php:136 +msgid "Password prompt" +msgstr "Richiesta password" + +#: controllers/FrontController.php:138 +msgid "" +"You need a password in order to download this video with Alltube Download" +msgstr "" +"Ti serve una password per scaricare questo video con Alltube Download" + +#: controllers/FrontController.php:169 +msgid "Video download" +msgstr "Download video" + +#: controllers/FrontController.php:171 +msgid "Download video from @extractor" +msgstr "Scarica video da @extractor" + +#: controllers/FrontController.php:177 +msgid "Download @title from @extractor" +msgstr "Scarica @title da @extractor" + +#: controllers/FrontController.php:253 controllers/FrontController.php:284 +msgid "Error" +msgstr "Errore" diff --git a/i18n/template.pot b/i18n/template.pot index a7d5402..ce9d383 100644 --- a/i18n/template.pot +++ b/i18n/template.pot @@ -1,27 +1,6 @@ msgid "" 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 msgid "Code by @dev" msgstr "" @@ -46,6 +25,87 @@ msgstr "" msgid "Donate" 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 msgid "This video is protected" msgstr "" @@ -70,144 +130,51 @@ msgstr "" msgid "From" msgstr "" -#: templates/index.tpl:29 +#: templates/index.tpl:31 msgid "to" msgstr "" -#: templates/index.tpl:36 +#: templates/index.tpl:39 msgid "See all supported websites" msgstr "" -#: templates/index.tpl:38 +#: templates/index.tpl:41 msgid "Drag this to your bookmarks bar:" msgstr "" -#: templates/index.tpl:39 +#: templates/index.tpl:43 msgid "Bookmarklet" msgstr "" -#: templates/info.tpl:13 -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 +#: classes/Config.php:156 msgid "Best" msgstr "" -#: classes/Config.php:159 +#: classes/Config.php:157 msgid "Remux best video with best audio" msgstr "" -#: classes/Config.php:160 +#: classes/Config.php:158 msgid "Worst" msgstr "" -#: classes/Video.php:159 +#: controllers/DownloadController.php:63 controllers/FrontController.php:164 msgid "Wrong password" msgstr "" -#: classes/Video.php:250 -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 +#: controllers/DownloadController.php:68 msgid "Conversion of playlists is not supported." msgstr "" -#: classes/Video.php:435 classes/Video.php:578 +#: controllers/DownloadController.php:75 msgid "Conversion of M3U8 files is not supported." msgstr "" -#: classes/Video.php:437 +#: controllers/DownloadController.php:81 msgid "Conversion of DASH segments is not supported." msgstr "" -#: classes/Video.php:446 classes/Video.php:488 classes/Video.php:525 -#: 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 +#: controllers/FrontController.php:63 msgid "" "Easily download videos from Youtube, Dailymotion, Vimeo and other websites." msgstr "" @@ -227,18 +194,18 @@ msgid "" "You need a password in order to download this video with Alltube Download" msgstr "" -#: controllers/FrontController.php:169 +#: controllers/FrontController.php:172 msgid "Video download" msgstr "" -#: controllers/FrontController.php:171 +#: controllers/FrontController.php:174 msgid "Download video from @extractor" msgstr "" -#: controllers/FrontController.php:177 +#: controllers/FrontController.php:180 msgid "Download @title from @extractor" msgstr "" -#: controllers/FrontController.php:253 controllers/FrontController.php:284 +#: controllers/FrontController.php:253 msgid "Error" msgstr "" diff --git a/index.php b/index.php index a8b3aed..4760670 100644 --- a/index.php +++ b/index.php @@ -85,8 +85,6 @@ $app->any( '/info', [$frontController, 'info'] )->setName('info'); -// Legacy route. -$app->any('/video', [$frontController, 'info']); $app->any( '/watch', @@ -97,8 +95,6 @@ $app->any( '/download', [$downloadController, 'download'] )->setName('download'); -// Legacy route. -$app->get('/redirect', [$downloadController, 'download']); $app->get( '/locale/{locale}', @@ -116,5 +112,5 @@ try { 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()); + die('Error when starting the app: ' . htmlentities($e->getMessage())); } diff --git a/resources/FAQ.md b/resources/FAQ.md index d6304e8..2c49197 100644 --- a/resources/FAQ.md +++ b/resources/FAQ.md @@ -33,13 +33,13 @@ In order to enable audio conversion, you need to add this to your `config.yml` f ```yaml convert: true -avconv: path/to/avconv +ffmpeg: path/to/ffmpeg ``` -You will also need to install `avconv` on your server: +You will also need to install `ffmpeg` on your server: ```bash -sudo apt-get install libav-tools +sudo apt-get install ffmpeg ``` ## Deploy AllTube on Heroku @@ -47,7 +47,6 @@ sudo apt-get install libav-tools Create a dyno with the following buildpacks: * `heroku/php` -* `heroku/nodejs` * `heroku/python` You might also need to add the following config variables: @@ -113,30 +112,6 @@ you can download it if you enable streaming (see above). docker run -p 8080:80 rudloff/alltube ``` -## Run Heroku locally - -You should be able to use `heroku local` like this: - -```bash -sudo APACHE_LOCK_DIR=. APACHE_PID_FILE=./pid APACHE_RUN_USER=www-data \ - APACHE_RUN_GROUP=www-data APACHE_LOG_DIR=. \ - heroku local -``` - -You might need to create some symlinks before that: - -```bash -ln -s /usr/sbin/apache2 /usr/sbin/httpd -ln -s /usr/sbin/php-fpm7.0 /usr/sbin/php-fpm -``` - -And you probably need to run this in another terminal -after `heroku local` has finished launching `php-fpm`: - -```bash -chmod 0667 /tmp/heroku.fcgi.5000.sock -``` - ## Download 1080p videos from Youtube Youtube distributes HD content in two separate video and audio files. diff --git a/resources/error.xhtml b/resources/error.xhtml deleted file mode 100644 index c0f95bf..0000000 --- a/resources/error.xhtml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - AllTube Download - Maintenance - - - - -
-
-

- -

-
An error occurred in the application and your page could not be served. Please try again in a few - moments. -
-
-
- - diff --git a/resources/maintenance.xhtml b/resources/maintenance.xhtml deleted file mode 100644 index c1c9eba..0000000 --- a/resources/maintenance.xhtml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - AllTube Download - Maintenance - - - - -
-
-

- -

-
This application is undergoing maintenance right now. Please check back later.
-
-
- - diff --git a/templates/error.tpl b/templates/error.tpl index 49663ec..98605b8 100644 --- a/templates/error.tpl +++ b/templates/error.tpl @@ -4,6 +4,6 @@ {include file="inc/logo.tpl"}

{t}An error occurred{/t}

{t}Please check the URL of your video.{/t} -

{$error|escape}

+

{$error|escape|nl2br}

{include file='inc/footer.tpl'} diff --git a/templates/inc/head.tpl b/templates/inc/head.tpl index 00a98c8..7b2f3d4 100644 --- a/templates/inc/head.tpl +++ b/templates/inc/head.tpl @@ -2,7 +2,7 @@ - + {if isset($description)} @@ -19,7 +19,8 @@ - + + diff --git a/templates/info.tpl b/templates/info.tpl index 07ae547..fce119a 100644 --- a/templates/info.tpl +++ b/templates/info.tpl @@ -76,7 +76,7 @@
{/if} {if $config->stream} - stream != 'ask'}checked{/if} name="stream" id="stream"/> + stream !== 'ask'}checked{/if} name="stream" id="stream"/>

diff --git a/tests/BaseTest.php b/tests/BaseTest.php index d717b79..a377617 100644 --- a/tests/BaseTest.php +++ b/tests/BaseTest.php @@ -7,7 +7,7 @@ namespace Alltube\Test; use Alltube\Config; -use Exception; +use Alltube\Exception\ConfigException; use PHPUnit\Framework\TestCase; /** @@ -22,18 +22,12 @@ abstract class BaseTest extends TestCase */ protected function getConfigFile() { - if (PHP_OS == 'WINNT') { - $configFile = 'config_test_windows.yml'; - } else { - $configFile = 'config_test.yml'; - } - - return __DIR__ . '/../config/' . $configFile; + return __DIR__ . '/../config/config_test.yml'; } /** * Prepare tests. - * @throws Exception + * @throws ConfigException */ protected function setUp(): void { diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 09374cf..e0fe7d8 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -7,7 +7,7 @@ namespace Alltube\Test; use Alltube\Config; -use Exception; +use Alltube\Exception\ConfigException; /** * Unit tests for the Config class. @@ -23,7 +23,7 @@ class ConfigTest extends BaseTest /** * Prepare tests. - * @throws Exception + * @throws ConfigException */ protected function setUp(): void { @@ -70,7 +70,7 @@ class ConfigTest extends BaseTest $this->assertIsArray($config->params); $this->assertIsString($config->youtubedl); $this->assertIsString($config->python); - $this->assertIsString($config->avconv); + $this->assertIsString($config->ffmpeg); $this->assertIsBool($config->convert); $this->assertIsBool($config->uglyUrls); $this->assertIsBool($config->stream); @@ -82,7 +82,7 @@ class ConfigTest extends BaseTest * Test the setFile function. * * @return void - * @throws Exception + * @throws ConfigException */ public function testSetFile() { @@ -97,7 +97,7 @@ class ConfigTest extends BaseTest */ public function testSetFileWithMissingFile() { - $this->expectException(Exception::class); + $this->expectException(ConfigException::class); Config::setFile('foo'); } @@ -105,7 +105,7 @@ class ConfigTest extends BaseTest * Test the setOptions function. * * @return void - * @throws Exception + * @throws ConfigException */ public function testSetOptions() { @@ -118,7 +118,7 @@ class ConfigTest extends BaseTest * Test the setOptions function. * * @return void - * @throws Exception + * @throws ConfigException */ public function testSetOptionsWithoutUpdate() { @@ -134,7 +134,7 @@ class ConfigTest extends BaseTest */ public function testSetOptionsWithBadYoutubedl() { - $this->expectException(Exception::class); + $this->expectException(ConfigException::class); Config::setOptions(['youtubedl' => 'foo']); } @@ -145,7 +145,7 @@ class ConfigTest extends BaseTest */ public function testSetOptionsWithBadPython() { - $this->expectException(Exception::class); + $this->expectException(ConfigException::class); Config::setOptions(['python' => 'foo']); } @@ -153,7 +153,7 @@ class ConfigTest extends BaseTest * Test the getInstance function with the CONVERT and PYTHON environment variables. * * @return void - * @throws Exception + * @throws ConfigException */ public function testGetInstanceWithEnv() { diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index bd9f737..abb3756 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -9,13 +9,14 @@ namespace Alltube\Test; use Alltube\Controller\BaseController; use Alltube\Controller\DownloadController; use Alltube\Controller\FrontController; +use Alltube\Exception\ConfigException; use Alltube\LocaleManager; use Alltube\ViewFactory; -use Exception; use Slim\Container; use Slim\Http\Environment; use Slim\Http\Request; use Slim\Http\Response; +use SmartyException; /** * Abstract class used by the controller tests. @@ -51,7 +52,7 @@ abstract class ControllerTest extends BaseTest /** * Prepare tests. - * @throws Exception + * @throws ConfigException|SmartyException */ protected function setUp(): void { diff --git a/tests/ConvertedPlaylistArchiveStreamTest.php b/tests/ConvertedPlaylistArchiveStreamTest.php index e8cb002..0ed373f 100644 --- a/tests/ConvertedPlaylistArchiveStreamTest.php +++ b/tests/ConvertedPlaylistArchiveStreamTest.php @@ -6,9 +6,9 @@ namespace Alltube\Test; +use Alltube\Config; +use Alltube\Exception\ConfigException; use Alltube\Stream\ConvertedPlaylistArchiveStream; -use Alltube\Video; -use Exception; /** * Unit tests for the ConvertedPlaylistArchiveStream class. @@ -18,14 +18,16 @@ class ConvertedPlaylistArchiveStreamTest extends StreamTest { /** * Prepare tests. - * @throws Exception + * @throws ConfigException */ protected function setUp(): void { 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); } } diff --git a/tests/DownloadControllerTest.php b/tests/DownloadControllerTest.php index 761ba21..019122b 100644 --- a/tests/DownloadControllerTest.php +++ b/tests/DownloadControllerTest.php @@ -8,7 +8,11 @@ namespace Alltube\Test; use Alltube\Config; 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. @@ -18,7 +22,7 @@ class DownloadControllerTest extends ControllerTest { /** * Prepare tests. - * @throws Exception + * @throws ConfigException|SmartyException */ protected function setUp(): void { @@ -64,7 +68,7 @@ class DownloadControllerTest extends ControllerTest * Test the download() function with streams enabled. * * @return void - * @throws Exception + * @throws ConfigException */ public function testDownloadWithStream() { @@ -80,7 +84,7 @@ class DownloadControllerTest extends ControllerTest * Test the download() function with an M3U stream. * * @return void - * @throws Exception + * @throws ConfigException */ public function testDownloadWithM3uStream() { @@ -100,7 +104,7 @@ class DownloadControllerTest extends ControllerTest * Test the download() function with an RTMP stream. * * @return void - * @throws Exception + * @throws ConfigException */ public function testDownloadWithRtmpStream() { @@ -118,7 +122,7 @@ class DownloadControllerTest extends ControllerTest * Test the download() function with a remuxed video. * * @return void - * @throws Exception + * @throws ConfigException */ public function testDownloadWithRemux() { @@ -140,7 +144,8 @@ class DownloadControllerTest extends ControllerTest */ public function testDownloadWithRemuxDisabled() { - $this->assertRequestIsServerError( + $this->expectException(RemuxException::class); + $this->getRequestResult( 'download', [ 'url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU', @@ -166,7 +171,8 @@ class DownloadControllerTest extends ControllerTest */ 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() { - $this->assertRequestIsServerError( + $this->expectException(EmptyUrlException::class); + $this->getRequestResult( 'download', ['url' => 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC'] ); @@ -188,7 +195,7 @@ class DownloadControllerTest extends ControllerTest * * @return void * @requires OS Linux - * @throws Exception + * @throws ConfigException */ public function testDownloadWithPlaylist() { @@ -204,7 +211,7 @@ class DownloadControllerTest extends ControllerTest * Test the download() function with an advanced conversion. * * @return void - * @throws Exception + * @throws ConfigException */ public function testDownloadWithAdvancedConversion() { diff --git a/tests/FrontControllerTest.php b/tests/FrontControllerTest.php index 92e4824..0e2ffe0 100644 --- a/tests/FrontControllerTest.php +++ b/tests/FrontControllerTest.php @@ -8,9 +8,12 @@ namespace Alltube\Test; use Alltube\Config; use Alltube\Controller\FrontController; +use Alltube\Exception\ConfigException; +use Alltube\Library\Exception\AlltubeLibraryException; use Exception; use Slim\Http\Environment; use Slim\Http\Request; +use SmartyException; /** * Unit tests for the FrontController class. @@ -25,7 +28,7 @@ class FrontControllerTest extends ControllerTest /** * Prepare tests. - * @throws Exception + * @throws ConfigException|SmartyException */ protected function setUp(): void { @@ -48,7 +51,7 @@ class FrontControllerTest extends ControllerTest * Test the constructor with streams enabled. * * @return void - * @throws Exception + * @throws ConfigException */ public function testConstructorWithStream() { @@ -99,7 +102,7 @@ class FrontControllerTest extends ControllerTest */ public function testPassword() { - $this->assertRequestIsOk('password'); + $this->assertRequestIsClientError('password'); } /** @@ -128,7 +131,7 @@ class FrontControllerTest extends ControllerTest * * @return void * @requires download - * @throws Exception + * @throws ConfigException */ public function testInfoWithAudio() { @@ -145,7 +148,7 @@ class FrontControllerTest extends ControllerTest * * @return void * @requires download - * @throws Exception + * @throws ConfigException */ public function testInfoWithVimeoAudio() { @@ -160,7 +163,7 @@ class FrontControllerTest extends ControllerTest * * @return void * @requires download - * @throws Exception + * @throws ConfigException */ public function testInfoWithUnconvertedAudio() { @@ -180,6 +183,7 @@ class FrontControllerTest extends ControllerTest * * @return void * @requires download + * @throws AlltubeLibraryException */ public function testInfoWithPassword() { @@ -199,8 +203,8 @@ class FrontControllerTest extends ControllerTest */ public function testInfoWithMissingPassword() { - $this->assertRequestIsOk('info', ['url' => 'http://vimeo.com/68375962']); - $this->assertRequestIsOk('info', ['url' => 'http://vimeo.com/68375962', 'audio' => true]); + $this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962']); + $this->assertRequestIsClientError('info', ['url' => 'http://vimeo.com/68375962', 'audio' => true]); } /** @@ -208,7 +212,7 @@ class FrontControllerTest extends ControllerTest * * @return void * @requires download - * @throws Exception + * @throws ConfigException */ public function testInfoWithStream() { diff --git a/tests/JsonControllerTest.php b/tests/JsonControllerTest.php index d2d6c3e..438b482 100644 --- a/tests/JsonControllerTest.php +++ b/tests/JsonControllerTest.php @@ -7,7 +7,9 @@ namespace Alltube\Test; use Alltube\Controller\JsonController; -use Exception; +use Alltube\Exception\ConfigException; +use Alltube\Library\Exception\YoutubedlException; +use SmartyException; /** * Unit tests for the FrontController class. @@ -16,7 +18,7 @@ class JsonControllerTest extends ControllerTest { /** * Prepare tests. - * @throws Exception + * @throws ConfigException|SmartyException */ protected function setUp(): void { @@ -44,7 +46,8 @@ class JsonControllerTest extends ControllerTest */ public function testJsonWithError() { - $this->assertRequestIsServerError('json', ['url' => 'http://example.com/foo']); + $this->expectException(YoutubedlException::class); + $this->getRequestResult('json', ['url' => 'http://example.com/foo']); } /** diff --git a/tests/PlaylistArchiveStreamTest.php b/tests/PlaylistArchiveStreamTest.php index f02a6ea..29b103f 100644 --- a/tests/PlaylistArchiveStreamTest.php +++ b/tests/PlaylistArchiveStreamTest.php @@ -6,9 +6,9 @@ namespace Alltube\Test; +use Alltube\Config; +use Alltube\Exception\ConfigException; use Alltube\Stream\PlaylistArchiveStream; -use Alltube\Video; -use Exception; /** * Unit tests for the PlaylistArchiveStream class. @@ -18,14 +18,16 @@ class PlaylistArchiveStreamTest extends StreamTest { /** * Prepare tests. - * @throws Exception + * @throws ConfigException */ protected function setUp(): void { 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); } } diff --git a/tests/VideoStubsTest.php b/tests/VideoStubsTest.php index 51f758d..789e839 100644 --- a/tests/VideoStubsTest.php +++ b/tests/VideoStubsTest.php @@ -6,36 +6,51 @@ 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 phpmock\mockery\PHPMockery; -use Exception; /** * Unit tests for the Video class. * They are in a separate file so they can safely replace PHP functions with stubs. + * + * @requires download */ class VideoStubsTest extends BaseTest { /** - * Video URL used in many tests. + * Video used in many tests. * * @var Video */ private $video; + /** + * Downloader instance used in tests. + * + * @var Downloader + */ + private $downloader; + /** * Initialize properties used by test. - * @throws Exception + * @throws ConfigException */ protected function setUp(): void { parent::setUp(); - PHPMockery::mock('Alltube', 'popen'); - PHPMockery::mock('Alltube', 'fopen'); + PHPMockery::mock('Alltube\Library', 'popen'); + 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. * * @return void + * @throws AlltubeLibraryException */ public function testGetAudioStreamWithPopenError() { - $this->expectException(Exception::class); - $this->video->getAudioStream(); + $this->expectException(PopenStreamException::class); + $this->downloader->getAudioStream($this->video); } /** * Test getM3uStream function with a buggy popen. * * @return void + * @throws AlltubeLibraryException */ public function testGetM3uStreamWithPopenError() { - $this->expectException(Exception::class); - $this->video->getM3uStream(); + $this->expectException(PopenStreamException::class); + $this->downloader->getM3uStream($this->video); } /** * Test getRtmpStream function with a buggy popen. * * @return void + * @throws AlltubeLibraryException */ public function testGetRtmpStreamWithPopenError() { - $this->expectException(Exception::class); - $this->video->getRtmpStream(); + $this->expectException(PopenStreamException::class); + $this->downloader->getRtmpStream($this->video); } /** * Test getRemuxStream function with a buggy popen. * * @return void + * @throws AlltubeLibraryException */ public function testGetRemuxStreamWithPopenError() { - $this->expectException(Exception::class); + $this->expectException(PopenStreamException::class); $video = $this->video->withFormat('bestvideo+bestaudio'); - $video->getRemuxStream(); + $this->downloader->getRemuxStream($video); } /** * Test getConvertedStream function with a buggy popen. * * @return void + * @throws AlltubeLibraryException */ public function testGetConvertedStreamWithPopenError() { - $this->expectException(Exception::class); - $this->video->getConvertedStream(32, 'flv'); + $this->expectException(PopenStreamException::class); + $this->downloader->getConvertedStream($this->video, 32, 'flv'); } } diff --git a/tests/VideoTest.php b/tests/VideoTest.php index e6d4552..7f58516 100644 --- a/tests/VideoTest.php +++ b/tests/VideoTest.php @@ -7,26 +7,61 @@ namespace Alltube\Test; use Alltube\Config; -use Alltube\Exception\EmptyUrlException; -use Alltube\Exception\PasswordException; -use Alltube\Video; -use Exception; +use Alltube\Exception\ConfigException; +use Alltube\Library\Downloader; +use Alltube\Library\Exception\AlltubeLibraryException; +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. * @requires download + * @todo Split Downloader and Video tests. */ 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. * * @return void - * @throws PasswordException + * @throws AlltubeLibraryException */ 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 * * @return void - * @throws PasswordException - * @throws EmptyUrlException + * @throws AlltubeLibraryException * @dataProvider urlProvider * @dataProvider m3uUrlProvider * @dataProvider remuxUrlProvider @@ -52,7 +86,7 @@ class VideoTest extends BaseTest /* @scrutinizer ignore-unused */ $extension, $domain ) { - $video = new Video($url, $format); + $video = new Video($this->downloader, $url, $format); foreach ($video->getUrl() as $videoURL) { $this->assertStringContainsString($domain, $videoURL); } @@ -62,12 +96,11 @@ class VideoTest extends BaseTest * Test getUrl function with a protected video. * * @return void - * @throws EmptyUrlException - * @throws PasswordException + * @throws AlltubeLibraryException */ 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) { $this->assertStringContainsString('vimeocdn.com', $videoURL); } @@ -77,13 +110,12 @@ class VideoTest extends BaseTest * Test getUrl function with a protected video and no password. * * @return void - * @throws EmptyUrlException - * @throws PasswordException + * @throws AlltubeLibraryException */ public function testgetUrlWithMissingPassword() { - $this->expectException(Exception::class); - $video = new Video('http://vimeo.com/68375962'); + $this->expectException(PasswordException::class); + $video = new Video($this->downloader, 'http://vimeo.com/68375962', $this->format); $video->getUrl(); } @@ -91,13 +123,12 @@ class VideoTest extends BaseTest * Test getUrl function with a protected video and a wrong password. * * @return void - * @throws EmptyUrlException - * @throws PasswordException + * @throws AlltubeLibraryException */ public function testgetUrlWithWrongPassword() { - $this->expectException(Exception::class); - $video = new Video('http://vimeo.com/68375962', 'best', 'foo'); + $this->expectException(WrongPasswordException::class); + $video = new Video($this->downloader, 'http://vimeo.com/68375962', 'best', 'foo'); $video->getUrl(); } @@ -107,14 +138,13 @@ class VideoTest extends BaseTest * @param string $url URL * * @return void - * @throws EmptyUrlException - * @throws PasswordException - * @dataProvider ErrorUrlProvider + * @throws AlltubeLibraryException + * @dataProvider ErrorUrlProvider */ public function testgetUrlError($url) { - $this->expectException(Exception::class); - $video = new Video($url); + $this->expectException(YoutubedlException::class); + $video = new Video($this->downloader, $url, $this->format); $video->getUrl(); } @@ -224,13 +254,13 @@ class VideoTest extends BaseTest * @param string $format Format * * @return void + * @throws AlltubeLibraryException * @dataProvider urlProvider * @dataProvider m3uUrlProvider - * @throws PasswordException */ public function testGetJson($url, $format) { - $video = new Video($url, $format); + $video = new Video($this->downloader, $url, $format); $info = $video->getJson(); $this->assertObjectHasAttribute('webpage_url', $info); $this->assertObjectHasAttribute('url', $info); @@ -246,13 +276,13 @@ class VideoTest extends BaseTest * @param string $url URL * * @return void - * @dataProvider ErrorURLProvider - * @throws PasswordException + * @throws AlltubeLibraryException + * @dataProvider ErrorURLProvider */ public function testGetJsonError($url) { - $this->expectException(Exception::class); - $video = new Video($url); + $this->expectException(YoutubedlException::class); + $video = new Video($this->downloader, $url, $this->format); $video->getJson(); } @@ -265,14 +295,14 @@ class VideoTest extends BaseTest * @param string $extension File extension * * @return void + * @throws AlltubeLibraryException * @dataProvider urlProvider * @dataProvider m3uUrlProvider * @dataProvider remuxUrlProvider - * @throws PasswordException */ 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); } @@ -282,13 +312,13 @@ class VideoTest extends BaseTest * @param string $url URL * * @return void - * @dataProvider ErrorUrlProvider - * @throws PasswordException + * @throws AlltubeLibraryException + * @dataProvider ErrorUrlProvider */ public function testGetFilenameError($url) { - $this->expectException(Exception::class); - $video = new Video($url); + $this->expectException(YoutubedlException::class); + $video = new Video($this->downloader, $url, $this->format); $video->getFilename(); } @@ -300,73 +330,80 @@ class VideoTest extends BaseTest * * @return void * @dataProvider urlProvider - * @throws Exception + * @throws AlltubeLibraryException */ public function testGetAudioStream($url, $format) { - $video = new Video($url, $format); - $this->assertStream($video->getAudioStream()); + $video = new Video($this->downloader, $url, $format); + $this->assertStream($this->downloader->getAudioStream($video)); } /** - * Test getAudioStream function without avconv. + * Test getAudioStream function without ffmpeg. * - * @param string $url URL + * @param string $url URL * @param string $format Format * * @return void - * @dataProvider urlProvider + * @throws AlltubeLibraryException|ConfigException + * @dataProvider urlProvider */ - public function testGetAudioStreamAvconvError($url, $format) + public function testGetAudioStreamFfmpegError($url, $format) { - $this->expectException(Exception::class); - Config::setOptions(['avconv' => 'foobar']); + $this->expectException(AvconvException::class); + Config::setOptions(['ffmpeg' => 'foobar']); + $config = Config::getInstance(); + $downloader = $config->getDownloader(); - $video = new Video($url, $format); - $video->getAudioStream(); + $video = new Video($this->downloader, $url, $format, $this->format); + $downloader->getAudioStream($video); } /** * Test getAudioStream function with a M3U8 file. * - * @param string $url URL + * @param string $url URL * @param string $format Format * * @return void + * @throws AlltubeLibraryException * @dataProvider m3uUrlProvider */ public function testGetAudioStreamM3uError($url, $format) { - $this->expectException(Exception::class); - $video = new Video($url, $format); - $video->getAudioStream(); + $this->expectException(InvalidProtocolConversionException::class); + $video = new Video($this->downloader, $url, $format); + $this->downloader->getAudioStream($video); } /** * Test getAudioStream function with a DASH URL. * * @return void + * @throws AlltubeLibraryException */ public function testGetAudioStreamDashError() { - $this->expectException(Exception::class); - $video = new Video('https://vimeo.com/251997032', 'bestaudio/best'); - $video->getAudioStream(); + $this->expectException(InvalidProtocolConversionException::class); + $video = new Video($this->downloader, 'https://vimeo.com/251997032', 'bestaudio/best'); + $this->downloader->getAudioStream($video); } /** * Test getAudioStream function with a playlist. * * @return void + * @throws AlltubeLibraryException */ public function testGetAudioStreamPlaylistError() { - $this->expectException(Exception::class); + $this->expectException(PlaylistConversionException::class); $video = new Video( + $this->downloader, 'https://www.youtube.com/playlist?list=PLgdySZU6KUXL_8Jq5aUkyNV7wCa-4wZsC', 'best' ); - $video->getAudioStream(); + $this->downloader->getAudioStream($video); } /** @@ -390,12 +427,12 @@ class VideoTest extends BaseTest * * @return void * @dataProvider m3uUrlProvider - * @throws Exception + * @throws AlltubeLibraryException */ public function testGetM3uStream($url, $format) { - $video = new Video($url, $format); - $this->assertStream($video->getM3uStream()); + $video = new Video($this->downloader, $url, $format); + $this->assertStream($this->downloader->getM3uStream($video)); } /** @@ -406,28 +443,29 @@ class VideoTest extends BaseTest * * @return void * @dataProvider remuxUrlProvider - * @throws Exception + * @throws AlltubeLibraryException */ public function testGetRemuxStream($url, $format) { - $video = new Video($url, $format); - $this->assertStream($video->getRemuxStream()); + $video = new Video($this->downloader, $url, $format); + $this->assertStream($this->downloader->getRemuxStream($video)); } /** * Test getRemuxStream function with a video with only one URL. * - * @param string $url URL + * @param string $url URL * @param string $format Format * * @return void + * @throws AlltubeLibraryException * @dataProvider urlProvider */ public function testGetRemuxStreamWithWrongVideo($url, $format) { - $this->expectException(Exception::class); - $video = new Video($url, $format); - $video->getRemuxStream(); + $this->expectException(RemuxException::class); + $video = new Video($this->downloader, $url, $format); + $this->downloader->getRemuxStream($video); } /** @@ -437,65 +475,69 @@ class VideoTest extends BaseTest * @param string $format Format * * @return void + * @throws AlltubeLibraryException * @dataProvider rtmpUrlProvider - * @throws Exception */ public function testGetRtmpStream($url, $format) { $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)); } /** - * Test getM3uStream function without avconv. + * Test getM3uStream function without ffmpeg. * - * @param string $url URL + * @param string $url URL * @param string $format Format * * @return void + * @throws AlltubeLibraryException|ConfigException * @dataProvider m3uUrlProvider */ - public function testGetM3uStreamAvconvError($url, $format) + public function testGetM3uStreamFfmpegError($url, $format) { - $this->expectException(Exception::class); - Config::setOptions(['avconv' => 'foobar']); + $this->expectException(AvconvException::class); + Config::setOptions(['ffmpeg' => 'foobar']); + $config = Config::getInstance(); + $downloader = $config->getDownloader(); - $video = new Video($url, $format); - $video->getM3uStream(); + $video = new Video($downloader, $url, $format); + $downloader->getM3uStream($video); } /** - * Test getConvertedStream function without avconv. + * Test getConvertedStream function without ffmpeg. * * @param string $url URL * @param string $format Format * * @return void * @dataProvider urlProvider - * @throws Exception + * @throws AlltubeLibraryException */ public function testGetConvertedStream($url, $format) { - $video = new Video($url, $format); - $this->assertStream($video->getConvertedStream(32, 'flv')); + $video = new Video($this->downloader, $url, $format); + $this->assertStream($this->downloader->getConvertedStream($video, 32, 'flv')); } /** * Test getConvertedStream function with a M3U8 file. * - * @param string $url URL + * @param string $url URL * @param string $format Format * * @return void + * @throws AlltubeLibraryException * @dataProvider m3uUrlProvider */ public function testGetConvertedStreamM3uError($url, $format) { - $this->expectException(Exception::class); - $video = new Video($url, $format); - $video->getConvertedStream(32, 'flv'); + $this->expectException(InvalidProtocolConversionException::class); + $video = new Video($this->downloader, $url, $format); + $this->downloader->getConvertedStream($video, 32, 'flv'); } } diff --git a/tests/YoutubeChunkStreamTest.php b/tests/YoutubeChunkStreamTest.php index 41a2f38..aec785d 100644 --- a/tests/YoutubeChunkStreamTest.php +++ b/tests/YoutubeChunkStreamTest.php @@ -6,9 +6,10 @@ namespace Alltube\Test; +use Alltube\Config; +use Alltube\Exception\ConfigException; +use Alltube\Library\Exception\AlltubeLibraryException; use Alltube\Stream\YoutubeChunkStream; -use Alltube\Video; -use Exception; /** * Unit tests for the YoutubeChunkStream class. @@ -18,14 +19,17 @@ class YoutubeChunkStreamTest extends StreamTest { /** * Prepare tests. - * @throws Exception + * @throws ConfigException + * @throws AlltubeLibraryException */ protected function setUp(): void { 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)); } } diff --git a/tests/YoutubeStreamTest.php b/tests/YoutubeStreamTest.php index b8b4d13..d551e0c 100644 --- a/tests/YoutubeStreamTest.php +++ b/tests/YoutubeStreamTest.php @@ -6,9 +6,10 @@ namespace Alltube\Test; +use Alltube\Config; +use Alltube\Exception\ConfigException; +use Alltube\Library\Exception\AlltubeLibraryException; use Alltube\Stream\YoutubeStream; -use Alltube\Video; -use Exception; /** * Unit tests for the YoutubeStream class. @@ -18,15 +19,17 @@ class YoutubeStreamTest extends StreamTest { /** * Prepare tests. - * @throws Exception + * @throws ConfigException|AlltubeLibraryException */ protected function setUp(): void { 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); } /**