feat: Add a way to trim the audio
This commit is contained in:
parent
c80828f300
commit
f1cf0a2cdc
4 changed files with 112 additions and 38 deletions
|
@ -268,27 +268,52 @@ class VideoDownload
|
||||||
* @param int $audioBitrate Audio bitrate of the converted file
|
* @param int $audioBitrate Audio bitrate of the converted file
|
||||||
* @param string $filetype Filetype of the converted file
|
* @param string $filetype Filetype of the converted file
|
||||||
* @param bool $audioOnly True to return an audio-only 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
|
||||||
*
|
*
|
||||||
* @throws Exception If avconv/ffmpeg is missing
|
* @throws Exception If avconv/ffmpeg is missing
|
||||||
*
|
*
|
||||||
* @return Process Process
|
* @return Process Process
|
||||||
*/
|
*/
|
||||||
private function getAvconvProcess(stdClass $video, $audioBitrate, $filetype = 'mp3', $audioOnly = true)
|
private function getAvconvProcess(
|
||||||
{
|
stdClass $video,
|
||||||
|
$audioBitrate,
|
||||||
|
$filetype = 'mp3',
|
||||||
|
$audioOnly = true,
|
||||||
|
$from = null,
|
||||||
|
$to = null
|
||||||
|
) {
|
||||||
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
|
if (!$this->checkCommand([$this->config->avconv, '-version'])) {
|
||||||
throw new Exception(_('Can\'t find avconv or ffmpeg at ').$this->config->avconv.'.');
|
throw new Exception(_('Can\'t find avconv or ffmpeg at ').$this->config->avconv.'.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$durationRegex = '/(\d+:)?(\d+:)?(\d+)/';
|
||||||
|
|
||||||
if ($video->protocol == 'rtmp') {
|
if ($video->protocol == 'rtmp') {
|
||||||
$rtmpArguments = $this->getRtmpArguments($video);
|
$beforeArguments = $this->getRtmpArguments($video);
|
||||||
} else {
|
} else {
|
||||||
$rtmpArguments = [];
|
$beforeArguments = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($audioOnly) {
|
if ($audioOnly) {
|
||||||
$videoArguments = ['-vn'];
|
$afterArguments = ['-vn'];
|
||||||
} else {
|
} else {
|
||||||
$videoArguments = [];
|
$afterArguments = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($from) && !empty($from)) {
|
||||||
|
if (!preg_match($durationRegex, $from)) {
|
||||||
|
throw new Exception(_('Invalid start time: ').$from.'.');
|
||||||
|
}
|
||||||
|
$afterArguments[] = '-ss';
|
||||||
|
$afterArguments[] = $from;
|
||||||
|
}
|
||||||
|
if (isset($to) && !empty($to)) {
|
||||||
|
if (!preg_match($durationRegex, $to)) {
|
||||||
|
throw new Exception(_('Invalid end time: ').$to.'.');
|
||||||
|
}
|
||||||
|
$afterArguments[] = '-to';
|
||||||
|
$afterArguments[] = $to;
|
||||||
}
|
}
|
||||||
|
|
||||||
$arguments = array_merge(
|
$arguments = array_merge(
|
||||||
|
@ -296,13 +321,13 @@ class VideoDownload
|
||||||
$this->config->avconv,
|
$this->config->avconv,
|
||||||
'-v', $this->config->avconvVerbosity,
|
'-v', $this->config->avconvVerbosity,
|
||||||
],
|
],
|
||||||
$rtmpArguments,
|
$beforeArguments,
|
||||||
[
|
[
|
||||||
'-i', $video->url,
|
'-i', $video->url,
|
||||||
'-f', $filetype,
|
'-f', $filetype,
|
||||||
'-b:a', $audioBitrate.'k',
|
'-b:a', $audioBitrate.'k',
|
||||||
],
|
],
|
||||||
$videoArguments,
|
$afterArguments,
|
||||||
[
|
[
|
||||||
'pipe:1',
|
'pipe:1',
|
||||||
]
|
]
|
||||||
|
@ -322,13 +347,15 @@ class VideoDownload
|
||||||
* @param string $url URL of page
|
* @param string $url URL of page
|
||||||
* @param string $format Format to use for the video
|
* @param string $format Format to use for the video
|
||||||
* @param string $password Video password
|
* @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 and M3U8 video
|
||||||
* @throws Exception If the popen stream was not created correctly
|
* @throws Exception If the popen stream was not created correctly
|
||||||
*
|
*
|
||||||
* @return resource popen stream
|
* @return resource popen stream
|
||||||
*/
|
*/
|
||||||
public function getAudioStream($url, $format, $password = null)
|
public function getAudioStream($url, $format, $password = null, $from = null, $to = null)
|
||||||
{
|
{
|
||||||
$video = $this->getJSON($url, $format, $password);
|
$video = $this->getJSON($url, $format, $password);
|
||||||
|
|
||||||
|
@ -336,13 +363,15 @@ class VideoDownload
|
||||||
throw new Exception(_('Conversion of playlists is not supported.'));
|
throw new Exception(_('Conversion of playlists is not supported.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
|
if (isset($video->protocol)) {
|
||||||
throw new Exception(_('Conversion of M3U8 files is not supported.'));
|
if (in_array($video->protocol, ['m3u8', 'm3u8_native'])) {
|
||||||
} elseif ($video->protocol == 'http_dash_segments') {
|
throw new Exception(_('Conversion of M3U8 files is not supported.'));
|
||||||
throw new Exception(_('Conversion of DASH segments is not supported.'));
|
} elseif ($video->protocol == 'http_dash_segments') {
|
||||||
|
throw new Exception(_('Conversion of DASH segments is not supported.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$avconvProc = $this->getAvconvProcess($video, $this->config->audioBitrate);
|
$avconvProc = $this->getAvconvProcess($video, $this->config->audioBitrate, 'mp3', true, $from, $to);
|
||||||
|
|
||||||
$stream = popen($avconvProc->getCommandLine(), 'r');
|
$stream = popen($avconvProc->getCommandLine(), 'r');
|
||||||
|
|
||||||
|
|
|
@ -201,7 +201,50 @@ class FrontController
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the converted MP3 file.
|
* Return a converted MP3 file.
|
||||||
|
*
|
||||||
|
* @param Request $request PSR-7 request
|
||||||
|
* @param Response $response PSR-7 response
|
||||||
|
* @param array $params GET query parameters
|
||||||
|
* @param string $password Video password
|
||||||
|
*
|
||||||
|
* @return Response HTTP response
|
||||||
|
*/
|
||||||
|
private function getConvertedAudioResponse(Request $request, Response $response, array $params, $password = null)
|
||||||
|
{
|
||||||
|
$response = $response->withHeader(
|
||||||
|
'Content-Disposition',
|
||||||
|
'attachment; filename="'.
|
||||||
|
$this->download->getAudioFilename($params['url'], 'bestaudio/best', $password).'"'
|
||||||
|
);
|
||||||
|
$response = $response->withHeader('Content-Type', 'audio/mpeg');
|
||||||
|
|
||||||
|
if ($request->isGet() || $request->isPost()) {
|
||||||
|
try {
|
||||||
|
$process = $this->download->getAudioStream(
|
||||||
|
$params['url'],
|
||||||
|
'bestaudio/best',
|
||||||
|
$password,
|
||||||
|
$params['from'],
|
||||||
|
$params['to']
|
||||||
|
);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$process = $this->download->getAudioStream(
|
||||||
|
$params['url'],
|
||||||
|
$this->defaultFormat,
|
||||||
|
$password,
|
||||||
|
$params['from'],
|
||||||
|
$params['to']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$response = $response->withBody(new Stream($process));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the MP3 file.
|
||||||
*
|
*
|
||||||
* @param Request $request PSR-7 request
|
* @param Request $request PSR-7 request
|
||||||
* @param Response $response PSR-7 response
|
* @param Response $response PSR-7 response
|
||||||
|
@ -213,6 +256,10 @@ class FrontController
|
||||||
private function getAudioResponse(Request $request, Response $response, array $params, $password = null)
|
private function getAudioResponse(Request $request, Response $response, array $params, $password = null)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if (isset($params['from']) || isset($params['to'])) {
|
||||||
|
throw new Exception('Force convert when we need to seek.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->config->stream) {
|
if ($this->config->stream) {
|
||||||
return $this->getStream($params['url'], 'mp3', $response, $request, $password);
|
return $this->getStream($params['url'], 'mp3', $response, $request, $password);
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,23 +270,7 @@ class FrontController
|
||||||
} catch (PasswordException $e) {
|
} catch (PasswordException $e) {
|
||||||
return $this->password($request, $response);
|
return $this->password($request, $response);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$response = $response->withHeader(
|
return $this->getConvertedAudioResponse($request, $response, $params, $password);
|
||||||
'Content-Disposition',
|
|
||||||
'attachment; filename="'.
|
|
||||||
$this->download->getAudioFilename($params['url'], 'bestaudio/best', $password).'"'
|
|
||||||
);
|
|
||||||
$response = $response->withHeader('Content-Type', 'audio/mpeg');
|
|
||||||
|
|
||||||
if ($request->isGet() || $request->isPost()) {
|
|
||||||
try {
|
|
||||||
$process = $this->download->getAudioStream($params['url'], 'bestaudio/best', $password);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$process = $this->download->getAudioStream($params['url'], $this->defaultFormat, $password);
|
|
||||||
}
|
|
||||||
$response = $response->withBody(new Stream($process));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -260,11 +260,10 @@ footer a:hover {
|
||||||
width:622px;
|
width:622px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mp3 p {
|
.mp3-inner {
|
||||||
padding:3px;
|
padding:3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.audio:not(:checked),
|
.audio:not(:checked),
|
||||||
.audio:checked {
|
.audio:checked {
|
||||||
left: -9999px;
|
left: -9999px;
|
||||||
|
@ -273,7 +272,7 @@ footer a:hover {
|
||||||
.audio:not(:checked) + label,
|
.audio:not(:checked) + label,
|
||||||
.audio:checked + label {
|
.audio:checked + label {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height:22px;
|
line-height:20px;
|
||||||
padding-left: 82px;
|
padding-left: 82px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -373,7 +372,15 @@ footer a:hover {
|
||||||
width:73px;
|
width:73px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.seekOptions {
|
||||||
|
display: none;
|
||||||
|
margin-top: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio:checked ~ .seekOptions {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Playlists */
|
/* Playlists */
|
||||||
|
|
|
@ -19,9 +19,16 @@
|
||||||
<input class="downloadBtn large-font" type="submit" value="{t}Download{/t}" /><br/>
|
<input class="downloadBtn large-font" type="submit" value="{t}Download{/t}" /><br/>
|
||||||
{if $config->convert}
|
{if $config->convert}
|
||||||
<div class="mp3 small-font">
|
<div class="mp3 small-font">
|
||||||
<p><input type="checkbox" id="audio" class="audio" name="audio">
|
<div class="mp3-inner">
|
||||||
<label for="audio"><span class="ui"></span>
|
<input type="checkbox" id="audio" class="audio" name="audio">
|
||||||
{t}Audio only (MP3){/t}</label></p>
|
<label for="audio"><span class="ui"></span>
|
||||||
|
{t}Audio only (MP3){/t}
|
||||||
|
</label>
|
||||||
|
<div class="seekOptions">
|
||||||
|
From <input type="text" placeholder="00:00:00" value="" name="from" />
|
||||||
|
to <input type="text" placeholder="00:22:30" value="" name="to" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue