Skip to content
Permalink
Browse files
feat: Add a new convertAdvanced option
It allows user to convert videos to several other audio/video formats

Fixes #148
  • Loading branch information
Rudloff committed Jan 24, 2018
1 parent c5e3c61 commit 4972c8ab8e1e9a4899b5f8b371342ca494bc0487
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 14 deletions.
@@ -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.
*
@@ -249,15 +249,18 @@ private function checkCommand(array $command)
}

/**
* 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 @@ private function getAvconvMp3Process(\stdClass $video)
$rtmpArguments = [];
}

if ($audioOnly) {
$videoArguments = ['-vn'];
} else {
$videoArguments = [];
}

$arguments = array_merge(
[
$this->config->avconv,
@@ -277,9 +286,11 @@ private function getAvconvMp3Process(\stdClass $video)
$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 @@ public function getAudioStream($url, $format, $password = null)
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 @@ public function getPlaylistArchiveStream(\stdClass $video, $format)

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;
}
}
@@ -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

@@ -471,6 +471,45 @@ private function getRedirectResponse($url, $format, Response $response, Request
}
}

/**
* 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 @@ private function getRedirectResponse($url, $format, Response $response, Request
*/
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());
@@ -556,6 +556,10 @@ h1 {
font-family:monospace;
}

.customBitrate {
width: 6ex;
}

.locales {
float: left;
padding-left: 1em;
@@ -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 ""
@@ -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.
@@ -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}
@@ -517,6 +517,27 @@ public function testRedirectWithPlaylist()
);
}

/**
* 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.
*
@@ -124,4 +124,15 @@ public function testGetPlaylistArchiveStreamWithPopenError()
);
$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');
}
}
@@ -485,7 +485,7 @@ public function testGetM3uStreamAvconvError($url, $format)
}

/**
* Test getPlaylistArchiveStream function without avconv.
* Test getPlaylistArchiveStream function.
*
* @return void
* @requires OS Linux
@@ -498,4 +498,33 @@ public function testGetPlaylistArchiveStream()
);
$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');
}
}

0 comments on commit 4972c8a

Please sign in to comment.