feat: Add a new convertAdvanced option

It allows user to convert videos to several other audio/video formats

Fixes #148
This commit is contained in:
Pierre Rudloff 2018-01-24 23:30:24 +01:00
parent c5e3c618f2
commit 4972c8ab8e
11 changed files with 221 additions and 14 deletions

View file

@ -47,6 +47,20 @@ class Config
*/
public $convert = false;
/**
* Enable advanced conversion mode.
*
* @var bool
*/
public $convertAdvanced = false;
/**
* List of formats available in advanced conversion mode.
*
* @var array
*/
public $convertAdvancedFormats = ['mp3', 'avi', 'flv', 'wav'];
/**
* avconv or ffmpeg binary path.
*

View file

@ -249,15 +249,18 @@ class VideoDownload
}
/**
* Get a process that runs avconv in order to convert a video to MP3.
* Get a process that runs avconv in order to convert a video.
*
* @param object $video Video object returned by youtube-dl
* @param object $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
*
* @throws \Exception If avconv/ffmpeg is missing
*
* @return Process Process
*/
private function getAvconvMp3Process(\stdClass $video)
private function getAvconvProcess(\stdClass $video, $audioBitrate, $filetype = 'mp3', $audioOnly = true)
{
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw(new \Exception('Can\'t find avconv or ffmpeg'));
@ -269,6 +272,12 @@ class VideoDownload
$rtmpArguments = [];
}
if ($audioOnly) {
$videoArguments = ['-vn'];
} else {
$videoArguments = [];
}
$arguments = array_merge(
[
$this->config->avconv,
@ -277,9 +286,11 @@ class VideoDownload
$rtmpArguments,
[
'-i', $video->url,
'-f', 'mp3',
'-b:a', $this->config->audioBitrate.'k',
'-vn',
'-f', $filetype,
'-b:a', $audioBitrate.'k',
],
$videoArguments,
[
'pipe:1',
]
);
@ -311,7 +322,7 @@ class VideoDownload
throw(new \Exception('Conversion of M3U8 files is not supported.'));
}
$avconvProc = $this->getAvconvMp3Process($video);
$avconvProc = $this->getAvconvProcess($video, $this->config->audioBitrate);
$stream = popen($avconvProc->getCommandLine(), 'r');
@ -448,4 +459,36 @@ class VideoDownload
return $stream;
}
/**
* 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)
{
$video = $this->getJSON($url, $format, $password);
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
throw(new \Exception('Conversion of M3U8 files is not supported.'));
}
$avconvProc = $this->getAvconvProcess($video, $audioBitrate, $filetype, false);
$stream = popen($avconvProc->getCommandLine(), 'r');
if (!is_resource($stream)) {
throw new \Exception('Could not open popen stream.');
}
return $stream;
}
}

View file

@ -15,6 +15,12 @@ params:
# True to enable audio conversion
convert: false
# True to enable advanced conversion mode
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

View file

@ -471,6 +471,45 @@ class FrontController
}
}
/**
* Return a converted video file.
*
* @param Request $request PSR-7 request
* @param Response $response PSR-7 response
* @param array $params GET query parameters
* @param string $format Requested source format
*
* @return Response HTTP response
*/
private function getConvertedResponse(Request $request, Response $response, array $params, $format)
{
$password = $request->getParam('password');
$response = $response->withHeader(
'Content-Disposition',
'attachment; filename="'.
$this->download->getFileNameWithExtension(
$params['customFormat'],
$params['url'],
$format,
$password
).'"'
);
$response = $response->withHeader('Content-Type', 'video/'.$params['customFormat']);
if ($request->isGet() || $request->isPost()) {
$process = $this->download->getConvertedStream(
$params['url'],
$format,
$params['customBitrate'],
$params['customFormat'],
$password
);
$response = $response->withBody(new Stream($process));
}
return $response;
}
/**
* Redirect to video file.
*
@ -481,14 +520,18 @@ class FrontController
*/
public function redirect(Request $request, Response $response)
{
$url = $request->getQueryParam('url');
$params = $request->getQueryParams();
$format = $this->getFormat($request);
if (isset($url)) {
if (isset($params['url'])) {
try {
return $this->getRedirectResponse($url, $format, $response, $request);
if ($this->config->convertAdvanced && !is_null($request->getQueryParam('customConvert'))) {
return $this->getConvertedResponse($request, $response, $params, $format);
}
return $this->getRedirectResponse($params['url'], $format, $response, $request);
} catch (PasswordException $e) {
return $response->withRedirect(
$this->container->get('router')->pathFor('video').'?url='.urlencode($url)
$this->container->get('router')->pathFor('video').'?url='.urlencode($params['url'])
);
} catch (\Exception $e) {
$response->getBody()->write($e->getMessage());

View file

@ -556,6 +556,10 @@ h1 {
font-family:monospace;
}
.customBitrate {
width: 6ex;
}
.locales {
float: left;
padding-left: 1em;

View file

@ -17,8 +17,8 @@ msgstr ""
msgid ":"
msgstr ""
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:83
#: templates/video.tpl:86 templates/index.tpl:19
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:97
#: templates/video.tpl:100 templates/index.tpl:19
msgid "Download"
msgstr ""
@ -70,6 +70,14 @@ msgstr ""
msgid "Detailed formats"
msgstr ""
#: templates/video.tpl:86
msgid "Convert into a custom format:"
msgstr ""
#: templates/video.tpl:94
msgid "kbit/s audio"
msgstr ""
#: templates/inc/footer.tpl:4
msgid "Code by"
msgstr ""

View file

@ -140,3 +140,17 @@ You then need to merge them together with a tool like ffmpeg.
You can also enable the experimental remux mode
that will merge the best video and the best audio format on the fly.
## I want to convert videos to something other than MP3
By default the `convert` option only allows converting to MP3,
in order to keep things simple and ressources usage low.
However, you can use the `convertAdvanced` option like this:
```yaml
convertAdvanced: true
convertAdvancedFormats: [mp3, avi, flv, wav]
```
This will add new inputs on the download page
that allow users to converted videos to other formats.

View file

@ -79,7 +79,21 @@
{/if}
{/foreach}
</optgroup>
<option value="{$format->format_id}">
</select><br/><br/>
{if $config->convertAdvanced}
<input type="checkbox" name="customConvert" id="customConvert"/>
<label for="customConvert">{t}Convert into a custom format:{/t}</label>
<select title="Custom format" name="customFormat">
{foreach $config->convertAdvancedFormats as $format}
<option>{$format}</option>
{/foreach}
</select>
with
<input type="number" value="{$config->audioBitrate}" title="Custom bitrate" class="customBitrate"name="customBitrate" id="customBitrate" />
<label for="customBitrate">{t}kbit/s audio{/t}</label>
<br/><br/>
{/if}
<input class="downloadBtn" type="submit" value="{t}Download{/t}" /><br/>
</form>
{else}

View file

@ -517,6 +517,27 @@ class FrontControllerTest extends TestCase
);
}
/**
* Test the redirect() function with an advanced conversion.
*
* @return void
*/
public function testRedirectWithAdvancedConversion()
{
$this->config->convertAdvanced = true;
$this->assertRequestIsOk(
'redirect',
[
'url' => 'https://www.youtube.com/watch?v=M7IpKCZ47pU',
'format' => 'best',
'customConvert' => 'on',
'customBitrate' => 32,
'customFormat' => 'flv',
],
$this->config
);
}
/**
* Test the locale() function.
*

View file

@ -124,4 +124,15 @@ class VideoDownloadStubsTest extends TestCase
);
$this->download->getPlaylistArchiveStream($video, 'best');
}
/**
* Test getConvertedStream function with a buggy popen.
*
* @return void
* @expectedException Exception
*/
public function testGetConvertedStreamWithPopenError()
{
$this->download->getConvertedStream($this->url, 'best', 32, 'flv');
}
}

View file

@ -485,7 +485,7 @@ class VideoDownloadTest extends TestCase
}
/**
* Test getPlaylistArchiveStream function without avconv.
* Test getPlaylistArchiveStream function.
*
* @return void
* @requires OS Linux
@ -498,4 +498,33 @@ class VideoDownloadTest extends TestCase
);
$this->assertStream($this->download->getPlaylistArchiveStream($video, 'best'));
}
/**
* Test getConvertedStream function without avconv.
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @dataProvider urlProvider
*/
public function testGetConvertedStream($url, $format)
{
$this->assertStream($this->download->getConvertedStream($url, $format, 32, 'flv'));
}
/**
* Test getConvertedStream function with a M3U8 file.
*
* @param string $url URL
* @param string $format Format
*
* @return void
* @expectedException Exception
* @dataProvider m3uUrlProvider
*/
public function testGetConvertedStreamM3uError($url, $format)
{
$this->download->getConvertedStream($url, $format, 32, 'flv');
}
}