refactor: New Video class
The news class provides a cleaner object-oriented logic BREAKING CHANGE: The VideoDownload class has been removed and the Config constructor is now private
This commit is contained in:
parent
feb8998188
commit
4c9af8ad1d
18 changed files with 665 additions and 719 deletions
|
@ -129,16 +129,49 @@ class Config
|
|||
/**
|
||||
* Config constructor.
|
||||
*
|
||||
* @param array $options Options (see `config/config.example.yml` for available options)
|
||||
* @param array $options Options
|
||||
*/
|
||||
public function __construct(array $options)
|
||||
private function __construct(array $options = [])
|
||||
{
|
||||
$this->applyOptions($options);
|
||||
$this->getEnv();
|
||||
$this->validateOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception if some of the options are invalid.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception If youtube-dl is missing
|
||||
* @throws Exception If Python 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the provided options.
|
||||
*
|
||||
* @param array $options Options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyOptions(array $options)
|
||||
{
|
||||
foreach ($options as $option => $value) {
|
||||
if (isset($this->$option) && isset($value)) {
|
||||
$this->$option = $value;
|
||||
}
|
||||
}
|
||||
$this->getEnv();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,34 +192,51 @@ class Config
|
|||
}
|
||||
|
||||
/**
|
||||
* Get Config singleton instance from YAML config file.
|
||||
*
|
||||
* @param string $yamlfile YAML config file name
|
||||
* Get Config singleton instance.
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
public static function getInstance($yamlfile = 'config/config.yml')
|
||||
public static function getInstance()
|
||||
{
|
||||
$yamlPath = __DIR__.'/../'.$yamlfile;
|
||||
if (is_null(self::$instance) || self::$instance->file != $yamlfile) {
|
||||
if (is_file($yamlfile)) {
|
||||
$options = Yaml::parse(file_get_contents($yamlPath));
|
||||
} elseif ($yamlfile == 'config/config.yml' || empty($yamlfile)) {
|
||||
/*
|
||||
Allow for the default file to be missing in order to
|
||||
not surprise users that did not create a config file
|
||||
*/
|
||||
$options = [];
|
||||
} else {
|
||||
throw new Exception("Can't find config file at ".$yamlPath);
|
||||
}
|
||||
self::$instance = new self($options);
|
||||
self::$instance->file = $yamlfile;
|
||||
if (!isset(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options from a YAML file.
|
||||
*
|
||||
* @param string $file Path to the YAML file
|
||||
*/
|
||||
public static function setFile($file)
|
||||
{
|
||||
if (is_file($file)) {
|
||||
$options = Yaml::parse(file_get_contents($file));
|
||||
self::$instance = new self($options);
|
||||
} else {
|
||||
throw new Exception("Can't find config file at ".$file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually set some options.
|
||||
*
|
||||
* @param array $options Options (see `config/config.example.yml` for available options)
|
||||
* @param boolean $update True to update an existing instance
|
||||
*/
|
||||
public static function setOptions(array $options, $update = true)
|
||||
{
|
||||
if ($update) {
|
||||
$config = self::getInstance();
|
||||
$config->applyOptions($options);
|
||||
$config->validateOptions();
|
||||
} else {
|
||||
self::$instance = new self($options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy singleton instance.
|
||||
*
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
namespace Alltube;
|
||||
|
||||
use Barracuda\ArchiveStream\TarArchive;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
@ -21,7 +21,7 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
/**
|
||||
* videos to add in the archive.
|
||||
*
|
||||
* @var PlaylistArchiveVideo[]
|
||||
* @var Video[]
|
||||
*/
|
||||
private $videos = [];
|
||||
|
||||
|
@ -32,53 +32,32 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
private $buffer;
|
||||
|
||||
/**
|
||||
* Guzzle client.
|
||||
*
|
||||
* @var Client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* VideoDownload instance.
|
||||
*
|
||||
* @var VideoDownload
|
||||
*/
|
||||
private $download;
|
||||
|
||||
/**
|
||||
* Current video being streamed to the archive.
|
||||
*
|
||||
* @var int
|
||||
* @var Stream
|
||||
*/
|
||||
private $curVideo;
|
||||
private $curVideoStream;
|
||||
|
||||
/**
|
||||
* Video format to download.
|
||||
*
|
||||
* @var string
|
||||
* True if the archive is complete.
|
||||
* @var bool
|
||||
*/
|
||||
private $format;
|
||||
private $isComplete = false;
|
||||
|
||||
/**
|
||||
* PlaylistArchiveStream constructor.
|
||||
*
|
||||
* @param Config $config Config instance.
|
||||
* @param stdClass $video Video object returned by youtube-dl
|
||||
* @param string $format Requested format
|
||||
* @param Video $video Video/playlist to download
|
||||
*/
|
||||
public function __construct(Config $config, stdClass $video, $format)
|
||||
public function __construct(Video $video)
|
||||
{
|
||||
$this->client = new Client();
|
||||
$this->download = new VideoDownload($config);
|
||||
|
||||
$this->format = $format;
|
||||
$buffer = fopen('php://temp', 'r+');
|
||||
if ($buffer !== false) {
|
||||
$this->buffer = $buffer;
|
||||
}
|
||||
foreach ($video->entries as $entry) {
|
||||
$this->videos[] = new PlaylistArchiveVideo($entry->url);
|
||||
$this->videos[] = new Video($entry->url);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,26 +70,27 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
protected function send($data)
|
||||
{
|
||||
$pos = ftell($this->buffer);
|
||||
$pos = $this->tell();
|
||||
|
||||
// Add data to the buffer.
|
||||
fwrite($this->buffer, $data);
|
||||
// Add data to the end of the buffer.
|
||||
$this->seek(0, SEEK_END);
|
||||
$this->write($data);
|
||||
if ($pos !== false) {
|
||||
// Rewind so that read() can later read this data.
|
||||
fseek($this->buffer, $pos);
|
||||
$this->seek($pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* @param string $string The string that is to be written.
|
||||
* @param string $string The string that is to be written
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
throw new RuntimeException('This stream is not writeable.');
|
||||
fwrite($this->buffer, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,7 +109,7 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,7 +119,7 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
public function rewind()
|
||||
{
|
||||
throw new RuntimeException('This stream is not seekable.');
|
||||
rewind($this->buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,7 +129,7 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -181,6 +161,15 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
$meta = stream_get_meta_data($this->buffer);
|
||||
|
||||
if (!isset($key)) {
|
||||
return $meta;
|
||||
}
|
||||
|
||||
if (isset($meta[$key])) {
|
||||
return $meta[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -203,13 +192,7 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$string = '';
|
||||
|
||||
foreach ($this->videos as $file) {
|
||||
$string .= $file->url;
|
||||
}
|
||||
|
||||
return $string;
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -232,23 +215,37 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new RuntimeException('This stream is not seekable.');
|
||||
fseek($this->buffer, $offset, $whence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the stream is at the end of the stream.
|
||||
* Returns true if the stream is at the end of the archive.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eof()
|
||||
{
|
||||
foreach ($this->videos as $file) {
|
||||
if (!$file->complete) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $this->isComplete && feof($this->buffer);
|
||||
}
|
||||
|
||||
return true;
|
||||
/**
|
||||
* Start streaming a new video.
|
||||
*
|
||||
* @param Video $video Video to stream
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function startVideoStream(Video $video)
|
||||
{
|
||||
$response = $video->getHttpResponse();
|
||||
|
||||
$this->curVideoStream = $response->getBody();
|
||||
$contentLengthHeaders = $response->getHeader('Content-Length');
|
||||
|
||||
$this->init_file_stream_transfer(
|
||||
$video->getFilename(),
|
||||
$contentLengthHeaders[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -260,30 +257,30 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
*/
|
||||
public function read($count)
|
||||
{
|
||||
if (isset($this->curVideo)) {
|
||||
if (isset($this->curVideo->stream)) {
|
||||
if (!$this->curVideo->stream->eof()) {
|
||||
$this->stream_file_part($this->curVideo->stream->read($count));
|
||||
} elseif (!$this->curVideo->complete) {
|
||||
// If the archive is complete, we only read the remaining buffer.
|
||||
if (!$this->isComplete) {
|
||||
if (isset($this->curVideoStream)) {
|
||||
if ($this->curVideoStream->eof()) {
|
||||
// Stop streaming the current video.
|
||||
$this->complete_file_stream();
|
||||
$this->curVideo->complete = true;
|
||||
|
||||
$video = next($this->videos);
|
||||
if ($video) {
|
||||
// Start streaming the next video.
|
||||
$this->startVideoStream($video);
|
||||
} else {
|
||||
// No video left.
|
||||
$this->finish();
|
||||
$this->isComplete = true;
|
||||
}
|
||||
} else {
|
||||
$this->curVideo = next($this->videos);
|
||||
// Continue streaming the current video.
|
||||
$this->stream_file_part($this->curVideoStream->read($count));
|
||||
}
|
||||
} else {
|
||||
$urls = $this->download->getURL($this->curVideo->url, $this->format);
|
||||
$response = $this->client->request('GET', $urls[0], ['stream' => true]);
|
||||
|
||||
$contentLengthHeaders = $response->getHeader('Content-Length');
|
||||
$this->init_file_stream_transfer(
|
||||
$this->download->getFilename($this->curVideo->url, $this->format),
|
||||
$contentLengthHeaders[0]
|
||||
);
|
||||
|
||||
$this->curVideo->stream = $response->getBody();
|
||||
// Start streaming the first video.
|
||||
$this->startVideoStream(current($this->videos));
|
||||
}
|
||||
} else {
|
||||
$this->curVideo = current($this->videos);
|
||||
}
|
||||
|
||||
return fread($this->buffer, $count);
|
||||
|
@ -299,10 +296,8 @@ class PlaylistArchiveStream extends TarArchive implements StreamInterface
|
|||
if (is_resource($this->buffer)) {
|
||||
fclose($this->buffer);
|
||||
}
|
||||
foreach ($this->videos as $file) {
|
||||
if (is_resource($file->stream)) {
|
||||
fclose($file->stream);
|
||||
}
|
||||
if (isset($this->curVideoStream)) {
|
||||
$this->curVideoStream->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* PlaylistArchiveVideo class.
|
||||
*/
|
||||
|
||||
namespace Alltube;
|
||||
|
||||
/**
|
||||
* Video streamed to a PlaylistArchiveStream.
|
||||
*/
|
||||
class PlaylistArchiveVideo
|
||||
{
|
||||
/**
|
||||
* Video page URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $url;
|
||||
|
||||
/**
|
||||
* Has the video been streamed entirely ?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $complete = false;
|
||||
|
||||
/**
|
||||
* popen stream containing the video.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public $stream;
|
||||
|
||||
/**
|
||||
* PlaylistArchiveVideo constructor.
|
||||
*
|
||||
* @param string $url Video page URL
|
||||
*/
|
||||
public function __construct($url)
|
||||
{
|
||||
$this->url = $url;
|
||||
}
|
||||
}
|
|
@ -6,13 +6,17 @@
|
|||
namespace Alltube;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use stdClass;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Extract info about videos.
|
||||
*
|
||||
* Due to the way youtube-dl, this class can also contain a playlist.
|
||||
*/
|
||||
class VideoDownload
|
||||
class Video
|
||||
{
|
||||
/**
|
||||
* Config instance.
|
||||
|
@ -21,30 +25,37 @@ class VideoDownload
|
|||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* URL of the page containing the video
|
||||
* @var string
|
||||
*/
|
||||
private $webpageUrl;
|
||||
|
||||
/**
|
||||
* Requested video format
|
||||
* @var string
|
||||
*/
|
||||
private $requestedFormat;
|
||||
|
||||
/**
|
||||
* Password
|
||||
* @var string
|
||||
*/
|
||||
private $password;
|
||||
|
||||
/**
|
||||
* VideoDownload constructor.
|
||||
*
|
||||
* @param Config $config Config instance.
|
||||
*
|
||||
* @throws Exception If youtube-dl is missing
|
||||
* @throws Exception If Python is missing
|
||||
* @param string $webpageUrl URL of the page containing the video
|
||||
* @param string $requestedFormat Requested video format
|
||||
* @param string $password Password
|
||||
*/
|
||||
public function __construct(Config $config = null)
|
||||
public function __construct($webpageUrl, $requestedFormat = 'best', $password = null)
|
||||
{
|
||||
if (isset($config)) {
|
||||
$this->config = $config;
|
||||
} else {
|
||||
$this->config = Config::getInstance();
|
||||
}
|
||||
/*
|
||||
We don't translate these exceptions because they always occur before Slim can catch them
|
||||
so they will always go to the logs.
|
||||
*/
|
||||
if (!is_file($this->config->youtubedl)) {
|
||||
throw new Exception("Can't find youtube-dl at ".$this->config->youtubedl);
|
||||
} elseif (!$this->checkCommand([$this->config->python, '--version'])) {
|
||||
throw new Exception("Can't find Python at ".$this->config->python);
|
||||
}
|
||||
$this->webpageUrl = $webpageUrl;
|
||||
$this->requestedFormat = $requestedFormat;
|
||||
$this->password = $password;
|
||||
$this->config = Config::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,10 +67,11 @@ class VideoDownload
|
|||
*/
|
||||
private function getProcess(array $arguments)
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
return new Process(
|
||||
array_merge(
|
||||
[$this->config->python, $this->config->youtubedl],
|
||||
$this->config->params,
|
||||
[$config->python, $config->youtubedl],
|
||||
$config->params,
|
||||
$arguments
|
||||
)
|
||||
);
|
||||
|
@ -70,18 +82,15 @@ class VideoDownload
|
|||
*
|
||||
* @return string[] Extractors
|
||||
* */
|
||||
public function listExtractors()
|
||||
public static function getExtractors()
|
||||
{
|
||||
return explode("\n", trim($this->getProp(null, null, 'list-extractors')));
|
||||
return explode("\n", trim(self::getProp('list-extractors')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property from youtube-dl.
|
||||
*
|
||||
* @param string $url URL to parse
|
||||
* @param string $format Format
|
||||
* @param string $prop Property
|
||||
* @param string $password Video password
|
||||
*
|
||||
* @throws PasswordException If the video is protected by a password and no password was specified
|
||||
* @throws Exception If the password is wrong
|
||||
|
@ -89,23 +98,30 @@ class VideoDownload
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getProp($url = null, $format = null, $prop = 'dump-json', $password = null)
|
||||
private function getProp($prop = 'dump-json')
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
|
||||
$arguments = ['--'.$prop];
|
||||
if (isset($url)) {
|
||||
$arguments[] = $url;
|
||||
}
|
||||
if (isset($format)) {
|
||||
$arguments[] = '-f '.$format;
|
||||
}
|
||||
if (isset($password)) {
|
||||
$arguments[] = '--video-password';
|
||||
$arguments[] = $password;
|
||||
|
||||
// This function can also be called statically.
|
||||
if (isset($this)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
$process = $this->getProcess($arguments);
|
||||
$process = self::getProcess($arguments);
|
||||
//This is needed by the openload extractor because it runs PhantomJS
|
||||
$process->setEnv(['PATH'=>$this->config->phantomjsDir]);
|
||||
$process->setEnv(['PATH'=>$config->phantomjsDir]);
|
||||
$process->inheritEnvironmentVariables();
|
||||
$process->run();
|
||||
if (!$process->isSuccessful()) {
|
||||
|
@ -126,15 +142,41 @@ class VideoDownload
|
|||
/**
|
||||
* Get all information about a video.
|
||||
*
|
||||
* @param string $url URL of page
|
||||
* @param string $format Format to use for the video
|
||||
* @param string $password Video password
|
||||
*
|
||||
* @return stdClass Decoded JSON
|
||||
* */
|
||||
public function getJSON($url, $format = null, $password = null)
|
||||
public function getJson()
|
||||
{
|
||||
return json_decode($this->getProp($url, $format, 'dump-single-json', $password));
|
||||
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
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if (isset($this->$name)) {
|
||||
return $this->getJson()->$name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to check if the JSON object returned by youtube-dl has a property.
|
||||
*
|
||||
* @param string $name Property
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->getJson()->$name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,15 +186,11 @@ class VideoDownload
|
|||
* But it can return two URLs when multiple formats are specified
|
||||
* (eg. bestvideo+bestaudio).
|
||||
*
|
||||
* @param string $url URL of page
|
||||
* @param string $format Format to use for the video
|
||||
* @param string $password Video password
|
||||
*
|
||||
* @return string[] URLs of video
|
||||
* */
|
||||
public function getURL($url, $format = null, $password = null)
|
||||
public function getUrl()
|
||||
{
|
||||
$urls = explode("\n", $this->getProp($url, $format, 'get-url', $password));
|
||||
$urls = explode("\n", $this->getProp('get-url'));
|
||||
|
||||
if (empty($urls[0])) {
|
||||
throw new EmptyUrlException(_('youtube-dl returned an empty URL.'));
|
||||
|
@ -164,32 +202,25 @@ class VideoDownload
|
|||
/**
|
||||
* Get filename of video file from URL of page.
|
||||
*
|
||||
* @param string $url URL of page
|
||||
* @param string $format Format to use for the video
|
||||
* @param string $password Video password
|
||||
*
|
||||
* @return string Filename of extracted video
|
||||
* */
|
||||
public function getFilename($url, $format = null, $password = null)
|
||||
public function getFilename()
|
||||
{
|
||||
return trim($this->getProp($url, $format, 'get-filename', $password));
|
||||
return trim($this->getProp('get-filename'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename of video with the specified extension.
|
||||
*
|
||||
* @param string $extension New file extension
|
||||
* @param string $url URL of page
|
||||
* @param string $format Format to use for the video
|
||||
* @param string $password Video password
|
||||
*
|
||||
* @return string Filename of extracted video with specified extension
|
||||
*/
|
||||
public function getFileNameWithExtension($extension, $url, $format = null, $password = null)
|
||||
public function getFileNameWithExtension($extension)
|
||||
{
|
||||
return html_entity_decode(
|
||||
pathinfo(
|
||||
$this->getFilename($url, $format, $password),
|
||||
$this->getFilename(),
|
||||
PATHINFO_FILENAME
|
||||
).'.'.$extension,
|
||||
ENT_COMPAT,
|
||||
|
@ -197,32 +228,16 @@ class VideoDownload
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename of audio from URL of page.
|
||||
*
|
||||
* @param string $url URL of page
|
||||
* @param string $format Format to use for the video
|
||||
* @param string $password Video password
|
||||
*
|
||||
* @return string Filename of converted audio file
|
||||
* */
|
||||
public function getAudioFilename($url, $format = null, $password = null)
|
||||
{
|
||||
return $this->getFileNameWithExtension('mp3', $url, $format, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return arguments used to run rtmp for a specific video.
|
||||
*
|
||||
* @param stdClass $video Video object returned by youtube-dl
|
||||
*
|
||||
* @return array Arguments
|
||||
*/
|
||||
private function getRtmpArguments(stdClass $video)
|
||||
private function getRtmpArguments()
|
||||
{
|
||||
$arguments = [];
|
||||
|
||||
if ($video->protocol == 'rtmp') {
|
||||
if ($this->protocol == 'rtmp') {
|
||||
foreach ([
|
||||
'url' => '-rtmp_tcurl',
|
||||
'webpage_url' => '-rtmp_pageurl',
|
||||
|
@ -231,14 +246,14 @@ class VideoDownload
|
|||
'play_path' => '-rtmp_playpath',
|
||||
'app' => '-rtmp_app',
|
||||
] as $property => $option) {
|
||||
if (isset($video->{$property})) {
|
||||
if (isset($this->{$property})) {
|
||||
$arguments[] = $option;
|
||||
$arguments[] = $video->{$property};
|
||||
$arguments[] = $this->{$property};
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($video->rtmp_conn)) {
|
||||
foreach ($video->rtmp_conn as $conn) {
|
||||
if (isset($this->rtmp_conn)) {
|
||||
foreach ($this->rtmp_conn as $conn) {
|
||||
$arguments[] = '-rtmp_conn';
|
||||
$arguments[] = $conn;
|
||||
}
|
||||
|
@ -255,7 +270,7 @@ class VideoDownload
|
|||
*
|
||||
* @return bool False if the command returns an error, true otherwise
|
||||
*/
|
||||
private function checkCommand(array $command)
|
||||
public static function checkCommand(array $command)
|
||||
{
|
||||
$process = new Process($command);
|
||||
$process->run();
|
||||
|
@ -266,7 +281,6 @@ class VideoDownload
|
|||
/**
|
||||
* Get a process that runs avconv in order to convert a video.
|
||||
*
|
||||
* @param stdClass $video Video object returned by youtube-dl
|
||||
* @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
|
||||
|
@ -278,7 +292,6 @@ class VideoDownload
|
|||
* @return Process Process
|
||||
*/
|
||||
private function getAvconvProcess(
|
||||
stdClass $video,
|
||||
$audioBitrate,
|
||||
$filetype = 'mp3',
|
||||
$audioOnly = true,
|
||||
|
@ -317,9 +330,9 @@ class VideoDownload
|
|||
$this->config->avconv,
|
||||
'-v', $this->config->avconvVerbosity,
|
||||
],
|
||||
$this->getRtmpArguments($video),
|
||||
$this->getRtmpArguments(),
|
||||
[
|
||||
'-i', $video->url,
|
||||
'-i', $this->url,
|
||||
'-f', $filetype,
|
||||
'-b:a', $audioBitrate.'k',
|
||||
],
|
||||
|
@ -328,10 +341,10 @@ class VideoDownload
|
|||
'pipe:1',
|
||||
]
|
||||
);
|
||||
if ($video->url != '-') {
|
||||
if ($this->url != '-') {
|
||||
//Vimeo needs a correct user-agent
|
||||
$arguments[] = '-user_agent';
|
||||
$arguments[] = $this->getProp(null, null, 'dump-user-agent');
|
||||
$arguments[] = $this->getProp('dump-user-agent');
|
||||
}
|
||||
|
||||
return new Process($arguments);
|
||||
|
@ -340,34 +353,29 @@ class VideoDownload
|
|||
/**
|
||||
* Get audio stream of converted video.
|
||||
*
|
||||
* @param string $url URL of page
|
||||
* @param string $format Format to use for the video
|
||||
* @param string $password Video password
|
||||
* @param string $from Start the conversion at this time
|
||||
* @param string $to End the conversion at this time
|
||||
*
|
||||
* @throws Exception If your try to convert and M3U8 video
|
||||
* @throws Exception If your try to convert an M3U8 video
|
||||
* @throws Exception If the popen stream was not created correctly
|
||||
*
|
||||
* @return resource popen stream
|
||||
*/
|
||||
public function getAudioStream($url, $format, $password = null, $from = null, $to = null)
|
||||
public function getAudioStream($from = null, $to = null)
|
||||
{
|
||||
$video = $this->getJSON($url, $format, $password);
|
||||
|
||||
if (isset($video->_type) && $video->_type == 'playlist') {
|
||||
if (isset($this->_type) && $this->_type == 'playlist') {
|
||||
throw new Exception(_('Conversion of playlists is not supported.'));
|
||||
}
|
||||
|
||||
if (isset($video->protocol)) {
|
||||
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
|
||||
if (isset($this->protocol)) {
|
||||
if (in_array($this->protocol, ['m3u8', 'm3u8_native'])) {
|
||||
throw new Exception(_('Conversion of M3U8 files is not supported.'));
|
||||
} elseif ($video->protocol == 'http_dash_segments') {
|
||||
} elseif ($this->protocol == 'http_dash_segments') {
|
||||
throw new Exception(_('Conversion of DASH segments is not supported.'));
|
||||
}
|
||||
}
|
||||
|
||||
$avconvProc = $this->getAvconvProcess($video, $this->config->audioBitrate, 'mp3', true, $from, $to);
|
||||
$avconvProc = $this->getAvconvProcess($this->config->audioBitrate, 'mp3', true, $from, $to);
|
||||
|
||||
$stream = popen($avconvProc->getCommandLine(), 'r');
|
||||
|
||||
|
@ -381,14 +389,12 @@ class VideoDownload
|
|||
/**
|
||||
* Get video stream from an M3U playlist.
|
||||
*
|
||||
* @param stdClass $video Video object returned by getJSON
|
||||
*
|
||||
* @throws Exception If avconv/ffmpeg is missing
|
||||
* @throws Exception If the popen stream was not created correctly
|
||||
*
|
||||
* @return resource popen stream
|
||||
*/
|
||||
public function getM3uStream(stdClass $video)
|
||||
public function getM3uStream()
|
||||
{
|
||||
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
|
||||
throw new Exception(_('Can\'t find avconv or ffmpeg at ').$this->config->avconv.'.');
|
||||
|
@ -398,8 +404,8 @@ class VideoDownload
|
|||
[
|
||||
$this->config->avconv,
|
||||
'-v', $this->config->avconvVerbosity,
|
||||
'-i', $video->url,
|
||||
'-f', $video->ext,
|
||||
'-i', $this->url,
|
||||
'-f', $this->ext,
|
||||
'-c', 'copy',
|
||||
'-bsf:a', 'aac_adtstoasc',
|
||||
'-movflags', 'frag_keyframe+empty_moov',
|
||||
|
@ -418,14 +424,18 @@ class VideoDownload
|
|||
/**
|
||||
* Get an avconv stream to remux audio and video.
|
||||
*
|
||||
* @param array $urls URLs of the video ($urls[0]) and audio ($urls[1]) files
|
||||
*
|
||||
* @throws Exception If the popen stream was not created correctly
|
||||
*
|
||||
* @return resource popen stream
|
||||
*/
|
||||
public function getRemuxStream(array $urls)
|
||||
public function getRemuxStream()
|
||||
{
|
||||
$urls = $this->getUrl();
|
||||
|
||||
if (!isset($urls[0]) || !isset($urls[1])) {
|
||||
throw new Exception(_('This video does not have two URLs.'));
|
||||
}
|
||||
|
||||
$process = new Process(
|
||||
[
|
||||
$this->config->avconv,
|
||||
|
@ -451,13 +461,11 @@ class VideoDownload
|
|||
/**
|
||||
* Get video stream from an RTMP video.
|
||||
*
|
||||
* @param stdClass $video Video object returned by getJSON
|
||||
*
|
||||
* @throws Exception If the popen stream was not created correctly
|
||||
*
|
||||
* @return resource popen stream
|
||||
*/
|
||||
public function getRtmpStream(stdClass $video)
|
||||
public function getRtmpStream()
|
||||
{
|
||||
$process = new Process(
|
||||
array_merge(
|
||||
|
@ -465,10 +473,10 @@ class VideoDownload
|
|||
$this->config->avconv,
|
||||
'-v', $this->config->avconvVerbosity,
|
||||
],
|
||||
$this->getRtmpArguments($video),
|
||||
$this->getRtmpArguments(),
|
||||
[
|
||||
'-i', $video->url,
|
||||
'-f', $video->ext,
|
||||
'-i', $this->url,
|
||||
'-f', $this->ext,
|
||||
'pipe:1',
|
||||
]
|
||||
)
|
||||
|
@ -484,25 +492,21 @@ class VideoDownload
|
|||
/**
|
||||
* Get the stream of a converted video.
|
||||
*
|
||||
* @param string $url URL of page
|
||||
* @param string $format Source format to use for the conversion
|
||||
* @param int $audioBitrate Audio bitrate of the converted file
|
||||
* @param string $filetype Filetype of the converted file
|
||||
* @param string $password Video password
|
||||
*
|
||||
* @throws Exception If your try to convert and M3U8 video
|
||||
* @throws Exception If the popen stream was not created correctly
|
||||
*
|
||||
* @return resource popen stream
|
||||
*/
|
||||
public function getConvertedStream($url, $format, $audioBitrate, $filetype, $password = null)
|
||||
public function getConvertedStream($audioBitrate, $filetype)
|
||||
{
|
||||
$video = $this->getJSON($url, $format, $password);
|
||||
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
|
||||
if (in_array($this->protocol, ['m3u8', 'm3u8_native'])) {
|
||||
throw new Exception(_('Conversion of M3U8 files is not supported.'));
|
||||
}
|
||||
|
||||
$avconvProc = $this->getAvconvProcess($video, $audioBitrate, $filetype, false);
|
||||
$avconvProc = $this->getAvconvProcess($audioBitrate, $filetype, false);
|
||||
|
||||
$stream = popen($avconvProc->getCommandLine(), 'r');
|
||||
|
||||
|
@ -512,4 +516,29 @@ class VideoDownload
|
|||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the same video but with another format.
|
||||
*
|
||||
* @param string $format New format
|
||||
*
|
||||
* @return Video
|
||||
*/
|
||||
public function withFormat($format)
|
||||
{
|
||||
return new Video($this->webpageUrl, $format, $this->password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a HTTP response containing the video.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function getHttpResponse()
|
||||
{
|
||||
$client = new Client();
|
||||
$urls = $this->getUrl();
|
||||
|
||||
return $client->request('GET', $urls[0], ['stream' => true]);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue