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; 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. * 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 * @throws \Exception If avconv/ffmpeg is missing
* *
* @return Process Process * @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'])) { if (!$this->checkCommand([$this->config->avconv, '-version'])) {
throw(new \Exception('Can\'t find avconv or ffmpeg')); throw(new \Exception('Can\'t find avconv or ffmpeg'));
@ -269,6 +272,12 @@ class VideoDownload
$rtmpArguments = []; $rtmpArguments = [];
} }
if ($audioOnly) {
$videoArguments = ['-vn'];
} else {
$videoArguments = [];
}
$arguments = array_merge( $arguments = array_merge(
[ [
$this->config->avconv, $this->config->avconv,
@ -277,9 +286,11 @@ class VideoDownload
$rtmpArguments, $rtmpArguments,
[ [
'-i', $video->url, '-i', $video->url,
'-f', 'mp3', '-f', $filetype,
'-b:a', $this->config->audioBitrate.'k', '-b:a', $audioBitrate.'k',
'-vn', ],
$videoArguments,
[
'pipe:1', 'pipe:1',
] ]
); );
@ -311,7 +322,7 @@ class VideoDownload
throw(new \Exception('Conversion of M3U8 files is not supported.')); 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'); $stream = popen($avconvProc->getCommandLine(), 'r');
@ -448,4 +459,36 @@ class VideoDownload
return $stream; 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 # True to enable audio conversion
convert: false 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 # Path to your avconv or ffmpeg binary
avconv: vendor/bin/ffmpeg 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. * Redirect to video file.
* *
@ -481,14 +520,18 @@ class FrontController
*/ */
public function redirect(Request $request, Response $response) public function redirect(Request $request, Response $response)
{ {
$url = $request->getQueryParam('url'); $params = $request->getQueryParams();
$format = $this->getFormat($request); $format = $this->getFormat($request);
if (isset($url)) { if (isset($params['url'])) {
try { 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) { } catch (PasswordException $e) {
return $response->withRedirect( 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) { } catch (\Exception $e) {
$response->getBody()->write($e->getMessage()); $response->getBody()->write($e->getMessage());

View file

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

View file

@ -17,8 +17,8 @@ msgstr ""
msgid ":" msgid ":"
msgstr "" msgstr ""
#: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:83 #: templates/playlist.tpl:26 templates/password.tpl:10 templates/video.tpl:97
#: templates/video.tpl:86 templates/index.tpl:19 #: templates/video.tpl:100 templates/index.tpl:19
msgid "Download" msgid "Download"
msgstr "" msgstr ""
@ -70,6 +70,14 @@ msgstr ""
msgid "Detailed formats" msgid "Detailed formats"
msgstr "" 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 #: templates/inc/footer.tpl:4
msgid "Code by" msgid "Code by"
msgstr "" 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 You can also enable the experimental remux mode
that will merge the best video and the best audio format on the fly. 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} {/if}
{/foreach} {/foreach}
</optgroup> </optgroup>
<option value="{$format->format_id}">
</select><br/><br/> </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/> <input class="downloadBtn" type="submit" value="{t}Download{/t}" /><br/>
</form> </form>
{else} {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. * Test the locale() function.
* *

View file

@ -124,4 +124,15 @@ class VideoDownloadStubsTest extends TestCase
); );
$this->download->getPlaylistArchiveStream($video, 'best'); $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 * @return void
* @requires OS Linux * @requires OS Linux
@ -498,4 +498,33 @@ class VideoDownloadTest extends TestCase
); );
$this->assertStream($this->download->getPlaylistArchiveStream($video, 'best')); $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');
}
} }